use destructured args and pass the tests

This commit is contained in:
Khaleel Al-Adhami 2024-11-05 18:29:25 -08:00 committed by Elijah
parent ec5f751f3f
commit 112ecdc2e3
8 changed files with 123 additions and 54 deletions

View File

@ -2,18 +2,18 @@
from __future__ import annotations from __future__ import annotations
import dataclasses
import textwrap 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, Sequence, 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, ArgsFunctionOperation from reflex.vars.function import ARRAY_ISARRAY, ArgsFunctionOperation, DestructuredArg
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.
@ -79,6 +79,7 @@ def get_base_component_map() -> dict[str, Callable]:
@dataclasses.dataclass(frozen=True) @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) _explicit_return: bool = dataclasses.field(default=False)
@classmethod @classmethod
@ -92,7 +93,10 @@ class MarkdownComponentMap:
@classmethod @classmethod
def create_map_fn_var( def create_map_fn_var(
cls, fn_body: Var | None = None, fn_args: tuple[str, ...] | None = None, explicit_return: bool | None = None cls,
fn_body: Var | None = None,
fn_args: Sequence[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.
@ -108,10 +112,14 @@ class MarkdownComponentMap:
fn_body = fn_body if fn_body is not None else cls.get_fn_body() fn_body = fn_body if fn_body is not None else cls.get_fn_body()
explicit_return = explicit_return or cls._explicit_return explicit_return = explicit_return or cls._explicit_return
return ArgsFunctionOperation.create(args_names=fn_args, return_expr=fn_body, destructure_args=True, explicit_return=explicit_return) return ArgsFunctionOperation.create(
args_names=(DestructuredArg(fields=tuple(fn_args)),),
return_expr=fn_body,
explicit_return=explicit_return,
)
@classmethod @classmethod
def get_fn_args(cls) -> list[str]: def get_fn_args(cls) -> Sequence[str]:
"""Get the function arguments for the component map. """Get the function arguments for the component map.
Returns: Returns:
@ -126,7 +134,7 @@ class MarkdownComponentMap:
Returns: Returns:
The function body as a string. The function body as a string.
""" """
return Var(_js_expr="", _var_type=str) return Var(_js_expr="undefined", _var_type=None)
class Markdown(Component): class Markdown(Component):
@ -290,7 +298,7 @@ class Markdown(Component):
_PROPS._js_expr, _PROPS._js_expr,
), ),
fn_body=Var(_js_expr=formatted_code), fn_body=Var(_js_expr=formatted_code),
explicit_return=True explicit_return=True,
) )
def get_component(self, tag: str, **props) -> Component: def get_component(self, tag: str, **props) -> Component:
@ -359,7 +367,9 @@ 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) 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(fn_body=formatted_component) return component.create_map_fn_var(fn_body=formatted_component)

View File

