use destructured args and pass the tests
This commit is contained in:
parent
ec5f751f3f
commit
112ecdc2e3
@ -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)
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
)
|
||||
|
||||
|
||||
|
@ -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"]
|
||||
)
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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], ({ }))))}"
|
||||
)
|
||||
|
||||
|
||||
|
@ -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():
|
||||
|
@ -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"),
|
||||
|
Loading…
Reference in New Issue
Block a user