Source code for structum_lab.plugins.dynaconf.features.health

# src/structum_lab.plugins.dynaconf/health.py
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2025 PythonWoods

"""Configuration Health Check System.

Provides architectural components to perform runtime validation of the
configuration system integrity.
"""

from typing import Protocol, List, Dict, Any, TYPE_CHECKING
from enum import Enum
from dataclasses import dataclass, field
from abc import abstractmethod
import json
import logging

if TYPE_CHECKING:
    from structum_lab.plugins.dynaconf.core.manager import ConfigManager

log = logging.getLogger(__name__)


[docs] class HealthStatus(str, Enum): HEALTHY = "healthy" DEGRADED = "degraded" UNHEALTHY = "unhealthy"
[docs] @dataclass class HealthCheckResult: name: str status: HealthStatus message: str metadata: Dict[str, Any] = field(default_factory=dict)
[docs] class HealthCheck(Protocol): """Protocol for a configuration health check component."""
[docs] @abstractmethod def check(self) -> HealthCheckResult: """Executes the check and returns the result.""" pass
[docs] class ConfigFileIntegrityCheck: """Verifies that persistence files are valid JSON if they exist."""
[docs] def __init__(self, config_manager: "ConfigManager"): self.manager = config_manager
[docs] def check(self) -> HealthCheckResult: try: # We access the internal builders map to check all registered configs # This logic assumes we are inside the package and allowed to access internals for config_name, builder_cls in self.manager._builders.items(): builder = builder_cls() # Check persistence file persistence_file = builder.get_persistence_file() if persistence_file.exists(): try: content = persistence_file.read_text(encoding="utf-8") json.loads(content) except json.JSONDecodeError as e: return HealthCheckResult( name="config_file_integrity", status=HealthStatus.UNHEALTHY, message=f"Corrupted persistence file for '{config_name}' at {persistence_file}", metadata={"error": str(e), "file": str(persistence_file)}, ) except Exception as e: return HealthCheckResult( name="config_file_integrity", status=HealthStatus.UNHEALTHY, message=f"Error reading persistence file for '{config_name}': {e}", metadata={"file": str(persistence_file)}, ) return HealthCheckResult( name="config_file_integrity", status=HealthStatus.HEALTHY, message="All configuration files are valid", ) except Exception as e: return HealthCheckResult( name="config_file_integrity", status=HealthStatus.UNHEALTHY, message=f"System error during integrity check: {e}", )
[docs] class PydanticValidationCheck: """Re-validates all loaded configuration models against their schemas."""
[docs] def __init__(self, config_manager: "ConfigManager"): self.manager = config_manager
[docs] def check(self) -> HealthCheckResult: try: for config_name in self.manager._builders.keys(): # get_config() retrieves the cached model instance # We force re-validation by dumping and re-validating try: model = self.manager.get_config(config_name) # Support Pydantic V2 and V1 if hasattr(model, "model_dump"): data = model.model_dump() model.model_validate(data) elif hasattr(model, "dict"): data = model.dict() model.parse_obj(data) except Exception as e: return HealthCheckResult( name="pydantic_validation", status=HealthStatus.UNHEALTHY, message=f"Model validation failed for '{config_name}'", metadata={"error": str(e), "config": config_name}, ) return HealthCheckResult( name="pydantic_validation", status=HealthStatus.HEALTHY, message="All models pass validation", ) except Exception as e: return HealthCheckResult( name="pydantic_validation", status=HealthStatus.UNHEALTHY, message=f"System error during validation check: {e}", )
[docs] class HealthCheckRegistry: """Registry to manage and run multiple health checks."""
[docs] def __init__(self): self._checks: List[HealthCheck] = []
[docs] def register(self, check: HealthCheck) -> None: self._checks.append(check)
[docs] def run_all(self) -> Dict[str, HealthCheckResult]: """Executes all registered checks.""" results = {} for check in self._checks: try: result = check.check() results[result.name] = result except Exception as e: # Catch-all for check execution failures log.exception(f"Health check failed unexpectedly: {check}") error_result = HealthCheckResult( name=check.__class__.__name__, status=HealthStatus.UNHEALTHY, message=f"Check execution failed: {e}", ) results[error_result.name] = error_result return results