Skip to content

Commit ee18a30

Browse files
committed
feat(test): add arguments for wait_for's check
1 parent 0f76ec0 commit ee18a30

File tree

10 files changed

+167
-137
lines changed

10 files changed

+167
-137
lines changed

CHANGELOG.md

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

3+
## Version 0.15.10
4+
5+
- feat(test): add arguments for `wait_for`'s `check`
6+
37
## Version 0.15.9
48

59
- refactor(core): use `str_to_bool` of `python-strtobool` instead of `strtobool`

demo.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,16 @@
2525
from redux.main import CreateStoreOptions
2626

2727

28-
class CountAction(BaseAction):
29-
...
28+
class CountAction(BaseAction): ...
3029

3130

32-
class IncrementAction(CountAction):
33-
...
31+
class IncrementAction(CountAction): ...
3432

3533

36-
class DecrementAction(CountAction):
37-
...
34+
class DecrementAction(CountAction): ...
3835

3936

40-
class DoNothingAction(CountAction):
41-
...
37+
class DoNothingAction(CountAction): ...
4238

4339

4440
ActionType = InitAction | FinishAction | CountAction | CombineReducerAction

poetry.lock

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

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "python-redux"
3-
version = "0.15.9"
3+
version = "0.15.10"
44
description = "Redux implementation for Python"
55
authors = ["Sassan Haradji <[email protected]>"]
66
license = "Apache-2.0"
@@ -20,8 +20,8 @@ optional = true
2020

2121
[tool.poetry.group.dev.dependencies]
2222
poethepoet = "^0.24.4"
23-
pyright = "^1.1.361"
24-
ruff = "^0.4.3"
23+
pyright = "^1.1.373"
24+
ruff = "^0.5.4"
2525
pytest = "^8.1.1"
2626
pytest-cov = "^4.1.0"
2727
pytest-timeout = "^2.3.1"

redux/autorun.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class Autorun(
3333
AutorunArgs,
3434
],
3535
):
36-
def __init__( # noqa: PLR0913
36+
def __init__(
3737
self: Autorun,
3838
*,
3939
store: Store[State, Action, Event],

redux_pytest/fixtures/snapshot.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
class StoreSnapshot(Generic[State]):
2424
"""Context object for tests taking snapshots of the store."""
2525

26-
def __init__( # noqa: PLR0913
26+
def __init__(
2727
self: StoreSnapshot,
2828
*,
2929
test_id: str,

redux_pytest/fixtures/wait_for.py

Lines changed: 40 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
Coroutine,
99
Generator,
1010
Literal,
11+
ParamSpec,
1112
TypeAlias,
1213
overload,
1314
)
@@ -21,8 +22,10 @@
2122
from tenacity.stop import StopBaseT
2223
from tenacity.wait import WaitBaseT
2324

24-
Waiter: TypeAlias = Callable[[], None]
25-
AsyncWaiter: TypeAlias = Callable[[], Coroutine[None, None, None]]
25+
WaitForArgs = ParamSpec('WaitForArgs')
26+
27+
Waiter: TypeAlias = Callable[WaitForArgs, None]
28+
AsyncWaiter: TypeAlias = Callable[WaitForArgs, Coroutine[None, None, None]]
2629

2730

2831
class WaitFor:
@@ -38,33 +41,33 @@ def __call__(
3841
*,
3942
stop: StopBaseT | None = None,
4043
wait: WaitBaseT | None = None,
41-
) -> Callable[[Callable[[], None]], Waiter]: ...
44+
) -> Callable[[Callable[WaitForArgs, None]], Waiter[WaitForArgs]]: ...
4245

4346
@overload
4447
def __call__(
4548
self: WaitFor,
46-
check: Callable[[], None],
49+
check: Callable[WaitForArgs, None],
4750
*,
4851
stop: StopBaseT | None = None,
4952
wait: WaitBaseT | None = None,
50-
) -> Waiter: ...
53+
) -> Waiter[WaitForArgs]: ...
5154

5255
@overload
5356
def __call__(
5457
self: WaitFor,
5558
*,
5659
timeout: float | None = None,
5760
wait: WaitBaseT | None = None,
58-
) -> Callable[[Callable[[], None]], Waiter]: ...
61+
) -> Callable[[Callable[WaitForArgs, None]], Waiter[WaitForArgs]]: ...
5962

