Skip to content

Commit

Permalink
chore(test): move fixtures and testing utilities to redux-pytest pa…
Browse files Browse the repository at this point in the history
…ckage

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
  • Loading branch information
sassanh committed Mar 23, 2024
1 parent 17432ed commit 4b02d18
Show file tree
Hide file tree
Showing 67 changed files with 1,381 additions and 443 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ nosetests.xml
coverage.xml
*.cover
.hypothesis/
tests/**/results/*mismatch.json
tests/**/results/**/*.mismatch.json

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

## Version 0.13.0

- 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

## Version 0.12.7

- fix: automatically unsubscribe autoruns when the weakref is dead
Expand Down
184 changes: 184 additions & 0 deletions demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
# ruff: noqa: D100, D101, D102, D103, D104, D107, A003, T201
from __future__ import annotations

import time

from immutable import Immutable

from redux import (
BaseAction,
BaseCombineReducerState,
CombineReducerAction,
CombineReducerRegisterAction,
CombineReducerUnregisterAction,
InitAction,
InitializationActionError,
Store,
combine_reducers,
)
from redux.basic_types import (
BaseEvent,
CompleteReducerResult,
FinishAction,
ReducerResult,
)
from redux.main import CreateStoreOptions


class CountAction(BaseAction):
...


class IncrementAction(CountAction):
...


class DecrementAction(CountAction):
...


class DoNothingAction(CountAction):
...


ActionType = InitAction | FinishAction | CountAction | CombineReducerAction


class CountStateType(Immutable):
count: int


class StateType(BaseCombineReducerState):
straight: CountStateType
base10: CountStateType
inverse: CountStateType


# Reducers <
def straight_reducer(
state: CountStateType | None,
action: ActionType,
) -> CountStateType:
if state is None:
if isinstance(action, InitAction):
return CountStateType(count=0)
raise InitializationActionError(action)
if isinstance(action, IncrementAction):
return CountStateType(count=state.count + 1)
if isinstance(action, DecrementAction):
return CountStateType(count=state.count - 1)
return state


def base10_reducer(
state: CountStateType | None,
action: ActionType,
) -> CountStateType:
if state is None:
if isinstance(action, InitAction):
return CountStateType(count=10)
raise InitializationActionError(action)
if isinstance(action, IncrementAction):
return CountStateType(count=state.count + 1)
if isinstance(action, DecrementAction):
return CountStateType(count=state.count - 1)
return state


class SleepEvent(BaseEvent):
duration: int


class PrintEvent(BaseEvent):
message: str


def inverse_reducer(
state: CountStateType | None,
action: ActionType,
) -> ReducerResult[CountStateType, ActionType, SleepEvent]:
if state is None:
if isinstance(action, InitAction):
return CountStateType(count=0)
raise InitializationActionError(action)
if isinstance(action, IncrementAction):
return CountStateType(count=state.count - 1)
if isinstance(action, DecrementAction):
return CountStateType(count=state.count + 1)
if isinstance(action, DoNothingAction):
return CompleteReducerResult(
state=state,
actions=[IncrementAction()],
events=[SleepEvent(duration=3)],
)
return state


reducer, reducer_id = combine_reducers(
state_type=StateType,
action_type=ActionType, # pyright: ignore [reportArgumentType]
event_type=SleepEvent | PrintEvent, # pyright: ignore [reportArgumentType]
straight=straight_reducer,
base10=base10_reducer,
)
# >


def main() -> None:
# Initialization <
store = Store(
reducer,
CreateStoreOptions(auto_init=True, threads=2),
)

def event_handler(event: SleepEvent) -> None:
time.sleep(event.duration)

store.subscribe_event(SleepEvent, event_handler)
# >

# -----

# Subscription <
store.subscribe(lambda state: print('Subscription state:', state))
# >

# -----

# Autorun <
@store.autorun(lambda state: state.base10)
def render(base10_value: CountStateType) -> int:
print('Autorun:', base10_value)
return base10_value.count

render.subscribe(lambda a: print(a))

print(f'Render output {render()}')

store.dispatch(IncrementAction())
print(f'Render output {render()}')

store.dispatch(
CombineReducerRegisterAction(
_id=reducer_id,
key='inverse',
reducer=inverse_reducer,
),
)

store.dispatch(DoNothingAction())
print(f'Render output {render()}')

store.dispatch(
CombineReducerUnregisterAction(
_id=reducer_id,
key='straight',
),
)
print(f'Render output {render()}')

store.dispatch(DecrementAction())
print(f'Render output {render()}')

store.dispatch(FinishAction())
# >
75 changes: 53 additions & 22 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 10 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
[tool.poetry]
name = "python-redux"
version = "0.12.7"
version = "0.13.0"
description = "Redux implementation for Python"
authors = ["Sassan Haradji <sassanh@gmail.com>"]
license = "Apache-2.0"
readme = "README.md"
packages = [{ include = "redux" }]
packages = [{ include = "redux" }, { include = "redux_pytest" }]

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

[tool.poetry.group.dev]
optional = true
Expand All @@ -22,6 +21,8 @@ ruff = "^0.3.3"
pytest = "^8.1.1"
pytest-cov = "^4.1.0"
pytest-timeout = "^2.3.1"
pytest-mock = "^3.14.0"
tenacity = "^8.2.3"

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

[tool.ruff]
Expand Down Expand Up @@ -69,4 +70,8 @@ timeout = 1
exclude_also = ["if TYPE_CHECKING:"]

[tool.coverage.run]
omit = ['redux/test.py']
source = ['redux', 'redux_pytest']
omit = ['redux_pytest/plugin.py']

[tool.poetry.plugins.pytest11]
redux = "redux_pytest.plugin"
2 changes: 0 additions & 2 deletions redux/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
Dispatch,
DispatchParameters,
EventSubscriber,
EventSubscriptionOptions,
FinishAction,
FinishEvent,
InitAction,
Expand All @@ -43,7 +42,6 @@
'Dispatch',
'DispatchParameters',
'EventSubscriber',
'EventSubscriptionOptions',
'FinishAction',
'FinishEvent',
'InitAction',
Expand Down
Loading

0 comments on commit 4b02d18

Please sign in to comment.