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

前端 未结 2 551
日久生厌
日久生厌 2021-01-16 03:32

SQLAlchemy nicely documents how to use Association Objects with back_populates.

However, when copy-and-pasting the example from that documentation, adding children t

相关标签:
2条回答
  • 2021-01-16 03:53

    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.

    0 讨论(0)
  • 2021-01-16 04:06

    So to make a long story short.

    You need to append an association object containing your child object onto your parent. Otherwise, you need to follow Lars' suggestion about a proxy association.

    I recommend the former since it's the ORM-based way:

    p = Parent()
    p.children.append(Association(child = Child()))
    session.add(p)
    session.commit()
    

    Note that if you have any non-nullable fields, they're easy to add on object creation for a quick test commit.

    0 讨论(0)
提交回复
热议问题