ex4nicegui 0.8.0__py3-none-any.whl → 0.8.2__py3-none-any.whl

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.
@@ -1,5 +1,5 @@
1
1
  from __future__ import annotations
2
- from typing import Callable, Union, Type, TypeVar
2
+ from typing import Any, Callable, List, Optional, Union, Type, TypeVar, overload
3
3
  from ex4nicegui.utils.signals import (
4
4
  deep_ref,
5
5
  is_ref,
@@ -13,8 +13,16 @@ from ex4nicegui.utils.signals import (
13
13
  from ex4nicegui.utils.types import ReadonlyRef
14
14
  from functools import partial
15
15
  from signe.core.reactive import NoProxy
16
+ from ex4nicegui.utils.proxy.descriptor import class_var_setter
17
+ from ex4nicegui.utils.proxy.bool import BoolProxy
18
+ from ex4nicegui.utils.proxy.int import IntProxy
19
+ from ex4nicegui.utils.proxy.float import FloatProxy
20
+ from ex4nicegui.utils.proxy.base import Proxy
21
+ from ex4nicegui.utils.proxy import to_value_if_base_type_proxy
22
+ from ex4nicegui.utils.proxy.descriptor import ProxyDescriptor
16
23
 
17
24
  _CACHED_VARS_FLAG = "__vm_cached__"
25
+ _LIST_VAR_FLAG = "__vm_list_var__"
18
26
 
19
27
  _T = TypeVar("_T")
20
28
 
@@ -31,15 +39,30 @@ class ViewModel(NoProxy):
31
39
  from ex4nicegui import rxui
32
40
 
33
41
  class MyVm(rxui.ViewModel):
34
- count = rxui.var(0)
35
- data = rxui.var(lambda: [1,2,3])
42
+ count = 0
43
+ data = []
44
+ nums = rxui.list_var(lambda: [1, 2, 3])
45
+
46
+ def __init__(self):
47
+ super().__init__()
48
+ self.data = [1, 2, 3]
49
+
50
+ def increment(self):
51
+ self.count += 1
52
+
53
+ def add_data(self):
54
+ self.data.append(4)
55
+
36
56
 
37
57
  vm = MyVm()
38
58
 
39
59
  rxui.label(vm.count)
40
60
  rxui.number(value=vm.count)
41
61
 
42
-
62
+ ui.button("Increment", on_click=vm.increment)
63
+ ui.button("Add Data", on_click=vm.add_data)
64
+ rxui.label(vm.data)
65
+ rxui.label(vm.nums)
43
66
  """
44
67
 
45
68
  def __init__(self):
@@ -49,8 +72,86 @@ class ViewModel(NoProxy):
49
72
  if callable(value) and hasattr(value, _CACHED_VARS_FLAG):
50
73
  setattr(self, name, computed(partial(value, self)))
51
74
 
75
+ def __init_subclass__(cls) -> None:
76
+ need_vars = (
77
+ (name, value)
78
+ for name, value in vars(cls).items()
79
+ if not name.startswith("_")
80
+ )
81
+
82
+ for name, value in need_vars:
83
+ class_var_setter(cls, name, value, _LIST_VAR_FLAG)
84
+
85
+ @overload
86
+ @staticmethod
87
+ def on_refs_changed(vm: ViewModel): ...
88
+
89
+ @overload
90
+ @staticmethod
91
+ def on_refs_changed(
92
+ vm: ViewModel, callback: Optional[Callable[[], Any]] = None
93
+ ): ...
94
+
95
+ @staticmethod
96
+ def on_refs_changed(vm: ViewModel, callback: Optional[Callable[[], Any]] = None):
97
+ if callback is None:
98
+
99
+ def wrapper(fn: Callable[[], Any]):
100
+ return ViewModel.on_refs_changed(vm, fn)
101
+
102
+ return wrapper
103
+
104
+ refs = ViewModel.get_refs(vm)
105
+
106
+ @on(refs, onchanges=True, deep=False)
107
+ def _():
108
+ callback()
109
+
110
+ @staticmethod
111
+ def get_refs(vm: ViewModel):
112
+ """Get all the refs of a ViewModel.
113
+
114
+ Args:
115
+ vm (ViewModel): The ViewModel to get refs from.
116
+
117
+
118
+ """
119
+ var_names = [
120
+ name
121
+ for name, value in vm.__class__.__dict__.items()
122
+ if isinstance(value, ProxyDescriptor)
123
+ ]
124
+
125
+ return [getattr(vm, name) for name in var_names]
126
+
127
+ @staticmethod
128
+ def to_value(value: Union[ViewModel, Proxy, List, dict]):
129
+ """Convert a ViewModel, Proxy, List, or dict to a value.
130
+
131
+ Args:
132
+ value (Union[ViewModel, Proxy, List, dict]): The value to convert.
133
+
134
+ """
135
+ if isinstance(value, list):
136
+ return [ViewModel.to_value(v) for v in value]
137
+
138
+ if isinstance(value, dict):
139
+ return {k: ViewModel.to_value(v) for k, v in value.items()}
140
+
141
+ if isinstance(value, ViewModel):
142
+ return {
143
+ k: ViewModel.to_value(v)
144
+ for k, v in value.__dict__.items()
145
+ if not k.startswith("_")
146
+ }
147
+
148
+ if isinstance(value, Proxy):
149
+ return to_value_if_base_type_proxy(value)
150
+
151
+ return value
152
+
52
153
  @staticmethod
53
- def display(model: Union[ViewModel, Type]):
154
+ def _display(model: Union[ViewModel, Type]):
54
155
  result = to_ref("")
55
156
 
56
157
  watch_refs = _recursive_to_refs(model)
@@ -67,6 +168,18 @@ class ViewModel(NoProxy):
67
168
 
68
169
  return result
69
170
 
171
+ @staticmethod
172
+ def is_bool(value: Any):
173
+ return isinstance(value, bool) or isinstance(value, BoolProxy)
174
+
175
+ @staticmethod
176
+ def is_int(value: Any):
177
+ return isinstance(value, int) or isinstance(value, IntProxy)
178
+
179
+ @staticmethod
180
+ def is_float(value: Any):
181
+ return isinstance(value, float) or isinstance(value, FloatProxy)
182
+
70
183
 
71
184
  def _recursive_to_value(value_or_model):
72
185
  value = to_raw(to_value(value_or_model))
@@ -139,6 +252,34 @@ def var(value: Union[_T_Var_Value, Callable[[], _T_Var_Value]]) -> Ref[_T_Var_Va
139
252
  return deep_ref(value)
140
253
 
141
254
 
255
+ def list_var(factory: Callable[[], List[_T_Var_Value]]) -> List[_T_Var_Value]:
256
+ """Create implicitly proxied reactive variables for lists. Use them just like ordinary lists while maintaining reactivity. Only use within rxui.ViewModel.
257
+
258
+ Args:
259
+ factory (Callable[[], List[_T_Var_Value]]): A factory function that returns a new list.
260
+
261
+ Example:
262
+ .. code-block:: python
263
+ from ex4nicegui import rxui
264
+ class State(rxui.ViewModel):
265
+ data = rxui.list_var(lambda: [1, 2, 3])
266
+
267
+ def append_data(self):
268
+ self.data.append(len(self.data) + 1)
269
+
270
+ def display_data(self):
271
+ return ",".join(map(str, self.data))
272
+
273
+ state = State()
274
+ ui.button("Append", on_click=state.append_data)
275
+ rxui.label(state.display_data)
276
+
277
+ """
278
+ assert callable(factory), "factory must be a callable"
279
+ setattr(factory, _LIST_VAR_FLAG, None)
280
+ return factory # type: ignore
281
+
282
+
142
283
  def cached_var(func: Callable[..., _T]) -> ReadonlyRef[_T]:
143
284
  """A decorator to cache the result of a function. Only use within rxui.ViewModel.
144
285
 
@@ -149,7 +290,7 @@ def cached_var(func: Callable[..., _T]) -> ReadonlyRef[_T]:
149
290
  .. code-block:: python
150
291
  from ex4nicegui import rxui
151
292
  class MyVm(rxui.ViewModel):
152
- name = rxui.var("John")
293
+ name = "John"
153
294
 
154
295
  @rxui.cached_var
155
296
  def uppper_name(self):
@@ -0,0 +1,19 @@
1
+ from .base import Proxy
2
+
3
+
4
+ def is_base_type_proxy(obj):
5
+ return isinstance(obj, Proxy)
6
+
7
+
8
+ def to_ref_if_base_type_proxy(obj):
9
+ if is_base_type_proxy(obj):
10
+ return obj._ref
11
+ else:
12
+ return obj
13
+
14
+
15
+ def to_value_if_base_type_proxy(obj):
16
+ if is_base_type_proxy(obj):
17
+ return obj._ref.value
18
+ else:
19
+ return obj
@@ -0,0 +1,9 @@
1
+ from typing import Any, Protocol
2
+
3
+
4
+ class ProxyProtocol(Protocol):
5
+ _ref: Any
6
+
7
+
8
+ class Proxy(ProxyProtocol):
9
+ pass
@@ -0,0 +1,58 @@
1
+ from typing import Tuple
2
+ from . import utils
3
+ from .base import Proxy
4
+
5
+
6
+ class BoolProxy(Proxy):
7
+ def __init__(self, value: bool):
8
+ from ex4nicegui.utils.signals import to_ref
9
+
10
+ if isinstance(value, (bool, int)):
11
+ self._ref = to_ref(value)
12
+ else:
13
+ raise ValueError("only accepts boolean or integer values")
14
+
15
+ def __bool__(self):
16
+ return self._ref.value
17
+
18
+ def __str__(self):
19
+ return str(self._ref.value)
20
+
21
+ def __eq__(self, other):
22
+ return self._ref.value.__eq__(utils.to_value(other))
23
+
24
+ def __ne__(self, other):
25
+ return self._ref.value.__ne__(utils.to_value(other))
26
+
27
+ def __lt__(self, value) -> bool:
28
+ return self._ref.value.__lt__(bool(utils.to_value(value)))
29
+
30
+ def __le__(self, value) -> bool:
31
+ return self._ref.value.__le__(bool(utils.to_value(value)))
32
+
33
+ def __gt__(self, value) -> bool:
34
+ return self._ref.value.__gt__(bool(utils.to_value(value)))
35
+
36
+ def __ge__(self, value) -> bool:
37
+ return self._ref.value.__ge__(bool(utils.to_value(value)))
38
+
39
+ def __and__(self, other):
40
+ return self._ref.value.__and__(utils.to_value(other))
41
+
42
+ def __or__(self, other):
43
+ return self._ref.value.__or__(utils.to_value(other))
44
+
45
+ def __xor__(self, other):
46
+ return self._ref.value.__xor__(utils.to_value(other))
47
+
48
+ def __rand__(self, value: bool, /) -> bool:
49
+ return self._ref.value.__rand__(value)
50
+
51
+ def __ror__(self, value: bool, /) -> bool:
52
+ return self._ref.value.__ror__(value)
53
+
54
+ def __rxor__(self, value: bool, /) -> bool:
55
+ return self._ref.value.__rxor__(value)
56
+
57
+ def __getnewargs__(self) -> Tuple[int]:
58
+ return (int(self._ref.value),)
@@ -0,0 +1,88 @@
1
+ from . import utils
2
+ import datetime
3
+ from .base import Proxy
4
+
5
+
6
+ class DateProxy(Proxy, datetime.date):
7
+ def __new__(cls, year, month=None, day=None):
8
+ return super().__new__(cls, year, month, day)
9
+
10
+ def __init__(self, year, month, day):
11
+ from ex4nicegui.utils.signals import to_ref
12
+
13
+ self._ref = to_ref(datetime.date(year, month, day))
14
+
15
+ def __str__(self):
16
+ return str(self._ref.value)
17
+
18
+ def ctime(self):
19
+ return self._ref.value.ctime()
20
+
21
+ def strftime(self, fmt):
22
+ return self._ref.value.strftime(fmt)
23
+
24
+ def __format__(self, fmt):
25
+ return self._ref.value.__format__(fmt)
26
+
27
+ def isoformat(self):
28
+ return self._ref.value.isoformat()
29
+
30
+ @property
31
+ def year(self):
32
+ return self._ref.value.year
33
+
34
+ @property
35
+ def month(self):
36
+ return self._ref.value.month
37
+
38
+ @property
39
+ def day(self):
40
+ return self._ref.value.day
41
+
42
+ def timetuple(self):
43
+ return self._ref.value.timetuple()
44
+
45
+ def toordinal(self):
46
+ return self._ref.value.toordinal()
47
+
48
+ def replace(self, year=None, month=None, day=None):
49
+ return self._ref.value.replace(year=year, month=month, day=day)
50
+
51
+ def __eq__(self, other):
52
+ return self._ref.value.__eq__(utils.to_value(other))
53
+
54
+ def __le__(self, other):
55
+ return self._ref.value.__le__(utils.to_value(other))
56
+
57
+ def __lt__(self, other):
58
+ return self._ref.value.__lt__(utils.to_value(other))
59
+
60
+ def __ge__(self, other):
61
+ return self._ref.value.__ge__(utils.to_value(other))
62
+
63
+ def __gt__(self, other):
64
+ return self._ref.value.__gt__(utils.to_value(other))
65
+
66
+ def __hash__(self):
67
+ return self._ref.value.__hash__()
68
+
69
+ def __add__(self, other):
70
+ return self._ref.value.__add__(utils.to_value(other))
71
+
72
+ def __radd__(self, other):
73
+ return self._ref.value.__radd__(utils.to_value(other))
74
+
75
+ def __sub__(self, other):
76
+ return self._ref.value.__sub__(utils.to_value(other))
77
+
78
+ def weekday(self):
79
+ return self._ref.value.weekday()
80
+
81
+ def isoweekday(self):
82
+ return self._ref.value.isoweekday()
83
+
84
+ def isocalendar(self):
85
+ return self._ref.value.isocalendar()
86
+
87
+ def __reduce__(self):
88
+ return self._ref.value.__reduce__()
@@ -0,0 +1,126 @@
1
+ import sys
2
+ from typing import Any, Callable, Dict, List, Optional, Type, TypeVar, Generic
3
+ from .base import ProxyProtocol
4
+ from .int import IntProxy
5
+ from .list import ListProxy
6
+ from .string import StringProxy
7
+ from .float import FloatProxy
8
+ from .bool import BoolProxy
9
+ from .date import DateProxy
10
+ from .dict import DictProxy
11
+ import datetime
12
+ import warnings
13
+ from . import to_value_if_base_type_proxy
14
+
15
+ T = TypeVar("T")
16
+
17
+
18
+ class ProxyDescriptor(Generic[T]):
19
+ def __init__(
20
+ self,
21
+ name: str,
22
+ value: T,
23
+ proxy_builder: Callable[[T], ProxyProtocol],
24
+ proxy_rebuilder: Optional[Callable[[ProxyProtocol], ProxyProtocol]] = None,
25
+ ) -> None:
26
+ self.value = value
27
+ self.name = name
28
+ self._proxy_builder = proxy_builder
29
+ self._proxy_rebuilder = proxy_rebuilder
30
+
31
+ def __get__(self, instance: object, owner: Any):
32
+ if instance is None:
33
+ return self
34
+
35
+ proxy = instance.__dict__.get(self.name, None)
36
+ if proxy is None:
37
+ proxy = self._proxy_builder(self.value)
38
+ instance.__dict__[self.name] = proxy
39
+ else:
40
+ proxy = self._proxy_rebuilder(proxy) if self._proxy_rebuilder else proxy
41
+ proxy._ref.value # type: ignore
42
+ return proxy
43
+
44
+ def __set__(self, instance: object, value: T) -> None:
45
+ value = to_value_if_base_type_proxy(value)
46
+ proxy = instance.__dict__.get(self.name, None)
47
+ if proxy is None:
48
+ proxy = self._proxy_builder(value) # type: ignore
49
+ instance.__dict__[self.name] = proxy
50
+
51
+ proxy._ref.value = value # type: ignore
52
+
53
+
54
+ class IntDescriptor(ProxyDescriptor[int]):
55
+ def __init__(self, name: str, value: int) -> None:
56
+ super().__init__(name, value, IntProxy)
57
+
58
+
59
+ class ListDescriptor(ProxyDescriptor[Callable[[], List]]):
60
+ def __init__(self, name: str, value: Callable[[], List]) -> None:
61
+ super().__init__(name, value, lambda x: ListProxy(x() if callable(x) else x))
62
+
63
+
64
+ class DictDescriptor(ProxyDescriptor[Dict]):
65
+ def __init__(self, name: str, value: Dict) -> None:
66
+ super().__init__(name, value, DictProxy)
67
+
68
+
69
+ class StringDescriptor(ProxyDescriptor[str]):
70
+ def __init__(self, name: str, value: str) -> None:
71
+ def rebuild_proxy(proxy: ProxyProtocol) -> ProxyProtocol:
72
+ sp = StringProxy(proxy._ref.value)
73
+ sp._ref = proxy._ref
74
+ return sp
75
+
76
+ super().__init__(name, value, StringProxy, rebuild_proxy)
77
+
78
+
79
+ class FloatDescriptor(ProxyDescriptor[float]):
80
+ def __init__(self, name: str, value: float) -> None:
81
+ super().__init__(name, value, FloatProxy)
82
+
83
+
84
+ class BoolDescriptor(ProxyDescriptor[bool]):
85
+ def __init__(self, name: str, value: bool) -> None:
86
+ super().__init__(name, value, BoolProxy)
87
+
88
+
89
+ class DateDescriptor(ProxyDescriptor[datetime.date]):
90
+ def __init__(self, name: str, value: datetime.date) -> None:
91
+ super().__init__(name, value, lambda x: DateProxy(x.year, x.month, x.day))
92
+
93
+
94
+ def class_var_setter(cls: Type, name: str, value, list_var_flat: str) -> None:
95
+ if value is None or isinstance(value, str):
96
+ setattr(cls, name, StringDescriptor(name, value))
97
+ elif isinstance(value, bool):
98
+ setattr(cls, name, BoolDescriptor(name, value))
99
+ elif isinstance(value, int):
100
+ setattr(cls, name, IntDescriptor(name, value))
101
+ elif callable(value) and hasattr(value, list_var_flat):
102
+ setattr(cls, name, ListDescriptor(name, value))
103
+
104
+ elif isinstance(value, list):
105
+ if len(value) > 0:
106
+ with warnings.catch_warnings():
107
+ warnings.showwarning = _custom_showwarning
108
+ warnings.warn(
109
+ f"The variable [{cls.__name__}.{name}] will be empty list.you should initialize it in the constructor,or use list_var for definition.\n {name} = rxui.list_var(lambda:[1,2,3])",
110
+ stacklevel=3,
111
+ )
112
+ setattr(cls, name, ListDescriptor(name, lambda: []))
113
+
114
+ elif isinstance(value, dict):
115
+ pass # TODO
116
+ # setattr(cls, name, DictDescriptor(name, value))
117
+ elif isinstance(value, float):
118
+ setattr(cls, name, FloatDescriptor(name, value))
119
+ elif isinstance(value, datetime.date):
120
+ setattr(cls, name, DateDescriptor(name, value))
121
+ else:
122
+ pass
123
+
124
+
125
+ def _custom_showwarning(message, category, filename, lineno, file=None, line=None):
126
+ sys.stderr.write(f"{filename}:{lineno}: {category.__name__}: {message}\n")
@@ -0,0 +1,110 @@
1
+ import sys
2
+ from typing import Dict, Iterator, TypeVar, Generic
3
+ import datetime
4
+ from .string import StringProxy
5
+ from .int import IntProxy
6
+ from .float import FloatProxy
7
+ from .bool import BoolProxy
8
+ from .date import DateProxy
9
+
10
+ from signe import to_raw
11
+ from ex4nicegui.utils.signals import to_ref_wrapper
12
+
13
+
14
+ _KT = TypeVar("_KT")
15
+ _VT = TypeVar("_VT")
16
+
17
+
18
+ class DictProxy(dict, Generic[_KT, _VT]):
19
+ def __init__(self, value: Dict):
20
+ from ex4nicegui.utils.signals import deep_ref
21
+
22
+ super().__init__(value)
23
+ self._ref = deep_ref(value)
24
+
25
+ self.__key_cache = {}
26
+
27
+ def copy(self) -> Dict[_KT, _VT]:
28
+ return to_raw(self._ref.value).copy()
29
+
30
+ def keys(self):
31
+ return to_raw(self._ref.value).keys()
32
+
33
+ def values(self):
34
+ return to_raw(self._ref.value).values()
35
+
36
+ def items(self):
37
+ return to_raw(self._ref.value).items()
38
+
39
+ def get(self, key: _KT, default, /):
40
+ return self._ref.value.get(key, default)
41
+
42
+ def pop(self, key: _KT, default, /):
43
+ return self._ref.value.pop(key, default)
44
+
45
+ def __len__(self) -> int:
46
+ return self._ref.value.__len__()
47
+
48
+ def __getitem__(self, key: _KT, /):
49
+ print("getitem")
50
+ value = self._ref.value.__getitem__(key)
51
+
52
+ if isinstance(value, str):
53
+ if key not in self.__key_cache:
54
+ self.__key_cache[key] = StringProxy(value)
55
+
56
+ sp = self.__key_cache[key]
57
+
58
+ def getter():
59
+ return to_raw(self._ref.value)[key]
60
+
61
+ def setter(v):
62
+ self._ref.value[key] = v
63
+
64
+ sp._ref = to_ref_wrapper(getter, setter)
65
+
66
+ return sp
67
+ elif isinstance(value, int):
68
+ return IntProxy(value)
69
+ elif isinstance(value, float):
70
+ return FloatProxy(value)
71
+ elif isinstance(value, bool):
72
+ return BoolProxy(value)
73
+ elif isinstance(value, datetime.date):
74
+ return DateProxy(value.year, value.month, value.day)
75
+ else:
76
+ pass
77
+
78
+ return value
79
+
80
+ def __setitem__(self, key: _KT, value: _VT, /) -> None:
81
+ self._ref.value.__setitem__(key, value)
82
+
83
+ def __delitem__(self, key: _KT, /) -> None:
84
+ self._ref.value.__delitem__(key)
85
+
86
+ def __iter__(self) -> Iterator[_KT]:
87
+ return self._ref.value.__iter__()
88
+
89
+ def __eq__(self, value: object, /) -> bool:
90
+ return self._ref.value.__eq__(value)
91
+
92
+ def __reversed__(self) -> Iterator[_KT]:
93
+ return self._ref.value.__reversed__()
94
+
95
+ def __str__(self) -> str:
96
+ return self._ref.value.__str__()
97
+
98
+ def __contains__(self, key: object) -> bool:
99
+ return self._ref.value.__contains__(key)
100
+
101
+ if sys.version_info >= (3, 9):
102
+
103
+ def __or__(self, value, /):
104
+ return self._ref.value.__or__(value)
105
+
106
+ def __ror__(self, value, /):
107
+ return self._ref.value.__ror__(value)
108
+
109
+ def __ior__(self, value, /):
110
+ self._ref.value.update(value)