Source code for qtest.backends.registry
"""Module-level registry of available backends.
The registry stores a mapping ``{name: Backend subclass}`` and tracks which
name is the current default. The Qiskit backend is auto-registered on
import and set as the default.
Registration stores the *class* (a zero-argument-callable factory) so that
each call to :func:`get_backend` returns a fresh, stateless instance —
honouring the no-cross-call-state contract documented in
:class:`qtest.backends.base.Backend`.
"""
from __future__ import annotations
from qtest.backends.base import Backend
_REGISTRY: dict[str, type[Backend]] = {}
_DEFAULT_BACKEND_NAME: str | None = None
[docs]
def register_backend(name: str, backend_class: type[Backend]) -> None:
"""Register *backend_class* under *name*.
Subsequent registrations under the same name overwrite the previous
entry.
"""
if not isinstance(name, str) or not name:
raise ValueError("name must be a non-empty string")
if not (isinstance(backend_class, type) and issubclass(backend_class, Backend)):
raise TypeError(f"backend_class must be a subclass of Backend, got {backend_class!r}")
_REGISTRY[name] = backend_class
[docs]
def get_backend(name: str | None = None) -> Backend:
"""Instantiate and return a backend.
If *name* is ``None``, the current default backend is used. Raises
:class:`KeyError` if *name* is not registered, or :class:`RuntimeError`
if no default is set.
"""
if name is None:
if _DEFAULT_BACKEND_NAME is None:
raise RuntimeError(
"No default backend is set. Use set_default_backend() or " "pass a name explicitly."
)
name = _DEFAULT_BACKEND_NAME
if name not in _REGISTRY:
raise KeyError(f"Unknown backend {name!r}. Available: {list_available_backends()}")
return _REGISTRY[name]()
[docs]
def get_default_backend() -> Backend:
"""Return a fresh instance of the current default backend."""
return get_backend(None)
[docs]
def set_default_backend(name: str) -> None:
"""Set *name* as the default backend. Must already be registered."""
global _DEFAULT_BACKEND_NAME
if name not in _REGISTRY:
raise KeyError(
f"Cannot set default to unknown backend {name!r}. "
f"Available: {list_available_backends()}"
)
_DEFAULT_BACKEND_NAME = name
[docs]
def list_available_backends() -> list[str]:
"""Return a sorted list of registered backend names."""
return sorted(_REGISTRY)
def _auto_register_defaults() -> None:
"""Register the bundled backends and make Qiskit the default.
Importing the backend modules does **not** import their underlying SDKs
(all such imports are lazy), so this is safe even when Qiskit / Cirq /
PennyLane are not installed; an :class:`ImportError` is only raised when
an execution method is actually called on a backend whose SDK is missing.
"""
from qtest.backends.cirq_backend import CirqBackend
from qtest.backends.pennylane_backend import PennyLaneBackend
from qtest.backends.qiskit_backend import QiskitBackend
register_backend("qiskit", QiskitBackend)
register_backend("cirq", CirqBackend)
register_backend("pennylane", PennyLaneBackend)
global _DEFAULT_BACKEND_NAME
if _DEFAULT_BACKEND_NAME is None:
_DEFAULT_BACKEND_NAME = "qiskit"
_auto_register_defaults()