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

Skip to content

# Validation API

::: injectq.diagnostics.validation

## Overview

The validation module provides comprehensive tools for validating dependency injection configurations, detecting issues, and ensuring container integrity.

## Container Validation

### Basic Validation

    from injectq.diagnostics import ContainerValidator

    # Create validator
    validator = ContainerValidator(container)

    # Validate container configuration
    result = validator.validate()

    if result.is_valid:
        print("Container configuration is valid")
    else:
        print("Validation errors found:")
        for error in result.errors:
            print(f"  - {error}")

### Validation Result

    from dataclasses import dataclass
    from typing import List, Dict, Any

    @dataclass
    class ValidationResult:
        """Result of container validation."""
        is_valid: bool
        errors: List[str]
        warnings: List[str]
        suggestions: List[str]
        metadata: Dict[str, Any]

        def print_summary(self):
            """Print validation summary."""
            print(f"Validation Result: {'PASSED' if self.is_valid else 'FAILED'}")

            if self.errors:
                print(f"\nErrors ({len(self.errors)}):")
                for error in self.errors:
                    print(f"  ❌ {error}")

            if self.warnings:
                print(f"\nWarnings ({len(self.warnings)}):")
                for warning in self.warnings:
                    print(f"  ⚠️ {warning}")

            if self.suggestions:
                print(f"\nSuggestions ({len(self.suggestions)}):")
                for suggestion in self.suggestions:
                    print(f"  💡 {suggestion}")

## Validation Rules

### Dependency Validation

    class DependencyValidator:
        """Validates service dependencies."""

        def __init__(self, container):
            self.container = container
            self.registry = container._registry

        def validate_dependencies(self) -> ValidationResult:
            """Validate all service dependencies."""
            errors = []
            warnings = []
            suggestions = []

            for service_type in self.registry.get_all_services():
                # Check if all dependencies are registered
                dependencies = self.get_service_dependencies(service_type)

                for dep_name, dep_type in dependencies.items():
                    if not self.registry.is_registered(dep_type):
                        if self.is_optional_dependency(dep_type):
                            warnings.append(f"Optional dependency {dep_type.__name__} not registered for {service_type.__name__}")
                        else:
                            errors.append(f"Required dependency {dep_type.__name__} not registered for {service_type.__name__}")

            return ValidationResult(
                is_valid=len(errors) == 0,
                errors=errors,
                warnings=warnings,
                suggestions=suggestions,
                metadata={}
            )

        def get_service_dependencies(self, service_type: type) -> Dict[str, type]:
            """Get dependencies for a service type."""
            dependencies = {}

            # Analyze constructor parameters
            if hasattr(service_type, '__init__'):
                import inspect
                sig = inspect.signature(service_type.__init__)

                for param_name, param in sig.parameters.items():
                    if param_name != 'self' and param.annotation != inspect.Parameter.empty:
                        dependencies[param_name] = param.annotation

            return dependencies

        def is_optional_dependency(self, dep_type: type) -> bool:
            """Check if dependency is optional."""
            from typing import get_origin, get_args

            origin = get_origin(dep_type)
            if origin is Union:
                args = get_args(dep_type)
                return type(None) in args

            return False

### Circular Dependency Detection

    class CircularDependencyValidator:
        """Detects circular dependencies in service graph."""

        def __init__(self, container):
            self.container = container
            self.registry = container._registry

        def validate_circular_dependencies(self) -> ValidationResult:
            """Detect circular dependencies."""
            errors = []
            visited = set()
            recursion_stack = set()

            for service_type in self.registry.get_all_services():
                if service_type not in visited:
                    cycles = self.find_cycles(service_type, visited, recursion_stack, [])

                    for cycle in cycles:
                        cycle_str = " -> ".join(t.__name__ for t in cycle)
                        errors.append(f"Circular dependency detected: {cycle_str}")

            return ValidationResult(
                is_valid=len(errors) == 0,
                errors=errors,
                warnings=[],
                suggestions=[],
                metadata={'total_services': len(self.registry.get_all_services())}
            )

        def find_cycles(self, service_type: type, visited: set, rec_stack: set, path: List[type]):
            """Find cycles starting from a service type."""
            visited.add(service_type)
            rec_stack.add(service_type)
            path.append(service_type)

            cycles = []
            dependencies = self.get_dependencies(service_type)

            for dep_type in dependencies:
                if dep_type not in visited:
                    cycles.extend(self.find_cycles(dep_type, visited, rec_stack, path.copy()))
                elif dep_type in rec_stack:
                    # Found a cycle
                    cycle_start = path.index(dep_type)
                    cycle = path[cycle_start:] + [dep_type]
                    cycles.append(cycle)

            rec_stack.remove(service_type)
            return cycles

        def get_dependencies(self, service_type: type) -> List[type]:
            """Get direct dependencies of a service type."""
            dependencies = []

            if hasattr(service_type, '__init__'):
                import inspect
                sig = inspect.signature(service_type.__init__)

                for param in sig.parameters.values():
                    if param.name != 'self' and param.annotation != inspect.Parameter.empty:
                        dependencies.append(param.annotation)

            return dependencies

