【问题标题】:SQLAlchemy: multi-branch polymorphism and relationships through association tablesSQLAlchemy:通过关联表的多分支多态性和关系
【发布时间】:2020-02-05 22:42:11
【问题描述】:

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

我试图通过一个示例来归结这一点,在该示例中我尝试使用 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】:

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

    欢迎任何 cmets/其他答案!

    参考以下 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"}
    
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-11-22
      • 1970-01-01
      • 2015-03-16
      • 1970-01-01
      • 2013-03-03
      • 2015-08-05
      • 2011-11-10
      相关资源
      最近更新 更多