M2M relationship back_populates KeyError

703 views
Skip to first unread message

Juan

unread,
Jul 1, 2018, 11:39:00 PM7/1/18
to sqlal...@googlegroups.com
Hi,

I do not know if this is that I am missing something in how SQLAlchemy
works or an issue; I have read the doc and searched for an explanation
but could not find an answer.

When appending via an intermediate table,  a  M2M relationship behaves
differently wether I use the back_populates argument or not ( or use
backref)

* Without using back_populates or backref is works fine
```
from sqlalchemy import Column, Integer, String, ForeignKey, Table
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class User(Base):
    __tablename__ = 'user'
    id = Column(Integer, primary_key=True)
    name = Column(String(64))
    keywords = relationship("UsersKeywords")

    def __init__(self, name):
        self.name = name

class Keyword(Base):
    __tablename__ = 'keyword'
    id = Column(Integer, primary_key=True)
    keyword = Column('keyword', String(64))

    def __init__(self, keyword):
        self.keyword = keyword

class UsersKeywords(Base):
    __tablename__ = 'userskeywords'
    user_id = Column( Integer, ForeignKey("user.id"), primary_key=True)
    keyword_id = Column(Integer, ForeignKey("keyword.id"),
primary_key=True)
    user = relationship(User, back_populates="keywords")
    keyword = relationship(Keyword)

>>>u = User(‘John’)
>>>u.keywords.append(Keyword(‘Python’)
>>>u.keywords
[<Keyword object at 0x10d09f860>]
```

* With back_populates or backref in one of the ‘M’ sides of the
relation, ‘User’ in this case, I get a KeyError exception:
```
class User(Base):
    __tablename__ = 'user'
    id = Column(Integer, primary_key=True)
    name = Column(String(64))
    keywords = relationship('UsersKeywords', back_populates='user') #
added back_populates argument

    def __init__(self, name):
        self.name = name

>>>u = User(‘John’)
>>>u.keywords.append(Keyword(‘Python’)

Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File
"/Users/xxx/Development/python_ws/flask_env/lib/python3.6/site-packages/sqlalchemy/orm/collections.py",
line 1044, in append
    item = __set(self, item, _sa_initiator)
  File
"/Users/xxx/Development/python_ws/flask_env/lib/python3.6/site-packages/sqlalchemy/orm/collections.py",
line 1016, in __set
    item = executor.fire_append_event(item, _sa_initiator)
  File
"/Users/xxx/Development/python_ws/flask_env/lib/python3.6/site-packages/sqlalchemy/orm/collections.py",
line 680, in fire_append_event
    item, initiator)
  File
"/Users/xxx/Development/python_ws/flask_env/lib/python3.6/site-packages/sqlalchemy/orm/attributes.py",
line 943, in fire_append_event
    state, value, initiator or self._append_token)
  File
"/Users/xxx/Development/python_ws/flask_env/lib/python3.6/site-packages/sqlalchemy/orm/attributes.py",
line 1210, in emit_backref_from_collection_append_event
    child_impl = child_state.manager[key].impl
KeyError: 'user'
```

Why is the behavior different?

Thanks in advance

Juan

[SQLAlchemy version: 1.2.9]

Simon King

unread,
Jul 2, 2018, 6:04:36 AM7/2/18
to sqlal...@googlegroups.com
Your "User.keywords" property is supposed to contain "UsersKeyword"
instances (which have a "user" property), You've appended a "Keyword"
instance to it, which *doesn't* have a "user" property, hence the
error.

If your userskeywords table only has foreign keys to the user and
keyword tables, and doesn't have any other behaviour, you probably
don't want to define it as a mapped class at all. Instead, use the
Table constructor, as in the first example at
http://docs.sqlalchemy.org/en/latest/orm/basic_relationships.html#many-to-many.

If you really do want to treat UsersKeywords as a mapped class in its
own right, you need to ensure that those objects are constructed
whenever you make a relationship between a user and a keyword:

>>> u.keywords.append(UsersKeywords(keyword=keyword))

The association proxy
(http://docs.sqlalchemy.org/en/latest/orm/extensions/associationproxy.html)
can help with this.

Hope that helps,

Simon
Reply all
Reply to author
Forward
0 new messages