Ce à quoi j'étais accro en combinant l'héritage de classe et l'héritage de table commune dans SQLAlchemy

@declared_attr a un effet jusqu'à la table dérivée

supposition

--Il existe deux tables (= modèles) que vous souhaitez configurer avec l'héritage de table conjointe (employé, gestionnaire, ingénieur). --Omoto (Employee) a également des colonnes en commun avec d'autres tables, et je souhaite les définir dans DRY en incluant l'index (Human). --Par conséquent, la configuration est Human-> Employee-> (Manager, Engineer).

J'étais accro à

Si vous définissez un index pour la classe Human avec le décorateur @declared_attr, l'index ira à la classe inférieure.

joint_ng.py


from sqlalchemy.ext.declarative import *
from sqlalchemy import *
from sqlalchemy_utils import database_exists, drop_database, create_database
from sqlalchemy.orm import sessionmaker

Base = declarative_base()

class Human(object):
    age = Column(Integer)
    name = Column(String(50))

    @declared_attr
    def __table_args__(cls):
        return(Index('index_name', 'name'),)

class Employee(Human, Base):
    __tablename__ = 'employee'
    id = Column(Integer, primary_key=True)
    type = Column(String(50))

    __mapper_args__ = {
        'polymorphic_identity':'employee',
        'polymorphic_on':type
    }

class Engineer(Employee):
    __tablename__ = 'engineer'
    id = Column(Integer, ForeignKey('employee.id'), primary_key=True)
    engineer_name = Column(String(30))

    __mapper_args__ = {
        'polymorphic_identity':'engineer',
    }
    __table_args__ = {
        
    }

class Manager(Employee):
    __tablename__ = 'manager'
    id = Column(Integer, ForeignKey('employee.id'), primary_key=True)
    manager_name = Column(String(30))

    __mapper_args__ = {
        'polymorphic_identity':'manager',
    }
    __table_args__ = {
        
    }

engine = create_engine('mysql://user:pass@localhost/dummy?charset=utf8')

if database_exists(engine.url):
    drop_database(engine.url)
create_database(engine.url)
Base.metadata.create_all(engine)

Session = sessionmaker(bind=engine)
session = Session()

m = Manager(age=10, name = 'foo', manager_name='hoge')
session.add(m)
session.commit()

Une erreur se produit lors de la tentative d'indexation de la colonne de nom dans une table Manager qui ne définit pas de colonne de nom.

Traceback (most recent call last):
  File "joint.py", line 27, in <module>
    class Engineer(Employee):
(réduction…)
  File "/home/satosi/.pyenv/versions/3.6.1/lib/python3.5/site-packages/sqlalchemy/util/_collections.py", line 194, in __getitem__
    return self._data[key]
KeyError: 'name'

Cela semble évident, mais je n'ai pas pu avaler la situation à partir du message d'erreur, et il a fallu du temps pour en préciser la cause, alors je vais le publier.

Contre-mesures

Redéfinissez simplement table_args dans la classe Manager.

joint_ok.py


class Engineer(Employee):
    __tablename__ = 'engineer'
    id = Column(Integer, ForeignKey('employee.id'), primary_key=True)
    engineer_name = Column(String(30))

    __mapper_args__ = {
        'polymorphic_identity':'engineer',
    }
    __table_args__ = {
        
    }

class Manager(Employee):
    __tablename__ = 'manager'
    id = Column(Integer, ForeignKey('employee.id'), primary_key=True)
    manager_name = Column(String(30))

    __mapper_args__ = {
        'polymorphic_identity':'manager',
    }
    __table_args__ = {
        
    }

L'héritage de polymorphic_identity est inattendu

supposition

Je veux écrire la définition de la table et la logique du modèle séparément.

J'étais accro à

J'ai essayé d'hériter dans l'ordre déclarative_base-> Classe qui spécifie le membre de la colonne (Manager) -> Classe de la couche d'application (SubManager), mais le type qui est la clé du polymorphisme n'est pas spécifié.

joint_ng2.py


import sys
from sqlalchemy.ext.declarative import *
from sqlalchemy import *
from sqlalchemy_utils import database_exists, drop_database, create_database
from sqlalchemy.orm import sessionmaker

Base = declarative_base()

