2

When I have two objects, linked with a "relation" in SQLAlchemy, I realised that simply assigning to that relation is not enough to propagate the values to the other object. For example (see below), if I have a "user" table and a "contact" table (both are highly contrived, but demonstrate the issue well), and a "user" can have multiple "contacts". In that case I will have a foreign key between the users and contacts. If I create both an instance of User and Contact and later assign the user to the contact, I would expect the attributes of the FK to be updated (even without a DB flush) but they are not. Why? And how can I tell SA to do this automatically?

This would be something I would expect to work, but as you can see in the full example below, it does not:

user = User(name='a', lname='b')
contact(type='email', value='foo@bar.com')
contact.user = user
assert contact.username == 'a'  # <-- Fails because the attribute is still `None`

Full runnable example:

"""
This code example shows two tables related to each other by a composite key,
using an SQLAlchemy "relation" to allow easy access to related items.

However, as the last few lines show, simply assigning an object A to the
relation of object B does not update the attributes of object B until at least
a "flush" is called.
"""
from sqlalchemy import Column, ForeignKeyConstraint, Unicode, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relation, sessionmaker

Base = declarative_base()

class User(Base):
    __tablename__ = "user"

    name = Column(Unicode, primary_key=True)
    lname = Column(Unicode, primary_key=True)


class Contact(Base):
    __tablename__ = "contact"
    __table_args__ = (
        ForeignKeyConstraint(
            ['username', 'userlname'],
            ['user.name', 'user.lname']
        ),
    )

    username = Column(Unicode, primary_key=True)
    userlname = Column(Unicode, primary_key=True)
    type = Column(Unicode)
    value = Column(Unicode)

    user = relation(User)


engine = create_engine('sqlite://')
Base.metadata.create_all(engine)

Session = sessionmaker(bind=engine)
session = Session()

user = User(name="John", lname="Doe")
contact = Contact(type='email', value='john.doe@example.com')
contact.user = user  # <-- How can I tell SA to set the FKs on *contact* here?
session.add(contact)

print('Before flush: contact.username user=%r' % contact.username)
session.flush()
print('After flush : contact.username user=%r' % contact.username)
exhuma
  • 20,071
  • 12
  • 90
  • 123

1 Answers1

2

According to this answer - https://stackoverflow.com/a/52911047/4981223 it is not possible:

The FK of the child object isn't updated until you issue a flush() either explicitly or through a commit(). I think the reason for this is that if the parent object of a relationship is also a new instance with an auto-increment PK, SQLAlchemy needs to get the PK from the database before it can update the FK on the child object (but I stand to be corrected!).

ishefi
  • 480
  • 4
  • 12