### Scope Validation

    from injectq.core.scopes import Scope

    class ScopeValidator:
        """Validates scope configurations."""

        def __init__(self, container):
            self.container = container
            self.registry = container._registry

        def validate_scopes(self) -> ValidationResult:
            """Validate scope configurations."""
            errors = []
            warnings = []
            suggestions = []

            for service_type, binding in self.registry.get_all_services().items():
                # Check for scope anti-patterns
                self.check_singleton_dependencies(service_type, binding, errors, warnings)
                self.check_transient_performance(service_type, binding, warnings, suggestions)
                self.check_scoped_lifecycle(service_type, binding, warnings, suggestions)

            return ValidationResult(
                is_valid=len(errors) == 0,
                errors=errors,
                warnings=warnings,
                suggestions=suggestions,
                metadata={}
            )

        def check_singleton_dependencies(self, service_type: type, binding, errors: List[str], warnings: List[str]):
            """Check if singleton services depend on non-singleton services."""
            if binding.scope == Scope.SINGLETON:
                dependencies = self.get_dependencies(service_type)

                for dep_type in dependencies:
                    dep_binding = self.registry.get_binding(dep_type)
                    if dep_binding and dep_binding.scope != Scope.SINGLETON:
                        if dep_binding.scope == Scope.SCOPED:
                            errors.append(f"Singleton service {service_type.__name__} depends on scoped service {dep_type.__name__}")
                        elif dep_binding.scope == Scope.TRANSIENT:
                            warnings.append(f"Singleton service {service_type.__name__} depends on transient service {dep_type.__name__}")

        def check_transient_performance(self, service_type: type, binding, warnings: List[str], suggestions: List[str]):
            """Check for performance issues with transient services."""
            if binding.scope == Scope.TRANSIENT:
                # Check if service is expensive to create
                if self.is_expensive_service(service_type):
                    warnings.append(f"Expensive service {service_type.__name__} is configured as transient")
                    suggestions.append(f"Consider making {service_type.__name__} singleton or scoped")

        def check_scoped_lifecycle(self, service_type: type, binding, warnings: List[str], suggestions: List[str]):
            """Check scoped service lifecycle."""
            if binding.scope == Scope.SCOPED:
                # Check if service implements disposable pattern
                if not hasattr(service_type, 'dispose') and not hasattr(service_type, '__exit__'):
                    suggestions.append(f"Scoped service {service_type.__name__} should implement disposal pattern")

        def is_expensive_service(self, service_type: type) -> bool:
            """Check if service is expensive to create."""
            # Heuristics for expensive services
            expensive_indicators = [
                'Database', 'Connection', 'Client', 'Service', 'Manager',
                'Repository', 'Cache', 'Pool', 'Factory'
            ]

            service_name = service_type.__name__
            return any(indicator in service_name for indicator in expensive_indicators)

        def get_dependencies(self, service_type: type) -> List[type]:
            """Get dependencies for a service type."""
            dependencies = []

            if hasattr(service_type, '__init__'):
                import inspect
                sig = inspect.signature(service_type.__init__)

                for param in sig.parameters.values():
                    if param.name != 'self' and param.annotation != inspect.Parameter.empty:
                        dependencies.append(param.annotation)

            return dependencies

## Advanced Validation