class Employee(Base):
    __tablename__ = 'employee'
    id = Column(Integer, primary_key=True)
    type = Column(String(50))

    __mapper_args__ = {
        'polymorphic_identity':'employee',
        'polymorphic_on':type
    }

class Manager(Employee):
    __tablename__ = 'manager'
    id = Column(Integer, ForeignKey('employee.id'), primary_key=True)
    manager_name = Column(String(30))
    
    __mapper_args__ = {
        'polymorphic_identity':'manager',
    }

    __table_args__ = {        
    }

    def shout(self):
        print('Oh')

class SubManager(Manager):
    def shout(self):
        print('Wah')

engine = create_engine('mysql://user:pass@localhost/dummy?charset=utf8')

if database_exists(engine.url):
    drop_database(engine.url)
create_database(engine.url)
Base.metadata.create_all(engine)

Session = sessionmaker(bind=engine)
session = Session()

m = SubManager(manager_name='hoge')
m.shout()
session.add(m)
session.commit()
query = session.query(Manager)
print(query)
manager = query.first()
assert manager.type is None
manager.shout()

production


Wah
SELECT manager.id AS manager_id, employee.id AS employee_id, employee.type AS employee_type, manager.manager_name AS manager_manager_name 
FROM employee INNER JOIN manager ON employee.id = manager.id
Oh

Essayez donc de spécifier @declared_attr.

joint_ng2.py


class Manager(Employee):
    (snip...)
    @declared_attr
    def __mapper_args__(cls):
       return {
        'polymorphic_identity':'manager',
       }

Cela a fonctionné, mais on m'a prévenu que c'était une double définition.

production


/home/satosi/.pyenv/versions/3.6.1-mtxweb/lib/python3.5/site-packages/sqlalchemy/orm/mapper.py:1034: SAWarning: Reassigning polymorphic association for identity 'manager' from <Mapper at 0x7fb0202ed978; Manager> to <Mapper at 0x7fb0202edbe0; SubManager>: Check for duplicate use of 'manager' as value for polymorphic_identity.
  self, self.polymorphic_identity)
Wah
SELECT manager.id AS manager_id, employee.id AS employee_id, employee.type AS employee_type, manager.manager_name AS manager_manager_name 
FROM employee INNER JOIN manager ON employee.id = manager.id
Wah

Contre-mesures

Changez simplement l'ordre d'héritage. Tant qu'il existe une correspondance biunivoque entre la couche d'application et le type dérivé d'héritage de table conjointe, il ne doit y avoir aucune différence de fonctionnalité.

joint_ok.py


from sqlalchemy.ext.declarative import *
from sqlalchemy import *
from sqlalchemy_utils import database_exists, drop_database, create_database
from sqlalchemy.orm import sessionmaker

Base = declarative_base()

class _Manager():
    def shout(self):
        print('Oh')

class Employee(Base):
    __tablename__ = 'employee'
    id = Column(Integer, primary_key=True)
    type = Column(String(50))

    __mapper_args__ = {
        'polymorphic_identity':'employee',
        'polymorphic_on':type
    }

class Manager(Employee, _Manager):
    __tablename__ = 'manager'
    id = Column(Integer, ForeignKey('employee.id'), primary_key=True)
    manager_name = Column(String(30))
    
    __mapper_args__ ={
    'polymorphic_identity':'manager',
    }

    __table_args__ = {        
    }

    def shout(self):
        print('Wah')

engine = create_engine('mysql://user:pass@localhost/dummy?charset=utf8')

if database_exists(engine.url):
    drop_database(engine.url)
create_database(engine.url)
Base.metadata.create_all(engine)

Session = sessionmaker(bind=engine)
session = Session()

m = Manager(manager_name='hoge')
m.shout()
session.add(m)
session.commit()
query = session.query(Manager)
print(query)
manager = query.first()
assert manager.type is not None
manager.shout()

production


Wah
SELECT manager.id AS manager_id, employee.id AS employee_id, employee.type AS employee_type, manager.manager_name AS manager_manager_name 
FROM employee INNER JOIN manager ON employee.id = manager.id
Wah

C'était pauvre. Ce serait une joie inattendue si cela aide quelqu'un.

Recommended Posts

