Skip to content

Commit b789424

Browse files
committed
feat: support view to be applied to methods of classes, not just functions, it works for autorun too, but only when it is being called directly like a view
1 parent 5df494f commit b789424

File tree

6 files changed

+177
-59
lines changed

6 files changed

+177
-59
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
- chore: add badges in `README.md` and classifiers in `pyproject.toml`
66
- refactor: move the common code for manipulating the signature of the wrapped functions in `WithStore` and `Autorun` to a utility function
77
- feat: support `with_state` to be applied to methods of classes, not just functions
8+
- feat: support `view` to be applied to methods of classes, not just functions, it works for `autorun` too, but only when it is being called directly like a view
89

910
## Version 0.23.0
1011

redux/autorun.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from __future__ import annotations
44

55
import asyncio
6+
import functools
67
import inspect
78
import weakref
89
from asyncio import iscoroutine, iscoroutinefunction
@@ -27,7 +28,7 @@
2728
State,
2829
T,
2930
)
30-
from redux.utils import drop_with_store_parameter
31+
from redux.utils import call_func, signature_without_selector
3132

3233
if TYPE_CHECKING:
3334
from collections.abc import Callable, Coroutine, Generator
@@ -105,7 +106,7 @@ def __init__( # noqa: C901, PLR0912
105106
self.__qualname__ = f'Autorun:{func.__qualname__}'
106107
else:
107108
self.__qualname__ = f'Autorun:{func}'
108-
self.__signature__ = drop_with_store_parameter(func)
109+
self.__signature__ = signature_without_selector(func)
109110
self.__module__ = func.__module__
110111
if (annotations := getattr(func, '__annotations__', None)) is not None:
111112
self.__annotations__ = annotations
@@ -260,8 +261,9 @@ def call(
260261
"""Call the wrapped function with the current state of the store."""
261262
func = self._func() if isinstance(self._func, weakref.ref) else self._func
262263
if func and self._last_selector_result is not None:
263-
value: ReturnType = func(
264-
self._last_selector_result,
264+
value: ReturnType = call_func(
265+
func,
266+
[self._last_selector_result],
265267
*args,
266268
**kwargs,
267269
)
@@ -372,3 +374,27 @@ def unsubscribe() -> None:
372374
self._subscriptions.discard(callback_ref)
373375

374376
return unsubscribe
377+
378+
def __get__(
379+
self: Autorun[
380+
State,
381+
Action,
382+
Event,
383+
SelectorOutput,
384+
ComparatorOutput,
385+
Args,
386+
ReturnType,
387+
],
388+
obj: object | None,
389+
type_: type | None = None,
390+
) -> Autorun[
391+
State,
392+
Action,
393+
Event,
394+
SelectorOutput,
395+
ComparatorOutput,
396+
Args,
397+
ReturnType,
398+
]:
399+
"""Get the autorun instance."""
400+
return cast('Autorun', functools.partial(self, obj))

redux/basic_types.py

Lines changed: 79 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ class FinishEvent(BaseEvent): ...
5959
EventHandler: TypeAlias = Callable[[Event], Any] | Callable[[], Any]
6060
Args = ParamSpec('Args')
6161
Payload = TypeVar('Payload', bound=Any, default=None)
62+
MethodSelf = TypeVar('MethodSelf', bound=object, infer_variance=True)
6263

6364

6465
class CompleteReducerResult(Immutable, Generic[State, Action, Event]):
@@ -232,7 +233,7 @@ class AutorunOptionsImplementation(Immutable, Generic[ReturnType, AutoAwait]):
232233

233234
class AutorunReturnType(
234235
Protocol,
235-
Generic[ReturnType, Args],
236+
Generic[Args, ReturnType],
236237
):
237238
def __call__(
238239
self: AutorunReturnType,
@@ -256,39 +257,84 @@ def unsubscribe(self: AutorunReturnType) -> None: ...
256257
__name__: str
257258

258259

260+
class MethodAutorunReturnType(
261+
AutorunReturnType,
262+
Protocol,
263+
Generic[MethodSelf, Args, ReturnType],
264+
):
265+
def __call__(
266+
self: AutorunReturnType,
267+
self_: MethodSelf,
268+
*args: Args.args,
269+
**kwargs: Args.kwargs,
270+
) -> ReturnType: ...
271+
272+
259273
class AutorunDecorator(Protocol, Generic[ReturnType, SelectorOutput, AutoAwait]):
274+
@overload
275+
def __call__(
276+
self: AutorunDecorator[ReturnType, SelectorOutput, Literal[None]],
277+
func: Callable[Concatenate[SelectorOutput, Args], Awaitable[ReturnType]],
278+
) -> AutorunReturnType[Args, None]: ...
260279
@overload
261280
def __call__(
262281
self: AutorunDecorator[ReturnType, SelectorOutput, Literal[None]],
263282
func: Callable[
264-
Concatenate[SelectorOutput, Args],
283+
Concatenate[MethodSelf, SelectorOutput, Args],
265284
Awaitable[ReturnType],
266285
],
267-
) -> AutorunReturnType[None, Args]: ...
286+
) -> MethodAutorunReturnType[MethodSelf, Args, None]: ...
287+
268288
@overload
269289
def __call__(
270290
self: AutorunDecorator[ReturnType, SelectorOutput, Literal[None]],
271291
func: Callable[
272292
Concatenate[SelectorOutput, Args],
273293
ReturnType,
274294
],
275-
) -> AutorunReturnType[ReturnType, Args]: ...
295+
) -> AutorunReturnType[Args, ReturnType]: ...
296+
@overload
297+
def __call__(
298+
self: AutorunDecorator[ReturnType, SelectorOutput, Literal[None]],
299+
func: Callable[
300+
Concatenate[MethodSelf, SelectorOutput, Args],
301+
ReturnType,
302+
],
303+
) -> MethodAutorunReturnType[MethodSelf, Args, ReturnType]: ...
304+
276305
@overload
277306
def __call__(
278307
self: AutorunDecorator[ReturnType, SelectorOutput, Literal[True]],
279308
func: Callable[
280309
Concatenate[SelectorOutput, Args],
281310
Awaitable[ReturnType],
282311
],
283-
) -> AutorunReturnType[None, Args]: ...
312+
) -> AutorunReturnType[Args, None]: ...
313+
@overload
314+
def __call__(
315+
self: AutorunDecorator[ReturnType, SelectorOutput, Literal[True]],
316+
func: Callable[
317+
Concatenate[MethodSelf, SelectorOutput, Args],
318+
Awaitable[ReturnType],
319+
],
320+
) -> MethodAutorunReturnType[MethodSelf, Args, None]: ...
321+
284322
@overload
285323
def __call__(
286324
self: AutorunDecorator[ReturnType, SelectorOutput, Literal[False]],
287325
func: Callable[
288326
Concatenate[SelectorOutput, Args],
289327
Awaitable[ReturnType],
290328
],
291-
) -> AutorunReturnType[Awaitable[ReturnType], Args]: ...
329+
) -> AutorunReturnType[Args, Awaitable[ReturnType]]: ...
330+
@overload
331+
def __call__(
332+
self: AutorunDecorator[ReturnType, SelectorOutput, Literal[False]],
333+
func: Callable[
334+
Concatenate[MethodSelf, SelectorOutput, Args],
335+
Awaitable[ReturnType],
336+
],
337+
) -> MethodAutorunReturnType[MethodSelf, Args, Awaitable[ReturnType]]: ...
292338

