SQLAlchemy 在使用带有 back_populates 的关联对象时抛出 KeyError – 文档中的示例不起作用

Lar*_*erg 4 python sqlalchemy exception keyerror

SQLAlchemy 很好地记录了如何将关联对象与back_populates.

但是,当从该文档复制并粘贴示例时,将子项添加到父项会抛出 a ,KeyError如以下代码所示。模型类 100% 从文档中复制:

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

Base = declarative_base(metadata=MetaData())

class Association(Base):
    __tablename__ = 'association'
    left_id = Column(Integer, ForeignKey('left.id'), primary_key=True)
    right_id = Column(Integer, ForeignKey('right.id'), primary_key=True)
    extra_data = Column(String(50))
    child = relationship("Child", back_populates="parents")
    parent = relationship("Parent", back_populates="children")

class Parent(Base):
    __tablename__ = 'left'
    id = Column(Integer, primary_key=True)
    children = relationship("Association", back_populates="parent")

class Child(Base):
    __tablename__ = 'right'
    id = Column(Integer, primary_key=True)
    parents = relationship("Association", back_populates="child")

parent = Parent(children=[Child()])
Run Code Online (Sandbox Code Playgroud)

使用 SQLAlchemy 版本 1.2.11 运行该代码会引发以下异常:

lars$ venv/bin/python test.py
Traceback (most recent call last):
  File "test.py", line 26, in <module>
    parent = Parent(children=[Child()])
  File "<string>", line 4, in __init__
  File "/Users/lars/coding/sqlalchemy_association_object_test/venv/lib/python3.7/site-packages/sqlalchemy/orm/state.py", line 417, in _initialize_instance
    manager.dispatch.init_failure(self, args, kwargs)
  File "/Users/lars/coding/sqlalchemy_association_object_test/venv/lib/python3.7/site-packages/sqlalchemy/util/langhelpers.py", line 66, in __exit__
    compat.reraise(exc_type, exc_value, exc_tb)
  File "/Users/lars/coding/sqlalchemy_association_object_test/venv/lib/python3.7/site-packages/sqlalchemy/util/compat.py", line 249, in reraise
    raise value
  File "/Users/lars/coding/sqlalchemy_association_object_test/venv/lib/python3.7/site-packages/sqlalchemy/orm/state.py", line 414, in _initialize_instance
    return manager.original_init(*mixed[1:], **kwargs)
  File "/Users/lars/coding/sqlalchemy_association_object_test/venv/lib/python3.7/site-packages/sqlalchemy/ext/declarative/base.py", line 737, in _declarative_constructor
    setattr(self, k, kwargs[k])
  File "/Users/lars/coding/sqlalchemy_association_object_test/venv/lib/python3.7/site-packages/sqlalchemy/orm/attributes.py", line 229, in __set__
    instance_dict(instance), value, None)
  File "/Users/lars/coding/sqlalchemy_association_object_test/venv/lib/python3.7/site-packages/sqlalchemy/orm/attributes.py", line 1077, in set
    initiator=evt)
  File "/Users/lars/coding/sqlalchemy_association_object_test/venv/lib/python3.7/site-packages/sqlalchemy/orm/collections.py", line 762, in bulk_replace
    appender(member, _sa_initiator=initiator)
  File "/Users/lars/coding/sqlalchemy_association_object_test/venv/lib/python3.7/site-packages/sqlalchemy/orm/collections.py", line 1044, in append
    item = __set(self, item, _sa_initiator)
  File "/Users/lars/coding/sqlalchemy_association_object_test/venv/lib/python3.7/site-packages/sqlalchemy/orm/collections.py", line 1016, in __set
    item = executor.fire_append_event(item, _sa_initiator)
  File "/Users/lars/coding/sqlalchemy_association_object_test/venv/lib/python3.7/site-packages/sqlalchemy/orm/collections.py", line 680, in fire_append_event
    item, initiator)
  File "/Users/lars/coding/sqlalchemy_association_object_test/venv/lib/python3.7/site-packages/sqlalchemy/orm/attributes.py", line 943, in fire_append_event
    state, value, initiator or self._append_token)
  File "/Users/lars/coding/sqlalchemy_association_object_test/venv/lib/python3.7/site-packages/sqlalchemy/orm/attributes.py", line 1210, in emit_backref_from_collection_append_event
    child_impl = child_state.manager[key].impl
KeyError: 'parent'
Run Code Online (Sandbox Code Playgroud)

我已将此作为SQLAlchemy 问题跟踪器中的错误提交。也许有人可以同时向我指出一个可行的解决方案或解决方法?

Lar*_*erg 5

太棒了;我们必须使用关联代理扩展为关联对象创建一个自定义构造函数,该构造函数将子对象作为第一个 (!) 参数。请参阅基于以下问题示例的解决方案。

SQLAlchemy 的文档实际上在下一段中指出,如果我们想直接将Child模型添加到Parent模型而跳过中间Association模型,那么我们还没有完成:

以直接形式使用关联模式要求子对象在附加到父对象之前与关联实例相关联;类似地,从父级到子级的访问也通过关联对象。

# create parent, append a child via association
p = Parent()
a = Association(extra_data="some data")
a.child = Child()
p.children.append(a)
Run Code Online (Sandbox Code Playgroud)

要编写问题中所要求的方便代码,即p.children = [Child()],我们必须使用关联代理扩展

这是使用关联代理扩展的解决方案,它允许“直接”将子级添加到父级,而无需在它们之间显式创建关联:

from sqlalchemy import Column, ForeignKey, Integer, String
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import backref, relationship
from sqlalchemy.schema import MetaData

Base = declarative_base(metadata=MetaData())

class Association(Base):
    __tablename__ = 'association'
    left_id = Column(Integer, ForeignKey('left.id'), primary_key=True)
    right_id = Column(Integer, ForeignKey('right.id'), primary_key=True)
    extra_data = Column(String(50))
    child = relationship("Child", back_populates="parents")
    parent = relationship("Parent", backref=backref("parent_children"))

    def __init__(self, child=None, parent=None):
        self.parent = parent
        self.child = child

class Parent(Base):
    __tablename__ = 'left'
    id = Column(Integer, primary_key=True)
    children = association_proxy("parent_children", "child")

class Child(Base):
    __tablename__ = 'right'
    id = Column(Integer, primary_key=True)
    parents = relationship("Association", back_populates="child")

p = Parent(children=[Child()])
Run Code Online (Sandbox Code Playgroud)

不幸的是我只知道如何使用backref而不是back_populates“现代”方法。

请特别注意创建一个__init__将子级作为第一个参数的自定义方法。