diff --git a/CHANGELOG.md b/CHANGELOG.md index 7542de5..a5847a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Upcoming - chore: migrate from poetry to uv for the sake of improving performance and dealing with conflicting sub-dependencies +- refactor(core): avoid passing events to `dispatch`, to enforce using them as side-effects only ## Version 0.18.0 diff --git a/redux/basic_types.py b/redux/basic_types.py index b0fa3b2..3990d3a 100644 --- a/redux/basic_types.py +++ b/redux/basic_types.py @@ -282,7 +282,7 @@ def __call__( ) -> Callable[[], None]: ... -DispatchParameters: TypeAlias = Action | Event | list[Action | Event] +DispatchParameters: TypeAlias = Action | list[Action] class Dispatch(Protocol, Generic[State, Action, Event]): diff --git a/redux/main.py b/redux/main.py index a748385..88d481b 100644 --- a/redux/main.py +++ b/redux/main.py @@ -136,13 +136,13 @@ def _run_actions(self: Store[State, Action, Event]) -> None: if is_complete_reducer_result(result): self._state = result.state self._call_listeners(self._state) - self.dispatch([*(result.actions or []), *(result.events or [])]) + self._dispatch([*(result.actions or []), *(result.events or [])]) elif is_state_reducer_result(result): self._state = result self._call_listeners(self._state) if isinstance(action, FinishAction): - self.dispatch(cast(Event, FinishEvent())) + self._dispatch([cast(Event, FinishEvent())]) def _run_event_handlers(self: Store[State, Action, Event]) -> None: while len(self._events) > 0: @@ -177,22 +177,37 @@ def wait_for_event_handlers(self: Store[State, Action, Event]) -> None: """Wait for the event handlers to finish.""" self._event_handlers_queue.join() + @overload def dispatch( self: Store[State, Action, Event], - *parameters: DispatchParameters[Action, Event], - with_state: Callable[[State | None], DispatchParameters[Action, Event]] - | None = None, + *parameters: DispatchParameters[Action], + ) -> None: ... + @overload + def dispatch( + self: Store[State, Action, Event], + *, + with_state: Callable[[State | None], DispatchParameters[Action]] | None = None, + ) -> None: ... + def dispatch( + self: Store[State, Action, Event], + *parameters: DispatchParameters[Action], + with_state: Callable[[State | None], DispatchParameters[Action]] | None = None, ) -> None: - """Dispatch actions and/or events.""" + """Dispatch actions.""" if with_state is not None: self.dispatch(with_state(self._state)) - items = [ - item - for items in parameters - for item in (items if isinstance(items, list) else [items]) + actions = [ + action + for actions in parameters + for action in (actions if isinstance(actions, list) else [actions]) ] + self._dispatch(actions) + def _dispatch( + self: Store[State, Action, Event], + items: list[Action | Event], + ) -> None: for item in items: if isinstance(item, BaseAction): action = cast(Action, item) diff --git a/tests/test_middleware.py b/tests/test_middleware.py index 06a1ba7..362abc7 100644 --- a/tests/test_middleware.py +++ b/tests/test_middleware.py @@ -16,6 +16,7 @@ FinishEvent, InitAction, InitializationActionError, + ReducerResult, ) from redux.main import Store @@ -39,14 +40,17 @@ class SomeEvent(BaseEvent): ... def reducer( state: StateType | None, action: Action, -) -> StateType | CompleteReducerResult[StateType, Action, SomeEvent | FinishEvent]: +) -> StateType | ReducerResult[StateType, Action, SomeEvent | FinishEvent]: if state is None: if isinstance(action, InitAction): return StateType(value=0) raise InitializationActionError(action) if isinstance(action, IncrementAction): - return replace(state, value=state.value + 1) + return CompleteReducerResult( + state=replace(state, value=state.value + 1), + events=[SomeEvent()], + ) if isinstance(action, DecrementAction): return replace(state, value=state.value - 1) @@ -131,19 +135,15 @@ def middleware(event: SomeEvent) -> SomeEvent | FinishEvent: store.register_event_middleware(middleware) - events = [ - SomeEvent(), - SomeEvent(), - SomeEvent(), - ] + actions = [IncrementAction()] * 3 def check() -> None: - assert calls == events + assert calls == [SomeEvent()] * 3 store.subscribe_event(FinishEvent, check) - for event in events: - store.dispatch(event) + for action in actions: + store.dispatch(action) def test_cancelling_event_middlewares(store: StoreType) -> None: @@ -162,17 +162,14 @@ def some_side_effect(event: SomeEvent) -> None: store.register_event_middleware(middleware) - events = [ - SomeEvent(), - SomeEvent(), - ] + actions = [IncrementAction()] * 2 def check() -> None: - assert side_effect_calls == events[1:2] + assert side_effect_calls == actions[1:2] store.subscribe_event(SomeEvent, some_side_effect) store.subscribe_event(FinishEvent, check) - for event in events: - store.dispatch(event) + for action in actions: + store.dispatch(action) store.dispatch(FinishAction()) diff --git a/tests/test_weakref.py b/tests/test_weakref.py index 0c2385d..2ce7dab 100644 --- a/tests/test_weakref.py +++ b/tests/test_weakref.py @@ -300,7 +300,7 @@ def event_subscription_without_keep_ref(_: DummyEvent) -> None: del event_subscription_without_keep_ref assert ref2() is None - store.dispatch(DummyEvent()) + store._dispatch([DummyEvent()]) # noqa: SLF001 @wait_for def subscriptions_ran() -> None: @@ -343,7 +343,7 @@ def test_event_subscription_method( del instance_without_keep_ref assert ref() is None - store.dispatch(DummyEvent()) + store._dispatch([DummyEvent()]) # noqa: SLF001 @wait_for def subscriptions_ran() -> None: