Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

KeyError when adding objects to SQLAlchemy association object

I have two tables, tablet and correspondent:

class Correspondent(db.Model, GlyphMixin):
    # PK column and tablename etc. come from the mixin
    name = db.Column(db.String(100), nullable=False, unique=True)
    # association proxy
    tablets = association_proxy('correspondent_tablets', 'tablet')

    def __init__(self, name, tablets=None):
        self.name = name
        if tablets:
            self.tablets = tablets


class Tablet(db.Model, GlyphMixin):
    # PK column and tablename etc. come from the mixin
    area = db.Column(db.String(100), nullable=False, unique=True)
    # association proxy
    correspondents = association_proxy('tablet_correspondents', 'correspondent')

    def __init__(self, area, correspondents=None):
        self.area = area
        if correspondents:
            self.correspondents = correspondents


class Tablet_Correspondent(db.Model):

    __tablename__ = "tablet_correspondent"
    tablet_id = db.Column("tablet_id",
        db.Integer(), db.ForeignKey("tablet.id"), primary_key=True)
    correspondent_id = db.Column("correspondent_id",
        db.Integer(), db.ForeignKey("correspondent.id"), primary_key=True)
    # relations
    tablet = db.relationship(
        "Tablet",
        backref="tablet_correspondents",
        cascade="all, delete-orphan",
        single_parent=True)
    correspondent = db.relationship(
        "Correspondent",
        backref="correspondent_tablets",
        cascade="all, delete-orphan",
        single_parent=True)

    def __init__(self, tablet=None, correspondent=None):
        self.tablet = tablet
        self.correspondent = correspondent

I can add records to tablet and correspondent, and doing e.g. Tablet.query.first().correspondents simply returns an empty list, as you would expect. If I manually insert a row into my tablet_correspondent table using existing tablet and correspondent IDs, the list is populated, again as you would expect.

However, if I try to do

cor = Correspondent.query.first()
tab = Tablet.query.first()
tab.correspondents.append(cor)

I get:

KeyError: 'tablet_correspondents'

I'm pretty sure I'm leaving out something fairly elementary here.

like image 523
urschrei Avatar asked Jun 18 '12 21:06

urschrei


Video Answer


1 Answers

The problem with your code is in the .__init__ method. If you are to debug-watch/print() the parameters, you will notice that the parameter tablet is actually an instance of Correspondent:

class Tablet_Correspondent(db.Model):
    def __init__(self, tablet=None, correspondent=None):
        print "in __init__: ", tablet, correspondent
        self.tablet = tablet
        self.correspondent = correspondent

The reason for this is the way SA creates new values. From documentation Creation of New Values:

When a list append() event (or set add(), dictionary __setitem__(), or scalar assignment event) is intercepted by the association proxy, it instantiates a new instance of the “intermediary” object using its constructor, passing as a single argument the given value.

In your case when you call tab.correspondents.append(cor), the Tablet_Correspondent.__init__ is called with single argument cor.

Solution? If you will only be adding Correspondents to the Tablet, then just switch the parameters in the __init__. In fact, remove the second parameter completely.
If, however, you will also be using cor.tablets.append(tab), then you need to explicitely use the creator argument to the association_proxy as explained in the documentation linked to above:

class Tablet(db.Model, GlyphMixin):
    # ...
    correspondents = association_proxy('tablet_correspondents', 'correspondent', creator=lambda cor: Tablet_Correspondent(correspondent=cor))

class Correspondent(db.Model, GlyphMixin):
    # ...
    tablets = association_proxy('correspondent_tablets', 'tablet', creator=lambda tab: Tablet_Correspondent(tablet=tab))
like image 183
van Avatar answered Nov 01 '22 00:11

van