diff --git a/reflex/.templates/web/utils/state.js b/reflex/.templates/web/utils/state.js index 41dbee446..ec603fd13 100644 --- a/reflex/.templates/web/utils/state.js +++ b/reflex/.templates/web/utils/state.js @@ -410,7 +410,14 @@ export const connect = async ( autoUnref: false, }); // Ensure undefined fields in events are sent as null instead of removed - socket.current.io.encoder.replacer = (k, v) => (v === undefined ? null : v) + socket.current.io.encoder.replacer = (k, v) => (v === undefined ? null : v); + socket.current.io.decoder.tryParse = (str) => { + try { + return JSON5.parse(str); + } catch (e) { + return false; + } + }; function checkVisibility() { if (document.visibilityState === "visible") { diff --git a/reflex/components/dynamic.py b/reflex/components/dynamic.py index fbfc55f97..806d610df 100644 --- a/reflex/components/dynamic.py +++ b/reflex/components/dynamic.py @@ -136,6 +136,23 @@ def load_dynamic_serializer(): module_code_lines.insert(0, "const React = window.__reflex.react;") + function_line = next( + index + for index, line in enumerate(module_code_lines) + if line.startswith("export default function") + ) + + module_code_lines = [ + line + for _, line in sorted( + enumerate(module_code_lines), + key=lambda x: ( + not (x[1].startswith("import ") and x[0] < function_line), + x[0], + ), + ) + ] + return "\n".join( [ "//__reflex_evaluate", diff --git a/reflex/components/radix/themes/color_mode.py b/reflex/components/radix/themes/color_mode.py index 2dd0f5e83..e93a26ef6 100644 --- a/reflex/components/radix/themes/color_mode.py +++ b/reflex/components/radix/themes/color_mode.py @@ -151,8 +151,8 @@ class ColorModeIconButton(IconButton): dropdown_menu.trigger( super().create( ColorModeIcon.create(), - **props, - ) + ), + **props, ), dropdown_menu.content( color_mode_item("light"), diff --git a/reflex/config.py b/reflex/config.py index fa54fc182..a01ac0ba8 100644 --- a/reflex/config.py +++ b/reflex/config.py @@ -567,6 +567,9 @@ class EnvironmentVariables: # The maximum size of the reflex state in kilobytes. REFLEX_STATE_SIZE_LIMIT: EnvVar[int] = env_var(1000) + # Whether to use the turbopack bundler. + REFLEX_USE_TURBOPACK: EnvVar[bool] = env_var(True) + # Whether to compress the reflex state. REFLEX_COMPRESS_STATE: EnvVar[bool] = env_var(False) diff --git a/reflex/constants/installer.py b/reflex/constants/installer.py index 0b45586dd..f9dd26b5a 100644 --- a/reflex/constants/installer.py +++ b/reflex/constants/installer.py @@ -182,7 +182,7 @@ class PackageJson(SimpleNamespace): "@emotion/react": "11.13.3", "axios": "1.7.7", "json5": "2.2.3", - "next": "14.2.16", + "next": "15.1.4", "next-sitemap": "4.2.3", "next-themes": "0.4.3", "react": "18.3.1", diff --git a/reflex/reflex.py b/reflex/reflex.py index 22fcb9fb8..b0f4ccd91 100644 --- a/reflex/reflex.py +++ b/reflex/reflex.py @@ -519,7 +519,9 @@ def deploy( if prerequisites.needs_reinit(frontend=True): _init(name=config.app_name, loglevel=loglevel) prerequisites.check_latest_package_version(constants.ReflexHostingCLI.MODULE_NAME) - + extra: dict[str, str] = ( + {"config_path": config_path} if config_path is not None else {} + ) hosting_cli.deploy( app_name=app_name, export_fn=lambda zip_dest_dir, @@ -545,7 +547,7 @@ def deploy( loglevel=type(loglevel).INFO, # type: ignore token=token, project=project, - config_path=config_path, + **extra, ) diff --git a/reflex/state.py b/reflex/state.py index 8713fd962..cb1c447e4 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -105,6 +105,7 @@ from reflex.utils.exceptions import ( LockExpiredError, ReflexRuntimeError, SetUndefinedStateVarError, + StateMismatchError, StateSchemaMismatchError, StateSerializationError, StateTooLargeError, @@ -1204,7 +1205,6 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): fget=func, auto_deps=False, deps=["router"], - cache=True, _js_expr=param, _var_data=VarData.from_state(cls), ) @@ -1548,7 +1548,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): # Return the direct parent of target_state_cls for subsequent linking. return parent_state - def _get_state_from_cache(self, state_cls: Type[BaseState]) -> BaseState: + def _get_state_from_cache(self, state_cls: Type[T_STATE]) -> T_STATE: """Get a state instance from the cache. Args: @@ -1556,11 +1556,19 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): Returns: The instance of state_cls associated with this state's client_token. + + Raises: + StateMismatchError: If the state instance is not of the expected type. """ root_state = self._get_root_state() - return root_state.get_substate(state_cls.get_full_name().split(".")) + substate = root_state.get_substate(state_cls.get_full_name().split(".")) + if not isinstance(substate, state_cls): + raise StateMismatchError( + f"Searched for state {state_cls.get_full_name()} but found {substate}." + ) + return substate - async def _get_state_from_redis(self, state_cls: Type[BaseState]) -> BaseState: + async def _get_state_from_redis(self, state_cls: Type[T_STATE]) -> T_STATE: """Get a state instance from redis. Args: @@ -1571,6 +1579,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): Raises: RuntimeError: If redis is not used in this backend process. + StateMismatchError: If the state instance is not of the expected type. """ # Fetch all missing parent states from redis. parent_state_of_state_cls = await self._populate_parent_states(state_cls) @@ -1582,14 +1591,22 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): f"Requested state {state_cls.get_full_name()} is not cached and cannot be accessed without redis. " "(All states should already be available -- this is likely a bug).", ) - return await state_manager.get_state( + + state_in_redis = await state_manager.get_state( token=_substate_key(self.router.session.client_token, state_cls), top_level=False, get_substates=True, parent_state=parent_state_of_state_cls, ) - async def get_state(self, state_cls: Type[BaseState]) -> BaseState: + if not isinstance(state_in_redis, state_cls): + raise StateMismatchError( + f"Searched for state {state_cls.get_full_name()} but found {state_in_redis}." + ) + + return state_in_redis + + async def get_state(self, state_cls: Type[T_STATE]) -> T_STATE: """Get an instance of the state associated with this token. Allows for arbitrary access to sibling states from within an event handler. @@ -2358,6 +2375,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): return state # type: ignore +T_STATE = TypeVar("T_STATE", bound=BaseState) + + class State(BaseState): """The app Base State.""" diff --git a/reflex/utils/console.py b/reflex/utils/console.py index be545140a..8929b63b6 100644 --- a/reflex/utils/console.py +++ b/reflex/utils/console.py @@ -2,6 +2,11 @@ from __future__ import annotations +import inspect +import shutil +from pathlib import Path +from types import FrameType + from rich.console import Console from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn from rich.prompt import Prompt @@ -188,6 +193,33 @@ def warn(msg: str, dedupe: bool = False, **kwargs): print(f"[orange1]Warning: {msg}[/orange1]", **kwargs) +def _get_first_non_framework_frame() -> FrameType | None: + import click + import typer + import typing_extensions + + import reflex as rx + + # Exclude utility modules that should never be the source of deprecated reflex usage. + exclude_modules = [click, rx, typer, typing_extensions] + exclude_roots = [ + p.parent.resolve() + if (p := Path(m.__file__)).name == "__init__.py" + else p.resolve() + for m in exclude_modules + ] + # Specifically exclude the reflex cli module. + if reflex_bin := shutil.which(b"reflex"): + exclude_roots.append(Path(reflex_bin.decode())) + + frame = inspect.currentframe() + while frame := frame and frame.f_back: + frame_path = Path(inspect.getfile(frame)).resolve() + if not any(frame_path.is_relative_to(root) for root in exclude_roots): + break + return frame + + def deprecate( feature_name: str, reason: str, @@ -206,15 +238,27 @@ def deprecate( dedupe: If True, suppress multiple console logs of deprecation message. kwargs: Keyword arguments to pass to the print function. """ - if feature_name not in _EMITTED_DEPRECATION_WARNINGS: + dedupe_key = feature_name + loc = "" + + # See if we can find where the deprecation exists in "user code" + origin_frame = _get_first_non_framework_frame() + if origin_frame is not None: + filename = Path(origin_frame.f_code.co_filename) + if filename.is_relative_to(Path.cwd()): + filename = filename.relative_to(Path.cwd()) + loc = f"{filename}:{origin_frame.f_lineno}" + dedupe_key = f"{dedupe_key} {loc}" + + if dedupe_key not in _EMITTED_DEPRECATION_WARNINGS: msg = ( f"{feature_name} has been deprecated in version {deprecation_version} {reason.rstrip('.')}. It will be completely " - f"removed in {removal_version}" + f"removed in {removal_version}. ({loc})" ) if _LOG_LEVEL <= LogLevel.WARNING: print(f"[yellow]DeprecationWarning: {msg}[/yellow]", **kwargs) if dedupe: - _EMITTED_DEPRECATION_WARNINGS.add(feature_name) + _EMITTED_DEPRECATION_WARNINGS.add(dedupe_key) def error(msg: str, dedupe: bool = False, **kwargs): diff --git a/reflex/utils/exceptions.py b/reflex/utils/exceptions.py index bceadc977..339abcda1 100644 --- a/reflex/utils/exceptions.py +++ b/reflex/utils/exceptions.py @@ -163,6 +163,10 @@ class StateSerializationError(ReflexError): """Raised when the state cannot be serialized.""" +class StateMismatchError(ReflexError, ValueError): + """Raised when the state retrieved does not match the expected state.""" + + class SystemPackageMissingError(ReflexError): """Raised when a system package is missing.""" diff --git a/reflex/utils/prerequisites.py b/reflex/utils/prerequisites.py index d838c0eea..e450393c3 100644 --- a/reflex/utils/prerequisites.py +++ b/reflex/utils/prerequisites.py @@ -610,10 +610,14 @@ def initialize_web_directory(): init_reflex_json(project_hash=project_hash) +def _turbopack_flag() -> str: + return " --turbopack" if environment.REFLEX_USE_TURBOPACK.get() else "" + + def _compile_package_json(): return templates.PACKAGE_JSON.render( scripts={ - "dev": constants.PackageJson.Commands.DEV, + "dev": constants.PackageJson.Commands.DEV + _turbopack_flag(), "export": constants.PackageJson.Commands.EXPORT, "export_sitemap": constants.PackageJson.Commands.EXPORT_SITEMAP, "prod": constants.PackageJson.Commands.PROD, diff --git a/reflex/utils/processes.py b/reflex/utils/processes.py index 871b5f323..3673b36b2 100644 --- a/reflex/utils/processes.py +++ b/reflex/utils/processes.py @@ -17,6 +17,7 @@ import typer from redis.exceptions import RedisError from reflex import constants +from reflex.config import environment from reflex.utils import console, path_ops, prerequisites @@ -156,24 +157,30 @@ def new_process(args, run: bool = False, show_logs: bool = False, **kwargs): Raises: Exit: When attempting to run a command with a None value. """ - node_bin_path = str(path_ops.get_node_bin_path()) - if not node_bin_path and not prerequisites.CURRENTLY_INSTALLING_NODE: - console.warn( - "The path to the Node binary could not be found. Please ensure that Node is properly " - "installed and added to your system's PATH environment variable or try running " - "`reflex init` again." - ) + # Check for invalid command first. if None in args: console.error(f"Invalid command: {args}") raise typer.Exit(1) - # Add the node bin path to the PATH environment variable. + + path_env: str = os.environ.get("PATH", "") + + # Add node_bin_path to the PATH environment variable. + if not environment.REFLEX_BACKEND_ONLY.get(): + node_bin_path = str(path_ops.get_node_bin_path()) + if not node_bin_path and not prerequisites.CURRENTLY_INSTALLING_NODE: + console.warn( + "The path to the Node binary could not be found. Please ensure that Node is properly " + "installed and added to your system's PATH environment variable or try running " + "`reflex init` again." + ) + path_env = os.pathsep.join([node_bin_path, path_env]) + env: dict[str, str] = { **os.environ, - "PATH": os.pathsep.join( - [node_bin_path if node_bin_path else "", os.environ["PATH"]] - ), # type: ignore + "PATH": path_env, **kwargs.pop("env", {}), } + kwargs = { "env": env, "stderr": None if show_logs else subprocess.STDOUT, diff --git a/reflex/vars/base.py b/reflex/vars/base.py index 0a93901cd..2892d004d 100644 --- a/reflex/vars/base.py +++ b/reflex/vars/base.py @@ -561,7 +561,7 @@ class Var(Generic[VAR_TYPE]): if _var_is_local is not None: console.deprecate( feature_name="_var_is_local", - reason="The _var_is_local argument is not supported for Var." + reason="The _var_is_local argument is not supported for Var. " "If you want to create a Var from a raw Javascript expression, use the constructor directly", deprecation_version="0.6.0", removal_version="0.7.0", @@ -569,7 +569,7 @@ class Var(Generic[VAR_TYPE]): if _var_is_string is not None: console.deprecate( feature_name="_var_is_string", - reason="The _var_is_string argument is not supported for Var." + reason="The _var_is_string argument is not supported for Var. " "If you want to create a Var from a raw Javascript expression, use the constructor directly", deprecation_version="0.6.0", removal_version="0.7.0", @@ -1838,7 +1838,7 @@ class ComputedVar(Var[RETURN_TYPE]): self, fget: Callable[[BASE_STATE], RETURN_TYPE], initial_value: RETURN_TYPE | types.Unset = types.Unset(), - cache: bool = False, + cache: bool = True, deps: Optional[List[Union[str, Var]]] = None, auto_deps: bool = True, interval: Optional[Union[int, datetime.timedelta]] = None, @@ -2253,7 +2253,7 @@ if TYPE_CHECKING: def computed_var( fget: None = None, initial_value: Any | types.Unset = types.Unset(), - cache: bool = False, + cache: bool = True, deps: Optional[List[Union[str, Var]]] = None, auto_deps: bool = True, interval: Optional[Union[datetime.timedelta, int]] = None, @@ -2266,7 +2266,7 @@ def computed_var( def computed_var( fget: Callable[[BASE_STATE], RETURN_TYPE], initial_value: RETURN_TYPE | types.Unset = types.Unset(), - cache: bool = False, + cache: bool = True, deps: Optional[List[Union[str, Var]]] = None, auto_deps: bool = True, interval: Optional[Union[datetime.timedelta, int]] = None, @@ -2278,7 +2278,7 @@ def computed_var( def computed_var( fget: Callable[[BASE_STATE], Any] | None = None, initial_value: Any | types.Unset = types.Unset(), - cache: Optional[bool] = None, + cache: bool = True, deps: Optional[List[Union[str, Var]]] = None, auto_deps: bool = True, interval: Optional[Union[datetime.timedelta, int]] = None, @@ -2304,15 +2304,6 @@ def computed_var( ValueError: If caching is disabled and an update interval is set. VarDependencyError: If user supplies dependencies without caching. """ - if cache is None: - cache = False - console.deprecate( - "Default non-cached rx.var", - "the default value will be `@rx.var(cache=True)` in a future release. " - "To retain uncached var, explicitly pass `@rx.var(cache=False)`", - deprecation_version="0.6.8", - removal_version="0.7.0", - ) if cache is False and interval is not None: raise ValueError("Cannot set update interval without caching.") diff --git a/reflex/vars/function.py b/reflex/vars/function.py index 2a7d50e1b..131f15b9f 100644 --- a/reflex/vars/function.py +++ b/reflex/vars/function.py @@ -390,6 +390,7 @@ class ArgsFunctionOperation(CachedVarOperation, FunctionVar): Returns: The function var. """ + return_expr = Var.create(return_expr) return cls( _js_expr="", _var_type=_var_type, @@ -445,6 +446,7 @@ class ArgsFunctionOperationBuilder(CachedVarOperation, BuilderFunctionVar): Returns: The function var. """ + return_expr = Var.create(return_expr) return cls( _js_expr="", _var_type=_var_type, diff --git a/reflex/vars/number.py b/reflex/vars/number.py index d04aded35..a2a0293d5 100644 --- a/reflex/vars/number.py +++ b/reflex/vars/number.py @@ -20,7 +20,6 @@ from typing import ( from reflex.constants.base import Dirs from reflex.utils.exceptions import PrimitiveUnserializableToJSON, VarTypeError from reflex.utils.imports import ImportDict, ImportVar -from reflex.utils.types import is_optional from .base import ( CustomVarOperationReturn, @@ -431,7 +430,7 @@ class NumberVar(Var[NUMBER_T], python_types=(int, float)): """ if not isinstance(other, NUMBER_TYPES): raise_unsupported_operand_types("<", (type(self), type(other))) - return less_than_operation(self, +other) + return less_than_operation(+self, +other) @overload def __le__(self, other: number_types) -> BooleanVar: ... @@ -450,7 +449,7 @@ class NumberVar(Var[NUMBER_T], python_types=(int, float)): """ if not isinstance(other, NUMBER_TYPES): raise_unsupported_operand_types("<=", (type(self), type(other))) - return less_than_or_equal_operation(self, +other) + return less_than_or_equal_operation(+self, +other) def __eq__(self, other: Any): """Equal comparison. @@ -462,7 +461,7 @@ class NumberVar(Var[NUMBER_T], python_types=(int, float)): The result of the comparison. """ if isinstance(other, NUMBER_TYPES): - return equal_operation(self, +other) + return equal_operation(+self, +other) return equal_operation(self, other) def __ne__(self, other: Any): @@ -475,7 +474,7 @@ class NumberVar(Var[NUMBER_T], python_types=(int, float)): The result of the comparison. """ if isinstance(other, NUMBER_TYPES): - return not_equal_operation(self, +other) + return not_equal_operation(+self, +other) return not_equal_operation(self, other) @overload @@ -495,7 +494,7 @@ class NumberVar(Var[NUMBER_T], python_types=(int, float)): """ if not isinstance(other, NUMBER_TYPES): raise_unsupported_operand_types(">", (type(self), type(other))) - return greater_than_operation(self, +other) + return greater_than_operation(+self, +other) @overload def __ge__(self, other: number_types) -> BooleanVar: ... @@ -514,17 +513,7 @@ class NumberVar(Var[NUMBER_T], python_types=(int, float)): """ if not isinstance(other, NUMBER_TYPES): raise_unsupported_operand_types(">=", (type(self), type(other))) - return greater_than_or_equal_operation(self, +other) - - def bool(self): - """Boolean conversion. - - Returns: - The boolean value of the number. - """ - if is_optional(self._var_type): - return boolify((self != None) & (self != 0)) # noqa: E711 - return self != 0 + return greater_than_or_equal_operation(+self, +other) def _is_strict_float(self) -> bool: """Check if the number is a float. diff --git a/tests/integration/test_computed_vars.py b/tests/integration/test_computed_vars.py index 03aaf18b4..f56001ea8 100644 --- a/tests/integration/test_computed_vars.py +++ b/tests/integration/test_computed_vars.py @@ -22,22 +22,22 @@ def ComputedVars(): count: int = 0 # cached var with dep on count - @rx.var(cache=True, interval=15) + @rx.var(interval=15) def count1(self) -> int: return self.count # cached backend var with dep on count - @rx.var(cache=True, interval=15, backend=True) + @rx.var(interval=15, backend=True) def count1_backend(self) -> int: return self.count # same as above but implicit backend with `_` prefix - @rx.var(cache=True, interval=15) + @rx.var(interval=15) def _count1_backend(self) -> int: return self.count # explicit disabled auto_deps - @rx.var(interval=15, cache=True, auto_deps=False) + @rx.var(interval=15, auto_deps=False) def count3(self) -> int: # this will not add deps, because auto_deps is False print(self.count1) @@ -45,19 +45,27 @@ def ComputedVars(): return self.count # explicit dependency on count var - @rx.var(cache=True, deps=["count"], auto_deps=False) + @rx.var(deps=["count"], auto_deps=False) def depends_on_count(self) -> int: return self.count # explicit dependency on count1 var - @rx.var(cache=True, deps=[count1], auto_deps=False) + @rx.var(deps=[count1], auto_deps=False) def depends_on_count1(self) -> int: return self.count - @rx.var(deps=[count3], auto_deps=False, cache=True) + @rx.var( + deps=[count3], + auto_deps=False, + ) def depends_on_count3(self) -> int: return self.count + # special floats should be properly decoded on the frontend + @rx.var(cache=True, initial_value=[]) + def special_floats(self) -> list[float]: + return [42.9, float("nan"), float("inf"), float("-inf")] + @rx.event def increment(self): self.count += 1 @@ -103,6 +111,11 @@ def ComputedVars(): State.depends_on_count3, id="depends_on_count3", ), + rx.text("special_floats:"), + rx.text( + State.special_floats.join(", "), + id="special_floats", + ), ), ) @@ -224,6 +237,10 @@ async def test_computed_vars( assert depends_on_count3 assert depends_on_count3.text == "0" + special_floats = driver.find_element(By.ID, "special_floats") + assert special_floats + assert special_floats.text == "42.9, NaN, Infinity, -Infinity" + increment = driver.find_element(By.ID, "increment") assert increment.is_enabled() diff --git a/tests/integration/test_dynamic_routes.py b/tests/integration/test_dynamic_routes.py index 8a3cde3a2..c210bde69 100644 --- a/tests/integration/test_dynamic_routes.py +++ b/tests/integration/test_dynamic_routes.py @@ -74,16 +74,16 @@ def DynamicRoute(): class ArgState(rx.State): """The app state.""" - @rx.var + @rx.var(cache=False) def arg(self) -> int: return int(self.arg_str or 0) class ArgSubState(ArgState): - @rx.var(cache=True) + @rx.var def cached_arg(self) -> int: return self.arg - @rx.var(cache=True) + @rx.var def cached_arg_str(self) -> str: return self.arg_str diff --git a/tests/integration/test_lifespan.py b/tests/integration/test_lifespan.py index 0fa4a7e92..d79273fbc 100644 --- a/tests/integration/test_lifespan.py +++ b/tests/integration/test_lifespan.py @@ -36,7 +36,7 @@ def LifespanApp(): print("Lifespan global started.") try: while True: - lifespan_task_global += inc # pyright: ignore[reportUnboundVariable] + lifespan_task_global += inc # pyright: ignore[reportUnboundVariable, reportPossiblyUnboundVariable] await asyncio.sleep(0.1) except asyncio.CancelledError as ce: print(f"Lifespan global cancelled: {ce}.") @@ -45,11 +45,11 @@ def LifespanApp(): class LifespanState(rx.State): interval: int = 100 - @rx.var + @rx.var(cache=False) def task_global(self) -> int: return lifespan_task_global - @rx.var + @rx.var(cache=False) def context_global(self) -> int: return lifespan_context_global diff --git a/tests/integration/test_media.py b/tests/integration/test_media.py index 10af26591..649038a7e 100644 --- a/tests/integration/test_media.py +++ b/tests/integration/test_media.py @@ -22,31 +22,31 @@ def MediaApp(): img.format = format # type: ignore return img - @rx.var(cache=True) + @rx.var def img_default(self) -> Image.Image: return self._blue() - @rx.var(cache=True) + @rx.var def img_bmp(self) -> Image.Image: return self._blue(format="BMP") - @rx.var(cache=True) + @rx.var def img_jpg(self) -> Image.Image: return self._blue(format="JPEG") - @rx.var(cache=True) + @rx.var def img_png(self) -> Image.Image: return self._blue(format="PNG") - @rx.var(cache=True) + @rx.var def img_gif(self) -> Image.Image: return self._blue(format="GIF") - @rx.var(cache=True) + @rx.var def img_webp(self) -> Image.Image: return self._blue(format="WEBP") - @rx.var(cache=True) + @rx.var def img_from_url(self) -> Image.Image: img_url = "https://picsum.photos/id/1/200/300" img_resp = httpx.get(img_url, follow_redirects=True) diff --git a/tests/units/test_app.py b/tests/units/test_app.py index 48a4bdda1..f805f83ec 100644 --- a/tests/units/test_app.py +++ b/tests/units/test_app.py @@ -908,7 +908,7 @@ class DynamicState(BaseState): """Increment the counter var.""" self.counter = self.counter + 1 - @computed_var(cache=True) + @computed_var def comp_dynamic(self) -> str: """A computed var that depends on the dynamic var. @@ -1549,11 +1549,11 @@ def test_app_with_valid_var_dependencies(compilable_app: tuple[App, Path]): base: int = 0 _backend: int = 0 - @computed_var(cache=True) + @computed_var() def foo(self) -> str: return "foo" - @computed_var(deps=["_backend", "base", foo], cache=True) + @computed_var(deps=["_backend", "base", foo]) def bar(self) -> str: return "bar" @@ -1565,7 +1565,7 @@ def test_app_with_invalid_var_dependencies(compilable_app: tuple[App, Path]): app, _ = compilable_app class InvalidDepState(BaseState): - @computed_var(deps=["foolksjdf"], cache=True) + @computed_var(deps=["foolksjdf"]) def bar(self) -> str: return "bar" diff --git a/tests/units/test_state.py b/tests/units/test_state.py index 41fac443e..19f3e4239 100644 --- a/tests/units/test_state.py +++ b/tests/units/test_state.py @@ -202,7 +202,7 @@ class GrandchildState(ChildState): class GrandchildState2(ChildState2): """A grandchild state fixture.""" - @rx.var(cache=True) + @rx.var def cached(self) -> str: """A cached var. @@ -215,7 +215,7 @@ class GrandchildState2(ChildState2): class GrandchildState3(ChildState3): """A great grandchild state fixture.""" - @rx.var + @rx.var(cache=False) def computed(self) -> str: """A computed var. @@ -796,7 +796,7 @@ async def test_process_event_simple(test_state): # The delta should contain the changes, including computed vars. assert update.delta == { - TestState.get_full_name(): {"num1": 69, "sum": 72.14, "upper": ""}, + TestState.get_full_name(): {"num1": 69, "sum": 72.14}, GrandchildState3.get_full_name(): {"computed": ""}, } assert update.events == [] @@ -823,7 +823,7 @@ async def test_process_event_substate(test_state, child_state, grandchild_state) assert child_state.value == "HI" assert child_state.count == 24 assert update.delta == { - TestState.get_full_name(): {"sum": 3.14, "upper": ""}, + # TestState.get_full_name(): {"sum": 3.14, "upper": ""}, ChildState.get_full_name(): {"value": "HI", "count": 24}, GrandchildState3.get_full_name(): {"computed": ""}, } @@ -839,7 +839,7 @@ async def test_process_event_substate(test_state, child_state, grandchild_state) update = await test_state._process(event).__anext__() assert grandchild_state.value2 == "new" assert update.delta == { - TestState.get_full_name(): {"sum": 3.14, "upper": ""}, + # TestState.get_full_name(): {"sum": 3.14, "upper": ""}, GrandchildState.get_full_name(): {"value2": "new"}, GrandchildState3.get_full_name(): {"computed": ""}, } @@ -989,7 +989,7 @@ class InterdependentState(BaseState): v1: int = 0 _v2: int = 1 - @rx.var(cache=True) + @rx.var def v1x2(self) -> int: """Depends on var v1. @@ -998,7 +998,7 @@ class InterdependentState(BaseState): """ return self.v1 * 2 - @rx.var(cache=True) + @rx.var def v2x2(self) -> int: """Depends on backend var _v2. @@ -1007,7 +1007,7 @@ class InterdependentState(BaseState): """ return self._v2 * 2 - @rx.var(cache=True, backend=True) + @rx.var(backend=True) def v2x2_backend(self) -> int: """Depends on backend var _v2. @@ -1016,7 +1016,7 @@ class InterdependentState(BaseState): """ return self._v2 * 2 - @rx.var(cache=True) + @rx.var def v1x2x2(self) -> int: """Depends on ComputedVar v1x2. @@ -1025,7 +1025,7 @@ class InterdependentState(BaseState): """ return self.v1x2 * 2 # type: ignore - @rx.var(cache=True) + @rx.var def _v3(self) -> int: """Depends on backend var _v2. @@ -1034,7 +1034,7 @@ class InterdependentState(BaseState): """ return self._v2 - @rx.var(cache=True) + @rx.var def v3x2(self) -> int: """Depends on ComputedVar _v3. @@ -1239,7 +1239,7 @@ def test_computed_var_cached(): class ComputedState(BaseState): v: int = 0 - @rx.var(cache=True) + @rx.var def comp_v(self) -> int: nonlocal comp_v_calls comp_v_calls += 1 @@ -1264,15 +1264,15 @@ def test_computed_var_cached_depends_on_non_cached(): class ComputedState(BaseState): v: int = 0 - @rx.var + @rx.var(cache=False) def no_cache_v(self) -> int: return self.v - @rx.var(cache=True) + @rx.var def dep_v(self) -> int: return self.no_cache_v # type: ignore - @rx.var(cache=True) + @rx.var def comp_v(self) -> int: return self.v @@ -1304,14 +1304,14 @@ def test_computed_var_depends_on_parent_non_cached(): counter = 0 class ParentState(BaseState): - @rx.var + @rx.var(cache=False) def no_cache_v(self) -> int: nonlocal counter counter += 1 return counter class ChildState(ParentState): - @rx.var(cache=True) + @rx.var def dep_v(self) -> int: return self.no_cache_v # type: ignore @@ -1357,7 +1357,7 @@ def test_cached_var_depends_on_event_handler(use_partial: bool): def handler(self): self.x = self.x + 1 - @rx.var(cache=True) + @rx.var def cached_x_side_effect(self) -> int: self.handler() nonlocal counter @@ -1393,7 +1393,7 @@ def test_computed_var_dependencies(): def testprop(self) -> int: return self.v - @rx.var(cache=True) + @rx.var def comp_v(self) -> int: """Direct access. @@ -1402,7 +1402,7 @@ def test_computed_var_dependencies(): """ return self.v - @rx.var(cache=True, backend=True) + @rx.var(backend=True) def comp_v_backend(self) -> int: """Direct access backend var. @@ -1411,7 +1411,7 @@ def test_computed_var_dependencies(): """ return self.v - @rx.var(cache=True) + @rx.var def comp_v_via_property(self) -> int: """Access v via property. @@ -1420,7 +1420,7 @@ def test_computed_var_dependencies(): """ return self.testprop - @rx.var(cache=True) + @rx.var def comp_w(self): """Nested lambda. @@ -1429,7 +1429,7 @@ def test_computed_var_dependencies(): """ return lambda: self.w - @rx.var(cache=True) + @rx.var def comp_x(self): """Nested function. @@ -1442,7 +1442,7 @@ def test_computed_var_dependencies(): return _ - @rx.var(cache=True) + @rx.var def comp_y(self) -> List[int]: """Comprehension iterating over attribute. @@ -1451,7 +1451,7 @@ def test_computed_var_dependencies(): """ return [round(y) for y in self.y] - @rx.var(cache=True) + @rx.var def comp_z(self) -> List[bool]: """Comprehension accesses attribute. @@ -2027,10 +2027,6 @@ async def test_state_proxy(grandchild_state: GrandchildState, mock_app: rx.App): assert mcall.args[0] == str(SocketEvent.EVENT) assert mcall.args[1] == StateUpdate( delta={ - parent_state.get_full_name(): { - "upper": "", - "sum": 3.14, - }, grandchild_state.get_full_name(): { "value2": "42", }, @@ -2053,7 +2049,7 @@ class BackgroundTaskState(BaseState): super().__init__(**kwargs) self.router_data = {"simulate": "hydrate"} - @rx.var + @rx.var(cache=False) def computed_order(self) -> List[str]: """Get the order as a computed var. @@ -3040,10 +3036,6 @@ async def test_get_state(mock_app: rx.App, token: str): grandchild_state.value2 = "set_value" assert test_state.get_delta() == { - TestState.get_full_name(): { - "sum": 3.14, - "upper": "", - }, GrandchildState.get_full_name(): { "value2": "set_value", }, @@ -3081,10 +3073,6 @@ async def test_get_state(mock_app: rx.App, token: str): child_state2.value = "set_c2_value" assert new_test_state.get_delta() == { - TestState.get_full_name(): { - "sum": 3.14, - "upper": "", - }, ChildState2.get_full_name(): { "value": "set_c2_value", }, @@ -3139,7 +3127,7 @@ async def test_get_state_from_sibling_not_cached(mock_app: rx.App, token: str): child3_var: int = 0 - @rx.var + @rx.var(cache=False) def v(self): pass @@ -3210,8 +3198,8 @@ def test_potentially_dirty_substates(): def bar(self) -> str: return "" - assert RxState._potentially_dirty_substates() == {State} - assert State._potentially_dirty_substates() == {C1} + assert RxState._potentially_dirty_substates() == set() + assert State._potentially_dirty_substates() == set() assert C1._potentially_dirty_substates() == set() @@ -3226,7 +3214,7 @@ def test_router_var_dep() -> None: class RouterVarDepState(RouterVarParentState): """A state with a router var dependency.""" - @rx.var(cache=True) + @rx.var def foo(self) -> str: return self.router.page.params.get("foo", "") @@ -3421,7 +3409,7 @@ class MixinState(State, mixin=True): _backend: int = 0 _backend_no_default: dict - @rx.var(cache=True) + @rx.var def computed(self) -> str: """A computed var on mixin state. diff --git a/tests/units/test_state_tree.py b/tests/units/test_state_tree.py index ebdd877de..6fe828819 100644 --- a/tests/units/test_state_tree.py +++ b/tests/units/test_state_tree.py @@ -42,7 +42,7 @@ class SubA_A_A_A(SubA_A_A): class SubA_A_A_B(SubA_A_A): """SubA_A_A_B is a child of SubA_A_A.""" - @rx.var(cache=True) + @rx.var def sub_a_a_a_cached(self) -> int: """A cached var. @@ -117,7 +117,7 @@ class TreeD(Root): d: int - @rx.var + @rx.var(cache=False) def d_var(self) -> int: """A computed var. @@ -156,7 +156,7 @@ class SubE_A_A_A_A(SubE_A_A_A): sub_e_a_a_a_a: int - @rx.var + @rx.var(cache=False) def sub_e_a_a_a_a_var(self) -> int: """A computed var. @@ -183,7 +183,7 @@ class SubE_A_A_A_D(SubE_A_A_A): sub_e_a_a_a_d: int - @rx.var(cache=True) + @rx.var def sub_e_a_a_a_d_var(self) -> int: """A computed var. diff --git a/tests/units/test_var.py b/tests/units/test_var.py index bfa8aa35a..6ad82a761 100644 --- a/tests/units/test_var.py +++ b/tests/units/test_var.py @@ -1004,7 +1004,7 @@ def test_all_number_operations(): assert ( str(even_more_complicated_number) - == "!(((Math.abs(Math.floor(((Math.floor(((-((-5.4 + 1)) * 2) / 3) / 2) % 3) ** 2))) || (2 && Math.round(((Math.floor(((-((-5.4 + 1)) * 2) / 3) / 2) % 3) ** 2)))) !== 0))" + == "!(isTrue((Math.abs(Math.floor(((Math.floor(((-((-5.4 + 1)) * 2) / 3) / 2) % 3) ** 2))) || (2 && Math.round(((Math.floor(((-((-5.4 + 1)) * 2) / 3) / 2) % 3) ** 2))))))" ) assert str(LiteralNumberVar.create(5) > False) == "(5 > 0)" @@ -1814,10 +1814,7 @@ def cv_fget(state: BaseState) -> int: ], ) def test_computed_var_deps(deps: List[Union[str, Var]], expected: Set[str]): - @computed_var( - deps=deps, - cache=True, - ) + @computed_var(deps=deps) def test_var(state) -> int: return 1 @@ -1835,10 +1832,7 @@ def test_computed_var_deps(deps: List[Union[str, Var]], expected: Set[str]): def test_invalid_computed_var_deps(deps: List): with pytest.raises(TypeError): - @computed_var( - deps=deps, - cache=True, - ) + @computed_var(deps=deps) def test_var(state) -> int: return 1