我正在创建一个具有多对多关系的SQLAlchemy声明模型,其中一个表是具体的,另一个表是具有抽象基础的多态。
用例:我发送的邮件包含多个组件(component1,component2),每个组件都有非常不同的属性集(因此每个组件都有自己的数据库表)。组件可以通过多种不同的消息发送。
我想在Message类和一个名为Component的抽象父类之间建立一个M:N关系 - 但是数据库不应该包含任何抽象的"组件"表,只有具体子项的表(" component1"," component2"等。)。
我尝试将abstract 1:N relation using AbstractConcreteBase(请参阅本章中的最后一个/第三个代码段)与regular M:N relation合并到以下代码中,该代码无法找到抽象基类的表名:
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'
为什么忽略Component类的 mapper_args ,并且无法通过提供的polymorphic_identity找到该类?
编辑:我已经意识到我可以使用连接表继承(grr,我不能发布超过2个链接),它通过显式鉴别器替换声明性帮助器mixin来获取多态M: N关系 - 但仍然,基类需要自己的数据库表。
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()
代码似乎到目前为止工作但抱怨flush 问题。只要我不手动写入"组件表"是否可以安全地忽略警告。 - 还是有更好的方法?
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
答案 0 :(得分:1)
解决方案:删除除Message类之外的所有关系,并将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()