Skip to content

Commit 4b02d18

Browse files
committed
chore(test): move fixtures and testing utilities to redux-pytest package
feat(test): add `wait_for`, `store_monitor`, `event_loop` and `needs_finish` fixtures test: add tests for scheduler and fixtures refactor: `SideEffectRunnerThread` now runs async side effects in its own event-loop refactor: removed `immediate_run` from event subscriptions refactor: removed `EventSubscriptionOptions` as the only option left was `keep_ref`, it's now a parameter of `subscribe_event` feat: new `on_finish` callback for the store, it runs when all worker threads are joined and resources are freed
1 parent 17432ed commit 4b02d18

File tree

67 files changed

+1381
-443
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+1381
-443
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ nosetests.xml
4545
coverage.xml
4646
*.cover
4747
.hypothesis/
48-
tests/**/results/*mismatch.json
48+
tests/**/results/**/*.mismatch.json
4949

5050
# Translations
5151
*.mo

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
# Changelog
22

3+
## Version 0.13.0
4+
5+
- chore(test): move fixtures and testing utilities to `redux-pytest` package
6+
- feat(test): add `wait_for`, `store_monitor`, `event_loop` and `needs_finish` fixtures
7+
- test: add tests for scheduler and fixtures
8+
- refactor: `SideEffectRunnerThread` now runs async side effects in its own event-loop
9+
- refactor: removed `immediate_run` from event subscriptions
10+
- refactor: removed `EventSubscriptionOptions` as the only option left was `keep_ref`,
11+
it's now a parameter of `subscribe_event`
12+
- feat: new `on_finish` callback for the store, it runs when all worker threads are
13+
joined and resources are freed
14+
315
## Version 0.12.7
416

517
- fix: automatically unsubscribe autoruns when the weakref is dead

demo.py

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
# ruff: noqa: D100, D101, D102, D103, D104, D107, A003, T201
2+
from __future__ import annotations
3+
4+
import time
5+
6+
from immutable import Immutable
7+
8+
from redux import (
9+
BaseAction,
10+
BaseCombineReducerState,
11+
CombineReducerAction,
12+
CombineReducerRegisterAction,
13+
CombineReducerUnregisterAction,
14+
InitAction,
15+
InitializationActionError,
16+
Store,
17+
combine_reducers,
18+
)
19+
from redux.basic_types import (
20+
BaseEvent,
21+
CompleteReducerResult,
22+
FinishAction,
23+
ReducerResult,
24+
)
25+
from redux.main import CreateStoreOptions
26+
27+
28+
class CountAction(BaseAction):
29+
...
30+
31+
32+
class IncrementAction(CountAction):
33+
...
34+
35+
36+
class DecrementAction(CountAction):
37+
...
38+
39+
40+
class DoNothingAction(CountAction):
41+
...
42+
43+
44+
ActionType = InitAction | FinishAction | CountAction | CombineReducerAction
45+
46+
47+
class CountStateType(Immutable):
48+
count: int
49+
50+
51+
class StateType(BaseCombineReducerState):
52+
straight: CountStateType
53+
base10: CountStateType
54+
inverse: CountStateType
55+
56+
57+
# Reducers <
58+
def straight_reducer(
59+
state: CountStateType | None,
60+
action: ActionType,
61+
) -> CountStateType:
62+
if state is None:
63+
if isinstance(action, InitAction):
64+
return CountStateType(count=0)
65+
raise InitializationActionError(action)
66+
if isinstance(action, IncrementAction):
67+
return CountStateType(count=state.count + 1)
68+
if isinstance(action, DecrementAction):
69+
return CountStateType(count=state.count - 1)
70+
return state
71+
72+
73+
def base10_reducer(
74+
state: CountStateType | None,
75+
action: ActionType,
76+
) -> CountStateType:
77+
if state is None:
78+
if isinstance(action, InitAction):
79+
return CountStateType(count=10)
80+
raise InitializationActionError(action)
81+
if isinstance(action, IncrementAction):
82+
return CountStateType(count=state.count + 1)
83+
if isinstance(action, DecrementAction):
84+
return CountStateType(count=state.count - 1)
85+
return state
86+
87+
88+
class SleepEvent(BaseEvent):
89+
duration: int
90+
91+
92+
class PrintEvent(BaseEvent):
93+
message: str
94+
95+
96+
def inverse_reducer(
97+
state: CountStateType | None,
98+
action: ActionType,
99+
) -> ReducerResult[CountStateType, ActionType, SleepEvent]:
100+
if state is None:
101+
if isinstance(action, InitAction):
102+
return CountStateType(count=0)
103+
raise InitializationActionError(action)
104+
if isinstance(action, IncrementAction):
105+
return CountStateType(count=state.count - 1)
106+
if isinstance(action, DecrementAction):
107+
return CountStateType(count=state.count + 1)
108+
if isinstance(action, DoNothingAction):
109+
return CompleteReducerResult(
110+
state=state,
111+
actions=[IncrementAction()],
112+
events=[SleepEvent(duration=3)],
113+
)
114+
return state
115+
116+
117+
reducer, reducer_id = combine_reducers(
118+
state_type=StateType,
119+
action_type=ActionType, # pyright: ignore [reportArgumentType]
120+
event_type=SleepEvent | PrintEvent, # pyright: ignore [reportArgumentType]
121+
straight=straight_reducer,
122+
base10=base10_reducer,
123+
)
124+
# >
125+
126+
127+
def main() -> None:
128+
# Initialization <
129+
store = Store(
130+
reducer,
131+
CreateStoreOptions(auto_init=True, threads=2),
132+
)
133+
134+
def event_handler(event: SleepEvent) -> None:
135+
time.sleep(event.duration)
136+
137+
store.subscribe_event(SleepEvent, event_handler)
138+
# >
139+
140+
# -----
141+
142+
# Subscription <
143+
store.subscribe(lambda state: print('Subscription state:', state))
144+
# >
145+
146+
# -----
147+
148+
# Autorun <
149+
@store.autorun(lambda state: state.base10)
150+
def render(base10_value: CountStateType) -> int:
151+
print('Autorun:', base10_value)
152+
return base10_value.count
153+
154+
render.subscribe(lambda a: print(a))
155+
156+
print(f'Render output {render()}')
157+
158+
store.dispatch(IncrementAction())
159+
print(f'Render output {render()}')
160+
161+
store.dispatch(
162+
CombineReducerRegisterAction(
163+
_id=reducer_id,
164+
key='inverse',
165+
reducer=inverse_reducer,
166+
),
167+
)
168+
169+
store.dispatch(DoNothingAction())
170+
print(f'Render output {render()}')
171+
172+
store.dispatch(
173+
CombineReducerUnregisterAction(
174+
_id=reducer_id,
175+
key='straight',
176+
),
177+
)
178+
print(f'Render output {render()}')
179+
180+
store.dispatch(DecrementAction())
181+
print(f'Render output {render()}')
182+
183+
store.dispatch(FinishAction())
184+
# >

