Source code for matmelt.backends.registry

"""The backend registry: ``kind`` string -> ``ComputeBackend`` class.

The single seam where *config* becomes a *running backend*. A user's YAML names
each backend only by ``kind`` (``local``, ``ssh_exec``, ...); this module maps
that string to the concrete class and instantiates it. Because the lookup is by
string, adding a backend is one subclass + one ``register`` call -- no stage,
orchestrator, or CLI change.

Registration is an **explicit call**, not an import-time decorator, so there is
no hidden import-order dependency: a concrete backend module calls
``register("local", LocalBackend)`` at the bottom of its file, and whoever wires
the tool imports the backend modules it intends to offer.

**Construction contract:** a registered class is instantiated as
``cls(name, options)`` -- the backend's configured name plus its free-form
options dict. The dict (rather than unpacked kwargs) keeps the contract stable
as backends grow new options; each backend validates what it needs in its own
``__init__`` and raises ``BackendError`` for a missing/invalid option.
"""

from __future__ import annotations

from typing import Any

from matmelt.backends.base import ComputeBackend
from matmelt.core.jobspec import BackendError

#: The live ``kind`` -> class table. Module-global and intentionally mutable:
#: concrete backend modules register into it when imported. Starts empty.
_REGISTRY: dict[str, type[ComputeBackend]] = {}


[docs] def register(kind: str, cls: type[ComputeBackend]) -> None: """Register ``cls`` as the implementation of ``kind``. Called explicitly at the bottom of each concrete backend module. Parameters ---------- kind The string a config's ``backends.<name>.kind`` names. cls The implementing class, which must be a ``ComputeBackend`` subclass. Raises ------ BackendError If ``cls`` is not a ``ComputeBackend`` subclass, or if ``kind`` is already registered to a *different* class. Re-registering the same class is idempotent, so a module re-import is a no-op, but silently shadowing an existing kind is refused. """ if not (isinstance(cls, type) and issubclass(cls, ComputeBackend)): raise BackendError( f"cannot register {cls!r} for kind {kind!r}: not a ComputeBackend subclass" ) existing = _REGISTRY.get(kind) if existing is not None and existing is not cls: raise BackendError( f"kind {kind!r} is already registered to {existing.__name__}; " f"refusing to shadow it with {cls.__name__}" ) _REGISTRY[kind] = cls
[docs] def get(kind: str) -> type[ComputeBackend]: """Return the class registered under ``kind``. Parameters ---------- kind The backend kind to look up. Returns ------- type The registered ``ComputeBackend`` subclass. Raises ------ BackendError If nothing is registered under ``kind``. The message names the kinds that *are* known, so a config typo produces an actionable message rather than a ``KeyError``. """ try: return _REGISTRY[kind] except KeyError: known = ", ".join(sorted(_REGISTRY)) or "(none registered)" raise BackendError(f"unknown backend kind {kind!r}; known kinds: {known}") from None
[docs] def create_backend(name: str, kind: str, options: dict[str, Any]) -> ComputeBackend: """Instantiate the backend ``name`` of ``kind`` with its ``options`` dict. The one seam where a config block becomes a live backend. Parameters ---------- name The user's configured name for this instance, carried into every ``Handle`` it issues. kind Which registered implementation to construct. options The backend's free-form ``options:`` block, passed through per the ``cls(name, options)`` contract. Returns ------- ComputeBackend The constructed, option-validated backend. Raises ------ BackendError If ``kind`` is unknown, or the backend's own ``__init__`` validation rejects an option -- a missing required value or a bad one. Examples -------- >>> from matmelt.backends import local # registers kind "local" >>> backend = create_backend("laptop", "local", {"max_parallel": 4}) >>> backend.name, backend.capabilities().max_parallel ('laptop', 4) """ cls = get(kind) return cls(name, options)
[docs] def known_kinds() -> list[str]: """List the backend kinds currently registered. Returns ------- list of str Sorted kinds, used in diagnostics and error messages. Which kinds are present depends on which backend modules have been imported. """ return sorted(_REGISTRY)