habit.core.common.config_validator 源代码

"""
Configuration validation middleware and utilities.

Provides unified configuration validation and loading across all HABIT modules.
"""

from typing import Dict, Any, Optional, Type, TypeVar, Union
from pathlib import Path
import logging
from pydantic import ValidationError

from .config_base import BaseConfig, ConfigValidationError, ConfigAccessor
from habit.core.common.config_loader import load_config, resolve_config_paths

logger = logging.getLogger(__name__)

ConfigType = TypeVar('ConfigType', bound=BaseConfig)


[文档] class ConfigValidator: """ Unified configuration validator and loader. Provides a single entry point for loading and validating configurations across all HABIT modules. """
[文档] @staticmethod def validate_and_load( config_path: Union[str, Path], config_class: Type[ConfigType], resolve_paths: bool = True, strict: bool = True ) -> ConfigType: """ Load and validate configuration from file. This is the recommended way to load configurations in HABIT. It provides: - Automatic path resolution - Unified error handling - Type-safe configuration objects Args: config_path: Path to configuration file config_class: Configuration class (must inherit from BaseConfig) resolve_paths: Whether to resolve relative paths (default: True) strict: Whether to raise exceptions on validation errors (default: True) Returns: Validated configuration instance Raises: FileNotFoundError: If configuration file not found ConfigValidationError: If validation fails and strict=True Example: >>> from habit.core.habitat_analysis.config_schemas import HabitatAnalysisConfig >>> config = ConfigValidator.validate_and_load( ... 'config.yaml', ... HabitatAnalysisConfig ... ) """ config_path = Path(config_path) if not config_path.exists(): error_msg = f"Configuration file not found: {config_path}" if strict: raise FileNotFoundError(error_msg) else: logger.error(error_msg) raise FileNotFoundError(error_msg) try: # Load raw configuration config_dict = load_config(str(config_path), resolve_paths=resolve_paths) # Resolve paths if requested if resolve_paths: config_dict = resolve_config_paths(config_dict, config_path) # Create and validate configuration instance config = config_class.from_dict(config_dict, config_path=str(config_path)) logger.info(f"Successfully loaded and validated configuration: {config_path}") return config except ValidationError as e: error = ConfigValidationError( message=f"Configuration validation failed: {config_path}", errors=e.errors(), config_path=str(config_path) ) if strict: raise error else: logger.error(str(error)) raise error except Exception as e: error = ConfigValidationError( message=f"Failed to load configuration: {str(e)}", config_path=str(config_path) ) if strict: raise error else: logger.error(str(error)) raise error
[文档] @staticmethod def validate_dict( config_dict: Dict[str, Any], config_class: Type[ConfigType], config_path: Optional[str] = None, strict: bool = True ) -> ConfigType: """ Validate configuration dictionary. Args: config_dict: Configuration dictionary config_class: Configuration class config_path: Optional path for error reporting strict: Whether to raise exceptions on validation errors Returns: Validated configuration instance Raises: ConfigValidationError: If validation fails and strict=True """ try: config = config_class.from_dict(config_dict, config_path=config_path) return config except ValidationError as e: error = ConfigValidationError( message=f"Configuration validation failed", errors=e.errors(), config_path=config_path ) if strict: raise error else: logger.error(str(error)) raise error
[文档] @staticmethod def safe_validate( config_dict: Dict[str, Any], config_class: Type[ConfigType], default: Optional[ConfigType] = None ) -> Optional[ConfigType]: """ Safely validate configuration (returns None on failure instead of raising). Useful for optional configurations or when you want to handle validation errors gracefully. Args: config_dict: Configuration dictionary config_class: Configuration class default: Default value to return on validation failure Returns: Validated configuration instance or default """ try: return config_class.from_dict(config_dict) except (ConfigValidationError, ValidationError) as e: logger.warning(f"Configuration validation failed: {e}") return default
[文档] def load_and_validate_config( config_path: Union[str, Path], config_class: Type[ConfigType], resolve_paths: bool = True ) -> ConfigType: """ Convenience function for loading and validating configurations. This is a shorthand for ConfigValidator.validate_and_load(). Args: config_path: Path to configuration file config_class: Configuration class resolve_paths: Whether to resolve relative paths Returns: Validated configuration instance Example: >>> from habit.core.habitat_analysis.config_schemas import HabitatAnalysisConfig >>> config = load_and_validate_config('config.yaml', HabitatAnalysisConfig) """ return ConfigValidator.validate_and_load( config_path=config_path, config_class=config_class, resolve_paths=resolve_paths, strict=True )