diff --git a/reflex/.templates/web/utils/state.js b/reflex/.templates/web/utils/state.js index 08945d52bf9..559ef5003d2 100644 --- a/reflex/.templates/web/utils/state.js +++ b/reflex/.templates/web/utils/state.js @@ -777,7 +777,10 @@ const applyClientStorageDelta = (client_storage, delta) => { ); if (unqualified_states.length === 1) { const main_state = delta[unqualified_states[0]]; - if (main_state.is_hydrated !== undefined && !main_state.is_hydrated) { + if ( + main_state.is_hydrated_rx_state_ !== undefined && + !main_state.is_hydrated_rx_state_ + ) { // skip if the state is not hydrated yet, since all client storage // values are sent in the hydrate event return; @@ -1025,7 +1028,7 @@ export const useEventLoop = ( const change_start = () => { const main_state_dispatch = dispatch["reflex___state____state"]; if (main_state_dispatch !== undefined) { - main_state_dispatch({ is_hydrated: false }); + main_state_dispatch({ is_hydrated_rx_state_: false }); } }; change_start(); diff --git a/reflex/app.py b/reflex/app.py index 610a436efb4..145e32d0a77 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -1100,7 +1100,7 @@ def _validate_var_dependencies(self, state: type[BaseState] | None = None) -> No ) for dep in dep_set: if dep not in state_cls.vars and dep not in state_cls.backend_vars: - msg = f"ComputedVar {var._js_expr} on state {state.__name__} has an invalid dependency {state_name}.{dep}" + msg = f"ComputedVar {var._name} on state {state.__name__} has an invalid dependency {state_name}.{dep}" raise exceptions.VarDependencyError(msg) for substate in state.class_subclasses: diff --git a/reflex/base.py b/reflex/base.py index 6ad0986c31a..b166e56bd3c 100644 --- a/reflex/base.py +++ b/reflex/base.py @@ -1,48 +1,6 @@ """Define the base Reflex class.""" -from __future__ import annotations - -import os -from typing import TYPE_CHECKING, Any - -import pydantic.v1.main as pydantic_main from pydantic.v1 import BaseModel -from pydantic.v1.fields import ModelField - - -def validate_field_name(bases: list[type[BaseModel]], field_name: str) -> None: - """Ensure that the field's name does not shadow an existing attribute of the model. - - Args: - bases: List of base models to check for shadowed attrs. - field_name: name of attribute - - Raises: - VarNameError: If state var field shadows another in its parent state - """ - from reflex.utils.exceptions import VarNameError - - # can't use reflex.config.environment here cause of circular import - reload = os.getenv("__RELOAD_CONFIG", "").lower() == "true" - base = None - try: - for base in bases: - if not reload and getattr(base, field_name, None): - pass - except TypeError as te: - msg = ( - f'State var "{field_name}" in {base} has been shadowed by a substate var; ' - f'use a different field name instead".' - ) - raise VarNameError(msg) from te - - -# monkeypatch pydantic validate_field_name method to skip validating -# shadowed state vars when reloading app via utils.prerequisites.get_app(reload=True) -pydantic_main.validate_field_name = validate_field_name # pyright: ignore [reportPrivateImportUsage] - -if TYPE_CHECKING: - from reflex.vars import Var class Base(BaseModel): @@ -75,7 +33,7 @@ def json(self) -> str: default=serialize, ) - def set(self, **kwargs: Any): + def set(self, **kwargs: object): """Set multiple fields and return the object. Args: @@ -87,47 +45,3 @@ def set(self, **kwargs: Any): for key, value in kwargs.items(): setattr(self, key, value) return self - - @classmethod - def get_fields(cls) -> dict[str, ModelField]: - """Get the fields of the object. - - Returns: - The fields of the object. - """ - return cls.__fields__ - - @classmethod - def add_field(cls, var: Var, default_value: Any): - """Add a pydantic field after class definition. - - Used by State.add_var() to correctly handle the new variable. - - Args: - var: The variable to add a pydantic field for. - default_value: The default value of the field - """ - var_name = var._var_field_name - new_field = ModelField.infer( - name=var_name, - value=default_value, - annotation=var._var_type, - class_validators=None, - config=cls.__config__, - ) - cls.__fields__.update({var_name: new_field}) - - def get_value(self, key: str) -> Any: - """Get the value of a field. - - Args: - key: The key of the field. - - Returns: - The value of the field. - """ - if isinstance(key, str): - # Seems like this function signature was wrong all along? - # If the user wants a field that we know of, get it and pass it off to _get_value - return getattr(self, key) - return key diff --git a/reflex/compiler/compiler.py b/reflex/compiler/compiler.py index f4596bc80ca..c32cca82538 100644 --- a/reflex/compiler/compiler.py +++ b/reflex/compiler/compiler.py @@ -21,6 +21,7 @@ ) from reflex.config import get_config from reflex.constants.compiler import PageNames +from reflex.constants.state import FIELD_MARKER from reflex.environment import environment from reflex.state import BaseState from reflex.style import SYSTEM_COLOR_MODE @@ -694,6 +695,8 @@ def _modify_exception(e: Exception) -> None: f"{msg[:state_index]}{module_path}.{actual_state_name}{msg[dot_index:]}" ) + msg = msg.replace(FIELD_MARKER, "") + e.args = (msg,) diff --git a/reflex/compiler/utils.py b/reflex/compiler/utils.py index f908a0794f7..cef8c0ff1d8 100644 --- a/reflex/compiler/utils.py +++ b/reflex/compiler/utils.py @@ -19,6 +19,7 @@ from reflex.components.el.elements.metadata import Head, Meta, Title from reflex.components.el.elements.other import Html from reflex.components.el.elements.sectioning import Body +from reflex.constants.state import FIELD_MARKER from reflex.istate.storage import Cookie, LocalStorage, SessionStorage from reflex.state import BaseState, _resolve_delta from reflex.style import Style @@ -252,7 +253,7 @@ def _compile_client_storage_recursive( if name in state.inherited_vars: # only include vars defined in this state continue - state_key = f"{state_name}.{name}" + state_key = f"{state_name}.{name}" + FIELD_MARKER field_type, options = _compile_client_storage_field(field) if field_type is Cookie: cookies[state_key] = options diff --git a/reflex/components/core/foreach.py b/reflex/components/core/foreach.py index d6537a22e4c..f4d3d72a0b6 100644 --- a/reflex/components/core/foreach.py +++ b/reflex/components/core/foreach.py @@ -12,6 +12,7 @@ from reflex.components.core.cond import cond from reflex.components.tags import IterTag from reflex.constants import MemoizationMode +from reflex.constants.state import FIELD_MARKER from reflex.state import ComponentState from reflex.utils import types from reflex.utils.exceptions import UntypedVarError @@ -132,11 +133,11 @@ def _render(self) -> IterTag: if len(params) >= 1: # Determine the arg var name based on the params accepted by render_fn. - props["arg_var_name"] = params[0].name + props["arg_var_name"] = params[0].name + FIELD_MARKER if len(params) == 2: # Determine the index var name based on the params accepted by render_fn. - props["index_var_name"] = params[1].name + props["index_var_name"] = params[1].name + FIELD_MARKER else: render_fn = self.render_fn # Otherwise, use a deterministic index, based on the render function bytecode. diff --git a/reflex/constants/state.py b/reflex/constants/state.py index 5ce7cd62ab0..51c22384649 100644 --- a/reflex/constants/state.py +++ b/reflex/constants/state.py @@ -13,3 +13,5 @@ class StateManagerMode(str, Enum): # Used for things like console_log, etc. FRONTEND_EVENT_STATE = "__reflex_internal_frontend_event_state" + +FIELD_MARKER = "_rx_state_" diff --git a/reflex/state.py b/reflex/state.py index 5e62c71e1c9..65b2be40c9b 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -28,6 +28,7 @@ import reflex.istate.dynamic from reflex import constants, event from reflex.base import Base +from reflex.constants.state import FIELD_MARKER from reflex.environment import PerformanceMode, environment from reflex.event import ( BACKGROUND_TASK_MARKER, @@ -67,7 +68,6 @@ Var, computed_var, dispatch, - get_unique_variable_name, is_computed_var, ) @@ -255,7 +255,7 @@ def __call__(self, *args: Any) -> EventSpec: from pydantic.v1.fields import ModelField -def get_var_for_field(cls: type[BaseState], name: str, f: Field): +def get_var_for_field(cls: type[BaseState], name: str, f: Field) -> Var: """Get a Var instance for a state field. Args: @@ -266,7 +266,9 @@ def get_var_for_field(cls: type[BaseState], name: str, f: Field): Returns: The Var instance. """ - field_name = format.format_state_name(cls.get_full_name()) + "." + name + field_name = ( + format.format_state_name(cls.get_full_name()) + "." + name + FIELD_MARKER + ) return dispatch( field_name=field_name, @@ -423,14 +425,14 @@ def __repr__(self) -> str: return f"{type(self).__name__}({self.dict()})" @classmethod - def _get_computed_vars(cls) -> list[ComputedVar]: + def _get_computed_vars(cls) -> list[tuple[str, ComputedVar]]: """Helper function to get all computed vars of a instance. Returns: A list of computed vars. """ return [ - v + (name, v) for mixin in [*cls._mixins(), cls] for name, v in mixin.__dict__.items() if is_computed_var(v) and name not in cls.inherited_vars @@ -538,8 +540,8 @@ def __init_subclass__(cls, mixin: bool = False, **kwargs): if name not in cls.get_skip_vars() and f.is_var and not name.startswith("_") } cls.computed_vars = { - v._js_expr: v._replace(merge_var_data=VarData.from_state(cls)) - for v in computed_vars + name: v._replace(merge_var_data=VarData.from_state(cls)) + for name, v in computed_vars } cls.vars = { **cls.inherited_vars, @@ -549,8 +551,8 @@ def __init_subclass__(cls, mixin: bool = False, **kwargs): cls.event_handlers = {} # Setup the base vars at the class level. - for prop in cls.base_vars.values(): - cls._init_var(prop) + for name, prop in cls.base_vars.items(): + cls._init_var(name, prop) # Set up the event handlers. events = { @@ -568,8 +570,8 @@ def __init_subclass__(cls, mixin: bool = False, **kwargs): newcv = value._replace(fget=fget, _var_data=VarData.from_state(cls)) # cleanup refs to mixin cls in var_data setattr(cls, name, newcv) - cls.computed_vars[newcv._js_expr] = newcv - cls.vars[newcv._js_expr] = newcv + cls.computed_vars[name] = newcv + cls.vars[name] = newcv continue if types.is_backend_base_variable(name, mixin_cls): cls.backend_vars[name] = copy.deepcopy(value) @@ -672,9 +674,16 @@ def _evaluate(cls, f: Callable[[Self], Any], of_type: type | None = None) -> Var of_type = of_type or Component - unique_var_name = get_unique_variable_name() + unique_var_name = ( + ("dynamic_" + f.__module__ + "_" + f.__qualname__) + .replace("<", "") + .replace(">", "") + .replace(".", "_") + ) + + while unique_var_name in cls.vars: + unique_var_name += "_" - @computed_var(_js_expr=unique_var_name, return_type=of_type) def computed_var_func(state: Self): result = f(state) @@ -686,10 +695,16 @@ def computed_var_func(state: Self): return result - setattr(cls, unique_var_name, computed_var_func) - cls.computed_vars[unique_var_name] = computed_var_func - cls.vars[unique_var_name] = computed_var_func - cls._update_substate_inherited_vars({unique_var_name: computed_var_func}) + computed_var_func.__name__ = unique_var_name + + computed_var_func_arg = computed_var(return_type=of_type, cache=False)( + computed_var_func + ) + + setattr(cls, unique_var_name, computed_var_func_arg) + cls.computed_vars[unique_var_name] = computed_var_func_arg + cls.vars[unique_var_name] = computed_var_func_arg + cls._update_substate_inherited_vars({unique_var_name: computed_var_func_arg}) cls._always_dirty_computed_vars.add(unique_var_name) return getattr(cls, unique_var_name) @@ -827,8 +842,8 @@ def _check_overridden_basevars(cls): Raises: ComputedVarShadowsBaseVarsError: When a computed var shadows a base var. """ - for computed_var_ in cls._get_computed_vars(): - if computed_var_._js_expr in cls.__annotations__: + for name, computed_var_ in cls._get_computed_vars(): + if name in cls.__annotations__: msg = f"The computed var name `{computed_var_._js_expr}` shadows a base var in {cls.__module__}.{cls.__name__}; use a different name instead" raise ComputedVarShadowsBaseVarsError(msg) @@ -1001,10 +1016,11 @@ def is_user_defined(cls) -> bool: ) @classmethod - def _init_var(cls, prop: Var): + def _init_var(cls, name: str, prop: Var): """Initialize a variable. Args: + name: The name of the variable prop: The variable to initialize Raises: @@ -1021,10 +1037,10 @@ def _init_var(cls, prop: Var): f'Found var "{prop._js_expr}" with type {prop._var_type}.' ) raise VarTypeError(msg) - cls._set_var(prop) + cls._set_var(name, prop) if cls.is_user_defined() and get_config().state_auto_setters: - cls._create_setter(prop) - cls._set_default_value(prop) + cls._create_setter(name, prop) + cls._set_default_value(name, prop) @classmethod def add_var(cls, name: str, type_: Any, default_value: Any = None): @@ -1047,15 +1063,18 @@ def add_var(cls, name: str, type_: Any, default_value: Any = None): # create the variable based on name and type var = Var( - _js_expr=format.format_state_name(cls.get_full_name()) + "." + name, + _js_expr=format.format_state_name(cls.get_full_name()) + + "." + + name + + FIELD_MARKER, _var_type=type_, _var_data=VarData.from_state(cls, name), ).guess_type() # add the pydantic field dynamically (must be done before _init_var) - cls.add_field(var, default_value) + cls.add_field(name, var, default_value) - cls._init_var(var) + cls._init_var(name, var) # update the internal dicts so the new variable is correctly handled cls.base_vars.update({name: var}) @@ -1069,13 +1088,14 @@ def add_var(cls, name: str, type_: Any, default_value: Any = None): cls._init_var_dependency_dicts() @classmethod - def _set_var(cls, prop: Var): + def _set_var(cls, name: str, prop: Var): """Set the var as a class member. Args: + name: The name of the var. prop: The var instance to set. """ - setattr(cls, prop._var_field_name, prop) + setattr(cls, name, prop) @classmethod def _create_event_handler(cls, fn: Any): @@ -1095,27 +1115,29 @@ def _create_setvar(cls): cls.setvar = cls.event_handlers["setvar"] = EventHandlerSetVar(state_cls=cls) @classmethod - def _create_setter(cls, prop: Var): + def _create_setter(cls, name: str, prop: Var): """Create a setter for the var. Args: + name: The name of the var. prop: The var to create a setter for. """ - setter_name = prop._get_setter_name(include_state=False) + setter_name = Var._get_setter_name_for_name(name) if setter_name not in cls.__dict__: - event_handler = cls._create_event_handler(prop._get_setter()) + event_handler = cls._create_event_handler(prop._get_setter(name)) cls.event_handlers[setter_name] = event_handler setattr(cls, setter_name, event_handler) @classmethod - def _set_default_value(cls, prop: Var): + def _set_default_value(cls, name: str, prop: Var): """Set the default value for the var. Args: + name: The name of the var. prop: The var to set the default value for. """ # Get the pydantic field for the var. - field = cls.get_fields()[prop._var_field_name] + field = cls.get_fields()[name] if field.default is None and not types.is_optional(prop._var_type): # Ensure frontend uses null coalescing when accessing. @@ -1191,12 +1213,16 @@ def argsingle_factory(param: str): def inner_func(self: BaseState) -> str: return self.router.page.params.get(param, "") + inner_func.__name__ = param + return inner_func def arglist_factory(param: str): def inner_func(self: BaseState) -> list[str]: return self.router.page.params.get(param, []) + inner_func.__name__ = param + return inner_func dynamic_vars = {} @@ -1211,8 +1237,7 @@ def inner_func(self: BaseState) -> list[str]: fget=func, auto_deps=False, deps=["router"], - _js_expr=param, - _var_data=VarData.from_state(cls), + _var_data=VarData.from_state(cls, param), ) setattr(cls, param, dynamic_vars[param]) @@ -1940,7 +1965,7 @@ def get_delta(self) -> Delta: ) subdelta: dict[str, Any] = { - prop: self.get_value(prop) + prop + FIELD_MARKER: self.get_value(prop) for prop in delta_vars if not types.is_backend_base_variable(prop, type(self)) } @@ -2075,7 +2100,9 @@ def dict( computed_vars = {} variables = {**base_vars, **computed_vars} d = { - self.get_full_name(): {k: variables[k] for k in sorted(variables)}, + self.get_full_name(): { + k + FIELD_MARKER: variables[k] for k in sorted(variables) + }, } for substate_d in [ v.dict(include_computed=include_computed, initial=initial, **kwargs) @@ -2396,6 +2423,7 @@ async def update_vars_internal(self, vars: dict[str, Any]) -> None: """ for var, value in vars.items(): state_name, _, var_name = var.rpartition(".") + var_name = var_name.removesuffix(FIELD_MARKER) var_state_cls = State.get_class_substate(state_name) if var_state_cls._is_client_storage(var_name): var_state = await self.get_state(var_state_cls) diff --git a/reflex/vars/base.py b/reflex/vars/base.py index bd6250c3ca6..68afffaffc6 100644 --- a/reflex/vars/base.py +++ b/reflex/vars/base.py @@ -48,6 +48,7 @@ from reflex import constants from reflex.base import Base from reflex.constants.compiler import Hooks +from reflex.constants.state import FIELD_MARKER from reflex.utils import console, exceptions, imports, serializers, types from reflex.utils.exceptions import ( ComputedVarSignatureError, @@ -419,17 +420,6 @@ def _var_is_local(self) -> bool: """ return False - @property - def _var_field_name(self) -> str: - """The name of the field. - - Returns: - The name of the field. - """ - var_data = self._get_all_var_data() - field_name = var_data.field_name if var_data else None - return field_name or self._js_expr - @property def _var_is_string(self) -> bool: """Whether the var is a string literal. @@ -917,30 +907,29 @@ def guess_type(self) -> Var: return self - def _get_setter_name(self, include_state: bool = True) -> str: + @staticmethod + def _get_setter_name_for_name( + name: str, + ) -> str: """Get the name of the var's generated setter function. Args: - include_state: Whether to include the state name in the setter name. + name: The name of the var. Returns: The name of the setter function. """ - setter = constants.SETTER_PREFIX + self._var_field_name - var_data = self._get_all_var_data() - if var_data is None: - return setter - if not include_state or var_data.state == "": - return setter - return var_data.state + "." + setter + return constants.SETTER_PREFIX + name - def _get_setter(self) -> Callable[[BaseState, Any], None]: + def _get_setter(self, name: str) -> Callable[[BaseState, Any], None]: """Get the var's setter function. + Args: + name: The name of the var. + Returns: A function that that creates a setter for the var. """ - actual_name = self._var_field_name def setter(state: Any, value: Any): """Get the setter for the var. @@ -952,17 +941,17 @@ def setter(state: Any, value: Any): if self._var_type in [int, float]: try: value = self._var_type(value) - setattr(state, actual_name, value) + setattr(state, name, value) except ValueError: console.debug( f"{type(state).__name__}.{self._js_expr}: Failed conversion of {value!s} to '{self._var_type.__name__}'. Value not set.", ) else: - setattr(state, actual_name, value) + setattr(state, name, value) setter.__annotations__["value"] = self._var_type - setter.__qualname__ = self._get_setter_name() + setter.__qualname__ = Var._get_setter_name_for_name(name) return setter @@ -2022,6 +2011,8 @@ class ComputedVar(Var[RETURN_TYPE]): default_factory=lambda: lambda _: None ) # pyright: ignore [reportAssignmentType] + _name: str = dataclasses.field(default="") + def __init__( self, fget: Callable[[BASE_STATE], RETURN_TYPE], @@ -2055,14 +2046,18 @@ def __init__( if hint is Any: raise UntypedComputedVarError(var_name=fget.__name__) - kwargs.setdefault("_js_expr", fget.__name__) + is_using_fget_name = "_js_expr" not in kwargs + js_expr = kwargs.pop("_js_expr", fget.__name__ + FIELD_MARKER) kwargs.setdefault("_var_type", hint) Var.__init__( self, - _js_expr=kwargs.pop("_js_expr"), + _js_expr=js_expr, _var_type=kwargs.pop("_var_type"), - _var_data=kwargs.pop("_var_data", None), + _var_data=kwargs.pop( + "_var_data", + VarData(field_name=fget.__name__) if is_using_fget_name else None, + ), ) if kwargs: @@ -2075,6 +2070,7 @@ def __init__( object.__setattr__(self, "_backend", backend) object.__setattr__(self, "_initial_value", initial_value) object.__setattr__(self, "_cache", cache) + object.__setattr__(self, "_name", fget.__name__) if isinstance(interval, int): interval = datetime.timedelta(seconds=interval) @@ -2306,7 +2302,7 @@ def __get__(self, instance: BaseState | None, owner: type): """ if instance is None: state_where_defined = owner - while self._js_expr in state_where_defined.inherited_vars: + while self._name in state_where_defined.inherited_vars: state_where_defined = state_where_defined.get_parent_state() field_name = ( @@ -2317,7 +2313,7 @@ def __get__(self, instance: BaseState | None, owner: type): return dispatch( field_name, - var_data=VarData.from_state(state_where_defined, self._js_expr), + var_data=VarData.from_state(state_where_defined, self._name), result_var_type=self._var_type, existing_var=self, ) @@ -2429,7 +2425,7 @@ def add_dependency(self, objclass: type[BaseState], dep: Var): objclass.get_root_state().get_class_substate( state_name )._var_dependencies.setdefault(var_name, set()).add( - (objclass.get_full_name(), self._js_expr) + (objclass.get_full_name(), self._name) ) return msg = ( @@ -3710,16 +3706,16 @@ def get_fields(cls) -> Mapping[str, Field]: return cls.__fields__ @classmethod - def add_field(cls, var: Var, default_value: Any): + def add_field(cls, name: str, var: Var, default_value: Any): """Add a field to the class after class definition. Used by State.add_var() to correctly handle the new variable. Args: + name: The name of the field to add. var: The variable to add a field for. default_value: The default value of the field. """ - var_name = var._var_field_name if types.is_immutable(default_value): new_field = Field( default=default_value, @@ -3730,4 +3726,4 @@ def add_field(cls, var: Var, default_value: Any): default_factory=functools.partial(copy.deepcopy, default_value), annotated_type=var._var_type, ) - cls.__fields__[var_name] = new_field + cls.__fields__[name] = new_field diff --git a/tests/integration/test_background_task.py b/tests/integration/test_background_task.py index 8910da063b9..3d7a028caed 100644 --- a/tests/integration/test_background_task.py +++ b/tests/integration/test_background_task.py @@ -241,6 +241,7 @@ def token(background_task: AppHarness, driver: WebDriver) -> str: The token for the connected client """ assert background_task.app_instance is not None + token_input = background_task.poll_for_result( lambda: driver.find_element(By.ID, "token") ) diff --git a/tests/integration/test_client_storage.py b/tests/integration/test_client_storage.py index c24f28ad438..3f51434d7de 100644 --- a/tests/integration/test_client_storage.py +++ b/tests/integration/test_client_storage.py @@ -10,6 +10,7 @@ from selenium.webdriver.common.by import By from selenium.webdriver.remote.webdriver import WebDriver +from reflex.constants.state import FIELD_MARKER from reflex.state import ( State, StateManagerDisk, @@ -362,28 +363,28 @@ def set_sub_sub(var: str, value: str): ) exp_cookies = { - f"{sub_state_name}.c1": { + f"{sub_state_name}.c1" + FIELD_MARKER: { "domain": "localhost", "httpOnly": False, - "name": f"{sub_state_name}.c1", + "name": f"{sub_state_name}.c1" + FIELD_MARKER, "path": "/", "sameSite": "Lax", "secure": False, "value": "c1%20value", }, - f"{sub_state_name}.c2": { + f"{sub_state_name}.c2" + FIELD_MARKER: { "domain": "localhost", "httpOnly": False, - "name": f"{sub_state_name}.c2", + "name": f"{sub_state_name}.c2" + FIELD_MARKER, "path": "/", "sameSite": "Lax", "secure": False, "value": "c2%20value", }, - f"{sub_state_name}.c4": { + f"{sub_state_name}.c4" + FIELD_MARKER: { "domain": "localhost", "httpOnly": False, - "name": f"{sub_state_name}.c4", + "name": f"{sub_state_name}.c4" + FIELD_MARKER, "path": "/", "sameSite": "Strict", "secure": False, @@ -398,19 +399,19 @@ def set_sub_sub(var: str, value: str): "secure": False, "value": "c6%20value", }, - f"{sub_state_name}.c7": { + f"{sub_state_name}.c7" + FIELD_MARKER: { "domain": "localhost", "httpOnly": False, - "name": f"{sub_state_name}.c7", + "name": f"{sub_state_name}.c7" + FIELD_MARKER, "path": "/", "sameSite": "Lax", "secure": False, "value": "c7%20value", }, - f"{sub_sub_state_name}.c1s": { + f"{sub_sub_state_name}.c1s" + FIELD_MARKER: { "domain": "localhost", "httpOnly": False, - "name": f"{sub_sub_state_name}.c1s", + "name": f"{sub_sub_state_name}.c1s" + FIELD_MARKER, "path": "/", "sameSite": "Lax", "secure": False, @@ -428,13 +429,15 @@ def set_sub_sub(var: str, value: str): # Test cookie with expiry by itself to avoid timing flakiness set_sub("c3", "c3 value") - AppHarness._poll_for(lambda: f"{sub_state_name}.c3" in cookie_info_map(driver)) - c3_cookie = cookie_info_map(driver)[f"{sub_state_name}.c3"] + AppHarness._poll_for( + lambda: f"{sub_state_name}.c3" + FIELD_MARKER in cookie_info_map(driver) + ) + c3_cookie = cookie_info_map(driver)[f"{sub_state_name}.c3" + FIELD_MARKER] assert c3_cookie.pop("expiry") is not None assert c3_cookie == { "domain": "localhost", "httpOnly": False, - "name": f"{sub_state_name}.c3", + "name": f"{sub_state_name}.c3" + FIELD_MARKER, "path": "/", "sameSite": "Lax", "secure": False, @@ -443,24 +446,34 @@ def set_sub_sub(var: str, value: str): await asyncio.sleep(2) # wait for c3 to expire if not isinstance(driver, Firefox): # Note: Firefox does not remove expired cookies Bug 576347 - assert f"{sub_state_name}.c3" not in cookie_info_map(driver) + assert f"{sub_state_name}.c3" + FIELD_MARKER not in cookie_info_map(driver) local_storage_items = local_storage.items() local_storage_items.pop("last_compiled_time", None) local_storage_items.pop("theme", None) - assert local_storage_items.pop(f"{sub_state_name}.l1") == "l1 value" - assert local_storage_items.pop(f"{sub_state_name}.l2") == "l2 value" + assert local_storage_items.pop(f"{sub_state_name}.l1" + FIELD_MARKER) == "l1 value" + assert local_storage_items.pop(f"{sub_state_name}.l2" + FIELD_MARKER) == "l2 value" assert local_storage_items.pop("l3") == "l3 value" - assert local_storage_items.pop(f"{sub_state_name}.l4") == "l4 value" - assert local_storage_items.pop(f"{sub_sub_state_name}.l1s") == "l1s value" + assert local_storage_items.pop(f"{sub_state_name}.l4" + FIELD_MARKER) == "l4 value" + assert ( + local_storage_items.pop(f"{sub_sub_state_name}.l1s" + FIELD_MARKER) + == "l1s value" + ) assert not local_storage_items session_storage_items = session_storage.items() session_storage_items.pop("token", None) - assert session_storage_items.pop(f"{sub_state_name}.s1") == "s1 value" - assert session_storage_items.pop(f"{sub_state_name}.s2") == "s2 value" + assert ( + session_storage_items.pop(f"{sub_state_name}.s1" + FIELD_MARKER) == "s1 value" + ) + assert ( + session_storage_items.pop(f"{sub_state_name}.s2" + FIELD_MARKER) == "s2 value" + ) assert session_storage_items.pop("s3") == "s3 value" - assert session_storage_items.pop(f"{sub_sub_state_name}.s1s") == "s1s value" + assert ( + session_storage_items.pop(f"{sub_sub_state_name}.s1s" + FIELD_MARKER) + == "s1s value" + ) assert not session_storage_items assert c1.text == "c1 value" @@ -573,11 +586,13 @@ def set_sub_sub(var: str, value: str): assert s1s.text == "s1s value" # make sure c5 cookie shows up on the `/foo` route - AppHarness._poll_for(lambda: f"{sub_state_name}.c5" in cookie_info_map(driver)) - assert cookie_info_map(driver)[f"{sub_state_name}.c5"] == { + AppHarness._poll_for( + lambda: f"{sub_state_name}.c5" + FIELD_MARKER in cookie_info_map(driver) + ) + assert cookie_info_map(driver)[f"{sub_state_name}.c5" + FIELD_MARKER] == { "domain": "localhost", "httpOnly": False, - "name": f"{sub_state_name}.c5", + "name": f"{sub_state_name}.c5" + FIELD_MARKER, "path": "/foo/", "sameSite": "Lax", "secure": False, diff --git a/tests/integration/test_login_flow.py b/tests/integration/test_login_flow.py index 33eb0cbeb09..324ea19294b 100644 --- a/tests/integration/test_login_flow.py +++ b/tests/integration/test_login_flow.py @@ -9,6 +9,7 @@ from selenium.webdriver.common.by import By from selenium.webdriver.remote.webdriver import WebDriver +from reflex.constants.state import FIELD_MARKER from reflex.testing import AppHarness from . import utils @@ -143,7 +144,7 @@ def check_auth_token_header(): state_name = login_sample.get_full_state_name(["_state"]) assert login_sample._poll_for( - lambda: local_storage[f"{state_name}.auth_token"] == "" + lambda: local_storage[f"{state_name}.auth_token" + FIELD_MARKER] == "" ) with pytest.raises(NoSuchElementException): driver.find_element(By.ID, "auth-token") diff --git a/tests/units/components/core/test_colors.py b/tests/units/components/core/test_colors.py index d23a5079cd9..417b12e7261 100644 --- a/tests/units/components/core/test_colors.py +++ b/tests/units/components/core/test_colors.py @@ -3,6 +3,7 @@ import reflex as rx from reflex.components.datadisplay.code import CodeBlock from reflex.constants.colors import Color +from reflex.constants.state import FIELD_MARKER from reflex.vars.base import LiteralVar @@ -36,19 +37,19 @@ def create_color_var(color): (create_color_var(rx.color("mint", 3, True)), '"var(--mint-a3)"', Color), ( create_color_var(rx.color(ColorState.color, ColorState.shade)), - f'("var(--"+{color_state_name!s}.color+"-"+(((__to_string) => __to_string.toString())({color_state_name!s}.shade))+")")', + f'("var(--"+{color_state_name!s}.color{FIELD_MARKER}+"-"+(((__to_string) => __to_string.toString())({color_state_name!s}.shade{FIELD_MARKER}))+")")', Color, ), ( create_color_var( rx.color(ColorState.color, ColorState.shade, ColorState.alpha) ), - f'("var(--"+{color_state_name!s}.color+"-"+({color_state_name!s}.alpha ? "a" : "")+(((__to_string) => __to_string.toString())({color_state_name!s}.shade))+")")', + f'("var(--"+{color_state_name!s}.color{FIELD_MARKER}+"-"+({color_state_name!s}.alpha{FIELD_MARKER} ? "a" : "")+(((__to_string) => __to_string.toString())({color_state_name!s}.shade{FIELD_MARKER}))+")")', Color, ), ( create_color_var(color_with_fstring), - f'("var(--"+{color_state_name!s}.color+"-"+(((__to_string) => __to_string.toString())({color_state_name!s}.shade))+")")', + f'("var(--"+{color_state_name!s}.color{FIELD_MARKER}+"-"+(((__to_string) => __to_string.toString())({color_state_name!s}.shade{FIELD_MARKER}))+")")', Color, ), ( @@ -58,17 +59,17 @@ def create_color_var(color): ColorState.shade, ) ), - f'("var(--"+({color_state_name!s}.color_part+"ato")+"-"+(((__to_string) => __to_string.toString())({color_state_name!s}.shade))+")")', + f'("var(--"+({color_state_name!s}.color_part{FIELD_MARKER}+"ato")+"-"+(((__to_string) => __to_string.toString())({color_state_name!s}.shade{FIELD_MARKER}))+")")', Color, ), ( create_color_var(f"{rx.color(ColorState.color, ColorState.shade)}"), - f'("var(--"+{color_state_name!s}.color+"-"+{color_state_name!s}.shade+")")', + f'("var(--"+{color_state_name!s}.color{FIELD_MARKER}+"-"+{color_state_name!s}.shade{FIELD_MARKER}+")")', str, ), ( create_color_var(f"{color_with_fstring}"), - f'("var(--"+{color_state_name!s}.color+"-"+{color_state_name!s}.shade+")")', + f'("var(--"+{color_state_name!s}.color{FIELD_MARKER}+"-"+{color_state_name!s}.shade{FIELD_MARKER}+")")', str, ), ], @@ -87,7 +88,7 @@ def test_color(color, expected, expected_type: type[str] | type[Color]): ), ( rx.cond(True, rx.color(ColorState.color), rx.color(ColorState.color, 5)), - f'(true ? ("var(--"+{color_state_name!s}.color+"-7)") : ("var(--"+{color_state_name!s}.color+"-5)"))', + f'(true ? ("var(--"+{color_state_name!s}.color{FIELD_MARKER}+"-7)") : ("var(--"+{color_state_name!s}.color{FIELD_MARKER}+"-5)"))', ), ( rx.match( @@ -98,7 +99,7 @@ def test_color(color, expected, expected_type: type[str] | type[Color]): ), '(() => { switch (JSON.stringify("condition")) {case JSON.stringify("first"): return ("var(--mint-7)");' ' break;case JSON.stringify("second"): return ("var(--tomato-5)"); break;default: ' - f'return (("var(--"+{color_state_name!s}.color+"-2)")); break;}};}})()', + f'return (("var(--"+{color_state_name!s}.color{FIELD_MARKER}+"-2)")); break;}};}})()', ), ( rx.match( @@ -108,9 +109,9 @@ def test_color(color, expected, expected_type: type[str] | type[Color]): rx.color(ColorState.color, 2), ), '(() => { switch (JSON.stringify("condition")) {case JSON.stringify("first"): ' - f'return (("var(--"+{color_state_name!s}.color+"-7)")); break;case JSON.stringify("second"): ' - f'return (("var(--"+{color_state_name!s}.color+"-5)")); break;default: ' - f'return (("var(--"+{color_state_name!s}.color+"-2)")); break;}};}})()', + f'return (("var(--"+{color_state_name!s}.color{FIELD_MARKER}+"-7)")); break;case JSON.stringify("second"): ' + f'return (("var(--"+{color_state_name!s}.color{FIELD_MARKER}+"-5)")); break;default: ' + f'return (("var(--"+{color_state_name!s}.color{FIELD_MARKER}+"-2)")); break;}};}})()', ), ], ) diff --git a/tests/units/components/core/test_cond.py b/tests/units/components/core/test_cond.py index 9796bad760b..ec651d00671 100644 --- a/tests/units/components/core/test_cond.py +++ b/tests/units/components/core/test_cond.py @@ -6,6 +6,7 @@ from reflex.components.base.fragment import Fragment from reflex.components.core.cond import Cond, cond from reflex.components.radix.themes.typography.text import Text +from reflex.constants.state import FIELD_MARKER from reflex.state import BaseState from reflex.utils.format import format_state_name from reflex.vars.base import LiteralVar, Var, computed_var @@ -139,7 +140,8 @@ def computed_str(self) -> str: state_name = format_state_name(CondStateComputed.get_full_name()) assert ( - str(comp) == f"(true ? {state_name}.computed_int : {state_name}.computed_str)" + str(comp) + == f"(true ? {state_name}.computed_int{FIELD_MARKER} : {state_name}.computed_str{FIELD_MARKER})" ) assert comp._var_type == int | str diff --git a/tests/units/components/core/test_foreach.py b/tests/units/components/core/test_foreach.py index b2115ac8ae4..1239285440e 100644 --- a/tests/units/components/core/test_foreach.py +++ b/tests/units/components/core/test_foreach.py @@ -13,6 +13,7 @@ ) from reflex.components.radix.themes.layout.box import box from reflex.components.radix.themes.typography.text import text +from reflex.constants.state import FIELD_MARKER from reflex.state import BaseState, ComponentState from reflex.vars.base import Var from reflex.vars.number import NumberVar @@ -150,77 +151,84 @@ def display_color_index_tuple(color): ForEachState.colors_list, display_color, { - "iterable_state": f"{ForEachState.get_full_name()}.colors_list", + "iterable_state": f"{ForEachState.get_full_name()}.colors_list" + + FIELD_MARKER, }, ), ( ForEachState.colors_dict_list, display_color_name, { - "iterable_state": f"{ForEachState.get_full_name()}.colors_dict_list", + "iterable_state": f"{ForEachState.get_full_name()}.colors_dict_list" + + FIELD_MARKER, }, ), ( ForEachState.colors_nested_dict_list, display_shade, { - "iterable_state": f"{ForEachState.get_full_name()}.colors_nested_dict_list", + "iterable_state": f"{ForEachState.get_full_name()}.colors_nested_dict_list" + + FIELD_MARKER, }, ), ( ForEachState.primary_color, display_primary_colors, { - "iterable_state": f"Object.entries({ForEachState.get_full_name()}.primary_color)", + "iterable_state": f"Object.entries({ForEachState.get_full_name()}.primary_color{FIELD_MARKER})", }, ), ( ForEachState.color_with_shades, display_color_with_shades, { - "iterable_state": f"Object.entries({ForEachState.get_full_name()}.color_with_shades)", + "iterable_state": f"Object.entries({ForEachState.get_full_name()}.color_with_shades{FIELD_MARKER})", }, ), ( ForEachState.nested_colors_with_shades, display_nested_color_with_shades, { - "iterable_state": f"Object.entries({ForEachState.get_full_name()}.nested_colors_with_shades)", + "iterable_state": f"Object.entries({ForEachState.get_full_name()}.nested_colors_with_shades{FIELD_MARKER})", }, ), ( ForEachState.nested_colors_with_shades, display_nested_color_with_shades_v2, { - "iterable_state": f"Object.entries({ForEachState.get_full_name()}.nested_colors_with_shades)", + "iterable_state": f"Object.entries({ForEachState.get_full_name()}.nested_colors_with_shades{FIELD_MARKER})", }, ), ( ForEachState.color_tuple, display_color_tuple, { - "iterable_state": f"{ForEachState.get_full_name()}.color_tuple", + "iterable_state": f"{ForEachState.get_full_name()}.color_tuple" + + FIELD_MARKER, }, ), ( ForEachState.colors_set, display_colors_set, { - "iterable_state": f"{ForEachState.get_full_name()}.colors_set", + "iterable_state": f"{ForEachState.get_full_name()}.colors_set" + + FIELD_MARKER, }, ), ( ForEachState.nested_colors_list, lambda el, i: display_nested_list_element(el, i), { - "iterable_state": f"{ForEachState.get_full_name()}.nested_colors_list", + "iterable_state": f"{ForEachState.get_full_name()}.nested_colors_list" + + FIELD_MARKER, }, ), ( ForEachState.color_index_tuple, display_color_index_tuple, { - "iterable_state": f"{ForEachState.get_full_name()}.color_index_tuple", + "iterable_state": f"{ForEachState.get_full_name()}.color_index_tuple" + + FIELD_MARKER, }, ), ], diff --git a/tests/units/components/core/test_match.py b/tests/units/components/core/test_match.py index 9b3f684c7ee..eb870577880 100644 --- a/tests/units/components/core/test_match.py +++ b/tests/units/components/core/test_match.py @@ -6,6 +6,7 @@ import reflex as rx from reflex.components.component import Component from reflex.components.core.match import Match +from reflex.constants.state import FIELD_MARKER from reflex.state import BaseState from reflex.utils.exceptions import MatchTypeError from reflex.vars.base import Var @@ -39,7 +40,7 @@ def test_match_components(): [match_child] = match_dict["children"] assert match_child["name"] == "match" - assert str(match_child["cond"]) == f"{MatchState.get_name()}.value" + assert str(match_child["cond"]) == f"{MatchState.get_name()}.value" + FIELD_MARKER match_cases = match_child["match_cases"] assert len(match_cases) == 6 @@ -76,7 +77,9 @@ def test_match_components(): assert fifth_return_value_render["name"] == "RadixThemesText" assert fifth_return_value_render["children"][0]["contents"] == '"fifth value"' - assert match_cases[5][0]._js_expr == f"({MatchState.get_name()}.num + 1)" + assert ( + match_cases[5][0]._js_expr == f"({MatchState.get_name()}.num{FIELD_MARKER} + 1)" + ) assert match_cases[5][0]._var_type is int fifth_return_value_render = match_cases[5][1] assert fifth_return_value_render["name"] == "RadixThemesText" @@ -103,11 +106,11 @@ def test_match_components(): (MatchState.string, f"{MatchState.value} - string"), "default value", ), - f'(() => {{ switch (JSON.stringify({MatchState.get_name()}.value)) {{case JSON.stringify(1): return ("first"); break;case JSON.stringify(2): case JSON.stringify(3): return ' + f'(() => {{ switch (JSON.stringify({MatchState.get_name()}.value{FIELD_MARKER})) {{case JSON.stringify(1): return ("first"); break;case JSON.stringify(2): case JSON.stringify(3): return ' '("second value"); break;case JSON.stringify([1, 2]): return ("third-value"); break;case JSON.stringify("random"): ' 'return ("fourth_value"); break;case JSON.stringify(({ ["foo"] : "bar" })): return ("fifth value"); ' - f'break;case JSON.stringify(({MatchState.get_name()}.num + 1)): return ("sixth value"); break;case JSON.stringify(({MatchState.get_name()}.value+" - string")): ' - f'return ({MatchState.get_name()}.string); break;case JSON.stringify({MatchState.get_name()}.string): return (({MatchState.get_name()}.value+" - string")); break;default: ' + f'break;case JSON.stringify(({MatchState.get_name()}.num{FIELD_MARKER} + 1)): return ("sixth value"); break;case JSON.stringify(({MatchState.get_name()}.value{FIELD_MARKER}+" - string")): ' + f'return ({MatchState.get_name()}.string{FIELD_MARKER}); break;case JSON.stringify({MatchState.get_name()}.string{FIELD_MARKER}): return (({MatchState.get_name()}.value{FIELD_MARKER}+" - string")); break;default: ' 'return ("default value"); break;};})()', ), ( @@ -122,12 +125,12 @@ def test_match_components(): (MatchState.string, f"{MatchState.value} - string"), MatchState.string, ), - f'(() => {{ switch (JSON.stringify({MatchState.get_name()}.value)) {{case JSON.stringify(1): return ("first"); break;case JSON.stringify(2): case JSON.stringify(3): return ' + f'(() => {{ switch (JSON.stringify({MatchState.get_name()}.value{FIELD_MARKER})) {{case JSON.stringify(1): return ("first"); break;case JSON.stringify(2): case JSON.stringify(3): return ' '("second value"); break;case JSON.stringify([1, 2]): return ("third-value"); break;case JSON.stringify("random"): ' 'return ("fourth_value"); break;case JSON.stringify(({ ["foo"] : "bar" })): return ("fifth value"); ' - f'break;case JSON.stringify(({MatchState.get_name()}.num + 1)): return ("sixth value"); break;case JSON.stringify(({MatchState.get_name()}.value+" - string")): ' - f'return ({MatchState.get_name()}.string); break;case JSON.stringify({MatchState.get_name()}.string): return (({MatchState.get_name()}.value+" - string")); break;default: ' - f"return ({MatchState.get_name()}.string); break;}};}})()", + f'break;case JSON.stringify(({MatchState.get_name()}.num{FIELD_MARKER} + 1)): return ("sixth value"); break;case JSON.stringify(({MatchState.get_name()}.value{FIELD_MARKER}+" - string")): ' + f'return ({MatchState.get_name()}.string{FIELD_MARKER}); break;case JSON.stringify({MatchState.get_name()}.string{FIELD_MARKER}): return (({MatchState.get_name()}.value{FIELD_MARKER}+" - string")); break;default: ' + f"return ({MatchState.get_name()}.string{FIELD_MARKER}); break;}};}})()", ), ], ) diff --git a/tests/units/components/datadisplay/test_datatable.py b/tests/units/components/datadisplay/test_datatable.py index 902e37575ef..968fd58cb83 100644 --- a/tests/units/components/datadisplay/test_datatable.py +++ b/tests/units/components/datadisplay/test_datatable.py @@ -3,6 +3,7 @@ import reflex as rx from reflex.components.gridjs.datatable import DataTable +from reflex.constants.state import FIELD_MARKER from reflex.utils import types from reflex.utils.exceptions import UntypedComputedVarError from reflex.utils.serializers import serialize, serialize_dataframe @@ -44,11 +45,20 @@ def test_validate_data_table(data_table_state: rx.State, expected): # prefix expected with state name state_name = data_table_state.get_name() - expected = f"{state_name}.{expected}" if expected else state_name + expected_columns = ( + f"{state_name}.{expected}{FIELD_MARKER}.columns" + if expected + else state_name + ".columns" + FIELD_MARKER + ) + expected_data = ( + f"{state_name}.{expected}{FIELD_MARKER}.data" + if expected + else state_name + ".data" + FIELD_MARKER + ) assert data_table_dict["props"] == [ - f"columns:{expected}.columns", - f"data:{expected}.data", + "columns:" + expected_columns, + "data:" + expected_data, ] diff --git a/tests/units/components/test_component.py b/tests/units/components/test_component.py index 6f012f1af94..51e9ca72d4d 100644 --- a/tests/units/components/test_component.py +++ b/tests/units/components/test_component.py @@ -17,6 +17,7 @@ ) from reflex.components.radix.themes.layout.box import Box from reflex.constants import EventTriggers +from reflex.constants.state import FIELD_MARKER from reflex.event import ( EventChain, EventHandler, @@ -2197,7 +2198,7 @@ def add_style(self): # pyright: ignore [reportIncompatibleMethodOverride] assert "useParent" in page._get_all_hooks_internal() assert ( str(page).count( - f'css:({{ ["fakeParent"] : "parent", ["color"] : "var(--plum-10)", ["fake"] : "text", ["margin"] : ({test_state.get_name()}.num+"%") }})' + f'css:({{ ["fakeParent"] : "parent", ["color"] : "var(--plum-10)", ["fake"] : "text", ["margin"] : ({test_state.get_name()}.num{FIELD_MARKER}+"%") }})' ) == 1 ) diff --git a/tests/units/test_app.py b/tests/units/test_app.py index 2be1e71805e..c87302d15b8 100644 --- a/tests/units/test_app.py +++ b/tests/units/test_app.py @@ -35,6 +35,7 @@ from reflex.components.base.fragment import Fragment from reflex.components.core.cond import Cond from reflex.components.radix.themes.typography.text import Text +from reflex.constants.state import FIELD_MARKER from reflex.event import Event from reflex.middleware import HydrateMiddleware from reflex.model import Model @@ -492,7 +493,7 @@ async def test_dynamic_var_event(test_state: type[ATestState], token: str): payload={"value": 50}, ) ): - assert result.delta == {test_state.get_name(): {"int_val": 50}} + assert result.delta == {test_state.get_name(): {"int_val" + FIELD_MARKER: 50}} @pytest.mark.asyncio @@ -503,11 +504,11 @@ async def test_dynamic_var_event(test_state: type[ATestState], token: str): [ ( "make_friend", - {"plain_friends": ["Tommy", "another-fd"]}, + {"plain_friends" + FIELD_MARKER: ["Tommy", "another-fd"]}, ), ( "change_first_friend", - {"plain_friends": ["Jenny", "another-fd"]}, + {"plain_friends" + FIELD_MARKER: ["Jenny", "another-fd"]}, ), ], id="append then __setitem__", @@ -516,11 +517,11 @@ async def test_dynamic_var_event(test_state: type[ATestState], token: str): [ ( "unfriend_first_friend", - {"plain_friends": []}, + {"plain_friends" + FIELD_MARKER: []}, ), ( "make_friend", - {"plain_friends": ["another-fd"]}, + {"plain_friends" + FIELD_MARKER: ["another-fd"]}, ), ], id="delitem then append", @@ -529,19 +530,19 @@ async def test_dynamic_var_event(test_state: type[ATestState], token: str): [ ( "make_friends_with_colleagues", - {"plain_friends": ["Tommy", "Peter", "Jimmy"]}, + {"plain_friends" + FIELD_MARKER: ["Tommy", "Peter", "Jimmy"]}, ), ( "remove_tommy", - {"plain_friends": ["Peter", "Jimmy"]}, + {"plain_friends" + FIELD_MARKER: ["Peter", "Jimmy"]}, ), ( "remove_last_friend", - {"plain_friends": ["Peter"]}, + {"plain_friends" + FIELD_MARKER: ["Peter"]}, ), ( "unfriend_all_friends", - {"plain_friends": []}, + {"plain_friends" + FIELD_MARKER: []}, ), ], id="extend, remove, pop, clear", @@ -550,15 +551,20 @@ async def test_dynamic_var_event(test_state: type[ATestState], token: str): [ ( "add_jimmy_to_second_group", - {"friends_in_nested_list": [["Tommy"], ["Jenny", "Jimmy"]]}, + { + "friends_in_nested_list" + FIELD_MARKER: [ + ["Tommy"], + ["Jenny", "Jimmy"], + ] + }, ), ( "remove_first_person_from_first_group", - {"friends_in_nested_list": [[], ["Jenny", "Jimmy"]]}, + {"friends_in_nested_list" + FIELD_MARKER: [[], ["Jenny", "Jimmy"]]}, ), ( "remove_first_group", - {"friends_in_nested_list": [["Jenny", "Jimmy"]]}, + {"friends_in_nested_list" + FIELD_MARKER: [["Jenny", "Jimmy"]]}, ), ], id="nested list", @@ -567,15 +573,15 @@ async def test_dynamic_var_event(test_state: type[ATestState], token: str): [ ( "add_jimmy_to_tommy_friends", - {"friends_in_dict": {"Tommy": ["Jenny", "Jimmy"]}}, + {"friends_in_dict" + FIELD_MARKER: {"Tommy": ["Jenny", "Jimmy"]}}, ), ( "remove_jenny_from_tommy", - {"friends_in_dict": {"Tommy": ["Jimmy"]}}, + {"friends_in_dict" + FIELD_MARKER: {"Tommy": ["Jimmy"]}}, ), ( "tommy_has_no_fds", - {"friends_in_dict": {"Tommy": []}}, + {"friends_in_dict" + FIELD_MARKER: {"Tommy": []}}, ), ], id="list in dict", @@ -617,15 +623,15 @@ async def test_list_mutation_detection__plain_list( [ ( "add_age", - {"details": {"name": "Tommy", "age": 20}}, + {"details" + FIELD_MARKER: {"name": "Tommy", "age": 20}}, ), ( "change_name", - {"details": {"name": "Jenny", "age": 20}}, + {"details" + FIELD_MARKER: {"name": "Jenny", "age": 20}}, ), ( "remove_last_detail", - {"details": {"name": "Jenny"}}, + {"details" + FIELD_MARKER: {"name": "Jenny"}}, ), ], id="update then __setitem__", @@ -634,11 +640,11 @@ async def test_list_mutation_detection__plain_list( [ ( "clear_details", - {"details": {}}, + {"details" + FIELD_MARKER: {}}, ), ( "add_age", - {"details": {"age": 20}}, + {"details" + FIELD_MARKER: {"age": 20}}, ), ], id="delitem then update", @@ -647,15 +653,15 @@ async def test_list_mutation_detection__plain_list( [ ( "add_age", - {"details": {"name": "Tommy", "age": 20}}, + {"details" + FIELD_MARKER: {"name": "Tommy", "age": 20}}, ), ( "remove_name", - {"details": {"age": 20}}, + {"details" + FIELD_MARKER: {"age": 20}}, ), ( "pop_out_age", - {"details": {}}, + {"details" + FIELD_MARKER: {}}, ), ], id="add, remove, pop", @@ -664,12 +670,12 @@ async def test_list_mutation_detection__plain_list( [ ( "remove_home_address", - {"address": [{}, {"work": "work address"}]}, + {"address" + FIELD_MARKER: [{}, {"work": "work address"}]}, ), ( "add_street_to_home_address", { - "address": [ + "address" + FIELD_MARKER: [ {"street": "street address"}, {"work": "work address"}, ] @@ -683,7 +689,7 @@ async def test_list_mutation_detection__plain_list( ( "change_friend_name", { - "friend_in_nested_dict": { + "friend_in_nested_dict" + FIELD_MARKER: { "name": "Nikhil", "friend": {"name": "Tommy"}, } @@ -692,7 +698,7 @@ async def test_list_mutation_detection__plain_list( ( "add_friend_age", { - "friend_in_nested_dict": { + "friend_in_nested_dict" + FIELD_MARKER: { "name": "Nikhil", "friend": {"name": "Tommy", "age": 30}, } @@ -700,7 +706,7 @@ async def test_list_mutation_detection__plain_list( ), ( "remove_friend", - {"friend_in_nested_dict": {"name": "Nikhil"}}, + {"friend_in_nested_dict" + FIELD_MARKER: {"name": "Nikhil"}}, ), ], id="nested dict", @@ -743,7 +749,7 @@ async def test_dict_mutation_detection__plain_list( FileUploadState, { FileUploadState.get_full_name(): { - "img_list": ["image1.jpg", "image2.jpg"] + "img_list" + FIELD_MARKER: ["image1.jpg", "image2.jpg"] } }, ), @@ -751,7 +757,7 @@ async def test_dict_mutation_detection__plain_list( ChildFileUploadState, { ChildFileUploadState.get_full_name(): { - "img_list": ["image1.jpg", "image2.jpg"] + "img_list" + FIELD_MARKER: ["image1.jpg", "image2.jpg"] } }, ), @@ -759,7 +765,7 @@ async def test_dict_mutation_detection__plain_list( GrandChildFileUploadState, { GrandChildFileUploadState.get_full_name(): { - "img_list": ["image1.jpg", "image2.jpg"] + "img_list" + FIELD_MARKER: ["image1.jpg", "image2.jpg"] } }, ), @@ -830,7 +836,7 @@ def getlist(key: str): current_state = await app.state_manager.get_state(_substate_key(token, state)) state_dict = current_state.dict()[state.get_full_name()] - assert state_dict["img_list"] == [ + assert state_dict["img_list" + FIELD_MARKER] == [ "image1.jpg", "image2.jpg", ] @@ -1106,10 +1112,10 @@ def _dynamic_state_event(name, val, **kwargs): assert update == StateUpdate( delta={ state.get_name(): { - arg_name: exp_val, - f"comp_{arg_name}": exp_val, - constants.CompileVars.IS_HYDRATED: False, - "router": exp_router, + arg_name + FIELD_MARKER: exp_val, + f"comp_{arg_name}" + FIELD_MARKER: exp_val, + constants.CompileVars.IS_HYDRATED + FIELD_MARKER: False, + "router" + FIELD_MARKER: exp_router, } }, events=[ @@ -1149,7 +1155,7 @@ def _dynamic_state_event(name, val, **kwargs): assert on_load_update == StateUpdate( delta={ state.get_name(): { - "loaded": exp_index + 1, + "loaded" + FIELD_MARKER: exp_index + 1, }, }, events=[], @@ -1170,7 +1176,7 @@ def _dynamic_state_event(name, val, **kwargs): assert on_set_is_hydrated_update == StateUpdate( delta={ state.get_name(): { - "is_hydrated": True, + "is_hydrated" + FIELD_MARKER: True, }, }, events=[], @@ -1191,7 +1197,7 @@ def _dynamic_state_event(name, val, **kwargs): assert update == StateUpdate( delta={ state.get_name(): { - "counter": exp_index + 1, + "counter" + FIELD_MARKER: exp_index + 1, } }, events=[], diff --git a/tests/units/test_base.py b/tests/units/test_base.py index 515f70aa361..32c32be7936 100644 --- a/tests/units/test_base.py +++ b/tests/units/test_base.py @@ -24,7 +24,7 @@ def test_get_fields(child): Args: child: A child class. """ - assert child.get_fields().keys() == {"num", "key"} + assert child.__fields__.keys() == {"num", "key"} def test_set(child): @@ -71,7 +71,7 @@ def test_complex_get_fields(complex_child): Args: complex_child: A child class. """ - assert complex_child.get_fields().keys() == {"num", "key", "name", "age", "active"} + assert complex_child.__fields__.keys() == {"num", "key", "name", "age", "active"} def test_complex_set(complex_child): diff --git a/tests/units/test_state.py b/tests/units/test_state.py index 50c3716104c..0c1204f1392 100644 --- a/tests/units/test_state.py +++ b/tests/units/test_state.py @@ -27,6 +27,7 @@ from reflex.app import App from reflex.base import Base from reflex.constants import CompileVars, RouteVar, SocketEvent +from reflex.constants.state import FIELD_MARKER from reflex.event import Event, EventHandler from reflex.istate.manager import ( LockExpiredError, @@ -295,7 +296,7 @@ def test_base_class_vars(test_state): continue prop = getattr(cls, field) assert isinstance(prop, Var) - assert prop._js_expr.split(".")[-1] == field + assert prop._js_expr.split(".")[-1] == field + FIELD_MARKER assert cls.num1._var_type is int assert cls.num2._var_type is float @@ -310,8 +311,8 @@ def test_computed_class_var(test_state): """ cls = type(test_state) vars = [(prop._js_expr, prop._var_type) for prop in cls.computed_vars.values()] - assert ("sum", float) in vars - assert ("upper", str) in vars + assert ("sum" + FIELD_MARKER, float) in vars + assert ("upper" + FIELD_MARKER, str) in vars def test_class_vars(test_state): @@ -404,10 +405,12 @@ def test_dict(test_state: TestState): } test_state_dict = test_state.dict() assert set(test_state_dict) == substates - assert set(test_state_dict[test_state.get_name()]) == set(test_state.vars) - assert set(test_state.dict(include_computed=False)[test_state.get_name()]) == set( - test_state.base_vars - ) + assert set(test_state_dict[test_state.get_name()]) == { + var + FIELD_MARKER for var in test_state.vars + } + assert set(test_state.dict(include_computed=False)[test_state.get_name()]) == { + var + FIELD_MARKER for var in test_state.base_vars + } def test_default_setters(test_state): @@ -424,27 +427,31 @@ def test_default_setters(test_state): def test_class_indexing_with_vars(): """Test that we can index into a state var with another var.""" prop = TestState.array[TestState.num1] # pyright: ignore [reportCallIssue, reportArgumentType] - assert str(prop) == f"{TestState.get_name()}.array.at({TestState.get_name()}.num1)" + assert ( + str(prop) + == f"{TestState.get_name()}.array{FIELD_MARKER}.at({TestState.get_name()}.num1{FIELD_MARKER})" + ) prop = TestState.mapping["a"][TestState.num1] # pyright: ignore [reportCallIssue, reportArgumentType] assert ( str(prop) - == f'{TestState.get_name()}.mapping["a"].at({TestState.get_name()}.num1)' + == f'{TestState.get_name()}.mapping{FIELD_MARKER}["a"].at({TestState.get_name()}.num1{FIELD_MARKER})' ) prop = TestState.mapping[TestState.map_key] assert ( - str(prop) == f"{TestState.get_name()}.mapping[{TestState.get_name()}.map_key]" + str(prop) + == f"{TestState.get_name()}.mapping{FIELD_MARKER}[{TestState.get_name()}.map_key{FIELD_MARKER}]" ) def test_class_attributes(): """Test that we can get class attributes.""" prop = TestState.obj.prop1 - assert str(prop) == f'{TestState.get_name()}.obj["prop1"]' + assert str(prop) == f'{TestState.get_name()}.obj{FIELD_MARKER}["prop1"]' prop = TestState.complex[1].prop1 - assert str(prop) == f'{TestState.get_name()}.complex[1]["prop1"]' + assert str(prop) == f'{TestState.get_name()}.complex{FIELD_MARKER}[1]["prop1"]' def test_get_parent_state(): @@ -547,7 +554,9 @@ def test_set_class_var(): """Test setting the var of a class.""" with pytest.raises(AttributeError): TestState.num3 # pyright: ignore [reportAttributeAccessIssue] - TestState._set_var(Var(_js_expr="num3", _var_type=int)._var_set_state(TestState)) + TestState._set_var( + "num3", Var(_js_expr="num3", _var_type=int)._var_set_state(TestState) + ) var = TestState.num3 # pyright: ignore [reportAttributeAccessIssue] assert var._js_expr == TestState.get_full_name() + ".num3" assert var._var_type is int @@ -787,8 +796,11 @@ 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}, - GrandchildState3.get_full_name(): {"computed": ""}, + TestState.get_full_name(): { + "num1" + FIELD_MARKER: 69, + "sum" + FIELD_MARKER: 72.14, + }, + GrandchildState3.get_full_name(): {"computed" + FIELD_MARKER: ""}, } assert update.events == [] @@ -815,8 +827,11 @@ async def test_process_event_substate(test_state, child_state, grandchild_state) assert child_state.count == 24 assert update.delta == { # TestState.get_full_name(): {"sum": 3.14, "upper": ""}, - ChildState.get_full_name(): {"value": "HI", "count": 24}, - GrandchildState3.get_full_name(): {"computed": ""}, + ChildState.get_full_name(): { + "value" + FIELD_MARKER: "HI", + "count" + FIELD_MARKER: 24, + }, + GrandchildState3.get_full_name(): {"computed" + FIELD_MARKER: ""}, } test_state._clean() @@ -831,8 +846,8 @@ async def test_process_event_substate(test_state, child_state, grandchild_state) assert grandchild_state.value2 == "new" assert update.delta == { # TestState.get_full_name(): {"sum": 3.14, "upper": ""}, - GrandchildState.get_full_name(): {"value2": "new"}, - GrandchildState3.get_full_name(): {"computed": ""}, + GrandchildState.get_full_name(): {"value2" + FIELD_MARKER: "new"}, + GrandchildState3.get_full_name(): {"computed" + FIELD_MARKER: ""}, } @@ -856,7 +871,7 @@ async def test_process_event_generator(): else: assert gen_state.value == count assert update.delta == { - GenState.get_full_name(): {"value": count}, + GenState.get_full_name(): {"value" + FIELD_MARKER: count}, } assert not update.final @@ -1055,14 +1070,14 @@ def test_interdependent_state_initial_dict() -> None: s = InterdependentState() state_name = s.get_name() d = s.dict(initial=True)[state_name] - d.pop("router") + d.pop("router" + FIELD_MARKER) assert d == { - "x": 0, - "v1": 0, - "v1x2": 0, - "v2x2": 2, - "v1x2x2": 0, - "v3x2": 2, + "x" + FIELD_MARKER: 0, + "v1" + FIELD_MARKER: 0, + "v1x2" + FIELD_MARKER: 0, + "v2x2" + FIELD_MARKER: 2, + "v1x2x2" + FIELD_MARKER: 0, + "v3x2" + FIELD_MARKER: 2, } @@ -1076,7 +1091,7 @@ def test_not_dirty_computed_var_from_var( """ interdependent_state.x = 5 assert interdependent_state.get_delta() == { - interdependent_state.get_full_name(): {"x": 5}, + interdependent_state.get_full_name(): {"x" + FIELD_MARKER: 5}, } @@ -1091,7 +1106,11 @@ def test_dirty_computed_var_from_var(interdependent_state: InterdependentState) """ interdependent_state.v1 = 1 assert interdependent_state.get_delta() == { - interdependent_state.get_full_name(): {"v1": 1, "v1x2": 2, "v1x2x2": 4}, + interdependent_state.get_full_name(): { + "v1" + FIELD_MARKER: 1, + "v1x2" + FIELD_MARKER: 2, + "v1x2x2" + FIELD_MARKER: 4, + }, } @@ -1107,7 +1126,10 @@ def test_dirty_computed_var_from_backend_var( # assert InterdependentState._v3._backend is True interdependent_state._v2 = 2 assert interdependent_state.get_delta() == { - interdependent_state.get_full_name(): {"v2x2": 4, "v3x2": 4}, + interdependent_state.get_full_name(): { + "v2x2" + FIELD_MARKER: 4, + "v3x2" + FIELD_MARKER: 4, + }, } @@ -1245,9 +1267,9 @@ def comp_v(self) -> int: return self.v cs = ComputedState() - assert cs.dict()[cs.get_full_name()]["v"] == 0 + assert cs.dict()[cs.get_full_name()]["v" + FIELD_MARKER] == 0 assert comp_v_calls == 1 - assert cs.dict()[cs.get_full_name()]["comp_v"] == 0 + assert cs.dict()[cs.get_full_name()]["comp_v" + FIELD_MARKER] == 0 assert comp_v_calls == 1 assert cs.comp_v == 0 assert comp_v_calls == 1 @@ -1277,23 +1299,36 @@ def comp_v(self) -> int: cs = ComputedState() assert cs.dirty_vars == set() - assert cs.get_delta() == {cs.get_name(): {"no_cache_v": 0, "dep_v": 0}} + assert cs.get_delta() == { + cs.get_name(): {"no_cache_v" + FIELD_MARKER: 0, "dep_v" + FIELD_MARKER: 0} + } cs._clean() assert cs.dirty_vars == set() - assert cs.get_delta() == {cs.get_name(): {"no_cache_v": 0, "dep_v": 0}} + assert cs.get_delta() == { + cs.get_name(): {"no_cache_v" + FIELD_MARKER: 0, "dep_v" + FIELD_MARKER: 0} + } cs._clean() assert cs.dirty_vars == set() cs.v = 1 assert cs.dirty_vars == {"v", "comp_v", "dep_v", "no_cache_v"} assert cs.get_delta() == { - cs.get_name(): {"v": 1, "no_cache_v": 1, "dep_v": 1, "comp_v": 1} + cs.get_name(): { + "v" + FIELD_MARKER: 1, + "no_cache_v" + FIELD_MARKER: 1, + "dep_v" + FIELD_MARKER: 1, + "comp_v" + FIELD_MARKER: 1, + } } cs._clean() assert cs.dirty_vars == set() - assert cs.get_delta() == {cs.get_name(): {"no_cache_v": 1, "dep_v": 1}} + assert cs.get_delta() == { + cs.get_name(): {"no_cache_v" + FIELD_MARKER: 1, "dep_v" + FIELD_MARKER: 1} + } cs._clean() assert cs.dirty_vars == set() - assert cs.get_delta() == {cs.get_name(): {"no_cache_v": 1, "dep_v": 1}} + assert cs.get_delta() == { + cs.get_name(): {"no_cache_v" + FIELD_MARKER: 1, "dep_v" + FIELD_MARKER: 1} + } cs._clean() assert cs.dirty_vars == set() @@ -1322,22 +1357,22 @@ def dep_v(self) -> int: dict1 = json.loads(json_dumps(ps.dict())) assert dict1[ps.get_full_name()] == { - "no_cache_v": 1, - "router": formatted_router, + "no_cache_v" + FIELD_MARKER: 1, + "router" + FIELD_MARKER: formatted_router, } - assert dict1[cs.get_full_name()] == {"dep_v": 2} + assert dict1[cs.get_full_name()] == {"dep_v" + FIELD_MARKER: 2} dict2 = json.loads(json_dumps(ps.dict())) assert dict2[ps.get_full_name()] == { - "no_cache_v": 3, - "router": formatted_router, + "no_cache_v" + FIELD_MARKER: 3, + "router" + FIELD_MARKER: formatted_router, } - assert dict2[cs.get_full_name()] == {"dep_v": 4} + assert dict2[cs.get_full_name()] == {"dep_v" + FIELD_MARKER: 4} dict3 = json.loads(json_dumps(ps.dict())) assert dict3[ps.get_full_name()] == { - "no_cache_v": 5, - "router": formatted_router, + "no_cache_v" + FIELD_MARKER: 5, + "router" + FIELD_MARKER: formatted_router, } - assert dict3[cs.get_full_name()] == {"dep_v": 6} + assert dict3[cs.get_full_name()] == {"dep_v" + FIELD_MARKER: 6} assert counter == 6 @@ -2044,12 +2079,12 @@ async def test_state_proxy( assert mcall.args[0] == str(SocketEvent.EVENT) assert mcall.args[1] == StateUpdate( delta={ - TestState.get_full_name(): {"router": router_data}, + TestState.get_full_name(): {"router" + FIELD_MARKER: router_data}, grandchild_state.get_full_name(): { - "value2": "42", + "value2" + FIELD_MARKER: "42", }, GrandchildState3.get_full_name(): { - "computed": "", + "computed" + FIELD_MARKER: "", }, } ) @@ -2212,11 +2247,11 @@ async def test_background_task_no_block(mock_app: rx.App, token: str): assert update == StateUpdate( delta={ BackgroundTaskState.get_full_name(): { - "order": [ + "order" + FIELD_MARKER: [ "background_task:start", "other", ], - "computed_order": [ + "computed_order" + FIELD_MARKER: [ "background_task:start", "other", ], @@ -2248,14 +2283,16 @@ async def test_background_task_no_block(mock_app: rx.App, token: str): first_ws_message = emit_mock.mock_calls[0].args[1] # pyright: ignore [reportFunctionMemberAccess] assert ( - first_ws_message.delta[BackgroundTaskState.get_full_name()].pop("router") + first_ws_message.delta[BackgroundTaskState.get_full_name()].pop( + "router" + FIELD_MARKER + ) is not None ) assert first_ws_message == StateUpdate( delta={ BackgroundTaskState.get_full_name(): { - "order": ["background_task:start"], - "computed_order": ["background_task:start"], + "order" + FIELD_MARKER: ["background_task:start"], + "computed_order" + FIELD_MARKER: ["background_task:start"], } }, events=[], @@ -2265,7 +2302,7 @@ async def test_background_task_no_block(mock_app: rx.App, token: str): assert call.args[1] == StateUpdate( delta={ BackgroundTaskState.get_full_name(): { - "computed_order": ["background_task:start"], + "computed_order" + FIELD_MARKER: ["background_task:start"], } }, events=[], @@ -2274,9 +2311,9 @@ async def test_background_task_no_block(mock_app: rx.App, token: str): assert emit_mock.mock_calls[-2].args[1] == StateUpdate( # pyright: ignore [reportFunctionMemberAccess] delta={ BackgroundTaskState.get_full_name(): { - "order": exp_order, - "computed_order": exp_order, - "dict_list": {}, + "order" + FIELD_MARKER: exp_order, + "computed_order" + FIELD_MARKER: exp_order, + "dict_list" + FIELD_MARKER: {}, } }, events=[], @@ -2285,7 +2322,7 @@ async def test_background_task_no_block(mock_app: rx.App, token: str): assert emit_mock.mock_calls[-1].args[1] == StateUpdate( # pyright: ignore [reportFunctionMemberAccess] delta={ BackgroundTaskState.get_full_name(): { - "computed_order": exp_order, + "computed_order" + FIELD_MARKER: exp_order, }, }, events=[], @@ -2666,13 +2703,15 @@ class MutableContainsBase(BaseState): items: list[Foo] = [Foo()] dict_val = MutableContainsBase().dict() - assert isinstance(dict_val[MutableContainsBase.get_full_name()]["items"][0], Foo) + assert isinstance( + dict_val[MutableContainsBase.get_full_name()]["items" + FIELD_MARKER][0], Foo + ) val = json_dumps(dict_val) f_items = '[{"tags": ["123", "456"]}]' f_formatted_router = str(formatted_router).replace("'", '"') assert ( val - == f'{{"{MutableContainsBase.get_full_name()}": {{"items": {f_items}, "router": {f_formatted_router}}}}}' + == f'{{"{MutableContainsBase.get_full_name()}": {{"items{FIELD_MARKER}": {f_items}, "router{FIELD_MARKER}": {f_formatted_router}}}}}' ) @@ -2838,7 +2877,9 @@ def exp_is_hydrated(state: BaseState, is_hydrated: bool = True) -> dict[str, Any Returns: dict similar to that returned by `State.get_delta` with IS_HYDRATED: is_hydrated """ - return {state.get_full_name(): {CompileVars.IS_HYDRATED: is_hydrated}} + return { + state.get_full_name(): {CompileVars.IS_HYDRATED + FIELD_MARKER: is_hydrated} + } class OnLoadState(State): @@ -2927,13 +2968,13 @@ async def test_preprocess( assert isinstance(update, StateUpdate) updates.append(update) assert len(updates) == 1 - assert updates[0].delta[State.get_name()].pop("router") is not None + assert updates[0].delta[State.get_name()].pop("router" + FIELD_MARKER) is not None assert updates[0].delta == exp_is_hydrated(state, False) events = updates[0].events assert len(events) == 2 async for update in state._process(events[0]): - assert update.delta == {test_state.get_full_name(): {"num": 1}} + assert update.delta == {test_state.get_full_name(): {"num" + FIELD_MARKER: 1}} async for update in state._process(events[1]): assert update.delta == exp_is_hydrated(state) @@ -2977,15 +3018,15 @@ async def test_preprocess_multiple_load_events( assert isinstance(update, StateUpdate) updates.append(update) assert len(updates) == 1 - assert updates[0].delta[State.get_name()].pop("router") is not None + assert updates[0].delta[State.get_name()].pop("router" + FIELD_MARKER) is not None assert updates[0].delta == exp_is_hydrated(state, False) events = updates[0].events assert len(events) == 3 async for update in state._process(events[0]): - assert update.delta == {OnLoadState.get_full_name(): {"num": 1}} + assert update.delta == {OnLoadState.get_full_name(): {"num" + FIELD_MARKER: 1}} async for update in state._process(events[1]): - assert update.delta == {OnLoadState.get_full_name(): {"num": 2}} + assert update.delta == {OnLoadState.get_full_name(): {"num" + FIELD_MARKER: 2}} async for update in state._process(events[2]): assert update.delta == exp_is_hydrated(state) @@ -3061,10 +3102,10 @@ async def test_get_state(mock_app: rx.App, token: str): assert test_state.get_delta() == { GrandchildState.get_full_name(): { - "value2": "set_value", + "value2" + FIELD_MARKER: "set_value", }, GrandchildState3.get_full_name(): { - "computed": "", + "computed" + FIELD_MARKER: "", }, } @@ -3098,13 +3139,13 @@ async def test_get_state(mock_app: rx.App, token: str): assert new_test_state.get_delta() == { ChildState2.get_full_name(): { - "value": "set_c2_value", + "value" + FIELD_MARKER: "set_c2_value", }, GrandchildState2.get_full_name(): { - "cached": "set_c2_value", + "cached" + FIELD_MARKER: "set_c2_value", }, GrandchildState3.get_full_name(): { - "computed": "", + "computed" + FIELD_MARKER: "", }, } @@ -3746,8 +3787,8 @@ class GetValueState(rx.State): assert state.dict() == { state.get_full_name(): { - "foo": "FOO", - "bar": "BAR", + "foo" + FIELD_MARKER: "FOO", + "bar" + FIELD_MARKER: "BAR", } } assert state.get_delta() == {} @@ -3756,13 +3797,13 @@ class GetValueState(rx.State): assert state.dict() == { state.get_full_name(): { - "foo": "FOO", - "bar": "foo", + "foo" + FIELD_MARKER: "FOO", + "bar" + FIELD_MARKER: "foo", } } assert state.get_delta() == { state.get_full_name(): { - "bar": "foo", + "bar" + FIELD_MARKER: "foo", } } @@ -3883,7 +3924,9 @@ async def test_upcast_event_handler_arg(handler, payload): """ state = UpcastState() async for update in state._process_event(handler, state, payload): - assert update.delta == {UpcastState.get_full_name(): {"passed": True}} + assert update.delta == { + UpcastState.get_full_name(): {"passed" + FIELD_MARKER: True} + } @pytest.mark.asyncio diff --git a/tests/units/test_state_tree.py b/tests/units/test_state_tree.py index edf533d1b43..d3afa05fa0e 100644 --- a/tests/units/test_state_tree.py +++ b/tests/units/test_state_tree.py @@ -6,6 +6,7 @@ import pytest_asyncio import reflex as rx +from reflex.constants.state import FIELD_MARKER from reflex.state import BaseState, StateManager, StateManagerRedis, _substate_key @@ -194,8 +195,8 @@ def sub_e_a_a_a_d_var(self) -> int: ALWAYS_COMPUTED_VARS = { - TreeD.get_full_name(): {"d_var": 1}, - SubE_A_A_A_A.get_full_name(): {"sub_e_a_a_a_a_var": 1}, + TreeD.get_full_name(): {"d_var" + FIELD_MARKER: 1}, + SubE_A_A_A_A.get_full_name(): {"sub_e_a_a_a_a_var" + FIELD_MARKER: 1}, } ALWAYS_COMPUTED_DICT_KEYS = [ diff --git a/tests/units/test_var.py b/tests/units/test_var.py index 27a4bd985d4..b138cfe83bd 100644 --- a/tests/units/test_var.py +++ b/tests/units/test_var.py @@ -12,6 +12,7 @@ import reflex as rx from reflex.base import Base from reflex.constants.base import REFLEX_VAR_CLOSING_TAG, REFLEX_VAR_OPENING_TAG +from reflex.constants.state import FIELD_MARKER from reflex.environment import PerformanceMode from reflex.state import BaseState from reflex.utils.exceptions import ( @@ -248,30 +249,6 @@ def test_default_value(prop: Var, expected): assert get_default_value_for_type(prop._var_type) == expected -@pytest.mark.parametrize( - ("prop", "expected"), - zip( - test_vars, - [ - "set_prop1", - "set_key", - "state.set_value", - "state.set_local", - "set_local2", - ], - strict=True, - ), -) -def test_get_setter(prop: Var, expected): - """Test that the name of the setter function of a var is correct. - - Args: - prop: The var to test. - expected: The expected name of the setter function. - """ - assert prop._get_setter_name() == expected - - @pytest.mark.parametrize( ("value", "expected"), [ @@ -851,28 +828,28 @@ def test_computed_var_with_annotation_error(request, fixture): [ ( "StateWithInitialComputedVar", - "var_with_initial_value", + "var_with_initial_value" + FIELD_MARKER, "Initial value", "Runtime value", False, ), ( "ChildWithInitialComputedVar", - "var_with_initial_value_child", + "var_with_initial_value_child" + FIELD_MARKER, "Initial value", "Runtime value", False, ), ( "StateWithRuntimeOnlyVar", - "var_raises_at_runtime", + "var_raises_at_runtime" + FIELD_MARKER, None, None, True, ), ( "ChildWithRuntimeOnlyVar", - "var_raises_at_runtime_child", + "var_raises_at_runtime_child" + FIELD_MARKER, "Initial value", None, True, @@ -1798,15 +1775,15 @@ def test_invalid_var_operations(operand1_var: Var, operand2_var, operators: list (LiteralVar.create({"foo": "bar"}), '({ ["foo"] : "bar" })'), ( LiteralVar.create(ATestState.value), - f"{ATestState.get_full_name()}.value", + f"{ATestState.get_full_name()}.value" + FIELD_MARKER, ), ( LiteralVar.create(f"{ATestState.value} string"), - f'({ATestState.get_full_name()}.value+" string")', + f'({ATestState.get_full_name()}.value{FIELD_MARKER}+" string")', ), ( LiteralVar.create(ATestState.dict_val), - f"{ATestState.get_full_name()}.dict_val", + f"{ATestState.get_full_name()}.dict_val" + FIELD_MARKER, ), ], ) @@ -1858,7 +1835,8 @@ class TestState(BaseState): email: Email = Email("test@reflex.dev") assert ( - str(TestState.optional_email) == f"{TestState.get_full_name()}.optional_email" + str(TestState.optional_email) + == f"{TestState.get_full_name()}.optional_email" + FIELD_MARKER ) my_state = TestState() assert my_state.optional_email is None diff --git a/tests/units/utils/test_format.py b/tests/units/utils/test_format.py index a47ce3fb713..d8b425e845e 100644 --- a/tests/units/utils/test_format.py +++ b/tests/units/utils/test_format.py @@ -8,6 +8,7 @@ import pytest from reflex.components.tags.tag import Tag +from reflex.constants.state import FIELD_MARKER from reflex.event import ( EventChain, EventHandler, @@ -328,8 +329,8 @@ def test_format_route(route: str, format_case: bool, expected: bool): ], LiteralVar.create("yellow"), '(() => { switch (JSON.stringify(state__state.value)) {case JSON.stringify(1): return ("red"); break;case JSON.stringify(2): case JSON.stringify(3): ' - f'return ("blue"); break;case JSON.stringify({TestState.get_full_name()}.mapping): return ' - f'({TestState.get_full_name()}.num1); break;case JSON.stringify(({TestState.get_full_name()}.map_key+"-key")): return ("return-key");' + f'return ("blue"); break;case JSON.stringify({TestState.get_full_name()}.mapping{FIELD_MARKER}): return ' + f'({TestState.get_full_name()}.num1{FIELD_MARKER}); break;case JSON.stringify(({TestState.get_full_name()}.map_key{FIELD_MARKER}+"-key")): return ("return-key");' ' break;default: return ("yellow"); break;};})()', ) ], @@ -588,44 +589,44 @@ def test_format_query_params(input, output): TestState(_reflex_internal_init=True).dict(), # pyright: ignore [reportCallIssue] { TestState.get_full_name(): { - "array": [1, 2, 3.14], - "complex": { + "array" + FIELD_MARKER: [1, 2, 3.14], + "complex" + FIELD_MARKER: { 1: {"prop1": 42, "prop2": "hello"}, 2: {"prop1": 42, "prop2": "hello"}, }, - "dt": "1989-11-09 18:53:00+01:00", - "fig": serialize_figure(go.Figure()), - "key": "", - "map_key": "a", - "mapping": {"a": [1, 2, 3], "b": [4, 5, 6]}, - "num1": 0, - "num2": 3.14, - "obj": {"prop1": 42, "prop2": "hello"}, - "sum": 3.14, - "upper": "", - "router": formatted_router, - "asynctest": 0, + "dt" + FIELD_MARKER: "1989-11-09 18:53:00+01:00", + "fig" + FIELD_MARKER: serialize_figure(go.Figure()), + "key" + FIELD_MARKER: "", + "map_key" + FIELD_MARKER: "a", + "mapping" + FIELD_MARKER: {"a": [1, 2, 3], "b": [4, 5, 6]}, + "num1" + FIELD_MARKER: 0, + "num2" + FIELD_MARKER: 3.14, + "obj" + FIELD_MARKER: {"prop1": 42, "prop2": "hello"}, + "sum" + FIELD_MARKER: 3.14, + "upper" + FIELD_MARKER: "", + "router" + FIELD_MARKER: formatted_router, + "asynctest" + FIELD_MARKER: 0, }, ChildState.get_full_name(): { - "count": 23, - "value": "", + "count" + FIELD_MARKER: 23, + "value" + FIELD_MARKER: "", }, - ChildState2.get_full_name(): {"value": ""}, - ChildState3.get_full_name(): {"value": ""}, - GrandchildState.get_full_name(): {"value2": ""}, - GrandchildState2.get_full_name(): {"cached": ""}, - GrandchildState3.get_full_name(): {"computed": ""}, + ChildState2.get_full_name(): {"value" + FIELD_MARKER: ""}, + ChildState3.get_full_name(): {"value" + FIELD_MARKER: ""}, + GrandchildState.get_full_name(): {"value2" + FIELD_MARKER: ""}, + GrandchildState2.get_full_name(): {"cached" + FIELD_MARKER: ""}, + GrandchildState3.get_full_name(): {"computed" + FIELD_MARKER: ""}, }, ), ( DateTimeState(_reflex_internal_init=True).dict(), # pyright: ignore [reportCallIssue] { DateTimeState.get_full_name(): { - "d": "1989-11-09", - "dt": "1989-11-09 18:53:00+01:00", - "t": "18:53:00+01:00", - "td": "11 days, 0:11:00", - "router": formatted_router, + "d" + FIELD_MARKER: "1989-11-09", + "dt" + FIELD_MARKER: "1989-11-09 18:53:00+01:00", + "t" + FIELD_MARKER: "18:53:00+01:00", + "td" + FIELD_MARKER: "11 days, 0:11:00", + "router" + FIELD_MARKER: formatted_router, }, }, ),