From f3220470e8141a2017edb3a2183a78b30e0a903e Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Fri, 7 Feb 2025 14:20:29 -0800 Subject: [PATCH] fix dynamic icons for underscore and positional argument (#4767) * fix dynamic icons for underscore and positional argument * use no return --- reflex/components/core/sticky.py | 4 +-- reflex/components/lucide/icon.py | 14 ++++++-- reflex/vars/base.py | 34 ++++++++++++++----- reflex/vars/number.py | 2 +- reflex/vars/sequence.py | 31 +++++++++++++++-- .../components/datadisplay/test_shiki_code.py | 5 ++- tests/units/vars/test_object.py | 8 ++--- 7 files changed, 75 insertions(+), 23 deletions(-) diff --git a/reflex/components/core/sticky.py b/reflex/components/core/sticky.py index 162bab3cd7b..cbcec00a94e 100644 --- a/reflex/components/core/sticky.py +++ b/reflex/components/core/sticky.py @@ -107,9 +107,7 @@ def add_style(self): default=True, global_ref=False, ) - localhost_hostnames = Var.create( - ["localhost", "127.0.0.1", "[::1]"] - ).guess_type() + localhost_hostnames = Var.create(["localhost", "127.0.0.1", "[::1]"]) is_localhost_expr = localhost_hostnames.contains( Var("window.location.hostname", _var_type=str).guess_type(), ) diff --git a/reflex/components/lucide/icon.py b/reflex/components/lucide/icon.py index 6c7cbede7dd..269ef7f79ae 100644 --- a/reflex/components/lucide/icon.py +++ b/reflex/components/lucide/icon.py @@ -4,7 +4,7 @@ from reflex.utils import format from reflex.utils.imports import ImportVar from reflex.vars.base import LiteralVar, Var -from reflex.vars.sequence import LiteralStringVar +from reflex.vars.sequence import LiteralStringVar, StringVar class LucideIconComponent(Component): @@ -40,7 +40,12 @@ def create(cls, *children, **props) -> Component: The created component. """ if children: - if len(children) == 1 and isinstance(children[0], str): + if len(children) == 1: + child = Var.create(children[0]).guess_type() + if not isinstance(child, StringVar): + raise AttributeError( + f"Icon name must be a string, got {children[0]._var_type if isinstance(children[0], Var) else children[0]}" + ) props["tag"] = children[0] else: raise AttributeError( @@ -56,7 +61,10 @@ def create(cls, *children, **props) -> Component: else: raise TypeError(f"Icon name must be a string, got {type(tag)}") elif isinstance(tag, Var): - return DynamicIcon.create(name=tag, **props) + tag_stringified = tag.guess_type() + if not isinstance(tag_stringified, StringVar): + raise TypeError(f"Icon name must be a string, got {tag._var_type}") + return DynamicIcon.create(name=tag_stringified.replace("_", "-"), **props) if ( not isinstance(tag, str) diff --git a/reflex/vars/base.py b/reflex/vars/base.py index c9dd81986ac..0d8af8f3c22 100644 --- a/reflex/vars/base.py +++ b/reflex/vars/base.py @@ -75,9 +75,9 @@ if TYPE_CHECKING: from reflex.state import BaseState - from .number import BooleanVar, NumberVar - from .object import ObjectVar - from .sequence import ArrayVar, StringVar + from .number import BooleanVar, LiteralBooleanVar, LiteralNumberVar, NumberVar + from .object import LiteralObjectVar, ObjectVar + from .sequence import ArrayVar, LiteralArrayVar, LiteralStringVar, StringVar VAR_TYPE = TypeVar("VAR_TYPE", covariant=True) @@ -573,13 +573,21 @@ def _replace( return value_with_replaced + @overload + @classmethod + def create( # pyright: ignore[reportOverlappingOverload] + cls, + value: NoReturn, + _var_data: VarData | None = None, + ) -> Var[Any]: ... + @overload @classmethod def create( # pyright: ignore[reportOverlappingOverload] cls, value: bool, _var_data: VarData | None = None, - ) -> BooleanVar: ... + ) -> LiteralBooleanVar: ... @overload @classmethod @@ -587,7 +595,7 @@ def create( cls, value: int, _var_data: VarData | None = None, - ) -> NumberVar[int]: ... + ) -> LiteralNumberVar[int]: ... @overload @classmethod @@ -595,7 +603,15 @@ def create( cls, value: float, _var_data: VarData | None = None, - ) -> NumberVar[float]: ... + ) -> LiteralNumberVar[float]: ... + + @overload + @classmethod + def create( # pyright: ignore [reportOverlappingOverload] + cls, + value: str, + _var_data: VarData | None = None, + ) -> LiteralStringVar: ... @overload @classmethod @@ -611,7 +627,7 @@ def create( # pyright: ignore[reportOverlappingOverload] cls, value: None, _var_data: VarData | None = None, - ) -> NoneVar: ... + ) -> LiteralNoneVar: ... @overload @classmethod @@ -619,7 +635,7 @@ def create( cls, value: MAPPING_TYPE, _var_data: VarData | None = None, - ) -> ObjectVar[MAPPING_TYPE]: ... + ) -> LiteralObjectVar[MAPPING_TYPE]: ... @overload @classmethod @@ -627,7 +643,7 @@ def create( cls, value: SEQUENCE_TYPE, _var_data: VarData | None = None, - ) -> ArrayVar[SEQUENCE_TYPE]: ... + ) -> LiteralArrayVar[SEQUENCE_TYPE]: ... @overload @classmethod diff --git a/reflex/vars/number.py b/reflex/vars/number.py index 35a55490a75..87f1760a6f1 100644 --- a/reflex/vars/number.py +++ b/reflex/vars/number.py @@ -974,7 +974,7 @@ def boolean_not_operation(value: BooleanVar): frozen=True, slots=True, ) -class LiteralNumberVar(LiteralVar, NumberVar): +class LiteralNumberVar(LiteralVar, NumberVar[NUMBER_T]): """Base class for immutable literal number vars.""" _var_value: float | int = dataclasses.field(default=0) diff --git a/reflex/vars/sequence.py b/reflex/vars/sequence.py index fb797b4ec18..0e7b082f982 100644 --- a/reflex/vars/sequence.py +++ b/reflex/vars/sequence.py @@ -372,6 +372,33 @@ def __ge__(self, other: Any): return string_ge_operation(self, other) + @overload + def replace( # pyright: ignore [reportOverlappingOverload] + self, search_value: StringVar | str, new_value: StringVar | str + ) -> StringVar: ... + + @overload + def replace( + self, search_value: Any, new_value: Any + ) -> CustomVarOperationReturn[StringVar]: ... + + def replace(self, search_value: Any, new_value: Any) -> StringVar: # pyright: ignore [reportInconsistentOverload] + """Replace a string with a value. + + Args: + search_value: The string to search. + new_value: The value to be replaced with. + + Returns: + The string replace operation. + """ + if not isinstance(search_value, (StringVar, str)): + raise_unsupported_operand_types("replace", (type(self), type(search_value))) + if not isinstance(new_value, (StringVar, str)): + raise_unsupported_operand_types("replace", (type(self), type(new_value))) + + return string_replace_operation(self, search_value, new_value) + @var_operation def string_lt_operation(lhs: StringVar[Any] | str, rhs: StringVar[Any] | str): @@ -570,7 +597,7 @@ def array_join_operation(array: ArrayVar, sep: StringVar[Any] | str = ""): @var_operation def string_replace_operation( - string: StringVar, search_value: StringVar | str, new_value: StringVar | str + string: StringVar[Any], search_value: StringVar | str, new_value: StringVar | str ): """Replace a string with a value. @@ -583,7 +610,7 @@ def string_replace_operation( The string replace operation. """ return var_operation_return( - js_expression=f"{string}.replace({search_value}, {new_value})", + js_expression=f"{string}.replaceAll({search_value}, {new_value})", var_type=str, ) diff --git a/tests/units/components/datadisplay/test_shiki_code.py b/tests/units/components/datadisplay/test_shiki_code.py index cc05c35b074..e1c7984f1df 100644 --- a/tests/units/components/datadisplay/test_shiki_code.py +++ b/tests/units/components/datadisplay/test_shiki_code.py @@ -11,6 +11,7 @@ from reflex.components.radix.themes.layout.box import Box from reflex.style import Style from reflex.vars import Var +from reflex.vars.base import LiteralVar @pytest.mark.parametrize( @@ -99,7 +100,9 @@ def test_create_shiki_code_block( applied_styles = component.style for key, value in expected_styles.items(): - assert Var.create(applied_styles[key])._var_value == value + var = Var.create(applied_styles[key]) + assert isinstance(var, LiteralVar) + assert var._var_value == value @pytest.mark.parametrize( diff --git a/tests/units/vars/test_object.py b/tests/units/vars/test_object.py index 90e34be96ca..89ace55bb9e 100644 --- a/tests/units/vars/test_object.py +++ b/tests/units/vars/test_object.py @@ -74,11 +74,11 @@ class ObjectState(rx.State): @pytest.mark.parametrize("type_", [Base, Bare, SqlaModel, Dataclass]) -def test_var_create(type_: GenericType) -> None: +def test_var_create(type_: type[Base | Bare | SqlaModel | Dataclass]) -> None: my_object = type_() var = Var.create(my_object) assert var._var_type is type_ - + assert isinstance(var, ObjectVar) quantity = var.quantity assert quantity._var_type is int @@ -94,12 +94,12 @@ def test_literal_create(type_: GenericType) -> None: @pytest.mark.parametrize("type_", [Base, Bare, SqlaModel, Dataclass]) -def test_guess(type_: GenericType) -> None: +def test_guess(type_: type[Base | Bare | SqlaModel | Dataclass]) -> None: my_object = type_() var = Var.create(my_object) var = var.guess_type() assert var._var_type is type_ - + assert isinstance(var, ObjectVar) quantity = var.quantity assert quantity._var_type is int