Ce à quoi j'étais accro en combinant l'héritage de classe et l'héritage de table commune dans SQLAlchemy
Une note à laquelle j'étais accro lors de la création d'une table avec SQL Alchemy
J'étais accro aux variables de classe et aux variables d'instance erronées en Python
Ce à quoi j'étais accro lors de l'utilisation de Python tornado
Ce à quoi j'étais accro lors de la création d'applications Web dans un environnement Windows
Ce à quoi j'étais accro lorsque l'utilisateur de traitement est passé à Python
Quand j'ai essayé d'installer PIL et matplotlib dans un environnement virtualenv, j'en étais accro.
Ce à quoi j'étais accro en traitant d'énormes fichiers dans un environnement Linux 32 bits
Ce à quoi j'étais accro en présentant ALE à Vim pour Python
Ce que j'étais accro à Python autorun
Le nom du fichier était mauvais en Python et j'étais accro à l'importation
Trois choses auxquelles j'étais accro lors de l'utilisation de Python et MySQL avec Docker
J'étais accro au grattage avec Selenium (+ Python) en 2020
J'étais accro à essayer logging.getLogger avec Flask 1.1.x
Ce que j'ai fait quand j'étais en colère de le mettre avec l'option enable-shared
Qu'est-ce qui a été demandé lors de l'utilisation de Random Forest dans la pratique
Une histoire à laquelle j'étais accro en spécifiant nil comme argument de fonction dans Go
Quand j'ai essayé de gratter en utilisant des requêtes en python, j'étais accro à SSLError, donc un mémo de contournement
Le record auquel j'étais accro en mettant MeCab dans Heroku
Une note à laquelle j'étais accro lors de l'émission d'un bip sous Linux
Ce à quoi j'étais accro lorsque j'ai construit mon propre réseau de neurones en utilisant les poids et les biais que j'ai obtenus avec le classificateur MLP de scikit-learn.
J'étais accro au multitraitement + psycopg2
Lorsque j'ai mis Django dans mon répertoire personnel, je suis devenu accro à un fichier statique avec une erreur d'autorisation
L'intelligence de Numpy (saisie complète) est incomplète dans VS Code et j'étais légèrement accro à la solution
Ce à quoi j'ai fait référence en étudiant tkinter
J'étais accro à pip installer mysqlclient
J'étais accro à Flask sur dotCloud
Docker x Visualization ne fonctionnait pas et j'en étais accro, alors je l'ai résumé!
Une note à laquelle j'étais accro lors de l'exécution de Python avec Visual Studio Code
Une histoire à laquelle j'étais accro après la communication SFTP avec python
Notez que j'étais accro à la configuration de TensowFlow
[Introduction à json] Non, j'en étais accro. .. .. ♬
J'ai écrit une classe en Python3 et Java
Ce que j'ai fait lors de la mise à jour de Python 2.6 vers 2.7
[Question] Que se passe-t-il si vous utilisez% en python?
Notez que j'étais accro au script npm ne passant pas dans l'environnement de vérification
Que faire lorsque seule la fenêtre est affichée et que rien ne s'affiche dans le pygame
Après avoir implémenté l'application Watson IoT Platform avec Flask, j'étais accro à la connexion MQTT
Remarques sur l'ajout de setter et deleter dans une classe dérivée à la propriété getter de la classe de base
Fonctionnement pratique du clavier Linux que je veux apprendre moi-même quand j'étais à l'école
[Go language] Soyez prudent lors de la création d'un serveur avec mux + cors + alice. Surtout à propos de ce à quoi j'étais accro autour de CORS.
Que faire lorsque l'imitation est intégrée à Python
[openpyxl] Que faire lorsque IllegalCharacterError apparaît dans pandas.DataFrame.to_excel
pickle Pour lire ce qui a été fait en 2 séries avec 3 séries
[Spark] Une histoire sur le fait d'être accro aux pièges de "", null et [] dans DataFrame
Une histoire à laquelle j'étais accro chez np.where
Python: peut être répété en lambda
Je veux faire quelque chose avec Python à la fin
J'ai été surpris de recevoir une belle critique lorsque j'ai écrit Python à CheckIO et son explication
Mémo (mars 2020) auquel j'étais accro lors de l'installation d'Arch Linux sur MacBook Air 11'Early 2015
Ce à quoi j'étais accro dans Collective Intelligence Chaprter 3. Ce n'est pas une faute de frappe, donc je pense que quelque chose ne va pas avec mon code.