diff --git a/reflex/__init__.py b/reflex/__init__.py index b03bcb8a9..1399da4c4 100644 --- a/reflex/__init__.py +++ b/reflex/__init__.py @@ -330,7 +330,7 @@ _MAPPING: dict = { "ComponentState", "State", ], - "istate": ["get_state"], + "istate.wrappers": ["get_state"], "style": ["Style", "toggle_color_mode"], "utils.imports": ["ImportVar"], "utils.serializers": ["serializer"], diff --git a/reflex/__init__.pyi b/reflex/__init__.pyi index 444cfbd75..6978d3c12 100644 --- a/reflex/__init__.pyi +++ b/reflex/__init__.pyi @@ -174,7 +174,7 @@ from .event import stop_propagation as stop_propagation from .event import upload_files as upload_files from .event import window_alert as window_alert from .experimental import _x as _x -from .istate import get_state as get_state +from .istate.wrappers import get_state as get_state from .middleware import Middleware as Middleware from .middleware import middleware as middleware from .model import Model as Model diff --git a/reflex/istate/__init__.py b/reflex/istate/__init__.py index d10d841c7..d71d038f8 100644 --- a/reflex/istate/__init__.py +++ b/reflex/istate/__init__.py @@ -1,3 +1 @@ """This module will provide interfaces for the state.""" - -from .wrappers import get_state diff --git a/reflex/istate/proxy.py b/reflex/istate/proxy.py new file mode 100644 index 000000000..8d6051cf2 --- /dev/null +++ b/reflex/istate/proxy.py @@ -0,0 +1,33 @@ +"""A module to hold state proxy classes.""" + +from typing import Any + +from reflex.state import StateProxy + + +class ReadOnlyStateProxy(StateProxy): + """A read-only proxy for a state.""" + + def __setattr__(self, name: str, value: Any) -> None: + """Prevent setting attributes on the state for read-only proxy. + + Args: + name: The attribute name. + value: The attribute value. + + Raises: + NotImplementedError: Always raised when trying to set an attribute on proxied state. + """ + if name.startswith("_self_"): + # Special case attributes of the proxy itself, not applied to the wrapped object. + super().__setattr__(name, value) + return + raise NotImplementedError("This is a read-only state proxy.") + + def mark_dirty(self): + """Mark the state as dirty. + + Raises: + NotImplementedError: Always raised when trying to mark the proxied state as dirty. + """ + raise NotImplementedError("This is a read-only state proxy.") diff --git a/reflex/istate/wrappers.py b/reflex/istate/wrappers.py index bbc8382b0..7f010eb9e 100644 --- a/reflex/istate/wrappers.py +++ b/reflex/istate/wrappers.py @@ -2,10 +2,15 @@ from typing import Any -from reflex.state import _split_substate_key, _substate_key, get_state_manager +from reflex.istate.proxy import ReadOnlyStateProxy +from reflex.state import ( + _split_substate_key, + _substate_key, + get_state_manager, +) -async def get_state(token, state_cls: Any | None = None): +async def get_state(token, state_cls: Any | None = None) -> ReadOnlyStateProxy: """Get the instance of a state for a token. Args: @@ -13,7 +18,7 @@ async def get_state(token, state_cls: Any | None = None): state_cls: The class of the state. Returns: - The state instance. + A read-only proxy of the state instance. """ mng = get_state_manager() if state_cls is not None: @@ -22,4 +27,5 @@ async def get_state(token, state_cls: Any | None = None): root_state = await mng.get_state(token) _, state_path = _split_substate_key(token) state_cls = root_state.get_class_substate(tuple(state_path.split("."))) - return await root_state.get_state(state_cls) + instance = await root_state.get_state(state_cls) + return ReadOnlyStateProxy(instance)