SQLAlchemy:多分支多态性和通过关联表的关系

问题描述 投票:0回答:1

我有一个用例,我想将多态关联特征应用于可能不相关的类型集合。使该用例复杂化的是,其中一些类型已经在应用程序的其他地方使用共享表继承来实现其他目的。基本上,我有一个具有“源”属性的类型,我需要该“源”属性映射到任意数量的潜在源,其中一些已出于其他原因已经使用了多态,而某些在形式上并不相似或函数,因此继承没有意义。我希望该关联更像一个特征,任何带有此特征标记/混合的特征都是我对象的潜在来源。

我试图用一个示例来简化这一点,在该示例中,我尝试使用SA文档中的关联代理示例来对类定义和声明的attrs进行建模。我显然缺少一些东西,可以真正使用一些帮助。我希望最后的打印语句能够产生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)
python sqlalchemy polymorphic-associations
1个回答
0
投票

所以这似乎可行,但是缺点是我无法直接查询最通用的“ Slobber”类型的源,source属性仅在由C生成的派生类Lab.SlobberPerson.Slobber等上可用。 slobbers声明的属性。这种方法至少确实强制执行外键关系,这就是为什么我们最终拥有这么多不同的关联表的原因。另一方面,我开始怀疑这是否是一个值得权衡的选择,而不是仅使用带有说明符列的单个关联表来处理,并让应用程序逻辑处理必要的约束。

欢迎任何评论/其他答案!

这通过参考以下SA示例得到了帮助:https://docs.sqlalchemy.org/en/13/_modules/examples/generic_associations/table_per_related.html


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)


class IsSlobberSource:
    slobber_assoc_tables = {}

    @declared_attr
    def slobbers(cls):
        tablename = cls.__tablename__
        table_key = f"{tablename}_slobbers"
        cls.Slobber = IsSlobberSource.slobber_assoc_tables.get(table_key, None)
        if cls.Slobber is None:
            cls.Slobber = type(
                f"{cls.__name__}Slobber",
                (Slobber, Base, ),
                dict(__tablename__=f"{tablename}_slobber",
                    source_id=Column(Integer, ForeignKey(f"{tablename}.id")),
                    source=relationship(cls),
                    slobber_id=Column(Integer, ForeignKey("slobber.id"), primary_key=True),
                ),
            )
            IsSlobberSource.slobber_assoc_tables[table_key] = cls.Slobber
        return relationship(cls.Slobber)

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"}

© www.soinside.com 2019 - 2024. All rights reserved.