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
import dataclasses
import textwrap
from functools import lru_cache
from hashlib import md5
from typing import Any, Callable, Dict, Union
import dataclasses
from typing import Any, Callable, Dict, Sequence, Union
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, ArgsFunctionOperation
from reflex.vars.function import ARRAY_ISARRAY, ArgsFunctionOperation, DestructuredArg
from reflex.vars.number import ternary_operation
# Special vars used in the component map.
@ -79,6 +79,7 @@ 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
@ -92,7 +93,10 @@ class MarkdownComponentMap:
@classmethod
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:
"""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()
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
def get_fn_args(cls) -> list[str]:
def get_fn_args(cls) -> Sequence[str]:
"""Get the function arguments for the component map.
Returns:
@ -126,7 +134,7 @@ class MarkdownComponentMap:
Returns:
The function body as a string.
"""
return Var(_js_expr="", _var_type=str)
return Var(_js_expr="undefined", _var_type=None)
class Markdown(Component):
@ -290,7 +298,7 @@ class Markdown(Component):
_PROPS._js_expr,
),
fn_body=Var(_js_expr=formatted_code),
explicit_return=True
explicit_return=True,
)
def get_component(self, tag: str, **props) -> Component:
@ -359,7 +367,9 @@ class Markdown(Component):
Returns:
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):
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.function import (
ArgsFunctionOperation,
FunctionArgs,
FunctionStringVar,
FunctionVar,
VarOperationCall,
@ -1643,7 +1644,7 @@ class LiteralEventChainVar(ArgsFunctionOperation, LiteralVar, EventChainVar):
_js_expr="",
_var_type=EventChain,
_var_data=_var_data,
_args_names=arg_def,
_args=FunctionArgs(arg_def),
_return_expr=invocation.call(
LiteralVar.create([LiteralVar.create(event) for event in value.events]),
arg_def_expr,

View File

@ -4,10 +4,10 @@ from __future__ import annotations
import dataclasses
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.types import GenericType
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(
eq=False,
frozen=True,
@ -135,10 +165,9 @@ class VarOperationCall(CachedVarOperation, Var):
class ArgsFunctionOperation(CachedVarOperation, FunctionVar):
"""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)
_destructure_args: bool = dataclasses.field(default=False)
_explicit_return: bool = dataclasses.field(default=True)
_explicit_return: bool = dataclasses.field(default=False)
@cached_property_no_lock
def _cached_var_name(self) -> str:
@ -147,35 +176,40 @@ class ArgsFunctionOperation(CachedVarOperation, FunctionVar):
Returns:
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))
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})"
return_expr_str_wrapped = (
format.wrap(return_expr_str, "{", "}")
if self._explicit_return
else return_expr_str
)
return f"(({arg_names_str}) => {return_expr_str_wrapped})"
@classmethod
def create(
cls,
args_names: Tuple[str, ...],
args_names: Sequence[Union[str, DestructuredArg]],
return_expr: Var | Any,
destructure_args: bool = False,
rest: str | None = None,
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.
rest: The name of the rest argument.
explicit_return: Whether to use explicit return syntax.
_var_data: Additional hooks and imports associated with the Var.
@ -186,10 +220,9 @@ class ArgsFunctionOperation(CachedVarOperation, FunctionVar):
_js_expr="",
_var_type=_var_type,
_var_data=_var_data,
_args_names=args_names,
_args=FunctionArgs(args=tuple(args_names), rest=rest),
_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()
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"]
)
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"]
)
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"]
)

View File

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

View File

@ -844,9 +844,9 @@ def test_component_event_trigger_arbitrary_args():
comp = C1.create(on_foo=C1State.mock_handler)
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) }}), ({{ }})))], '
"[__e, _alpha, _bravo, _charlie], ({ })))))}"
"[__e, _alpha, _bravo, _charlie], ({ }))))}"
)

View File

@ -22,8 +22,16 @@ from reflex.vars.base import (
var_operation,
var_operation_return,
)
from reflex.vars.function import ArgsFunctionOperation, FunctionStringVar
from reflex.vars.number import LiteralBooleanVar, LiteralNumberVar, NumberVar
from reflex.vars.function import (
ArgsFunctionOperation,
DestructuredArg,
FunctionStringVar,
)
from reflex.vars.number import (
LiteralBooleanVar,
LiteralNumberVar,
NumberVar,
)
from reflex.vars.object import LiteralObjectVar, ObjectVar
from reflex.vars.sequence import (
ArrayVar,
@ -921,13 +929,13 @@ def test_function_var():
)
assert (
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)
assert (
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(
@ -937,20 +945,24 @@ def test_function_var():
last_name = LiteralStringVar.create("Universe")
assert (
str(create_hello_statement.call(f"{first_name} {last_name}"))
== '(((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
(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
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)'
assert str(explicit_return_func.call(1, 2)) == "(((a, b) => {return a + b})(1, 2))"
def test_var_operation():

View File

@ -374,7 +374,7 @@ def test_format_match(
events=[EventSpec(handler=EventHandler(fn=mock_event))],
args_spec=lambda: [],
),
'((...args) => ((addEvents([(Event("mock_event", ({ }), ({ })))], args, ({ })))))',
'((...args) => (addEvents([(Event("mock_event", ({ }), ({ })))], args, ({ }))))',
),
(
EventChain(
@ -395,7 +395,7 @@ def test_format_match(
],
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(
@ -403,7 +403,7 @@ def test_format_match(
args_spec=lambda: [],
event_actions={"stopPropagation": True},
),
'((...args) => ((addEvents([(Event("mock_event", ({ }), ({ })))], args, ({ ["stopPropagation"] : true })))))',
'((...args) => (addEvents([(Event("mock_event", ({ }), ({ })))], args, ({ ["stopPropagation"] : true }))))',
),
(
EventChain(
@ -415,7 +415,7 @@ def test_format_match(
],
args_spec=lambda: [],
),
'((...args) => ((addEvents([(Event("mock_event", ({ }), ({ ["stopPropagation"] : true })))], args, ({ })))))',
'((...args) => (addEvents([(Event("mock_event", ({ }), ({ ["stopPropagation"] : true })))], args, ({ }))))',
),
(
EventChain(
@ -423,7 +423,7 @@ def test_format_match(
args_spec=lambda: [],
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" })'),
(Var(_js_expr="var", _var_type=int).guess_type(), "var"),