Skip to content

Commit 492c4db

Browse files
committed
refactor: move the common code for manipulating the signature of the wrapped functions in WithStore and Autorun to a utility function
1 parent ed7d6d7 commit 492c4db

File tree

5 files changed

+83
-24
lines changed

5 files changed

+83
-24
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## Upcoming
44

55
- chore: add badges in `README.md` and classifiers in `pyproject.toml`
6+
- refactor: move the common code for manipulating the signature of the wrapped functions in `WithStore` and `Autorun` to a utility function
67

78
## Version 0.23.0
89

redux/autorun.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
State,
2828
T,
2929
)
30+
from redux.utils import drop_with_store_parameter
3031

3132
if TYPE_CHECKING:
3233
from collections.abc import Callable, Coroutine, Generator
@@ -83,7 +84,7 @@ class Autorun(
8384
):
8485
"""Run a wrapped function in response to specific state changes in the store."""
8586

86-
def __init__( # noqa: C901, PLR0912, PLR0915
87+
def __init__( # noqa: C901, PLR0912
8788
self: Autorun,
8889
*,
8990
store: Store[State, Action, Event],
@@ -104,14 +105,7 @@ def __init__( # noqa: C901, PLR0912, PLR0915
104105
self.__qualname__ = f'Autorun:{func.__qualname__}'
105106
else:
106107
self.__qualname__ = f'Autorun:{func}'
107-
signature = inspect.signature(func)
108-
parameters = list(signature.parameters.values())
109-
if parameters and parameters[0].kind in [
110-
inspect.Parameter.POSITIONAL_ONLY,
111-
inspect.Parameter.POSITIONAL_OR_KEYWORD,
112-
]:
113-
parameters = parameters[1:]
114-
self.__signature__ = signature.replace(parameters=parameters)
108+
self.__signature__ = drop_with_store_parameter(func)
115109
self.__module__ = func.__module__
116110
if (annotations := getattr(func, '__annotations__', None)) is not None:
117111
self.__annotations__ = annotations

redux/main.py

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
is_state_reducer_result,
5656
)
5757
from redux.serialization_mixin import SerializationMixin
58+
from redux.utils import drop_with_store_parameter
5859

5960
if TYPE_CHECKING:
6061
from collections.abc import Callable
@@ -395,18 +396,8 @@ def wrapper(*args: Args.args, **kwargs: Args.kwargs) -> ReturnType:
395396
raise RuntimeError(msg)
396397
return func(selector(self._state), *args, **kwargs)
397398

398-
signature = inspect.signature(func)
399-
parameters = list(signature.parameters.values())
400-
if parameters and parameters[0].kind in [
401-
inspect.Parameter.POSITIONAL_ONLY,
402-
inspect.Parameter.POSITIONAL_OR_KEYWORD,
403-
]:
404-
parameters = parameters[1:]
405-
406399
wrapped = wraps(func)(wrapper)
407-
wrapped.__signature__ = signature.replace( # pyright: ignore [reportAttributeAccessIssue]
408-
parameters=parameters,
409-
)
400+
wrapped.__signature__ = drop_with_store_parameter(func) # pyright: ignore [reportAttributeAccessIssue]
410401

411402
return wrapped
412403

redux/utils.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
"""Utility functions for the project."""
2+
3+
from __future__ import annotations
4+
5+
import inspect
6+
from typing import TYPE_CHECKING
7+
8+
if TYPE_CHECKING:
9+
from collections.abc import Callable
10+
11+
12+
def drop_with_store_parameter(func: Callable) -> inspect.Signature:
13+
"""Drop the parameter associated consumed by the with_store/autorun/view wrapper."""
14+
signature = inspect.signature(func)
15+
parameters = list(signature.parameters.values())
16+
self_parameter: list[inspect.Parameter] = []
17+
is_method = parameters and parameters[0].name == 'self'
18+
if is_method:
19+
self_parameter = [parameters[0]]
20+
parameters = parameters[1:]
21+
if parameters and parameters[0].kind in [
22+
inspect.Parameter.POSITIONAL_ONLY,
23+
inspect.Parameter.POSITIONAL_OR_KEYWORD,
24+
]:
25+
parameters = parameters[1:]
26+
return signature.replace(parameters=self_parameter + parameters)

tests/test_autorun.py

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@
2323
from redux.main import Store
2424

2525
if TYPE_CHECKING:
26-
from collections.abc import Generator
27-
2826
from pytest_mock import MockerFixture
2927

3028
from redux_pytest.fixtures import StoreSnapshot
@@ -73,10 +71,12 @@ def reducer(
7371

7472

7573
@pytest.fixture
76-
def store() -> Generator[StoreType, None, None]:
74+
def store() -> StoreType:
7775
store: StoreType = Store(reducer, options=StoreOptions(auto_init=True))
78-
yield store
76+
return store
77+
7978

79+
def dispatch_actions(store: StoreType) -> None:
8080
store.dispatch(IncrementAction())
8181
store.dispatch(IncrementByTwoAction())
8282
store.dispatch(IncrementAction())
@@ -89,6 +89,8 @@ def _(value: int) -> int:
8989
store_snapshot.take()
9090
return value
9191

92+
dispatch_actions(store)
93+
9294

9395
def test_name_attr(store: StoreType) -> None:
9496
"""Test `autorun` decorator name attribute."""
@@ -117,6 +119,8 @@ def __repr__(self) -> str:
117119

118120
assert decorated_instance.__name__ == 'Autorun:Decorated'
119121

122+
dispatch_actions(store)
123+
120124

121125
def test_signature(store: StoreType) -> None:
122126
"""Test `with_state` decorator `__signature__` attribute."""
@@ -178,12 +182,16 @@ def func(
178182

179183
assert signature.return_annotation == 'int'
180184

185+
dispatch_actions(store)
186+
181187

182188
def test_ignore_attribute_error_in_selector(store: StoreType) -> None:
183189
@store.autorun(lambda state: cast('Any', state).non_existing)
184190
def _(_: int) -> None:
185191
pytest.fail('This should never be called')
186192

193+
dispatch_actions(store)
194+
187195

188196
def test_ignore_attribute_error_in_comparator(store: StoreType) -> None:
189197
@store.autorun(
@@ -193,6 +201,8 @@ def test_ignore_attribute_error_in_comparator(store: StoreType) -> None:
193201
def _(_: int) -> None:
194202
pytest.fail('This should never be called')
195203

204+
dispatch_actions(store)
205+
196206

197207
def test_with_comparator(
198208
store_snapshot: StoreSnapshot,
@@ -206,6 +216,8 @@ def _(value: int) -> int:
206216
store_snapshot.take()
207217
return value
208218

219+
dispatch_actions(store)
220+
209221

210222
def test_value_property(store_snapshot: StoreSnapshot, store: StoreType) -> None:
211223
@store.autorun(lambda state: state.value)
@@ -221,6 +233,8 @@ def check(_: int) -> None:
221233

222234
render.subscribe(check)
223235

236+
dispatch_actions(store)
237+
224238

225239
def test_callability(store_snapshot: StoreSnapshot, store: StoreType) -> None:
226240
@store.autorun(lambda state: state.value)
@@ -233,6 +247,8 @@ def check(state: StateType) -> None:
233247

234248
store._subscribe(check) # noqa: SLF001
235249

250+
dispatch_actions(store)
251+
236252

237253
def test_subscription(store_snapshot: StoreSnapshot, store: StoreType) -> None:
238254
@store.autorun(lambda state: state.value)
@@ -244,6 +260,8 @@ def reaction(_: int) -> None:
244260

245261
render.subscribe(reaction, initial_run=True)
246262

263+
dispatch_actions(store)
264+
247265

248266
def test_unsubscription(store: StoreType) -> None:
249267
@store.autorun(lambda state: state.value)
@@ -256,6 +274,8 @@ def reaction(_: int) -> None:
256274
unsubscribe = render.subscribe(reaction, initial_run=False)
257275
unsubscribe()
258276

277+
dispatch_actions(store)
278+
259279

260280
def test_repr(store: StoreType) -> None:
261281
@store.autorun(lambda state: state.value)
@@ -274,6 +294,8 @@ def render(value: int) -> int:
274294
repr(render),
275295
)
276296

297+
store.dispatch(FinishAction())
298+
277299

278300
call_sequence = [
279301
# 0
@@ -317,6 +339,8 @@ def render(_: int) -> None: ...
317339

318340
assert render.mock_calls == [call(0), call(1), call(3), call(1)]
319341

342+
store.dispatch(FinishAction())
343+
320344

321345
def test_no_auto_call_and_no_initial_call_with_reactive_set(
322346
store: StoreType,
@@ -338,6 +362,8 @@ def render(_: int) -> None: ...
338362

339363
assert render.mock_calls == [call(1), call(3), call(1)]
340364

365+
store.dispatch(FinishAction())
366+
341367

342368
def test_with_auto_call_and_initial_call_and_reactive_set(
343369
store: StoreType,
@@ -369,6 +395,8 @@ def render(_: int) -> None: ...
369395
call(1),
370396
]
371397

398+
store.dispatch(FinishAction())
399+
372400

373401
def test_task_mode_without_arguments(
374402
store: StoreType,
@@ -392,6 +420,8 @@ def check() -> None:
392420

393421
store.subscribe_event(FinishEvent, check)
394422

423+
dispatch_actions(store)
424+
395425

396426
def test_view_mode_with_arguments_autorun(
397427
store: StoreType,
@@ -409,3 +439,20 @@ def render(_: int, *, some_other_value: int) -> int:
409439
return some_other_value
410440

411441
assert render(some_other_value=12345) == 12345
442+
443+
store.dispatch(FinishAction())
444+
445+
446+
def test_methods(store: StoreType) -> None:
447+
calls = []
448+
449+
class SomeClass:
450+
def render(self, value: int) -> None:
451+
calls.append(value)
452+
453+
instance = SomeClass()
454+
store.autorun(lambda state: state.value)(instance.render)
455+
456+
dispatch_actions(store)
457+
458+
assert calls == [0, 1, 3, 4]

0 commit comments

Comments
 (0)