summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/build/changelog/unreleased_12/add_initiator.rst7
-rw-r--r--lib/sqlalchemy/orm/attributes.py19
-rw-r--r--test/orm/test_attributes.py36
3 files changed, 60 insertions, 2 deletions
diff --git a/doc/build/changelog/unreleased_12/add_initiator.rst b/doc/build/changelog/unreleased_12/add_initiator.rst
new file mode 100644
index 000000000..a37f9ae84
--- /dev/null
+++ b/doc/build/changelog/unreleased_12/add_initiator.rst
@@ -0,0 +1,7 @@
+.. change::
+ :tags: feature, orm
+
+ Added new argument :paramref:`.attributes.set_attribute.inititator`
+ to the :func:`.attributes.set_attribute` function, allowing an
+ event token received from a listener function to be propagated
+ to subsequent set events.
diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py
index b175297ac..e9227362e 100644
--- a/lib/sqlalchemy/orm/attributes.py
+++ b/lib/sqlalchemy/orm/attributes.py
@@ -1592,7 +1592,7 @@ def set_committed_value(instance, key, value):
state.manager[key].impl.set_committed_value(state, dict_, value)
-def set_attribute(instance, key, value):
+def set_attribute(instance, key, value, initiator=None):
"""Set the value of an attribute, firing history events.
This function may be used regardless of instrumentation
@@ -1601,9 +1601,24 @@ def set_attribute(instance, key, value):
of this method to establish attribute state as understood
by SQLAlchemy.
+ :param instance: the object that will be modified
+
+ :param key: string name of the attribute
+
+ :param value: value to assign
+
+ :param initiator: an instance of :class:`.Event` that would have
+ been propagated from a previous event listener. This argument
+ is used when the :func:`.set_attribute` function is being used within
+ an existing event listening function where an :class:`.Event` object
+ is being supplied; the object may be used to track the origin of the
+ chain of events.
+
+ .. versionadded:: 1.2.3
+
"""
state, dict_ = instance_state(instance), instance_dict(instance)
- state.manager[key].impl.set(state, dict_, value, None)
+ state.manager[key].impl.set(state, dict_, value, initiator)
def get_attribute(instance, key):
diff --git a/test/orm/test_attributes.py b/test/orm/test_attributes.py
index 4646a010a..12c9dddb9 100644
--- a/test/orm/test_attributes.py
+++ b/test/orm/test_attributes.py
@@ -1014,6 +1014,42 @@ class UtilTest(fixtures.ORMTest):
attributes.del_attribute(f1, "coll")
assert "coll" not in f1.__dict__
+ def test_initiator_arg(self):
+ class Foo(object):
+ pass
+
+ class Bar(object):
+ pass
+
+ instrumentation.register_class(Foo)
+ instrumentation.register_class(Bar)
+ attributes.register_attribute(
+ Foo, "a", uselist=False, useobject=False)
+ attributes.register_attribute(
+ Bar, "b", uselist=False, useobject=False)
+
+ @event.listens_for(Foo.a, "set")
+ def sync_a(target, value, oldvalue, initiator):
+ parentclass = initiator.parent_token.class_
+ if parentclass is Foo:
+ attributes.set_attribute(target.bar, "b", value, initiator)
+
+ @event.listens_for(Bar.b, "set")
+ def sync_b(target, value, oldvalue, initiator):
+ parentclass = initiator.parent_token.class_
+ if parentclass is Bar:
+ attributes.set_attribute(target.foo, "a", value, initiator)
+
+ f1 = Foo()
+ b1 = Bar()
+ f1.bar = b1
+ b1.foo = f1
+
+ f1.a = 'x'
+ eq_(b1.b, 'x')
+ b1.b = 'y'
+ eq_(f1.a, 'y')
+
class BackrefTest(fixtures.ORMTest):