Skip to content

Commit

Permalink
feat(test): add arguments for wait_for's check
Browse files Browse the repository at this point in the history
  • Loading branch information
sassanh committed Jul 25, 2024
1 parent 0f76ec0 commit ee18a30
Show file tree
Hide file tree
Showing 10 changed files with 167 additions and 137 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## Version 0.15.10

- feat(test): add arguments for `wait_for`'s `check`

## Version 0.15.9

- refactor(core): use `str_to_bool` of `python-strtobool` instead of `strtobool`
Expand Down
12 changes: 4 additions & 8 deletions demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,16 @@
from redux.main import CreateStoreOptions


class CountAction(BaseAction):
...
class CountAction(BaseAction): ...


class IncrementAction(CountAction):
...
class IncrementAction(CountAction): ...


class DecrementAction(CountAction):
...
class DecrementAction(CountAction): ...


class DoNothingAction(CountAction):
...
class DoNothingAction(CountAction): ...


ActionType = InitAction | FinishAction | CountAction | CombineReducerAction
Expand Down
171 changes: 86 additions & 85 deletions poetry.lock

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "python-redux"
version = "0.15.9"
version = "0.15.10"
description = "Redux implementation for Python"
authors = ["Sassan Haradji <sassanh@gmail.com>"]
license = "Apache-2.0"
Expand All @@ -20,8 +20,8 @@ optional = true

[tool.poetry.group.dev.dependencies]
poethepoet = "^0.24.4"
pyright = "^1.1.361"
ruff = "^0.4.3"
pyright = "^1.1.373"
ruff = "^0.5.4"
pytest = "^8.1.1"
pytest-cov = "^4.1.0"
pytest-timeout = "^2.3.1"
Expand Down
2 changes: 1 addition & 1 deletion redux/autorun.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class Autorun(
AutorunArgs,
],
):
def __init__( # noqa: PLR0913
def __init__(
self: Autorun,
*,
store: Store[State, Action, Event],
Expand Down
2 changes: 1 addition & 1 deletion redux_pytest/fixtures/snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
class StoreSnapshot(Generic[State]):
"""Context object for tests taking snapshots of the store."""

def __init__( # noqa: PLR0913
def __init__(
self: StoreSnapshot,
*,
test_id: str,
Expand Down
72 changes: 40 additions & 32 deletions redux_pytest/fixtures/wait_for.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
Coroutine,
Generator,
Literal,
ParamSpec,
TypeAlias,
overload,
)
Expand All @@ -21,8 +22,10 @@
from tenacity.stop import StopBaseT
from tenacity.wait import WaitBaseT

Waiter: TypeAlias = Callable[[], None]
AsyncWaiter: TypeAlias = Callable[[], Coroutine[None, None, None]]
WaitForArgs = ParamSpec('WaitForArgs')

Waiter: TypeAlias = Callable[WaitForArgs, None]
AsyncWaiter: TypeAlias = Callable[WaitForArgs, Coroutine[None, None, None]]


class WaitFor:
Expand All @@ -38,33 +41,33 @@ def __call__(
*,
stop: StopBaseT | None = None,
wait: WaitBaseT | None = None,
) -> Callable[[Callable[[], None]], Waiter]: ...
) -> Callable[[Callable[WaitForArgs, None]], Waiter[WaitForArgs]]: ...

@overload
def __call__(
self: WaitFor,
check: Callable[[], None],
check: Callable[WaitForArgs, None],
*,
stop: StopBaseT | None = None,
wait: WaitBaseT | None = None,
) -> Waiter: ...
) -> Waiter[WaitForArgs]: ...

@overload
def __call__(
self: WaitFor,
*,
timeout: float | None = None,
wait: WaitBaseT | None = None,
) -> Callable[[Callable[[], None]], Waiter]: ...
) -> Callable[[Callable[WaitForArgs, None]], Waiter[WaitForArgs]]: ...

@overload
def __call__(
self: WaitFor,
check: Callable[[], None],
check: Callable[WaitForArgs, None],
*,
timeout: float | None = None,
wait: WaitBaseT | None = None,
) -> Waiter: ...
) -> Waiter[WaitForArgs]: ...

@overload
def __call__(
Expand All @@ -73,17 +76,17 @@ def __call__(
stop: StopBaseT | None = None,
wait: WaitBaseT | None = None,
run_async: Literal[True],
) -> Callable[[Callable[[], None]], AsyncWaiter]: ...
) -> Callable[[Callable[WaitForArgs, None]], AsyncWaiter[WaitForArgs]]: ...

@overload
def __call__(
self: WaitFor,
check: Callable[[], None],
check: Callable[WaitForArgs, None],
*,
stop: StopBaseT | None = None,
wait: WaitBaseT | None = None,
run_async: Literal[True],
) -> AsyncWaiter: ...
) -> AsyncWaiter[WaitForArgs]: ...

@overload
def __call__(
Expand All @@ -92,57 +95,62 @@ def __call__(
timeout: float | None = None,
wait: WaitBaseT | None = None,
run_async: Literal[True],
) -> Callable[[Callable[[], None]], AsyncWaiter]: ...
) -> Callable[[Callable[WaitForArgs, None]], AsyncWaiter[WaitForArgs]]: ...

@overload
def __call__(
self: WaitFor,
check: Callable[[], None],
check: Callable[WaitForArgs, None],
*,
timeout: float | None = None,
wait: WaitBaseT | None = None,
run_async: Literal[True],
) -> AsyncWaiter: ...
) -> AsyncWaiter[WaitForArgs]: ...

