Преглед на файлове

raise error when passing a str(var) (#4769)

* raise error when passing a str(var)

* make it faster

* fix typo

* fix tests

* mocker consistency

Co-authored-by: Thomas Brandého <thomas.brandeho@gmail.com>

* ditto

Co-authored-by: Thomas Brandého <thomas.brandeho@gmail.com>

---------

Co-authored-by: Thomas Brandého <thomas.brandeho@gmail.com>
Khaleel Al-Adhami преди 3 месеца
родител
ревизия
372bd22475
променени са 4 файла, в които са добавени 91 реда и са изтрити 1 реда
  1. 40 0
      reflex/components/base/bare.py
  2. 1 1
      reflex/config.py
  3. 25 0
      reflex/utils/decorator.py
  4. 25 0
      tests/units/test_var.py

+ 40 - 0
reflex/components/base/bare.py

@@ -7,9 +7,44 @@ from typing import Any, Iterator
 from reflex.components.component import Component, LiteralComponentVar
 from reflex.components.component import Component, LiteralComponentVar
 from reflex.components.tags import Tag
 from reflex.components.tags import Tag
 from reflex.components.tags.tagless import Tagless
 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.utils.imports import ParsedImportDict
 from reflex.vars import BooleanVar, ObjectVar, Var
 from reflex.vars import BooleanVar, ObjectVar, Var
 from reflex.vars.base import VarData
 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):
 class Bare(Component):
@@ -28,9 +63,14 @@ class Bare(Component):
             The component.
             The component.
         """
         """
         if isinstance(contents, Var):
         if isinstance(contents, Var):
+            if isinstance(contents, LiteralStringVar):
+                validate_str(contents._var_value)
             return cls(contents=contents)
             return cls(contents=contents)
         else:
         else:
+            if isinstance(contents, str):
+                validate_str(contents)
             contents = str(contents) if contents is not None else ""
             contents = str(contents) if contents is not None else ""
+
         return cls(contents=contents)
         return cls(contents=contents)
 
 
     def _get_all_hooks_internal(self) -> dict[str, VarData | None]:
     def _get_all_hooks_internal(self) -> dict[str, VarData | None]:

+ 1 - 1
reflex/config.py

@@ -577,7 +577,7 @@ class EnvironmentVariables:
     REFLEX_CHECK_LATEST_VERSION: EnvVar[bool] = env_var(True)
     REFLEX_CHECK_LATEST_VERSION: EnvVar[bool] = env_var(True)
 
 
     # In which performance mode to run the app.
     # 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.
     # The maximum size of the reflex state in kilobytes.
     REFLEX_STATE_SIZE_LIMIT: EnvVar[int] = env_var(1000)
     REFLEX_STATE_SIZE_LIMIT: EnvVar[int] = env_var(1000)

+ 25 - 0
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

+ 25 - 0
tests/units/test_var.py

@@ -8,6 +8,7 @@ from pandas import DataFrame
 
 
 import reflex as rx
 import reflex as rx
 from reflex.base import Base
 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.constants.base import REFLEX_VAR_CLOSING_TAG, REFLEX_VAR_OPENING_TAG
 from reflex.state import BaseState
 from reflex.state import BaseState
 from reflex.utils.exceptions import (
 from reflex.utils.exceptions import (
@@ -1893,3 +1894,27 @@ def test_var_data_hooks():
 def test_var_data_with_hooks_value():
 def test_var_data_with_hooks_value():
     var_data = VarData(hooks={"what": VarData(hooks={"whot": VarData(hooks="whott")})})
     var_data = VarData(hooks={"what": VarData(hooks={"whot": VarData(hooks="whott")})})
     assert var_data == VarData(hooks=["what", "whot", "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),
+    )