]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.13] gh-125608: Trigger dictionary watchers when inline values change (GH-125611...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Fri, 25 Oct 2024 14:35:04 +0000 (16:35 +0200)
committerGitHub <noreply@github.com>
Fri, 25 Oct 2024 14:35:04 +0000 (14:35 +0000)
Dictionary watchers on an object's attributes dictionary
(`object.__dict__`) were not triggered when the managed dictionary used
the object's inline values.
(cherry picked from commit 5989eb74463c26780632f17f221d6bf4c9372a01)

Co-authored-by: Sam Gross <colesbury@gmail.com>
Lib/test/test_capi/test_watchers.py
Misc/NEWS.d/next/C_API/2024-10-16-19-28-23.gh-issue-125608.gTsU2g.rst [new file with mode: 0644]
Objects/dictobject.c

index 90665a7561b3162306a608972dcfa77dd54955bc..018e6def6bafddbeca10c5ebddb568a542148dd8 100644 (file)
@@ -96,6 +96,23 @@ class TestDictWatchers(unittest.TestCase):
             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 (file)
index 0000000..e70f9f1
--- /dev/null
@@ -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.
index a5a5bce3965555354f3096d943534d5eb993581b..91bc24b49a1bd880ff3044ab9228dbf4e90a7b65 100644 (file)
@@ -6825,15 +6825,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);