SQLAlchemy 多态 many-to-many 与声明性语法中抽象基的关系

SQLAlchemy polymorphic many-to-many relation with abstract base in declarative syntax

我正在创建一个具有 many-to-many 关系的 SQLAlchemy 声明性模型,其中一个 table 是具体的,另一个是具有抽象基础的多态。

用例: 我正在发送包含多个组件(组件 1、组件 2)的消息,每个组件都有非常不同的属性集(因此每个组件都有自己的数据库 table).一个组件可以在多个不同的消息中发送。

我想在消息 class 和称为组件的抽象 parent class 之间建立 M:N 关系 - 但 数据库应该不包含任何摘要 "component" table,只有 table 用于具体的 children("component1"、"component2" 等) .

我试图将 abstract 1:N relation using AbstractConcreteBase (see the last/3rd code snippet in this chapter) with regular M:N relation 合并到以下代码中,但未能找到抽象基 class 的 table 的名称:

from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base, AbstractConcreteBase

BaseModel = declarative_base()

association = Table("message_component_association", BaseModel.metadata,
    Column("message_id", Integer, ForeignKey("message.id")),
    Column("component_id", Integer, ForeignKey("component.id")))  # Fails to reference the Component class

class Message(BaseModel):
    __tablename__ = "message"
    id = Column(Integer, primary_key=True)
    components = relationship("Component", secondary=association, back_populates="messages")

class Component(AbstractConcreteBase, BaseModel):
    __mapper_args__ = dict(polymorphic_identity="component", concrete=False)  # This seems to be ignored

class Component1(Component):
    __tablename__ = "component1"
    __mapper_args__ = dict(polymorphic_identity="component1", concrete=True)
    id = Column(Integer, primary_key=True)
    messages = relationship("Message", secondary=association, back_populates="components")

engine = create_engine("sqlite://")
BaseModel.metadata.create_all(engine)

例外说:

sqlalchemy.exc.NoReferencedTableError: Foreign key associated with column 'message_component_association.component_id' could not find table 'component' with which to generate a foreign key to target column 'id'

为什么组件class的mapper_args被忽略,无法通过提供的polymorphic_identity找到class?

编辑: 我意识到我可以使用 Joined Table 继承(grr,我不能 post 超过 2 个链接)来代替声明helper mixin 通过显式鉴别器获得多态 M:N 关系 - 但是,base class 仍然需要自己的数据库 table.

from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base

BaseModel = declarative_base()

association = Table("message_component_association", BaseModel.metadata,
Column("message_id", Integer, ForeignKey("message.id")),
Column("component_id", Integer, ForeignKey("component.id")))

class Message(BaseModel):
    __tablename__ = "message"
    id = Column(Integer, primary_key=True)
    components = relationship("Component", secondary=association, back_populates="messages")

class Component(BaseModel):  # Declarative mixin removed
    __tablename__ = "component"  # Requires a real DB table despite being abstract
    __mapper_args__ = dict(polymorphic_identity="component", polymorphic_on="type")  # Apply the discriminator
    id = Column(Integer, primary_key=True)
    type = Column(String(32))  # Explicit discriminator
    messages = relationship("Message", secondary=association, back_populates="components")

class Component1(Component):
    __tablename__ = "component1"
    __mapper_args__ = dict(polymorphic_identity="component1")
    id = Column(Integer, ForeignKey("component.id"), primary_key=True)  # Shares the primary key sequence with the parent and with all other child classes
    messages = relationship("Message", secondary=association, back_populates="components")

engine = create_engine("sqlite://", echo=True)
BaseModel.metadata.create_all(engine)
session = Session(engine)

component_1 = Component1(id=1)
session.commit()

到目前为止代码似乎可以正常工作,但它抱怨刷新的问题。只要我不手动写入 "components table" 就可以忽略警告是否安全——或者有更好的方法吗?

SAWarning: Warning: relationship 'messages' on mapper 'Mapper|Component1|component1' supersedes the same relationship on inherited mapper 'Mapper|Component|component'; this can cause dependency issues during flush

解决方案: 删除除消息 class 之外的所有关系,并将 back_populates 替换为 backref。 Backref 将动态创建相反的方向,映射器将看不到被覆盖的关系。此外,抽象祖先上的 polymorphic_identity 不是必需的。

from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base

BaseModel = declarative_base()

association = Table("message_component_association", BaseModel.metadata,
Column("message_id", Integer, ForeignKey("message.id")),
Column("component_id", Integer, ForeignKey("component.id")))

class Message(BaseModel):
    __tablename__ = "message"
    id = Column(Integer, primary_key=True)
    components = relationship("Component", secondary=association, backref="messages")  # backref instead of back_populates

class Component(BaseModel):
    __tablename__ = "component"
    __mapper_args__ = dict(polymorphic_on="type")  # Polymorphic identity removed
    id = Column(Integer, primary_key=True)
    type = Column(String(32))

    # relationship removed

class Component1(Component):
    __tablename__ = "component1"
    __mapper_args__ = dict(polymorphic_identity="component1")
    id = Column(Integer, ForeignKey("component.id"), primary_key=True)

    # relationship removed

engine = create_engine("sqlite://", echo=True)
BaseModel.metadata.create_all(engine)
session = Session(engine)

component_1 = Component1(id=1)
session.commit()