python-redux 0.25.2__cp314-cp314-win_arm64.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.
- python_redux-0.25.2.dist-info/METADATA +340 -0
- python_redux-0.25.2.dist-info/RECORD +30 -0
- python_redux-0.25.2.dist-info/WHEEL +5 -0
- python_redux-0.25.2.dist-info/licenses/LICENSE +201 -0
- python_redux-0.25.2.dist-info/top_level.txt +1 -0
- redux/__init__.c +5018 -0
- redux/__init__.cp314-win_arm64.pyd +0 -0
- redux/__init__.py +65 -0
- redux/autorun.c +19337 -0
- redux/autorun.cp314-win_arm64.pyd +0 -0
- redux/autorun.py +410 -0
- redux/basic_types.c +19301 -0
- redux/basic_types.cp314-win_arm64.pyd +0 -0
- redux/basic_types.py +514 -0
- redux/combine_reducers.c +11828 -0
- redux/combine_reducers.cp314-win_arm64.pyd +0 -0
- redux/combine_reducers.py +160 -0
- redux/main.c +22250 -0
- redux/main.cp314-win_arm64.pyd +0 -0
- redux/main.py +473 -0
- redux/py.typed +1 -0
- redux/serialization_mixin.c +9331 -0
- redux/serialization_mixin.cp314-win_arm64.pyd +0 -0
- redux/serialization_mixin.py +44 -0
- redux/side_effect_runner.c +13641 -0
- redux/side_effect_runner.cp314-win_arm64.pyd +0 -0
- redux/side_effect_runner.py +87 -0
- redux/utils.c +9301 -0
- redux/utils.cp314-win_arm64.pyd +0 -0
- redux/utils.py +49 -0
|
Binary file
|
redux/autorun.py
ADDED
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
"""Redux autorun module."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import functools
|
|
7
|
+
import inspect
|
|
8
|
+
import weakref
|
|
9
|
+
from asyncio import iscoroutine, iscoroutinefunction
|
|
10
|
+
from typing import (
|
|
11
|
+
TYPE_CHECKING,
|
|
12
|
+
Any,
|
|
13
|
+
Concatenate,
|
|
14
|
+
Generic,
|
|
15
|
+
Literal,
|
|
16
|
+
cast,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
from redux.basic_types import (
|
|
20
|
+
NOT_SET,
|
|
21
|
+
Action,
|
|
22
|
+
Args,
|
|
23
|
+
AutoAwait,
|
|
24
|
+
AutorunOptionsType,
|
|
25
|
+
ComparatorOutput,
|
|
26
|
+
Event,
|
|
27
|
+
ReturnType,
|
|
28
|
+
SelectorOutput,
|
|
29
|
+
State,
|
|
30
|
+
T,
|
|
31
|
+
)
|
|
32
|
+
from redux.utils import call_func, signature_without_selector
|
|
33
|
+
|
|
34
|
+
if TYPE_CHECKING:
|
|
35
|
+
from collections.abc import Callable, Coroutine, Generator
|
|
36
|
+
|
|
37
|
+
from redux.main import Store
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class AwaitableWrapper(Generic[T]):
|
|
41
|
+
"""A wrapper for a coroutine to track if it has been awaited."""
|
|
42
|
+
|
|
43
|
+
_unawaited = object()
|
|
44
|
+
value: tuple[Literal[False], None] | tuple[Literal[True], T]
|
|
45
|
+
|
|
46
|
+
def __init__(self, coro: Coroutine[None, None, T]) -> None:
|
|
47
|
+
"""Initialize the AwaitableWrapper with a coroutine."""
|
|
48
|
+
self.coro = coro
|
|
49
|
+
self.value = (False, None)
|
|
50
|
+
|
|
51
|
+
def __await__(self) -> Generator[None, None, T]:
|
|
52
|
+
"""Await the coroutine and set the awaited flag to True."""
|
|
53
|
+
return self._wrap().__await__()
|
|
54
|
+
|
|
55
|
+
async def _wrap(self) -> T:
|
|
56
|
+
"""Wrap the coroutine and set the awaited flag to True."""
|
|
57
|
+
if self.value[0] is True:
|
|
58
|
+
return self.value[1]
|
|
59
|
+
self.value = (True, await self.coro)
|
|
60
|
+
return self.value[1]
|
|
61
|
+
|
|
62
|
+
def close(self) -> None:
|
|
63
|
+
"""Close the coroutine if it has not been awaited."""
|
|
64
|
+
self.coro.close()
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def awaited(self) -> bool:
|
|
68
|
+
"""Check if the coroutine has been awaited."""
|
|
69
|
+
return self.value[0] is True
|
|
70
|
+
|
|
71
|
+
def __repr__(self) -> str:
|
|
72
|
+
"""Return a string representation of the AwaitableWrapper."""
|
|
73
|
+
return f'AwaitableWrapper({self.coro}, awaited={self.awaited})'
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class Autorun(
|
|
77
|
+
Generic[
|
|
78
|
+
State,
|
|
79
|
+
Action,
|
|
80
|
+
Event,
|
|
81
|
+
SelectorOutput,
|
|
82
|
+
ComparatorOutput,
|
|
83
|
+
Args,
|
|
84
|
+
ReturnType,
|
|
85
|
+
],
|
|
86
|
+
):
|
|
87
|
+
"""Run a wrapped function in response to specific state changes in the store."""
|
|
88
|
+
|
|
89
|
+
def __init__( # noqa: C901, PLR0912
|
|
90
|
+
self: Autorun,
|
|
91
|
+
*,
|
|
92
|
+
store: Store[State, Action, Event],
|
|
93
|
+
selector: Callable[[State], SelectorOutput],
|
|
94
|
+
comparator: Callable[[State], Any] | None,
|
|
95
|
+
func: Callable[
|
|
96
|
+
Concatenate[SelectorOutput, Args],
|
|
97
|
+
ReturnType,
|
|
98
|
+
],
|
|
99
|
+
options: AutorunOptionsType[ReturnType, AutoAwait],
|
|
100
|
+
) -> None:
|
|
101
|
+
"""Initialize the Autorun instance."""
|
|
102
|
+
if hasattr(func, '__name__'):
|
|
103
|
+
self.__name__ = f'Autorun:{func.__name__}'
|
|
104
|
+
else:
|
|
105
|
+
self.__name__ = f'Autorun:{func}'
|
|
106
|
+
if hasattr(func, '__qualname__'):
|
|
107
|
+
self.__qualname__ = f'Autorun:{func.__qualname__}'
|
|
108
|
+
else:
|
|
109
|
+
self.__qualname__ = f'Autorun:{func}'
|
|
110
|
+
self.__signature__ = signature_without_selector(func)
|
|
111
|
+
self.__module__ = func.__module__
|
|
112
|
+
if (annotations := getattr(func, '__annotations__', None)) is not None:
|
|
113
|
+
self.__annotations__ = annotations
|
|
114
|
+
if (defaults := getattr(func, '__defaults__', None)) is not None:
|
|
115
|
+
self.__defaults__ = defaults
|
|
116
|
+
if (kwdefaults := getattr(func, '__kwdefaults__', None)) is not None:
|
|
117
|
+
self.__kwdefaults__ = kwdefaults
|
|
118
|
+
|
|
119
|
+
self._store = store
|
|
120
|
+
self._selector = selector
|
|
121
|
+
self._comparator = comparator
|
|
122
|
+
self._should_be_called = False
|
|
123
|
+
|
|
124
|
+
if options.keep_ref:
|
|
125
|
+
self._func = func
|
|
126
|
+
elif inspect.ismethod(func):
|
|
127
|
+
self._func = weakref.WeakMethod(func, self.unsubscribe)
|
|
128
|
+
else:
|
|
129
|
+
self._func = weakref.ref(func, self.unsubscribe)
|
|
130
|
+
self._is_coroutine = (
|
|
131
|
+
asyncio.coroutines._is_coroutine # type: ignore [reportAttributeAccessIssue] # noqa: SLF001
|
|
132
|
+
if asyncio.iscoroutinefunction(func) and options.auto_await is False
|
|
133
|
+
else None
|
|
134
|
+
)
|
|
135
|
+
self._options = options
|
|
136
|
+
|
|
137
|
+
self._last_selector_result: SelectorOutput | None = NOT_SET
|
|
138
|
+
self._last_comparator_result: ComparatorOutput = cast(
|
|
139
|
+
'ComparatorOutput',
|
|
140
|
+
object(),
|
|
141
|
+
)
|
|
142
|
+
if iscoroutinefunction(func):
|
|
143
|
+
|
|
144
|
+
async def default_value_wrapper() -> ReturnType | None:
|
|
145
|
+
return options.default_value
|
|
146
|
+
|
|
147
|
+
default_value = default_value_wrapper()
|
|
148
|
+
|
|
149
|
+
self._create_task(default_value)
|
|
150
|
+
self._latest_value: ReturnType = default_value
|
|
151
|
+
else:
|
|
152
|
+
self._latest_value: ReturnType = options.default_value
|
|
153
|
+
self._subscriptions: set[
|
|
154
|
+
Callable[[ReturnType], Any] | weakref.ref[Callable[[ReturnType], Any]]
|
|
155
|
+
] = set()
|
|
156
|
+
|
|
157
|
+
if (
|
|
158
|
+
store.with_state(lambda state: state, ignore_uninitialized_store=True)(
|
|
159
|
+
self.check,
|
|
160
|
+
)()
|
|
161
|
+
and self._options.initial_call
|
|
162
|
+
):
|
|
163
|
+
self._should_be_called = False
|
|
164
|
+
self.call()
|
|
165
|
+
|
|
166
|
+
if self._options.reactive:
|
|
167
|
+
self._unsubscribe = store._subscribe(self.react) # noqa: SLF001
|
|
168
|
+
else:
|
|
169
|
+
self._unsubscribe = None
|
|
170
|
+
|
|
171
|
+
def _create_task(self: Autorun, coro: Coroutine[None, None, Any]) -> None:
|
|
172
|
+
"""Create a task for the coroutine."""
|
|
173
|
+
if self._store.store_options.task_creator:
|
|
174
|
+
self._store.store_options.task_creator(coro)
|
|
175
|
+
|
|
176
|
+
def react(
|
|
177
|
+
self: Autorun,
|
|
178
|
+
state: State,
|
|
179
|
+
) -> None:
|
|
180
|
+
"""React to state changes in the store."""
|
|
181
|
+
if self._options.reactive and self.check(state):
|
|
182
|
+
self._should_be_called = False
|
|
183
|
+
self.call()
|
|
184
|
+
|
|
185
|
+
def unsubscribe(
|
|
186
|
+
self: Autorun[
|
|
187
|
+
State,
|
|
188
|
+
Action,
|
|
189
|
+
Event,
|
|
190
|
+
SelectorOutput,
|
|
191
|
+
ComparatorOutput,
|
|
192
|
+
Args,
|
|
193
|
+
ReturnType,
|
|
194
|
+
],
|
|
195
|
+
_: weakref.ref | None = None,
|
|
196
|
+
) -> None:
|
|
197
|
+
"""Unsubscribe the autorun from the store and clean up resources."""
|
|
198
|
+
if self._unsubscribe:
|
|
199
|
+
self._unsubscribe()
|
|
200
|
+
self._unsubscribe = None
|
|
201
|
+
|
|
202
|
+
def inform_subscribers(
|
|
203
|
+
self: Autorun[
|
|
204
|
+
State,
|
|
205
|
+
Action,
|
|
206
|
+
Event,
|
|
207
|
+
SelectorOutput,
|
|
208
|
+
ComparatorOutput,
|
|
209
|
+
Args,
|
|
210
|
+
ReturnType,
|
|
211
|
+
],
|
|
212
|
+
) -> None:
|
|
213
|
+
"""Inform all subscribers about the latest value."""
|
|
214
|
+
for subscriber_ in self._subscriptions.copy():
|
|
215
|
+
if isinstance(subscriber_, weakref.ref):
|
|
216
|
+
subscriber = subscriber_()
|
|
217
|
+
if subscriber is None:
|
|
218
|
+
self._subscriptions.discard(subscriber_)
|
|
219
|
+
continue
|
|
220
|
+
else:
|
|
221
|
+
subscriber = subscriber_
|
|
222
|
+
subscriber(self._latest_value)
|
|
223
|
+
|
|
224
|
+
def check(
|
|
225
|
+
self: Autorun[
|
|
226
|
+
State,
|
|
227
|
+
Action,
|
|
228
|
+
Event,
|
|
229
|
+
SelectorOutput,
|
|
230
|
+
ComparatorOutput,
|
|
231
|
+
Args,
|
|
232
|
+
ReturnType,
|
|
233
|
+
],
|
|
234
|
+
state: State,
|
|
235
|
+
) -> bool:
|
|
236
|
+
"""Check if the autorun should be called based on the current state."""
|
|
237
|
+
try:
|
|
238
|
+
selector_result = self._selector(state)
|
|
239
|
+
except AttributeError:
|
|
240
|
+
return False
|
|
241
|
+
if self._comparator is None:
|
|
242
|
+
comparator_result = cast('ComparatorOutput', selector_result)
|
|
243
|
+
else:
|
|
244
|
+
try:
|
|
245
|
+
comparator_result = self._comparator(state)
|
|
246
|
+
except AttributeError:
|
|
247
|
+
return False
|
|
248
|
+
self._should_be_called = (
|
|
249
|
+
self._should_be_called or comparator_result != self._last_comparator_result
|
|
250
|
+
)
|
|
251
|
+
self._last_selector_result = selector_result
|
|
252
|
+
self._last_comparator_result = comparator_result
|
|
253
|
+
return self._should_be_called
|
|
254
|
+
|
|
255
|
+
def call(
|
|
256
|
+
self: Autorun[
|
|
257
|
+
State,
|
|
258
|
+
Action,
|
|
259
|
+
Event,
|
|
260
|
+
SelectorOutput,
|
|
261
|
+
ComparatorOutput,
|
|
262
|
+
Args,
|
|
263
|
+
ReturnType,
|
|
264
|
+
],
|
|
265
|
+
*args: Args.args,
|
|
266
|
+
**kwargs: Args.kwargs,
|
|
267
|
+
) -> None:
|
|
268
|
+
"""Call the wrapped function with the current state of the store."""
|
|
269
|
+
func = self._func() if isinstance(self._func, weakref.ref) else self._func
|
|
270
|
+
if func and self._last_selector_result is not NOT_SET:
|
|
271
|
+
value: ReturnType = call_func(
|
|
272
|
+
func,
|
|
273
|
+
[self._last_selector_result],
|
|
274
|
+
*args,
|
|
275
|
+
**kwargs,
|
|
276
|
+
)
|
|
277
|
+
previous_value = self._latest_value
|
|
278
|
+
if iscoroutine(value):
|
|
279
|
+
if (
|
|
280
|
+
self._options.auto_await
|
|
281
|
+
is False # only explicit `False` disables auto-await, not `None`
|
|
282
|
+
):
|
|
283
|
+
if (
|
|
284
|
+
self._latest_value is not NOT_SET
|
|
285
|
+
and isinstance(self._latest_value, AwaitableWrapper)
|
|
286
|
+
and not self._latest_value.awaited
|
|
287
|
+
):
|
|
288
|
+
self._latest_value.close()
|
|
289
|
+
self._latest_value = cast('ReturnType', AwaitableWrapper(value))
|
|
290
|
+
else:
|
|
291
|
+
self._latest_value = cast('ReturnType', None)
|
|
292
|
+
self._create_task(value)
|
|
293
|
+
else:
|
|
294
|
+
self._latest_value = value
|
|
295
|
+
if self._latest_value is not previous_value:
|
|
296
|
+
self.inform_subscribers()
|
|
297
|
+
|
|
298
|
+
def __call__(
|
|
299
|
+
self: Autorun[
|
|
300
|
+
State,
|
|
301
|
+
Action,
|
|
302
|
+
Event,
|
|
303
|
+
SelectorOutput,
|
|
304
|
+
ComparatorOutput,
|
|
305
|
+
Args,
|
|
306
|
+
ReturnType,
|
|
307
|
+
],
|
|
308
|
+
*args: Args.args,
|
|
309
|
+
**kwargs: Args.kwargs,
|
|
310
|
+
) -> ReturnType:
|
|
311
|
+
"""Call the wrapped function with the current state of the store."""
|
|
312
|
+
self._store.with_state(lambda state: state, ignore_uninitialized_store=True)(
|
|
313
|
+
self.check,
|
|
314
|
+
)()
|
|
315
|
+
if self._should_be_called or args or kwargs or not self._options.memoization:
|
|
316
|
+
self._should_be_called = False
|
|
317
|
+
self.call(*args, **kwargs)
|
|
318
|
+
return self._latest_value
|
|
319
|
+
|
|
320
|
+
def __repr__(
|
|
321
|
+
self: Autorun[
|
|
322
|
+
State,
|
|
323
|
+
Action,
|
|
324
|
+
Event,
|
|
325
|
+
SelectorOutput,
|
|
326
|
+
ComparatorOutput,
|
|
327
|
+
Args,
|
|
328
|
+
ReturnType,
|
|
329
|
+
],
|
|
330
|
+
) -> str:
|
|
331
|
+
"""Return a string representation of the Autorun instance."""
|
|
332
|
+
return (
|
|
333
|
+
super().__repr__()
|
|
334
|
+
+ f'(func: {self._func}, last_value: {self._latest_value})'
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
@property
|
|
338
|
+
def value(
|
|
339
|
+
self: Autorun[
|
|
340
|
+
State,
|
|
341
|
+
Action,
|
|
342
|
+
Event,
|
|
343
|
+
SelectorOutput,
|
|
344
|
+
ComparatorOutput,
|
|
345
|
+
Args,
|
|
346
|
+
ReturnType,
|
|
347
|
+
],
|
|
348
|
+
) -> ReturnType:
|
|
349
|
+
"""Get the latest value of the autorun function."""
|
|
350
|
+
return self._latest_value
|
|
351
|
+
|
|
352
|
+
def subscribe(
|
|
353
|
+
self: Autorun[
|
|
354
|
+
State,
|
|
355
|
+
Action,
|
|
356
|
+
Event,
|
|
357
|
+
SelectorOutput,
|
|
358
|
+
ComparatorOutput,
|
|
359
|
+
Args,
|
|
360
|
+
ReturnType,
|
|
361
|
+
],
|
|
362
|
+
callback: Callable[[ReturnType], Any],
|
|
363
|
+
*,
|
|
364
|
+
initial_run: bool | None = None,
|
|
365
|
+
keep_ref: bool | None = None,
|
|
366
|
+
) -> Callable[[], None]:
|
|
367
|
+
"""Subscribe to the autorun to be notified of changes in the state."""
|
|
368
|
+
if initial_run is None:
|
|
369
|
+
initial_run = self._options.subscribers_initial_run
|
|
370
|
+
if keep_ref is None:
|
|
371
|
+
keep_ref = self._options.subscribers_keep_ref
|
|
372
|
+
if keep_ref:
|
|
373
|
+
callback_ref = callback
|
|
374
|
+
elif inspect.ismethod(callback):
|
|
375
|
+
callback_ref = weakref.WeakMethod(callback)
|
|
376
|
+
else:
|
|
377
|
+
callback_ref = weakref.ref(callback)
|
|
378
|
+
self._subscriptions.add(callback_ref)
|
|
379
|
+
|
|
380
|
+
if initial_run and self.value is not NOT_SET:
|
|
381
|
+
callback(self.value)
|
|
382
|
+
|
|
383
|
+
def unsubscribe() -> None:
|
|
384
|
+
self._subscriptions.discard(callback_ref)
|
|
385
|
+
|
|
386
|
+
return unsubscribe
|
|
387
|
+
|
|
388
|
+
def __get__(
|
|
389
|
+
self: Autorun[
|
|
390
|
+
State,
|
|
391
|
+
Action,
|
|
392
|
+
Event,
|
|
393
|
+
SelectorOutput,
|
|
394
|
+
ComparatorOutput,
|
|
395
|
+
Args,
|
|
396
|
+
ReturnType,
|
|
397
|
+
],
|
|
398
|
+
obj: object | None,
|
|
399
|
+
_: type | None = None,
|
|
400
|
+
) -> Autorun[
|
|
401
|
+
State,
|
|
402
|
+
Action,
|
|
403
|
+
Event,
|
|
404
|
+
SelectorOutput,
|
|
405
|
+
ComparatorOutput,
|
|
406
|
+
Args,
|
|
407
|
+
ReturnType,
|
|
408
|
+
]:
|
|
409
|
+
"""Get the autorun instance."""
|
|
410
|
+
return cast('Autorun', functools.partial(self, obj))
|