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/main.py
ADDED
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
"""Redux store for managing state and side effects."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import contextlib
|
|
7
|
+
import inspect
|
|
8
|
+
import queue
|
|
9
|
+
import time
|
|
10
|
+
import weakref
|
|
11
|
+
from collections import defaultdict
|
|
12
|
+
from collections.abc import Awaitable, Iterable, Sequence
|
|
13
|
+
from functools import wraps
|
|
14
|
+
from threading import Lock, Thread
|
|
15
|
+
from typing import (
|
|
16
|
+
TYPE_CHECKING,
|
|
17
|
+
Any,
|
|
18
|
+
Concatenate,
|
|
19
|
+
Generic,
|
|
20
|
+
cast,
|
|
21
|
+
overload,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
from redux.basic_types import (
|
|
25
|
+
Action,
|
|
26
|
+
ActionMiddleware,
|
|
27
|
+
Args,
|
|
28
|
+
AutoAwait,
|
|
29
|
+
AutorunDecorator,
|
|
30
|
+
AutorunOptions,
|
|
31
|
+
AutorunOptionsType,
|
|
32
|
+
AutorunReturnType,
|
|
33
|
+
AwaitableOrNot,
|
|
34
|
+
BaseAction,
|
|
35
|
+
BaseEvent,
|
|
36
|
+
ComparatorOutput,
|
|
37
|
+
DispatchParameters,
|
|
38
|
+
Event,
|
|
39
|
+
EventHandler,
|
|
40
|
+
EventMiddleware,
|
|
41
|
+
FinishAction,
|
|
42
|
+
FinishEvent,
|
|
43
|
+
InitAction,
|
|
44
|
+
MethodSelf,
|
|
45
|
+
ReducerType,
|
|
46
|
+
ReturnType,
|
|
47
|
+
SelectorOutput,
|
|
48
|
+
SnapshotAtom,
|
|
49
|
+
State,
|
|
50
|
+
StoreOptions,
|
|
51
|
+
StrictEvent,
|
|
52
|
+
SubscribeEventCleanup,
|
|
53
|
+
ViewDecorator,
|
|
54
|
+
ViewOptions,
|
|
55
|
+
ViewReturnType,
|
|
56
|
+
WithStateDecorator,
|
|
57
|
+
is_complete_reducer_result,
|
|
58
|
+
is_state_reducer_result,
|
|
59
|
+
)
|
|
60
|
+
from redux.serialization_mixin import SerializationMixin
|
|
61
|
+
from redux.utils import call_func, signature_without_selector
|
|
62
|
+
|
|
63
|
+
if TYPE_CHECKING:
|
|
64
|
+
from collections.abc import Callable
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class Store(SerializationMixin, Generic[State, Action, Event]):
|
|
68
|
+
"""Redux store for managing state and side effects."""
|
|
69
|
+
|
|
70
|
+
def __init__(
|
|
71
|
+
self,
|
|
72
|
+
reducer: ReducerType[State, Action | InitAction, Event | None],
|
|
73
|
+
options: StoreOptions[Action, Event] | None = None,
|
|
74
|
+
) -> None:
|
|
75
|
+
"""Create a new store."""
|
|
76
|
+
self.store_options = options or StoreOptions()
|
|
77
|
+
self.reducer = reducer
|
|
78
|
+
|
|
79
|
+
self._action_middlewares = list(self.store_options.action_middlewares)
|
|
80
|
+
self._event_middlewares = list(self.store_options.event_middlewares)
|
|
81
|
+
|
|
82
|
+
self._state: State | None = None
|
|
83
|
+
self._listeners: set[
|
|
84
|
+
Callable[[State], AwaitableOrNot[None]]
|
|
85
|
+
| weakref.ref[Callable[[State], AwaitableOrNot[None]]]
|
|
86
|
+
] = set()
|
|
87
|
+
self._event_handlers: defaultdict[
|
|
88
|
+
type[Event | FinishEvent],
|
|
89
|
+
set[EventHandler | weakref.ref[EventHandler]],
|
|
90
|
+
] = defaultdict(set)
|
|
91
|
+
|
|
92
|
+
self._actions: list[Action | InitAction] = []
|
|
93
|
+
self._events: list[Event | FinishEvent] = []
|
|
94
|
+
|
|
95
|
+
self._event_handlers_queue = queue.Queue[
|
|
96
|
+
tuple[EventHandler[Event | FinishEvent], Event | FinishEvent] | None
|
|
97
|
+
]()
|
|
98
|
+
self._workers = [
|
|
99
|
+
self.store_options.side_effect_runner_class(
|
|
100
|
+
task_queue=self._event_handlers_queue,
|
|
101
|
+
create_task=self.store_options.task_creator,
|
|
102
|
+
)
|
|
103
|
+
for _ in range(self.store_options.side_effect_threads)
|
|
104
|
+
]
|
|
105
|
+
for worker in self._workers:
|
|
106
|
+
worker.start()
|
|
107
|
+
|
|
108
|
+
self._is_running = Lock()
|
|
109
|
+
|
|
110
|
+
if self.store_options.auto_init:
|
|
111
|
+
if self.store_options.scheduler:
|
|
112
|
+
self.store_options.scheduler(
|
|
113
|
+
lambda: self.dispatch(InitAction()),
|
|
114
|
+
interval=False,
|
|
115
|
+
)
|
|
116
|
+
else:
|
|
117
|
+
self.dispatch(InitAction())
|
|
118
|
+
|
|
119
|
+
if self.store_options.scheduler:
|
|
120
|
+
self.store_options.scheduler(self.run, interval=True)
|
|
121
|
+
|
|
122
|
+
def _call_listeners(self: Store[State, Action, Event], state: State) -> None:
|
|
123
|
+
for listener_ in self._listeners.copy():
|
|
124
|
+
listener = listener_() if isinstance(listener_, weakref.ref) else listener_
|
|
125
|
+
if listener is not None:
|
|
126
|
+
result = listener(state)
|
|
127
|
+
if asyncio.iscoroutine(result) and self.store_options.task_creator:
|
|
128
|
+
self.store_options.task_creator(result)
|
|
129
|
+
|
|
130
|
+
def _run_actions(self: Store[State, Action, Event]) -> None:
|
|
131
|
+
while len(self._actions) > 0:
|
|
132
|
+
action = self._actions.pop(0)
|
|
133
|
+
if action is not None:
|
|
134
|
+
result = self.reducer(self._state, action)
|
|
135
|
+
if is_complete_reducer_result(result):
|
|
136
|
+
self._state = result.state
|
|
137
|
+
self._call_listeners(self._state)
|
|
138
|
+
self._dispatch([*(result.actions or []), *(result.events or [])])
|
|
139
|
+
elif is_state_reducer_result(result):
|
|
140
|
+
self._state = result
|
|
141
|
+
self._call_listeners(self._state)
|
|
142
|
+
|
|
143
|
+
if isinstance(action, FinishAction):
|
|
144
|
+
self._dispatch([FinishEvent()])
|
|
145
|
+
|
|
146
|
+
def _run_event_handlers(self: Store[State, Action, Event]) -> None:
|
|
147
|
+
while len(self._events) > 0:
|
|
148
|
+
event = self._events.pop(0)
|
|
149
|
+
if event is not None:
|
|
150
|
+
if isinstance(event, FinishEvent):
|
|
151
|
+
self._handle_finish_event()
|
|
152
|
+
for event_handler in self._event_handlers[type(event)].copy():
|
|
153
|
+
self._event_handlers_queue.put_nowait((event_handler, event))
|
|
154
|
+
|
|
155
|
+
def run(self: Store[State, Action, Event]) -> None:
|
|
156
|
+
"""Run the store."""
|
|
157
|
+
with self._is_running:
|
|
158
|
+
while len(self._actions) > 0 or len(self._events) > 0:
|
|
159
|
+
if len(self._actions) > 0:
|
|
160
|
+
self._run_actions()
|
|
161
|
+
|
|
162
|
+
if len(self._events) > 0:
|
|
163
|
+
self._run_event_handlers()
|
|
164
|
+
|
|
165
|
+
def clean_up(self: Store[State, Action, Event]) -> None:
|
|
166
|
+
"""Clean up the store."""
|
|
167
|
+
self.wait_for_event_handlers()
|
|
168
|
+
for _ in range(self.store_options.side_effect_threads):
|
|
169
|
+
self._event_handlers_queue.put_nowait(None)
|
|
170
|
+
self.wait_for_event_handlers()
|
|
171
|
+
for worker in self._workers:
|
|
172
|
+
worker.join()
|
|
173
|
+
self._workers.clear()
|
|
174
|
+
self._listeners.clear()
|
|
175
|
+
self._event_handlers.clear()
|
|
176
|
+
|
|
177
|
+
def wait_for_event_handlers(self: Store[State, Action, Event]) -> None:
|
|
178
|
+
"""Wait for the event handlers to finish."""
|
|
179
|
+
self._event_handlers_queue.join()
|
|
180
|
+
|
|
181
|
+
@overload
|
|
182
|
+
def dispatch(
|
|
183
|
+
self: Store[State, Action, Event],
|
|
184
|
+
*parameters: DispatchParameters[Action],
|
|
185
|
+
) -> None: ...
|
|
186
|
+
@overload
|
|
187
|
+
def dispatch(
|
|
188
|
+
self: Store[State, Action, Event],
|
|
189
|
+
*,
|
|
190
|
+
with_state: Callable[[State | None], DispatchParameters[Action]] | None = None,
|
|
191
|
+
) -> None: ...
|
|
192
|
+
def dispatch(
|
|
193
|
+
self: Store[State, Action, Event],
|
|
194
|
+
*parameters: DispatchParameters[Action],
|
|
195
|
+
with_state: Callable[[State | None], DispatchParameters[Action]] | None = None,
|
|
196
|
+
) -> None:
|
|
197
|
+
"""Dispatch actions."""
|
|
198
|
+
if with_state is not None:
|
|
199
|
+
self.dispatch(with_state(self._state))
|
|
200
|
+
|
|
201
|
+
actions = [
|
|
202
|
+
action
|
|
203
|
+
for actions in parameters
|
|
204
|
+
for action in (actions if isinstance(actions, Iterable) else [actions])
|
|
205
|
+
]
|
|
206
|
+
self._dispatch(actions)
|
|
207
|
+
|
|
208
|
+
def _dispatch(
|
|
209
|
+
self: Store[State, Action, Event],
|
|
210
|
+
items: Sequence[Action | Event | InitAction | FinishEvent | None],
|
|
211
|
+
) -> None:
|
|
212
|
+
for item in items:
|
|
213
|
+
if isinstance(item, BaseAction):
|
|
214
|
+
action = item
|
|
215
|
+
for action_middleware in self._action_middlewares:
|
|
216
|
+
action_ = action_middleware(action)
|
|
217
|
+
if action_ is None:
|
|
218
|
+
break
|
|
219
|
+
action = action_
|
|
220
|
+
else:
|
|
221
|
+
self._actions.append(action)
|
|
222
|
+
if isinstance(item, BaseEvent):
|
|
223
|
+
event = item
|
|
224
|
+
for event_middleware in self._event_middlewares:
|
|
225
|
+
event_ = event_middleware(event)
|
|
226
|
+
if event_ is None:
|
|
227
|
+
break
|
|
228
|
+
event = event_
|
|
229
|
+
else:
|
|
230
|
+
self._events.append(event)
|
|
231
|
+
|
|
232
|
+
if self.store_options.scheduler is None and not self._is_running.locked():
|
|
233
|
+
self.run()
|
|
234
|
+
|
|
235
|
+
def _subscribe(
|
|
236
|
+
self: Store[State, Action, Event],
|
|
237
|
+
listener: Callable[[State], Any],
|
|
238
|
+
*,
|
|
239
|
+
keep_ref: bool = True,
|
|
240
|
+
) -> Callable[[], None]:
|
|
241
|
+
"""Subscribe to state changes."""
|
|
242
|
+
|
|
243
|
+
def unsubscribe(_: weakref.ref | None = None) -> None:
|
|
244
|
+
with contextlib.suppress(KeyError):
|
|
245
|
+
self._listeners.remove(listener_ref)
|
|
246
|
+
|
|
247
|
+
if keep_ref:
|
|
248
|
+
listener_ref = listener
|
|
249
|
+
elif inspect.ismethod(listener):
|
|
250
|
+
listener_ref = weakref.WeakMethod(listener, unsubscribe)
|
|
251
|
+
else:
|
|
252
|
+
listener_ref = weakref.ref(listener, unsubscribe)
|
|
253
|
+
|
|
254
|
+
self._listeners.add(listener_ref)
|
|
255
|
+
|
|
256
|
+
return unsubscribe
|
|
257
|
+
|
|
258
|
+
def subscribe_event(
|
|
259
|
+
self: Store[State, Action, Event],
|
|
260
|
+
event_type: type[StrictEvent],
|
|
261
|
+
handler: EventHandler[StrictEvent],
|
|
262
|
+
*,
|
|
263
|
+
keep_ref: bool = True,
|
|
264
|
+
) -> SubscribeEventCleanup:
|
|
265
|
+
"""Subscribe to events."""
|
|
266
|
+
|
|
267
|
+
def unsubscribe(_: weakref.ref | None = None) -> None:
|
|
268
|
+
self._event_handlers[cast('Event', event_type)].discard(handler_ref)
|
|
269
|
+
|
|
270
|
+
if keep_ref:
|
|
271
|
+
handler_ref = handler
|
|
272
|
+
elif inspect.ismethod(handler):
|
|
273
|
+
handler_ref = weakref.WeakMethod(handler, unsubscribe)
|
|
274
|
+
else:
|
|
275
|
+
handler_ref = weakref.ref(handler, unsubscribe)
|
|
276
|
+
|
|
277
|
+
self._event_handlers[cast('Event', event_type)].add(handler_ref)
|
|
278
|
+
|
|
279
|
+
return SubscribeEventCleanup(unsubscribe=unsubscribe, handler=handler)
|
|
280
|
+
|
|
281
|
+
def _wait_for_store_to_finish(self: Store[State, Action, Event]) -> None:
|
|
282
|
+
"""Wait for the store to finish."""
|
|
283
|
+
while True:
|
|
284
|
+
if (
|
|
285
|
+
self._actions == []
|
|
286
|
+
and self._events == []
|
|
287
|
+
and self._event_handlers_queue.qsize() == 0
|
|
288
|
+
):
|
|
289
|
+
time.sleep(self.store_options.grace_time_in_seconds)
|
|
290
|
+
self.clean_up()
|
|
291
|
+
if self.store_options.on_finish:
|
|
292
|
+
self.store_options.on_finish()
|
|
293
|
+
break
|
|
294
|
+
|
|
295
|
+
def _handle_finish_event(self: Store[State, Action, Event]) -> None:
|
|
296
|
+
Thread(target=self._wait_for_store_to_finish).start()
|
|
297
|
+
|
|
298
|
+
def autorun(
|
|
299
|
+
self: Store[State, Action, Event],
|
|
300
|
+
selector: Callable[[State], SelectorOutput],
|
|
301
|
+
comparator: Callable[[State], ComparatorOutput] | None = None,
|
|
302
|
+
*,
|
|
303
|
+
options: AutorunOptionsType[ReturnType, AutoAwait] | None = None,
|
|
304
|
+
) -> AutorunDecorator[ReturnType, SelectorOutput, AutoAwait]:
|
|
305
|
+
"""Create a new autorun, reflecting on state changes."""
|
|
306
|
+
|
|
307
|
+
def autorun_decorator(
|
|
308
|
+
func: Callable[
|
|
309
|
+
Concatenate[SelectorOutput, Args],
|
|
310
|
+
AwaitableOrNot[ReturnType],
|
|
311
|
+
],
|
|
312
|
+
) -> AutorunReturnType[Args, ReturnType]:
|
|
313
|
+
return self.store_options.autorun_class(
|
|
314
|
+
store=self,
|
|
315
|
+
selector=selector,
|
|
316
|
+
comparator=comparator,
|
|
317
|
+
func=cast('Callable', func),
|
|
318
|
+
options=options or AutorunOptions(),
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
return cast('AutorunDecorator', autorun_decorator)
|
|
322
|
+
|
|
323
|
+
def view(
|
|
324
|
+
self: Store[State, Action, Event],
|
|
325
|
+
selector: Callable[[State], SelectorOutput],
|
|
326
|
+
*,
|
|
327
|
+
options: ViewOptions[ReturnType] | None = None,
|
|
328
|
+
) -> ViewDecorator[ReturnType, SelectorOutput]:
|
|
329
|
+
"""Create a new view, throttling calls for unchanged selector results."""
|
|
330
|
+
|
|
331
|
+
@overload
|
|
332
|
+
def view_decorator(
|
|
333
|
+
func: Callable[
|
|
334
|
+
Concatenate[SelectorOutput, Args],
|
|
335
|
+
ReturnType,
|
|
336
|
+
]
|
|
337
|
+
| Callable[
|
|
338
|
+
Concatenate[MethodSelf, SelectorOutput, Args],
|
|
339
|
+
ReturnType,
|
|
340
|
+
],
|
|
341
|
+
) -> ViewReturnType[ReturnType, Args]: ...
|
|
342
|
+
@overload
|
|
343
|
+
def view_decorator(
|
|
344
|
+
func: Callable[
|
|
345
|
+
Concatenate[SelectorOutput, Args],
|
|
346
|
+
Awaitable[ReturnType],
|
|
347
|
+
]
|
|
348
|
+
| Callable[
|
|
349
|
+
Concatenate[MethodSelf, SelectorOutput, Args],
|
|
350
|
+
Awaitable[ReturnType],
|
|
351
|
+
],
|
|
352
|
+
) -> ViewReturnType[Awaitable[ReturnType], Args]: ...
|
|
353
|
+
def view_decorator(
|
|
354
|
+
func: Callable[
|
|
355
|
+
Concatenate[SelectorOutput, Args],
|
|
356
|
+
AwaitableOrNot[ReturnType],
|
|
357
|
+
]
|
|
358
|
+
| Callable[
|
|
359
|
+
Concatenate[MethodSelf, SelectorOutput, Args],
|
|
360
|
+
AwaitableOrNot[ReturnType],
|
|
361
|
+
],
|
|
362
|
+
) -> ViewReturnType[AwaitableOrNot[ReturnType], Args]:
|
|
363
|
+
_options = options or ViewOptions()
|
|
364
|
+
return self.store_options.autorun_class(
|
|
365
|
+
store=self,
|
|
366
|
+
selector=selector,
|
|
367
|
+
comparator=None,
|
|
368
|
+
func=cast('Callable', func),
|
|
369
|
+
options=AutorunOptions(
|
|
370
|
+
default_value=_options.default_value,
|
|
371
|
+
auto_await=False,
|
|
372
|
+
initial_call=False,
|
|
373
|
+
reactive=False,
|
|
374
|
+
memoization=_options.memoization,
|
|
375
|
+
keep_ref=_options.keep_ref,
|
|
376
|
+
subscribers_initial_run=_options.subscribers_initial_run,
|
|
377
|
+
subscribers_keep_ref=_options.subscribers_keep_ref,
|
|
378
|
+
),
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
return view_decorator
|
|
382
|
+
|
|
383
|
+
def with_state(
|
|
384
|
+
self: Store[State, Action, Event],
|
|
385
|
+
selector: Callable[[State], SelectorOutput],
|
|
386
|
+
*,
|
|
387
|
+
ignore_uninitialized_store: bool = False,
|
|
388
|
+
) -> WithStateDecorator[SelectorOutput]:
|
|
389
|
+
"""Wrap a function, passing the result of the selector as its first argument.
|
|
390
|
+
|
|
391
|
+
Note that it has zero reactivity, it just runs the function with the result
|
|
392
|
+
of the selector whenever it is called explicitly. To make it reactive, use
|
|
393
|
+
`autorun` instead.
|
|
394
|
+
|
|
395
|
+
This is mostly for improving code oragnization and readability. Directly Using
|
|
396
|
+
`store._state` is also possible.
|
|
397
|
+
"""
|
|
398
|
+
|
|
399
|
+
@overload
|
|
400
|
+
def with_state_decorator(
|
|
401
|
+
func: Callable[
|
|
402
|
+
Concatenate[SelectorOutput, Args],
|
|
403
|
+
ReturnType,
|
|
404
|
+
],
|
|
405
|
+
) -> Callable[Args, ReturnType]: ...
|
|
406
|
+
@overload
|
|
407
|
+
def with_state_decorator(
|
|
408
|
+
func: Callable[
|
|
409
|
+
Concatenate[MethodSelf, SelectorOutput, Args],
|
|
410
|
+
ReturnType,
|
|
411
|
+
],
|
|
412
|
+
) -> Callable[Concatenate[MethodSelf, Args], ReturnType]: ...
|
|
413
|
+
def with_state_decorator(
|
|
414
|
+
func: Callable[
|
|
415
|
+
Concatenate[SelectorOutput, Args],
|
|
416
|
+
ReturnType,
|
|
417
|
+
]
|
|
418
|
+
| Callable[
|
|
419
|
+
Concatenate[MethodSelf, SelectorOutput, Args],
|
|
420
|
+
ReturnType,
|
|
421
|
+
],
|
|
422
|
+
) -> (
|
|
423
|
+
Callable[Args, ReturnType]
|
|
424
|
+
| Callable[Concatenate[MethodSelf, Args], ReturnType]
|
|
425
|
+
):
|
|
426
|
+
def wrapper(*args: Args.args, **kwargs: Args.kwargs) -> ReturnType:
|
|
427
|
+
if self._state is None:
|
|
428
|
+
if ignore_uninitialized_store:
|
|
429
|
+
return cast('ReturnType', None)
|
|
430
|
+
msg = 'Store has not been initialized yet.'
|
|
431
|
+
raise RuntimeError(msg)
|
|
432
|
+
return call_func(func, [selector(self._state)], *args, **kwargs)
|
|
433
|
+
|
|
434
|
+
signature = signature_without_selector(func)
|
|
435
|
+
wrapped = wraps(cast('Any', func))(wrapper)
|
|
436
|
+
wrapped.__signature__ = signature # type: ignore [reportAttributeAccessIssue]
|
|
437
|
+
|
|
438
|
+
return wrapped
|
|
439
|
+
|
|
440
|
+
return with_state_decorator
|
|
441
|
+
|
|
442
|
+
@property
|
|
443
|
+
def snapshot(self: Store[State, Action, Event]) -> SnapshotAtom:
|
|
444
|
+
"""Return a snapshot of the current state of the store."""
|
|
445
|
+
return self.serialize_value(self._state)
|
|
446
|
+
|
|
447
|
+
def register_action_middleware(
|
|
448
|
+
self: Store[State, Action, Event],
|
|
449
|
+
action_middleware: ActionMiddleware,
|
|
450
|
+
) -> None:
|
|
451
|
+
"""Register an action dispatch middleware."""
|
|
452
|
+
self._action_middlewares.append(action_middleware)
|
|
453
|
+
|
|
454
|
+
def register_event_middleware(
|
|
455
|
+
self: Store[State, Action, Event],
|
|
456
|
+
event_middleware: EventMiddleware,
|
|
457
|
+
) -> None:
|
|
458
|
+
"""Register an action dispatch middleware."""
|
|
459
|
+
self._event_middlewares.append(event_middleware)
|
|
460
|
+
|
|
461
|
+
def unregister_action_middleware(
|
|
462
|
+
self: Store[State, Action, Event],
|
|
463
|
+
action_middleware: ActionMiddleware,
|
|
464
|
+
) -> None:
|
|
465
|
+
"""Unregister an action dispatch middleware."""
|
|
466
|
+
self._action_middlewares.remove(action_middleware)
|
|
467
|
+
|
|
468
|
+
def unregister_event_middleware(
|
|
469
|
+
self: Store[State, Action, Event],
|
|
470
|
+
event_middleware: EventMiddleware,
|
|
471
|
+
) -> None:
|
|
472
|
+
"""Unregister an action dispatch middleware."""
|
|
473
|
+
self._event_middlewares.remove(event_middleware)
|
redux/py.typed
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Marker file for PEP 561. The redux package uses inline types.
|