Pārlūkot izejas kodu

add immutable var class (#3607)

* add immutable var class

* add missing docs

* override _replace

* fix type imports

* override create as well

* remove deprecated properties and arguments

* remove unused code in ImmutableVar

* fix namespace issue

* no Self in 3.8
Khaleel Al-Adhami 11 mēneši atpakaļ
vecāks
revīzija
046c0f9760

+ 2 - 0
reflex/experimental/__init__.py

@@ -8,6 +8,7 @@ from reflex.components.sonner.toast import toast as toast
 
 from ..utils.console import warn
 from . import hooks as hooks
+from . import vars as vars
 from .assets import asset as asset
 from .client_state import ClientStateVar as ClientStateVar
 from .layout import layout as layout
@@ -42,6 +43,7 @@ _x = ExperimentalNamespace(
     asset=asset,
     client_state=ClientStateVar.create,
     hooks=hooks,
+    vars=vars,
     layout=layout,
     progress=progress,
     PropsBase=PropsBase,

+ 3 - 0
reflex/experimental/vars/__init__.py

@@ -0,0 +1,3 @@
+"""Experimental Immutable-Based Var System."""
+
+from .base import ImmutableVar as ImmutableVar

+ 158 - 0
reflex/experimental/vars/base.py

@@ -0,0 +1,158 @@
+"""Collection of base classes."""
+
+from __future__ import annotations
+
+import dataclasses
+import sys
+from typing import Any, Optional, Type
+
+from reflex.utils import serializers, types
+from reflex.utils.exceptions import VarTypeError
+from reflex.vars import Var, VarData, _extract_var_data
+
+
+@dataclasses.dataclass(
+    eq=False,
+    frozen=True,
+    **{"slots": True} if sys.version_info >= (3, 10) else {},
+)
+class ImmutableVar(Var):
+    """Base class for immutable vars."""
+
+    # The name of the var.
+    _var_name: str = dataclasses.field()
+
+    # The type of the var.
+    _var_type: Type = dataclasses.field(default=Any)
+
+    # Extra metadata associated with the Var
+    _var_data: Optional[VarData] = dataclasses.field(default=None)
+
+    @property
+    def _var_is_local(self) -> bool:
+        """Whether this is a local javascript variable.
+
+        Returns:
+            False
+        """
+        return False
+
+    @property
+    def _var_is_string(self) -> bool:
+        """Whether the var is a string literal.
+
+        Returns:
+            False
+        """
+        return False
+
+    @property
+    def _var_full_name_needs_state_prefix(self) -> bool:
+        """Whether the full name of the var needs a _var_state prefix.
+
+        Returns:
+            False
+        """
+        return False
+
+    def _replace(self, merge_var_data=None, **kwargs: Any):
+        """Make a copy of this Var with updated fields.
+
+        Args:
+            merge_var_data: VarData to merge into the existing VarData.
+            **kwargs: Var fields to update.
+
+        Returns:
+            A new ImmutableVar with the updated fields overwriting the corresponding fields in this Var.
+
+        Raises:
+            TypeError: If _var_is_local, _var_is_string, or _var_full_name_needs_state_prefix is not None.
+        """
+        if kwargs.get("_var_is_local", False) is not False:
+            raise TypeError(
+                "The _var_is_local argument is not supported for ImmutableVar."
+            )
+
+        if kwargs.get("_var_is_string", False) is not False:
+            raise TypeError(
+                "The _var_is_string argument is not supported for ImmutableVar."
+            )
+
+        if kwargs.get("_var_full_name_needs_state_prefix", False) is not False:
+            raise TypeError(
+                "The _var_full_name_needs_state_prefix argument is not supported for ImmutableVar."
+            )
+
+        field_values = dict(
+            _var_name=kwargs.pop("_var_name", self._var_name),
+            _var_type=kwargs.pop("_var_type", self._var_type),
+            _var_data=VarData.merge(
+                kwargs.get("_var_data", self._var_data), merge_var_data
+            ),
+        )
+        return ImmutableVar(**field_values)
+
+    @classmethod
+    def create(
+        cls,
+        value: Any,
+        _var_is_local: bool | None = None,
+        _var_is_string: bool | None = None,
+        _var_data: VarData | None = None,
+    ) -> Var | None:
+        """Create a var from a value.
+
+        Args:
+            value: The value to create the var from.
+            _var_is_local: Whether the var is local. Deprecated.
+            _var_is_string: Whether the var is a string literal. Deprecated.
+            _var_data: Additional hooks and imports associated with the Var.
+
+        Returns:
+            The var.
+
+        Raises:
+            VarTypeError: If the value is JSON-unserializable.
+            TypeError: If _var_is_local or _var_is_string is not None.
+        """
+        if _var_is_local is not None:
+            raise TypeError(
+                "The _var_is_local argument is not supported for ImmutableVar."
+            )
+
+        if _var_is_string is not None:
+            raise TypeError(
+                "The _var_is_string argument is not supported for ImmutableVar."
+            )
+
+        from reflex.utils import format
+
+        # Check for none values.
+        if value is None:
+            return None
+
+        # If the value is already a var, do nothing.
+        if isinstance(value, Var):
+            return value
+
+        # Try to pull the imports and hooks from contained values.
+        if not isinstance(value, str):
+            _var_data = VarData.merge(*_extract_var_data(value), _var_data)
+
+        # Try to serialize the value.
+        type_ = type(value)
+        if type_ in types.JSONType:
+            name = value
+        else:
+            name, _serialized_type = serializers.serialize(value, get_type=True)
+        if name is None:
+            raise VarTypeError(
+                f"No JSON serializer found for var {value} of type {type_}."
+            )
+        name = name if isinstance(name, str) else format.json_dumps(name)
+
+        return ImmutableVar(
+            _var_name=name,
+            _var_type=type_,
+            _var_data=_var_data,
+        )