Skip to content

Commit

Permalink
Move _create_event_chain to EventChain.create (#4557)
Browse files Browse the repository at this point in the history
The ability to convert `EventType` arguments into `EventChain` is crucial for
wrapping JS libraries that need to pass event handlers via hooks or
other non-component centric interfaces.

Improve typing such that wrapped components accepting `EventType` arguments can
be directly converted to `EventChain` / `EventChainVar` to be rendered as JS
code.
  • Loading branch information
masenf authored Jan 2, 2025
1 parent 848b870 commit 41ed9f0
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 84 deletions.
102 changes: 18 additions & 84 deletions reflex/components/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
Union,
)

from typing_extensions import deprecated

import reflex.state
from reflex.base import Base
from reflex.compiler.templates import STATEFUL_COMPONENT
Expand All @@ -43,17 +45,13 @@
from reflex.event import (
EventCallback,
EventChain,
EventChainVar,
EventHandler,
EventSpec,
EventVar,
call_event_fn,
call_event_handler,
get_handler_args,
no_args_event_spec,
)
from reflex.style import Style, format_as_emotion
from reflex.utils import format, imports, types
from reflex.utils import console, format, imports, types
from reflex.utils.imports import (
ImmutableParsedImportDict,
ImportDict,
Expand Down Expand Up @@ -493,8 +491,7 @@ def __init__(self, *args, **kwargs):
)
# Check if the key is an event trigger.
if key in component_specific_triggers:
# Temporarily disable full control for event triggers.
kwargs["event_triggers"][key] = self._create_event_chain(
kwargs["event_triggers"][key] = EventChain.create(
value=value, # type: ignore
args_spec=component_specific_triggers[key],
key=key,
Expand Down Expand Up @@ -548,6 +545,7 @@ def __init__(self, *args, **kwargs):
# Construct the component.
super().__init__(*args, **kwargs)

@deprecated("Use rx.EventChain.create instead.")
def _create_event_chain(
self,
args_spec: types.ArgsSpec | Sequence[types.ArgsSpec],
Expand All @@ -569,82 +567,18 @@ def _create_event_chain(
Returns:
The event chain.
Raises:
ValueError: If the value is not a valid event chain.
"""
# If it's an event chain var, return it.
if isinstance(value, Var):
if isinstance(value, EventChainVar):
return value
elif isinstance(value, EventVar):
value = [value]
elif issubclass(value._var_type, (EventChain, EventSpec)):
return self._create_event_chain(args_spec, value.guess_type(), key=key)
else:
raise ValueError(
f"Invalid event chain: {value!s} of type {value._var_type}"
)
elif isinstance(value, EventChain):
# Trust that the caller knows what they're doing passing an EventChain directly
return value

# If the input is a single event handler, wrap it in a list.
if isinstance(value, (EventHandler, EventSpec)):
value = [value]

# If the input is a list of event handlers, create an event chain.
if isinstance(value, List):
events: List[Union[EventSpec, EventVar]] = []
for v in value:
if isinstance(v, (EventHandler, EventSpec)):
# Call the event handler to get the event.
events.append(call_event_handler(v, args_spec, key=key))
elif isinstance(v, Callable):
# Call the lambda to get the event chain.
result = call_event_fn(v, args_spec, key=key)
if isinstance(result, Var):
raise ValueError(
f"Invalid event chain: {v}. Cannot use a Var-returning "
"lambda inside an EventChain list."
)
events.extend(result)
elif isinstance(v, EventVar):
events.append(v)
else:
raise ValueError(f"Invalid event: {v}")

# If the input is a callable, create an event chain.
elif isinstance(value, Callable):
result = call_event_fn(value, args_spec, key=key)
if isinstance(result, Var):
# Recursively call this function if the lambda returned an EventChain Var.
return self._create_event_chain(args_spec, result, key=key)
events = [*result]

# Otherwise, raise an error.
else:
raise ValueError(f"Invalid event chain: {value}")

# Add args to the event specs if necessary.
events = [
(e.with_args(get_handler_args(e)) if isinstance(e, EventSpec) else e)
for e in events
]

# Return the event chain.
if isinstance(args_spec, Var):
return EventChain(
events=events,
args_spec=None,
event_actions={},
)
else:
return EventChain(
events=events,
args_spec=args_spec,
event_actions={},
)
"""
console.deprecate(
"Component._create_event_chain",
"Use rx.EventChain.create instead.",
deprecation_version="0.6.8",
removal_version="0.7.0",
)
return EventChain.create(
value=value, # type: ignore
args_spec=args_spec,
key=key,
)

def get_event_triggers(
self,
Expand Down Expand Up @@ -1737,7 +1671,7 @@ def __init__(self, *args, **kwargs):

# Handle event chains.
if types._issubclass(type_, EventChain):
value = self._create_event_chain(
value = EventChain.create(
value=value,
args_spec=event_triggers_in_component_declaration.get(
key, no_args_event_spec
Expand Down
90 changes: 90 additions & 0 deletions reflex/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,96 @@ class EventChain(EventActionsMixin):

invocation: Optional[Var] = dataclasses.field(default=None)

@classmethod
def create(
cls,
value: EventType,
args_spec: ArgsSpec | Sequence[ArgsSpec],
key: Optional[str] = None,
) -> Union[EventChain, Var]:
"""Create an event chain from a variety of input types.
Args:
value: The value to create the event chain from.
args_spec: The args_spec of the event trigger being bound.
key: The key of the event trigger being bound.
Returns:
The event chain.
Raises:
ValueError: If the value is not a valid event chain.
"""
# If it's an event chain var, return it.
if isinstance(value, Var):
if isinstance(value, EventChainVar):
return value
elif isinstance(value, EventVar):
value = [value]
elif issubclass(value._var_type, (EventChain, EventSpec)):
return cls.create(
value=value.guess_type(),
args_spec=args_spec,
key=key,
)
else:
raise ValueError(
f"Invalid event chain: {value!s} of type {value._var_type}"
)
elif isinstance(value, EventChain):
# Trust that the caller knows what they're doing passing an EventChain directly
return value

# If the input is a single event handler, wrap it in a list.
if isinstance(value, (EventHandler, EventSpec)):
value = [value]

# If the input is a list of event handlers, create an event chain.
if isinstance(value, List):
events: List[Union[EventSpec, EventVar]] = []
for v in value:
if isinstance(v, (EventHandler, EventSpec)):
# Call the event handler to get the event.
events.append(call_event_handler(v, args_spec, key=key))
elif isinstance(v, Callable):
# Call the lambda to get the event chain.
result = call_event_fn(v, args_spec, key=key)
if isinstance(result, Var):
raise ValueError(
f"Invalid event chain: {v}. Cannot use a Var-returning "
"lambda inside an EventChain list."
)
events.extend(result)
elif isinstance(v, EventVar):
events.append(v)
else:
raise ValueError(f"Invalid event: {v}")

# If the input is a callable, create an event chain.
elif isinstance(value, Callable):
result = call_event_fn(value, args_spec, key=key)
if isinstance(result, Var):
# Recursively call this function if the lambda returned an EventChain Var.
return cls.create(value=result, args_spec=args_spec, key=key)
events = [*result]

# Otherwise, raise an error.
else:
raise ValueError(f"Invalid event chain: {value}")

# Add args to the event specs if necessary.
events = [
(e.with_args(get_handler_args(e)) if isinstance(e, EventSpec) else e)
for e in events
]

# Return the event chain.
return cls(
events=events,
args_spec=args_spec,
event_actions={},
)


@dataclasses.dataclass(
init=True,
Expand Down

0 comments on commit 41ed9f0

Please sign in to comment.