Registry API¶
::: injectq.core.registry
Overview¶
The Registry is the internal component responsible for storing and managing service bindings. It maintains the mapping between service types and their implementations, scopes, and factory functions.
Core Functionality¶
Service Registration¶
The registry stores bindings that define how services should be created and managed.
# Internal registry usage (typically handled by container)
from injectq.core.registry import ServiceRegistry
registry = ServiceRegistry()
# Register a service binding
binding = ServiceBinding(
service_type=UserService,
implementation=UserService,
scope=Scope.SINGLETON,
factory=None
)
registry.register(UserService, binding)
Binding Resolution¶
# Resolve a binding
binding = registry.get_binding(UserService)
if binding:
print(f"Service: {binding.service_type.__name__}")
print(f"Implementation: {binding.implementation.__name__}")
print(f"Scope: {binding.scope}")
Service Bindings¶
Binding Types¶
from injectq.core.registry import ServiceBinding, Scope
# Class binding
class_binding = ServiceBinding(
service_type=IUserService,
implementation=UserService,
scope=Scope.SINGLETON
)
# Instance binding
instance = UserService()
instance_binding = ServiceBinding(
service_type=IUserService,
implementation=instance,
scope=Scope.SINGLETON,
is_instance=True
)
# Factory binding
def create_user_service():
return UserService(special_config=True)
factory_binding = ServiceBinding(
service_type=IUserService,
implementation=create_user_service,
scope=Scope.TRANSIENT,
is_factory=True
)
Named Bindings¶
# Named service bindings for multiple implementations
smtp_binding = ServiceBinding(
service_type=IEmailService,
implementation=SMTPEmailService,
scope=Scope.SINGLETON,
name="smtp"
)
sendgrid_binding = ServiceBinding(
service_type=IEmailService,
implementation=SendGridEmailService,
scope=Scope.SINGLETON,
name="sendgrid"
)
registry.register(IEmailService, smtp_binding, name="smtp")
registry.register(IEmailService, sendgrid_binding, name="sendgrid")
# Retrieve named bindings
smtp_binding = registry.get_binding(IEmailService, name="smtp")
sendgrid_binding = registry.get_binding(IEmailService, name="sendgrid")
Registry Operations¶
Batch Operations¶
# Register multiple bindings at once
bindings = [
(UserService, ServiceBinding(UserService, UserService, Scope.SCOPED)),
(ProductService, ServiceBinding(ProductService, ProductService, Scope.SCOPED)),
(OrderService, ServiceBinding(OrderService, OrderService, Scope.TRANSIENT))
]
registry.register_batch(bindings)
Conditional Registration¶
# Register service only if not already registered
if not registry.is_registered(UserService):
registry.register(UserService, user_service_binding)
# Register with override check
try:
registry.register(UserService, new_binding, allow_override=False)
except ServiceAlreadyRegisteredException:
print("Service already registered")
Registry Inspection¶
# Get all registered services
all_services = registry.get_all_services()
for service_type, binding in all_services.items():
print(f"{service_type.__name__}: {binding.scope}")
# Get services by scope
singleton_services = registry.get_services_by_scope(Scope.SINGLETON)
scoped_services = registry.get_services_by_scope(Scope.SCOPED)
transient_services = registry.get_services_by_scope(Scope.TRANSIENT)
# Check if service is registered
is_registered = registry.is_registered(UserService)
Advanced Registry Features¶
Registry Validation¶
class ValidatingRegistry(ServiceRegistry):
"""Registry with validation capabilities."""
def register(self, service_type: type, binding: ServiceBinding, **kwargs):
# Validate binding before registration
validation_errors = self.validate_binding(service_type, binding)
if validation_errors:
raise ValidationError(f"Invalid binding: {validation_errors}")
super().register(service_type, binding, **kwargs)
def validate_binding(self, service_type: type, binding: ServiceBinding):
"""Validate a service binding."""
errors = []
# Check type compatibility
if not self.is_compatible_type(service_type, binding.implementation):
errors.append(f"Implementation {binding.implementation} not compatible with {service_type}")
# Check scope validity
if binding.scope not in [Scope.SINGLETON, Scope.SCOPED, Scope.TRANSIENT]:
errors.append(f"Invalid scope: {binding.scope}")
return errors
def is_compatible_type(self, service_type: type, implementation) -> bool:
"""Check if implementation is compatible with service type."""
if isinstance(implementation, type):
return issubclass(implementation, service_type) or service_type == implementation
return isinstance(implementation, service_type)
Registry Events¶
from typing import Callable, List
class EventEmittingRegistry(ServiceRegistry):
"""Registry that emits events for registration operations."""
def __init__(self):
super().__init__()
self.event_handlers = {
'service_registered': [],
'service_unregistered': [],
'binding_resolved': []
}
def on(self, event: str, handler: Callable):
"""Register event handler."""
if event in self.event_handlers:
self.event_handlers[event].append(handler)
def emit(self, event: str, *args, **kwargs):
"""Emit event to all handlers."""
if event in self.event_handlers:
for handler in self.event_handlers[event]:
handler(*args, **kwargs)
def register(self, service_type: type, binding: ServiceBinding, **kwargs):
super().register(service_type, binding, **kwargs)
self.emit('service_registered', service_type, binding)
def get_binding(self, service_type: type, name: str = None):
binding = super().get_binding(service_type, name)
if binding:
self.emit('binding_resolved', service_type, binding)
return binding
# Usage
registry = EventEmittingRegistry()
# Register event handlers
registry.on('service_registered', lambda svc, binding: print(f"Registered: {svc.__name__}"))
registry.on('binding_resolved', lambda svc, binding: print(f"Resolved: {svc.__name__}"))
Registry Hierarchies¶
class HierarchicalRegistry(ServiceRegistry):
"""Registry with parent-child relationships."""
def __init__(self, parent: 'HierarchicalRegistry' = None):
super().__init__()
self.parent = parent
self.children: List['HierarchicalRegistry'] = []
if parent:
parent.children.append(self)
def get_binding(self, service_type: type, name: str = None):
"""Get binding, checking parent if not found locally."""
binding = super().get_binding(service_type, name)
# If not found locally, check parent
if not binding and self.parent:
binding = self.parent.get_binding(service_type, name)
return binding
def create_child_registry(self) -> 'HierarchicalRegistry':
"""Create a child registry."""
return HierarchicalRegistry(parent=self)
def get_all_services(self, include_parent: bool = True):
"""Get all services including from parent."""
services = super().get_all_services()
if include_parent and self.parent:
parent_services = self.parent.get_all_services()
# Child services override parent services
parent_services.update(services)
services = parent_services
return services
Registry Performance¶
Optimized Registry¶
class OptimizedRegistry(ServiceRegistry):
"""High-performance registry with caching."""
def __init__(self):
super().__init__()
self.binding_cache = {}
self.cache_hits = 0
self.cache_misses = 0
def get_binding(self, service_type: type, name: str = None):
# Create cache key
cache_key = (service_type, name)
# Check cache first
if cache_key in self.binding_cache:
self.cache_hits += 1
return self.binding_cache[cache_key]
# Get from registry
binding = super().get_binding(service_type, name)
# Cache the result (including None for not found)
self.binding_cache[cache_key] = binding
self.cache_misses += 1
return binding
def register(self, service_type: type, binding: ServiceBinding, **kwargs):
super().register(service_type, binding, **kwargs)
# Invalidate cache for this service type
cache_key = (service_type, kwargs.get('name'))
self.binding_cache.pop(cache_key, None)
def get_cache_stats(self):
"""Get cache performance statistics."""
total = self.cache_hits + self.cache_misses
hit_rate = self.cache_hits / total if total > 0 else 0
return {
'hits': self.cache_hits,
'misses': self.cache_misses,
'hit_rate': hit_rate,
'cache_size': len(self.binding_cache)
}
Registry Metrics¶
import time
from collections import defaultdict
class MetricsRegistry(ServiceRegistry):
"""Registry that collects performance metrics."""
def __init__(self):
super().__init__()
self.metrics = {
'registrations': 0,
'resolutions': 0,
'resolution_times': [],
'services_by_scope': defaultdict(int)
}
def register(self, service_type: type, binding: ServiceBinding, **kwargs):
super().register(service_type, binding, **kwargs)
self.metrics['registrations'] += 1
self.metrics['services_by_scope'][binding.scope] += 1
def get_binding(self, service_type: type, name: str = None):
start_time = time.perf_counter()
binding = super().get_binding(service_type, name)
resolution_time = time.perf_counter() - start_time
self.metrics['resolutions'] += 1
self.metrics['resolution_times'].append(resolution_time)
return binding
def get_metrics(self):
"""Get registry performance metrics."""
resolution_times = self.metrics['resolution_times']
return {
'total_registrations': self.metrics['registrations'],
'total_resolutions': self.metrics['resolutions'],
'avg_resolution_time': sum(resolution_times) / len(resolution_times) if resolution_times else 0,
'services_by_scope': dict(self.metrics['services_by_scope']),
'total_services': len(self.bindings)
}
Error Handling¶
Registry Exceptions¶
class RegistryError(Exception):
"""Base exception for registry operations."""
pass
class ServiceNotRegisteredException(RegistryError):
"""Raised when trying to access a service that is not registered."""
def __init__(self, service_type: type, name: str = None):
self.service_type = service_type
self.name = name
if name:
message = f"Service {service_type.__name__} with name '{name}' is not registered"
else:
message = f"Service {service_type.__name__} is not registered"
super().__init__(message)
class ServiceAlreadyRegisteredException(RegistryError):
"""Raised when trying to register a service that already exists."""
def __init__(self, service_type: type, name: str = None):
self.service_type = service_type
self.name = name
if name:
message = f"Service {service_type.__name__} with name '{name}' is already registered"
else:
message = f"Service {service_type.__name__} is already registered"
super().__init__(message)
# Usage in registry
class SafeRegistry(ServiceRegistry):
def register(self, service_type: type, binding: ServiceBinding, allow_override: bool = True, **kwargs):
if not allow_override and self.is_registered(service_type, kwargs.get('name')):
raise ServiceAlreadyRegisteredException(service_type, kwargs.get('name'))
super().register(service_type, binding, **kwargs)
def get_binding(self, service_type: type, name: str = None):
binding = super().get_binding(service_type, name)
if not binding:
raise ServiceNotRegisteredException(service_type, name)
return binding