Implement configuration loader

This commit is contained in:
2025-08-05 15:12:34 +00:00
parent 4e14222522
commit 8c0d68e722
2 changed files with 83 additions and 1 deletions

View File

@@ -4,8 +4,11 @@ This module defines Pydantic models for all configuration sections.
Configuration can be loaded from YAML files and validated at runtime.
"""
from typing import Literal
import os
from pathlib import Path
from typing import Any, Literal
import yaml
from pydantic import BaseModel, Field
@@ -117,3 +120,81 @@ class AppConfig(BaseModel):
logging: LoggingConfig = Field(default_factory=LoggingConfig)
dashboard: DashboardConfig = Field(default_factory=DashboardConfig)
api: APIConfig = Field(default_factory=APIConfig)
def _apply_env_overrides(config_dict: dict[str, Any]) -> None:
"""Apply environment variable overrides to config dictionary.
Environment variables follow the pattern: PYDVTATE__{SECTION}__{KEY}
For nested keys, use double underscores: PYDVTATE__{SECTION}__{SUBSECTION}__{KEY}
Examples:
PYDVTATE__INSTRUMENTS__BACKEND=pyvisa
PYDVTATE__PHYSICS__UPDATE_RATE_HZ=50.0
PYDVTATE__SIMULATOR__HOST=192.168.1.100
"""
prefix = "PYDVTATE__"
for env_key, env_value in os.environ.items():
if not env_key.startswith(prefix):
continue
# Remove prefix and split into parts
key_parts = env_key[len(prefix) :].lower().split("__")
# Navigate/create nested structure
current = config_dict
for part in key_parts[:-1]:
if part not in current:
current[part] = {}
current = current[part]
# Set the final value
final_key = key_parts[-1]
# Try to parse as YAML to handle types (int, float, bool, etc.)
try:
current[final_key] = yaml.safe_load(env_value)
except yaml.YAMLError:
# If parsing fails, use as string
current[final_key] = env_value
def load_config(config_path: str | Path | None = None) -> AppConfig:
"""Load configuration from YAML file with environment variable overrides.
Args:
config_path: Path to YAML configuration file. If None, uses defaults only.
Returns:
Validated AppConfig instance.
Raises:
FileNotFoundError: If config_path is provided but does not exist.
yaml.YAMLError: If YAML file is malformed.
pydantic.ValidationError: If configuration is invalid.
Environment Variables:
Configuration can be overridden using environment variables with the
pattern PYDVTATE__{SECTION}__{KEY}. For example:
PYDVTATE__INSTRUMENTS__BACKEND=pyvisa
PYDVTATE__PHYSICS__UPDATE_RATE_HZ=50.0
"""
# Start with empty dict (will use Pydantic defaults)
config_dict: dict[str, Any] = {}
# Load from YAML file if provided
if config_path is not None:
path = Path(config_path)
if not path.exists():
raise FileNotFoundError(f"Configuration file not found: {config_path}")
with path.open("r") as f:
loaded = yaml.safe_load(f)
if loaded is not None:
config_dict = loaded
# Apply environment variable overrides
_apply_env_overrides(config_dict)
# Validate and return
return AppConfig(**config_dict)