From 71a858817cb8c11451ae577c61329f4239fab46b Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Wed, 12 May 2021 09:26:03 -0400 Subject: Create new event for collection add w/o mutation Fixed issue when using :paramref:`_orm.relationship.cascade_backrefs` parameter set to ``False``, which per :ref:`change_5150` is set to become the standard behavior in SQLAlchemy 2.0, where adding the item to a collection that uniquifies, such as ``set`` or ``dict`` would fail to fire a cascade event if the object were already associated in that collection via the backref. This fix represents a fundamental change in the collection mechanics by introducing a new event state which can fire off for a collection mutation even if there is no net change on the collection; the action is now suited using a new event hook :meth:`_orm.AttributeEvents.append_wo_mutation`. Fixes: #6471 Change-Id: Ic50413f7e62440dad33ab84838098ea62ff4e815 --- lib/sqlalchemy/orm/events.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) (limited to 'lib/sqlalchemy/orm/events.py') diff --git a/lib/sqlalchemy/orm/events.py b/lib/sqlalchemy/orm/events.py index 0824ae7de..926c2dea7 100644 --- a/lib/sqlalchemy/orm/events.py +++ b/lib/sqlalchemy/orm/events.py @@ -2295,6 +2295,36 @@ class AttributeEvents(event.Events): """ + def append_wo_mutation(self, target, value, initiator): + """Receive a collection append event where the collection was not + actually mutated. + + This event differs from :meth:`_orm.AttributeEvents.append` in that + it is fired off for de-duplicating collections such as sets and + dictionaries, when the object already exists in the target collection. + The event does not have a return value and the identity of the + given object cannot be changed. + + The event is used for cascading objects into a :class:`_orm.Session` + when the collection has already been mutated via a backref event. + + :param target: the object instance receiving the event. + If the listener is registered with ``raw=True``, this will + be the :class:`.InstanceState` object. + :param value: the value that would be appended if the object did not + already exist in the collection. + :param initiator: An instance of :class:`.attributes.Event` + representing the initiation of the event. May be modified + from its original value by backref handlers in order to control + chained event propagation, as well as be inspected for information + about the source of the event. + + :return: No return value is defined for this event. + + .. versionadded:: 1.4.15 + + """ + def bulk_replace(self, target, values, initiator): """Receive a collection 'bulk replace' event. -- cgit v1.2.1