Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

raise error when passing a str(var) #4769

Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions reflex/components/base/bare.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,44 @@
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 performace_mode():
adhami3310 marked this conversation as resolved.
Show resolved Hide resolved
"""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 = performace_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):
Expand All @@ -28,9 +63,14 @@ def create(cls, contents: Any) -> 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]:
Expand Down
2 changes: 1 addition & 1 deletion reflex/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -563,7 +563,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)
Expand Down
25 changes: 25 additions & 0 deletions reflex/utils/decorator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""Decorator utilities."""

from typing import Callable, TypeVar

T = TypeVar("T")


def once(f: Callable[[], T]) -> Callable[[], T]:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we use the new global var cache for this so we have some control over evicting the values?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could, but i think we should keep this one away from global var cache until we improve that one (also unsure if we want to mix var with not vars)

"""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 changes: 25 additions & 0 deletions tests/units/test_var.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

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 (
Expand Down Expand Up @@ -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.performace_mode",
lambda: PerformanceMode.RAISE,
adhami3310 marked this conversation as resolved.
Show resolved Hide resolved
)

with pytest.raises(ValueError):
rx.vstack(
str(StateWithVar.field),
)

mocker.patch(
"reflex.components.base.bare.performace_mode",
lambda: PerformanceMode.OFF,
adhami3310 marked this conversation as resolved.
Show resolved Hide resolved
)

rx.vstack(
str(StateWithVar.field),
)