Skip to content

Commit

Permalink
refactor: improve creating new state classes in combine_reducers up…
Browse files Browse the repository at this point in the history
…on 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
  • Loading branch information
sassanh committed Mar 15, 2024
1 parent 521bb85 commit a17c652
Show file tree
Hide file tree
Showing 32 changed files with 823 additions and 208 deletions.
67 changes: 66 additions & 1 deletion .github/workflows/integration_delivery.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,70 @@ jobs:
- name: Lint
run: poetry run poe lint

test:
name: Test
needs:
- dependencies
runs-on: ubuntu-latest
environment:
name: test
url: https://app.codecov.io/gh/${{ github.repository }}/
steps:
- uses: actions/checkout@v4
name: Checkout

- uses: actions/setup-python@v5
name: Setup Python
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64

- name: Load Cached Poetry
id: cached-poetry
uses: actions/cache/restore@v4
with:
path: |
~/.cache
~/.local
key: poetry-${{ hashFiles('poetry.lock') }}

- name: Test
run: poetry run poe test --cov-report=xml --cov-report=html

- name: Prepare list of JSON files with mismatching pairs
if: failure()
run: |
mkdir -p artifacts
for file in $(find tests/ -name "*.mismatch.json"); do
base=${file%.mismatch.json}.json
if [[ -f "$base" ]]; then
echo "$file" >> artifacts/files_to_upload.txt
echo "$base" >> artifacts/files_to_upload.txt
fi
done
- name: Collect Mismatching Store Snapshots
if: failure()
uses: actions/upload-artifact@v4
with:
name: mismatching-snapshots
path: |
@artifacts/files_to_upload.txt
- name: Collect HTML Coverage Report
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: htmlcov

- name: Upload Coverage to Codecov
uses: codecov/codecov-action@v4
with:
file: ./coverage.xml
flags: integration
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}

