SQLAlchemy lanza KeyError cuando se usan objetos de asociación con back_populations – el ejemplo de la documentación no funciona

SQLAlchemy documenta muy bien cómo utilizar los objetos de asociación con back_populates .

Sin embargo, al copiar y pegar el ejemplo de esa documentación, agregar hijos a un padre lanza un KeyError como se muestra en el siguiente código. Las clases modelo se copian 100% de la documentación:

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

Al ejecutar ese código con SQLAlchemy versión 1.2.11 se produce esta excepción:

 lars$ venv/bin/python test.py Traceback (most recent call last): File "test.py", line 26, in  parent = Parent(children=[Child()]) File "", 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' 

He archivado esto como un error en el rastreador de problemas de SQLAlchemy . ¿Tal vez alguien pueda indicarme una solución funcional o una solución alternativa mientras tanto?

Related of "SQLAlchemy lanza KeyError cuando se usan objetos de asociación con back_populations – el ejemplo de la documentación no funciona"

tldr; Tenemos que usar las extensiones Proxy de asociación y crear un constructor personalizado para el objeto de asociación que toma el objeto secundario como el primer parámetro (!). Ver solución basada en el ejemplo de la pregunta a continuación.

La documentación de SQLAlchemy en realidad establece en el siguiente párrafo que aún no hemos terminado si queremos agregar directamente los modelos de Child modelos de Parent mientras se saltan los modelos de la Association intermediaria:

Trabajar con el patrón de asociación en su forma directa requiere que los objetos secundarios estén asociados con una instancia de asociación antes de ser agregados al padre; De manera similar, el acceso de padre a hijo pasa por el objeto de asociación.

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

Para escribir un código conveniente, tal como se solicita en la pregunta, es decir, p.children = [Child()] , tenemos que hacer uso de la extensión Association Proxy .

Aquí está la solución que usa una extensión de Proxy de asociación que permite agregar hijos a un padre “directamente” sin crear explícitamente una asociación entre ambos:

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

Desafortunadamente, solo descubrí cómo utilizar backref lugar de back_populates que no es el enfoque “moderno”.

Preste especial atención para crear un método __init__ personalizado que tome al niño como el primer argumento.