Skip to content

Commit a17c652

Browse files
committed
refactor: improve creating new state classes in combine_reducers upon registering/unregistering sub-reducers
feat: add test fixture for snapshot testing the store chore(test): add test infrastructure for snapshot testing the store test: move demo files to test files and update the to use snapshot fixture
1 parent 521bb85 commit a17c652

32 files changed

+823
-208
lines changed

.github/workflows/integration_delivery.yml

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,70 @@ jobs:
9393
- name: Lint
9494
run: poetry run poe lint
9595

96+
test:
97+
name: Test
98+
needs:
99+
- dependencies
100+
runs-on: ubuntu-latest
101+
environment:
102+
name: test
103+
url: https://app.codecov.io/gh/${{ github.repository }}/
104+
steps:
105+
- uses: actions/checkout@v4
106+
name: Checkout
107+
108+
- uses: actions/setup-python@v5
109+
name: Setup Python
110+
with:
111+
python-version: ${{ env.PYTHON_VERSION }}
112+
architecture: x64
113+
114+
- name: Load Cached Poetry
115+
id: cached-poetry
116+
uses: actions/cache/restore@v4
117+
with:
118+
path: |
119+
~/.cache
120+
~/.local
121+
key: poetry-${{ hashFiles('poetry.lock') }}
122+
123+
- name: Test
124+
run: poetry run poe test --cov-report=xml --cov-report=html
125+
126+
- name: Prepare list of JSON files with mismatching pairs
127+
if: failure()
128+
run: |
129+
mkdir -p artifacts
130+
for file in $(find tests/ -name "*.mismatch.json"); do
131+
base=${file%.mismatch.json}.json
132+
if [[ -f "$base" ]]; then
133+
echo "$file" >> artifacts/files_to_upload.txt
134+
echo "$base" >> artifacts/files_to_upload.txt
135+
fi
136+
done
137+
138+
- name: Collect Mismatching Store Snapshots
139+
if: failure()
140+
uses: actions/upload-artifact@v4
141+
with:
142+
name: mismatching-snapshots
143+
path: |
144+
@artifacts/files_to_upload.txt
145+
146+
- name: Collect HTML Coverage Report
147+
uses: actions/upload-artifact@v4
148+
with:
149+
name: coverage-report
150+
path: htmlcov
151+
152+
- name: Upload Coverage to Codecov
153+
uses: codecov/codecov-action@v4
154+
with:
155+
file: ./coverage.xml
156+
flags: integration
157+
fail_ci_if_error: true
158+
token: ${{ secrets.CODECOV_TOKEN }}
159+
96160
build:
97161
name: Build
98162
needs:
@@ -150,6 +214,7 @@ jobs:
150214
needs:
151215
- type-check
152216
- lint
217+
- test
153218
- build
154219
runs-on: ubuntu-latest
155220
environment:
@@ -179,11 +244,11 @@ jobs:
179244
needs:
180245
- type-check
181246
- lint
247+
- test
182248
- build
183249
- pypi-publish
184250
environment:
185251
name: release
186-
url: https://pypi.org/p/${{ needs.build.outputs.name }}
187252
runs-on: ubuntu-latest
188253
permissions:
189254
contents: write

.gitignore

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

4950
# Translations
5051
*.mo

CHANGELOG.md

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

3+
## Version 0.12.0
4+
5+
- refactor: improve creating new state classes in `combine_reducers` upon registering/unregistering
6+
sub-reducers
7+
- feat: add test fixture for snapshot testing the store
8+
- chore(test): add test infrastructure for snapshot testing the store
9+
- test: move demo files to test files and update the to use snapshot fixture
10+
311
## Version 0.11.0
412

513
- feat: add `keep_ref` parameter to subscriptions and autoruns, defaulting to `True`,

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# 🚀 Python Redux
22