def __call__( # noqa: PLR0913
def __call__(
self: WaitFor,
check: Callable[[], None] | None = None,
check: Callable[WaitForArgs, None] | None = None,
*,
timeout: float | None = None,
stop: StopBaseT | None = None,
wait: WaitBaseT | None = None,
run_async: bool = False,
) -> (
Callable[[Callable[[], None]], Waiter]
| Waiter
| Callable[[Callable[[], None]], AsyncWaiter]
| AsyncWaiter
Callable[[Callable[WaitForArgs, None]], Waiter[WaitForArgs]]
| Waiter[WaitForArgs]
| Callable[[Callable[WaitForArgs, None]], AsyncWaiter[WaitForArgs]]
| AsyncWaiter[WaitForArgs]
):
"""Create a waiter for a condition to be met."""
args = {}
parameters = {}
if timeout is not None:
args['stop'] = stop_after_delay(timeout)
parameters['stop'] = stop_after_delay(timeout)
elif stop:
args['stop'] = stop
parameters['stop'] = stop

args['wait'] = wait or wait_exponential(multiplier=0.5)
parameters['wait'] = wait or wait_exponential(multiplier=0.5)

if run_async:

def async_decorator(check: Callable[[], None]) -> AsyncWaiter:
async def async_wrapper() -> None:
async for attempt in AsyncRetrying(**args):
def async_decorator(
check: Callable[WaitForArgs, None],
) -> AsyncWaiter[WaitForArgs]:
async def async_wrapper(
*args: WaitForArgs.args,
**kwargs: WaitForArgs.kwargs,
) -> None:
async for attempt in AsyncRetrying(**parameters):
with attempt:
check()
check(*args, **kwargs)

return async_wrapper

return async_decorator(check) if check else async_decorator

def decorator(check: Callable[[], None]) -> Waiter:
@retry(**args)
def wrapper() -> None:
check()
def decorator(check: Callable[WaitForArgs, None]) -> Waiter[WaitForArgs]:
@retry(**parameters)
def wrapper(*args: WaitForArgs.args, **kwargs: WaitForArgs.kwargs) -> None:
check(*args, **kwargs)

return wrapper

Expand Down
6 changes: 3 additions & 3 deletions tests/test_autorun.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import re
from dataclasses import replace
from typing import TYPE_CHECKING, Generator
from typing import TYPE_CHECKING, Any, Generator, cast
from unittest.mock import call

import pytest
Expand Down Expand Up @@ -88,15 +88,15 @@ def _(value: int) -> int:


def test_ignore_attribute_error_in_selector(store: StoreType) -> None:
@store.autorun(lambda state: state.non_existing) # pyright: ignore[reportAttributeAccessIssue]
@store.autorun(lambda state: cast(Any, state).non_existing)
def _(_: int) -> int:
pytest.fail('This should never be called')


def test_ignore_attribute_error_in_comparator(store: StoreType) -> None:
@store.autorun(
lambda state: state.value,
lambda state: state.non_existing, # pyright: ignore[reportAttributeAccessIssue]
lambda state: cast(Any, state).non_existing,
)
def _(_: int) -> int:
pytest.fail('This should never be called')
Expand Down
25 changes: 24 additions & 1 deletion tests/test_wait_for_fixture.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# ruff: noqa: D100, D101, D102, D103, D104, D107
import pytest
from _pytest.outcomes import Failed
from tenacity import stop_after_delay
from tenacity import RetryError, stop_after_delay
from tenacity.wait import wait_fixed

from redux_pytest.fixtures.event_loop import LoopThread
from redux_pytest.fixtures.wait_for import WaitFor
Expand All @@ -24,6 +25,28 @@ def check() -> None:
event_loop.create_task(runner())


def test_arguments(wait_for: WaitFor) -> None:
@wait_for(timeout=0.1, wait=wait_fixed(0.05))
def check(min_value: int) -> None:
nonlocal counter
counter += 1
assert counter >= min_value

counter = 0
check(1)

counter = 0
with pytest.raises(RetryError):
check(4)

counter = 0
check(min_value=1)

counter = 0
with pytest.raises(RetryError):
check(min_value=4)


def test_with_stop(wait_for: WaitFor) -> None:
@wait_for(stop=stop_after_delay(0.1))
def check() -> None:
Expand Down
4 changes: 1 addition & 3 deletions tests/test_weakref.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# ruff: noqa: D100, D101, D102, D103, D104, D107
from __future__ import annotations

import gc
import weakref
from dataclasses import replace
from typing import TYPE_CHECKING, Generator, TypeAlias
Expand Down Expand Up @@ -94,7 +93,7 @@ def method_without_keep_ref(self: EventSubscriptionClass, _: DummyEvent) -> None

@pytest.fixture()
def store() -> Generator[StoreType, None, None]:
store = Store(reducer, options=CreateStoreOptions(auto_init=True))
store = Store(reducer, options=CreateStoreOptions(auto_init=True)) # pyright: ignore [reportArgumentType]
yield store
store.dispatch(FinishAction())

Expand Down Expand Up @@ -131,7 +130,6 @@ def render_without_keep_ref(_: int) -> int:

@wait_for
def check_no_ref() -> None:
gc.collect()
assert ref() is None

check_no_ref()
Expand Down

0 comments on commit ee18a30

Please sign in to comment.