fix(event): declare LambdaEventCallback.__code__ read-only so bound methods match the protocol
LambdaEventCallback declares __code__: types.CodeType. A plain annotation on a
protocol means a settable attribute, but a bound method's __code__ is a read-only
property on types.MethodType that reads its function's. So under the typing spec a
bound method never satisfies the protocol, and passing one to an event trigger, for
example reflex's own ClientStateVar.set_value as on_change, is rejected by
checkers that enforce this (ty does, pyright does not):
import types
from typing import Any, Protocol
class Settable(Protocol):
__code__: types.CodeType
def __call__(self, value: int, /) -> Any: ...
class ReadOnly(Protocol):
@property
def __code__(self) -> types.CodeType: ...
def __call__(self, value: int, /) -> Any: ...
class Obj:
def meth(self, value: int) -> None: ...
a: Settable = Obj().meth # ty: invalid-assignment
b: ReadOnly = Obj().meth # ok
Declaring __code__ as a read-only property keeps functions and lambdas matching and
lets bound methods match as well.
ty rejects bound methods passed as event handlers for two other reasons as well:
astral-sh/ty#4098 (__call__ overloads whose self does not fit the specialization
are not skipped) and astral-sh/ty#4746 (protocol methods with a protocol-typed self
reject valid implementations, fix in astral-sh/ruff#29450). With both fixed, this
change is what lets bound methods through. I checked that on ty 0.0.64 and on current
ty with astral-sh/ruff#29450, each with the astral-sh/ty#4098 fix simulated by keeping
only the [V] overload.
pyright still accepts lambdas, functions and bound methods as event handlers, and
tests/units/test_event.py passes.