3+
[![codecov](https://codecov.io/gh/sassanh/python-redux/graph/badge.svg?token=4F3EWZRLCL)](https://codecov.io/gh/sassanh/python-redux)
4+
35
## 🌟 Overview
46

57
Python Redux is a Redux implementation for Python, bringing Redux's state management

poetry.lock

Lines changed: 183 additions & 30 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 & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "python-redux"
3-
version = "0.11.0"
3+
version = "0.12.0"
44
description = "Redux implementation for Python"
55
authors = ["Sassan Haradji <[email protected]>"]
66
license = "Apache-2.0"
@@ -9,7 +9,7 @@ packages = [{ include = "redux" }]
99

1010
[tool.poetry.dependencies]
1111
python = "^3.11"
12-
python-immutable = "^1.0.2"
12+
python-immutable = "^1.0.5"
1313
typing-extensions = "^4.9.0"
1414

1515
[tool.poetry.group.dev]
@@ -18,7 +18,9 @@ optional = true
1818
[tool.poetry.group.dev.dependencies]
1919
poethepoet = "^0.24.4"
2020
pyright = "^1.1.354"
21-
ruff = "^0.3.2"
21+
ruff = "^0.3.3"
22+
pytest = "^8.1.1"
23+
pytest-cov = "^4.1.0"
2224

2325
[build-system]
2426
requires = ["poetry-core"]
@@ -31,7 +33,8 @@ todo_demo = "todo_demo:main"
3133
[tool.poe.tasks]
3234
lint = "ruff check . --unsafe-fixes"
3335
typecheck = "pyright -p pyproject.toml ."
34-
sanity = ["typecheck", "lint"]
36+
test = "pytest --cov=redux --cov-report=term-missing"
37+
sanity = ["typecheck", "lint", "test"]
3538

3639
[tool.ruff]
3740
lint.select = ['ALL']
@@ -44,6 +47,9 @@ docstring-quotes = "double"
4447
inline-quotes = "single"
4548
multiline-quotes = "double"
4649

50+
[tool.ruff.lint.per-file-ignores]
51+
"tests/*" = ["S101"]
52+
4753
[tool.ruff.format]
4854
quote-style = 'single'
4955

redux/__init__.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@
55
AutorunReturnType,
66
AutorunType,
77
BaseAction,
8+
BaseCombineReducerState,
89
BaseEvent,
10+
CombineReducerAction,
11+
CombineReducerInitAction,
12+
CombineReducerRegisterAction,
13+
CombineReducerUnregisterAction,
914
CompleteReducerResult,
1015
CreateStoreOptions,
1116
Dispatch,
@@ -22,14 +27,7 @@
2227
is_complete_reducer_result,
2328
is_state_reducer_result,
2429
)
25-
from .combine_reducers import (
26-
BaseCombineReducerState,
27-
CombineReducerAction,
28-
CombineReducerInitAction,
29-
CombineReducerRegisterAction,
30-
CombineReducerUnregisterAction,
31-
combine_reducers,
32-
)
30+
from .combine_reducers import combine_reducers
3331
from .main import Store
3432

3533
__all__ = (

redux/basic_types.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,3 +170,24 @@ def __call__(
170170
| None = None,
171171
) -> None:
172172
...
173+
174+
175+
class BaseCombineReducerState(Immutable):
176+
_id: str
177+
178+
179+
class CombineReducerAction(BaseAction):
180+
_id: str
181+
182+
183+
class CombineReducerInitAction(CombineReducerAction, InitAction):
184+
key: str
185+
186+
187+
class CombineReducerRegisterAction(CombineReducerAction):
188+
key: str
189+
reducer: ReducerType
190+
191+
192+
class CombineReducerUnregisterAction(CombineReducerAction):
193+
key: str

redux/combine_reducers.py

Lines changed: 16 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,20 @@
55
import functools
66
import operator
77
import uuid
8-
from dataclasses import asdict, make_dataclass
8+
from dataclasses import asdict, fields, make_dataclass
99
from typing import TYPE_CHECKING, Any, TypeVar, cast
1010

1111
from .basic_types import (
1212
Action,
1313
BaseAction,
14+
BaseCombineReducerState,
1415
BaseEvent,
16+
CombineReducerAction,
17+
CombineReducerInitAction,
18+
CombineReducerRegisterAction,
19+
CombineReducerUnregisterAction,
1520
CompleteReducerResult,
1621
Event,
17-
Immutable,
1822
InitAction,
1923
is_complete_reducer_result,
2024
)
@@ -23,27 +27,6 @@
2327
from redux import ReducerType
2428

2529

26-
class BaseCombineReducerState(Immutable):
27-
_id: str
28-
29-
30-
class CombineReducerAction(BaseAction):
31-
_id: str
32-
33-
34-
class CombineReducerInitAction(CombineReducerAction, InitAction):
35-
key: str
36-
37-
38-
class CombineReducerRegisterAction(CombineReducerAction):
39-
key: str
40-
reducer: ReducerType
41-
42-
43-
class CombineReducerUnregisterAction(CombineReducerAction):
44-
key: str
45-
46-
4730
CombineReducerState = TypeVar(
4831
'CombineReducerState',
4932
bound=BaseCombineReducerState,
@@ -64,7 +47,7 @@ def combine_reducers(
6447
state_class = cast(
6548
type[state_type],
6649
make_dataclass(
67-
'combined_reducer',
50+
state_type.__name__,
6851
('_id', *reducers.keys()),
6952
frozen=True,
7053
kw_only=True,
@@ -84,9 +67,10 @@ def combined_reducer(
8467
reducer = action.reducer
8568
reducers[key] = reducer
8669
state_class = make_dataclass(
87-
'combined_reducer',
70+
state_type.__name__,
8871
('_id', *reducers.keys()),
8972
frozen=True,
73+
kw_only=True,
9074
)
9175
reducer_result = reducer(
9276
None,
@@ -123,11 +107,16 @@ def combined_reducer(
123107
key = action.key
124108

125109
del reducers[key]
126-
fields_copy = copy.copy(cast(Any, state_class).__dataclass_fields__)
110+
fields_copy = {field.name: field for field in fields(state_class)}
127111
annotations_copy = copy.deepcopy(state_class.__annotations__)
128112
del fields_copy[key]
129113
del annotations_copy[key]
130-
state_class = make_dataclass('combined_reducer', annotations_copy)
114+
state_class = make_dataclass(
115+
state_type.__name__,
116+
annotations_copy,
117+
frozen=True,
118+
kw_only=True,
119+
)
131120
cast(Any, state_class).__dataclass_fields__ = fields_copy
132121

133122
state = state_class(

0 commit comments

Comments
 (0)