From 114f0cbaeb2ddbfc47cb02e355b515105927d4f6 Mon Sep 17 00:00:00 2001 From: Benedikt Bartscher Date: Sun, 5 May 2024 01:40:44 +0200 Subject: [PATCH 1/6] add static deps for computed vars --- reflex/vars.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/reflex/vars.py b/reflex/vars.py index be6aa7eb891..9ab118b7f76 100644 --- a/reflex/vars.py +++ b/reflex/vars.py @@ -1860,13 +1860,18 @@ class ComputedVar(Var, property): # Whether to track dependencies and cache computed values _cache: bool = dataclasses.field(default=False) - _initial_value: Any | types.Unset = dataclasses.field(default_factory=types.Unset) + # The initial value of the computed var + _initial_value: Any | types.Unset = dataclasses.field(default=types.Unset) + + # Explicit var dependencies to track + _static_deps: set[str] = dataclasses.field(default_factory=set) def __init__( self, fget: Callable[[BaseState], Any], initial_value: Any | types.Unset = types.Unset(), cache: bool = False, + deps: Optional[set[Union[str, Var]]] = None, **kwargs, ): """Initialize a ComputedVar. @@ -1875,10 +1880,16 @@ def __init__( fget: The getter function. initial_value: The initial value of the computed var. cache: Whether to cache the computed value. + deps: Explicit var dependencies to track. **kwargs: additional attributes to set on the instance """ self._initial_value = initial_value self._cache = cache + if deps is None: + deps = set() + self._static_deps = { + dep._var_name if isinstance(dep, Var) else dep for dep in deps + } property.__init__(self, fget) kwargs["_var_name"] = kwargs.pop("_var_name", fget.__name__) kwargs["_var_type"] = kwargs.pop("_var_type", self._determine_var_type()) @@ -1965,7 +1976,7 @@ def _deps( VarValueError: if the function references the get_state, parent_state, or substates attributes (cannot track deps in a related state, only implicitly via parent state). """ - d = set() + d = self._static_deps.copy() if obj is None: fget = property.__getattribute__(self, "fget") if fget is not None: @@ -2063,6 +2074,7 @@ def computed_var( fget: Callable[[BaseState], Any] | None = None, initial_value: Any | None = None, cache: bool = False, + deps: Optional[set[Union[str, Var]]] = None, **kwargs, ) -> ComputedVar | Callable[[Callable[[BaseState], Any]], ComputedVar]: """A ComputedVar decorator with or without kwargs. @@ -2071,6 +2083,7 @@ def computed_var( fget: The getter function. initial_value: The initial value of the computed var. cache: Whether to cache the computed value. + deps: Explicit var dependencies to track. **kwargs: additional attributes to set on the instance Returns: @@ -2084,6 +2097,7 @@ def wrapper(fget): fget=fget, initial_value=initial_value, cache=cache, + deps=deps, **kwargs, ) From df0a541ff9264fb534eee33c7ffa8819657ee442 Mon Sep 17 00:00:00 2001 From: Benedikt Bartscher Date: Sun, 5 May 2024 02:18:49 +0200 Subject: [PATCH 2/6] allow to disable auto-deps, implement interval --- reflex/state.py | 2 +- reflex/vars.py | 70 +++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 69 insertions(+), 3 deletions(-) diff --git a/reflex/state.py b/reflex/state.py index ba87029c8b1..b2cb3021232 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -674,7 +674,7 @@ def _init_var_dependency_dicts(cls): cls._always_dirty_computed_vars = set( cvar_name for cvar_name, cvar in cls.computed_vars.items() - if not cvar._cache + if cvar.always_dirty ) # Any substate containing a ComputedVar with cache=False always needs to be recomputed diff --git a/reflex/vars.py b/reflex/vars.py index 9ab118b7f76..56c3e944f2b 100644 --- a/reflex/vars.py +++ b/reflex/vars.py @@ -4,6 +4,7 @@ import contextlib import dataclasses +import datetime import dis import functools import inspect @@ -1861,17 +1862,25 @@ class ComputedVar(Var, property): _cache: bool = dataclasses.field(default=False) # The initial value of the computed var - _initial_value: Any | types.Unset = dataclasses.field(default=types.Unset) + _initial_value: Any | types.Unset = dataclasses.field(default=types.Unset()) # Explicit var dependencies to track _static_deps: set[str] = dataclasses.field(default_factory=set) + # Whether var dependencies should be auto-determined + _auto_deps: bool = dataclasses.field(default=True) + + # Interval at which the computed var should be updated + _update_interval: Optional[datetime.timedelta] = dataclasses.field(default=None) + def __init__( self, fget: Callable[[BaseState], Any], initial_value: Any | types.Unset = types.Unset(), cache: bool = False, deps: Optional[set[Union[str, Var]]] = None, + auto_deps: bool = True, + interval: Optional[Union[int, datetime.timedelta]] = None, **kwargs, ): """Initialize a ComputedVar. @@ -1881,15 +1890,21 @@ def __init__( initial_value: The initial value of the computed var. cache: Whether to cache the computed value. deps: Explicit var dependencies to track. + auto_deps: Whether var dependencies should be auto-determined. + interval: Interval at which the computed var should be updated. **kwargs: additional attributes to set on the instance """ self._initial_value = initial_value self._cache = cache + if isinstance(interval, int): + interval = datetime.timedelta(seconds=interval) + self._update_interval = interval if deps is None: deps = set() self._static_deps = { dep._var_name if isinstance(dep, Var) else dep for dep in deps } + self._auto_deps = auto_deps property.__init__(self, fget) kwargs["_var_name"] = kwargs.pop("_var_name", fget.__name__) kwargs["_var_type"] = kwargs.pop("_var_type", self._determine_var_type()) @@ -1930,6 +1945,40 @@ def _cache_attr(self) -> str: """ return f"__cached_{self._var_name}" + @property + def _last_updated_attr(self) -> str: + """Get the attribute used to store the last updated timestamp. + + Returns: + An attribute name. + """ + return f"__last_updated_{self._var_name}" + + @property + def always_dirty(self) -> bool: + """Whether the computed var should always be marked as dirty. + + Returns: + True if the computed var should always be marked as dirty, False otherwise. + """ + return not self._cache or self._update_interval + + def _needs_update(self, instance) -> bool: + """Check if the computed var needs to be updated. + + Args: + instance: The state instance that the computed var is attached to. + + Returns: + True if the computed var needs to be updated, False otherwise. + """ + if self._update_interval is None: + return False + last_updated = getattr(instance, self._last_updated_attr, None) + if last_updated is None: + return True + return datetime.datetime.now() - last_updated > self._update_interval + def __get__(self, instance, owner): """Get the ComputedVar value. @@ -1946,10 +1995,13 @@ def __get__(self, instance, owner): return super().__get__(instance, owner) # handle caching - if not hasattr(instance, self._cache_attr): + if not hasattr(instance, self._cache_attr) or self._needs_update(instance): + # Set cache attr on state instance. setattr(instance, self._cache_attr, super().__get__(instance, owner)) # Ensure the computed var gets serialized to redis. instance._was_touched = True + # Set the last updated timestamp on the state instance. + setattr(instance, self._last_updated_attr, datetime.datetime.now()) return getattr(instance, self._cache_attr) def _deps( @@ -1976,6 +2028,8 @@ def _deps( VarValueError: if the function references the get_state, parent_state, or substates attributes (cannot track deps in a related state, only implicitly via parent state). """ + if not self._auto_deps: + return self._static_deps d = self._static_deps.copy() if obj is None: fget = property.__getattribute__(self, "fget") @@ -2075,6 +2129,8 @@ def computed_var( initial_value: Any | None = None, cache: bool = False, deps: Optional[set[Union[str, Var]]] = None, + auto_deps: bool = True, + interval: Optional[Union[datetime.timedelta, int]] = None, **kwargs, ) -> ComputedVar | Callable[[Callable[[BaseState], Any]], ComputedVar]: """A ComputedVar decorator with or without kwargs. @@ -2084,11 +2140,19 @@ def computed_var( initial_value: The initial value of the computed var. cache: Whether to cache the computed value. deps: Explicit var dependencies to track. + auto_deps: Whether var dependencies should be auto-determined. + interval: Interval at which the computed var should be updated. **kwargs: additional attributes to set on the instance Returns: A ComputedVar instance. + + Raises: + ValueError: If caching is disabled and an update interval is set. """ + if cache is False and interval is not None: + raise ValueError("Cannot set update interval without caching.") + if fget is not None: return ComputedVar(fget=fget, cache=cache) @@ -2098,6 +2162,8 @@ def wrapper(fget): initial_value=initial_value, cache=cache, deps=deps, + auto_deps=auto_deps, + interval=interval, **kwargs, ) From cc5ae186a67fd67cabd7cad90d0df2fa5aed7303 Mon Sep 17 00:00:00 2001 From: Benedikt Bartscher Date: Sun, 5 May 2024 02:21:52 +0200 Subject: [PATCH 3/6] fix pyright --- reflex/vars.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reflex/vars.py b/reflex/vars.py index 56c3e944f2b..63dc0934abf 100644 --- a/reflex/vars.py +++ b/reflex/vars.py @@ -1961,7 +1961,7 @@ def always_dirty(self) -> bool: Returns: True if the computed var should always be marked as dirty, False otherwise. """ - return not self._cache or self._update_interval + return not self._cache or self._update_interval is not None def _needs_update(self, instance) -> bool: """Check if the computed var needs to be updated. From 43259a3b9ea58487b5864b065ae478209b927c63 Mon Sep 17 00:00:00 2001 From: Benedikt Bartscher Date: Sun, 5 May 2024 03:58:02 +0200 Subject: [PATCH 4/6] fix typing, add simple test --- integration/test_computed_vars.py | 182 ++++++++++++++++++++++++++++++ reflex/vars.py | 6 +- reflex/vars.pyi | 15 +++ 3 files changed, 200 insertions(+), 3 deletions(-) create mode 100644 integration/test_computed_vars.py diff --git a/integration/test_computed_vars.py b/integration/test_computed_vars.py new file mode 100644 index 00000000000..c0f0cfb51fe --- /dev/null +++ b/integration/test_computed_vars.py @@ -0,0 +1,182 @@ +"""Test computed vars.""" + +from __future__ import annotations + +import time +from typing import Generator + +import pytest +from selenium.webdriver.common.by import By + +from reflex.testing import DEFAULT_TIMEOUT, AppHarness, WebDriver + + +def ComputedVars(): + """Test app for computed vars.""" + import reflex as rx + + class State(rx.State): + count: int = 0 + + # cached var with dep on count + @rx.cached_var(interval=15) + def count1(self) -> int: + return self.count + + # same as above, different notation + @rx.var(interval=15, cache=True) + def count2(self) -> int: + return self.count + + # explicit disabled auto_deps + @rx.var(interval=15, cache=True, auto_deps=False) + def count3(self) -> int: + # this will not add deps, because auto_deps is False + print(self.count1) + print(self.count2) + + return self.count + + # explicit dependency on count1 var + @rx.cached_var(deps=[count1]) + def depends_on_count1(self) -> int: + return self.count + + def increment(self): + self.count += 1 + + def do_nothing(self): + pass + + def index() -> rx.Component: + return rx.center( + rx.vstack( + rx.input( + id="token", + value=State.router.session.client_token, + is_read_only=True, + ), + rx.button("Increment", on_click=State.increment, id="increment"), + rx.button("Do nothing", on_click=State.do_nothing, id="do_nothing"), + rx.text(State.count, id="count"), + rx.text(State.count1, id="count1"), + rx.text(State.count2, id="count2"), + rx.text(State.count3, id="count3"), + rx.text( + State.depends_on_count1, + id="depends_on_count1", + ), + ), + ) + + app = rx.App() + app.add_page(index) + + +@pytest.fixture(scope="module") +def computed_vars( + tmp_path_factory, +) -> Generator[AppHarness, None, None]: + """Start ComputedVars app at tmp_path via AppHarness. + + Args: + tmp_path_factory: pytest tmp_path_factory fixture + + Yields: + running AppHarness instance + """ + with AppHarness.create( + root=tmp_path_factory.mktemp(f"computed_vars"), + app_source=ComputedVars, # type: ignore + ) as harness: + yield harness + + +@pytest.fixture +def driver(computed_vars: AppHarness) -> Generator[WebDriver, None, None]: + """Get an instance of the browser open to the computed_vars app. + + Args: + computed_vars: harness for ComputedVars app + + Yields: + WebDriver instance. + """ + assert computed_vars.app_instance is not None, "app is not running" + driver = computed_vars.frontend() + try: + yield driver + finally: + driver.quit() + + +@pytest.fixture() +def token(computed_vars: AppHarness, driver: WebDriver) -> str: + """Get a function that returns the active token. + + Args: + computed_vars: harness for ComputedVars app. + driver: WebDriver instance. + + Returns: + The token for the connected client + """ + assert computed_vars.app_instance is not None + token_input = driver.find_element(By.ID, "token") + assert token_input + + # wait for the backend connection to send the token + token = computed_vars.poll_for_value(token_input, timeout=DEFAULT_TIMEOUT * 2) + assert token is not None + + return token + + +def test_computed_vars( + computed_vars: AppHarness, + driver: WebDriver, + token: str, +): + """Test that computed vars are working as expected. + + Args: + computed_vars: harness for ComputedVars app. + driver: WebDriver instance. + token: The token for the connected client. + """ + assert computed_vars.app_instance is not None + + count = driver.find_element(By.ID, "count") + assert count + assert count.text == "0" + + count1 = driver.find_element(By.ID, "count1") + assert count1 + assert count1.text == "0" + + count2 = driver.find_element(By.ID, "count2") + assert count2 + assert count2.text == "0" + + count3 = driver.find_element(By.ID, "count3") + assert count3 + assert count3.text == "0" + + increment = driver.find_element(By.ID, "increment") + assert increment.is_enabled() + + do_nothing = driver.find_element(By.ID, "do_nothing") + assert do_nothing.is_enabled() + + increment.click() + assert computed_vars.poll_for_content(count, timeout=2, exp_not_equal="0") == "1" + assert computed_vars.poll_for_content(count1, timeout=2, exp_not_equal="0") == "1" + assert computed_vars.poll_for_content(count2, timeout=2, exp_not_equal="0") == "1" + + do_nothing.click() + with pytest.raises(TimeoutError): + computed_vars.poll_for_content(count3, timeout=5, exp_not_equal="0") + + time.sleep(10) + do_nothing.click() + assert computed_vars.poll_for_content(count3, timeout=2) == "1" diff --git a/reflex/vars.py b/reflex/vars.py index 63dc0934abf..685f3039c34 100644 --- a/reflex/vars.py +++ b/reflex/vars.py @@ -1878,7 +1878,7 @@ def __init__( fget: Callable[[BaseState], Any], initial_value: Any | types.Unset = types.Unset(), cache: bool = False, - deps: Optional[set[Union[str, Var]]] = None, + deps: Optional[List[Union[str, Var]]] = None, auto_deps: bool = True, interval: Optional[Union[int, datetime.timedelta]] = None, **kwargs, @@ -1900,7 +1900,7 @@ def __init__( interval = datetime.timedelta(seconds=interval) self._update_interval = interval if deps is None: - deps = set() + deps = [] self._static_deps = { dep._var_name if isinstance(dep, Var) else dep for dep in deps } @@ -2128,7 +2128,7 @@ def computed_var( fget: Callable[[BaseState], Any] | None = None, initial_value: Any | None = None, cache: bool = False, - deps: Optional[set[Union[str, Var]]] = None, + deps: Optional[List[Union[str, Var]]] = None, auto_deps: bool = True, interval: Optional[Union[datetime.timedelta, int]] = None, **kwargs, diff --git a/reflex/vars.pyi b/reflex/vars.pyi index 169e2d919c0..a5975f5f2e9 100644 --- a/reflex/vars.pyi +++ b/reflex/vars.pyi @@ -2,6 +2,7 @@ from __future__ import annotations +import datetime from dataclasses import dataclass from _typeshed import Incomplete from reflex import constants as constants @@ -155,10 +156,24 @@ class ComputedVar(Var): def computed_var( fget: Callable[[BaseState], Any] | None = None, initial_value: Any | None = None, + cache: bool = False, + deps: Optional[List[Union[str, Var]]] = None, + auto_deps: bool = True, + interval: Optional[Union[datetime.timedelta, int]] = None, **kwargs, ) -> Callable[[Callable[[Any], Any]], ComputedVar]: ... @overload def computed_var(fget: Callable[[Any], Any]) -> ComputedVar: ... +@overload +def cached_var( + fget: Callable[[BaseState], Any] | None = None, + initial_value: Any | None = None, + deps: Optional[List[Union[str, Var]]] = None, + auto_deps: bool = True, + interval: Optional[Union[datetime.timedelta, int]] = None, + **kwargs, +) -> Callable[[Callable[[Any], Any]], ComputedVar]: ... +@overload def cached_var(fget: Callable[[Any], Any]) -> ComputedVar: ... class CallableVar(BaseVar): From 30bfaf954a7778babe2aeadb7ec641ee55eeeeef Mon Sep 17 00:00:00 2001 From: Benedikt Bartscher Date: Tue, 7 May 2024 02:37:28 +0200 Subject: [PATCH 5/6] correctly determine expired computed vars + minor typing improvements --- integration/test_computed_vars.py | 46 +++++++++++++++++++++++++------ reflex/state.py | 18 +++++++++++- reflex/vars.py | 17 +++--------- reflex/vars.pyi | 1 + 4 files changed, 59 insertions(+), 23 deletions(-) diff --git a/integration/test_computed_vars.py b/integration/test_computed_vars.py index c0f0cfb51fe..2523248a905 100644 --- a/integration/test_computed_vars.py +++ b/integration/test_computed_vars.py @@ -38,15 +38,19 @@ def count3(self) -> int: return self.count # explicit dependency on count1 var - @rx.cached_var(deps=[count1]) + @rx.cached_var(deps=[count1], auto_deps=False) def depends_on_count1(self) -> int: return self.count + @rx.var(deps=[count3], auto_deps=False, cache=True) + def depends_on_count3(self) -> int: + return self.count + def increment(self): self.count += 1 - def do_nothing(self): - pass + def mark_dirty(self): + self._mark_dirty() def index() -> rx.Component: return rx.center( @@ -57,18 +61,29 @@ def index() -> rx.Component: is_read_only=True, ), rx.button("Increment", on_click=State.increment, id="increment"), - rx.button("Do nothing", on_click=State.do_nothing, id="do_nothing"), + rx.button("Do nothing", on_click=State.mark_dirty, id="mark_dirty"), + rx.text("count:"), rx.text(State.count, id="count"), + rx.text("count1:"), rx.text(State.count1, id="count1"), + rx.text("count2:"), rx.text(State.count2, id="count2"), + rx.text("count3:"), rx.text(State.count3, id="count3"), + rx.text("depends_on_count1:"), rx.text( State.depends_on_count1, id="depends_on_count1", ), + rx.text("depends_on_count3:"), + rx.text( + State.depends_on_count3, + id="depends_on_count3", + ), ), ) + # raise Exception(State.count3._deps(objclass=State)) app = rx.App() app.add_page(index) @@ -162,21 +177,34 @@ def test_computed_vars( assert count3 assert count3.text == "0" + depends_on_count1 = driver.find_element(By.ID, "depends_on_count1") + assert depends_on_count1 + assert depends_on_count1.text == "0" + + depends_on_count3 = driver.find_element(By.ID, "depends_on_count3") + assert depends_on_count3 + assert depends_on_count3.text == "0" + increment = driver.find_element(By.ID, "increment") assert increment.is_enabled() - do_nothing = driver.find_element(By.ID, "do_nothing") - assert do_nothing.is_enabled() + mark_dirty = driver.find_element(By.ID, "mark_dirty") + assert mark_dirty.is_enabled() + + mark_dirty.click() increment.click() assert computed_vars.poll_for_content(count, timeout=2, exp_not_equal="0") == "1" assert computed_vars.poll_for_content(count1, timeout=2, exp_not_equal="0") == "1" assert computed_vars.poll_for_content(count2, timeout=2, exp_not_equal="0") == "1" - do_nothing.click() + mark_dirty.click() with pytest.raises(TimeoutError): computed_vars.poll_for_content(count3, timeout=5, exp_not_equal="0") time.sleep(10) - do_nothing.click() - assert computed_vars.poll_for_content(count3, timeout=2) == "1" + assert count3.text == "0" + assert depends_on_count3.text == "0" + mark_dirty.click() + assert computed_vars.poll_for_content(count3, timeout=2, exp_not_equal="0") == "1" + assert depends_on_count3.text == "1" diff --git a/reflex/state.py b/reflex/state.py index b2cb3021232..b6a6189145a 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -674,7 +674,7 @@ def _init_var_dependency_dicts(cls): cls._always_dirty_computed_vars = set( cvar_name for cvar_name, cvar in cls.computed_vars.items() - if cvar.always_dirty + if not cvar._cache ) # Any substate containing a ComputedVar with cache=False always needs to be recomputed @@ -1534,6 +1534,18 @@ def _mark_dirty_computed_vars(self) -> None: if actual_var is not None: actual_var.mark_dirty(instance=self) + def _expired_computed_vars(self) -> set[str]: + """Determine ComputedVars that need to be recalculated based on the expiration time. + + Returns: + Set of computed vars to include in the delta. + """ + return set( + cvar + for cvar in self.computed_vars + if self.computed_vars[cvar].needs_update(instance=self) + ) + def _dirty_computed_vars(self, from_vars: set[str] | None = None) -> set[str]: """Determine ComputedVars that need to be recalculated based on the given vars. @@ -1586,6 +1598,7 @@ def get_delta(self) -> Delta: # and always dirty computed vars (cache=False) delta_vars = ( self.dirty_vars.intersection(self.base_vars) + .union(self.dirty_vars.intersection(self.computed_vars)) .union(self._dirty_computed_vars()) .union(self._always_dirty_computed_vars) ) @@ -1619,6 +1632,9 @@ def _mark_dirty(self): self.parent_state.dirty_substates.add(self.get_name()) self.parent_state._mark_dirty() + # Append expired computed vars to dirty_vars to trigger recalculation + self.dirty_vars.update(self._expired_computed_vars()) + # have to mark computed vars dirty to allow access to newly computed # values within the same ComputedVar function self._mark_dirty_computed_vars() diff --git a/reflex/vars.py b/reflex/vars.py index 685f3039c34..3dd920b1159 100644 --- a/reflex/vars.py +++ b/reflex/vars.py @@ -1954,16 +1954,7 @@ def _last_updated_attr(self) -> str: """ return f"__last_updated_{self._var_name}" - @property - def always_dirty(self) -> bool: - """Whether the computed var should always be marked as dirty. - - Returns: - True if the computed var should always be marked as dirty, False otherwise. - """ - return not self._cache or self._update_interval is not None - - def _needs_update(self, instance) -> bool: + def needs_update(self, instance: BaseState) -> bool: """Check if the computed var needs to be updated. Args: @@ -1979,7 +1970,7 @@ def _needs_update(self, instance) -> bool: return True return datetime.datetime.now() - last_updated > self._update_interval - def __get__(self, instance, owner): + def __get__(self, instance: BaseState | None, owner): """Get the ComputedVar value. If the value is already cached on the instance, return the cached value. @@ -1995,7 +1986,7 @@ def __get__(self, instance, owner): return super().__get__(instance, owner) # handle caching - if not hasattr(instance, self._cache_attr) or self._needs_update(instance): + if not hasattr(instance, self._cache_attr) or self.needs_update(instance): # Set cache attr on state instance. setattr(instance, self._cache_attr, super().__get__(instance, owner)) # Ensure the computed var gets serialized to redis. @@ -2156,7 +2147,7 @@ def computed_var( if fget is not None: return ComputedVar(fget=fget, cache=cache) - def wrapper(fget): + def wrapper(fget: Callable[[BaseState], Any]) -> ComputedVar: return ComputedVar( fget=fget, initial_value=initial_value, diff --git a/reflex/vars.pyi b/reflex/vars.pyi index a5975f5f2e9..01b2763428b 100644 --- a/reflex/vars.pyi +++ b/reflex/vars.pyi @@ -142,6 +142,7 @@ class ComputedVar(Var): def _deps(self, objclass: Type, obj: Optional[FunctionType] = ...) -> Set[str]: ... def _replace(self, merge_var_data=None, **kwargs: Any) -> ComputedVar: ... def mark_dirty(self, instance) -> None: ... + def needs_update(self, instance) -> bool: ... def _determine_var_type(self) -> Type: ... @overload def __init__( From 70bdddad2df492e1d7cf75a2ca35a367960af8bb Mon Sep 17 00:00:00 2001 From: Benedikt Bartscher Date: Tue, 21 May 2024 22:20:21 +0200 Subject: [PATCH 6/6] add new ComputedVar args to _replace --- reflex/vars.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/reflex/vars.py b/reflex/vars.py index 3dd920b1159..2215fed9de6 100644 --- a/reflex/vars.py +++ b/reflex/vars.py @@ -1925,6 +1925,9 @@ def _replace(self, merge_var_data=None, **kwargs: Any) -> ComputedVar: fget=kwargs.get("fget", self.fget), initial_value=kwargs.get("initial_value", self._initial_value), cache=kwargs.get("cache", self._cache), + deps=kwargs.get("deps", self._static_deps), + auto_deps=kwargs.get("auto_deps", self._auto_deps), + interval=kwargs.get("interval", self._update_interval), _var_name=kwargs.get("_var_name", self._var_name), _var_type=kwargs.get("_var_type", self._var_type), _var_is_local=kwargs.get("_var_is_local", self._var_is_local),