From 372bd22475a4f6d1a6c10d5a4091170b0b45ff18 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Tue, 11 Feb 2025 11:39:55 -0800 Subject: [PATCH] raise error when passing a str(var) (#4769) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * raise error when passing a str(var) * make it faster * fix typo * fix tests * mocker consistency Co-authored-by: Thomas Brandého * ditto Co-authored-by: Thomas Brandého --------- Co-authored-by: Thomas Brandého --- reflex/components/base/bare.py | 40 ++++++++++++++++++++++++++++++++++ reflex/config.py | 2 +- reflex/utils/decorator.py | 25 +++++++++++++++++++++ tests/units/test_var.py | 25 +++++++++++++++++++++ 4 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 reflex/utils/decorator.py diff --git a/reflex/components/base/bare.py b/reflex/components/base/bare.py index 0f0bef8b9..73b0680d3 100644 --- a/reflex/components/base/bare.py +++ b/reflex/components/base/bare.py @@ -7,9 +7,44 @@ from typing import Any, Iterator from reflex.components.component import Component, LiteralComponentVar from reflex.components.tags import Tag from reflex.components.tags.tagless import Tagless +from reflex.config import PerformanceMode, environment +from reflex.utils import console +from reflex.utils.decorator import once from reflex.utils.imports import ParsedImportDict from reflex.vars import BooleanVar, ObjectVar, Var from reflex.vars.base import VarData +from reflex.vars.sequence import LiteralStringVar + + +@once +def get_performance_mode(): + """Get the performance mode. + + Returns: + The performance mode. + """ + return environment.REFLEX_PERF_MODE.get() + + +def validate_str(value: str): + """Validate a string value. + + Args: + value: The value to validate. + + Raises: + ValueError: If the value is a Var and the performance mode is set to raise. + """ + perf_mode = get_performance_mode() + if perf_mode != PerformanceMode.OFF and value.startswith("reflex___state"): + if perf_mode == PerformanceMode.WARN: + console.warn( + f"Output includes {value!s} which will be displayed as a string. If you are calling `str` on a Var, consider using .to_string() instead." + ) + elif perf_mode == PerformanceMode.RAISE: + raise ValueError( + f"Output includes {value!s} which will be displayed as a string. If you are calling `str` on a Var, consider using .to_string() instead." + ) class Bare(Component): @@ -28,9 +63,14 @@ class Bare(Component): The component. """ if isinstance(contents, Var): + if isinstance(contents, LiteralStringVar): + validate_str(contents._var_value) return cls(contents=contents) else: + if isinstance(contents, str): + validate_str(contents) contents = str(contents) if contents is not None else "" + return cls(contents=contents) def _get_all_hooks_internal(self) -> dict[str, VarData | None]: diff --git a/reflex/config.py b/reflex/config.py index f5f000e82..8a062f4e6 100644 --- a/reflex/config.py +++ b/reflex/config.py @@ -577,7 +577,7 @@ class EnvironmentVariables: REFLEX_CHECK_LATEST_VERSION: EnvVar[bool] = env_var(True) # In which performance mode to run the app. - REFLEX_PERF_MODE: EnvVar[Optional[PerformanceMode]] = env_var(PerformanceMode.WARN) + REFLEX_PERF_MODE: EnvVar[PerformanceMode] = env_var(PerformanceMode.WARN) # The maximum size of the reflex state in kilobytes. REFLEX_STATE_SIZE_LIMIT: EnvVar[int] = env_var(1000) diff --git a/reflex/utils/decorator.py b/reflex/utils/decorator.py new file mode 100644 index 000000000..5c9c0bf3a --- /dev/null +++ b/reflex/utils/decorator.py @@ -0,0 +1,25 @@ +"""Decorator utilities.""" + +from typing import Callable, TypeVar + +T = TypeVar("T") + + +def once(f: Callable[[], T]) -> Callable[[], T]: + """A decorator that calls the function once and caches the result. + + Args: + f: The function to call. + + Returns: + A function that calls the function once and caches the result. + """ + unset = object() + value: object | T = unset + + def wrapper() -> T: + nonlocal value + value = f() if value is unset else value + return value # pyright: ignore[reportReturnType] + + return wrapper diff --git a/tests/units/test_var.py b/tests/units/test_var.py index a72242814..8fcd288e6 100644 --- a/tests/units/test_var.py +++ b/tests/units/test_var.py @@ -8,6 +8,7 @@ from pandas import DataFrame import reflex as rx from reflex.base import Base +from reflex.config import PerformanceMode from reflex.constants.base import REFLEX_VAR_CLOSING_TAG, REFLEX_VAR_OPENING_TAG from reflex.state import BaseState from reflex.utils.exceptions import ( @@ -1893,3 +1894,27 @@ def test_var_data_hooks(): def test_var_data_with_hooks_value(): var_data = VarData(hooks={"what": VarData(hooks={"whot": VarData(hooks="whott")})}) assert var_data == VarData(hooks=["what", "whot", "whott"]) + + +def test_str_var_in_components(mocker): + class StateWithVar(rx.State): + field: int = 1 + + mocker.patch( + "reflex.components.base.bare.get_performance_mode", + return_value=PerformanceMode.RAISE, + ) + + with pytest.raises(ValueError): + rx.vstack( + str(StateWithVar.field), + ) + + mocker.patch( + "reflex.components.base.bare.get_performance_mode", + return_value=PerformanceMode.OFF, + ) + + rx.vstack( + str(StateWithVar.field), + )