diff --git a/reflex/experimental/__init__.py b/reflex/experimental/__init__.py index 164790fe5..c0fe2a8e0 100644 --- a/reflex/experimental/__init__.py +++ b/reflex/experimental/__init__.py @@ -11,6 +11,7 @@ from ..utils.console import warn from . import hooks as hooks from .assets import asset as asset from .client_state import ClientStateVar as ClientStateVar +from .hybrid_property import hybrid_property as hybrid_property from .layout import layout as layout from .misc import run_in_thread as run_in_thread @@ -69,4 +70,5 @@ _x = ExperimentalNamespace( PropsBase=PropsBase, run_in_thread=run_in_thread, code_block=code_block, + hybrid_property=hybrid_property, ) diff --git a/reflex/experimental/hybrid_property.py b/reflex/experimental/hybrid_property.py new file mode 100644 index 000000000..df2ea0eef --- /dev/null +++ b/reflex/experimental/hybrid_property.py @@ -0,0 +1,49 @@ +"""hybrid_property decorator which functions like a normal python property but additionally allows (class-level) access from the frontend. You can use the same code for frontend and backend, or implement 2 different methods.""" + +from typing import Any, Callable + +from reflex.utils.types import Self, override +from reflex.vars.base import Var + + +class HybridProperty(property): + """A hybrid property that can also be used in frontend/as var.""" + + # The optional var function for the property. + _var: Callable[[Any], Var] | None = None + + @override + def __get__(self, instance: Any, owner: type | None = None, /) -> Any: + """Get the value of the property. If the property is not bound to an instance return a frontend Var. + + Args: + instance: The instance of the class accessing this property. + owner: The class that this descriptor is attached to. + + Returns: + The value of the property or a frontend Var. + """ + if instance is not None: + return super().__get__(instance, owner) + if self._var is not None: + # Call custom var function if set + return self._var(owner) + else: + # Call the property getter function if no custom var function is set + assert self.fget is not None + return self.fget(owner) + + def var(self, func: Callable[[Any], Var]) -> Self: + """Set the (optional) var function for the property. + + Args: + func: The var function to set. + + Returns: + The property instance with the var function set. + """ + self._var = func + return self + + +hybrid_property = HybridProperty diff --git a/reflex/vars/__init__.py b/reflex/vars/__init__.py index 28f4bf691..1a4cebe19 100644 --- a/reflex/vars/__init__.py +++ b/reflex/vars/__init__.py @@ -7,7 +7,6 @@ from .base import VarData as VarData from .base import field as field from .base import get_unique_variable_name as get_unique_variable_name from .base import get_uuid_string_var as get_uuid_string_var -from .base import hybrid_property as hybrid_property from .base import var_operation as var_operation from .base import var_operation_return as var_operation_return from .function import FunctionStringVar as FunctionStringVar diff --git a/reflex/vars/base.py b/reflex/vars/base.py index dd4343dfe..b06e7b7c9 100644 --- a/reflex/vars/base.py +++ b/reflex/vars/base.py @@ -2949,49 +2949,3 @@ def field(value: T) -> Field[T]: The Field. """ return value # type: ignore - - -VAR_CALLABLE = Callable[[Any], Var] - - -class HybridProperty(property): - """A hybrid property that can also be used in frontend/as var.""" - - # The optional var function for the property. - _var: VAR_CALLABLE | None = None - - @override - def __get__(self, instance: Any, owner: type | None = None, /) -> Any: - """Get the value of the property. If the property is not bound to an instance return a frontend Var. - - Args: - instance: The instance of the class accessing this property. - owner: The class that this descriptor is attached to. - - Returns: - The value of the property or a frontend Var. - """ - if instance is not None: - return super().__get__(instance, owner) - if self._var is not None: - # Call custom var function if set - return self._var(owner) - else: - # Call the property getter function if no custom var function is set - assert self.fget is not None - return self.fget(owner) - - def var(self, func: VAR_CALLABLE) -> Self: - """Set the (optional) var function for the property. - - Args: - func: The var function to set. - - Returns: - The property instance with the var function set. - """ - self._var = func - return self - - -hybrid_property = HybridProperty diff --git a/tests/integration/test_hybrid_properties.py b/tests/integration/test_hybrid_properties.py index 85fd6037f..371ddbe18 100644 --- a/tests/integration/test_hybrid_properties.py +++ b/tests/integration/test_hybrid_properties.py @@ -14,7 +14,8 @@ from reflex.testing import DEFAULT_TIMEOUT, AppHarness, WebDriver def HybridProperties(): """Test app for hybrid properties.""" import reflex as rx - from reflex.vars import Var, hybrid_property + from reflex.experimental import hybrid_property + from reflex.vars import Var class State(rx.State): first_name: str = "John" @@ -71,7 +72,7 @@ def HybridProperties(): rx.text(f"has_last_name: {State.has_last_name}", id="has_last_name"), rx.input( value=State.last_name, - on_change=State.set_last_name, # type: ignore + on_change=State.setvar("last_name"), id="set_last_name", ), ),