SQLAlchemy throwing KeyError when using Association Objects with back_populates – example from documentation doesn't work

Deadly 提交于 2019-12-02 09:35:24

tldr; We have to use Association Proxy extensions and create a custom constructor for the association object which takes the child object as the first (!) parameter. See solution based on the example from the question below.

SQLAlchemy's documentation actually states in the next paragraph that we aren't done yet if we want to directly add Child models to Parent models while skipping the intermediary Association models:

Working with the association pattern in its direct form requires that child objects are associated with an association instance before being appended to the parent; similarly, access from parent to child goes through the association object.

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

To write convient code such as requested in the question, i.e. p.children = [Child()], we have to make use of the Association Proxy extension.

Here is the solution using an Association Proxy extension which allows to add children to a parent "directly" without explicitly creating an association between both of them:

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

Unfortunately I only figured out how to use backref instead of back_populates which isn't the "modern" approach.

Pay special attention to create a custom __init__ method which takes the child as the first argument.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!