Source code for structum_lab.plugins.bootstrap.validators
# src/structum_lab/plugins/bootstrap/validators.py
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2025 PythonWoods
"""Built-in validators for system bootstrap.
This module provides common validation checks that can be used during
application startup to ensure the runtime environment is correctly configured.
Example:
>>> from structum_lab.plugins.bootstrap import SystemBootstrapper
>>> from structum_lab.plugins.bootstrap.validators import PythonVersionValidator
>>>
>>> boot = SystemBootstrapper()
>>> boot.add_validator(PythonVersionValidator(min_version=(3, 11)))
>>> boot.run_or_exit()
"""
import os
import sys
from pathlib import Path
from typing import Any
from structum_lab.config import get_config
from structum_lab.validation import ValidationContext
[docs]
class PythonVersionValidator:
"""Validates Python version."""
[docs]
def __init__(self, min_version: tuple[Any, ...] = (3, 9)) -> None:
"""Initialize the validator.
Args:
min_version: Minimum required Python version tuple.
"""
self.min_version = min_version
[docs]
def validate(self, context: ValidationContext) -> None:
"""Validate Python version meets minimum requirement."""
current = sys.version_info
version_str = f"{current.major}.{current.minor}.{current.micro}"
passed = current >= self.min_version
if passed:
context.add_check("Python Version", True, f"Python {version_str} >= {self.min_version}")
else:
context.add_check(
"Python Version",
False,
f"Found {version_str}, required >= {self.min_version}",
)
[docs]
class EnvVarValidator:
"""Validates presence of environment variables."""
[docs]
def __init__(self, required: list[str]) -> None:
"""Initialize the validator.
Args:
required: List of required environment variable names.
"""
self.required = required
[docs]
def validate(self, context: ValidationContext) -> None:
"""Validate that all required environment variables are set."""
missing = [var for var in self.required if not os.getenv(var)]
if missing:
context.add_check("Environment Variables", False, f"Missing: {', '.join(missing)}")
else:
context.add_check("Environment Variables", True, f"Checked {len(self.required)} vars")
[docs]
class DirectoryValidator:
"""Validates critical directories exist and are writable."""
[docs]
def __init__(self, paths: list[str]) -> None:
"""Initialize the validator.
Args:
paths: List of directory paths to validate.
"""
self.paths = paths
[docs]
def validate(self, context: ValidationContext) -> None:
"""Validate that directories exist and are writable."""
failed = []
for p in self.paths:
path = Path(p)
try:
path.mkdir(parents=True, exist_ok=True)
# Test write
test_file = path / ".write_test"
test_file.touch()
test_file.unlink()
except Exception as e:
failed.append(f"{p} ({e})")
if failed:
context.add_check("Directories", False, f"Failed: {'; '.join(failed)}")
else:
context.add_check("Directories", True, f"Verified {len(self.paths)} directories")
[docs]
class ConfigValidator:
"""Validates that Structum configuration loads correctly."""
[docs]
def validate(self, context: ValidationContext) -> None:
"""Validate that Structum configuration loads correctly.
Args:
context: Validation context for recording results.
"""
try:
# Attempt to access config. If provider is set[Any] up, this triggers validation.
# If no provider is set[Any], get_config() might return a dummy or fail depending on core
# We assume a provider is registered before bootstrap if using this validator.
get_config()
# Just accessing it is often enough to trigger load
# We can also check if it's empty
context.add_check("Configuration", True, "Configuration loaded successfully")
except Exception as e:
context.add_check("Configuration", False, f"Error loading config: {e}")