Refactor: Move format_prop Static Method for Improved Reusability (#1714)
This commit is contained in:
parent
1d9f25be6d
commit
829a7751b5
@ -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]
|
||||
|
@ -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,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",
|
||||
[
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user