Переглянути джерело

fix dynamic icons for underscore and positional argument (#4767)

* fix dynamic icons for underscore and positional argument

* use no return
Khaleel Al-Adhami 3 місяців тому
батько
коміт
f3220470e8

+ 1 - 3
reflex/components/core/sticky.py

@@ -107,9 +107,7 @@ class StickyBadge(A):
             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(),
         )

+ 11 - 3
reflex/components/lucide/icon.py

@@ -4,7 +4,7 @@ from reflex.components.component import Component
 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 @@ class Icon(LucideIconComponent):
             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 @@ class Icon(LucideIconComponent):
             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)

+ 25 - 9
reflex/vars/base.py

@@ -75,9 +75,9 @@ from reflex.utils.types import (
 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 @@ class Var(Generic[VAR_TYPE]):
 
         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 @@ class Var(Generic[VAR_TYPE]):
         cls,
         value: int,
         _var_data: VarData | None = None,
-    ) -> NumberVar[int]: ...
+    ) -> LiteralNumberVar[int]: ...
 
     @overload
     @classmethod
@@ -595,7 +603,15 @@ class Var(Generic[VAR_TYPE]):
         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 @@ class Var(Generic[VAR_TYPE]):
         cls,
         value: None,
         _var_data: VarData | None = None,
-    ) -> NoneVar: ...
+    ) -> LiteralNoneVar: ...
 
     @overload
     @classmethod
@@ -619,7 +635,7 @@ class Var(Generic[VAR_TYPE]):
         cls,
         value: MAPPING_TYPE,
         _var_data: VarData | None = None,
-    ) -> ObjectVar[MAPPING_TYPE]: ...
+    ) -> LiteralObjectVar[MAPPING_TYPE]: ...
 
     @overload
     @classmethod
@@ -627,7 +643,7 @@ class Var(Generic[VAR_TYPE]):
         cls,
         value: SEQUENCE_TYPE,
         _var_data: VarData | None = None,
-    ) -> ArrayVar[SEQUENCE_TYPE]: ...
+    ) -> LiteralArrayVar[SEQUENCE_TYPE]: ...
 
     @overload
     @classmethod

+ 1 - 1
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)

+ 29 - 2
reflex/vars/sequence.py

@@ -372,6 +372,33 @@ class StringVar(Var[STRING_TYPE], python_types=str):
 
         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,
     )
 

+ 4 - 1
tests/units/components/datadisplay/test_shiki_code.py

@@ -11,6 +11,7 @@ from reflex.components.lucide.icon import Icon
 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(

+ 4 - 4
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