improve object var symantics (#4290)
* improve object var symantics * add case for serializers * check against serializer with to = dict * add tests * fix typing issues * remove default value * older version of python doesn't have assert type * add base to rx field cases * get it from typing_extension
This commit is contained in:
parent
0ed7c5d969
commit
b5d1e03de1
@ -78,7 +78,7 @@ def serializer(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Apply type transformation if requested
|
# Apply type transformation if requested
|
||||||
if to is not None:
|
if to is not None or ((to := type_hints.get("return")) is not None):
|
||||||
SERIALIZER_TYPES[type_] = to
|
SERIALIZER_TYPES[type_] = to
|
||||||
get_serializer_type.cache_clear()
|
get_serializer_type.cache_clear()
|
||||||
|
|
||||||
@ -189,16 +189,37 @@ def get_serializer_type(type_: Type) -> Optional[Type]:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def has_serializer(type_: Type) -> bool:
|
def has_serializer(type_: Type, into_type: Type | None = None) -> bool:
|
||||||
"""Check if there is a serializer for the type.
|
"""Check if there is a serializer for the type.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
type_: The type to check.
|
type_: The type to check.
|
||||||
|
into_type: The type to serialize into.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Whether there is a serializer for the type.
|
Whether there is a serializer for the type.
|
||||||
"""
|
"""
|
||||||
return get_serializer(type_) is not None
|
serializer_for_type = get_serializer(type_)
|
||||||
|
return serializer_for_type is not None and (
|
||||||
|
into_type is None or get_serializer_type(type_) == into_type
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def can_serialize(type_: Type, into_type: Type | None = None) -> bool:
|
||||||
|
"""Check if there is a serializer for the type.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
type_: The type to check.
|
||||||
|
into_type: The type to serialize into.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Whether there is a serializer for the type.
|
||||||
|
"""
|
||||||
|
return has_serializer(type_, into_type) or (
|
||||||
|
isinstance(type_, type)
|
||||||
|
and dataclasses.is_dataclass(type_)
|
||||||
|
and (into_type is None or into_type is dict)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@serializer(to=str)
|
@serializer(to=str)
|
||||||
@ -214,7 +235,7 @@ def serialize_type(value: type) -> str:
|
|||||||
return value.__name__
|
return value.__name__
|
||||||
|
|
||||||
|
|
||||||
@serializer
|
@serializer(to=dict)
|
||||||
def serialize_base(value: Base) -> dict:
|
def serialize_base(value: Base) -> dict:
|
||||||
"""Serialize a Base instance.
|
"""Serialize a Base instance.
|
||||||
|
|
||||||
|
@ -75,7 +75,6 @@ from reflex.utils.types import (
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from reflex.state import BaseState
|
from reflex.state import BaseState
|
||||||
|
|
||||||
from .function import FunctionVar
|
|
||||||
from .number import (
|
from .number import (
|
||||||
BooleanVar,
|
BooleanVar,
|
||||||
NumberVar,
|
NumberVar,
|
||||||
@ -279,6 +278,24 @@ def _decode_var_immutable(value: str) -> tuple[VarData | None, str]:
|
|||||||
return VarData.merge(*var_datas) if var_datas else None, value
|
return VarData.merge(*var_datas) if var_datas else None, value
|
||||||
|
|
||||||
|
|
||||||
|
def can_use_in_object_var(cls: GenericType) -> bool:
|
||||||
|
"""Check if the class can be used in an ObjectVar.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cls: The class to check.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Whether the class can be used in an ObjectVar.
|
||||||
|
"""
|
||||||
|
if types.is_union(cls):
|
||||||
|
return all(can_use_in_object_var(t) for t in types.get_args(cls))
|
||||||
|
return (
|
||||||
|
inspect.isclass(cls)
|
||||||
|
and not issubclass(cls, Var)
|
||||||
|
and serializers.can_serialize(cls, dict)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass(
|
@dataclasses.dataclass(
|
||||||
eq=False,
|
eq=False,
|
||||||
frozen=True,
|
frozen=True,
|
||||||
@ -565,36 +582,33 @@ class Var(Generic[VAR_TYPE]):
|
|||||||
# Encode the _var_data into the formatted output for tracking purposes.
|
# Encode the _var_data into the formatted output for tracking purposes.
|
||||||
return f"{constants.REFLEX_VAR_OPENING_TAG}{hashed_var}{constants.REFLEX_VAR_CLOSING_TAG}{self._js_expr}"
|
return f"{constants.REFLEX_VAR_OPENING_TAG}{hashed_var}{constants.REFLEX_VAR_CLOSING_TAG}{self._js_expr}"
|
||||||
|
|
||||||
@overload
|
|
||||||
def to(self, output: Type[StringVar]) -> StringVar: ...
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def to(self, output: Type[str]) -> StringVar: ...
|
def to(self, output: Type[str]) -> StringVar: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def to(self, output: Type[BooleanVar]) -> BooleanVar: ...
|
def to(self, output: Type[bool]) -> BooleanVar: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def to(
|
def to(self, output: type[int] | type[float]) -> NumberVar: ...
|
||||||
self, output: Type[NumberVar], var_type: type[int] | type[float] = float
|
|
||||||
) -> NumberVar: ...
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def to(
|
def to(
|
||||||
self,
|
self,
|
||||||
output: Type[ArrayVar],
|
output: type[list] | type[tuple] | type[set],
|
||||||
var_type: type[list] | type[tuple] | type[set] = list,
|
|
||||||
) -> ArrayVar: ...
|
) -> ArrayVar: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def to(
|
def to(
|
||||||
self, output: Type[ObjectVar], var_type: types.GenericType = dict
|
self, output: Type[ObjectVar], var_type: Type[VAR_INSIDE]
|
||||||
) -> ObjectVar: ...
|
) -> ObjectVar[VAR_INSIDE]: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def to(
|
def to(
|
||||||
self, output: Type[FunctionVar], var_type: Type[Callable] = Callable
|
self, output: Type[ObjectVar], var_type: None = None
|
||||||
) -> FunctionVar: ...
|
) -> ObjectVar[VAR_TYPE]: ...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def to(self, output: VAR_SUBCLASS, var_type: None = None) -> VAR_SUBCLASS: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def to(
|
def to(
|
||||||
@ -630,21 +644,19 @@ class Var(Generic[VAR_TYPE]):
|
|||||||
return get_to_operation(NoneVar).create(self) # type: ignore
|
return get_to_operation(NoneVar).create(self) # type: ignore
|
||||||
|
|
||||||
# Handle fixed_output_type being Base or a dataclass.
|
# Handle fixed_output_type being Base or a dataclass.
|
||||||
try:
|
if can_use_in_object_var(fixed_output_type):
|
||||||
if issubclass(fixed_output_type, Base):
|
|
||||||
return self.to(ObjectVar, output)
|
|
||||||
except TypeError:
|
|
||||||
pass
|
|
||||||
if dataclasses.is_dataclass(fixed_output_type) and not issubclass(
|
|
||||||
fixed_output_type, Var
|
|
||||||
):
|
|
||||||
return self.to(ObjectVar, output)
|
return self.to(ObjectVar, output)
|
||||||
|
|
||||||
if inspect.isclass(output):
|
if inspect.isclass(output):
|
||||||
for var_subclass in _var_subclasses[::-1]:
|
for var_subclass in _var_subclasses[::-1]:
|
||||||
if issubclass(output, var_subclass.var_subclass):
|
if issubclass(output, var_subclass.var_subclass):
|
||||||
|
current_var_type = self._var_type
|
||||||
|
if current_var_type is Any:
|
||||||
|
new_var_type = var_type
|
||||||
|
else:
|
||||||
|
new_var_type = var_type or current_var_type
|
||||||
to_operation_return = var_subclass.to_var_subclass.create(
|
to_operation_return = var_subclass.to_var_subclass.create(
|
||||||
value=self, _var_type=var_type
|
value=self, _var_type=new_var_type
|
||||||
)
|
)
|
||||||
return to_operation_return # type: ignore
|
return to_operation_return # type: ignore
|
||||||
|
|
||||||
@ -707,11 +719,7 @@ class Var(Generic[VAR_TYPE]):
|
|||||||
):
|
):
|
||||||
return self.to(NumberVar, self._var_type)
|
return self.to(NumberVar, self._var_type)
|
||||||
|
|
||||||
if all(
|
if can_use_in_object_var(var_type):
|
||||||
inspect.isclass(t)
|
|
||||||
and (issubclass(t, Base) or dataclasses.is_dataclass(t))
|
|
||||||
for t in inner_types
|
|
||||||
):
|
|
||||||
return self.to(ObjectVar, self._var_type)
|
return self.to(ObjectVar, self._var_type)
|
||||||
|
|
||||||
return self
|
return self
|
||||||
@ -730,13 +738,9 @@ class Var(Generic[VAR_TYPE]):
|
|||||||
if issubclass(fixed_type, var_subclass.python_types):
|
if issubclass(fixed_type, var_subclass.python_types):
|
||||||
return self.to(var_subclass.var_subclass, self._var_type)
|
return self.to(var_subclass.var_subclass, self._var_type)
|
||||||
|
|
||||||
try:
|
if can_use_in_object_var(fixed_type):
|
||||||
if issubclass(fixed_type, Base):
|
|
||||||
return self.to(ObjectVar, self._var_type)
|
|
||||||
except TypeError:
|
|
||||||
pass
|
|
||||||
if dataclasses.is_dataclass(fixed_type):
|
|
||||||
return self.to(ObjectVar, self._var_type)
|
return self.to(ObjectVar, self._var_type)
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def get_default_value(self) -> Any:
|
def get_default_value(self) -> Any:
|
||||||
@ -1181,6 +1185,9 @@ class Var(Generic[VAR_TYPE]):
|
|||||||
|
|
||||||
OUTPUT = TypeVar("OUTPUT", bound=Var)
|
OUTPUT = TypeVar("OUTPUT", bound=Var)
|
||||||
|
|
||||||
|
VAR_SUBCLASS = TypeVar("VAR_SUBCLASS", bound=Var)
|
||||||
|
VAR_INSIDE = TypeVar("VAR_INSIDE")
|
||||||
|
|
||||||
|
|
||||||
class ToOperation:
|
class ToOperation:
|
||||||
"""A var operation that converts a var to another type."""
|
"""A var operation that converts a var to another type."""
|
||||||
@ -2888,6 +2895,8 @@ def dispatch(
|
|||||||
|
|
||||||
V = TypeVar("V")
|
V = TypeVar("V")
|
||||||
|
|
||||||
|
BASE_TYPE = TypeVar("BASE_TYPE", bound=Base)
|
||||||
|
|
||||||
|
|
||||||
class Field(Generic[T]):
|
class Field(Generic[T]):
|
||||||
"""Shadow class for Var to allow for type hinting in the IDE."""
|
"""Shadow class for Var to allow for type hinting in the IDE."""
|
||||||
@ -2924,6 +2933,11 @@ class Field(Generic[T]):
|
|||||||
self: Field[Dict[str, V]], instance: None, owner
|
self: Field[Dict[str, V]], instance: None, owner
|
||||||
) -> ObjectVar[Dict[str, V]]: ...
|
) -> ObjectVar[Dict[str, V]]: ...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def __get__(
|
||||||
|
self: Field[BASE_TYPE], instance: None, owner
|
||||||
|
) -> ObjectVar[BASE_TYPE]: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def __get__(self, instance: None, owner) -> Var[T]: ...
|
def __get__(self, instance: None, owner) -> Var[T]: ...
|
||||||
|
|
||||||
|
@ -1116,7 +1116,9 @@ U = TypeVar("U")
|
|||||||
|
|
||||||
|
|
||||||
@var_operation
|
@var_operation
|
||||||
def ternary_operation(condition: BooleanVar, if_true: Var[T], if_false: Var[U]):
|
def ternary_operation(
|
||||||
|
condition: BooleanVar, if_true: Var[T], if_false: Var[U]
|
||||||
|
) -> CustomVarOperationReturn[Union[T, U]]:
|
||||||
"""Create a ternary operation.
|
"""Create a ternary operation.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -36,7 +36,7 @@ from .base import (
|
|||||||
from .number import BooleanVar, NumberVar, raise_unsupported_operand_types
|
from .number import BooleanVar, NumberVar, raise_unsupported_operand_types
|
||||||
from .sequence import ArrayVar, StringVar
|
from .sequence import ArrayVar, StringVar
|
||||||
|
|
||||||
OBJECT_TYPE = TypeVar("OBJECT_TYPE", bound=Dict)
|
OBJECT_TYPE = TypeVar("OBJECT_TYPE")
|
||||||
|
|
||||||
KEY_TYPE = TypeVar("KEY_TYPE")
|
KEY_TYPE = TypeVar("KEY_TYPE")
|
||||||
VALUE_TYPE = TypeVar("VALUE_TYPE")
|
VALUE_TYPE = TypeVar("VALUE_TYPE")
|
||||||
@ -59,7 +59,7 @@ class ObjectVar(Var[OBJECT_TYPE], python_types=dict):
|
|||||||
|
|
||||||
@overload
|
@overload
|
||||||
def _value_type(
|
def _value_type(
|
||||||
self: ObjectVar[Dict[KEY_TYPE, VALUE_TYPE]],
|
self: ObjectVar[Dict[Any, VALUE_TYPE]],
|
||||||
) -> Type[VALUE_TYPE]: ...
|
) -> Type[VALUE_TYPE]: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
@ -87,7 +87,7 @@ class ObjectVar(Var[OBJECT_TYPE], python_types=dict):
|
|||||||
|
|
||||||
@overload
|
@overload
|
||||||
def values(
|
def values(
|
||||||
self: ObjectVar[Dict[KEY_TYPE, VALUE_TYPE]],
|
self: ObjectVar[Dict[Any, VALUE_TYPE]],
|
||||||
) -> ArrayVar[List[VALUE_TYPE]]: ...
|
) -> ArrayVar[List[VALUE_TYPE]]: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
@ -103,7 +103,7 @@ class ObjectVar(Var[OBJECT_TYPE], python_types=dict):
|
|||||||
|
|
||||||
@overload
|
@overload
|
||||||
def entries(
|
def entries(
|
||||||
self: ObjectVar[Dict[KEY_TYPE, VALUE_TYPE]],
|
self: ObjectVar[Dict[Any, VALUE_TYPE]],
|
||||||
) -> ArrayVar[List[Tuple[str, VALUE_TYPE]]]: ...
|
) -> ArrayVar[List[Tuple[str, VALUE_TYPE]]]: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
@ -133,47 +133,47 @@ class ObjectVar(Var[OBJECT_TYPE], python_types=dict):
|
|||||||
# NoReturn is used here to catch when key value is Any
|
# NoReturn is used here to catch when key value is Any
|
||||||
@overload
|
@overload
|
||||||
def __getitem__(
|
def __getitem__(
|
||||||
self: ObjectVar[Dict[KEY_TYPE, NoReturn]],
|
self: ObjectVar[Dict[Any, NoReturn]],
|
||||||
key: Var | Any,
|
key: Var | Any,
|
||||||
) -> Var: ...
|
) -> Var: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def __getitem__(
|
def __getitem__(
|
||||||
self: (
|
self: (
|
||||||
ObjectVar[Dict[KEY_TYPE, int]]
|
ObjectVar[Dict[Any, int]]
|
||||||
| ObjectVar[Dict[KEY_TYPE, float]]
|
| ObjectVar[Dict[Any, float]]
|
||||||
| ObjectVar[Dict[KEY_TYPE, int | float]]
|
| ObjectVar[Dict[Any, int | float]]
|
||||||
),
|
),
|
||||||
key: Var | Any,
|
key: Var | Any,
|
||||||
) -> NumberVar: ...
|
) -> NumberVar: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def __getitem__(
|
def __getitem__(
|
||||||
self: ObjectVar[Dict[KEY_TYPE, str]],
|
self: ObjectVar[Dict[Any, str]],
|
||||||
key: Var | Any,
|
key: Var | Any,
|
||||||
) -> StringVar: ...
|
) -> StringVar: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def __getitem__(
|
def __getitem__(
|
||||||
self: ObjectVar[Dict[KEY_TYPE, list[ARRAY_INNER_TYPE]]],
|
self: ObjectVar[Dict[Any, list[ARRAY_INNER_TYPE]]],
|
||||||
key: Var | Any,
|
key: Var | Any,
|
||||||
) -> ArrayVar[list[ARRAY_INNER_TYPE]]: ...
|
) -> ArrayVar[list[ARRAY_INNER_TYPE]]: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def __getitem__(
|
def __getitem__(
|
||||||
self: ObjectVar[Dict[KEY_TYPE, set[ARRAY_INNER_TYPE]]],
|
self: ObjectVar[Dict[Any, set[ARRAY_INNER_TYPE]]],
|
||||||
key: Var | Any,
|
key: Var | Any,
|
||||||
) -> ArrayVar[set[ARRAY_INNER_TYPE]]: ...
|
) -> ArrayVar[set[ARRAY_INNER_TYPE]]: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def __getitem__(
|
def __getitem__(
|
||||||
self: ObjectVar[Dict[KEY_TYPE, tuple[ARRAY_INNER_TYPE, ...]]],
|
self: ObjectVar[Dict[Any, tuple[ARRAY_INNER_TYPE, ...]]],
|
||||||
key: Var | Any,
|
key: Var | Any,
|
||||||
) -> ArrayVar[tuple[ARRAY_INNER_TYPE, ...]]: ...
|
) -> ArrayVar[tuple[ARRAY_INNER_TYPE, ...]]: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def __getitem__(
|
def __getitem__(
|
||||||
self: ObjectVar[Dict[KEY_TYPE, dict[OTHER_KEY_TYPE, VALUE_TYPE]]],
|
self: ObjectVar[Dict[Any, dict[OTHER_KEY_TYPE, VALUE_TYPE]]],
|
||||||
key: Var | Any,
|
key: Var | Any,
|
||||||
) -> ObjectVar[dict[OTHER_KEY_TYPE, VALUE_TYPE]]: ...
|
) -> ObjectVar[dict[OTHER_KEY_TYPE, VALUE_TYPE]]: ...
|
||||||
|
|
||||||
@ -195,50 +195,56 @@ class ObjectVar(Var[OBJECT_TYPE], python_types=dict):
|
|||||||
# NoReturn is used here to catch when key value is Any
|
# NoReturn is used here to catch when key value is Any
|
||||||
@overload
|
@overload
|
||||||
def __getattr__(
|
def __getattr__(
|
||||||
self: ObjectVar[Dict[KEY_TYPE, NoReturn]],
|
self: ObjectVar[Dict[Any, NoReturn]],
|
||||||
name: str,
|
name: str,
|
||||||
) -> Var: ...
|
) -> Var: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def __getattr__(
|
def __getattr__(
|
||||||
self: (
|
self: (
|
||||||
ObjectVar[Dict[KEY_TYPE, int]]
|
ObjectVar[Dict[Any, int]]
|
||||||
| ObjectVar[Dict[KEY_TYPE, float]]
|
| ObjectVar[Dict[Any, float]]
|
||||||
| ObjectVar[Dict[KEY_TYPE, int | float]]
|
| ObjectVar[Dict[Any, int | float]]
|
||||||
),
|
),
|
||||||
name: str,
|
name: str,
|
||||||
) -> NumberVar: ...
|
) -> NumberVar: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def __getattr__(
|
def __getattr__(
|
||||||
self: ObjectVar[Dict[KEY_TYPE, str]],
|
self: ObjectVar[Dict[Any, str]],
|
||||||
name: str,
|
name: str,
|
||||||
) -> StringVar: ...
|
) -> StringVar: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def __getattr__(
|
def __getattr__(
|
||||||
self: ObjectVar[Dict[KEY_TYPE, list[ARRAY_INNER_TYPE]]],
|
self: ObjectVar[Dict[Any, list[ARRAY_INNER_TYPE]]],
|
||||||
name: str,
|
name: str,
|
||||||
) -> ArrayVar[list[ARRAY_INNER_TYPE]]: ...
|
) -> ArrayVar[list[ARRAY_INNER_TYPE]]: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def __getattr__(
|
def __getattr__(
|
||||||
self: ObjectVar[Dict[KEY_TYPE, set[ARRAY_INNER_TYPE]]],
|
self: ObjectVar[Dict[Any, set[ARRAY_INNER_TYPE]]],
|
||||||
name: str,
|
name: str,
|
||||||
) -> ArrayVar[set[ARRAY_INNER_TYPE]]: ...
|
) -> ArrayVar[set[ARRAY_INNER_TYPE]]: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def __getattr__(
|
def __getattr__(
|
||||||
self: ObjectVar[Dict[KEY_TYPE, tuple[ARRAY_INNER_TYPE, ...]]],
|
self: ObjectVar[Dict[Any, tuple[ARRAY_INNER_TYPE, ...]]],
|
||||||
name: str,
|
name: str,
|
||||||
) -> ArrayVar[tuple[ARRAY_INNER_TYPE, ...]]: ...
|
) -> ArrayVar[tuple[ARRAY_INNER_TYPE, ...]]: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def __getattr__(
|
def __getattr__(
|
||||||
self: ObjectVar[Dict[KEY_TYPE, dict[OTHER_KEY_TYPE, VALUE_TYPE]]],
|
self: ObjectVar[Dict[Any, dict[OTHER_KEY_TYPE, VALUE_TYPE]]],
|
||||||
name: str,
|
name: str,
|
||||||
) -> ObjectVar[dict[OTHER_KEY_TYPE, VALUE_TYPE]]: ...
|
) -> ObjectVar[dict[OTHER_KEY_TYPE, VALUE_TYPE]]: ...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def __getattr__(
|
||||||
|
self: ObjectVar,
|
||||||
|
name: str,
|
||||||
|
) -> ObjectItemOperation: ...
|
||||||
|
|
||||||
def __getattr__(self, name) -> Var:
|
def __getattr__(self, name) -> Var:
|
||||||
"""Get an attribute of the var.
|
"""Get an attribute of the var.
|
||||||
|
|
||||||
@ -377,8 +383,8 @@ class LiteralObjectVar(CachedVarOperation, ObjectVar[OBJECT_TYPE], LiteralVar):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def create(
|
def create(
|
||||||
cls,
|
cls,
|
||||||
_var_value: OBJECT_TYPE,
|
_var_value: dict,
|
||||||
_var_type: GenericType | None = None,
|
_var_type: Type[OBJECT_TYPE] | None = None,
|
||||||
_var_data: VarData | None = None,
|
_var_data: VarData | None = None,
|
||||||
) -> LiteralObjectVar[OBJECT_TYPE]:
|
) -> LiteralObjectVar[OBJECT_TYPE]:
|
||||||
"""Create the literal object var.
|
"""Create the literal object var.
|
||||||
|
@ -853,31 +853,31 @@ class ArrayVar(Var[ARRAY_VAR_TYPE], python_types=(list, tuple, set)):
|
|||||||
@overload
|
@overload
|
||||||
def __getitem__(
|
def __getitem__(
|
||||||
self: (
|
self: (
|
||||||
ArrayVar[Tuple[OTHER_TUPLE, int]]
|
ArrayVar[Tuple[Any, int]]
|
||||||
| ArrayVar[Tuple[OTHER_TUPLE, float]]
|
| ArrayVar[Tuple[Any, float]]
|
||||||
| ArrayVar[Tuple[OTHER_TUPLE, int | float]]
|
| ArrayVar[Tuple[Any, int | float]]
|
||||||
),
|
),
|
||||||
i: Literal[1, -1],
|
i: Literal[1, -1],
|
||||||
) -> NumberVar: ...
|
) -> NumberVar: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def __getitem__(
|
def __getitem__(
|
||||||
self: ArrayVar[Tuple[str, OTHER_TUPLE]], i: Literal[0, -2]
|
self: ArrayVar[Tuple[str, Any]], i: Literal[0, -2]
|
||||||
) -> StringVar: ...
|
) -> StringVar: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def __getitem__(
|
def __getitem__(
|
||||||
self: ArrayVar[Tuple[OTHER_TUPLE, str]], i: Literal[1, -1]
|
self: ArrayVar[Tuple[Any, str]], i: Literal[1, -1]
|
||||||
) -> StringVar: ...
|
) -> StringVar: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def __getitem__(
|
def __getitem__(
|
||||||
self: ArrayVar[Tuple[bool, OTHER_TUPLE]], i: Literal[0, -2]
|
self: ArrayVar[Tuple[bool, Any]], i: Literal[0, -2]
|
||||||
) -> BooleanVar: ...
|
) -> BooleanVar: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def __getitem__(
|
def __getitem__(
|
||||||
self: ArrayVar[Tuple[OTHER_TUPLE, bool]], i: Literal[1, -1]
|
self: ArrayVar[Tuple[Any, bool]], i: Literal[1, -1]
|
||||||
) -> BooleanVar: ...
|
) -> BooleanVar: ...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
|
@ -45,7 +45,7 @@ from reflex.testing import chdir
|
|||||||
from reflex.utils import format, prerequisites, types
|
from reflex.utils import format, prerequisites, types
|
||||||
from reflex.utils.exceptions import SetUndefinedStateVarError
|
from reflex.utils.exceptions import SetUndefinedStateVarError
|
||||||
from reflex.utils.format import json_dumps
|
from reflex.utils.format import json_dumps
|
||||||
from reflex.vars.base import ComputedVar, Var
|
from reflex.vars.base import Var, computed_var
|
||||||
from tests.units.states.mutation import MutableSQLAModel, MutableTestState
|
from tests.units.states.mutation import MutableSQLAModel, MutableTestState
|
||||||
|
|
||||||
from .states import GenState
|
from .states import GenState
|
||||||
@ -109,7 +109,7 @@ class TestState(BaseState):
|
|||||||
_backend: int = 0
|
_backend: int = 0
|
||||||
asynctest: int = 0
|
asynctest: int = 0
|
||||||
|
|
||||||
@ComputedVar
|
@computed_var
|
||||||
def sum(self) -> float:
|
def sum(self) -> float:
|
||||||
"""Dynamically sum the numbers.
|
"""Dynamically sum the numbers.
|
||||||
|
|
||||||
@ -118,7 +118,7 @@ class TestState(BaseState):
|
|||||||
"""
|
"""
|
||||||
return self.num1 + self.num2
|
return self.num1 + self.num2
|
||||||
|
|
||||||
@ComputedVar
|
@computed_var
|
||||||
def upper(self) -> str:
|
def upper(self) -> str:
|
||||||
"""Uppercase the key.
|
"""Uppercase the key.
|
||||||
|
|
||||||
@ -1124,7 +1124,7 @@ def test_child_state():
|
|||||||
v: int = 2
|
v: int = 2
|
||||||
|
|
||||||
class ChildState(MainState):
|
class ChildState(MainState):
|
||||||
@ComputedVar
|
@computed_var
|
||||||
def rendered_var(self):
|
def rendered_var(self):
|
||||||
return self.v
|
return self.v
|
||||||
|
|
||||||
@ -1143,7 +1143,7 @@ def test_conditional_computed_vars():
|
|||||||
t1: str = "a"
|
t1: str = "a"
|
||||||
t2: str = "b"
|
t2: str = "b"
|
||||||
|
|
||||||
@ComputedVar
|
@computed_var
|
||||||
def rendered_var(self) -> str:
|
def rendered_var(self) -> str:
|
||||||
if self.flag:
|
if self.flag:
|
||||||
return self.t1
|
return self.t1
|
||||||
@ -3095,12 +3095,12 @@ def test_potentially_dirty_substates():
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
class State(RxState):
|
class State(RxState):
|
||||||
@ComputedVar
|
@computed_var
|
||||||
def foo(self) -> str:
|
def foo(self) -> str:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
class C1(State):
|
class C1(State):
|
||||||
@ComputedVar
|
@computed_var
|
||||||
def bar(self) -> str:
|
def bar(self) -> str:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
102
tests/units/vars/test_object.py
Normal file
102
tests/units/vars/test_object.py
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import pytest
|
||||||
|
from typing_extensions import assert_type
|
||||||
|
|
||||||
|
import reflex as rx
|
||||||
|
from reflex.utils.types import GenericType
|
||||||
|
from reflex.vars.base import Var
|
||||||
|
from reflex.vars.object import LiteralObjectVar, ObjectVar
|
||||||
|
|
||||||
|
|
||||||
|
class Bare:
|
||||||
|
"""A bare class with a single attribute."""
|
||||||
|
|
||||||
|
quantity: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
@rx.serializer
|
||||||
|
def serialize_bare(obj: Bare) -> dict:
|
||||||
|
"""A serializer for the bare class.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
obj: The object to serialize.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A dictionary with the quantity attribute.
|
||||||
|
"""
|
||||||
|
return {"quantity": obj.quantity}
|
||||||
|
|
||||||
|
|
||||||
|
class Base(rx.Base):
|
||||||
|
"""A reflex base class with a single attribute."""
|
||||||
|
|
||||||
|
quantity: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
class ObjectState(rx.State):
|
||||||
|
"""A reflex state with bare and base objects."""
|
||||||
|
|
||||||
|
bare: rx.Field[Bare] = rx.field(Bare())
|
||||||
|
base: rx.Field[Base] = rx.field(Base())
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("type_", [Base, Bare])
|
||||||
|
def test_var_create(type_: GenericType) -> None:
|
||||||
|
my_object = type_()
|
||||||
|
var = Var.create(my_object)
|
||||||
|
assert var._var_type is type_
|
||||||
|
|
||||||
|
quantity = var.quantity
|
||||||
|
assert quantity._var_type is int
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("type_", [Base, Bare])
|
||||||
|
def test_literal_create(type_: GenericType) -> None:
|
||||||
|
my_object = type_()
|
||||||
|
var = LiteralObjectVar.create(my_object)
|
||||||
|
assert var._var_type is type_
|
||||||
|
|
||||||
|
quantity = var.quantity
|
||||||
|
assert quantity._var_type is int
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("type_", [Base, Bare])
|
||||||
|
def test_guess(type_: GenericType) -> None:
|
||||||
|
my_object = type_()
|
||||||
|
var = Var.create(my_object)
|
||||||
|
var = var.guess_type()
|
||||||
|
assert var._var_type is type_
|
||||||
|
|
||||||
|
quantity = var.quantity
|
||||||
|
assert quantity._var_type is int
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("type_", [Base, Bare])
|
||||||
|
def test_state(type_: GenericType) -> None:
|
||||||
|
attr_name = type_.__name__.lower()
|
||||||
|
var = getattr(ObjectState, attr_name)
|
||||||
|
assert var._var_type is type_
|
||||||
|
|
||||||
|
quantity = var.quantity
|
||||||
|
assert quantity._var_type is int
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("type_", [Base, Bare])
|
||||||
|
def test_state_to_operation(type_: GenericType) -> None:
|
||||||
|
attr_name = type_.__name__.lower()
|
||||||
|
original_var = getattr(ObjectState, attr_name)
|
||||||
|
|
||||||
|
var = original_var.to(ObjectVar, type_)
|
||||||
|
assert var._var_type is type_
|
||||||
|
|
||||||
|
var = original_var.to(ObjectVar)
|
||||||
|
assert var._var_type is type_
|
||||||
|
|
||||||
|
|
||||||
|
def test_typing() -> None:
|
||||||
|
# Bare
|
||||||
|
var = ObjectState.bare.to(ObjectVar)
|
||||||
|
_ = assert_type(var, ObjectVar[Bare])
|
||||||
|
|
||||||
|
# Base
|
||||||
|
var = ObjectState.base
|
||||||
|
_ = assert_type(var, ObjectVar[Base])
|
Loading…
Reference in New Issue
Block a user