### Type Safety Validation

    class TypeSafetyValidator:
        """Validates type safety of dependency injection."""

        def __init__(self, container):
            self.container = container
            self.registry = container._registry

        def validate_type_safety(self) -> ValidationResult:
            """Validate type safety of all bindings."""
            errors = []
            warnings = []
            suggestions = []

            for service_type, binding in self.registry.get_all_services().items():
                # Check implementation compatibility
                if not self.is_compatible_implementation(service_type, binding.implementation):
                    errors.append(f"Implementation {binding.implementation} is not compatible with {service_type}")

                # Check generic types
                if self.has_generic_types(service_type):
                    generic_errors = self.validate_generic_types(service_type, binding)
                    errors.extend(generic_errors)

                # Check forward references
                forward_ref_warnings = self.check_forward_references(service_type)
                warnings.extend(forward_ref_warnings)

            return ValidationResult(
                is_valid=len(errors) == 0,
                errors=errors,
                warnings=warnings,
                suggestions=suggestions,
                metadata={}
            )

        def is_compatible_implementation(self, service_type: type, implementation) -> bool:
            """Check if implementation is compatible with service type."""
            if isinstance(implementation, type):
                try:
                    return issubclass(implementation, service_type) or service_type == implementation
                except TypeError:
                    # Handle cases where issubclass doesn't work (e.g., generics)
                    return True

            return isinstance(implementation, service_type)

        def has_generic_types(self, service_type: type) -> bool:
            """Check if service type uses generics."""
            from typing import get_origin
            return get_origin(service_type) is not None

        def validate_generic_types(self, service_type: type, binding) -> List[str]:
            """Validate generic type bindings."""
            errors = []

            from typing import get_origin, get_args

            origin = get_origin(service_type)
            args = get_args(service_type)

            if origin and args:
                # Validate that implementation satisfies generic constraints
                impl_origin = get_origin(binding.implementation) if hasattr(binding.implementation, '__origin__') else None
                impl_args = get_args(binding.implementation) if hasattr(binding.implementation, '__args__') else ()

                if impl_origin != origin:
                    errors.append(f"Generic implementation {binding.implementation} doesn't match service type {service_type}")
                elif len(impl_args) != len(args):
                    errors.append(f"Generic argument count mismatch for {service_type}")

            return errors

        def check_forward_references(self, service_type: type) -> List[str]:
            """Check for unresolved forward references."""
            warnings = []

            if hasattr(service_type, '__forward_arg__'):
                warnings.append(f"Service type {service_type} has unresolved forward reference")

            return warnings

### Performance Validation

    class PerformanceValidator:
        """Validates performance characteristics of container configuration."""

        def __init__(self, container):
            self.container = container
            self.registry = container._registry

        def validate_performance(self) -> ValidationResult:
            """Validate performance characteristics."""
            errors = []
            warnings = []
            suggestions = []

            # Check dependency depth
            max_depth = self.check_dependency_depth()
            if max_depth > 10:
                warnings.append(f"Maximum dependency depth is {max_depth}, which may impact performance")

            # Check for singleton abuse
            singleton_count = self.count_singletons()
            total_services = len(self.registry.get_all_services())

            if singleton_count / total_services > 0.8:
                warnings.append(f"High percentage of singleton services ({singleton_count}/{total_services})")
                suggestions.append("Consider using scoped services for better memory management")

            # Check for transient overuse
            transient_count = self.count_transients()
            if transient_count / total_services > 0.5:
                warnings.append(f"High percentage of transient services ({transient_count}/{total_services})")
                suggestions.append("Consider using singleton or scoped services for better performance")

            return ValidationResult(
                is_valid=len(errors) == 0,
                errors=errors,
                warnings=warnings,
                suggestions=suggestions,
                metadata={
                    'max_dependency_depth': max_depth,
                    'singleton_count': singleton_count,
                    'transient_count': transient_count,
                    'total_services': total_services
                }
            )

        def check_dependency_depth(self) -> int:
            """Calculate maximum dependency depth."""
            max_depth = 0

            for service_type in self.registry.get_all_services():
                depth = self.calculate_depth(service_type, set())
                max_depth = max(max_depth, depth)

            return max_depth

        def calculate_depth(self, service_type: type, visited: set) -> int:
            """Calculate dependency depth for a service."""
            if service_type in visited:
                return 0  # Circular dependency

            visited.add(service_type)

            dependencies = self.get_dependencies(service_type)
            if not dependencies:
                return 1

            max_child_depth = 0
            for dep_type in dependencies:
                child_depth = self.calculate_depth(dep_type, visited.copy())
                max_child_depth = max(max_child_depth, child_depth)

            return 1 + max_child_depth

        def count_singletons(self) -> int:
            """Count singleton services."""
            count = 0
            for _, binding in self.registry.get_all_services().items():
                if binding.scope == Scope.SINGLETON:
                    count += 1
            return count

        def count_transients(self) -> int:
            """Count transient services."""
            count = 0
            for _, binding in self.registry.get_all_services().items():
                if binding.scope == Scope.TRANSIENT:
                    count += 1
            return count

        def get_dependencies(self, service_type: type) -> List[type]:
            """Get dependencies for a service type."""
            dependencies = []

            if hasattr(service_type, '__init__'):
                import inspect
                sig = inspect.signature(service_type.__init__)

                for param in sig.parameters.values():
                    if param.name != 'self' and param.annotation != inspect.Parameter.empty:
                        dependencies.append(param.annotation)

            return dependencies

## Validation Tools