293339
@overload
294340
def __call__(
@@ -297,7 +343,15 @@ def __call__(
297343
Concatenate[SelectorOutput, Args],
298344
ReturnType,
299345
],
300-
) -> AutorunReturnType[ReturnType, Args]: ...
346+
) -> AutorunReturnType[Args, ReturnType]: ...
347+
@overload
348+
def __call__(
349+
self: AutorunDecorator[ReturnType, SelectorOutput, bool],
350+
func: Callable[
351+
Concatenate[MethodSelf, SelectorOutput, Args],
352+
ReturnType,
353+
],
354+
) -> MethodAutorunReturnType[MethodSelf, Args, ReturnType]: ...
301355

302356

303357
# View
@@ -347,6 +401,14 @@ def __call__(
347401
Awaitable[ReturnType],
348402
],
349403
) -> ViewReturnType[Awaitable[ReturnType], Args]: ...
404+
@overload
405+
def __call__(
406+
self: ViewDecorator,
407+
func: Callable[
408+
Concatenate[MethodSelf, SelectorOutput, Args],
409+
Awaitable[ReturnType],
410+
],
411+
) -> ViewReturnType[Awaitable[ReturnType], Args]: ...
350412

351413
@overload
352414
def __call__(
@@ -356,14 +418,19 @@ def __call__(
356418
ReturnType,
357419
],
358420
) -> ViewReturnType[ReturnType, Args]: ...
421+
@overload
422+
def __call__(
423+
self: ViewDecorator,
424+
func: Callable[
425+
Concatenate[MethodSelf, SelectorOutput, Args],
426+
ReturnType,
427+
],
428+
) -> ViewReturnType[ReturnType, Args]: ...
359429

360430

361431
# With Store
362432

363433

364-
Self = TypeVar('Self', bound=object, infer_variance=True)
365-
366-
367434
class WithStateDecorator(
368435
Protocol,
369436
Generic[SelectorOutput],
@@ -377,8 +444,8 @@ def __call__(
377444
@overload
378445
def __call__(
379446
self: WithStateDecorator,
380-
func: Callable[Concatenate[Self, SelectorOutput, Args], ReturnType],
381-
) -> Callable[Concatenate[Self, Args], ReturnType]: ...
447+
func: Callable[Concatenate[MethodSelf, SelectorOutput, Args], ReturnType],
448+
) -> Callable[Concatenate[MethodSelf, Args], ReturnType]: ...
382449

383450

384451
class EventSubscriber(Protocol):

redux/main.py

Lines changed: 25 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,10 @@
3939
FinishAction,
4040
FinishEvent,
4141
InitAction,
42+
MethodSelf,
4243
ReducerType,
4344
ReturnType,
4445
SelectorOutput,
45-
Self,
4646
SnapshotAtom,
4747
State,
4848
StoreOptions,
@@ -56,7 +56,7 @@
5656
is_state_reducer_result,
5757
)
5858
from redux.serialization_mixin import SerializationMixin
59-
from redux.utils import drop_with_store_parameter
59+
from redux.utils import call_func, signature_without_selector
6060

