The association proxy currently covers the "creator" use case, that is, a new 
Item is given, you pass it a "creator" that says, "create a new OrderItem() 
object and assign to OrderItem.item", but there is no support at the moment for 
a "deleter" which says, "take the OrderItem and set OrderItem.item to None".

the delete use case is not well covered with assocation proxy, I can hardcode a 
proof of concept for this case like this:

iff --git a/lib/sqlalchemy/ext/associationproxy.py 
b/lib/sqlalchemy/ext/associationproxy.py 
index 2f8d8627c..9e1f2a68d 100644
--- a/lib/sqlalchemy/ext/associationproxy.py
+++ b/lib/sqlalchemy/ext/associationproxy.py
@@ -1102,7 +1102,9 @@ class _AssociationList(_AssociationCollection):
     def remove(self, value):
         for i, val in enumerate(self):
             if val == value:
+                holder = self.col[i]
                 del self.col[i]
+                self.setter(holder, None)
                 return
         raise ValueError("value not in list")


but that would need to have a lot of tests written and changelogs and stuff and 
also it might want to be a configurable option since I'm not sure under what 
cases such a thing might not be what's desired.  

so if you wanted to open an issue and all of that there could be a place to 
track thinking of getting "assocation proxy" to be comprehensive for 
delete/remove operations but for the moment just doing the below will fix for 
now:

    def remove_item(order, item): 
        for i, val in enumerate(order.order_items):
            if val.item is item:
                val.item = None
                del order.order_items[i]
                return

    remove_item(order, item)
    print_state(*ids, session)




On Tue, Dec 22, 2020, at 11:59 AM, S wrote:
> 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 [email protected].
> To view this discussion on the web visit 
> https://groups.google.com/d/msgid/sqlalchemy/cbb6763f-7442-40d8-91cc-6029d7ae1a54n%40googlegroups.com
>  
> <https://groups.google.com/d/msgid/sqlalchemy/cbb6763f-7442-40d8-91cc-6029d7ae1a54n%40googlegroups.com?utm_medium=email&utm_source=footer>.
> 
> 
> *Attachments:*
>  * association_proxy_example.py

-- 
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 [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/sqlalchemy/95be88e4-8ecf-46ad-b0ac-badee6286766%40www.fastmail.com.

Reply via email to