SQLAlchemy具体继承的多态关系

6 投票
1 回答
3910 浏览
提问于 2025-04-17 12:44

我正在使用SQLAlchemy的具体表继承功能。在声明式风格的模型类中,我已经成功配置了它。

我的代码大致是这样的:

class Entry(AbstractConcreteBase, db.Model):
    """Base Class of Entry."""

    id = db.Column(db.Integer, primary_key=True, nullable=False)
    created = db.Column(db.DateTime, nullable=False)
    post_id = declared_attr(lambda c: db.Column(db.ForeignKey("post.id")))
    post = declared_attr(lambda c: db.relationship("Post", lazy="joined"))

    @declared_attr
    def __tablename__(cls):
        return cls.__name__.lower()

    @declared_attr
    def __mapper_args__(cls):
        # configurate subclasses about concrete table inheritance
        return {'polymorphic_identity': cls.__name__,
                'concrete': True} if cls.__name__ != "Entry" else {}

class TextEntry(Entry):
    """Text and Article Entry."""

    text = db.deferred(db.Column(db.Text, nullable=False))

class PhotoEntry(Entry):
    """Photo Entry."""

    path = db.deferred(db.Column(db.String(256), nullable=False))

在测试时,它在命令行中运行得很好:

>>> from models.entry import Entry
>>>
>>> Entry.query.all()
[<PhotoEntry 'Title' created by tonyseek>,
 <PhotoEntry 'TITLE 2' created by tonyseek>,
 <PhotoEntry 'Title 3' created by tonyseek>,
 <PhotoEntry 'Title 4' created by tonyseek>,
 <TextEntry 'Title' created by tonyseek>]

但是在设置其他模型之间的关系时,我遇到了麻烦。每个条目都有一个外键post_id,用来连接Post模型,但我无法在Post中定义反向引用。这段代码无法正常工作:

class Post(db.Model):
    """An Post."""

    id = db.Column(db.Integer, primary_key=True, nullable=False)
    description = db.Column(db.Unicode(140), nullable=False)
    entries = db.relationship(Entry, lazy="dynamic")

它抛出了一个异常,提示:

InvalidRequestError: 一个或多个映射器初始化失败 - 无法继续初始化其他映射器。原始异常是:类'models.entry.Entry'没有被映射。

显然,Entry是一个抽象类,无法映射到一个真实存在的表。官方网站的文档中有一个例子,但它的基类不是抽象的。现在我该如何设置与抽象模型的多态关系呢?

1 个回答

10

我找到了问题的原因和解决办法。

根据sqlalchemy官方网站的文档,抽象类可以是一个映射类,因为polymorphic_union函数可以创建一个虚拟表。

我使用的是声明式风格的模型,而不是手动构建映射器,所以虚拟表pjoin不需要手动创建。基类AbstractConcreteBase有一个方法__delcare_last__,这个方法会通过polymorphic_union函数来创建pjoin,但它是在事件after_configured触发时被调用的。

Post类生成后,和Entry的关系会被创建,这时候事件after_configured还没有触发,所以__delcare_last__函数没有创建虚拟表pjoin并将其映射到Entry。因此,会出现“类 'models.entry.Entry' 没有被映射。”的异常。

现在,我重构了Post模型,让它在__delcare_last__函数中创建和Entry的关系,这样就能成功了,因为事件被触发了,Entry也被映射了。

我新实现的类如下:

class Post(db.Model):
    """An Post."""

    id = db.Column(db.Integer, primary_key=True, nullable=False)
    description = db.Column(db.Unicode(140), nullable=False)

    @classmethod
    def __declare_last__(cls):
        cls.entries = db.relationship(Entry, viewonly=True)

    def attach_entries(self, entries):
        """Attach Entries To This Post.

        Example:
            >>> post = Post("An Interesting News", "Wow !!!")
            >>> text_entry = TextEntry(*t_args)
            >>> photo_entry = PhotoEntry(*p_args)
            >>> post.attach_entries([text_entry, photo_entry])
            >>> len(post.entries)
            2
            >>> db.session.commit()
            >>>
        """
        for entry in entries:
            self.entries.append(entry)
            entry.post = self
            db.session.add(entry)

撰写回答