Requiring 3.13 meant the device needed an interpreter the distribution does not
have, which is what dragged uv in, and uv then had to be matched to the Pi's
32-bit userland by hand and to build Pillow from source because no armv7 wheel
exists for a 3.13 ABI. Dropping to 3.11 removes all of that: apt provides the
interpreter and piwheels has prebuilt armhf wheels for the native dependencies.
The 3.13-only syntax was shallow - PEP 695 throughout, which converts back
mechanically:
type X = Y -> X: TypeAlias = Y
type Handler[E: Event] -> E = TypeVar("E", bound=Event) plus a plain alias,
which is generic anyway because it carries a TypeVar
def f[T: Bound](...) -> a module-level TypeVar
Also drop the one @override (3.12, and static-only), and stop the lirc test
harness calling Server.close_clients(), which is 3.13: the scripted handler now
releases its connection when asked, which is what that call was there to force.
Verified on 3.11.14 - 485 passed, mypy strict clean - and still 496 passed on
the 3.14 dev venv, which additionally has the analysis extra.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
58 lines
1.7 KiB
Python
58 lines
1.7 KiB
Python
"""The ``@on`` decorator and the binding step.
|
|
|
|
Kept in its own module so the reaction modules can import ``on`` without importing the
|
|
package that imports them.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from collections.abc import Callable, Coroutine
|
|
from typing import TYPE_CHECKING, Any, TypeAlias, TypeVar
|
|
|
|
from musicmouse.bus import EventBus
|
|
from musicmouse.events import Event
|
|
|
|
if TYPE_CHECKING:
|
|
from musicmouse.app import App
|
|
|
|
_log = logging.getLogger(__name__)
|
|
|
|
__all__ = ["Reaction", "on", "register_all", "registered"]
|
|
|
|
E = TypeVar("E", bound=Event)
|
|
|
|
Reaction: TypeAlias = Callable[[E, "App"], Coroutine[Any, Any, None] | None]
|
|
|
|
_REGISTRY: list[tuple[type[Event], Reaction[Any]]] = []
|
|
|
|
|
|
def on(event_type: type[E]) -> Callable[[Reaction[E]], Reaction[E]]:
|
|
"""Register a reaction for ``event_type`` (and any subclass of it)."""
|
|
|
|
def decorator(reaction: Reaction[E]) -> Reaction[E]:
|
|
_REGISTRY.append((event_type, reaction))
|
|
return reaction
|
|
|
|
return decorator
|
|
|
|
|
|
def registered() -> list[tuple[type[Event], Reaction[Any]]]:
|
|
return list(_REGISTRY)
|
|
|
|
|
|
def register_all(bus: EventBus, app: App) -> None:
|
|
"""Subscribe every declared reaction, with ``app`` bound as its second argument."""
|
|
for event_type, reaction in _REGISTRY:
|
|
bus.subscribe(event_type, _bind(reaction, app))
|
|
_log.debug("Registered %d reactions", len(_REGISTRY))
|
|
|
|
|
|
def _bind(reaction: Reaction[E], app: App) -> Callable[[E], Any]:
|
|
def handler(event: E) -> Any:
|
|
return reaction(event, app)
|
|
|
|
# Keep the reaction's name, so a failing handler is identifiable in the log.
|
|
handler.__qualname__ = getattr(reaction, "__qualname__", repr(reaction))
|
|
return handler
|