@ -45,6 +45,7 @@ from reflex.vars import VarData
from reflex.vars.base import LiteralVar, Var from reflex.vars.base import LiteralVar, Var
from reflex.vars.function import ( from reflex.vars.function import (
ArgsFunctionOperation, ArgsFunctionOperation,
FunctionArgs,
FunctionStringVar, FunctionStringVar,
FunctionVar, FunctionVar,
VarOperationCall, VarOperationCall,
@ -1643,7 +1644,7 @@ class LiteralEventChainVar(ArgsFunctionOperation, LiteralVar, EventChainVar):
_js_expr="", _js_expr="",
_var_type=EventChain, _var_type=EventChain,
_var_data=_var_data, _var_data=_var_data,
_args_names=arg_def, _args=FunctionArgs(arg_def),
_return_expr=invocation.call( _return_expr=invocation.call(
LiteralVar.create([LiteralVar.create(event) for event in value.events]), LiteralVar.create([LiteralVar.create(event) for event in value.events]),
arg_def_expr, arg_def_expr,

View File

@ -4,10 +4,10 @@ from __future__ import annotations
import dataclasses import dataclasses
import sys import sys
from typing import Any, Callable, Optional, Tuple, Type, Union from typing import Any, Callable, Optional, Sequence, Tuple, Type, Union
from reflex.utils.types import GenericType
from reflex.utils import format from reflex.utils import format
from reflex.utils.types import GenericType
from .base import CachedVarOperation, LiteralVar, Var, VarData, cached_property_no_lock from .base import CachedVarOperation, LiteralVar, Var, VarData, cached_property_no_lock
@ -127,6 +127,36 @@ class VarOperationCall(CachedVarOperation, Var):
) )
@dataclasses.dataclass(frozen=True)
class DestructuredArg:
"""Class for destructured arguments."""
fields: Tuple[str, ...] = tuple()
rest: Optional[str] = None
def to_javascript(self) -> str:
"""Convert the destructured argument to JavaScript.
Returns:
The destructured argument in JavaScript.
"""
return format.wrap(
", ".join(self.fields) + (f", ...{self.rest}" if self.rest else ""),
"{",
"}",
)
@dataclasses.dataclass(
frozen=True,
)
class FunctionArgs:
"""Class for function arguments."""
args: Tuple[Union[str, DestructuredArg], ...] = tuple()
rest: Optional[str] = None
@dataclasses.dataclass( @dataclasses.dataclass(
eq=False, eq=False,
frozen=True, frozen=True,
@ -135,10 +165,9 @@ class VarOperationCall(CachedVarOperation, Var):
class ArgsFunctionOperation(CachedVarOperation, FunctionVar): class ArgsFunctionOperation(CachedVarOperation, FunctionVar):
"""Base class for immutable function defined via arguments and return expression.""" """Base class for immutable function defined via arguments and return expression."""
_args_names: Tuple[str, ...] = dataclasses.field(default_factory=tuple) _args: FunctionArgs = dataclasses.field(default_factory=FunctionArgs)
_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=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:
@ -147,35 +176,40 @@ class ArgsFunctionOperation(CachedVarOperation, FunctionVar):
Returns: Returns:
The name of the var. The name of the var.
""" """
arg_names_str = ", ".join(self._args_names) arg_names_str = ", ".join(
[
arg if isinstance(arg, str) else arg.to_javascript()
for arg in self._args.args
]
) + (f", ...{self._args.rest}" if self._args.rest else "")
return_expr_str = str(LiteralVar.create(self._return_expr)) 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. # 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_expr_str_wrapped = (
format.wrap(return_expr_str, "{", "}")
return f"(({arg_names_str}) => {return_expr_str})" if self._explicit_return
else return_expr_str
)
return f"(({arg_names_str}) => {return_expr_str_wrapped})"
@classmethod @classmethod
def create( def create(
cls, cls,
args_names: Tuple[str, ...], args_names: Sequence[Union[str, DestructuredArg]],
return_expr: Var | Any, return_expr: Var | Any,
destructure_args: bool = False, rest: str | None = None,
explicit_return: 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. rest: The name of the rest argument.
explicit_return: Whether to use explicit return syntax. 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.
@ -186,10 +220,9 @@ class ArgsFunctionOperation(CachedVarOperation, FunctionVar):
_js_expr="", _js_expr="",
_var_type=_var_type, _var_type=_var_type,
_var_data=_var_data, _var_data=_var_data,
_args_names=args_names, _args=FunctionArgs(args=tuple(args_names), rest=rest),
_return_expr=return_expr, _return_expr=return_expr,
_destructure_args=destructure_args, _explicit_return=explicit_return,
_explicit_return=explicit_return
) )

View File

@ -62,14 +62,14 @@ def test_script_event_handler():
) )
render_dict = component.render() render_dict = component.render()
assert ( assert (
f'onReady={{((...args) => ((addEvents([(Event("{EvState.get_full_name()}.on_ready", ({{ }}), ({{ }})))], args, ({{ }})))))}}' f'onReady={{((...args) => (addEvents([(Event("{EvState.get_full_name()}.on_ready", ({{ }}), ({{ }})))], args, ({{ }}))))}}'
in render_dict["props"] in render_dict["props"]
) )
assert ( assert (
f'onLoad={{((...args) => ((addEvents([(Event("{EvState.get_full_name()}.on_load", ({{ }}), ({{ }})))], args, ({{ }})))))}}' f'onLoad={{((...args) => (addEvents([(Event("{EvState.get_full_name()}.on_load", ({{ }}), ({{ }})))], args, ({{ }}))))}}'
in render_dict["props"] in render_dict["props"]
) )
assert ( assert (
f'onError={{((...args) => ((addEvents([(Event("{EvState.get_full_name()}.on_error", ({{ }}), ({{ }})))], args, ({{ }})))))}}' f'onError={{((...args) => (addEvents([(Event("{EvState.get_full_name()}.on_error", ({{ }}), ({{ }})))], args, ({{ }}))))}}'
in render_dict["props"] in render_dict["props"]
) )

View File

@ -60,8 +60,13 @@ def syntax_highlighter_memoized_component(codeblock: Type[Component]):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"fn_body, fn_args, explicit_return, expected", "fn_body, fn_args, explicit_return, expected",
[ [
(None, None, False, Var(_js_expr="(({node, children, ...props}) => ())")), (
("return node", ("node", ), True, Var(_js_expr="(({node}) => {return node})")), None,
None,
False,
Var(_js_expr="(({node, children, ...props}) => undefined)"),
),
("return node", ("node",), True, Var(_js_expr="(({node}) => {return node})")),
( (
"return node + children", "return node + children",
("node", "children"), ("node", "children"),
@ -85,24 +90,28 @@ def syntax_highlighter_memoized_component(codeblock: Type[Component]):
], ],
) )
def test_create_map_fn_var(fn_body, fn_args, explicit_return, expected): 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) 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, explicit_return, expected", ("cls", "fn_body", "fn_args", "explicit_return", "expected"),
[ [
( (
MarkdownComponentMap, MarkdownComponentMap,
None, None,
None, None,
False, False,
Var(_js_expr="(({node, children, ...props}) => ())"), Var(_js_expr="(({node, children, ...props}) => undefined)"),
), ),
( (
MarkdownComponentMap, MarkdownComponentMap,
"return node", "return node",
("node", ), ("node",),
True, True,
Var(_js_expr="(({node}) => {return node})"), Var(_js_expr="(({node}) => {return node})"),
), ),
@ -125,7 +134,11 @@ def test_create_map_fn_var(fn_body, fn_args, explicit_return, expected):
], ],
) )
def test_create_map_fn_var_subclass(cls, fn_body, fn_args, explicit_return, expected): 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) 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

