Просмотр исходного кода

Refactor: Move format_prop Static Method for Improved Reusability (#1714)

Elijah Ahianyo 1 год назад
Родитель
Сommit
829a7751b5
4 измененных файлов с 143 добавлено и 147 удалено
  1. 3 65
      reflex/components/tags/tag.py
  2. 58 1
      reflex/utils/format.py
  3. 1 80
      tests/components/test_tag.py
  4. 81 1
      tests/test_utils.py

+ 3 - 65
reflex/components/tags/tag.py

@@ -2,20 +2,13 @@
 
 from __future__ import annotations
 
-import json
-from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple, Union
-
-from plotly.graph_objects import Figure
-from plotly.io import to_json
+from typing import Any, Dict, List, Optional, Set, Tuple, Union
 
 from reflex.base import Base
-from reflex.event import EVENT_ARG, EventChain
+from reflex.event import EventChain
 from reflex.utils import format, types
 from reflex.vars import Var
 
-if TYPE_CHECKING:
-    from reflex.components.component import ComponentStyle
-
 
 class Tag(Base):
     """A React tag."""
@@ -52,61 +45,6 @@ class Tag(Base):
             }
         super().__init__(*args, **kwargs)
 
-    @staticmethod
-    def format_prop(
-        prop: Union[Var, EventChain, ComponentStyle, str],
-    ) -> Union[int, float, str]:
-        """Format a prop.
-
-        Args:
-            prop: The prop to format.
-
-        Returns:
-            The formatted prop to display within a tag.
-
-        Raises:
-            TypeError: If the prop is not a valid type.
-        """
-        try:
-            # Handle var props.
-            if isinstance(prop, Var):
-                if not prop.is_local or prop.is_string:
-                    return str(prop)
-                if types._issubclass(prop.type_, str):
-                    return format.format_string(prop.full_name)
-                prop = prop.full_name
-
-            # Handle event props.
-            elif isinstance(prop, EventChain):
-                chain = ",".join([format.format_event(event) for event in prop.events])
-                event = f"Event([{chain}], {EVENT_ARG})"
-                prop = f"{EVENT_ARG} => {event}"
-
-            # Handle other types.
-            elif isinstance(prop, str):
-                if format.is_wrapped(prop, "{"):
-                    return prop
-                return format.json_dumps(prop)
-
-            elif isinstance(prop, Figure):
-                prop = json.loads(to_json(prop))["data"]  # type: ignore
-
-            # For dictionaries, convert any properties to strings.
-            elif isinstance(prop, dict):
-                prop = format.format_dict(prop)
-
-            else:
-                # Dump the prop as JSON.
-                prop = format.json_dumps(prop)
-        except TypeError as e:
-            raise TypeError(
-                f"Could not format prop: {prop} of type {type(prop)}"
-            ) from e
-
-        # Wrap the variable in braces.
-        assert isinstance(prop, str), "The prop must be a string."
-        return format.wrap(prop, "{", check_first=False)
-
     def format_props(self) -> List:
         """Format the tag's props.
 
@@ -119,7 +57,7 @@ class Tag(Base):
 
         # Format all the props.
         return [
-            f"{name}={self.format_prop(prop)}"
+            f"{name}={format.format_prop(prop)}"
             for name, prop in sorted(self.props.items())
             if prop is not None
         ] + [str(prop) for prop in self.special_props]

+ 58 - 1
reflex/utils/format.py

@@ -9,9 +9,10 @@ import os
 import os.path as op
 import re
 import sys
-from typing import TYPE_CHECKING, Any, Type
+from typing import TYPE_CHECKING, Any, Type, Union
 
 import plotly.graph_objects as go
+from plotly.graph_objects import Figure
 from plotly.io import to_json
 
 from reflex import constants
@@ -258,6 +259,62 @@ def format_cond(
     return wrap(f"{cond} ? {true_value} : {false_value}", "{")
 
 
+def format_prop(
+    prop: Union[Var, EventChain, ComponentStyle, str],
+) -> Union[int, float, str]:
+    """Format a prop.
+
+    Args:
+        prop: The prop to format.
+
+    Returns:
+        The formatted prop to display within a tag.
+
+    Raises:
+        TypeError: If the prop is not a valid type.
+    """
+    # import here to avoid circular import.
+    from reflex.event import EVENT_ARG, EventChain
+
+    try:
+        # Handle var props.
+        if isinstance(prop, Var):
+            if not prop.is_local or prop.is_string:
+                return str(prop)
+            if types._issubclass(prop.type_, str):
+                return format_string(prop.full_name)
+            prop = prop.full_name
+
+        # Handle event props.
+        elif isinstance(prop, EventChain):
+            chain = ",".join([format_event(event) for event in prop.events])
+            event = f"Event([{chain}], {EVENT_ARG})"
+            prop = f"{EVENT_ARG} => {event}"
+
+        # Handle other types.
+        elif isinstance(prop, str):
+            if is_wrapped(prop, "{"):
+                return prop
+            return json_dumps(prop)
+
+        elif isinstance(prop, Figure):
+            prop = json.loads(to_json(prop))["data"]  # type: ignore
+
+        # For dictionaries, convert any properties to strings.
+        elif isinstance(prop, dict):
+            prop = format_dict(prop)
+
+        else:
+            # Dump the prop as JSON.
+            prop = json_dumps(prop)
+    except TypeError as e:
+        raise TypeError(f"Could not format prop: {prop} of type {type(prop)}") from e
+
+    # Wrap the variable in braces.
+    assert isinstance(prop, str), "The prop must be a string."
+    return wrap(prop, "{", check_first=False)
+
+
 def get_event_handler_parts(handler: EventHandler) -> tuple[str, str]:
     """Get the state and function name of an event handler.
 

