Skip to content

Commit

Permalink
feat(core): add view method to Store to allow computing a derived…
Browse files Browse the repository at this point in the history
… value from the state only when it is accessed and caching the result until the relevant parts of the state change
  • Loading branch information
sassanh committed May 4, 2024
1 parent 58343ce commit debf12d
Show file tree
Hide file tree
Showing 8 changed files with 215 additions and 146 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Version 0.15.1

- feat(core): add `view` method to `Store` to allow computing a derived value from
the state only when it is accessed and caching the result until the relevant parts
of the state change

## Version 0.15.0

- refactor(autorun)!: setting `initial_run` option of autorun to `False` used to
Expand Down
46 changes: 23 additions & 23 deletions poetry.lock

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

4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ optional = true

[tool.poetry.group.dev.dependencies]
poethepoet = "^0.24.4"
pyright = "^1.1.357"
ruff = "^0.3.5"
pyright = "^1.1.361"
ruff = "^0.4.3"
pytest = "^8.1.1"
pytest-cov = "^4.1.0"
pytest-timeout = "^2.3.1"
Expand Down
16 changes: 9 additions & 7 deletions redux/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
AutorunDecorator,
AutorunOptions,
AutorunReturnType,
AutorunType,
BaseAction,
BaseCombineReducerState,
BaseEvent,
Expand All @@ -24,6 +23,8 @@
ReducerResult,
ReducerType,
Scheduler,
ViewDecorator,
ViewReturnType,
is_complete_reducer_result,
is_state_reducer_result,
)
Expand All @@ -34,9 +35,13 @@
'AutorunDecorator',
'AutorunOptions',
'AutorunReturnType',
'AutorunType',
'BaseAction',
'BaseCombineReducerState',
'BaseEvent',
'CombineReducerAction',
'CombineReducerInitAction',
'CombineReducerRegisterAction',
'CombineReducerUnregisterAction',
'CompleteReducerResult',
'CreateStoreOptions',
'Dispatch',
Expand All @@ -49,13 +54,10 @@
'ReducerResult',
'ReducerType',
'Scheduler',
'ViewDecorator',
'ViewReturnType',
'is_complete_reducer_result',
'is_state_reducer_result',
'BaseCombineReducerState',
'CombineReducerAction',
'CombineReducerInitAction',
'CombineReducerRegisterAction',
'CombineReducerUnregisterAction',
'combine_reducers',
'Store',
)
99 changes: 39 additions & 60 deletions redux/autorun.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
# ruff: noqa: D100, D101, D102, D103, D104, D105, D107
from __future__ import annotations

import functools
import inspect
import weakref
from asyncio import Task, iscoroutine
from inspect import signature
from typing import TYPE_CHECKING, Any, Callable, Generic, cast
from typing import TYPE_CHECKING, Any, Callable, Concatenate, Generic, cast

from redux.basic_types import (
Action,
AutorunArgs,
AutorunOptions,
AutorunOriginalReturnType,
ComparatorOutput,
Expand All @@ -19,8 +18,6 @@
)

if TYPE_CHECKING:
from types import MethodType

from redux.main import Store


