"""Which propagator answers, and one instance of it per process. ``get_backend()`` with no argument returns the deployment's configured default; with a name it returns that one specifically. Both matter. The default is what the request path should use, and the explicit name is what a caller needing physics the default cannot do -- space weather, drag fitting, decay -- must pass. That second case is not hypothetical. The default is ``sgp4``, which raises on any of those; a view that forgot to name ``"orekit"`` and swallowed the error rendered an empty chart for weeks without anything logging a complaint. If your call needs the sidecar, say so. """ from __future__ import annotations from django.conf import settings from odm import PropagatorBackend from .backends.orekit import OrekitBackend from .backends.sgp4 import Sgp4Backend BACKENDS: dict[str, type[PropagatorBackend]] = { "sgp4": Sgp4Backend, "orekit": OrekitBackend, } #: One instance per name. Backends are stateless apart from an HTTP client, so #: sharing them saves a connection pool rather than risking shared state. _INSTANCES: dict[str, PropagatorBackend] = {} DEFAULT_BACKEND = "sgp4" def register(name: str, backend: type[PropagatorBackend]) -> None: """Add a backend under ``name``. For a service with its own propagator.""" BACKENDS[name] = backend _INSTANCES.pop(name, None) def get_backend(name: str | None = None) -> PropagatorBackend: key = name or getattr(settings, "ORBITAL_PROPAGATOR_BACKEND", DEFAULT_BACKEND) try: cls = BACKENDS[key] except KeyError as exc: raise ValueError( f"unknown propagation backend {key!r}; " f"expected one of {', '.join(sorted(BACKENDS))}" ) from exc if key not in _INSTANCES: _INSTANCES[key] = cls() return _INSTANCES[key]