SQLAlchemy 多态关系与具体继承

发布于 2025-01-04 09:47:26 字数 1983 浏览 2 评论 0原文

我正在使用 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))

在 shell 中测试它时工作正常:

>>> 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是一个抽象类,它无法映射到真实存在的表。官网文档有一个例子,但它的基类不是抽象的。现在应该如何设置与抽象模型的多态关系呢?

I am using the concrete table inheritance with SQLAlchemy. In declartive style model class, I have configured it successfully.

My code just like:

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))

It works fine while testing it in the shell:

>>> 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>]

Then I fall into trouble while setting the relationship in other models. Each entry has a foreign key post_id to join Post model, but I could not define the back reference in Post. That can't work:

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

It raised a Exception and said:

InvalidRequestError: One or more mappers failed to initialize - can't proceed with initialization of other mappers. Original exception was: Class 'models.entry.Entry' is not mapped.

Obvious the Entry is a abstract class, which couldn't be mapped to a real exist table. The document in official website has a example but its base class is not abstract. Now how should I do to set the polymorphic relationship with a abstract model?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

情仇皆在手 2025-01-11 09:47:26

我已经找到了问题的原因及其解决方案。

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

我使用的是声明式样式模型,而不是手动构建映射器,因此不应手动创建虚拟表pjoin。基类 AbstractConcreteBase 有一个方法 __delcare_last__ 可以使用 polymorphic_union 函数创建 pjoin,但会在 while 时调用事件 after_configured 触发。

Post 中的 Entry 的关系会在 Post 类生成后创建,此时事件 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)

I have found the reason of the problem and its solution.

According to the document of sqlalchemy offical website, the abstract class could be a mapped class, because the polymorphic_union function could create a virtual table.

I am using the declartive style model, not build mapper by hand, so the virtual table pjoin should not be created by hand. The base class AbstractConcreteBase has a method __delcare_last__ would create the pjoin with polymorphic_union function, but it would be called while the event after_configured triggering.

The relationship with Entry in Post would be created after the Post class be generated, in this time the event after_configured have not been triggered, so __delcare_last__ function have not created the virtual table pjoin and mapped it into Entry. So the exception "Class 'models.entry.Entry' is not mapped." will be raised.

Now, I refactor the Post model, let it create the relationship with Entry in __delcare_last__ function, then it will be success because of the triggered event and the mapped Entry.

My new implemented class like this:

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)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文