+ 1 - 80
tests/components/test_tag.py

@@ -1,90 +1,11 @@
-from typing import Any, Dict, List
+from typing import Dict, List
 
 import pytest
 
 from reflex.components.tags import CondTag, Tag, tagless
-from reflex.event import EVENT_ARG, EventChain, EventHandler, EventSpec
-from reflex.style import Style
 from reflex.vars import BaseVar, Var
 
 
-def mock_event(arg):
-    pass
-
-
-@pytest.mark.parametrize(
-    "prop,formatted",
-    [
-        ("string", '"string"'),
-        ("{wrapped_string}", "{wrapped_string}"),
-        (True, "{true}"),
-        (False, "{false}"),
-        (123, "{123}"),
-        (3.14, "{3.14}"),
-        ([1, 2, 3], "{[1, 2, 3]}"),
-        (["a", "b", "c"], '{["a", "b", "c"]}'),
-        ({"a": 1, "b": 2, "c": 3}, '{{"a": 1, "b": 2, "c": 3}}'),
-        ({"a": 'foo "bar" baz'}, r'{{"a": "foo \"bar\" baz"}}'),
-        (
-            {
-                "a": 'foo "{ "bar" }" baz',
-                "b": BaseVar(name="val", type_="str"),
-            },
-            r'{{"a": "foo \"{ \"bar\" }\" baz", "b": val}}',
-        ),
-        (
-            EventChain(events=[EventSpec(handler=EventHandler(fn=mock_event))]),
-            '{_e => Event([E("mock_event", {})], _e)}',
-        ),
-        (
-            EventChain(
-                events=[
-                    EventSpec(
-                        handler=EventHandler(fn=mock_event),
-                        args=((Var.create_safe("arg"), EVENT_ARG.target.value),),
-                    )
-                ]
-            ),
-            '{_e => Event([E("mock_event", {arg:_e.target.value})], _e)}',
-        ),
-        ({"a": "red", "b": "blue"}, '{{"a": "red", "b": "blue"}}'),
-        (BaseVar(name="var", type_="int"), "{var}"),
-        (
-            BaseVar(
-                name="_",
-                type_=Any,
-                state="",
-                is_local=True,
-                is_string=False,
-            ),
-            "{_}",
-        ),
-        (BaseVar(name='state.colors["a"]', type_="str"), '{state.colors["a"]}'),
-        ({"a": BaseVar(name="val", type_="str")}, '{{"a": val}}'),
-        ({"a": BaseVar(name='"val"', type_="str")}, '{{"a": "val"}}'),
-        (
-            {"a": BaseVar(name='state.colors["val"]', type_="str")},
-            '{{"a": state.colors["val"]}}',
-        ),
-        # tricky real-world case from markdown component
-        (
-            {
-                "h1": f"{{({{node, ...props}}) => <Heading {{...props}} {''.join(Tag(name='', props=Style({'as_': 'h1'})).format_props())} />}}"
-            },
-            '{{"h1": ({node, ...props}) => <Heading {...props} as={`h1`} />}}',
-        ),
-    ],
-)
-def test_format_prop(prop: Var, formatted: str):
-    """Test that the formatted value of an prop is correct.
-
-    Args:
-        prop: The prop to test.
-        formatted: The expected formatted value.
-    """
-    assert Tag.format_prop(prop) == formatted
-
-
 @pytest.mark.parametrize(
     "props,test_props",
     [

+ 81 - 1
tests/test_utils.py

@@ -9,6 +9,9 @@ from packaging import version
 
 from reflex import constants
 from reflex.base import Base
+from reflex.components.tags import Tag
+from reflex.event import EVENT_ARG, EventChain, EventHandler, EventSpec
+from reflex.style import Style
 from reflex.utils import (
     build,
     format,
@@ -17,7 +20,11 @@ from reflex.utils import (
     types,
 )
 from reflex.utils import exec as utils_exec
-from reflex.vars import Var
+from reflex.vars import BaseVar, Var
+
+
+def mock_event(arg):
+    pass
 
 
 def get_above_max_version():
@@ -262,6 +269,79 @@ def test_format_route(route: str, expected: bool):
     assert format.format_route(route) == expected
 
 
+@pytest.mark.parametrize(
+    "prop,formatted",
+    [
+        ("string", '"string"'),
+        ("{wrapped_string}", "{wrapped_string}"),
+        (True, "{true}"),
+        (False, "{false}"),
+        (123, "{123}"),
+        (3.14, "{3.14}"),
+        ([1, 2, 3], "{[1, 2, 3]}"),
+        (["a", "b", "c"], '{["a", "b", "c"]}'),
+        ({"a": 1, "b": 2, "c": 3}, '{{"a": 1, "b": 2, "c": 3}}'),
+        ({"a": 'foo "bar" baz'}, r'{{"a": "foo \"bar\" baz"}}'),
+        (
+            {
+                "a": 'foo "{ "bar" }" baz',
+                "b": BaseVar(name="val", type_="str"),
+            },
+            r'{{"a": "foo \"{ \"bar\" }\" baz", "b": val}}',
+        ),
+        (
+            EventChain(events=[EventSpec(handler=EventHandler(fn=mock_event))]),
+            '{_e => Event([E("mock_event", {})], _e)}',
+        ),
+        (
+            EventChain(
+                events=[
+                    EventSpec(
+                        handler=EventHandler(fn=mock_event),
+                        args=((Var.create_safe("arg"), EVENT_ARG.target.value),),
+                    )
+                ]
+            ),
+            '{_e => Event([E("mock_event", {arg:_e.target.value})], _e)}',
+        ),
+        ({"a": "red", "b": "blue"}, '{{"a": "red", "b": "blue"}}'),
+        (BaseVar(name="var", type_="int"), "{var}"),
+        (
+            BaseVar(
+                name="_",
+                type_=Any,
+                state="",
+                is_local=True,
+                is_string=False,
+            ),
+            "{_}",
+        ),
+        (BaseVar(name='state.colors["a"]', type_="str"), '{state.colors["a"]}'),
+        ({"a": BaseVar(name="val", type_="str")}, '{{"a": val}}'),
+        ({"a": BaseVar(name='"val"', type_="str")}, '{{"a": "val"}}'),
+        (
+            {"a": BaseVar(name='state.colors["val"]', type_="str")},
+            '{{"a": state.colors["val"]}}',
+        ),
+        # tricky real-world case from markdown component
+        (
+            {
+                "h1": f"{{({{node, ...props}}) => <Heading {{...props}} {''.join(Tag(name='', props=Style({'as_': 'h1'})).format_props())} />}}"
+            },
+            '{{"h1": ({node, ...props}) => <Heading {...props} as={`h1`} />}}',
+        ),
+    ],
+)
+def test_format_prop(prop: Var, formatted: str):
+    """Test that the formatted value of an prop is correct.
+
+    Args:
+        prop: The prop to test.
+        formatted: The expected formatted value.
+    """
+    assert format.format_prop(prop) == formatted
+
+
 def test_validate_invalid_bun_path(mocker):
     """Test that an error is thrown when a custom specified bun path is not valid
     or does not exist.