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 firstget(). 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)
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.
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.
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.
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.
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.