use ArgsFunctionOperation

This commit is contained in:
Elijah 2024-11-04 17:43:01 +00:00
parent dc18101118
commit ec5f751f3f
5 changed files with 95 additions and 46 deletions

View File

@ -6,13 +6,14 @@ import textwrap
from functools import lru_cache from functools import lru_cache
from hashlib import md5 from hashlib import md5
from typing import Any, Callable, Dict, Union from typing import Any, Callable, Dict, Union
import dataclasses
from reflex.components.component import Component, CustomComponent from reflex.components.component import Component, CustomComponent
from reflex.components.tags.tag import Tag from reflex.components.tags.tag import Tag
from reflex.utils import types from reflex.utils import types
from reflex.utils.imports import ImportDict, ImportVar from reflex.utils.imports import ImportDict, ImportVar
from reflex.vars.base import LiteralVar, Var from reflex.vars.base import LiteralVar, Var
from reflex.vars.function import ARRAY_ISARRAY from reflex.vars.function import ARRAY_ISARRAY, ArgsFunctionOperation
from reflex.vars.number import ternary_operation from reflex.vars.number import ternary_operation
# Special vars used in the component map. # Special vars used in the component map.
@ -75,8 +76,10 @@ def get_base_component_map() -> dict[str, Callable]:
} }
@dataclasses.dataclass(frozen=True)
class MarkdownComponentMap: class MarkdownComponentMap:
"""Mixin class for handling custom component maps in Markdown components.""" """Mixin class for handling custom component maps in Markdown components."""
_explicit_return: bool = dataclasses.field(default=False)
@classmethod @classmethod
def get_component_map_custom_code(cls) -> str: def get_component_map_custom_code(cls) -> str:
@ -89,22 +92,23 @@ class MarkdownComponentMap:
@classmethod @classmethod
def create_map_fn_var( def create_map_fn_var(
cls, fn_body: str | None = None, fn_args: list[str] | None = None cls, fn_body: Var | None = None, fn_args: tuple[str, ...] | None = None, explicit_return: bool | None = None
) -> Var: ) -> Var:
"""Create a function Var for the component map. """Create a function Var for the component map.
Args: Args:
fn_body: The formatted component as a string. fn_body: The formatted component as a string.
fn_args: The function arguments. fn_args: The function arguments.
explicit_return: Whether to use explicit return syntax.
Returns: Returns:
The function Var for the component map. The function Var for the component map.
""" """
fn_args = fn_args or cls.get_fn_args() fn_args = fn_args or cls.get_fn_args()
fn_body = fn_body or cls.get_fn_body() fn_body = fn_body if fn_body is not None else cls.get_fn_body()
fn_args_str = ", ".join(fn_args) explicit_return = explicit_return or cls._explicit_return
return Var(_js_expr=f"(({{{fn_args_str}}}) => {fn_body})") return ArgsFunctionOperation.create(args_names=fn_args, return_expr=fn_body, destructure_args=True, explicit_return=explicit_return)
@classmethod @classmethod
def get_fn_args(cls) -> list[str]: def get_fn_args(cls) -> list[str]:
@ -116,13 +120,13 @@ class MarkdownComponentMap:
return ["node", _CHILDREN._js_expr, _PROPS._js_expr] return ["node", _CHILDREN._js_expr, _PROPS._js_expr]
@classmethod @classmethod
def get_fn_body(cls) -> str: def get_fn_body(cls) -> Var:
"""Get the function body for the component map. """Get the function body for the component map.
Returns: Returns:
The function body as a string. The function body as a string.
""" """
return "()" return Var(_js_expr="", _var_type=str)
class Markdown(Component): class Markdown(Component):
@ -269,23 +273,24 @@ class Markdown(Component):
codeblock_custom_code = "\n".join(custom_code_list) codeblock_custom_code = "\n".join(custom_code_list)
# Format the code to handle inline and block code. # Format the code to handle inline and block code.
formatted_code = f"""{{{codeblock_custom_code}; formatted_code = f"""{codeblock_custom_code};
return inline ? ( return inline ? (
{self.format_component("code")} {self.format_component("code")}
) : ( ) : (
{self.format_component("codeblock", language=_LANGUAGE)} {self.format_component("codeblock", language=_LANGUAGE)}
); );
}}""".replace("\n", " ") """.replace("\n", " ")
return MarkdownComponentMap.create_map_fn_var( return MarkdownComponentMap.create_map_fn_var(
fn_args=[ fn_args=(
"node", "node",
"inline", "inline",
"className", "className",
_CHILDREN._js_expr, _CHILDREN._js_expr,
_PROPS._js_expr, _PROPS._js_expr,
], ),
fn_body=formatted_code, fn_body=Var(_js_expr=formatted_code),
explicit_return=True
) )
def get_component(self, tag: str, **props) -> Component: def get_component(self, tag: str, **props) -> Component:
@ -354,11 +359,12 @@ class Markdown(Component):
Returns: Returns:
The function Var for the component map. The function Var for the component map.
""" """
formatted_component = Var(_js_expr=f"({self.format_component(tag)})", _var_type=str)
if isinstance(component, MarkdownComponentMap): if isinstance(component, MarkdownComponentMap):
return component.create_map_fn_var(f"({self.format_component(tag)})") return component.create_map_fn_var(fn_body=formatted_component)
# fallback to the default fn Var creation if the component is not a MarkdownComponentMap. # fallback to the default fn Var creation if the component is not a MarkdownComponentMap.
return MarkdownComponentMap.create_map_fn_var(f"({self.format_component(tag)})") return MarkdownComponentMap.create_map_fn_var(fn_body=formatted_component)
def _get_map_fn_custom_code_from_children(self, component) -> list[str]: def _get_map_fn_custom_code_from_children(self, component) -> list[str]:
"""Recursively get markdown custom code from children components. """Recursively get markdown custom code from children components.

