Spaces:
Build error
Build error
"""DTO Validation Utilities | |
Provides validation functions and utilities for DTOs, | |
including custom validation decorators and error handling. | |
""" | |
from typing import Any, Callable, TypeVar, Union | |
from functools import wraps | |
import logging | |
logger = logging.getLogger(__name__) | |
T = TypeVar('T') | |
class ValidationError(Exception): | |
"""Custom exception for DTO validation errors""" | |
def __init__(self, message: str, field: str = None, value: Any = None): | |
self.message = message | |
self.field = field | |
self.value = value | |
super().__init__(self.message) | |
def __str__(self): | |
if self.field: | |
return f"Validation error for field '{self.field}': {self.message}" | |
return f"Validation error: {self.message}" | |
def validate_dto(dto_instance: Any) -> bool: | |
"""Validate a DTO instance | |
Args: | |
dto_instance: The DTO instance to validate | |
Returns: | |
bool: True if validation passes | |
Raises: | |
ValidationError: If validation fails | |
""" | |
try: | |
# Call the DTO's validation method if it exists | |
if hasattr(dto_instance, '_validate'): | |
dto_instance._validate() | |
# Additional validation can be added here | |
logger.info(f"Successfully validated {type(dto_instance).__name__}") | |
return True | |
except ValueError as e: | |
logger.error(f"Validation failed for {type(dto_instance).__name__}: {e}") | |
raise ValidationError(str(e)) from e | |
except Exception as e: | |
logger.error(f"Unexpected error during validation of {type(dto_instance).__name__}: {e}") | |
raise ValidationError(f"Validation failed: {e}") from e | |
def validation_required(func: Callable[..., T]) -> Callable[..., T]: | |
"""Decorator to ensure DTO validation before method execution | |
Args: | |
func: The method to decorate | |
Returns: | |
Decorated function that validates 'self' before execution | |
""" | |
def wrapper(self, *args, **kwargs): | |
try: | |
validate_dto(self) | |
return func(self, *args, **kwargs) | |
except ValidationError: | |
raise | |
except Exception as e: | |
raise ValidationError(f"Error in {func.__name__}: {e}") from e | |
return wrapper | |
def validate_field(value: Any, field_name: str, validator: Callable[[Any], bool], | |
error_message: str = None) -> Any: | |
"""Validate a single field value | |
Args: | |
value: The value to validate | |
field_name: Name of the field being validated | |
validator: Function that returns True if value is valid | |
error_message: Custom error message | |
Returns: | |
The validated value | |
Raises: | |
ValidationError: If validation fails | |
""" | |
try: | |
if not validator(value): | |
message = error_message or f"Invalid value for field '{field_name}'" | |
raise ValidationError(message, field_name, value) | |
return value | |
except ValidationError: | |
raise | |
except Exception as e: | |
raise ValidationError(f"Validation error for field '{field_name}': {e}", field_name, value) from e | |
def validate_required(value: Any, field_name: str) -> Any: | |
"""Validate that a field is not None or empty | |
Args: | |
value: The value to validate | |
field_name: Name of the field being validated | |
Returns: | |
The validated value | |
Raises: | |
ValidationError: If field is None or empty | |
""" | |
if value is None: | |
raise ValidationError(f"Field '{field_name}' is required", field_name, value) | |
if isinstance(value, (str, list, dict)) and len(value) == 0: | |
raise ValidationError(f"Field '{field_name}' cannot be empty", field_name, value) | |
return value | |
def validate_type(value: Any, field_name: str, expected_type: Union[type, tuple]) -> Any: | |
"""Validate that a field is of the expected type | |
Args: | |
value: The value to validate | |
field_name: Name of the field being validated | |
expected_type: Expected type or tuple of types | |
Returns: | |
The validated value | |
Raises: | |
ValidationError: If type doesn't match | |
""" | |
if not isinstance(value, expected_type): | |
if isinstance(expected_type, tuple): | |
type_names = [t.__name__ for t in expected_type] | |
expected_str = " or ".join(type_names) | |
else: | |
expected_str = expected_type.__name__ | |
actual_type = type(value).__name__ | |
raise ValidationError( | |
f"Field '{field_name}' must be of type {expected_str}, got {actual_type}", | |
field_name, value | |
) | |
return value | |
def validate_range(value: Union[int, float], field_name: str, | |
min_value: Union[int, float] = None, | |
max_value: Union[int, float] = None) -> Union[int, float]: | |
"""Validate that a numeric value is within a specified range | |
Args: | |
value: The numeric value to validate | |
field_name: Name of the field being validated | |
min_value: Minimum allowed value (inclusive) | |
max_value: Maximum allowed value (inclusive) | |
Returns: | |
The validated value | |
Raises: | |
ValidationError: If value is outside the range | |
""" | |
if min_value is not None and value < min_value: | |
raise ValidationError( | |
f"Field '{field_name}' must be >= {min_value}, got {value}", | |
field_name, value | |
) | |
if max_value is not None and value > max_value: | |
raise ValidationError( | |
f"Field '{field_name}' must be <= {max_value}, got {value}", | |
field_name, value | |
) | |
return value | |
def validate_choices(value: Any, field_name: str, choices: list) -> Any: | |
"""Validate that a value is one of the allowed choices | |
Args: | |
value: The value to validate | |
field_name: Name of the field being validated | |
choices: List of allowed values | |
Returns: | |
The validated value | |
Raises: | |
ValidationError: If value is not in choices | |
""" | |
if value not in choices: | |
raise ValidationError( | |
f"Field '{field_name}' must be one of {choices}, got '{value}'", | |
field_name, value | |
) | |
return value |