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>
This commit is contained in:
Khaleel Al-Adhami 2025-02-11 11:39:55 -08:00 committed by GitHub
parent d545ee3f0b
commit 372bd22475
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 91 additions and 1 deletions

View File

@ -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]:

View File

@ -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)

25
reflex/utils/decorator.py Normal file
View File

@ -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

View File

@ -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),
)