View File

@ -3,6 +3,7 @@
# ------------------- DO NOT EDIT ---------------------- # ------------------- DO NOT EDIT ----------------------
# This file was generated by `reflex/utils/pyi_generator.py`! # This file was generated by `reflex/utils/pyi_generator.py`!
# ------------------------------------------------------ # ------------------------------------------------------
import dataclasses
from functools import lru_cache from functools import lru_cache
from typing import Any, Callable, Dict, Optional, Union, overload from typing import Any, Callable, Dict, Optional, Union, overload
@ -28,18 +29,21 @@ NO_PROPS_TAGS = ("ul", "ol", "li")
@lru_cache @lru_cache
def get_base_component_map() -> dict[str, Callable]: ... def get_base_component_map() -> dict[str, Callable]: ...
@dataclasses.dataclass(frozen=True)
class MarkdownComponentMap: class MarkdownComponentMap:
@classmethod @classmethod
def get_component_map_custom_code(cls) -> str: ... def get_component_map_custom_code(cls) -> str: ...
@classmethod @classmethod
def create_map_fn_var( def create_map_fn_var(
cls, fn_body: str | None = None, fn_args: list[str] | None = None cls,
fn_body: Var | None = None,
fn_args: tuple[str, ...] | None = None,
explicit_return: bool | None = None,
) -> Var: ... ) -> Var: ...
@classmethod @classmethod
def get_fn_args(cls) -> list[str]: ... def get_fn_args(cls) -> list[str]: ...
@classmethod @classmethod
def get_fn_body(cls) -> str: ... def get_fn_body(cls) -> Var: ...
class Markdown(Component): class Markdown(Component):
@overload @overload

View File

