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.constants import Dirs
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.utils.imports import ImportDict, ImportVar
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)
# Create the conditional var.
return TernaryOperator.create(
condition=cond_var.to(bool), # type: ignore
if_true=c1,
if_false=c2,
_var_data=VarData(imports=_IS_TRUE_IMPORT),
return ternary_operation(
cond_var.bool()._replace( # type: ignore
merge_var_data=VarData(imports=_IS_TRUE_IMPORT),
), # type: ignore
c1,
c2,
)

View File

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

View File

@ -20,6 +20,7 @@ from typing import (
Generic,
List,
Literal,
NoReturn,
Optional,
Sequence,
Set,
@ -384,10 +385,18 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
return self.to(BooleanVar, output)
if issubclass(output, NumberVar):
if fixed_type is not None and not issubclass(fixed_type, (int, float)):
raise TypeError(
f"Unsupported type {var_type} for NumberVar. Must be int or float."
)
if fixed_type is not None:
if fixed_type is Union:
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)
if issubclass(output, BooleanVar):
@ -440,7 +449,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Raises:
TypeError: If the type is not supported for guessing.
"""
from .number import NumberVar
from .number import BooleanVar, NumberVar
from .object import ObjectVar
from .sequence import ArrayVar, StringVar
@ -454,11 +463,16 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
fixed_type = get_origin(var_type) or var_type
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
if not inspect.isclass(fixed_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)):
return self.to(NumberVar, self._var_type)
if issubclass(fixed_type, dict):
@ -570,9 +584,9 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns:
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:
"""Check if the current object is not equal to the given object.
@ -583,9 +597,9 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns:
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:
"""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:
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:
"""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:
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:
"""Compare the current instance with another variable using the less than (<) operator.
@ -622,9 +636,9 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns:
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:
"""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:
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:
"""Convert the var to a boolean.
@ -645,9 +659,9 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns:
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:
"""Perform a logical AND operation on the current instance and another variable.
@ -658,7 +672,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns:
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:
"""Perform a logical AND operation on the current instance and another variable.
@ -669,7 +683,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns:
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:
"""Perform a logical OR operation on the current instance and another variable.
@ -680,7 +694,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns:
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:
"""Perform a logical OR operation on the current instance and another variable.
@ -691,7 +705,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns:
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:
"""Perform a logical NOT operation on the current instance.
@ -699,9 +713,7 @@ class ImmutableVar(Var, Generic[VAR_TYPE]):
Returns:
A `BooleanVar` object representing the result of the logical NOT operation.
"""
from .number import BooleanNotOperation
return BooleanNotOperation.create(self.bool())
return ~self.bool()
def to_string(self) -> ImmutableVar:
"""Convert the var to a string.
@ -926,52 +938,92 @@ class LiteralVar(ImmutableVar):
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.
Example:
```python
@var_operation(output=NumberVar)
@var_operation
def add(a: NumberVar, b: NumberVar):
return f"({a} + {b})"
return custom_var_operation(f"{a} + {b}")
```
Args:
output: The output type of the operation.
func: The function to decorate.
Returns:
The decorator.
The decorated function.
"""
def decorator(func: Callable[P, str], output=output):
@functools.wraps(func)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
args_vars = [
LiteralVar.create(arg) if not isinstance(arg, Var) else arg
for arg in args
]
kwargs_vars = {
key: LiteralVar.create(value) if not isinstance(value, Var) else value
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)
],
),
)
@functools.wraps(func)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> ImmutableVar[T]:
func_args = list(inspect.signature(func).parameters)
args_vars = {
func_args[i]: (LiteralVar.create(arg) if not isinstance(arg, Var) else arg)
for i, arg in enumerate(args)
}
kwargs_vars = {
key: LiteralVar.create(value) if not isinstance(value, Var) else value
for key, value in kwargs.items()
}
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:
@ -1100,114 +1152,64 @@ class CachedVarOperation:
)
@dataclasses.dataclass(
eq=False,
frozen=True,
**{"slots": True} if sys.version_info >= (3, 10) else {},
)
class AndOperation(CachedVarOperation, ImmutableVar):
"""Class for the logical AND operation."""
def and_operation(a: Var | Any, b: Var | Any) -> ImmutableVar:
"""Perform a logical AND operation on two variables.
# The first var.
_var1: Var = dataclasses.field(default_factory=lambda: LiteralVar.create(None))
Args:
a: The first variable.
b: The second variable.
# The second var.
_var2: Var = dataclasses.field(default_factory=lambda: LiteralVar.create(None))
@cached_property_no_lock
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,
)
Returns:
The result of the logical AND operation.
"""
return _and_operation(a, b) # type: ignore
@dataclasses.dataclass(
eq=False,
frozen=True,
**{"slots": True} if sys.version_info >= (3, 10) else {},
)
class OrOperation(CachedVarOperation, ImmutableVar):
"""Class for the logical OR operation."""
@var_operation
def _and_operation(a: ImmutableVar, b: ImmutableVar):
"""Perform a logical AND operation on two variables.
# The first var.
_var1: Var = dataclasses.field(default_factory=lambda: LiteralVar.create(None))
Args:
a: The first variable.
b: The second variable.
# The second var.
_var2: Var = dataclasses.field(default_factory=lambda: LiteralVar.create(None))
Returns:
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:
The cached var name.
"""
return f"({str(self._var1)} || {str(self._var2)})"
def or_operation(a: Var | Any, b: Var | Any) -> ImmutableVar:
"""Perform a logical OR operation on two variables.
def __hash__(self) -> int:
"""Calculates the hash value for the object.
Args:
a: The first variable.
b: The second variable.
Returns:
int: The hash value of the object.
"""
return hash((self.__class__.__name__, self._var1, self._var2))
Returns:
The result of the logical OR operation.
"""
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:
var1: The first var.
var2: The second var.
_var_data: Additional hooks and imports associated with the Var.
@var_operation
def _or_operation(a: ImmutableVar, b: ImmutableVar):
"""Perform a logical OR operation on two variables.
Returns:
The OrOperation.
"""
var1, var2 = map(LiteralVar.create, (var1, var2))
return OrOperation(
_var_name="",
_var_type=unionize(var1._var_type, var2._var_type),
_var_data=ImmutableVarData.merge(_var_data),
_var1=var1,
_var2=var2,
)
Args:
a: The first variable.
b: The second variable.
Returns:
The result of the logical OR operation.
"""
return var_operation_return(
js_expression=f"({a} || {b})",
var_type=unionize(a._var_type, b._var_type),
)
@dataclasses.dataclass(
@ -1797,3 +1799,114 @@ def immutable_computed_var(
)
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,
cached_property_no_lock,
figure_out_type,
var_operation,
var_operation_return,
)
from .number import BooleanVar, NumberVar
from .sequence import ArrayVar, StringVar
@ -56,7 +58,9 @@ class ObjectVar(ImmutableVar[OBJECT_TYPE]):
return str
@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
def _value_type(self) -> Type: ...
@ -79,7 +83,7 @@ class ObjectVar(ImmutableVar[OBJECT_TYPE]):
Returns:
The keys of the object.
"""
return ObjectKeysOperation.create(self)
return object_keys_operation(self)
@overload
def values(
@ -95,7 +99,7 @@ class ObjectVar(ImmutableVar[OBJECT_TYPE]):
Returns:
The values of the object.
"""
return ObjectValuesOperation.create(self)
return object_values_operation(self)
@overload
def entries(
@ -111,9 +115,9 @@ class ObjectVar(ImmutableVar[OBJECT_TYPE]):
Returns:
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.
Args:
@ -122,7 +126,7 @@ class ObjectVar(ImmutableVar[OBJECT_TYPE]):
Returns:
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
@overload
@ -270,7 +274,7 @@ class ObjectVar(ImmutableVar[OBJECT_TYPE]):
Returns:
The result of the check.
"""
return ObjectHasOwnProperty.create(self, key)
return object_has_own_property_operation(self, key)
@dataclasses.dataclass(
@ -387,207 +391,72 @@ class LiteralObjectVar(CachedVarOperation, ObjectVar[OBJECT_TYPE], LiteralVar):
)
@dataclasses.dataclass(
eq=False,
frozen=True,
**{"slots": True} if sys.version_info >= (3, 10) else {},
)
class ObjectToArrayOperation(CachedVarOperation, ArrayVar):
"""Base class for object to array operations."""
@var_operation
def object_keys_operation(value: ObjectVar):
"""Get the keys of an object.
_value: ObjectVar = dataclasses.field(
default_factory=lambda: LiteralObjectVar.create({})
Args:
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:
NotImplementedError: Must implement _cached_var_name.
"""
raise NotImplementedError(
"ObjectToArrayOperation must implement _cached_var_name"
)
@var_operation
def object_values_operation(value: ObjectVar):
"""Get the values of an object.
@classmethod
def create(
cls,
value: ObjectVar,
_var_type: GenericType | None = None,
_var_data: VarData | None = None,
) -> ObjectToArrayOperation:
"""Create the object to array operation.
Args:
value: The object to get the values from.
Args:
value: The value of the operation.
_var_data: Additional hooks and imports associated with the operation.
Returns:
The object to array operation.
"""
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({})
Returns:
The values of the object.
"""
return var_operation_return(
js_expression=f"Object.values({value})",
var_type=List[value._value_type()],
)
@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._lhs)}, ...{str(self._rhs)}}})"
@var_operation
def object_entries_operation(value: ObjectVar):
"""Get the entries of an object.
@classmethod
def create(
cls,
lhs: ObjectVar,
rhs: ObjectVar,
_var_data: VarData | None = None,
) -> ObjectMergeOperation:
"""Create the object merge operation.
Args:
value: The object to get the entries from.
Args:
lhs: The left object to merge.
rhs: The right object to merge.
_var_data: Additional hooks and imports associated with the operation.
Returns:
The entries of the object.
"""
return var_operation_return(
js_expression=f"Object.entries({value})",
var_type=List[Tuple[str, value._value_type()]],
)
Returns:
The object merge operation.
"""
# TODO: Figure out how to merge the types
return cls(
_var_name="",
_var_type=lhs._var_type,
_var_data=ImmutableVarData.merge(_var_data),
_lhs=lhs,
_rhs=rhs,
)
@var_operation
def object_merge_operation(lhs: ObjectVar, rhs: ObjectVar):
"""Merge two objects.
Args:
lhs: The first object to merge.
rhs: The second object to merge.
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(
@ -688,49 +557,18 @@ class ToObjectOperation(CachedVarOperation, ObjectVar):
)
@dataclasses.dataclass(
eq=False,
frozen=True,
**{"slots": True} if sys.version_info >= (3, 10) else {},
)
class ObjectHasOwnProperty(CachedVarOperation, BooleanVar):
"""Operation to check if an object has a property."""
@var_operation
def object_has_own_property_operation(object: ObjectVar, key: Var):
"""Check if an object has a key.
_object: ObjectVar = dataclasses.field(
default_factory=lambda: LiteralObjectVar.create({})
Args:
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)),
'(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
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(

View File

@ -23,7 +23,7 @@ def cond_state(request):
def test_f_string_cond_interpolation():
# make sure backticks inside interpolation don't get escaped
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(
@ -97,7 +97,7 @@ def test_prop_cond(c1: Any, c2: Any):
c1 = json.dumps(c1)
if not isinstance(c2, Var):
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():
@ -141,8 +141,7 @@ def test_cond_computed_var():
state_name = format_state_name(CondStateComputed.get_full_name())
assert (
str(comp)
== f"(Boolean(true) ? {state_name}.computed_int : {state_name}.computed_str)"
str(comp) == f"(true ? {state_name}.computed_int : {state_name}.computed_str)"
)
assert comp._var_type == Union[int, str]

View File

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