6063
@overload
6164
def __call__(
6265
self: WaitFor,
63-
check: Callable[[], None],
66+
check: Callable[WaitForArgs, None],
6467
*,
6568
timeout: float | None = None,
6669
wait: WaitBaseT | None = None,
67-
) -> Waiter: ...
70+
) -> Waiter[WaitForArgs]: ...
6871

6972
@overload
7073
def __call__(
@@ -73,17 +76,17 @@ def __call__(
7376
stop: StopBaseT | None = None,
7477
wait: WaitBaseT | None = None,
7578
run_async: Literal[True],
76-
) -> Callable[[Callable[[], None]], AsyncWaiter]: ...
79+
) -> Callable[[Callable[WaitForArgs, None]], AsyncWaiter[WaitForArgs]]: ...
7780

7881
@overload
7982
def __call__(
8083
self: WaitFor,
81-
check: Callable[[], None],
84+
check: Callable[WaitForArgs, None],
8285
*,
8386
stop: StopBaseT | None = None,
8487
wait: WaitBaseT | None = None,
8588
run_async: Literal[True],
86-
) -> AsyncWaiter: ...
89+
) -> AsyncWaiter[WaitForArgs]: ...
8790

8891
@overload
8992
def __call__(
@@ -92,57 +95,62 @@ def __call__(
9295
timeout: float | None = None,
9396
wait: WaitBaseT | None = None,
9497
run_async: Literal[True],
95-
) -> Callable[[Callable[[], None]], AsyncWaiter]: ...
98+
) -> Callable[[Callable[WaitForArgs, None]], AsyncWaiter[WaitForArgs]]: ...
9699

97100
@overload
98101
def __call__(
99102
self: WaitFor,
100-
check: Callable[[], None],
103+
check: Callable[WaitForArgs, None],
101104
*,
102105
timeout: float | None = None,
103106
wait: WaitBaseT | None = None,
104107
run_async: Literal[True],
105-
) -> AsyncWaiter: ...
108+
) -> AsyncWaiter[WaitForArgs]: ...
106109

