From ec5f751f3fbabb9d2000f369840f9abab0ed78b5 Mon Sep 17 00:00:00 2001 From: Elijah Date: Mon, 4 Nov 2024 17:43:01 +0000 Subject: [PATCH] use ArgsFunctionOperation --- reflex/components/markdown/markdown.py | 34 +++++----- reflex/components/markdown/markdown.pyi | 10 ++- reflex/vars/function.py | 22 ++++++- .../components/markdown/test_markdown.py | 63 ++++++++++--------- tests/units/test_var.py | 12 ++++ 5 files changed, 95 insertions(+), 46 deletions(-) diff --git a/reflex/components/markdown/markdown.py b/reflex/components/markdown/markdown.py index dda6791bc..e4a3b0c47 100644 --- a/reflex/components/markdown/markdown.py +++ b/reflex/components/markdown/markdown.py @@ -6,13 +6,14 @@ import textwrap from functools import lru_cache from hashlib import md5 from typing import Any, Callable, Dict, Union +import dataclasses from reflex.components.component import Component, CustomComponent from reflex.components.tags.tag import Tag from reflex.utils import types from reflex.utils.imports import ImportDict, ImportVar 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 # 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: """Mixin class for handling custom component maps in Markdown components.""" + _explicit_return: bool = dataclasses.field(default=False) @classmethod def get_component_map_custom_code(cls) -> str: @@ -89,22 +92,23 @@ class MarkdownComponentMap: @classmethod 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: """Create a function Var for the component map. Args: fn_body: The formatted component as a string. fn_args: The function arguments. + explicit_return: Whether to use explicit return syntax. Returns: The function Var for the component map. """ fn_args = fn_args or cls.get_fn_args() - fn_body = fn_body or cls.get_fn_body() - fn_args_str = ", ".join(fn_args) + fn_body = fn_body if fn_body is not None else cls.get_fn_body() + 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 def get_fn_args(cls) -> list[str]: @@ -116,13 +120,13 @@ class MarkdownComponentMap: return ["node", _CHILDREN._js_expr, _PROPS._js_expr] @classmethod - def get_fn_body(cls) -> str: + def get_fn_body(cls) -> Var: """Get the function body for the component map. Returns: The function body as a string. """ - return "()" + return Var(_js_expr="", _var_type=str) class Markdown(Component): @@ -269,23 +273,24 @@ class Markdown(Component): codeblock_custom_code = "\n".join(custom_code_list) # Format the code to handle inline and block code. - formatted_code = f"""{{{codeblock_custom_code}; + formatted_code = f"""{codeblock_custom_code}; return inline ? ( {self.format_component("code")} ) : ( {self.format_component("codeblock", language=_LANGUAGE)} ); - }}""".replace("\n", " ") + """.replace("\n", " ") return MarkdownComponentMap.create_map_fn_var( - fn_args=[ + fn_args=( "node", "inline", "className", _CHILDREN._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: @@ -354,11 +359,12 @@ class Markdown(Component): Returns: The function Var for the component map. """ + formatted_component = Var(_js_expr=f"({self.format_component(tag)})", _var_type=str) 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. - 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]: """Recursively get markdown custom code from children components. diff --git a/reflex/components/markdown/markdown.pyi b/reflex/components/markdown/markdown.pyi index 5327bfe1d..a980068aa 100644 --- a/reflex/components/markdown/markdown.pyi +++ b/reflex/components/markdown/markdown.pyi @@ -3,6 +3,7 @@ # ------------------- DO NOT EDIT ---------------------- # This file was generated by `reflex/utils/pyi_generator.py`! # ------------------------------------------------------ +import dataclasses from functools import lru_cache from typing import Any, Callable, Dict, Optional, Union, overload @@ -28,18 +29,21 @@ NO_PROPS_TAGS = ("ul", "ol", "li") @lru_cache def get_base_component_map() -> dict[str, Callable]: ... - +@dataclasses.dataclass(frozen=True) class MarkdownComponentMap: @classmethod def get_component_map_custom_code(cls) -> str: ... @classmethod 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: ... @classmethod def get_fn_args(cls) -> list[str]: ... @classmethod - def get_fn_body(cls) -> str: ... + def get_fn_body(cls) -> Var: ... class Markdown(Component): @overload diff --git a/reflex/vars/function.py b/reflex/vars/function.py index 49ef99614..d60b3bce1 100644 --- a/reflex/vars/function.py +++ b/reflex/vars/function.py @@ -7,6 +7,7 @@ import sys from typing import Any, Callable, Optional, Tuple, Type, Union from reflex.utils.types import GenericType +from reflex.utils import format 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) _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 def _cached_var_name(self) -> str: @@ -144,21 +147,36 @@ class ArgsFunctionOperation(CachedVarOperation, FunctionVar): Returns: 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 def create( cls, args_names: Tuple[str, ...], return_expr: Var | Any, + destructure_args: bool = False, + explicit_return: bool = False, _var_type: GenericType = Callable, _var_data: VarData | None = None, + ) -> ArgsFunctionOperation: """Create a new function var. Args: args_names: The names of the arguments. 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. Returns: @@ -170,6 +188,8 @@ class ArgsFunctionOperation(CachedVarOperation, FunctionVar): _var_data=_var_data, _args_names=args_names, _return_expr=return_expr, + _destructure_args=destructure_args, + _explicit_return=explicit_return ) diff --git a/tests/units/components/markdown/test_markdown.py b/tests/units/components/markdown/test_markdown.py index 7aeb0fbab..95535f37a 100644 --- a/tests/units/components/markdown/test_markdown.py +++ b/tests/units/components/markdown/test_markdown.py @@ -18,22 +18,22 @@ class CustomMarkdownComponent(Component, MarkdownComponentMap): library = "custom" @classmethod - def get_fn_args(cls) -> list[str]: + def get_fn_args(cls) -> tuple[str, ...]: """Return the function arguments. Returns: The function arguments. """ - return ["custom_node", "custom_children", "custom_props"] + return ("custom_node", "custom_children", "custom_props") @classmethod - def get_fn_body(cls) -> str: + def get_fn_body(cls) -> Var: """Return the function body. Returns: 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]): @@ -58,67 +58,74 @@ def syntax_highlighter_memoized_component(codeblock: Type[Component]): @pytest.mark.parametrize( - "fn_body, fn_args, expected", + "fn_body, fn_args, explicit_return, expected", [ - (None, None, Var(_js_expr="(({node, children, ...props}) => ())")), - ("{return node;}", ["node"], Var(_js_expr="(({node}) => {return node;})")), + (None, None, False, Var(_js_expr="(({node, children, ...props}) => ())")), + ("return node", ("node", ), True, Var(_js_expr="(({node}) => {return node})")), ( - "{return node + children;}", - ["node", "children"], - Var(_js_expr="(({node, children}) => {return node + children;})"), + "return node + children", + ("node", "children"), + True, + Var(_js_expr="(({node, children}) => {return node + children})"), ), ( - "{return node + props;}", - ["node", "...props"], - Var(_js_expr="(({node, ...props}) => {return node + props;})"), + "return node + props", + ("node", "...props"), + True, + Var(_js_expr="(({node, ...props}) => {return node + props})"), ), ( - "{return node + children + props;}", - ["node", "children", "...props"], + "return node + children + props", + ("node", "children", "...props"), + True, 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): - result = MarkdownComponentMap.create_map_fn_var(fn_body, fn_args) +def test_create_map_fn_var(fn_body, fn_args, explicit_return, expected): + 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 @pytest.mark.parametrize( - "cls, fn_body, fn_args, expected", + "cls, fn_body, fn_args, explicit_return, expected", [ ( MarkdownComponentMap, None, None, + False, Var(_js_expr="(({node, children, ...props}) => ())"), ), ( MarkdownComponentMap, - "{return node};", - ["node"], - Var(_js_expr="(({node}) => {return node};)"), + "return node", + ("node", ), + True, + Var(_js_expr="(({node}) => {return node})"), ), ( CustomMarkdownComponent, None, None, + True, 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, - "{return custom_node;}", - ["custom_node"], - Var(_js_expr="(({custom_node}) => {return custom_node;})"), + "return custom_node", + ("custom_node",), + True, + Var(_js_expr="(({custom_node}) => {return custom_node})"), ), ], ) -def test_create_map_fn_var_subclass(cls, fn_body, fn_args, expected): - result = cls.create_map_fn_var(fn_body, fn_args) +def test_create_map_fn_var_subclass(cls, fn_body, fn_args, explicit_return, expected): + 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 diff --git a/tests/units/test_var.py b/tests/units/test_var.py index 8ff829eac..64cad6c00 100644 --- a/tests/units/test_var.py +++ b/tests/units/test_var.py @@ -940,6 +940,18 @@ def test_function_var(): == '(((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(): @var_operation