I'm trying to set up a bidirectional association proxy, but am encountering 
some unexpected behaviour with adding to the association list vs. deleting. 
Adds/updates are propagated to both association lists immediately, but 
deleting an object from one side's association list ends up with the other 
side's list containing 'None' in its place until the object is expired.

Is this the intended behaviour, or am I setting something up incorrectly? I 
can't find anything in the docs to explain this, but I would love to know 
why this is happening. And ideally, I'd like to avoid having to remember to 
manually expire the other object every time. 

Example (adapted from existing docs example):

class *Order*(Base):
    __tablename__ = 'order'
    order_id = Column(Integer, primary_key=True)

    order_items = relationship('OrderItem', cascade='all, delete-orphan', 
back_populates='order')
    items = association_proxy('order_items', 'item', creator=lambda x: 
OrderItem(item=x))

    def __repr__(self):
        return f'Order({self.order_id})'


class *Item*(Base):
    __tablename__ = 'item'
    item_id = Column(Integer, primary_key=True)

    order_items = relationship('OrderItem', cascade='all, delete-orphan', 
back_populates='item')
    orders = association_proxy('order_items', 'order', creator=lambda x: 
OrderItem(order=x))

    def __repr__(self):
        return f'Item({self.item_id})'


class *OrderItem*(Base):
    __tablename__ = 'orderitem'
    id = Column(Integer, primary_key=True)

    order_id = Column(Integer, ForeignKey('order.order_id', 
ondelete='CASCADE'), nullable=False)
    item_id = Column(Integer, ForeignKey('item.item_id', 
ondelete='CASCADE'), nullable=False)

    item = relationship(Item, back_populates='order_items')
    order = relationship(Order, back_populates='order_items')

    def __repr__(self):
        return f'OrderItem({self.id}, item={self.item}, order={self.order})'


def *print_state*(order_id, item_id, session):
    order = session.query(Order).filter(Order.order_id == order_id).one()
    item = session.query(Item).filter(Item.item_id == item_id).one()
    order_items = session.query(OrderItem).all()
    print(f'  Order.items: {order.items}\n  Item.orders: {item.orders}\n  
OrderItems: {order_items}\n-----')


if __name__ == '__main__':
    engine = create_engine('sqlite://')
    Base.metadata.create_all(engine)
    session = Session(engine)

    item = Item()
    order = Order()
    session.add_all([item, order])
    session.commit()

    ids = order.order_id, item.item_id

    print('order.items.append(item)')
    order.items.append(item)
    print_state(*ids, session)

    print('order.items.remove(item)')
    order.items.remove(item)
    print_state(*ids, session)

    print('session.expire(item)')
    session.expire(item)
    print_state(*ids, session)


*Output:*
order.items.append(item)
  Order.items: [Item(1)]
  Item.orders: [Order(1)]
  OrderItems: [OrderItem(1, item=Item(1), order=Order(1))]
----
order.items.remove(item)
  Order.items: []
  *Item.orders: [None]*
  OrderItems: []
----
session.expire(item)
  Order.items: []
  Item.orders: []
  OrderItems: []
----

-- 
SQLAlchemy - 
The Python SQL Toolkit and Object Relational Mapper

http://www.sqlalchemy.org/

To post example code, please provide an MCVE: Minimal, Complete, and Verifiable 
Example.  See  http://stackoverflow.com/help/mcve for a full description.
--- 
You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to sqlalchemy+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/sqlalchemy/cbb6763f-7442-40d8-91cc-6029d7ae1a54n%40googlegroups.com.
from sqlalchemy import Column
from sqlalchemy import ForeignKey
from sqlalchemy import Integer
from sqlalchemy import create_engine
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import Session
from sqlalchemy.orm import relationship

Base = declarative_base()


class Order(Base):
    __tablename__ = 'order'
    order_id = Column(Integer, primary_key=True)

    order_items = relationship('OrderItem', cascade='all, delete-orphan', back_populates='order')
    items = association_proxy('order_items', 'item', creator=lambda x: OrderItem(item=x))

    def __repr__(self):
        return f'Order({self.order_id})'


class Item(Base):
    __tablename__ = 'item'
    item_id = Column(Integer, primary_key=True)

    order_items = relationship('OrderItem', cascade='all, delete-orphan', back_populates='item')
    orders = association_proxy('order_items', 'order', creator=lambda x: OrderItem(order=x))

    def __repr__(self):
        return f'Item({self.item_id})'


class OrderItem(Base):
    __tablename__ = 'orderitem'
    id = Column(Integer, primary_key=True)

    order_id = Column(Integer, ForeignKey('order.order_id', ondelete='CASCADE'), nullable=False)
    item_id = Column(Integer, ForeignKey('item.item_id', ondelete='CASCADE'), nullable=False)

    item = relationship(Item, back_populates='order_items')
    order = relationship(Order, back_populates='order_items')

    def __repr__(self):
        return f'OrderItem({self.id}, item={self.item}, order={self.order})'


def print_state(order_id, item_id, session):
    order = session.query(Order).filter(Order.order_id == order_id).one()
    item = session.query(Item).filter(Item.item_id == item_id).one()
    order_items = session.query(OrderItem).all()
    print(f'  Order.items: {order.items}\n  Item.orders: {item.orders}\n  OrderItems: {order_items}\n----')


if __name__ == '__main__':
    engine = create_engine('sqlite://')
    Base.metadata.create_all(engine)
    session = Session(engine)

    item = Item()
    order = Order()
    session.add_all([item, order])
    session.commit()

    ids = order.order_id, item.item_id

    print('order.items.append(item)')
    order.items.append(item)
    print_state(*ids, session)

    print('order.items.remove(item)')
    order.items.remove(item)
    print_state(*ids, session)

    print('session.expire(item)')
    session.expire(item)
    print_state(*ids, session)


Reply via email to