@ -844,9 +844,9 @@ def test_component_event_trigger_arbitrary_args():
comp = C1.create(on_foo=C1State.mock_handler) comp = C1.create(on_foo=C1State.mock_handler)
assert comp.render()["props"][0] == ( assert comp.render()["props"][0] == (
"onFoo={((__e, _alpha, _bravo, _charlie) => ((addEvents(" "onFoo={((__e, _alpha, _bravo, _charlie) => (addEvents("
f'[(Event("{C1State.get_full_name()}.mock_handler", ({{ ["_e"] : __e["target"]["value"], ["_bravo"] : _bravo["nested"], ["_charlie"] : (_charlie["custom"] + 42) }}), ({{ }})))], ' f'[(Event("{C1State.get_full_name()}.mock_handler", ({{ ["_e"] : __e["target"]["value"], ["_bravo"] : _bravo["nested"], ["_charlie"] : (_charlie["custom"] + 42) }}), ({{ }})))], '
"[__e, _alpha, _bravo, _charlie], ({ })))))}" "[__e, _alpha, _bravo, _charlie], ({ }))))}"
) )

View File

@ -22,8 +22,16 @@ from reflex.vars.base import (
var_operation, var_operation,
var_operation_return, var_operation_return,
) )
from reflex.vars.function import ArgsFunctionOperation, FunctionStringVar from reflex.vars.function import (
from reflex.vars.number import LiteralBooleanVar, LiteralNumberVar, NumberVar ArgsFunctionOperation,
DestructuredArg,
FunctionStringVar,
)
from reflex.vars.number import (
LiteralBooleanVar,
LiteralNumberVar,
NumberVar,
)
from reflex.vars.object import LiteralObjectVar, ObjectVar from reflex.vars.object import LiteralObjectVar, ObjectVar
from reflex.vars.sequence import ( from reflex.vars.sequence import (
ArrayVar, ArrayVar,
@ -921,13 +929,13 @@ def test_function_var():
) )
assert ( assert (
str(manual_addition_func.call(1, 2)) str(manual_addition_func.call(1, 2))
== '(((a, b) => (({ ["args"] : [a, b], ["result"] : a + b })))(1, 2))' == '(((a, b) => ({ ["args"] : [a, b], ["result"] : a + b }))(1, 2))'
) )
increment_func = addition_func(1) increment_func = addition_func(1)
assert ( assert (
str(increment_func.call(2)) str(increment_func.call(2))
== "(((...args) => ((((a, b) => a + b)(1, ...args))))(2))" == "(((...args) => (((a, b) => a + b)(1, ...args)))(2))"
) )
create_hello_statement = ArgsFunctionOperation.create( create_hello_statement = ArgsFunctionOperation.create(
@ -937,20 +945,24 @@ def test_function_var():
last_name = LiteralStringVar.create("Universe") last_name = LiteralStringVar.create("Universe")
assert ( assert (
str(create_hello_statement.call(f"{first_name} {last_name}")) str(create_hello_statement.call(f"{first_name} {last_name}"))
== '(((name) => (("Hello, "+name+"!")))("Steven Universe"))' == '(((name) => ("Hello, "+name+"!"))("Steven Universe"))'
) )
# Test with destructured arguments # Test with destructured arguments
destructured_func = ArgsFunctionOperation.create( destructured_func = ArgsFunctionOperation.create(
("a","b"), Var(_js_expr="a + b"), destructure_args=True (DestructuredArg(fields=("a", "b")),),
Var(_js_expr="a + b"),
)
assert (
str(destructured_func.call({"a": 1, "b": 2}))
== '((({a, b}) => a + b)(({ ["a"] : 1, ["b"] : 2 })))'
) )
assert str(destructured_func.call({"a": 1, "b": 2})) == '(({a, b}) => (a + b))({"a": 1, "b": 2})'
# Test with explicit return # Test with explicit return
explicit_return_func = ArgsFunctionOperation.create( explicit_return_func = ArgsFunctionOperation.create(
("a", "b"), Var(_js_expr="return a + b"), explicit_return=True ("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)' assert str(explicit_return_func.call(1, 2)) == "(((a, b) => {return a + b})(1, 2))"
def test_var_operation(): def test_var_operation():

View File

@ -374,7 +374,7 @@ def test_format_match(
events=[EventSpec(handler=EventHandler(fn=mock_event))], events=[EventSpec(handler=EventHandler(fn=mock_event))],
args_spec=lambda: [], args_spec=lambda: [],
), ),
'((...args) => ((addEvents([(Event("mock_event", ({ }), ({ })))], args, ({ })))))', '((...args) => (addEvents([(Event("mock_event", ({ }), ({ })))], args, ({ }))))',
), ),
( (
EventChain( EventChain(
@ -395,7 +395,7 @@ def test_format_match(
], ],
args_spec=lambda e: [e.target.value], args_spec=lambda e: [e.target.value],
), ),
'((_e) => ((addEvents([(Event("mock_event", ({ ["arg"] : _e["target"]["value"] }), ({ })))], [_e], ({ })))))', '((_e) => (addEvents([(Event("mock_event", ({ ["arg"] : _e["target"]["value"] }), ({ })))], [_e], ({ }))))',
), ),
( (
EventChain( EventChain(
@ -403,7 +403,7 @@ def test_format_match(
args_spec=lambda: [], args_spec=lambda: [],
event_actions={"stopPropagation": True}, event_actions={"stopPropagation": True},
), ),
'((...args) => ((addEvents([(Event("mock_event", ({ }), ({ })))], args, ({ ["stopPropagation"] : true })))))', '((...args) => (addEvents([(Event("mock_event", ({ }), ({ })))], args, ({ ["stopPropagation"] : true }))))',
), ),
( (
EventChain( EventChain(
@ -415,7 +415,7 @@ def test_format_match(
], ],
args_spec=lambda: [], args_spec=lambda: [],
), ),
'((...args) => ((addEvents([(Event("mock_event", ({ }), ({ ["stopPropagation"] : true })))], args, ({ })))))', '((...args) => (addEvents([(Event("mock_event", ({ }), ({ ["stopPropagation"] : true })))], args, ({ }))))',
), ),
( (
EventChain( EventChain(
@ -423,7 +423,7 @@ def test_format_match(
args_spec=lambda: [], args_spec=lambda: [],
event_actions={"preventDefault": True}, event_actions={"preventDefault": True},
), ),
'((...args) => ((addEvents([(Event("mock_event", ({ }), ({ })))], args, ({ ["preventDefault"] : true })))))', '((...args) => (addEvents([(Event("mock_event", ({ }), ({ })))], args, ({ ["preventDefault"] : true }))))',
), ),
({"a": "red", "b": "blue"}, '({ ["a"] : "red", ["b"] : "blue" })'), ({"a": "red", "b": "blue"}, '({ ["a"] : "red", ["b"] : "blue" })'),
(Var(_js_expr="var", _var_type=int).guess_type(), "var"), (Var(_js_expr="var", _var_type=int).guess_type(), "var"),