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,7 +385,15 @@ 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)):
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."
)
@ -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,53 +938,93 @@ 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
]
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 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 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 wrapper
return decorator
def unionize(*args: Type) -> Type:
"""Unionize the types.
@ -1100,113 +1152,63 @@ 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."""
# The first var.
_var1: Var = dataclasses.field(default_factory=lambda: LiteralVar.create(None))
# 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.
def and_operation(a: Var | Any, b: Var | Any) -> ImmutableVar:
"""Perform a logical AND operation on two variables.
Args:
var1: The first var.
var2: The second var.
_var_data: Additional hooks and imports associated with the Var.
a: The first variable.
b: The second variable.
Returns:
The AndOperation.
The result of the logical AND operation.
"""
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,
return _and_operation(a, b) # type: ignore
@var_operation
def _and_operation(a: ImmutableVar, b: ImmutableVar):
"""Perform a logical AND operation on two variables.
Args:
a: The first variable.
b: The second variable.
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),
)
@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."""
# The first var.
_var1: Var = dataclasses.field(default_factory=lambda: LiteralVar.create(None))
# 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 for 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
) -> OrOperation:
"""Create an OrOperation.
def or_operation(a: Var | Any, b: Var | Any) -> ImmutableVar:
"""Perform a logical OR operation on two variables.
Args:
var1: The first var.
var2: The second var.
_var_data: Additional hooks and imports associated with the Var.
a: The first variable.
b: The second variable.
Returns:
The OrOperation.
The result of the logical OR operation.
"""
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,
return _or_operation(a, b) # type: ignore
@var_operation
def _or_operation(a: ImmutableVar, b: ImmutableVar):
"""Perform a logical OR operation on two variables.
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),
)
@ -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,206 +391,71 @@ 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."""
_value: ObjectVar = dataclasses.field(
default_factory=lambda: LiteralObjectVar.create({})
)
@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"
)
@classmethod
def create(
cls,
value: ObjectVar,
_var_type: GenericType | None = None,
_var_data: VarData | None = None,
) -> ObjectToArrayOperation:
"""Create the object to array operation.
@var_operation
def object_keys_operation(value: ObjectVar):
"""Get the keys of an object.
Args:
value: The value of the operation.
_var_data: Additional hooks and imports associated with the operation.
value: The object to get the keys from.
Returns:
The object to array operation.
The keys of the object.
"""
return cls(
_var_name="",
_var_type=list if _var_type is None else _var_type,
_var_data=ImmutableVarData.merge(_var_data),
_value=value,
return var_operation_return(
js_expression=f"Object.keys({value})",
var_type=List[str],
)
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.
@var_operation
def object_values_operation(value: ObjectVar):
"""Get the values of an object.
Args:
value: The value of the operation.
_var_data: Additional hooks and imports associated with the operation.
value: The object to get the values from.
Returns:
The object keys operation.
The values of the object.
"""
return cls(
_var_name="",
_var_type=List[str],
_var_data=ImmutableVarData.merge(_var_data),
_value=value,
return var_operation_return(
js_expression=f"Object.values({value})",
var_type=List[value._value_type()],
)
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.
@var_operation
def object_entries_operation(value: ObjectVar):
"""Get the entries of an object.
Args:
value: The value of the operation.
_var_data: Additional hooks and imports associated with the operation.
value: The object to get the entries from.
Returns:
The object values operation.
The entries of the object.
"""
return cls(
_var_name="",
_var_type=List[value._value_type()],
_var_data=ImmutableVarData.merge(_var_data),
_value=value,
return var_operation_return(
js_expression=f"Object.entries({value})",
var_type=List[Tuple[str, value._value_type()]],
)
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.
@var_operation
def object_merge_operation(lhs: ObjectVar, rhs: ObjectVar):
"""Merge two objects.
Args:
value: The value of the operation.
_var_data: Additional hooks and imports associated with the operation.
lhs: The first object to merge.
rhs: The second object to merge.
Returns:
The object entries operation.
The merged object.
"""
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:
The name of the operation.
"""
return f"({{...{str(self._lhs)}, ...{str(self._rhs)}}})"
@classmethod
def create(
cls,
lhs: ObjectVar,
rhs: ObjectVar,
_var_data: VarData | None = None,
) -> ObjectMergeOperation:
"""Create the object merge operation.
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 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,
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()],
],
)
@ -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."""
_object: ObjectVar = dataclasses.field(
default_factory=lambda: LiteralObjectVar.create({})
)
_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.
@var_operation
def object_has_own_property_operation(object: ObjectVar, key: Var):
"""Check if an object has a key.
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.
The result of the check.
"""
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),
return var_operation_return(
js_expression=f"{object}.hasOwnProperty({key})",
var_type=bool,
)

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))"
)