Registry - InjectQ Documentation
Author: InjectQ Contributors
Description: Modern Python dependency injection library

Skip to content

# 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