poetry.lock

Lines changed: 53 additions & 22 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
[tool.poetry]
22
name = "python-redux"
3-
version = "0.12.7"
3+
version = "0.13.0"
44
description = "Redux implementation for Python"
55
authors = ["Sassan Haradji <[email protected]>"]
66
license = "Apache-2.0"
77
readme = "README.md"
8-
packages = [{ include = "redux" }]
8+
packages = [{ include = "redux" }, { include = "redux_pytest" }]
99

1010
[tool.poetry.dependencies]
1111
python = "^3.11"
1212
python-immutable = "^1.0.5"
13-
typing-extensions = "^4.9.0"
1413

1514
[tool.poetry.group.dev]
1615
optional = true
@@ -22,6 +21,8 @@ ruff = "^0.3.3"
2221
pytest = "^8.1.1"
2322
pytest-cov = "^4.1.0"
2423
pytest-timeout = "^2.3.1"
24+
pytest-mock = "^3.14.0"
25+
tenacity = "^8.2.3"
2526

2627
[build-system]
2728
requires = ["poetry-core"]
@@ -34,7 +35,7 @@ todo_demo = "todo_demo:main"
3435
[tool.poe.tasks]
3536
lint = "ruff check . --unsafe-fixes"
3637
typecheck = "pyright -p pyproject.toml ."
37-
test = "pytest --cov=redux --cov-report=term-missing --cov-report=html --cov-report=xml"
38+
test = "pytest --cov --cov-report=term-missing --cov-report=html --cov-report=xml"
3839
sanity = ["typecheck", "lint", "test"]
3940

4041
[tool.ruff]
@@ -69,4 +70,8 @@ timeout = 1
6970
exclude_also = ["if TYPE_CHECKING:"]
7071

7172
[tool.coverage.run]
72-
omit = ['redux/test.py']
73+
source = ['redux', 'redux_pytest']
74+
omit = ['redux_pytest/plugin.py']
75+
76+
[tool.poetry.plugins.pytest11]
77+
redux = "redux_pytest.plugin"

redux/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
Dispatch,
1818
DispatchParameters,
1919
EventSubscriber,
20-
EventSubscriptionOptions,
2120
FinishAction,
2221
FinishEvent,
2322
InitAction,
@@ -43,7 +42,6 @@
4342
'Dispatch',
4443
'DispatchParameters',
4544
'EventSubscriber',
46-
'EventSubscriptionOptions',
4745
'FinishAction',
4846
'FinishEvent',
4947
'InitAction',

0 commit comments

Comments
 (0)