【问题标题】:SQLAlchemy - Self-referential many-to-many relationship in custom declarative base classSQLAlchemy - 自定义声明性基类中的自引用多对多关系
【发布时间】:2021-11-05 15:35:30
【问题描述】:

我正在构建一个使用 SQLAlchemy 的 Python 应用程序,并且我正在尝试在自定义声明性基类与其自身之间实现多对多关系(自引用)。但我无法让它工作。我在下面附上代码以及错误回溯,以防有人可以提供帮助:) 模型的所有实体都已经从这个基类扩展,并且应用程序到目前为止正在运行,以防万一。

谢谢!!


代码(非功能性):

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from sqlalchemy import MetaData, Table
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy import String


permissions = Table(
    'permissions', MetaData(),
    Column('origin_id', String, ForeignKey('bases.id'), primary_key=True),
    Column('target_id', String, ForeignKey('bases.id'), primary_key=True)
)


class Base:

    __tablename__ = 'bases'
    __table_args__ = {
        'mysql_engine': 'InnoDB'
    }

    id = Column(String, primary_key=True)

    targets = relationship(
        'Base',
        secondary='permissions',
        primaryjoin='Base.id == permissions.c.origin_id',
        secondaryjoin='Base.id == permissions.c.target_id',
        backref='origins'

        # Reference:
        # https://docs.sqlalchemy.org/en/14/orm/join_conditions.html#self-referential-many-to-many
    )


Base = declarative_base(cls=Base)