### Comprehensive Validator

    class ContainerValidator:
        """Comprehensive container validator."""

        def __init__(self, container):
            self.container = container
            self.validators = [
                DependencyValidator(container),
                CircularDependencyValidator(container),
                ScopeValidator(container),
                TypeSafetyValidator(container),
                PerformanceValidator(container)
            ]

        def validate(self) -> ValidationResult:
            """Run all validation checks."""
            all_errors = []
            all_warnings = []
            all_suggestions = []
            combined_metadata = {}

            for validator in self.validators:
                if hasattr(validator, 'validate_dependencies'):
                    result = validator.validate_dependencies()
                elif hasattr(validator, 'validate_circular_dependencies'):
                    result = validator.validate_circular_dependencies()
                elif hasattr(validator, 'validate_scopes'):
                    result = validator.validate_scopes()
                elif hasattr(validator, 'validate_type_safety'):
                    result = validator.validate_type_safety()
                elif hasattr(validator, 'validate_performance'):
                    result = validator.validate_performance()
                else:
                    continue

                all_errors.extend(result.errors)
                all_warnings.extend(result.warnings)
                all_suggestions.extend(result.suggestions)
                combined_metadata.update(result.metadata)

            return ValidationResult(
                is_valid=len(all_errors) == 0,
                errors=all_errors,
                warnings=all_warnings,
                suggestions=all_suggestions,
                metadata=combined_metadata
            )

        def validate_and_report(self):
            """Validate and print detailed report."""
            result = self.validate()
            result.print_summary()
            return result

### Custom Validation Rules

    class CustomValidationRule:
        """Base class for custom validation rules."""

        def __init__(self, name: str, description: str):
            self.name = name
            self.description = description

        def validate(self, container) -> ValidationResult:
            """Implement validation logic."""
            raise NotImplementedError

    class NamingConventionRule(CustomValidationRule):
        """Validates service naming conventions."""

        def __init__(self):
            super().__init__(
                "naming_convention",
                "Validates that services follow naming conventions"
            )

        def validate(self, container) -> ValidationResult:
            """Validate naming conventions."""
            warnings = []
            suggestions = []

            for service_type in container._registry.get_all_services():
                service_name = service_type.__name__

                # Check for interface naming
                if service_name.startswith('I') and service_name[1].isupper():
                    # Interface should end with interface-indicating suffix
                    if not any(service_name.endswith(suffix) for suffix in ['Service', 'Repository', 'Factory', 'Manager']):
                        suggestions.append(f"Interface {service_name} should end with descriptive suffix")

                # Check for implementation naming
                if service_name.endswith('Impl'):
                    warnings.append(f"Service {service_name} uses 'Impl' suffix, consider more descriptive name")

            return ValidationResult(
                is_valid=True,  # Naming issues are not errors
                errors=[],
                warnings=warnings,
                suggestions=suggestions,
                metadata={}
            )

    # Usage
    validator = ContainerValidator(container)
    validator.validators.append(NamingConventionRule())

    result = validator.validate()

## Validation Configuration

### Validation Settings

    @dataclass
    class ValidationSettings:
        """Configuration for validation behavior."""

        # Dependency validation
        check_missing_dependencies: bool = True
        allow_optional_dependencies: bool = True

        # Circular dependency validation
        check_circular_dependencies: bool = True
        max_dependency_depth: int = 20

        # Scope validation
        check_scope_compatibility: bool = True
        warn_singleton_transient_deps: bool = True

        # Type safety validation
        check_type_compatibility: bool = True
        validate_generic_types: bool = True

        # Performance validation
        check_performance_issues: bool = True
        max_singleton_percentage: float = 0.8
        max_transient_percentage: float = 0.5

        # Custom rules
        custom_rules: List[CustomValidationRule] = field(default_factory=list)

    class ConfigurableValidator:
        """Validator with configurable settings."""

        def __init__(self, container, settings: ValidationSettings = None):
            self.container = container
            self.settings = settings or ValidationSettings()

        def validate(self) -> ValidationResult:
            """Validate with configured settings."""
            all_errors = []
            all_warnings = []
            all_suggestions = []
            combined_metadata = {}

            if self.settings.check_missing_dependencies:
                result = DependencyValidator(self.container).validate_dependencies()
                all_errors.extend(result.errors)
                all_warnings.extend(result.warnings)

            if self.settings.check_circular_dependencies:
                result = CircularDependencyValidator(self.container).validate_circular_dependencies()
                all_errors.extend(result.errors)

            # Continue with other validation checks based on settings...

            return ValidationResult(
                is_valid=len(all_errors) == 0,
                errors=all_errors,
                warnings=all_warnings,
                suggestions=all_suggestions,
                metadata=combined_metadata
            )