@ -7,6 +7,7 @@ import sys
from typing import Any, Callable, Optional, Tuple, Type, Union from typing import Any, Callable, Optional, Tuple, Type, Union
from reflex.utils.types import GenericType from reflex.utils.types import GenericType
from reflex.utils import format
from .base import CachedVarOperation, LiteralVar, Var, VarData, cached_property_no_lock from .base import CachedVarOperation, LiteralVar, Var, VarData, cached_property_no_lock
@ -136,6 +137,8 @@ class ArgsFunctionOperation(CachedVarOperation, FunctionVar):
_args_names: Tuple[str, ...] = dataclasses.field(default_factory=tuple) _args_names: Tuple[str, ...] = dataclasses.field(default_factory=tuple)
_return_expr: Union[Var, Any] = dataclasses.field(default=None) _return_expr: Union[Var, Any] = dataclasses.field(default=None)
_destructure_args: bool = dataclasses.field(default=False)
_explicit_return: bool = dataclasses.field(default=True)
@cached_property_no_lock @cached_property_no_lock
def _cached_var_name(self) -> str: def _cached_var_name(self) -> str:
@ -144,21 +147,36 @@ class ArgsFunctionOperation(CachedVarOperation, FunctionVar):
Returns: Returns:
The name of the var. The name of the var.
""" """
return f"(({', '.join(self._args_names)}) => ({str(LiteralVar.create(self._return_expr))}))" arg_names_str = ", ".join(self._args_names)
return_expr_str = str(LiteralVar.create(self._return_expr))
if self._destructure_args:
arg_names_str = format.wrap(arg_names_str, "{", "}")
# Wrap return expression in curly braces if explicit return syntax is used.
return_expr_str = format.wrap(return_expr_str, "{", "}") if self._explicit_return else format.wrap(return_expr_str, "(", ")")
return f"(({arg_names_str}) => {return_expr_str})"
@classmethod @classmethod
def create( def create(
cls, cls,
args_names: Tuple[str, ...], args_names: Tuple[str, ...],
return_expr: Var | Any, return_expr: Var | Any,
destructure_args: bool = False,
explicit_return: bool = False,
_var_type: GenericType = Callable, _var_type: GenericType = Callable,
_var_data: VarData | None = None, _var_data: VarData | None = None,
) -> ArgsFunctionOperation: ) -> ArgsFunctionOperation:
"""Create a new function var. """Create a new function var.
Args: Args:
args_names: The names of the arguments. args_names: The names of the arguments.
return_expr: The return expression of the function. return_expr: The return expression of the function.
destructure_args: Whether to destructure the arguments.
explicit_return: Whether to use explicit return syntax.
_var_data: Additional hooks and imports associated with the Var. _var_data: Additional hooks and imports associated with the Var.
Returns: Returns:
@ -170,6 +188,8 @@ class ArgsFunctionOperation(CachedVarOperation, FunctionVar):
_var_data=_var_data, _var_data=_var_data,
_args_names=args_names, _args_names=args_names,
_return_expr=return_expr, _return_expr=return_expr,
_destructure_args=destructure_args,
_explicit_return=explicit_return
) )

View File

