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

This commit is contained in:
Elijah Ahianyo 2023-09-01 20:01:11 +00:00 committed by GitHub
parent 1d9f25be6d
commit 829a7751b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 143 additions and 147 deletions

View File

@ -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]

View File

@ -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.

View File

@ -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",
[

View File

@ -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.