107-
def __call__( # noqa: PLR0913
110+
def __call__(
108111
self: WaitFor,
109-
check: Callable[[], None] | None = None,
112+
check: Callable[WaitForArgs, None] | None = None,
110113
*,
111114
timeout: float | None = None,
112115
stop: StopBaseT | None = None,
113116
wait: WaitBaseT | None = None,
114117
run_async: bool = False,
115118
) -> (
116-
Callable[[Callable[[], None]], Waiter]
117-
| Waiter
118-
| Callable[[Callable[[], None]], AsyncWaiter]
119-
| AsyncWaiter
119+
Callable[[Callable[WaitForArgs, None]], Waiter[WaitForArgs]]
120+
| Waiter[WaitForArgs]
121+
| Callable[[Callable[WaitForArgs, None]], AsyncWaiter[WaitForArgs]]
122+
| AsyncWaiter[WaitForArgs]
120123
):
121124
"""Create a waiter for a condition to be met."""
122-
args = {}
125+
parameters = {}
123126
if timeout is not None:
124-
args['stop'] = stop_after_delay(timeout)
127+
parameters['stop'] = stop_after_delay(timeout)
125128
elif stop:
126-
args['stop'] = stop
129+
parameters['stop'] = stop
127130

128-
args['wait'] = wait or wait_exponential(multiplier=0.5)
131+
parameters['wait'] = wait or wait_exponential(multiplier=0.5)
129132

130133
if run_async:
131134

132-
def async_decorator(check: Callable[[], None]) -> AsyncWaiter:
133-
async def async_wrapper() -> None:
134-
async for attempt in AsyncRetrying(**args):
135+
def async_decorator(
136+
check: Callable[WaitForArgs, None],
137+
) -> AsyncWaiter[WaitForArgs]:
138+
async def async_wrapper(
139+
*args: WaitForArgs.args,
140+
**kwargs: WaitForArgs.kwargs,
141+
) -> None:
142+
async for attempt in AsyncRetrying(**parameters):
135143
with attempt:
136-
check()
144+
check(*args, **kwargs)
137145

138146
return async_wrapper
139147

140148
return async_decorator(check) if check else async_decorator
141149

142-
def decorator(check: Callable[[], None]) -> Waiter:
143-
@retry(**args)
144-
def wrapper() -> None:
145-
check()
150+
def decorator(check: Callable[WaitForArgs, None]) -> Waiter[WaitForArgs]:
151+
@retry(**parameters)
152+
def wrapper(*args: WaitForArgs.args, **kwargs: WaitForArgs.kwargs) -> None:
153+
check(*args, **kwargs)
146154

147155
return wrapper
148156

tests/test_autorun.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
import re
55
from dataclasses import replace
6-
from typing import TYPE_CHECKING, Generator
6+
from typing import TYPE_CHECKING, Any, Generator, cast
77
from unittest.mock import call
88

99
import pytest
@@ -88,15 +88,15 @@ def _(value: int) -> int:
8888

8989

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

9595

9696
def test_ignore_attribute_error_in_comparator(store: StoreType) -> None:
9797
@store.autorun(
9898
lambda state: state.value,
99-
lambda state: state.non_existing, # pyright: ignore[reportAttributeAccessIssue]
99+
lambda state: cast(Any, state).non_existing,
100100
)
101101
def _(_: int) -> int:
102102
pytest.fail('This should never be called')

tests/test_wait_for_fixture.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# ruff: noqa: D100, D101, D102, D103, D104, D107
22
import pytest
33
from _pytest.outcomes import Failed
4-
from tenacity import stop_after_delay
4+
from tenacity import RetryError, stop_after_delay
5+
from tenacity.wait import wait_fixed
56

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

2627

28+
def test_arguments(wait_for: WaitFor) -> None:
29+
@wait_for(timeout=0.1, wait=wait_fixed(0.05))
30+
def check(min_value: int) -> None:
31+
nonlocal counter
32+
counter += 1
33+
assert counter >= min_value
34+
35+
counter = 0
36+
check(1)
37+
38+
counter = 0
39+
with pytest.raises(RetryError):
40+
check(4)
41+
42+
counter = 0
43+
check(min_value=1)
44+
45+
counter = 0
46+
with pytest.raises(RetryError):
47+
check(min_value=4)
48+
49+
2750
def test_with_stop(wait_for: WaitFor) -> None:
2851
@wait_for(stop=stop_after_delay(0.1))
2952
def check() -> None:

tests/test_weakref.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# ruff: noqa: D100, D101, D102, D103, D104, D107
22
from __future__ import annotations
33

4-
import gc
54
import weakref
65
from dataclasses import replace
76
from typing import TYPE_CHECKING, Generator, TypeAlias
@@ -94,7 +93,7 @@ def method_without_keep_ref(self: EventSubscriptionClass, _: DummyEvent) -> None
9493

9594
@pytest.fixture()
9695
def store() -> Generator[StoreType, None, None]:
97-
store = Store(reducer, options=CreateStoreOptions(auto_init=True))
96+
store = Store(reducer, options=CreateStoreOptions(auto_init=True)) # pyright: ignore [reportArgumentType]
9897
yield store
9998
store.dispatch(FinishAction())
10099

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

132131
@wait_for
133132
def check_no_ref() -> None:
134-
gc.collect()
135133
assert ref() is None
136134

137135
check_no_ref()

0 commit comments

Comments
 (0)