"""Dependency Injection Container for TrigDroid.
This module implements a simple but effective dependency injection container
that follows the Dependency Inversion Principle and enables loose coupling
between components.
"""
from abc import ABC, abstractmethod
from typing import TypeVar, Type, Callable, Dict, Any, Optional, Union
import logging
T = TypeVar('T')
[docs]
class DIContainer:
"""Simple dependency injection container."""
[docs]
def __init__(self):
self._services: Dict[str, Any] = {}
self._factories: Dict[str, Callable[[], Any]] = {}
self._singletons: Dict[str, Any] = {}
self._transients: Dict[str, Callable[[], Any]] = {}
[docs]
def register_singleton(self, interface: Type[T], implementation: Union[Type[T], Callable[[], T]], name: Optional[str] = None) -> 'DIContainer':
"""Register a singleton service."""
key = name or self._get_key(interface)
if isinstance(implementation, type):
# It's a class, create a factory
self._factories[key] = lambda: implementation()
else:
# It's already a factory function or instance
self._factories[key] = implementation
return self
[docs]
def register_transient(self, interface: Type[T], implementation: Union[Type[T], Callable[[], T]], name: Optional[str] = None) -> 'DIContainer':
"""Register a transient service (new instance every time)."""
key = name or self._get_key(interface)
if isinstance(implementation, type):
self._transients[key] = lambda: implementation()
else:
self._transients[key] = implementation
return self
[docs]
def register_instance(self, interface: Type[T], instance: T, name: Optional[str] = None) -> 'DIContainer':
"""Register a specific instance."""
key = name or self._get_key(interface)
self._singletons[key] = instance
return self
[docs]
def resolve(self, interface: Type[T], name: Optional[str] = None) -> T:
"""Resolve a service instance."""
key = name or self._get_key(interface)
# Check if it's already a singleton instance
if key in self._singletons:
return self._singletons[key]
# Check if it's a singleton factory
if key in self._factories:
instance = self._factories[key]()
self._singletons[key] = instance
return instance
# Check if it's a transient
if key in self._transients:
return self._transients[key]()
raise ValueError(f"Service {key} not registered")
[docs]
def has_service(self, interface: Type[T], name: Optional[str] = None) -> bool:
"""Check if a service is registered."""
key = name or self._get_key(interface)
return (key in self._singletons or
key in self._factories or
key in self._transients)
@staticmethod
def _get_key(interface: Type[T]) -> str:
"""Get a key for the interface."""
return f"{interface.__module__}.{interface.__qualname__}"
[docs]
class ServiceLocator:
"""Service locator pattern implementation."""
_container: Optional[DIContainer] = None
[docs]
@classmethod
def set_container(cls, container: DIContainer) -> None:
"""Set the global container."""
cls._container = container
[docs]
@classmethod
def get_service(cls, interface: Type[T], name: Optional[str] = None) -> T:
"""Get a service from the global container."""
if cls._container is None:
raise RuntimeError("Container not initialized. Call ServiceLocator.set_container() first.")
return cls._container.resolve(interface, name)
[docs]
class Injectable(ABC):
"""Base class for injectable services."""
[docs]
def __init__(self):
self._container: Optional[DIContainer] = None
[docs]
def set_container(self, container: DIContainer) -> None:
"""Set the DI container for this service."""
self._container = container
[docs]
def get_service(self, interface: Type[T], name: Optional[str] = None) -> T:
"""Get a service from the container."""
if self._container is None:
raise RuntimeError("Container not set. Call set_container() first.")
return self._container.resolve(interface, name)
# Dependency injection decorators
[docs]
def inject(**dependencies):
"""Decorator to inject dependencies into a class constructor."""
def decorator(cls):
original_init = cls.__init__
def new_init(self, *args, **kwargs):
# Resolve dependencies
container = ServiceLocator._container
if container:
for name, interface in dependencies.items():
if name not in kwargs:
kwargs[name] = container.resolve(interface)
# Call original constructor
original_init(self, *args, **kwargs)
cls.__init__ = new_init
return cls
return decorator
[docs]
def inject_service(interface: Type[T], name: Optional[str] = None):
"""Decorator to inject a single service."""
def decorator(func):
def wrapper(*args, **kwargs):
service = ServiceLocator.get_service(interface, name)
return func(service, *args, **kwargs)
return wrapper
return decorator