fix(event): declare LambdaEventCallback.__code__ read-only so bound methods match the protocol

Draft
BE
8d ago
+26-1

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.