Expand All @@ -32,6 +29,7 @@ class Autorun(
SelectorOutput,
ComparatorOutput,
AutorunOriginalReturnType,
AutorunArgs,
],
):
def __init__( # noqa: PLR0913
Expand All @@ -40,8 +38,10 @@ def __init__( # noqa: PLR0913
store: Store[State, Action, Event],
selector: Callable[[State], SelectorOutput],
comparator: Callable[[State], Any] | None,
func: Callable[[SelectorOutput], AutorunOriginalReturnType]
| Callable[[SelectorOutput, SelectorOutput], AutorunOriginalReturnType],
func: Callable[
Concatenate[SelectorOutput, AutorunArgs],
AutorunOriginalReturnType,
],
options: AutorunOptions[AutorunOriginalReturnType],
) -> None:
if not options.reactive and options.auto_call:
Expand All @@ -54,9 +54,9 @@ def __init__( # noqa: PLR0913
if options.keep_ref:
self._func = func
elif inspect.ismethod(func):
self._func = weakref.WeakMethod(func)
self._func = weakref.WeakMethod(func, self.unsubscribe)
else:
self._func = weakref.ref(func, lambda _: self.unsubscribe())
self._func = weakref.ref(func, self.unsubscribe)
self._options = options

self._last_selector_result: SelectorOutput | None = None
Expand All @@ -70,12 +70,19 @@ def __init__( # noqa: PLR0913
| weakref.ref[Callable[[AutorunOriginalReturnType], Any]]
] = set()

self._check_and_call(store._state, call=self._options.initial_call) # noqa: SLF001
self._check_and_call(store._state, self._options.initial_call) # noqa: SLF001

if self._options.reactive:
self.unsubscribe = store.subscribe(
functools.partial(self._check_and_call, call=self._options.auto_call),
self._unsubscribe = store.subscribe(
lambda state: self._check_and_call(state, self._options.auto_call),
)
else:
self._unsubscribe = None

def unsubscribe(self: Autorun, _: weakref.ref | None = None) -> None:
if self._unsubscribe:
self._unsubscribe()
self._unsubscribe = None

def inform_subscribers(
self: Autorun[
Expand All @@ -85,6 +92,7 @@ def inform_subscribers(
SelectorOutput,
ComparatorOutput,
AutorunOriginalReturnType,
AutorunArgs,
],
) -> None:
for subscriber_ in self._subscriptions.copy():
Expand All @@ -97,37 +105,6 @@ def inform_subscribers(
subscriber = subscriber_
subscriber(self._latest_value)

def call_func(
self: Autorun[
State,
Action,
Event,
SelectorOutput,
ComparatorOutput,
AutorunOriginalReturnType,
],
selector_result: SelectorOutput,
previous_result: SelectorOutput | None,
func: Callable[
[SelectorOutput, SelectorOutput],
AutorunOriginalReturnType,
]
| Callable[[SelectorOutput], AutorunOriginalReturnType]
| MethodType,
) -> AutorunOriginalReturnType:
if len(signature(func).parameters) == 1:
return cast(
Callable[[SelectorOutput], AutorunOriginalReturnType],
func,
)(selector_result)
return cast(
Callable[
[SelectorOutput, SelectorOutput | None],
AutorunOriginalReturnType,
],
func,
)(selector_result, previous_result)

def _task_callback(
self: Autorun[
State,
Expand All @@ -136,6 +113,7 @@ def _task_callback(
SelectorOutput,
ComparatorOutput,
AutorunOriginalReturnType,
AutorunArgs,
],
task: Task,
) -> None:
Expand All @@ -150,10 +128,12 @@ def _check_and_call(
SelectorOutput,
ComparatorOutput,
AutorunOriginalReturnType,
AutorunArgs,
],
state: State,
*,
call: bool = True,
_call: bool, # noqa: FBT001
*args: AutorunArgs.args,
**kwargs: AutorunArgs.kwargs,
) -> None:
try:
selector_result = self._selector(state)
Expand All @@ -167,26 +147,19 @@ def _check_and_call(
except AttributeError:
return
if self._should_be_called or comparator_result != self._last_comparator_result:
self._should_be_called = False
previous_result = self._last_selector_result
self._last_selector_result = selector_result
self._last_comparator_result = comparator_result
func = self._func() if isinstance(self._func, weakref.ref) else self._func
if func:
if call:
self._latest_value = self.call_func(
selector_result,
previous_result,
func,
)
self._should_be_called = not _call
if _call:
func = (
self._func() if isinstance(self._func, weakref.ref) else self._func
)
if func:
self._latest_value = func(selector_result, *args, **kwargs)
create_task = self._store._create_task # noqa: SLF001
if iscoroutine(self._latest_value) and create_task:
create_task(self._latest_value, callback=self._task_callback)
self.inform_subscribers()
else:
self._should_be_called = True
else:
self.unsubscribe()

def __call__(
self: Autorun[
Expand All @@ -196,11 +169,14 @@ def __call__(
SelectorOutput,
ComparatorOutput,
AutorunOriginalReturnType,
AutorunArgs,
],
*args: AutorunArgs.args,
**kwargs: AutorunArgs.kwargs,
) -> AutorunOriginalReturnType:
state = self._store._state # noqa: SLF001
if state is not None:
self._check_and_call(state, call=True)
self._check_and_call(state, True, *args, **kwargs) # noqa: FBT003
return cast(AutorunOriginalReturnType, self._latest_value)

def __repr__(
Expand All @@ -211,6 +187,7 @@ def __repr__(
SelectorOutput,
ComparatorOutput,
AutorunOriginalReturnType,
AutorunArgs,
],
) -> str:
return f"""{super().__repr__()}(func: {self._func}, last_value: {
Expand All @@ -225,6 +202,7 @@ def value(
SelectorOutput,
ComparatorOutput,
AutorunOriginalReturnType,
AutorunArgs,
],
) -> AutorunOriginalReturnType:
return cast(AutorunOriginalReturnType, self._latest_value)
Expand All @@ -237,6 +215,7 @@ def subscribe(
SelectorOutput,
ComparatorOutput,
AutorunOriginalReturnType,
AutorunArgs,
],
callback: Callable[[AutorunOriginalReturnType], Any],
*,
Expand Down
Loading

0 comments on commit debf12d

Please sign in to comment.