У меня есть сценарий использования, в котором я хочу применить полиморфную c ассоциативную черту к потенциально несвязанной коллекции типов. Что усложняет этот вариант использования, так это то, что некоторые из этих типов уже используют наследование общих таблиц в другом месте приложения для другой цели. В основном у меня есть тип, который имеет атрибут «источник», и мне нужен этот атрибут «источник» для сопоставления с любым количеством потенциальных источников, некоторые из которых уже используют полиморфизм по другим причинам, а некоторые из них не похожи по форме или функция, так что наследование не имеет смысла. Я хочу, чтобы ассоциация больше походила на черту, чтобы что-либо помеченное / смешанное с этой чертой было потенциальным источником для моего объекта.
Я пытался свести это к примеру, в котором я пытаюсь использовать пример прокси-сервера ассоциации в документации SA для моделирования определений классов и объявленных атрибутов. Я явно что-то упустил и действительно мог бы помочь. Я ожидаю, что последний оператор print создаст экземпляр Julius Lab.
Кто-нибудь видит ошибку в этой настройке? Большое спасибо заранее.
from sqlalchemy import Column, String, Integer, ForeignKey, create_engine
from sqlalchemy.schema import MetaData
from sqlalchemy.orm import relationship, backref, Session
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.declarative import declarative_base, declared_attr
class CustomBase:
id = Column(Integer, primary_key=True)
@declared_attr
def __tablename__(cls):
return cls.__name__.lower()
convention = {
"ix": "ix_%(column_0_label)s",
"uq": "uq_%(table_name)s_%(column_0_name)s",
"ck": "ck_%(table_name)s_%(constraint_name)s",
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
"pk": "pk_%(table_name)s",
}
metadata = MetaData(naming_convention=convention)
Base = declarative_base(cls=CustomBase, metadata=metadata)
class Animal(Base):
__tablename__ = "animal"
name = Column(String)
discriminator = Column("type", String(50))
__mapper_args__ = {
"polymorphic_identity": "animal",
"polymorphic_on": discriminator
}
class Slobber(Base):
consistency = Column(Integer)
association = relationship("SlobberAssociation", backref="slobber")
association_id = Column(Integer, ForeignKey("slobberassociation.id"))
source = association_proxy("association", "source")
class SlobberAssociation(Base):
"""Polymorphic trait that determines the source of Slobber,
be it a Lab or Spaniel or Person"""
discriminator = Column(String(50), nullable=False)
__mapper_args__ = {"polymorphic_on": discriminator}
def __init__(self, instance):
self.discriminator = instance.__class__.__name__.lower()
class IsSlobberSource:
@declared_attr
def slobberassociation_id(cls):
return Column(Integer, ForeignKey("slobberassociation.id"))
@declared_attr
def slobberassociation(cls):
name = cls.__name__
discriminator = name.lower()
assoc_cls = type(
f"{name}Association",
(SlobberAssociation,),
dict(
__tablename__=None,
__mapper_args__={"polymorphic_identity": discriminator},
),
)
cls.slobbers = association_proxy(
"slobberassociation",
"slobbers",
creator=lambda slobbers: assoc_cls(slobbers=slobbers),
)
return relationship(assoc_cls, backref=backref("source", uselist=False))
class Person(IsSlobberSource, Base):
name = Column(String())
class Lab(IsSlobberSource, Animal):
__table_args__ = {"extend_existing": True}
__mapper_args__ = {"polymorphic_identity": "lab"}
class Spaniel(IsSlobberSource, Animal):
__table_args__ = {"extend_existing": True}
__mapper_args__ = {"polymorphic_identity": "spaniel"}
class Cat(Animal):
__table_args__ = {"extend_existing": True}
__mapper_args__ = {"polymorphic_identity": "cat"}
engine = create_engine('sqlite://', echo=True)
Base.metadata.create_all(engine)
session = Session(engine)
julius = Lab(name='Julius')
poppy = Lab(name='Poppy')
lucas = Lab(name='Lucas')
wyatt = Spaniel(name='Wyatt')
holmes = Cat(name="Holmes")
session.add_all([julius, poppy, lucas, wyatt, holmes])
session.commit()
slobber_ball = Slobber(consistency=8)
slobber_ball.source = julius
session.add(slobber_ball)
session.commit()
s = session.query(Slobber).one()
print(s.source)