Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 49 additions & 8 deletions packages/reflex-base/src/reflex_base/event/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,14 @@
overload,
)

from typing_extensions import Self, TypeAliasType, TypedDict, TypeVarTuple, Unpack
from typing_extensions import (
Self,
TypeAliasType,
TypedDict,
TypeVarTuple,
Unpack,
is_typeddict,
)

from reflex_base import constants
from reflex_base.components.field import BaseField
Expand Down Expand Up @@ -58,6 +65,10 @@
if TYPE_CHECKING:
from reflex.state import BaseState

BASE_STATE = TypeVar("BASE_STATE", bound=BaseState)
else:
BASE_STATE = TypeVar("BASE_STATE")


@dataclasses.dataclass(
init=True,
Expand Down Expand Up @@ -1683,6 +1694,37 @@ def _values_returned_from_event(event_spec_annotations: list[Any]) -> list[Any]:
]


def _is_on_submit_mapping_event_arg_compatible_with_typed_dict(
provided_event_arg_type: Any,
callback_param_type: Any,
key: str,
) -> bool:
"""Check whether an on_submit mapping payload can satisfy a TypedDict callback.

This keeps the compatibility relaxation scoped to form submission payloads
rather than applying to unrelated mapping-based event triggers.

Args:
provided_event_arg_type: The type produced by the event trigger.
callback_param_type: The callback parameter annotation.
key: The event trigger key being validated.

Returns:
Whether the provided event payload should be treated as compatible.
"""
if key != constants.EventTriggers.ON_SUBMIT or not is_typeddict(
callback_param_type
):
return False

mapping_type = get_origin(provided_event_arg_type) or provided_event_arg_type
if not safe_issubclass(mapping_type, Mapping):
return False

key_type = get_args(provided_event_arg_type)[:1]
return not key_type or typehint_issubclass(key_type[0], str)


def _check_event_args_subclass_of_callback(
callback_params_names: list[str],
provided_event_types: list[Any],
Expand Down Expand Up @@ -1724,15 +1766,18 @@ def _check_event_args_subclass_of_callback(
continue

type_match_found.setdefault(arg, False)
callback_param_type = callback_param_name_to_type[arg]

try:
compare_result = typehint_issubclass(
args_types_without_vars[i], callback_param_name_to_type[arg]
args_types_without_vars[i], callback_param_type
) or _is_on_submit_mapping_event_arg_compatible_with_typed_dict(
args_types_without_vars[i], callback_param_type, key
)
except TypeError as te:
callback_name_context = f" of {callback_name}" if callback_name else ""
key_context = f" for {key}" if key else ""
msg = f"Could not compare types {args_types_without_vars[i]} and {callback_param_name_to_type[arg]} for argument {arg}{callback_name_context}{key_context}."
msg = f"Could not compare types {args_types_without_vars[i]} and {callback_param_type} for argument {arg}{callback_name_context}{key_context}."
raise TypeError(msg) from te

if compare_result:
Expand All @@ -1744,7 +1789,7 @@ def _check_event_args_subclass_of_callback(
)
delayed_exceptions.append(
EventHandlerArgTypeMismatchError(
f"Event handler {key} expects {args_types_without_vars[i]} for argument {arg} but got {callback_param_name_to_type[arg]}{as_annotated_in} instead."
f"Event handler {key} expects {args_types_without_vars[i]} for argument {arg} but got {callback_param_type}{as_annotated_in} instead."
)
)

Expand Down Expand Up @@ -2557,10 +2602,6 @@ def __call__(self, *args: Var) -> Any:
if TYPE_CHECKING:
from reflex.state import BaseState

BASE_STATE = TypeVar("BASE_STATE", bound=BaseState)
else:
BASE_STATE = TypeVar("BASE_STATE")


class EventNamespace:
"""A namespace for event related classes."""
Expand Down
Loading
Loading