https://github.com/python/cpython/commit/5989eb74463c26780632f17f221d6bf4c9372a01
commit: 5989eb74463c26780632f17f221d6bf4c9372a01
branch: main
author: Sam Gross <[email protected]>
committer: colesbury <[email protected]>
date: 2024-10-21T08:23:38-04:00
summary:

gh-125608: Trigger dictionary watchers when inline values change (#125611)

Dictionary watchers on an object's attributes dictionary
(`object.__dict__`) were not triggered when the managed dictionary used
the object's inline values.

files:
A Misc/NEWS.d/next/C_API/2024-10-16-19-28-23.gh-issue-125608.gTsU2g.rst
M Lib/test/test_capi/test_watchers.py
M Objects/dictobject.c

diff --git a/Lib/test/test_capi/test_watchers.py 
b/Lib/test/test_capi/test_watchers.py
index 4bb764bf9d0963..e578a622a03487 100644
--- a/Lib/test/test_capi/test_watchers.py
+++ b/Lib/test/test_capi/test_watchers.py
@@ -97,6 +97,23 @@ def test_dealloc(self):
             del d
             self.assert_events(["dealloc"])
 
+    def test_object_dict(self):
+        class MyObj: pass
+        o = MyObj()
+
+        with self.watcher() as wid:
+            self.watch(wid, o.__dict__)
+            o.foo = "bar"
+            o.foo = "baz"
+            del o.foo
+            self.assert_events(["new:foo:bar", "mod:foo:baz", "del:foo"])
+
+        with self.watcher() as wid:
+            self.watch(wid, o.__dict__)
+            for _ in range(100):
+                o.foo = "bar"
+            self.assert_events(["new:foo:bar"] + ["mod:foo:bar"] * 99)
+
     def test_unwatch(self):
         d = {}
         with self.watcher() as wid:
diff --git 
a/Misc/NEWS.d/next/C_API/2024-10-16-19-28-23.gh-issue-125608.gTsU2g.rst 
b/Misc/NEWS.d/next/C_API/2024-10-16-19-28-23.gh-issue-125608.gTsU2g.rst
new file mode 100644
index 00000000000000..e70f9f173957a2
--- /dev/null
+++ b/Misc/NEWS.d/next/C_API/2024-10-16-19-28-23.gh-issue-125608.gTsU2g.rst
@@ -0,0 +1,3 @@
+Fix a bug where dictionary watchers (e.g., :c:func:`PyDict_Watch`) on an
+object's attribute dictionary (:attr:`~object.__dict__`) were not triggered
+when the object's attributes were modified.
diff --git a/Objects/dictobject.c b/Objects/dictobject.c
index b27599d2815c82..806096f5814062 100644
--- a/Objects/dictobject.c
+++ b/Objects/dictobject.c
@@ -6835,15 +6835,24 @@ store_instance_attr_lock_held(PyObject *obj, 
PyDictValues *values,
     }
 
     PyObject *old_value = values->values[ix];
+    if (old_value == NULL && value == NULL) {
+        PyErr_Format(PyExc_AttributeError,
+                        "'%.100s' object has no attribute '%U'",
+                        Py_TYPE(obj)->tp_name, name);
+        return -1;
+    }
+
+    if (dict) {
+        PyInterpreterState *interp = _PyInterpreterState_GET();
+        PyDict_WatchEvent event = (old_value == NULL ? PyDict_EVENT_ADDED :
+                                   value == NULL ? PyDict_EVENT_DELETED :
+                                   PyDict_EVENT_MODIFIED);
+        _PyDict_NotifyEvent(interp, event, dict, name, value);
+    }
+
     FT_ATOMIC_STORE_PTR_RELEASE(values->values[ix], Py_XNewRef(value));
 
     if (old_value == NULL) {
-        if (value == NULL) {
-            PyErr_Format(PyExc_AttributeError,
-                         "'%.100s' object has no attribute '%U'",
-                         Py_TYPE(obj)->tp_name, name);
-            return -1;
-        }
         _PyDictValues_AddToInsertionOrder(values, ix);
         if (dict) {
             assert(dict->ma_values == values);

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]

Reply via email to