jmri_core.instance

InstanceManager — service locator container.

JMRI's Java InstanceManager is a reflection-driven service registry where defaults are auto-discovered at runtime. In Python we make this explicit: you register an implementation against an interface type, and look it up by type.

Two registration modes:

  • register() — install an already-built object (singleton).
  • register_factory() — install a zero-arg callable that builds the object on first get(). Lets you defer construction without exposing the order in which subsystems initialize.
 1"""``InstanceManager`` — service locator container.
 2
 3JMRI's Java ``InstanceManager`` is a reflection-driven service registry where
 4defaults are auto-discovered at runtime. In Python we make this explicit: you
 5register an implementation against an interface type, and look it up by type.
 6
 7Two registration modes:
 8
 9* :meth:`register` — install an already-built object (singleton).
10* :meth:`register_factory` — install a zero-arg callable that builds the
11  object on first :meth:`get`. Lets you defer construction without exposing
12  the order in which subsystems initialize.
13"""
14from __future__ import annotations
15
16from collections.abc import Callable
17from typing import TypeVar
18
19T = TypeVar("T")
20
21
22class InstanceManager:
23    """Per-application service container. Not a global singleton.
24
25    Construct one and pass it explicitly (or via dependency injection) into
26    components that need to resolve services. This avoids the global-state
27    pitfalls of JMRI's Java ``InstanceManager`` while keeping the locator
28    pattern that scripts and tools expect.
29    """
30
31    def __init__(self) -> None:
32        self._services: dict[type, object] = {}
33        self._factories: dict[type, Callable[[], object]] = {}
34
35    def register(self, iface: type[T], impl: T) -> None:
36        """Bind a concrete instance to *iface*. Replaces any prior binding."""
37        self._services[iface] = impl
38        # Clearing a factory ensures explicit register() wins over factory.
39        self._factories.pop(iface, None)
40
41    def register_factory(self, iface: type[T], factory: Callable[[], T]) -> None:
42        """Bind a zero-arg factory invoked on first :meth:`get`. Idempotent."""
43        # If something is already bound, prefer the factory only if no live
44        # instance exists.
45        self._factories[iface] = factory
46
47    def get(self, iface: type[T]) -> T:
48        """Return the bound implementation of *iface*. Build via factory if needed."""
49        if iface in self._services:
50            return self._services[iface]  # type: ignore[return-value]
51        if iface in self._factories:
52            built = self._factories[iface]()
53            self._services[iface] = built
54            return built  # type: ignore[return-value]
55        raise LookupError(f"No service registered for {iface.__name__}")
56
57    def try_get(self, iface: type[T]) -> T | None:
58        """Like :meth:`get` but returns ``None`` if no binding exists."""
59        try:
60            return self.get(iface)
61        except LookupError:
62            return None
63
64    def contains(self, iface: type) -> bool:
65        return iface in self._services or iface in self._factories
66
67    def remove(self, iface: type) -> None:
68        """Drop the binding for *iface*. No-op if absent."""
69        self._services.pop(iface, None)
70        self._factories.pop(iface, None)
class InstanceManager:
23class InstanceManager:
24    """Per-application service container. Not a global singleton.
25
26    Construct one and pass it explicitly (or via dependency injection) into
27    components that need to resolve services. This avoids the global-state
28    pitfalls of JMRI's Java ``InstanceManager`` while keeping the locator
29    pattern that scripts and tools expect.
30    """
31
32    def __init__(self) -> None:
33        self._services: dict[type, object] = {}
34        self._factories: dict[type, Callable[[], object]] = {}
35
36    def register(self, iface: type[T], impl: T) -> None:
37        """Bind a concrete instance to *iface*. Replaces any prior binding."""
38        self._services[iface] = impl
39        # Clearing a factory ensures explicit register() wins over factory.
40        self._factories.pop(iface, None)
41
42    def register_factory(self, iface: type[T], factory: Callable[[], T]) -> None:
43        """Bind a zero-arg factory invoked on first :meth:`get`. Idempotent."""
44        # If something is already bound, prefer the factory only if no live
45        # instance exists.
46        self._factories[iface] = factory
47
48    def get(self, iface: type[T]) -> T:
49        """Return the bound implementation of *iface*. Build via factory if needed."""
50        if iface in self._services:
51            return self._services[iface]  # type: ignore[return-value]
52        if iface in self._factories:
53            built = self._factories[iface]()
54            self._services[iface] = built
55            return built  # type: ignore[return-value]
56        raise LookupError(f"No service registered for {iface.__name__}")
57
58    def try_get(self, iface: type[T]) -> T | None:
59        """Like :meth:`get` but returns ``None`` if no binding exists."""
60        try:
61            return self.get(iface)
62        except LookupError:
63            return None
64
65    def contains(self, iface: type) -> bool:
66        return iface in self._services or iface in self._factories
67
68    def remove(self, iface: type) -> None:
69        """Drop the binding for *iface*. No-op if absent."""
70        self._services.pop(iface, None)
71        self._factories.pop(iface, None)

Per-application service container. Not a global singleton.

Construct one and pass it explicitly (or via dependency injection) into components that need to resolve services. This avoids the global-state pitfalls of JMRI's Java InstanceManager while keeping the locator pattern that scripts and tools expect.

def register(self, iface: type[~T], impl: ~T) -> None:
36    def register(self, iface: type[T], impl: T) -> None:
37        """Bind a concrete instance to *iface*. Replaces any prior binding."""
38        self._services[iface] = impl
39        # Clearing a factory ensures explicit register() wins over factory.
40        self._factories.pop(iface, None)

Bind a concrete instance to iface. Replaces any prior binding.

def register_factory(self, iface: type[~T], factory: Callable[[], ~T]) -> None:
42    def register_factory(self, iface: type[T], factory: Callable[[], T]) -> None:
43        """Bind a zero-arg factory invoked on first :meth:`get`. Idempotent."""
44        # If something is already bound, prefer the factory only if no live
45        # instance exists.
46        self._factories[iface] = factory

Bind a zero-arg factory invoked on first get(). Idempotent.

def get(self, iface: type[~T]) -> ~T:
48    def get(self, iface: type[T]) -> T:
49        """Return the bound implementation of *iface*. Build via factory if needed."""
50        if iface in self._services:
51            return self._services[iface]  # type: ignore[return-value]
52        if iface in self._factories:
53            built = self._factories[iface]()
54            self._services[iface] = built
55            return built  # type: ignore[return-value]
56        raise LookupError(f"No service registered for {iface.__name__}")

Return the bound implementation of iface. Build via factory if needed.

def try_get(self, iface: type[~T]) -> Optional[~T]:
58    def try_get(self, iface: type[T]) -> T | None:
59        """Like :meth:`get` but returns ``None`` if no binding exists."""
60        try:
61            return self.get(iface)
62        except LookupError:
63            return None

Like get() but returns None if no binding exists.

def contains(self, iface: type) -> bool:
65    def contains(self, iface: type) -> bool:
66        return iface in self._services or iface in self._factories
def remove(self, iface: type) -> None:
68    def remove(self, iface: type) -> None:
69        """Drop the binding for *iface*. No-op if absent."""
70        self._services.pop(iface, None)
71        self._factories.pop(iface, None)

Drop the binding for iface. No-op if absent.