Source code for scverse_backends._dispatcher

"""The ``BackendDispatcher`` class — a host's full dispatch surface."""

from __future__ import annotations

from typing import TYPE_CHECKING, Any

from scverse_backends._dispatch import _Dispatch
from scverse_backends._registry import _Registry
from scverse_backends._settings import _Settings

if TYPE_CHECKING:
    from collections.abc import Callable


[docs] class BackendDispatcher: """Per-host dispatch surface. Each host library instantiates one ``BackendDispatcher`` and re-exports its attributes as the host's public dispatch API. Parameters ---------- entrypoint_group Entrypoint group that backend packages register against (e.g. ``"example_host.backends"``). Entrypoints may load a backend module, class, or object. host_name Name of the host library, used in error messages. trusted_backends Mapping of canonical backend name to ``{"aliases": [...], "package": "pip-name"}``. Backends not in this list still work but emit a warning on first use. For trusted backends, ``package`` is also used as the expected Python distribution name during entrypoint discovery. Use ``distributions`` for backends that can be provided by multiple distribution names, and optionally ``entrypoints``, ``object_refs``, or ``module_prefixes`` for stricter provider verification. reserved_backends Host-owned backend names or aliases that no backend may claim. Values are human-readable reasons shown when users request a reserved name. Examples -------- >>> from scverse_backends import BackendDispatcher >>> _dispatcher = BackendDispatcher( ... entrypoint_group="example_host.backends", ... host_name="example_host", ... trusted_backends={ ... "example_accel": { ... "aliases": ["example", "accelerated"], ... "package": "example-host-accel", ... }, ... }, ... ) >>> backend_dispatch = _dispatcher.backend_dispatch >>> settings = _dispatcher.settings """
[docs] def __init__( self, *, entrypoint_group: str, host_name: str, trusted_backends: dict[str, dict[str, Any]] | None = None, reserved_backends: dict[str, str] | None = None, ) -> None: self.entrypoint_group = entrypoint_group self.host_name = host_name self._registry = _Registry( entrypoint_group=entrypoint_group, host_name=host_name, trusted_backends=trusted_backends or {}, reserved_backends=reserved_backends, ) self._settings = _Settings(self._registry) self._dispatch_impl = _Dispatch(self._registry, self._settings)
@property def backend_dispatch(self) -> Callable: """The ``@backend_dispatch`` decorator for host functions.""" return self._dispatch_impl.decorator @property def settings(self) -> _Settings: """Settings object exposing ``.backend`` and ``.use_backend()``.""" return self._settings
[docs] def get_backend(self, name: str) -> Any | None: """Look up a backend by name or alias. Returns ``None`` for ``"cpu"``.""" return self._registry.get_backend(name)
[docs] def available_backend_names(self) -> list[str]: """All registered backend names and aliases.""" return self._registry.available_backend_names()
[docs] def discover(self) -> None: """Eagerly load backends and merge their params into host signatures. Discovery is normally lazy — entrypoints are loaded the first time anything in the dispatcher is queried (settings setter, ``get_backend``, a non-CPU dispatched call). Call ``discover()`` when you want that to happen on a schedule you control: * In a host's Sphinx ``conf.py``, so autodoc sees the merged signatures with backend-specific params and their docstrings. * In tests that introspect ``help(fn)`` / ``inspect.signature(fn)``. * Anywhere else IDE/LSP integrations would otherwise see the un-merged signature. Idempotent — safe to call multiple times. """ self._registry._ensure_discovered()