add var_operation and move some operations to the new style (#3841)

* add var_operations and move some operations to the new style

* change bound style

* can't assume int anymore

* slice is not hashable (how did this work bef)

* convert to int explicitly

* move the rest of the operations to new style

* fix bool guess type

* forgot to precommit dangit

* add type ignore to bool for now
This commit is contained in:
Khaleel Al-Adhami 2024-09-03 11:39:05 -07:00 committed by GitHub
parent f3426456ad
commit c07a983f05
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 960 additions and 1700 deletions

View File

@ -9,7 +9,7 @@ from reflex.components.component import BaseComponent, Component, MemoizationLea
from reflex.components.tags import CondTag, Tag from reflex.components.tags import CondTag, Tag
from reflex.constants import Dirs from reflex.constants import Dirs
from reflex.ivars.base import ImmutableVar, LiteralVar from reflex.ivars.base import ImmutableVar, LiteralVar
from reflex.ivars.number import TernaryOperator from reflex.ivars.number import ternary_operation
from reflex.style import LIGHT_COLOR_MODE, resolved_color_mode from reflex.style import LIGHT_COLOR_MODE, resolved_color_mode
from reflex.utils.imports import ImportDict, ImportVar from reflex.utils.imports import ImportDict, ImportVar
from reflex.vars import Var, VarData from reflex.vars import Var, VarData
@ -163,11 +163,12 @@ def cond(condition: Any, c1: Any, c2: Any = None) -> Component | ImmutableVar:
c2 = create_var(c2) c2 = create_var(c2)
# Create the conditional var. # Create the conditional var.
return TernaryOperator.create( return ternary_operation(
condition=cond_var.to(bool), # type: ignore cond_var.bool()._replace( # type: ignore
if_true=c1, merge_var_data=VarData(imports=_IS_TRUE_IMPORT),
if_false=c2, ), # type: ignore
_var_data=VarData(imports=_IS_TRUE_IMPORT), c1,
c2,
) )

View File

@ -12,7 +12,6 @@ from .number import LiteralNumberVar as LiteralNumberVar
from .number import NumberVar as NumberVar from .number import NumberVar as NumberVar
from .object import LiteralObjectVar as LiteralObjectVar from .object import LiteralObjectVar as LiteralObjectVar
from .object import ObjectVar as ObjectVar from .object import ObjectVar as ObjectVar
from .sequence import ArrayJoinOperation as ArrayJoinOperation
from .sequence import ArrayVar as ArrayVar from .sequence import ArrayVar as ArrayVar
from .sequence import ConcatVarOperation as ConcatVarOperation from .sequence import ConcatVarOperation as ConcatVarOperation
from .sequence import LiteralArrayVar as LiteralArrayVar from .sequence import LiteralArrayVar as LiteralArrayVar

View File