追溯:

    class ContactMethod(Base):
  File "/usr/local/lib/python3.8/site-packages/SQLAlchemy-1.4.23-py3.8-linux-x86_64.egg/sqlalchemy/orm/decl_api.py", line 72, in __init__
    _as_declarative(reg, cls, dict_)
  File "/usr/local/lib/python3.8/site-packages/SQLAlchemy-1.4.23-py3.8-linux-x86_64.egg/sqlalchemy/orm/decl_base.py", line 126, in _as_declarative
    return _MapperConfig.setup_mapping(registry, cls, dict_, None, {})
  File "/usr/local/lib/python3.8/site-packages/SQLAlchemy-1.4.23-py3.8-linux-x86_64.egg/sqlalchemy/orm/decl_base.py", line 177, in setup_mapping
    return cfg_cls(registry, cls_, dict_, table, mapper_kw)
  File "/usr/local/lib/python3.8/site-packages/SQLAlchemy-1.4.23-py3.8-linux-x86_64.egg/sqlalchemy/orm/decl_base.py", line 299, in __init__
    self._scan_attributes()
  File "/usr/local/lib/python3.8/site-packages/SQLAlchemy-1.4.23-py3.8-linux-x86_64.egg/sqlalchemy/orm/decl_base.py", line 511, in _scan_attributes
    raise exc.InvalidRequestError(
sqlalchemy.exc.InvalidRequestError: Mapper properties (i.e. deferred,column_property(), relationship(), etc.) must be declared as @declared_attr callables on declarative mixin classes.  For dataclass field() objects, use a lambda:

【问题讨论】:

  • 你想用这个实现什么?所有类都应该有自己的 M-N 关系吗?还是所有类之间的通用 M-N 关系?或者完全是别的什么?
  • 你能听从我回答中给出的建议吗?
  • @van 我希望能够设置模型的任意两个实体之间的关系,因此这将是所有类之间的通用 M-N 关系。到目前为止还没有工作...
  • 好吧,在这种情况下,你真的应该考虑使用Inheritance声明性 Base 的目的与在继承方面拥有基础对象不同。
  • 谢谢@van!虽然我也试过。首先通过创建一个继承自 Base 的类并合并这种关系,以便所有其他类都继承自这个新类。另外,我尝试创建一个Mixin,以便所有类首先从 Base 继承,然后从 Mixin 继承。这些都不起作用......

标签: python python-3.x sqlalchemy


【解决方案1】:

以下是错误输出建议修复的示例:


from sqlalchemy.ext.declarative import declared_attr

class Base:
    ...
    @declared_attr
    def targets(cls):
        return relationship(
            'Base',
            secondary='permissions',
            primaryjoin='Base.id == permissions.c.origin_id',
            secondaryjoin='Base.id == permissions.c.target_id',
            backref='origins'
        )
    ...

旁注:您可以使用基类上的as_declarative mixin 作为快捷方式。

参考文献

扩充基础:https://docs.sqlalchemy.org/en/13/orm/extensions/declarative/mixins.html#augmenting-the-base

declared_attr: https://docs.sqlalchemy.org/en/13/orm/extensions/declarative/api.html#sqlalchemy.ext.declarative.declared_attr

as_declarative:https://docs.sqlalchemy.org/en/13/orm/extensions/declarative/api.html#sqlalchemy.ext.declarative.as_declarative

【讨论】:

  • 我已经检查了文档。问题是每个解决方案似乎都存在一些问题,但最终都没有工作......
【解决方案2】:

查看一个有效的自包含示例:

## Imports
from sqlalchemy import Column, ForeignKey, Integer, String, Table, create_engine
from sqlalchemy.orm import Session, as_declarative, declared_attr, relationship

## Configuration
engine = create_engine("sqlite:///:memory:", echo=True)


@as_declarative()
class Base(object):
    @declared_attr
    def __tablename__(cls):
        return cls.__name__.lower()


permissions = Table(
    "permissions",
    Base.metadata,
    Column("origin_id", String, ForeignKey("model_base.id"), primary_key=True),
    Column("target_id", String, ForeignKey("model_base.id"), primary_key=True),
)


## Model definitions
class ModelBase(Base):
    __tablename__ = "model_base"
    id = Column(Integer, primary_key=True)
    type = Column(String, nullable=False)

    __mapper_args__ = {
        "polymorphic_on": type,
        "polymorphic_identity": None,
    }

    targets = relationship(
        "ModelBase",
        secondary="permissions",
        primaryjoin="model_base.c.id == permissions.c.origin_id",
        secondaryjoin="model_base.c.id == permissions.c.target_id",
        backref="origins",
        # lazy="raise",
    )


class ClassA(ModelBase):
    __tablename__ = "class_a"
    __mapper_args__ = {"polymorphic_identity": "class_a"}
    id = Column(ForeignKey("model_base.id"), primary_key=True)

    name = Column(String)


class ClassB(ModelBase):
    __tablename__ = "class_b"
    __mapper_args__ = {"polymorphic_identity": "class_b"}
    id = Column(ForeignKey("model_base.id"), primary_key=True)

    name = Column(String)
    value = Column(Integer)


## Tests
def _main():
    with Session(engine) as session:
        Base.metadata.drop_all(engine)
        Base.metadata.create_all(engine)
        print("=" * 80)

        # data
        a1, a2 = [ClassA(name="a1"), ClassA(name="a2")]
        b1, b2 = [ClassB(name="b1"), ClassB(name="b2", value=3)]
        session.add_all([a1, a2, b1, b2])
        session.flush()

        a1.targets.append(a2)
        a1.targets.append(b1)
        a1.targets.append(b2)
        print(b2.targets)
        print(b2.origins)

        session.commit()
        session.expunge_all()

        print("=" * 80)
        a1 = session.query(ClassA).first()
        print(a1)
        print(a1.origins)
        print(a1.targets)

        session.commit()


if __name__ == "__main__":
    _main()

【讨论】:

  • 成功了!!非常感谢@van !!!
  • 关于此解决方案的附加说明:假设您有两个从 ModelBase 继承的类:Person 和 Object。让我们假设一个人可以通过多对一关系拥有多个对象,并且该关系具有级联行为(如果人被删除,它的所有对象也会被删除)。似乎,当 Person 被删除(从 Persons 表或 ModelBases 表中)时,Objects 表中的所有关联行也被删除,但这些 Objects 继承的 ModelBase 行没有被删除。还没想好怎么解决。
  • 您需要将cascade="all, delete", 添加到来自。阅读更多关于Using delete cascade with many-to-many relationships
猜你喜欢
  • 2011-05-09
  • 1970-01-01
  • 2016-08-27
  • 2018-10-02
  • 1970-01-01
  • 1970-01-01
  • 2011-11-10
  • 2018-11-14
  • 2021-05-31
相关资源
最近更新 更多