From 893c0b132e9e08c65812e1a82dc7d27f34f142c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Brand=C3=A9ho?= Date: Sat, 6 May 2023 22:10:35 +0200 Subject: [PATCH] Make argument optional for event handlers (#950) --- pynecone/components/component.py | 4 +++- pynecone/event.py | 22 +++++++++++----------- pynecone/utils/format.py | 18 ++++++++++++------ tests/components/test_component.py | 11 ++++------- 4 files changed, 30 insertions(+), 25 deletions(-) diff --git a/pynecone/components/component.py b/pynecone/components/component.py index 758211913..94e319194 100644 --- a/pynecone/components/component.py +++ b/pynecone/components/component.py @@ -220,7 +220,9 @@ class Component(Base, ABC): event = call_event_handler(v, arg) # Check that the event handler takes no args if it's uncontrolled. - if not is_controlled_event and len(event.args) > 0: + if not is_controlled_event and ( + event.args is not None and len(event.args) > 0 + ): raise ValueError( f"Event handler: {v.fn} for uncontrolled event {event_trigger} should not take any args." ) diff --git a/pynecone/event.py b/pynecone/event.py index 82f1e714e..2ac73f9de 100644 --- a/pynecone/event.py +++ b/pynecone/event.py @@ -90,7 +90,7 @@ class EventSpec(Base): local_args: Tuple[Var, ...] = () # The arguments to pass to the function. - args: Tuple[Tuple[Var, Var], ...] = () + args: Optional[Tuple[Tuple[Var, Var], ...]] = () # Whether to upload files. upload: bool = False @@ -318,7 +318,9 @@ def call_event_fn(fn: Callable, arg: Var) -> List[EventSpec]: return events -def get_handler_args(event_spec: EventSpec, arg: Var) -> Tuple[Tuple[Var, Var], ...]: +def get_handler_args( + event_spec: EventSpec, arg: Var +) -> Optional[Tuple[Tuple[Var, Var], ...]]: """Get the handler args for the given event spec. Args: @@ -327,16 +329,14 @@ def get_handler_args(event_spec: EventSpec, arg: Var) -> Tuple[Tuple[Var, Var], Returns: The handler args. - - Raises: - ValueError: If the event handler has an invalid signature. """ args = inspect.getfullargspec(event_spec.handler.fn).args - if len(args) < 2: - raise ValueError( - f"Event handler has an invalid signature, needed a method with a parameter, got {event_spec.handler}." - ) - return event_spec.args if len(args) > 2 else ((Var.create_safe(args[1]), arg),) + + return ( + event_spec.args + if len(args) > 2 + else (((Var.create_safe(args[1]), arg),) if len(args) == 2 else None) + ) def fix_events( @@ -369,7 +369,7 @@ def fix_events( e = e() assert isinstance(e, EventSpec), f"Unexpected event type, {type(e)}." name = format.format_event_handler(e.handler) - payload = {k.name: v.name for k, v in e.args} + payload = {k.name: v.name for k, v in e.args} if e.args else {} # Create an event and append it to the list. out.append( diff --git a/pynecone/utils/format.py b/pynecone/utils/format.py index 4d3354d21..933be4267 100644 --- a/pynecone/utils/format.py +++ b/pynecone/utils/format.py @@ -286,11 +286,17 @@ def format_event(event_spec: EventSpec) -> str: Returns: The compiled event. """ - args = ",".join( - [ - ":".join((name.name, json.dumps(val.name) if val.is_string else val.name)) - for name, val in event_spec.args - ] + args = ( + ",".join( + [ + ":".join( + (name.name, json.dumps(val.name) if val.is_string else val.name) + ) + for name, val in event_spec.args + ] + ) + if event_spec.args is not None + else "" ) return f"E(\"{format_event_handler(event_spec.handler)}\", {wrap(args, '{')})" @@ -323,7 +329,7 @@ def format_full_control_event(event_chain: EventChain) -> str: from pynecone.compiler import templates event_spec = event_chain.events[0] - arg = event_spec.args[0][1] + arg = event_spec.args[0][1] if event_spec.args else None state_name = event_chain.state_name chain = ",".join([format_event(event) for event in event_chain.events]) event = templates.FULL_CONTROL(state_name=state_name, arg=arg, chain=chain) diff --git a/tests/components/test_component.py b/tests/components/test_component.py index a3e0c4a3c..0be8f73bf 100644 --- a/tests/components/test_component.py +++ b/tests/components/test_component.py @@ -391,13 +391,10 @@ def test_invalid_event_handler_args(component2, test_state): # Controlled event handlers should take args. # This is okay. component2.create(on_open=test_state.do_something_arg) - # This is not okay. - with pytest.raises(ValueError): - component2.create(on_open=test_state.do_something) - with pytest.raises(ValueError): - component2.create( - on_open=[test_state.do_something_arg, test_state.do_something] - ) + + # do_something is allowed and will simply run while ignoring the arg + component2.create(on_open=test_state.do_something) + component2.create(on_open=[test_state.do_something_arg, test_state.do_something]) def test_get_hooks_nested(component1, component2, component3):