6161
if TYPE_CHECKING:
6262
from collections.abc import Callable
@@ -311,7 +311,7 @@ def autorun_decorator(
311311
Concatenate[SelectorOutput, Args],
312312
AwaitableOrNot[ReturnType],
313313
],
314-
) -> AutorunReturnType[ReturnType, Args]:
314+
) -> AutorunReturnType[Args, ReturnType]:
315315
return self.store_options.autorun_class(
316316
store=self,
317317
selector=selector,
@@ -335,20 +335,31 @@ def view_decorator(
335335
func: Callable[
336336
Concatenate[SelectorOutput, Args],
337337
ReturnType,
338+
]
339+
| Callable[
340+
Concatenate[MethodSelf, SelectorOutput, Args],
341+
ReturnType,
338342
],
339343
) -> ViewReturnType[ReturnType, Args]: ...
340344
@overload
341345
def view_decorator(
342346
func: Callable[
343347
Concatenate[SelectorOutput, Args],
344348
Awaitable[ReturnType],
349+
]
350+
| Callable[
351+
Concatenate[MethodSelf, SelectorOutput, Args],
352+
Awaitable[ReturnType],
345353
],
346354
) -> ViewReturnType[Awaitable[ReturnType], Args]: ...
347-
348355
def view_decorator(
349356
func: Callable[
350357
Concatenate[SelectorOutput, Args],
351358
AwaitableOrNot[ReturnType],
359+
]
360+
| Callable[
361+
Concatenate[MethodSelf, SelectorOutput, Args],
362+
AwaitableOrNot[ReturnType],
352363
],
353364
) -> ViewReturnType[AwaitableOrNot[ReturnType], Args]:
354365
_options = options or ViewOptions()
@@ -395,56 +406,31 @@ def with_state_decorator(
395406
@overload
396407
def with_state_decorator(
397408
func: Callable[
398-
Concatenate[Self, SelectorOutput, Args],
409+
Concatenate[MethodSelf, SelectorOutput, Args],
399410
ReturnType,
400411
],
401-
) -> Callable[Concatenate[Self, Args], ReturnType]: ...
412+
) -> Callable[Concatenate[MethodSelf, Args], ReturnType]: ...
402413
def with_state_decorator(
403414
func: Callable[
404415
Concatenate[SelectorOutput, Args],
405416
ReturnType,
406417
]
407418
| Callable[
408-
Concatenate[Self, SelectorOutput, Args],
419+
Concatenate[MethodSelf, SelectorOutput, Args],
409420
ReturnType,
410421
],
411-
) -> Callable[Args, ReturnType] | Callable[Concatenate[Self, Args], ReturnType]:
412-
signature = drop_with_store_parameter(func)
413-
414-
if (
415-
signature.parameters
416-
and next(iter(signature.parameters.values())).name == 'self'
417-
):
418-
func_ = cast(
419-
'Callable[Concatenate[Self, SelectorOutput, Args], ReturnType]',
420-
func,
421-
)
422-
423-
def wrapper(*args: Args.args, **kwargs: Args.kwargs) -> ReturnType:
424-
if self._state is None:
425-
msg = 'Store has not been initialized yet.'
426-
raise RuntimeError(msg)
427-
self_ = cast('Self', args[0])
428-
args_ = cast('Any', args[1:])
429-
return func_(self_, selector(self._state), *args_, **kwargs)
430-
431-
wrapped = wraps(func_)(wrapper)
432-
wrapped.__signature__ = signature # pyright: ignore [reportAttributeAccessIssue]
433-
434-
return wrapped
435-
436-
func_ = cast(
437-
'Callable[Concatenate[SelectorOutput, Args], ReturnType]',
438-
func,
439-
)
440-
422+
) -> (
423+
Callable[Args, ReturnType]
424+
| Callable[Concatenate[MethodSelf, Args], ReturnType]
425+
):
441426
def wrapper(*args: Args.args, **kwargs: Args.kwargs) -> ReturnType:
442427
if self._state is None:
443428
msg = 'Store has not been initialized yet.'
444429
raise RuntimeError(msg)
445-
return func_(selector(self._state), *args, **kwargs)
430+
return call_func(func, [selector(self._state)], *args, **kwargs)
446431

447-
wrapped = wraps(func_)(wrapper)
432+
signature = signature_without_selector(func)
433+
wrapped = wraps(cast('Any', func))(wrapper)
448434
wrapped.__signature__ = signature # pyright: ignore [reportAttributeAccessIssue]
449435

450436
return wrapped

0 commit comments

Comments
 (0)