mirror of
https://github.com/mwisnowski/mtg_python_deckbuilder.git
synced 2026-03-18 11:16:30 +01:00
202 lines
6.5 KiB
Python
202 lines
6.5 KiB
Python
"""Service registry for dependency injection.
|
|
|
|
Provides a centralized registry for managing service instances and dependencies.
|
|
Supports singleton and factory patterns with thread-safe access.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from typing import Any, Callable, Dict, Optional, Type, TypeVar, cast
|
|
import threading
|
|
|
|
|
|
T = TypeVar("T")
|
|
|
|
|
|
class ServiceRegistry:
|
|
"""Thread-safe service registry for dependency injection.
|
|
|
|
Manages service instances and factories with support for:
|
|
- Singleton services (one instance per registry)
|
|
- Factory services (new instance per request)
|
|
- Lazy initialization
|
|
- Thread-safe access
|
|
|
|
Example:
|
|
registry = ServiceRegistry()
|
|
registry.register_singleton(SessionService, session_service_instance)
|
|
registry.register_factory(BuildService, lambda: BuildService(deps...))
|
|
|
|
# Get services
|
|
session_svc = registry.get(SessionService)
|
|
build_svc = registry.get(BuildService)
|
|
"""
|
|
|
|
def __init__(self) -> None:
|
|
"""Initialize empty registry."""
|
|
self._singletons: Dict[Type[Any], Any] = {}
|
|
self._factories: Dict[Type[Any], Callable[[], Any]] = {}
|
|
self._lock = threading.RLock()
|
|
|
|
def register_singleton(self, service_type: Type[T], instance: T) -> None:
|
|
"""Register a singleton service instance.
|
|
|
|
Args:
|
|
service_type: Service type/interface
|
|
instance: Service instance to register
|
|
|
|
Raises:
|
|
ValueError: If service already registered
|
|
"""
|
|
with self._lock:
|
|
if service_type in self._singletons or service_type in self._factories:
|
|
raise ValueError(f"Service {service_type.__name__} already registered")
|
|
self._singletons[service_type] = instance
|
|
|
|
def register_factory(self, service_type: Type[T], factory: Callable[[], T]) -> None:
|
|
"""Register a factory for creating service instances.
|
|
|
|
Args:
|
|
service_type: Service type/interface
|
|
factory: Factory function that returns service instance
|
|
|
|
Raises:
|
|
ValueError: If service already registered
|
|
"""
|
|
with self._lock:
|
|
if service_type in self._singletons or service_type in self._factories:
|
|
raise ValueError(f"Service {service_type.__name__} already registered")
|
|
self._factories[service_type] = factory
|
|
|
|
def register_lazy_singleton(self, service_type: Type[T], factory: Callable[[], T]) -> None:
|
|
"""Register a lazy-initialized singleton service.
|
|
|
|
The factory will be called once on first access, then the instance is cached.
|
|
|
|
Args:
|
|
service_type: Service type/interface
|
|
factory: Factory function that returns service instance
|
|
|
|
Raises:
|
|
ValueError: If service already registered
|
|
"""
|
|
with self._lock:
|
|
if service_type in self._singletons or service_type in self._factories:
|
|
raise ValueError(f"Service {service_type.__name__} already registered")
|
|
|
|
# Wrap factory to cache result
|
|
instance_cache: Dict[str, Any] = {}
|
|
|
|
def lazy_factory() -> T:
|
|
if "instance" not in instance_cache:
|
|
instance_cache["instance"] = factory()
|
|
return instance_cache["instance"]
|
|
|
|
self._factories[service_type] = lazy_factory
|
|
|
|
def get(self, service_type: Type[T]) -> T:
|
|
"""Get service instance.
|
|
|
|
Args:
|
|
service_type: Service type/interface
|
|
|
|
Returns:
|
|
Service instance
|
|
|
|
Raises:
|
|
KeyError: If service not registered
|
|
"""
|
|
with self._lock:
|
|
# Check singletons first
|
|
if service_type in self._singletons:
|
|
return cast(T, self._singletons[service_type])
|
|
|
|
# Check factories
|
|
if service_type in self._factories:
|
|
return cast(T, self._factories[service_type]())
|
|
|
|
raise KeyError(f"Service {service_type.__name__} not registered")
|
|
|
|
def try_get(self, service_type: Type[T]) -> Optional[T]:
|
|
"""Try to get service instance, return None if not registered.
|
|
|
|
Args:
|
|
service_type: Service type/interface
|
|
|
|
Returns:
|
|
Service instance or None
|
|
"""
|
|
try:
|
|
return self.get(service_type)
|
|
except KeyError:
|
|
return None
|
|
|
|
def is_registered(self, service_type: Type[Any]) -> bool:
|
|
"""Check if service is registered.
|
|
|
|
Args:
|
|
service_type: Service type/interface
|
|
|
|
Returns:
|
|
True if registered
|
|
"""
|
|
with self._lock:
|
|
return service_type in self._singletons or service_type in self._factories
|
|
|
|
def unregister(self, service_type: Type[Any]) -> None:
|
|
"""Unregister a service.
|
|
|
|
Args:
|
|
service_type: Service type/interface
|
|
"""
|
|
with self._lock:
|
|
self._singletons.pop(service_type, None)
|
|
self._factories.pop(service_type, None)
|
|
|
|
def clear(self) -> None:
|
|
"""Clear all registered services."""
|
|
with self._lock:
|
|
self._singletons.clear()
|
|
self._factories.clear()
|
|
|
|
def get_registered_types(self) -> list[Type[Any]]:
|
|
"""Get list of all registered service types.
|
|
|
|
Returns:
|
|
List of service types
|
|
"""
|
|
with self._lock:
|
|
return list(self._singletons.keys()) + list(self._factories.keys())
|
|
|
|
|
|
# Global registry instance
|
|
_global_registry: Optional[ServiceRegistry] = None
|
|
_global_registry_lock = threading.Lock()
|
|
|
|
|
|
def get_registry() -> ServiceRegistry:
|
|
"""Get the global service registry instance.
|
|
|
|
Creates registry on first access (lazy initialization).
|
|
|
|
Returns:
|
|
Global ServiceRegistry instance
|
|
"""
|
|
global _global_registry
|
|
|
|
if _global_registry is None:
|
|
with _global_registry_lock:
|
|
if _global_registry is None:
|
|
_global_registry = ServiceRegistry()
|
|
|
|
return _global_registry
|
|
|
|
|
|
def reset_registry() -> None:
|
|
"""Reset the global registry (primarily for testing).
|
|
|
|
Clears all registered services and creates a new registry instance.
|
|
"""
|
|
global _global_registry
|
|
|
|
with _global_registry_lock:
|
|
_global_registry = ServiceRegistry()
|