@ -20,6 +20,7 @@ from typing import (
Generic, Generic,
List, List,
Literal, Literal,
NoReturn,
Optional, Optional,
Sequence, Sequence,
Set, Set,
@ -384,10 +385,18 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
return self.to(BooleanVar, output) return self.to(BooleanVar, output)
if issubclass(output, NumberVar): if issubclass(output, NumberVar):
if fixed_type is not None and not issubclass(fixed_type, (int, float)): if fixed_type is not None:
raise TypeError( if fixed_type is Union:
f"Unsupported type {var_type} for NumberVar. Must be int or float." inner_types = get_args(base_type)
) if not all(issubclass(t, (int, float)) for t in inner_types):
raise TypeError(
f"Unsupported type {var_type} for NumberVar. Must be int or float."
)
elif not issubclass(fixed_type, (int, float)):
raise TypeError(
f"Unsupported type {var_type} for NumberVar. Must be int or float."
)
return ToNumberVarOperation.create(self, var_type or float) return ToNumberVarOperation.create(self, var_type or float)
if issubclass(output, BooleanVar): if issubclass(output, BooleanVar):
@ -440,7 +449,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Raises: Raises:
TypeError: If the type is not supported for guessing. TypeError: If the type is not supported for guessing.
""" """
from .number import NumberVar from .number import BooleanVar, NumberVar
from .object import ObjectVar from .object import ObjectVar
from .sequence import ArrayVar, StringVar from .sequence import ArrayVar, StringVar
@ -454,11 +463,16 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
fixed_type = get_origin(var_type) or var_type fixed_type = get_origin(var_type) or var_type
if fixed_type is Union: if fixed_type is Union:
inner_types = get_args(var_type)
if int in inner_types and float in inner_types:
return self.to(NumberVar, self._var_type)
return self return self
if not inspect.isclass(fixed_type): if not inspect.isclass(fixed_type):
raise TypeError(f"Unsupported type {var_type} for guess_type.") raise TypeError(f"Unsupported type {var_type} for guess_type.")
if issubclass(fixed_type, bool):
return self.to(BooleanVar, self._var_type)
if issubclass(fixed_type, (int, float)): if issubclass(fixed_type, (int, float)):
return self.to(NumberVar, self._var_type) return self.to(NumberVar, self._var_type)
if issubclass(fixed_type, dict): if issubclass(fixed_type, dict):
@ -570,9 +584,9 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns: Returns:
BooleanVar: A BooleanVar object representing the result of the equality check. BooleanVar: A BooleanVar object representing the result of the equality check.
""" """
from .number import EqualOperation from .number import equal_operation
return EqualOperation.create(self, other) return equal_operation(self, other)
def __ne__(self, other: Var | Any) -> BooleanVar: def __ne__(self, other: Var | Any) -> BooleanVar:
"""Check if the current object is not equal to the given object. """Check if the current object is not equal to the given object.
@ -583,9 +597,9 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns: Returns:
BooleanVar: A BooleanVar object representing the result of the comparison. BooleanVar: A BooleanVar object representing the result of the comparison.
""" """
from .number import EqualOperation from .number import equal_operation
return ~EqualOperation.create(self, other) return ~equal_operation(self, other)
def __gt__(self, other: Var | Any) -> BooleanVar: def __gt__(self, other: Var | Any) -> BooleanVar:
"""Compare the current instance with another variable and return a BooleanVar representing the result of the greater than operation. """Compare the current instance with another variable and return a BooleanVar representing the result of the greater than operation.
@ -596,9 +610,9 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns: Returns:
BooleanVar: A BooleanVar representing the result of the greater than operation. BooleanVar: A BooleanVar representing the result of the greater than operation.
""" """
from .number import GreaterThanOperation from .number import greater_than_operation
return GreaterThanOperation.create(self, other) return greater_than_operation(self, other)
def __ge__(self, other: Var | Any) -> BooleanVar: def __ge__(self, other: Var | Any) -> BooleanVar:
"""Check if the value of this variable is greater than or equal to the value of another variable or object. """Check if the value of this variable is greater than or equal to the value of another variable or object.
@ -609,9 +623,9 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns: Returns:
BooleanVar: A BooleanVar object representing the result of the comparison. BooleanVar: A BooleanVar object representing the result of the comparison.
""" """
from .number import GreaterThanOrEqualOperation from .number import greater_than_or_equal_operation
return GreaterThanOrEqualOperation.create(self, other) return greater_than_or_equal_operation(self, other)
def __lt__(self, other: Var | Any) -> BooleanVar: def __lt__(self, other: Var | Any) -> BooleanVar:
"""Compare the current instance with another variable using the less than (<) operator. """Compare the current instance with another variable using the less than (<) operator.
@ -622,9 +636,9 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns: Returns:
A `BooleanVar` object representing the result of the comparison. A `BooleanVar` object representing the result of the comparison.
""" """
from .number import LessThanOperation from .number import less_than_operation
return LessThanOperation.create(self, other) return less_than_operation(self, other)
def __le__(self, other: Var | Any) -> BooleanVar: def __le__(self, other: Var | Any) -> BooleanVar:
"""Compare if the current instance is less than or equal to the given value. """Compare if the current instance is less than or equal to the given value.
@ -635,9 +649,9 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns: Returns:
A BooleanVar object representing the result of the comparison. A BooleanVar object representing the result of the comparison.
""" """
from .number import LessThanOrEqualOperation from .number import less_than_or_equal_operation
return LessThanOrEqualOperation.create(self, other) return less_than_or_equal_operation(self, other)
def bool(self) -> BooleanVar: def bool(self) -> BooleanVar:
"""Convert the var to a boolean. """Convert the var to a boolean.
@ -645,9 +659,9 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns: Returns:
The boolean var. The boolean var.
""" """
from .number import ToBooleanVarOperation from .number import boolify
return ToBooleanVarOperation.create(self) return boolify(self)
def __and__(self, other: Var | Any) -> ImmutableVar: def __and__(self, other: Var | Any) -> ImmutableVar:
"""Perform a logical AND operation on the current instance and another variable. """Perform a logical AND operation on the current instance and another variable.
@ -658,7 +672,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns: Returns:
A `BooleanVar` object representing the result of the logical AND operation. A `BooleanVar` object representing the result of the logical AND operation.
""" """
return AndOperation.create(self, other) return and_operation(self, other)
def __rand__(self, other: Var | Any) -> ImmutableVar: def __rand__(self, other: Var | Any) -> ImmutableVar:
"""Perform a logical AND operation on the current instance and another variable. """Perform a logical AND operation on the current instance and another variable.
@ -669,7 +683,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns: Returns:
A `BooleanVar` object representing the result of the logical AND operation. A `BooleanVar` object representing the result of the logical AND operation.
""" """
return AndOperation.create(other, self) return and_operation(other, self)
def __or__(self, other: Var | Any) -> ImmutableVar: def __or__(self, other: Var | Any) -> ImmutableVar:
"""Perform a logical OR operation on the current instance and another variable. """Perform a logical OR operation on the current instance and another variable.
@ -680,7 +694,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns: Returns:
A `BooleanVar` object representing the result of the logical OR operation. A `BooleanVar` object representing the result of the logical OR operation.
""" """
return OrOperation.create(self, other) return or_operation(self, other)
def __ror__(self, other: Var | Any) -> ImmutableVar: def __ror__(self, other: Var | Any) -> ImmutableVar:
"""Perform a logical OR operation on the current instance and another variable. """Perform a logical OR operation on the current instance and another variable.
@ -691,7 +705,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns: Returns:
A `BooleanVar` object representing the result of the logical OR operation. A `BooleanVar` object representing the result of the logical OR operation.
""" """
return OrOperation.create(other, self) return or_operation(other, self)
def __invert__(self) -> BooleanVar: def __invert__(self) -> BooleanVar:
"""Perform a logical NOT operation on the current instance. """Perform a logical NOT operation on the current instance.
@ -699,9 +713,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns: Returns:
A `BooleanVar` object representing the result of the logical NOT operation. A `BooleanVar` object representing the result of the logical NOT operation.
""" """
from .number import BooleanNotOperation return ~self.bool()
return BooleanNotOperation.create(self.bool())
def to_string(self) -> ImmutableVar: def to_string(self) -> ImmutableVar:
"""Convert the var to a string. """Convert the var to a string.
@ -926,52 +938,92 @@ class LiteralVar(ImmutableVar):
P = ParamSpec("P") P = ParamSpec("P")
T = TypeVar("T", bound=ImmutableVar) T = TypeVar("T")
def var_operation(*, output: Type[T]) -> Callable[[Callable[P, str]], Callable[P, T]]: # NoReturn is used to match CustomVarOperationReturn with no type hint.
@overload
def var_operation(
func: Callable[P, CustomVarOperationReturn[NoReturn]],
) -> Callable[P, ImmutableVar]: ...
@overload
def var_operation(
func: Callable[P, CustomVarOperationReturn[bool]],
) -> Callable[P, BooleanVar]: ...
NUMBER_T = TypeVar("NUMBER_T", int, float, Union[int, float])
@overload
def var_operation(
func: Callable[P, CustomVarOperationReturn[NUMBER_T]],
) -> Callable[P, NumberVar[NUMBER_T]]: ...
@overload
def var_operation(
func: Callable[P, CustomVarOperationReturn[str]],
) -> Callable[P, StringVar]: ...
LIST_T = TypeVar("LIST_T", bound=Union[List[Any], Tuple, Set])
@overload
def var_operation(
func: Callable[P, CustomVarOperationReturn[LIST_T]],
) -> Callable[P, ArrayVar[LIST_T]]: ...
OBJECT_TYPE = TypeVar("OBJECT_TYPE", bound=Dict)
@overload
def var_operation(
func: Callable[P, CustomVarOperationReturn[OBJECT_TYPE]],
) -> Callable[P, ObjectVar[OBJECT_TYPE]]: ...
def var_operation(
func: Callable[P, CustomVarOperationReturn[T]],
) -> Callable[P, ImmutableVar[T]]:
"""Decorator for creating a var operation. """Decorator for creating a var operation.
Example: Example:
```python ```python
@var_operation(output=NumberVar) @var_operation
def add(a: NumberVar, b: NumberVar): def add(a: NumberVar, b: NumberVar):
return f"({a} + {b})" return custom_var_operation(f"{a} + {b}")
``` ```
Args: Args:
output: The output type of the operation. func: The function to decorate.
Returns: Returns:
The decorator. The decorated function.
""" """
def decorator(func: Callable[P, str], output=output): @functools.wraps(func)
@functools.wraps(func) def wrapper(*args: P.args, **kwargs: P.kwargs) -> ImmutableVar[T]:
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T: func_args = list(inspect.signature(func).parameters)
args_vars = [ args_vars = {
LiteralVar.create(arg) if not isinstance(arg, Var) else arg func_args[i]: (LiteralVar.create(arg) if not isinstance(arg, Var) else arg)
for arg in args for i, arg in enumerate(args)
] }
kwargs_vars = { kwargs_vars = {
key: LiteralVar.create(value) if not isinstance(value, Var) else value key: LiteralVar.create(value) if not isinstance(value, Var) else value
for key, value in kwargs.items() for key, value in kwargs.items()
} }
return output(
_var_name=func(*args_vars, **kwargs_vars), # type: ignore
_var_data=VarData.merge(
*[arg._get_all_var_data() for arg in args if isinstance(arg, Var)],
*[
arg._get_all_var_data()
for arg in kwargs.values()
if isinstance(arg, Var)
],
),
)
return wrapper return CustomVarOperation.create(
args=tuple(list(args_vars.items()) + list(kwargs_vars.items())),
return_var=func(*args_vars.values(), **kwargs_vars), # type: ignore
).guess_type()
return decorator return wrapper
def unionize(*args: Type) -> Type: def unionize(*args: Type) -> Type:
@ -1100,114 +1152,64 @@ class CachedVarOperation:
) )
@dataclasses.dataclass( def and_operation(a: Var | Any, b: Var | Any) -> ImmutableVar:
eq=False, """Perform a logical AND operation on two variables.
frozen=True,
**{"slots": True} if sys.version_info >= (3, 10) else {},
)
class AndOperation(CachedVarOperation, ImmutableVar):
"""Class for the logical AND operation."""
# The first var. Args:
_var1: Var = dataclasses.field(default_factory=lambda: LiteralVar.create(None)) a: The first variable.
b: The second variable.
# The second var. Returns:
_var2: Var = dataclasses.field(default_factory=lambda: LiteralVar.create(None)) The result of the logical AND operation.
"""
@cached_property_no_lock return _and_operation(a, b) # type: ignore
def _cached_var_name(self) -> str:
"""Get the cached var name.
Returns:
The cached var name.
"""
return f"({str(self._var1)} && {str(self._var2)})"
def __hash__(self) -> int:
"""Calculates the hash value of the object.
Returns:
int: The hash value of the object.
"""
return hash((self.__class__.__name__, self._var1, self._var2))
@classmethod
def create(
cls, var1: Var | Any, var2: Var | Any, _var_data: VarData | None = None
) -> AndOperation:
"""Create an AndOperation.
Args:
var1: The first var.
var2: The second var.
_var_data: Additional hooks and imports associated with the Var.
Returns:
The AndOperation.
"""
var1, var2 = map(LiteralVar.create, (var1, var2))
return AndOperation(
_var_name="",
_var_type=unionize(var1._var_type, var2._var_type),
_var_data=ImmutableVarData.merge(_var_data),
_var1=var1,
_var2=var2,
)
@dataclasses.dataclass( @var_operation
eq=False, def _and_operation(a: ImmutableVar, b: ImmutableVar):
frozen=True, """Perform a logical AND operation on two variables.
**{"slots": True} if sys.version_info >= (3, 10) else {},
)
class OrOperation(CachedVarOperation, ImmutableVar):
"""Class for the logical OR operation."""
# The first var. Args:
_var1: Var = dataclasses.field(default_factory=lambda: LiteralVar.create(None)) a: The first variable.
b: The second variable.
# The second var. Returns:
_var2: Var = dataclasses.field(default_factory=lambda: LiteralVar.create(None)) The result of the logical AND operation.
"""
return var_operation_return(
js_expression=f"({a} && {b})",
var_type=unionize(a._var_type, b._var_type),
)
@cached_property_no_lock
def _cached_var_name(self) -> str:
"""Get the cached var name.
Returns: def or_operation(a: Var | Any, b: Var | Any) -> ImmutableVar:
The cached var name. """Perform a logical OR operation on two variables.
"""
return f"({str(self._var1)} || {str(self._var2)})"
def __hash__(self) -> int: Args:
"""Calculates the hash value for the object. a: The first variable.
b: The second variable.
Returns: Returns:
int: The hash value of the object. The result of the logical OR operation.
""" """
return hash((self.__class__.__name__, self._var1, self._var2)) return _or_operation(a, b) # type: ignore
@classmethod
def create(
cls, var1: Var | Any, var2: Var | Any, _var_data: VarData | None = None
) -> OrOperation:
"""Create an OrOperation.
Args: @var_operation
var1: The first var. def _or_operation(a: ImmutableVar, b: ImmutableVar):
var2: The second var. """Perform a logical OR operation on two variables.
_var_data: Additional hooks and imports associated with the Var.
Returns: Args:
The OrOperation. a: The first variable.
""" b: The second variable.
var1, var2 = map(LiteralVar.create, (var1, var2))
return OrOperation( Returns:
_var_name="", The result of the logical OR operation.
_var_type=unionize(var1._var_type, var2._var_type), """
_var_data=ImmutableVarData.merge(_var_data), return var_operation_return(
_var1=var1, js_expression=f"({a} || {b})",
_var2=var2, var_type=unionize(a._var_type, b._var_type),
) )
@dataclasses.dataclass( @dataclasses.dataclass(
@ -1797,3 +1799,114 @@ def immutable_computed_var(
) )
return wrapper return wrapper
RETURN = TypeVar("RETURN")
class CustomVarOperationReturn(ImmutableVar[RETURN]):
"""Base class for custom var operations."""
@classmethod
def create(
cls,
js_expression: str,
_var_type: Type[RETURN] | None = None,
_var_data: VarData | None = None,
) -> CustomVarOperationReturn[RETURN]:
"""Create a CustomVarOperation.
Args:
js_expression: The JavaScript expression to evaluate.
_var_type: The type of the var.
_var_data: Additional hooks and imports associated with the Var.
Returns:
The CustomVarOperation.
"""
return CustomVarOperationReturn(
_var_name=js_expression,
_var_type=_var_type or Any,
_var_data=ImmutableVarData.merge(_var_data),
)
def var_operation_return(
js_expression: str,
var_type: Type[RETURN] | None = None,
) -> CustomVarOperationReturn[RETURN]:
"""Shortcut for creating a CustomVarOperationReturn.
Args:
js_expression: The JavaScript expression to evaluate.
var_type: The type of the var.
Returns:
The CustomVarOperationReturn.
"""
return CustomVarOperationReturn.create(js_expression, var_type)
@dataclasses.dataclass(
eq=False,
frozen=True,
**{"slots": True} if sys.version_info >= (3, 10) else {},
)
class CustomVarOperation(CachedVarOperation, ImmutableVar[T]):
"""Base class for custom var operations."""
_args: Tuple[Tuple[str, Var], ...] = dataclasses.field(default_factory=tuple)
_return: CustomVarOperationReturn[T] = dataclasses.field(
default_factory=lambda: CustomVarOperationReturn.create("")
)
@cached_property_no_lock
def _cached_var_name(self) -> str:
"""Get the cached var name.
Returns:
The cached var name.
"""
return str(self._return)
@cached_property_no_lock
def _cached_get_all_var_data(self) -> ImmutableVarData | None:
"""Get the cached VarData.
Returns:
The cached VarData.
"""
return ImmutableVarData.merge(
*map(
lambda arg: arg[1]._get_all_var_data(),
self._args,
),
self._return._get_all_var_data(),
self._var_data,
)
@classmethod
def create(
cls,
args: Tuple[Tuple[str, Var], ...],
return_var: CustomVarOperationReturn[T],
_var_data: VarData | None = None,
) -> CustomVarOperation[T]:
"""Create a CustomVarOperation.
Args:
args: The arguments to the operation.
return_var: The return var.
_var_data: Additional hooks and imports associated with the Var.
Returns:
The CustomVarOperation.
"""
return CustomVarOperation(
_var_name="",
_var_type=return_var._var_type,
_var_data=ImmutableVarData.merge(_var_data),
_args=args,
_return=return_var,
)

File diff suppressed because it is too large Load Diff

View File

@ -30,6 +30,8 @@ from .base import (
LiteralVar, LiteralVar,
cached_property_no_lock, cached_property_no_lock,
figure_out_type, figure_out_type,
var_operation,
var_operation_return,
) )
from .number import BooleanVar, NumberVar from .number import BooleanVar, NumberVar
from .sequence import ArrayVar, StringVar from .sequence import ArrayVar, StringVar
@ -56,7 +58,9 @@ class ObjectVar(ImmutableVar[OBJECT_TYPE]):
return str return str
@overload @overload
def _value_type(self: ObjectVar[Dict[KEY_TYPE, VALUE_TYPE]]) -> VALUE_TYPE: ... def _value_type(
self: ObjectVar[Dict[KEY_TYPE, VALUE_TYPE]],
) -> Type[VALUE_TYPE]: ...
@overload @overload
def _value_type(self) -> Type: ... def _value_type(self) -> Type: ...
@ -79,7 +83,7 @@ class ObjectVar(ImmutableVar[OBJECT_TYPE]):
Returns: Returns:
The keys of the object. The keys of the object.
""" """
return ObjectKeysOperation.create(self) return object_keys_operation(self)
@overload @overload
def values( def values(
@ -95,7 +99,7 @@ class ObjectVar(ImmutableVar[OBJECT_TYPE]):
Returns: Returns:
The values of the object. The values of the object.
""" """
return ObjectValuesOperation.create(self) return object_values_operation(self)
@overload @overload
def entries( def entries(
@ -111,9 +115,9 @@ class ObjectVar(ImmutableVar[OBJECT_TYPE]):
Returns: Returns:
The entries of the object. The entries of the object.
""" """
return ObjectEntriesOperation.create(self) return object_entries_operation(self)
def merge(self, other: ObjectVar) -> ObjectMergeOperation: def merge(self, other: ObjectVar):
"""Merge two objects. """Merge two objects.
Args: Args:
@ -122,7 +126,7 @@ class ObjectVar(ImmutableVar[OBJECT_TYPE]):
Returns: Returns:
The merged object. The merged object.
""" """
return ObjectMergeOperation.create(self, other) return object_merge_operation(self, other)
# NoReturn is used here to catch when key value is Any # NoReturn is used here to catch when key value is Any
@overload @overload
@ -270,7 +274,7 @@ class ObjectVar(ImmutableVar[OBJECT_TYPE]):
Returns: Returns:
The result of the check. The result of the check.
""" """
return ObjectHasOwnProperty.create(self, key) return object_has_own_property_operation(self, key)
@dataclasses.dataclass( @dataclasses.dataclass(
@ -387,207 +391,72 @@ class LiteralObjectVar(CachedVarOperation, ObjectVar[OBJECT_TYPE], LiteralVar):
) )
@dataclasses.dataclass( @var_operation
eq=False, def object_keys_operation(value: ObjectVar):
frozen=True, """Get the keys of an object.
**{"slots": True} if sys.version_info >= (3, 10) else {},
)
class ObjectToArrayOperation(CachedVarOperation, ArrayVar):
"""Base class for object to array operations."""
_value: ObjectVar = dataclasses.field( Args:
default_factory=lambda: LiteralObjectVar.create({}) value: The object to get the keys from.
Returns:
The keys of the object.
"""
return var_operation_return(
js_expression=f"Object.keys({value})",
var_type=List[str],
) )
@cached_property_no_lock
def _cached_var_name(self) -> str:
"""The name of the operation.
Raises: @var_operation
NotImplementedError: Must implement _cached_var_name. def object_values_operation(value: ObjectVar):
""" """Get the values of an object.
raise NotImplementedError(
"ObjectToArrayOperation must implement _cached_var_name"
)
@classmethod Args:
def create( value: The object to get the values from.
cls,
value: ObjectVar,
_var_type: GenericType | None = None,
_var_data: VarData | None = None,
) -> ObjectToArrayOperation:
"""Create the object to array operation.
Args: Returns:
value: The value of the operation. The values of the object.
_var_data: Additional hooks and imports associated with the operation. """
return var_operation_return(
Returns: js_expression=f"Object.values({value})",
The object to array operation. var_type=List[value._value_type()],
"""
return cls(
_var_name="",
_var_type=list if _var_type is None else _var_type,
_var_data=ImmutableVarData.merge(_var_data),
_value=value,
)
class ObjectKeysOperation(ObjectToArrayOperation):
"""Operation to get the keys of an object."""
@cached_property_no_lock
def _cached_var_name(self) -> str:
"""The name of the operation.
Returns:
The name of the operation.
"""
return f"Object.keys({str(self._value)})"
@classmethod
def create(
cls,
value: ObjectVar,
_var_data: VarData | None = None,
) -> ObjectKeysOperation:
"""Create the object keys operation.
Args:
value: The value of the operation.
_var_data: Additional hooks and imports associated with the operation.
Returns:
The object keys operation.
"""
return cls(
_var_name="",
_var_type=List[str],
_var_data=ImmutableVarData.merge(_var_data),
_value=value,
)
class ObjectValuesOperation(ObjectToArrayOperation):
"""Operation to get the values of an object."""
@cached_property_no_lock
def _cached_var_name(self) -> str:
"""The name of the operation.
Returns:
The name of the operation.
"""
return f"Object.values({str(self._value)})"
@classmethod
def create(
cls,
value: ObjectVar,
_var_data: VarData | None = None,
) -> ObjectValuesOperation:
"""Create the object values operation.
Args:
value: The value of the operation.
_var_data: Additional hooks and imports associated with the operation.
Returns:
The object values operation.
"""
return cls(
_var_name="",
_var_type=List[value._value_type()],
_var_data=ImmutableVarData.merge(_var_data),
_value=value,
)
class ObjectEntriesOperation(ObjectToArrayOperation):
"""Operation to get the entries of an object."""
@cached_property_no_lock
def _cached_var_name(self) -> str:
"""The name of the operation.
Returns:
The name of the operation.
"""
return f"Object.entries({str(self._value)})"
@classmethod
def create(
cls,
value: ObjectVar,
_var_data: VarData | None = None,
) -> ObjectEntriesOperation:
"""Create the object entries operation.
Args:
value: The value of the operation.
_var_data: Additional hooks and imports associated with the operation.
Returns:
The object entries operation.
"""
return cls(
_var_name="",
_var_type=List[Tuple[str, value._value_type()]],
_var_data=ImmutableVarData.merge(_var_data),
_value=value,
)
@dataclasses.dataclass(
eq=False,
frozen=True,
**{"slots": True} if sys.version_info >= (3, 10) else {},
)
class ObjectMergeOperation(CachedVarOperation, ObjectVar):
"""Operation to merge two objects."""
_lhs: ObjectVar = dataclasses.field(
default_factory=lambda: LiteralObjectVar.create({})
)
_rhs: ObjectVar = dataclasses.field(
default_factory=lambda: LiteralObjectVar.create({})
) )
@cached_property_no_lock
def _cached_var_name(self) -> str:
"""The name of the operation.
Returns: @var_operation
The name of the operation. def object_entries_operation(value: ObjectVar):
""" """Get the entries of an object.
return f"({{...{str(self._lhs)}, ...{str(self._rhs)}}})"
@classmethod Args:
def create( value: The object to get the entries from.
cls,
lhs: ObjectVar,
rhs: ObjectVar,
_var_data: VarData | None = None,
) -> ObjectMergeOperation:
"""Create the object merge operation.
Args: Returns:
lhs: The left object to merge. The entries of the object.
rhs: The right object to merge. """
_var_data: Additional hooks and imports associated with the operation. return var_operation_return(
js_expression=f"Object.entries({value})",
var_type=List[Tuple[str, value._value_type()]],
)
Returns:
The object merge operation. @var_operation
""" def object_merge_operation(lhs: ObjectVar, rhs: ObjectVar):
# TODO: Figure out how to merge the types """Merge two objects.
return cls(
_var_name="", Args:
_var_type=lhs._var_type, lhs: The first object to merge.
_var_data=ImmutableVarData.merge(_var_data), rhs: The second object to merge.
_lhs=lhs,
_rhs=rhs, Returns:
) The merged object.
"""
return var_operation_return(
js_expression=f"({{...{lhs}, ...{rhs}}})",
var_type=Dict[
Union[lhs._key_type(), rhs._key_type()],
Union[lhs._value_type(), rhs._value_type()],
],
)
@dataclasses.dataclass( @dataclasses.dataclass(
@ -688,49 +557,18 @@ class ToObjectOperation(CachedVarOperation, ObjectVar):
) )
@dataclasses.dataclass( @var_operation
eq=False, def object_has_own_property_operation(object: ObjectVar, key: Var):
frozen=True, """Check if an object has a key.
**{"slots": True} if sys.version_info >= (3, 10) else {},
)
class ObjectHasOwnProperty(CachedVarOperation, BooleanVar):
"""Operation to check if an object has a property."""
_object: ObjectVar = dataclasses.field( Args:
default_factory=lambda: LiteralObjectVar.create({}) object: The object to check.
key: The key to check.
Returns:
The result of the check.
"""
return var_operation_return(
js_expression=f"{object}.hasOwnProperty({key})",
var_type=bool,
) )
_key: Var | Any = dataclasses.field(default_factory=lambda: LiteralVar.create(None))
@cached_property_no_lock
def _cached_var_name(self) -> str:
"""The name of the operation.
Returns:
The name of the operation.
"""
return f"{str(self._object)}.hasOwnProperty({str(self._key)})"
@classmethod
def create(
cls,
object: ObjectVar,
key: Var | Any,
_var_data: VarData | None = None,
) -> ObjectHasOwnProperty:
"""Create the object has own property operation.
Args:
object: The object to check.
key: The key to check.
_var_data: Additional hooks and imports associated with the operation.
Returns:
The object has own property operation.
"""
return cls(
_var_name="",
_var_type=bool,
_var_data=ImmutableVarData.merge(_var_data),
_object=object,
_key=key if isinstance(key, Var) else LiteralVar.create(key),
)

File diff suppressed because it is too large Load Diff

View File

@ -67,11 +67,11 @@ def test_color(color, expected):
[ [
( (
rx.cond(True, rx.color("mint"), rx.color("tomato", 5)), rx.cond(True, rx.color("mint"), rx.color("tomato", 5)),
'(Boolean(true) ? "var(--mint-7)" : "var(--tomato-5)")', '(true ? "var(--mint-7)" : "var(--tomato-5)")',
), ),
( (
rx.cond(True, rx.color(ColorState.color), rx.color(ColorState.color, 5)), # type: ignore rx.cond(True, rx.color(ColorState.color), rx.color(ColorState.color, 5)), # type: ignore
f'(Boolean(true) ? ("var(--"+{str(color_state_name)}.color+"-7)") : ("var(--"+{str(color_state_name)}.color+"-5)"))', f'(true ? ("var(--"+{str(color_state_name)}.color+"-7)") : ("var(--"+{str(color_state_name)}.color+"-5)"))',
), ),
( (
rx.match( rx.match(

View File

@ -23,7 +23,7 @@ def cond_state(request):
def test_f_string_cond_interpolation(): def test_f_string_cond_interpolation():
# make sure backticks inside interpolation don't get escaped # make sure backticks inside interpolation don't get escaped
var = LiteralVar.create(f"x {cond(True, 'a', 'b')}") var = LiteralVar.create(f"x {cond(True, 'a', 'b')}")
assert str(var) == '("x "+(Boolean(true) ? "a" : "b"))' assert str(var) == '("x "+(true ? "a" : "b"))'
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -97,7 +97,7 @@ def test_prop_cond(c1: Any, c2: Any):
c1 = json.dumps(c1) c1 = json.dumps(c1)
if not isinstance(c2, Var): if not isinstance(c2, Var):
c2 = json.dumps(c2) c2 = json.dumps(c2)
assert str(prop_cond) == f"(Boolean(true) ? {c1} : {c2})" assert str(prop_cond) == f"(true ? {c1} : {c2})"
def test_cond_no_mix(): def test_cond_no_mix():
@ -141,8 +141,7 @@ def test_cond_computed_var():
state_name = format_state_name(CondStateComputed.get_full_name()) state_name = format_state_name(CondStateComputed.get_full_name())
assert ( assert (
str(comp) str(comp) == f"(true ? {state_name}.computed_int : {state_name}.computed_str)"
== f"(Boolean(true) ? {state_name}.computed_int : {state_name}.computed_str)"
) )
assert comp._var_type == Union[int, str] assert comp._var_type == Union[int, str]

View File

@ -12,6 +12,7 @@ from reflex.ivars.base import (
ImmutableVar, ImmutableVar,
LiteralVar, LiteralVar,
var_operation, var_operation,
var_operation_return,
) )
from reflex.ivars.function import ArgsFunctionOperation, FunctionStringVar from reflex.ivars.function import ArgsFunctionOperation, FunctionStringVar
from reflex.ivars.number import ( from reflex.ivars.number import (
@ -925,9 +926,9 @@ def test_function_var():
def test_var_operation(): def test_var_operation():
@var_operation(output=NumberVar) @var_operation
def add(a: Union[NumberVar, int], b: Union[NumberVar, int]) -> str: def add(a: Union[NumberVar, int], b: Union[NumberVar, int]):
return f"({a} + {b})" return var_operation_return(js_expression=f"({a} + {b})", var_type=int)
assert str(add(1, 2)) == "(1 + 2)" assert str(add(1, 2)) == "(1 + 2)"
assert str(add(a=4, b=-9)) == "(4 + -9)" assert str(add(a=4, b=-9)) == "(4 + -9)"
@ -967,14 +968,14 @@ def test_all_number_operations():
assert ( assert (
str(even_more_complicated_number) str(even_more_complicated_number)
== "!(Boolean((Math.abs(Math.floor(((Math.floor(((-((-5.4 + 1)) * 2) / 3) / 2) % 3) ** 2))) || (2 && Math.round(((Math.floor(((-((-5.4 + 1)) * 2) / 3) / 2) % 3) ** 2))))))" == "!(((Math.abs(Math.floor(((Math.floor(((-((-5.4 + 1)) * 2) / 3) / 2) % 3) ** 2))) || (2 && Math.round(((Math.floor(((-((-5.4 + 1)) * 2) / 3) / 2) % 3) ** 2)))) !== 0))"
) )
assert str(LiteralNumberVar.create(5) > False) == "(5 > 0)" assert str(LiteralNumberVar.create(5) > False) == "(5 > 0)"
assert str(LiteralBooleanVar.create(False) < 5) == "((false ? 1 : 0) < 5)" assert str(LiteralBooleanVar.create(False) < 5) == "(Number(false) < 5)"
assert ( assert (
str(LiteralBooleanVar.create(False) < LiteralBooleanVar.create(True)) str(LiteralBooleanVar.create(False) < LiteralBooleanVar.create(True))
== "((false ? 1 : 0) < (true ? 1 : 0))" == "(Number(false) < Number(true))"
) )