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
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
from typing import Any, Dict, List, Optional, Set, Tuple, Union
|
||||||
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 reflex.base import Base
|
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.utils import format, types
|
||||||
from reflex.vars import Var
|
from reflex.vars import Var
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from reflex.components.component import ComponentStyle
|
|
||||||
|
|
||||||
|
|
||||||
class Tag(Base):
|
class Tag(Base):
|
||||||
"""A React tag."""
|
"""A React tag."""
|
||||||
@ -52,61 +45,6 @@ class Tag(Base):
|
|||||||
}
|
}
|
||||||
super().__init__(*args, **kwargs)
|
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:
|
def format_props(self) -> List:
|
||||||
"""Format the tag's props.
|
"""Format the tag's props.
|
||||||
|
|
||||||
@ -119,7 +57,7 @@ class Tag(Base):
|
|||||||
|
|
||||||
# Format all the props.
|
# Format all the props.
|
||||||
return [
|
return [
|
||||||
f"{name}={self.format_prop(prop)}"
|
f"{name}={format.format_prop(prop)}"
|
||||||
for name, prop in sorted(self.props.items())
|
for name, prop in sorted(self.props.items())
|
||||||
if prop is not None
|
if prop is not None
|
||||||
] + [str(prop) for prop in self.special_props]
|
] + [str(prop) for prop in self.special_props]
|
||||||
|
@ -9,9 +9,10 @@ import os
|
|||||||
import os.path as op
|
import os.path as op
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
from typing import TYPE_CHECKING, Any, Type
|
from typing import TYPE_CHECKING, Any, Type, Union
|
||||||
|
|
||||||
import plotly.graph_objects as go
|
import plotly.graph_objects as go
|
||||||
|
from plotly.graph_objects import Figure
|
||||||
from plotly.io import to_json
|
from plotly.io import to_json
|
||||||
|
|
||||||
from reflex import constants
|
from reflex import constants
|
||||||
@ -258,6 +259,62 @@ def format_cond(
|
|||||||
return wrap(f"{cond} ? {true_value} : {false_value}", "{")
|
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]:
|
def get_event_handler_parts(handler: EventHandler) -> tuple[str, str]:
|
||||||
"""Get the state and function name of an event handler.
|
"""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
|
import pytest
|
||||||
|
|
||||||
from reflex.components.tags import CondTag, Tag, tagless
|
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
|
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(
|
@pytest.mark.parametrize(
|
||||||
"props,test_props",
|
"props,test_props",
|
||||||
[
|
[
|
||||||
|
@ -9,6 +9,9 @@ from packaging import version
|
|||||||
|
|
||||||
from reflex import constants
|
from reflex import constants
|
||||||
from reflex.base import Base
|
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 (
|
from reflex.utils import (
|
||||||
build,
|
build,
|
||||||
format,
|
format,
|
||||||
@ -17,7 +20,11 @@ from reflex.utils import (
|
|||||||
types,
|
types,
|
||||||
)
|
)
|
||||||
from reflex.utils import exec as utils_exec
|
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():
|
def get_above_max_version():
|
||||||
@ -262,6 +269,79 @@ def test_format_route(route: str, expected: bool):
|
|||||||
assert format.format_route(route) == expected
|
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):
|
def test_validate_invalid_bun_path(mocker):
|
||||||
"""Test that an error is thrown when a custom specified bun path is not valid
|
"""Test that an error is thrown when a custom specified bun path is not valid
|
||||||
or does not exist.
|
or does not exist.
|
||||||
|
Loading…
Reference in New Issue
Block a user