Skip to content

Commit

Permalink
refactor: middleware functions can now return None to cancel an act…
Browse files Browse the repository at this point in the history
…ion or event
  • Loading branch information
sassanh committed Apr 23, 2024
1 parent 32f2c73 commit 5f16508
Show file tree
Hide file tree
Showing 5 changed files with 38 additions and 17 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## Version 0.14.2

- refactor: middleware functions can now return `None` to cancel an action or event

## Version 0.14.1

- feat: introduce `grace_time_in_seconds` parameter to `Store` to allow a grace
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "python-redux"
version = "0.14.1"
version = "0.14.2"
description = "Redux implementation for Python"
authors = ["Sassan Haradji <[email protected]>"]
license = "Apache-2.0"
Expand Down
4 changes: 2 additions & 2 deletions redux/basic_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,11 @@ def __call__(


class ActionMiddleware(Protocol, Generic[Action]):
def __call__(self: ActionMiddleware, action: Action) -> Action: ...
def __call__(self: ActionMiddleware, action: Action) -> Action | None: ...


class EventMiddleware(Protocol, Generic[Event]):
def __call__(self: EventMiddleware, event: Event) -> Event: ...
def __call__(self: EventMiddleware, event: Event) -> Event | None: ...


class CreateStoreOptions(Immutable, Generic[Action, Event]):
Expand Down
43 changes: 30 additions & 13 deletions redux/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class Store(Generic[State, Action, Event], SerializationMixin):
"""Redux store for managing state and side effects."""

def __init__(
self: Store[State, Action, Event],
self: Store,
reducer: ReducerType[State, Action, Event],
options: CreateStoreOptions[Action, Event] | None = None,
) -> None:
Expand Down Expand Up @@ -109,7 +109,12 @@ def _call_listeners(self: Store[State, Action, Event], state: State) -> None:
self._create_task(result)

def _run_actions(self: Store[State, Action, Event]) -> None:
action = self._actions.pop(0)
while True:
if len(self._actions) == 0:
return
action = self._actions.pop(0)
if action is not None:
break
result = self.reducer(self._state, action)
if is_complete_reducer_result(result):
self._state = result.state
Expand All @@ -123,7 +128,12 @@ def _run_actions(self: Store[State, Action, Event]) -> None:
self.dispatch(cast(Event, FinishEvent()))

def _run_event_handlers(self: Store[State, Action, Event]) -> None:
event = self._events.pop(0)
while True:
if len(self._events) == 0:
return
event = self._events.pop(0)
if event is not None:
break
for event_handler in self._event_handlers[type(event)].copy():
self._event_handlers_queue.put_nowait((event_handler, event))

Expand All @@ -139,6 +149,10 @@ def run(self: Store[State, Action, Event]) -> None:

def clean_up(self: Store[State, Action, Event]) -> None:
"""Clean up the store."""
self._event_handlers_queue.join()
for _ in range(self.store_options.threads):
self._event_handlers_queue.put_nowait(None)
self._event_handlers_queue.join()
for worker in self._workers:
worker.join()
self._workers.clear()
Expand All @@ -165,13 +179,21 @@ def dispatch(
if isinstance(item, BaseAction):
action = cast(Action, item)
for action_middleware in self._action_middlewares:
action = action_middleware(action)
self._actions.append(action)
action_ = action_middleware(action)
if action_ is None:
break
action = action_
else:
self._actions.append(action)
if isinstance(item, BaseEvent):
event = cast(Event, item)
for event_middleware in self._event_middlewares:
event = event_middleware(event)
self._events.append(event)
event_ = event_middleware(event)
if event_ is None:
break
event = event_
else:
self._events.append(event)

if self.store_options.scheduler is None and not self._is_running.locked():
self.run()
Expand Down Expand Up @@ -226,19 +248,14 @@ def wait_for_store_to_finish(self: Store[State, Action, Event]) -> None:
and self._event_handlers_queue.qsize() == 0
):
time.sleep(self.store_options.grace_time_in_seconds)
self._event_handlers_queue.join()
for _ in range(self.store_options.threads):
self._event_handlers_queue.put_nowait(None)
self._event_handlers_queue.join()
self.clean_up()
if self.store_options.on_finish:
self.store_options.on_finish()
break
time.sleep(0.1)

def _handle_finish_event(self: Store[State, Action, Event]) -> None:
thread = Thread(target=self.wait_for_store_to_finish)
thread.start()
Thread(target=self.wait_for_store_to_finish).start()

def autorun(
self: Store[State, Action, Event],
Expand Down
2 changes: 1 addition & 1 deletion redux/side_effect_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class SideEffectRunnerThread(threading.Thread, Generic[Event]):
"""Thread for running side effects."""

def __init__(
self: SideEffectRunnerThread[Event],
self: SideEffectRunnerThread,
*,
task_queue: queue.Queue[tuple[EventHandler[Event], Event] | None],
) -> None:
Expand Down

0 comments on commit 5f16508

Please sign in to comment.