Skip to content

Commit 7a1de65

Browse files
fix(event): only bind an EventCallback when it is read through a state (#7401)
* fix(event): only bind an EventCallback when it is read through a state * Use starred unpack syntax in EventCallback descriptor types --------- Co-authored-by: Masen Furer <m_github@0x26.net>
1 parent ce534da commit 7a1de65

3 files changed

Lines changed: 31 additions & 2 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
An event handler stored outside a state, e.g. in a component or dataclass field, now type-checks as the handler instead of a bound function, and a handler read through a state instance is typed as a function returning `Any`.

‎packages/reflex-base/src/reflex_base/event/__init__.py‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3013,7 +3013,16 @@ def __get__(
30133013
) -> "EventCallback[*P]": ...
30143014

30153015
@overload
3016-
def __get__(self, instance: Any, owner: Any) -> "Callable[[Unpack[P]]]": ...
3016+
def __get__(
3017+
self: "EventCallback[*P]", instance: "BaseState", owner: Any
3018+
) -> "Callable[[Unpack[P]], Any]": ...
3019+
3020+
# Only a state binds its handlers. Anywhere else, e.g. in a component or
3021+
# dataclass field, a stored callback reads back as the callback itself.
3022+
@overload
3023+
def __get__(
3024+
self: "EventCallback[*P]", instance: object, owner: Any
3025+
) -> "EventCallback[*P]": ...
30173026

30183027
def __get__(self, instance: Any, owner: Any) -> Callable:
30193028
"""Get the function with the instance bound to it.

‎tests/type_checking/event.py‎

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Calling an event handler on the state class types as the event it creates."""
1+
"""Inferred types for `reflex_base.event` that are part of the public contract."""
22

33
from collections.abc import Callable
44
from typing import Any, assert_type
@@ -46,3 +46,22 @@ def four(self, index: int, value: str, flag: bool, count: float) -> None: ...
4646
# A handler is a callable of its arguments, so it can be passed where one is expected.
4747
_one: Callable[[int], Any] = _HandlerState.one
4848
_two: Callable[[int, str], Any] = _HandlerState.two
49+
50+
51+
class _State(State):
52+
@event
53+
def set_name(self, name: str) -> None: ...
54+
55+
56+
class _Holder:
57+
on_name: EventCallback[str] | None = None
58+
59+
60+
def _reads(state: _State, holder: _Holder) -> None:
61+
# Read through its state class, a handler is the callback event triggers take.
62+
assert_type(_State.set_name, EventCallback[str])
63+
# Read through an instance of that state, it is the function bound to it.
64+
assert_type(state.set_name, Callable[[str], Any])
65+
# Stored anywhere else, e.g. a component or dataclass field, it stays the
66+
# callback: only a state instance binds it.
67+
assert_type(holder.on_name, EventCallback[str] | None)

0 commit comments

Comments
 (0)