@ -18,22 +18,22 @@ class CustomMarkdownComponent(Component, MarkdownComponentMap):
library = "custom" library = "custom"
@classmethod @classmethod
def get_fn_args(cls) -> list[str]: def get_fn_args(cls) -> tuple[str, ...]:
"""Return the function arguments. """Return the function arguments.
Returns: Returns:
The function arguments. The function arguments.
""" """
return ["custom_node", "custom_children", "custom_props"] return ("custom_node", "custom_children", "custom_props")
@classmethod @classmethod
def get_fn_body(cls) -> str: def get_fn_body(cls) -> Var:
"""Return the function body. """Return the function body.
Returns: Returns:
The function body. The function body.
""" """
return "{return custom_node + custom_children + custom_props;}" return Var(_js_expr="{return custom_node + custom_children + custom_props}")
def syntax_highlighter_memoized_component(codeblock: Type[Component]): def syntax_highlighter_memoized_component(codeblock: Type[Component]):
@ -58,67 +58,74 @@ def syntax_highlighter_memoized_component(codeblock: Type[Component]):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"fn_body, fn_args, expected", "fn_body, fn_args, explicit_return, expected",
[ [
(None, None, Var(_js_expr="(({node, children, ...props}) => ())")), (None, None, False, Var(_js_expr="(({node, children, ...props}) => ())")),
("{return node;}", ["node"], Var(_js_expr="(({node}) => {return node;})")), ("return node", ("node", ), True, Var(_js_expr="(({node}) => {return node})")),
( (
"{return node + children;}", "return node + children",
["node", "children"], ("node", "children"),
Var(_js_expr="(({node, children}) => {return node + children;})"), True,
Var(_js_expr="(({node, children}) => {return node + children})"),
), ),
( (
"{return node + props;}", "return node + props",
["node", "...props"], ("node", "...props"),
Var(_js_expr="(({node, ...props}) => {return node + props;})"), True,
Var(_js_expr="(({node, ...props}) => {return node + props})"),
), ),
( (
"{return node + children + props;}", "return node + children + props",
["node", "children", "...props"], ("node", "children", "...props"),
True,
Var( Var(
_js_expr="(({node, children, ...props}) => {return node + children + props;})" _js_expr="(({node, children, ...props}) => {return node + children + props})"
), ),
), ),
], ],
) )
def test_create_map_fn_var(fn_body, fn_args, expected): def test_create_map_fn_var(fn_body, fn_args, explicit_return, expected):
result = MarkdownComponentMap.create_map_fn_var(fn_body, fn_args) result = MarkdownComponentMap.create_map_fn_var(fn_body= Var(_js_expr=fn_body,_var_type=str) if fn_body else None, fn_args=fn_args, explicit_return=explicit_return)
assert result._js_expr == expected._js_expr assert result._js_expr == expected._js_expr
@pytest.mark.parametrize( @pytest.mark.parametrize(
"cls, fn_body, fn_args, expected", "cls, fn_body, fn_args, explicit_return, expected",
[ [
( (
MarkdownComponentMap, MarkdownComponentMap,
None, None,
None, None,
False,
Var(_js_expr="(({node, children, ...props}) => ())"), Var(_js_expr="(({node, children, ...props}) => ())"),
), ),
( (
MarkdownComponentMap, MarkdownComponentMap,
"{return node};", "return node",
["node"], ("node", ),
Var(_js_expr="(({node}) => {return node};)"), True,
Var(_js_expr="(({node}) => {return node})"),
), ),
( (
CustomMarkdownComponent, CustomMarkdownComponent,
None, None,
None, None,
True,
Var( Var(
_js_expr="(({custom_node, custom_children, custom_props}) => {return custom_node + custom_children + custom_props;})" _js_expr="(({custom_node, custom_children, custom_props}) => {return custom_node + custom_children + custom_props})"
), ),
), ),
( (
CustomMarkdownComponent, CustomMarkdownComponent,
"{return custom_node;}", "return custom_node",
["custom_node"], ("custom_node",),
Var(_js_expr="(({custom_node}) => {return custom_node;})"), True,
Var(_js_expr="(({custom_node}) => {return custom_node})"),
), ),
], ],
) )
def test_create_map_fn_var_subclass(cls, fn_body, fn_args, expected): def test_create_map_fn_var_subclass(cls, fn_body, fn_args, explicit_return, expected):
result = cls.create_map_fn_var(fn_body, fn_args) result = cls.create_map_fn_var(fn_body= Var(_js_expr=fn_body, _var_type=int) if fn_body else None, fn_args=fn_args, explicit_return=explicit_return)
assert result._js_expr == expected._js_expr assert result._js_expr == expected._js_expr

View File

@ -940,6 +940,18 @@ def test_function_var():
== '(((name) => (("Hello, "+name+"!")))("Steven Universe"))' == '(((name) => (("Hello, "+name+"!")))("Steven Universe"))'
) )
# Test with destructured arguments
destructured_func = ArgsFunctionOperation.create(
("a","b"), Var(_js_expr="a + b"), destructure_args=True
)
assert str(destructured_func.call({"a": 1, "b": 2})) == '(({a, b}) => (a + b))({"a": 1, "b": 2})'
# Test with explicit return
explicit_return_func = ArgsFunctionOperation.create(
("a", "b"), Var(_js_expr="return a + b"), explicit_return=True
)
assert str(explicit_return_func.call(1, 2)) == '((a, b) => {return a + b})(1, 2)'
def test_var_operation(): def test_var_operation():
@var_operation @var_operation