python-library-reactive-model 0.1.2__tar.gz → 0.1.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_library_reactive_model-0.1.2 → python_library_reactive_model-0.1.3}/.gitignore +3 -1
- {python_library_reactive_model-0.1.2 → python_library_reactive_model-0.1.3}/PKG-INFO +1 -1
- {python_library_reactive_model-0.1.2 → python_library_reactive_model-0.1.3}/pyproject.toml +1 -1
- python_library_reactive_model-0.1.3/reactive_model/__init__.py +13 -0
- python_library_reactive_model-0.1.2/reactive_model/computed.py → python_library_reactive_model-0.1.3/reactive_model/computed_model.py +1 -1
- python_library_reactive_model-0.1.3/reactive_model/computed_property.py +24 -0
- python_library_reactive_model-0.1.2/reactive_model/dict_ref.py → python_library_reactive_model-0.1.3/reactive_model/dict_ref_model.py +1 -1
- python_library_reactive_model-0.1.2/reactive_model/list_ref.py → python_library_reactive_model-0.1.3/reactive_model/list_ref_model.py +27 -9
- python_library_reactive_model-0.1.2/reactive_model/ref.py → python_library_reactive_model-0.1.3/reactive_model/ref_model.py +1 -1
- python_library_reactive_model-0.1.3/tests/test_computed_property.py +110 -0
- python_library_reactive_model-0.1.2/reactive_model/__init__.py +0 -11
- /python_library_reactive_model-0.1.2/reactive_model/reactive.py → /python_library_reactive_model-0.1.3/reactive_model/reactive_model.py +0 -0
- {python_library_reactive_model-0.1.2 → python_library_reactive_model-0.1.3}/reactive_model/track.py +0 -0
- {python_library_reactive_model-0.1.2 → python_library_reactive_model-0.1.3}/test.bat +0 -0
- {python_library_reactive_model-0.1.2 → python_library_reactive_model-0.1.3}/tests/test_reactive_model.py +0 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from .computed_model import ComputedModel
|
|
2
|
+
from .ref_model import RefModel
|
|
3
|
+
from .dict_ref_model import DictRefModel
|
|
4
|
+
from .list_ref_model import ListRefModel
|
|
5
|
+
from .computed_property import computed_property
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"RefModel",
|
|
9
|
+
"ComputedModel",
|
|
10
|
+
"DictRefModel",
|
|
11
|
+
"ListRefModel",
|
|
12
|
+
"computed_property",
|
|
13
|
+
]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from typing import Callable, Generic, TypeVar
|
|
2
|
+
|
|
3
|
+
from reactive_model.computed_model import ComputedModel
|
|
4
|
+
|
|
5
|
+
T = TypeVar("T")
|
|
6
|
+
|
|
7
|
+
class computed_property(Generic[T]):
|
|
8
|
+
def __init__(self, func: Callable[..., T]) -> None:
|
|
9
|
+
self.func = func
|
|
10
|
+
self.storage_name = ""
|
|
11
|
+
|
|
12
|
+
def __set_name__(self, owner, name):
|
|
13
|
+
self.storage_name = f"__computed_{name}"
|
|
14
|
+
|
|
15
|
+
def __get__(self, obj, owner=None) -> T:
|
|
16
|
+
if obj is None:
|
|
17
|
+
return self
|
|
18
|
+
|
|
19
|
+
model = obj.__dict__.get(self.storage_name)
|
|
20
|
+
if model is None:
|
|
21
|
+
model = ComputedModel(lambda: self.func(obj))
|
|
22
|
+
obj.__dict__[self.storage_name] = model
|
|
23
|
+
|
|
24
|
+
return model.value
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
from collections.abc import Iterable, MutableSequence
|
|
4
4
|
from typing import Generic, TypeVar, overload
|
|
5
5
|
|
|
6
|
-
from .
|
|
6
|
+
from .ref_model import RefModel
|
|
7
7
|
from .track import track
|
|
8
8
|
|
|
9
9
|
T = TypeVar("T")
|
|
@@ -18,6 +18,7 @@ class ListProxy(MutableSequence[T], Generic[T]):
|
|
|
18
18
|
|
|
19
19
|
@overload
|
|
20
20
|
def __getitem__(self, index: int) -> T: ...
|
|
21
|
+
|
|
21
22
|
@overload
|
|
22
23
|
def __getitem__(self, index: slice) -> list[T]: ...
|
|
23
24
|
|
|
@@ -26,6 +27,7 @@ class ListProxy(MutableSequence[T], Generic[T]):
|
|
|
26
27
|
|
|
27
28
|
@overload
|
|
28
29
|
def __setitem__(self, index: int, value: T) -> None: ...
|
|
30
|
+
|
|
29
31
|
@overload
|
|
30
32
|
def __setitem__(self, index: slice, value: Iterable[T]) -> None: ...
|
|
31
33
|
|
|
@@ -41,25 +43,41 @@ class ListProxy(MutableSequence[T], Generic[T]):
|
|
|
41
43
|
self._owner._value.insert(index, value)
|
|
42
44
|
self._owner.touch()
|
|
43
45
|
|
|
46
|
+
def append(self, value: T) -> None:
|
|
47
|
+
self._owner._value.append(value)
|
|
48
|
+
self._owner.touch()
|
|
49
|
+
|
|
50
|
+
def extend(self, values: Iterable[T]) -> None:
|
|
51
|
+
self._owner._value.extend(values)
|
|
52
|
+
self._owner.touch()
|
|
53
|
+
|
|
44
54
|
def clear(self) -> None:
|
|
45
55
|
if self._owner._value:
|
|
46
56
|
self._owner._value.clear()
|
|
47
57
|
self._owner.touch()
|
|
48
58
|
|
|
49
|
-
def
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
self._owner.touch()
|
|
59
|
+
def pop(self, index: int = -1) -> T:
|
|
60
|
+
item = self._owner._value.pop(index)
|
|
61
|
+
self._owner.touch()
|
|
62
|
+
return item
|
|
54
63
|
|
|
55
|
-
def
|
|
56
|
-
self._owner._value.
|
|
64
|
+
def remove(self, value: T) -> None:
|
|
65
|
+
self._owner._value.remove(value)
|
|
57
66
|
self._owner.touch()
|
|
58
67
|
|
|
59
68
|
def reverse(self) -> None:
|
|
60
69
|
self._owner._value.reverse()
|
|
61
70
|
self._owner.touch()
|
|
62
71
|
|
|
72
|
+
def sort(self, *args: object, **kwargs: object) -> None:
|
|
73
|
+
self._owner._value.sort(*args, **kwargs) # type: ignore[arg-type]
|
|
74
|
+
self._owner.touch()
|
|
75
|
+
|
|
76
|
+
def __iadd__(self, other: Iterable[T]) -> ListProxy[T]:
|
|
77
|
+
self._owner._value += list(other)
|
|
78
|
+
self._owner.touch()
|
|
79
|
+
return self
|
|
80
|
+
|
|
63
81
|
def __repr__(self) -> str:
|
|
64
82
|
return repr(self._owner._value)
|
|
65
83
|
|
|
@@ -79,4 +97,4 @@ class ListRefModel(RefModel[list[T]]):
|
|
|
79
97
|
@value.setter
|
|
80
98
|
def value(self, value: list[T]) -> None:
|
|
81
99
|
self._value = value
|
|
82
|
-
self.touch()
|
|
100
|
+
self.touch()
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import unittest
|
|
4
|
+
|
|
5
|
+
from reactive_model import RefModel, computed_property
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TestComputedProperty(unittest.TestCase):
|
|
9
|
+
def test_access_on_class_returns_descriptor(self) -> None:
|
|
10
|
+
class Host:
|
|
11
|
+
def __init__(self) -> None:
|
|
12
|
+
self.x = RefModel(1)
|
|
13
|
+
|
|
14
|
+
@computed_property
|
|
15
|
+
def doubled(self) -> int:
|
|
16
|
+
return self.x.value * 2
|
|
17
|
+
|
|
18
|
+
desc = Host.doubled
|
|
19
|
+
self.assertIsInstance(desc, computed_property)
|
|
20
|
+
self.assertEqual(desc.storage_name, "__computed_doubled")
|
|
21
|
+
|
|
22
|
+
def test_lazy_compute_and_cache(self) -> None:
|
|
23
|
+
calls: list[int] = []
|
|
24
|
+
|
|
25
|
+
class Host:
|
|
26
|
+
def __init__(self) -> None:
|
|
27
|
+
self.x = RefModel(1)
|
|
28
|
+
|
|
29
|
+
@computed_property
|
|
30
|
+
def doubled(self) -> int:
|
|
31
|
+
calls.append(1)
|
|
32
|
+
return self.x.value * 2
|
|
33
|
+
|
|
34
|
+
h = Host()
|
|
35
|
+
self.assertEqual(calls, [])
|
|
36
|
+
self.assertEqual(h.doubled, 2)
|
|
37
|
+
self.assertEqual(calls, [1])
|
|
38
|
+
self.assertEqual(h.doubled, 2)
|
|
39
|
+
self.assertEqual(calls, [1])
|
|
40
|
+
|
|
41
|
+
def test_invalidates_when_dependency_changes(self) -> None:
|
|
42
|
+
class Host:
|
|
43
|
+
def __init__(self) -> None:
|
|
44
|
+
self.x = RefModel(1)
|
|
45
|
+
|
|
46
|
+
@computed_property
|
|
47
|
+
def doubled(self) -> int:
|
|
48
|
+
return self.x.value * 2
|
|
49
|
+
|
|
50
|
+
h = Host()
|
|
51
|
+
self.assertEqual(h.doubled, 2)
|
|
52
|
+
h.x.value = 3
|
|
53
|
+
self.assertEqual(h.doubled, 6)
|
|
54
|
+
|
|
55
|
+
def test_model_stored_on_instance_dict(self) -> None:
|
|
56
|
+
class Host:
|
|
57
|
+
def __init__(self) -> None:
|
|
58
|
+
self.x = RefModel(10)
|
|
59
|
+
|
|
60
|
+
@computed_property
|
|
61
|
+
def label(self) -> str:
|
|
62
|
+
return f"x={self.x.value}"
|
|
63
|
+
|
|
64
|
+
h = Host()
|
|
65
|
+
self.assertNotIn("__computed_label", h.__dict__)
|
|
66
|
+
_ = h.label
|
|
67
|
+
self.assertIn("__computed_label", h.__dict__)
|
|
68
|
+
model = h.__dict__["__computed_label"]
|
|
69
|
+
self.assertEqual(model.value, "x=10")
|
|
70
|
+
|
|
71
|
+
def test_per_instance_independence(self) -> None:
|
|
72
|
+
class Host:
|
|
73
|
+
def __init__(self, n: int) -> None:
|
|
74
|
+
self.x = RefModel(n)
|
|
75
|
+
|
|
76
|
+
@computed_property
|
|
77
|
+
def doubled(self) -> int:
|
|
78
|
+
return self.x.value * 2
|
|
79
|
+
|
|
80
|
+
a = Host(2)
|
|
81
|
+
b = Host(5)
|
|
82
|
+
self.assertEqual(a.doubled, 4)
|
|
83
|
+
self.assertEqual(b.doubled, 10)
|
|
84
|
+
a.x.value = 3
|
|
85
|
+
self.assertEqual(a.doubled, 6)
|
|
86
|
+
self.assertEqual(b.doubled, 10)
|
|
87
|
+
|
|
88
|
+
def test_two_computed_properties_same_instance(self) -> None:
|
|
89
|
+
class Host:
|
|
90
|
+
def __init__(self) -> None:
|
|
91
|
+
self.x = RefModel(2)
|
|
92
|
+
|
|
93
|
+
@computed_property
|
|
94
|
+
def doubled(self) -> int:
|
|
95
|
+
return self.x.value * 2
|
|
96
|
+
|
|
97
|
+
@computed_property
|
|
98
|
+
def tripled(self) -> int:
|
|
99
|
+
return self.x.value * 3
|
|
100
|
+
|
|
101
|
+
h = Host()
|
|
102
|
+
self.assertEqual(h.doubled, 4)
|
|
103
|
+
self.assertEqual(h.tripled, 6)
|
|
104
|
+
h.x.value = 4
|
|
105
|
+
self.assertEqual(h.doubled, 8)
|
|
106
|
+
self.assertEqual(h.tripled, 12)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
if __name__ == "__main__":
|
|
110
|
+
unittest.main()
|
|
File without changes
|
{python_library_reactive_model-0.1.2 → python_library_reactive_model-0.1.3}/reactive_model/track.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|