[PYTHON] Combiner l'héritage polymorphe et plusieurs-à-plusieurs avec SQLAlchemy

Contexte

Il y a une table avec une structure similaire, et j'en ai fait une structure DRY en utilisant polymorphisme = polymorphisme. A partir de chaque table, il est devenu nécessaire d'avoir une relation plusieurs-à-plusieurs avec la même table séparée.

Revue 1

SQL Alchemy peut utiliser trois types de polymorphisme en fonction de la structure de la table. Le nombre de tables en supposant qu'il existe une classe de base et deux classes dérivées est également décrit.

--Héritage de table unique Placez toutes les tables dérivées dans une table. Le nombre de tables est de 1. --Héritage de la table conjointe La table de base et la table dérivée sont séparées. Le nombre de tables est de 3. --Héritage de table en béton N'a pas de classe de base dans la table, mais a une classe dérivée dans chaque table. Le nombre de tables est de 2.

En raison de la base de données externe, etc., nous avons adopté l'héritage de table en béton cette fois.

Revue 2

SQL Alchemy peut gérer plusieurs-à-plusieurs sans connaître les tables intermédiaires. Cité de l'échantillon dans le manuel officiel. Un modèle dans lequel un article de blog a plusieurs mots-clés.

post.keywords.append(Keyword('wendy'))

Alors comment tu fais ça

Supposons que chaque formulaire avec une configuration similaire possède plusieurs balises. Il existe quatre tableaux: les balises, chaque formulaire x 2 et une table intermédiaire.


# -*- coding: utf-8 -*-
from sqlalchemy import *
from sqlalchemy.ext.declarative import *
from sqlalchemy.orm import *
from sqlalchemy_utils import *

base = declarative_base()

#La table intermédiaire n'a pas besoin d'être une classe.
assoc = Table('assoc', base.metadata,
  Column('aform_id', Integer, ForeignKey('AForm.id')),
  Column('bform_id', Integer, ForeignKey('BForm.id')),
  Column('tag_id', Integer, ForeignKey('Tag.id'))
)

class Tag(base):
  __tablename__ = 'Tag'
  id = Column(Integer, primary_key = True)
  name = Column(String)
  #Même si vous ne l'utilisez pas directement, une erreur se produira sans cette définition.
  #En spécifiant backref, le contraire[AB]La définition de relation de forme peut être omise.
  aform = relationship('AForm', secondary = assoc, backref = 'atag')
  bform = relationship('BForm', secondary = assoc, backref = 'btag')

class Form(AbstractConcreteBase, base):
  id = Column(Integer, primary_key = True)
  amount = Column(Integer)

  @declared_attr
  def __tablename__(cls):
    return cls.__name__
  
  @declared_attr
  def __mapper_args__(cls):
    return {
      'polymorphic_identity': cls.__name__,
      'concrete':True
  }

  
class AForm(Form, base):
  pass

class BForm(Form, base):
  b_only = Column(String(10))


db_uri = 'sqlite:////tmp/m2m.sqlite'
engine = create_engine(db_uri, echo =True)

if database_exists(engine.url):
  drop_database(db_uri)
  create_database(engine.url)
base.metadata.create_all(bind = engine)

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

#Puisque atag est défini pour AForm et btag est défini pour BForm, il est nécessaire de les appeler séparément.
a = AForm(amount = 100)
atag = Tag(name = 'booked')
a.atag.append(atag)
session.add(a)

f = BForm(amount = 200)
tag = Tag(name = 'canceled')
f.btag.append(tag)
session.add(f)
session.commit()

#Il est également possible de les appeler dynamiquement ci-dessous.
getattr(btag, f.__class__.__name__.lower()).append(f)

forms=session.query(AForm).all()
for f in forms:
  print(f)
  print(f.atag[0].name)

Il est vrai que vous pouvez opérer sans connaître la table intermédiaire, mais comme atag est défini pour AForm et btag est défini pour BForm, il est nécessaire de les appeler séparément. Cependant, il est possible d'utiliser getattr pour générer une référence de méthode à partir du nom de classe de l'objet et la joindre dans la chaîne de méthodes.

Republier


getattr(tag, b.__class__.__name__.lower()).append(b)

Déroutant! Il peut y avoir une opinion selon laquelle il devrait être écrit comme ceci. Cela dépendra du style de codage et du nombre de classes dérivées.

if isinstance(f, AForm):
  tag.aform.append(f)

Recommended Posts

Combiner l'héritage polymorphe et plusieurs-à-plusieurs avec SQLAlchemy
Utiliser Enum avec SQLAlchemy
Obtenez la table dynamiquement avec sqlalchemy
Utiliser DATE_FORMAT avec le filtre SQLAlchemy
Introduction à RDB avec sqlalchemy Ⅰ
Comment mettre à jour avec SQLAlchemy?
Comment modifier avec SQLAlchemy?
Group_by avec sqlalchemy et sum
Prise en charge de plusieurs sessions avec SQL Alchemy
Comment supprimer avec SQLAlchemy?