Skip to content

Commit 56c59c2

Browse files
committed
refactor: housekeeping, remove unused callback of task creators, remove unused _task_callback of autoruns, clean up type hints, etc
1 parent 78d4d3d commit 56c59c2

File tree

9 files changed

+68
-90
lines changed

9 files changed

+68
-90
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
- refactor: autorun doesn't inform subscribers when the output value is not changed
77
- refactor: add `autorun_class` and `side_effect_runner_class` to improve extensibility
88
- refactor: setting `auto_await` for async autorun functions will make them return `None`, setting it to `False` will make them return the awaitable, the awaitable can be `await`ed multiple times, as it cashes the result if comparator is not changed, it can't be set for sync functions
9+
- refactor: housekeeping, remove unused callback of task creators, remove unused `_task_callback` of autoruns, clean up type hints, etc
910

1011
## Version 0.22.2
1112

redux/autorun.py

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import asyncio
66
import inspect
77
import weakref
8-
from asyncio import Future, Task, iscoroutine, iscoroutinefunction
8+
from asyncio import iscoroutine, iscoroutinefunction
99
from typing import (
1010
TYPE_CHECKING,
1111
Any,
@@ -217,27 +217,6 @@ def inform_subscribers(
217217
subscriber = subscriber_
218218
subscriber(self._latest_value)
219219

220-
def _task_callback(
221-
self: Autorun[
222-
State,
223-
Action,
224-
Event,
225-
SelectorOutput,
226-
ComparatorOutput,
227-
Args,
228-
ReturnType,
229-
],
230-
task: Task,
231-
*,
232-
future: Future,
233-
) -> None:
234-
task.add_done_callback(
235-
lambda result: (
236-
future.set_result(result.result()),
237-
self.inform_subscribers(),
238-
),
239-
)
240-
241220
def check(
242221
self: Autorun[
243222
State,

redux/basic_types.py

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,15 @@ class BaseAction(Immutable): ...
3838
class BaseEvent(Immutable): ...
3939

4040

41+
class InitAction(BaseAction): ...
42+
43+
44+
class FinishAction(BaseAction): ...
45+
46+
47+
class FinishEvent(BaseEvent): ...
48+
49+
4150
# Type variables
4251
State = TypeVar('State', bound=Immutable | None, infer_variance=True)
4352
Action = TypeVar('Action', bound=BaseAction | None, infer_variance=True)
@@ -47,7 +56,7 @@ class BaseEvent(Immutable): ...
4756
ComparatorOutput = TypeVar('ComparatorOutput', infer_variance=True)
4857
ReturnType = TypeVar('ReturnType', infer_variance=True)
4958
Comparator = Callable[[State], ComparatorOutput]
50-
EventHandler = Callable[[Event], Any] | Callable[[], Any]
59+
EventHandler: TypeAlias = Callable[[Event], Any] | Callable[[], Any]
5160
Args = ParamSpec('Args')
5261
Payload = TypeVar('Payload', bound=Any, default=None)
5362

@@ -58,8 +67,11 @@ class CompleteReducerResult(Immutable, Generic[State, Action, Event]):
5867
events: Sequence[Event] | None = None
5968

6069

61-
ReducerResult = CompleteReducerResult[State, Action, Event] | State
62-
ReducerType = Callable[[State | None, Action], ReducerResult[State, Action, Event]]
70+
ReducerResult: TypeAlias = CompleteReducerResult[State, Action, Event] | State
71+
ReducerType: TypeAlias = Callable[
72+
[State | None, Action],
73+
ReducerResult[State, Action, Event],
74+
]
6375

6476

6577
class InitializationActionError(Exception):
@@ -70,15 +82,6 @@ def __init__(self: InitializationActionError, action: BaseAction) -> None:
7082
)
7183

7284

73-
class InitAction(BaseAction): ...
74-
75-
76-
class FinishAction(BaseAction): ...
77-
78-
79-
class FinishEvent(BaseEvent): ...
80-
81-
8285
def is_complete_reducer_result(
8386
result: ReducerResult[State, Action, Event],
8487
) -> TypeGuard[CompleteReducerResult[State, Action, Event]]:
@@ -103,8 +106,6 @@ class TaskCreator(Protocol):
103106
def __call__(
104107
self: TaskCreator,
105108
coro: Coroutine,
106-
*,
107-
callback: TaskCreatorCallback | None = None,
108109
) -> None: ...
109110

110111

@@ -132,8 +133,12 @@ class StoreOptions(Immutable, Generic[Action, Event]):
132133
auto_init: bool = False
133134
side_effect_threads: int = 1
134135
scheduler: Scheduler | None = None
135-
action_middlewares: Sequence[ActionMiddleware[Action]] = field(default_factory=list)
136-
event_middlewares: Sequence[EventMiddleware[Event]] = field(default_factory=list)
136+
action_middlewares: Sequence[ActionMiddleware[Action | InitAction]] = field(
137+
default_factory=list,
138+
)
139+
event_middlewares: Sequence[EventMiddleware[Event | FinishEvent]] = field(
140+
default_factory=list,
141+
)
137142
task_creator: TaskCreator | None = None
138143
on_finish: Callable[[], Any] | None = None
139144
grace_time_in_seconds: float = 1
@@ -285,6 +290,15 @@ def __call__(
285290
],
286291
) -> AutorunReturnType[Awaitable[ReturnType], Args]: ...
287292

293+
@overload
294+
def __call__(
295+
self: AutorunDecorator[ReturnType, SelectorOutput, bool],
296+
func: Callable[
297+
Concatenate[SelectorOutput, Args],
298+
ReturnType,
299+
],
300+
) -> AutorunReturnType[ReturnType, Args]: ...
301+
288302

289303
# View
290304

@@ -367,7 +381,7 @@ def __call__(
367381
) -> Callable[[], None]: ...
368382

369383

370-
DispatchParameters: TypeAlias = Action | list[Action]
384+
DispatchParameters: TypeAlias = Action | InitAction | list[Action | InitAction]
371385

372386

373387
class Dispatch(Protocol, Generic[State, Action, Event]):

redux/main.py

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import queue
88
import weakref
99
from collections import defaultdict
10+
from collections.abc import Awaitable, Iterable, Sequence
1011
from functools import wraps
1112
from threading import Lock, Thread
1213
from typing import (
@@ -56,15 +57,15 @@
5657
from redux.serialization_mixin import SerializationMixin
5758

5859
if TYPE_CHECKING:
59-
from collections.abc import Awaitable, Callable
60+
from collections.abc import Callable
6061

6162

6263
class Store(Generic[State, Action, Event], SerializationMixin):
6364
"""Redux store for managing state and side effects."""
6465

6566
def __init__(
6667
self,
67-
reducer: ReducerType[State, Action, Event],
68+
reducer: ReducerType[State, Action | InitAction, Event | None],
6869
options: StoreOptions[Action, Event] | None = None,
6970
) -> None:
7071
"""Create a new store."""
@@ -77,18 +78,19 @@ def __init__(
7778

7879
self._state: State | None = None
7980
self._listeners: set[
80-
Callable[[State], Any] | weakref.ref[Callable[[State], Any]]
81+
Callable[[State], AwaitableOrNot[None]]
82+
| weakref.ref[Callable[[State], AwaitableOrNot[None]]]
8183
] = set()
8284
self._event_handlers: defaultdict[
83-
type[Event],
85+
type[Event | FinishEvent],
8486
set[EventHandler | weakref.ref[EventHandler]],
8587
] = defaultdict(set)
8688

87-
self._actions: list[Action] = []
88-
self._events: list[Event] = []
89+
self._actions: list[Action | InitAction] = []
90+
self._events: list[Event | FinishEvent] = []
8991

9092
self._event_handlers_queue = queue.Queue[
91-
tuple[EventHandler[Event], Event] | None
93+
tuple[EventHandler[Event | FinishEvent], Event | FinishEvent] | None
9294
]()
9395
self._workers = [
9496
self.store_options.side_effect_runner_class(
@@ -105,11 +107,11 @@ def __init__(
105107
if self.store_options.auto_init:
106108
if self.store_options.scheduler:
107109
self.store_options.scheduler(
108-
lambda: self.dispatch(cast('Action', InitAction())),
110+
lambda: self.dispatch(InitAction()),
109111
interval=False,
110112
)
111113
else:
112-
self.dispatch(cast('Action', InitAction()))
114+
self.dispatch(InitAction())
113115

114116
if self.store_options.scheduler:
115117
self.store_options.scheduler(self.run, interval=True)
@@ -139,7 +141,7 @@ def _run_actions(self: Store[State, Action, Event]) -> None:
139141
self._call_listeners(self._state)
140142

141143
if isinstance(action, FinishAction):
142-
self._dispatch([cast('Event', FinishEvent())])
144+
self._dispatch([FinishEvent()])
143145

144146
def _run_event_handlers(self: Store[State, Action, Event]) -> None:
145147
while len(self._events) > 0:
@@ -199,17 +201,17 @@ def dispatch(
199201
actions = [
200202
action
201203
for actions in parameters
202-
for action in (actions if isinstance(actions, list) else [actions])
204+
for action in (actions if isinstance(actions, Iterable) else [actions])
203205
]
204206
self._dispatch(actions)
205207

206208
def _dispatch(
207209
self: Store[State, Action, Event],
208-
items: list[Action | Event],
210+
items: Sequence[Action | Event | InitAction | FinishEvent | None],
209211
) -> None:
210212
for item in items:
211213
if isinstance(item, BaseAction):
212-
action = cast('Action', item)
214+
action = item
213215
for action_middleware in self._action_middlewares:
214216
action_ = action_middleware(action)
215217
if action_ is None:
@@ -218,7 +220,7 @@ def _dispatch(
218220
else:
219221
self._actions.append(action)
220222
if isinstance(item, BaseEvent):
221-
event = cast('Event', item)
223+
event = item
222224
for event_middleware in self._event_middlewares:
223225
event_ = event_middleware(event)
224226
if event_ is None:
@@ -267,10 +269,10 @@ def subscribe_event(
267269
else:
268270
handler_ref = weakref.ref(handler)
269271

270-
self._event_handlers[cast('Any', event_type)].add(handler_ref)
272+
self._event_handlers[cast('Event', event_type)].add(handler_ref)
271273

272274
def unsubscribe() -> None:
273-
self._event_handlers[cast('Any', event_type)].discard(handler_ref)
275+
self._event_handlers[cast('Event', event_type)].discard(handler_ref)
274276

275277
return SubscribeEventCleanup(unsubscribe=unsubscribe, handler=handler)
276278

@@ -305,9 +307,9 @@ def autorun(
305307
def autorun_decorator(
306308
func: Callable[
307309
Concatenate[SelectorOutput, Args],
308-
ReturnType,
310+
AwaitableOrNot[ReturnType],
309311
],
310-
) -> AutorunReturnType[ReturnType | None, Args]:
312+
) -> AutorunReturnType[ReturnType, Args]:
311313
return self.store_options.autorun_class(
312314
store=self,
313315
selector=selector,

redux/side_effect_runner.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
from __future__ import annotations
44

5-
import asyncio
65
import contextlib
76
import inspect
87
import threading
@@ -16,6 +15,8 @@
1615
import queue
1716
from collections.abc import Callable
1817

18+
from redux.basic_types import EventHandler, TaskCreator
19+
1920

2021
class SideEffectRunner(threading.Thread, Generic[Event]):
2122
"""Thread for running side effects."""
@@ -30,18 +31,16 @@ def __init__(
3031
super().__init__()
3132
self.name = 'Side Effect Runner'
3233
self.task_queue = task_queue
33-
self.loop = asyncio.get_event_loop()
3434
self._handles: set[Handle] = set()
3535
self.create_task = create_task
3636

3737
def run(self: SideEffectRunner[Event]) -> None:
3838
"""Run the side effect runner thread."""
3939
while True:
4040
task = self.task_queue.get()
41-
if task is None:
42-
self.task_queue.task_done()
43-
break
4441
try:
42+
if task is None:
43+
break
4544
event_handler_, event = task
4645
if isinstance(event_handler_, weakref.ref):
4746
event_handler = event_handler_()

redux_pytest/fixtures/event_loop.py

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@
88
import pytest
99

1010
if TYPE_CHECKING:
11-
from collections.abc import Callable, Coroutine
12-
13-
from redux.basic_types import TaskCreatorCallback
11+
from collections.abc import Coroutine
1412

1513

1614
class LoopThread(threading.Thread):
@@ -24,21 +22,8 @@ def run(self: LoopThread) -> None:
2422
def stop(self: LoopThread) -> None:
2523
self.loop.call_soon_threadsafe(self.loop.stop)
2624

27-
def create_task(
28-
self: LoopThread,
29-
coro: Coroutine,
30-
*,
31-
callback: TaskCreatorCallback | None = None,
32-
) -> None:
33-
def _(
34-
coro: Coroutine,
35-
callback: Callable[[asyncio.Task], None] | None = None,
36-
) -> None:
37-
task = self.loop.create_task(coro)
38-
if callback:
39-
task.add_done_callback(callback)
40-
41-
self.loop.call_soon_threadsafe(_, coro, callback)
25+
def create_task(self: LoopThread, coro: Coroutine) -> None:
26+
self.loop.call_soon_threadsafe(self.loop.create_task, coro)
4227

4328

4429
@pytest.fixture

tests/test_async.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -129,10 +129,7 @@ async def _(values: tuple[int, int]) -> None:
129129

130130

131131
def test_autorun_non_autoawait(store: StoreType) -> None:
132-
@store.autorun(
133-
lambda state: state.value,
134-
options=AutorunOptions(auto_await=False),
135-
)
132+
@store.autorun(lambda state: state.value, options=AutorunOptions(auto_await=False))
136133
async def sync_mirror(value: int) -> int:
137134
store.dispatch(SetMirroredValueAction(value=value))
138135
return value * 2
@@ -143,10 +140,11 @@ async def sync_mirror(value: int) -> int:
143140
async def _(values: tuple[int, int]) -> None:
144141
value, mirrored_value = values
145142
if mirrored_value != value:
146-
assert 'awaited=False' in str(sync_mirror())
147-
await sync_mirror()
148-
assert 'awaited=True' in str(sync_mirror())
149-
await sync_mirror()
143+
sync_mirror_returned_value = sync_mirror()
144+
assert 'awaited=False' in str(sync_mirror_returned_value)
145+
await sync_mirror_returned_value
146+
assert 'awaited=True' in str(sync_mirror_returned_value)
147+
await sync_mirror_returned_value
150148
elif value < INCREMENTS:
151149
store.dispatch(IncrementAction())
152150
else:

tests/test_autorun.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def reducer(
7474

7575
@pytest.fixture
7676
def store() -> Generator[StoreType, None, None]:
77-
store = Store(reducer, options=StoreOptions(auto_init=True))
77+
store: StoreType = Store(reducer, options=StoreOptions(auto_init=True))
7878
yield store
7979

8080
store.dispatch(IncrementAction())

tests/test_views.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def reducer(
6767

6868
@pytest.fixture
6969
def store() -> Generator[StoreType, None, None]:
70-
store = Store(reducer, options=StoreOptions(auto_init=True))
70+
store: StoreType = Store(reducer, options=StoreOptions(auto_init=True))
7171
yield store
7272

7373
store.dispatch(IncrementAction())

0 commit comments

Comments
 (0)