build:
name: Build
needs:
Expand Down Expand Up @@ -150,6 +214,7 @@ jobs:
needs:
- type-check
- lint
- test
- build
runs-on: ubuntu-latest
environment:
Expand Down Expand Up @@ -179,11 +244,11 @@ jobs:
needs:
- type-check
- lint
- test
- build
- pypi-publish
environment:
name: release
url: https://pypi.org/p/${{ needs.build.outputs.name }}
runs-on: ubuntu-latest
permissions:
contents: write
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ nosetests.xml
coverage.xml
*.cover
.hypothesis/
tests/**/results/*mismatch.json

# Translations
*.mo
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## Version 0.12.0

- 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

## Version 0.11.0

- feat: add `keep_ref` parameter to subscriptions and autoruns, defaulting to `True`,
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# 🚀 Python Redux

[![codecov](https://codecov.io/gh/sassanh/python-redux/graph/badge.svg?token=4F3EWZRLCL)](https://codecov.io/gh/sassanh/python-redux)

## 🌟 Overview

Python Redux is a Redux implementation for Python, bringing Redux's state management
Expand Down
213 changes: 183 additions & 30 deletions poetry.lock

Large diffs are not rendered by default.

14 changes: 10 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "python-redux"
version = "0.11.0"
version = "0.12.0"
description = "Redux implementation for Python"
authors = ["Sassan Haradji <[email protected]>"]
license = "Apache-2.0"
Expand All @@ -9,7 +9,7 @@ packages = [{ include = "redux" }]

[tool.poetry.dependencies]
python = "^3.11"
python-immutable = "^1.0.2"
python-immutable = "^1.0.5"
typing-extensions = "^4.9.0"

[tool.poetry.group.dev]
Expand All @@ -18,7 +18,9 @@ optional = true
[tool.poetry.group.dev.dependencies]
poethepoet = "^0.24.4"
pyright = "^1.1.354"
ruff = "^0.3.2"
ruff = "^0.3.3"
pytest = "^8.1.1"
pytest-cov = "^4.1.0"

[build-system]
requires = ["poetry-core"]
Expand All @@ -31,7 +33,8 @@ todo_demo = "todo_demo:main"
[tool.poe.tasks]
lint = "ruff check . --unsafe-fixes"
typecheck = "pyright -p pyproject.toml ."
sanity = ["typecheck", "lint"]
test = "pytest --cov=redux --cov-report=term-missing"
sanity = ["typecheck", "lint", "test"]

[tool.ruff]
lint.select = ['ALL']
Expand All @@ -44,6 +47,9 @@ docstring-quotes = "double"
inline-quotes = "single"
multiline-quotes = "double"

[tool.ruff.lint.per-file-ignores]
"tests/*" = ["S101"]

[tool.ruff.format]
quote-style = 'single'

Expand Down
14 changes: 6 additions & 8 deletions redux/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@
AutorunReturnType,
AutorunType,
BaseAction,
BaseCombineReducerState,
BaseEvent,
CombineReducerAction,
CombineReducerInitAction,
CombineReducerRegisterAction,
CombineReducerUnregisterAction,
CompleteReducerResult,
CreateStoreOptions,
Dispatch,
Expand All @@ -22,14 +27,7 @@
is_complete_reducer_result,
is_state_reducer_result,
)
from .combine_reducers import (
BaseCombineReducerState,
CombineReducerAction,
CombineReducerInitAction,
CombineReducerRegisterAction,
CombineReducerUnregisterAction,
combine_reducers,
)
from .combine_reducers import combine_reducers
from .main import Store

__all__ = (
Expand Down
21 changes: 21 additions & 0 deletions redux/basic_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,24 @@ def __call__(
| None = None,
) -> None:
...


class BaseCombineReducerState(Immutable):
_id: str


class CombineReducerAction(BaseAction):
_id: str


class CombineReducerInitAction(CombineReducerAction, InitAction):
key: str


class CombineReducerRegisterAction(CombineReducerAction):
key: str
reducer: ReducerType


class CombineReducerUnregisterAction(CombineReducerAction):
key: str
43 changes: 16 additions & 27 deletions redux/combine_reducers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,20 @@
import functools
import operator
import uuid
from dataclasses import asdict, make_dataclass
from dataclasses import asdict, fields, make_dataclass
from typing import TYPE_CHECKING, Any, TypeVar, cast

from .basic_types import (
Action,
BaseAction,
BaseCombineReducerState,
BaseEvent,
CombineReducerAction,
CombineReducerInitAction,
CombineReducerRegisterAction,
CombineReducerUnregisterAction,
CompleteReducerResult,
Event,
Immutable,
InitAction,
is_complete_reducer_result,
)
Expand All @@ -23,27 +27,6 @@
from redux import ReducerType


class BaseCombineReducerState(Immutable):
_id: str


class CombineReducerAction(BaseAction):
_id: str


class CombineReducerInitAction(CombineReducerAction, InitAction):
key: str


class CombineReducerRegisterAction(CombineReducerAction):
key: str
reducer: ReducerType


class CombineReducerUnregisterAction(CombineReducerAction):
key: str


CombineReducerState = TypeVar(
'CombineReducerState',
bound=BaseCombineReducerState,
Expand All @@ -64,7 +47,7 @@ def combine_reducers(
state_class = cast(
type[state_type],
make_dataclass(
'combined_reducer',
state_type.__name__,
('_id', *reducers.keys()),
frozen=True,
kw_only=True,
Expand All @@ -84,9 +67,10 @@ def combined_reducer(
reducer = action.reducer
reducers[key] = reducer
state_class = make_dataclass(
'combined_reducer',
state_type.__name__,
('_id', *reducers.keys()),
frozen=True,
kw_only=True,
)
reducer_result = reducer(
None,
Expand Down Expand Up @@ -123,11 +107,16 @@ def combined_reducer(
key = action.key

del reducers[key]
fields_copy = copy.copy(cast(Any, state_class).__dataclass_fields__)
fields_copy = {field.name: field for field in fields(state_class)}
annotations_copy = copy.deepcopy(state_class.__annotations__)
del fields_copy[key]
del annotations_copy[key]
state_class = make_dataclass('combined_reducer', annotations_copy)
state_class = make_dataclass(
state_type.__name__,
annotations_copy,
frozen=True,
kw_only=True,
)
cast(Any, state_class).__dataclass_fields__ = fields_copy

state = state_class(
Expand Down
Loading

0 comments on commit a17c652

Please sign in to comment.