Skip to content

Commit 9544f10

Browse files
committed
Fix OnChangedDict behaviour with recent python versions
1 parent ed3f30d commit 9544f10

2 files changed

Lines changed: 51 additions & 3 deletions

File tree

deebot_client/util/__init__.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,6 @@ class OnChangedDict(dict[_KT, _VT]):
8181
"pop",
8282
"popitem",
8383
"update",
84-
"__setitem__",
85-
"__delitem__",
8684
)
8785

8886
def __init__(
@@ -91,6 +89,16 @@ def __init__(
9189
super().__init__(iterable)
9290
self._on_change = on_change
9391

92+
# This is needed as __getattribute__ won't be invoked for implicit special method lookup
93+
def __setitem__(self, key: _KT, value: _VT) -> None:
94+
self._on_change()
95+
super().__setitem__(key, value)
96+
97+
# This is needed as __getattribute__ won't be invoked for implicit special method lookup
98+
def __delitem__(self, key: _KT) -> None:
99+
self._on_change()
100+
return super().__delitem__(key)
101+
94102
def __getattribute__(self, name: str, /) -> Any:
95103
if name in OnChangedDict._MODIFYING_FUNCTIONS:
96104
self._on_change()

tests/util/test_init.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import asyncio
44
from typing import Any
55

6-
from deebot_client.util import cancel, create_task
6+
from deebot_client.util import OnChangedDict, cancel, create_task
77

88

99
async def test_create_task_and_cancel() -> None:
@@ -35,3 +35,43 @@ async def sleep(delay: float) -> None:
3535
for task in _tasks:
3636
assert task.cancelled()
3737
assert task.done()
38+
39+
40+
def test_on_changed_dict() -> None:
41+
class OnChangedListener:
42+
def __init__(self) -> None:
43+
self.__counter = 0
44+
45+
def on_changed_dict(self) -> None:
46+
self.__counter += 1
47+
48+
def counter(self) -> int:
49+
return self.__counter
50+
51+
listener = OnChangedListener()
52+
53+
sut: OnChangedDict[str, int] = OnChangedDict(listener.on_changed_dict)
54+
55+
sut["test"] = 1001 # Should be triggered by __setitem__
56+
57+
assert sut["test"] == 1001
58+
59+
sut.update(
60+
{"test": 1002, "test2": 2001, "test3": 3001, "test4": 4001}
61+
) # Should trigger update()
62+
63+
assert sut["test"] == 1002
64+
65+
del sut["test"] # Should trigger __delitem__
66+
67+
assert "test" not in sut
68+
69+
assert sut.pop("test2") == 2001 # Should trigger pop()
70+
71+
(popped_key, popped_value) = sut.popitem() # Should trigger popitem()
72+
assert popped_key == "test4"
73+
assert popped_value == 4001
74+
75+
sut.clear() # Should trigger clear
76+
77+
assert listener.counter() == 6

0 commit comments

Comments
 (0)