init commit
This commit is contained in:
159
app/config/loader.py
Normal file
159
app/config/loader.py
Normal file
@@ -0,0 +1,159 @@
|
||||
"""Typed configuration loader with YAML and environment variable support."""
|
||||
|
||||
import os
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
|
||||
import yaml
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from app.models.alerts import AlertRules
|
||||
|
||||
|
||||
@dataclass
|
||||
class AppSettings:
|
||||
"""Application-level settings."""
|
||||
|
||||
name: str = "weather-alerts"
|
||||
version: str = "1.0.0"
|
||||
log_level: str = "INFO"
|
||||
|
||||
|
||||
@dataclass
|
||||
class WeatherSettings:
|
||||
"""Weather API settings."""
|
||||
|
||||
location: str = "viola,tn"
|
||||
hours_ahead: int = 24
|
||||
unit_group: str = "us"
|
||||
api_key: str = ""
|
||||
|
||||
|
||||
@dataclass
|
||||
class NtfySettings:
|
||||
"""Ntfy notification settings."""
|
||||
|
||||
server_url: str = "https://ntfy.sneakygeek.net"
|
||||
topic: str = "weather-alerts"
|
||||
priority: str = "high"
|
||||
tags: list[str] = field(default_factory=lambda: ["cloud", "warning"])
|
||||
access_token: str = ""
|
||||
|
||||
|
||||
@dataclass
|
||||
class NotificationSettings:
|
||||
"""Notification settings container."""
|
||||
|
||||
ntfy: NtfySettings = field(default_factory=NtfySettings)
|
||||
|
||||
|
||||
@dataclass
|
||||
class StateSettings:
|
||||
"""State management settings."""
|
||||
|
||||
file_path: str = "./data/state.json"
|
||||
dedup_window_hours: int = 24
|
||||
|
||||
|
||||
@dataclass
|
||||
class AlertSettings:
|
||||
"""Alert configuration settings."""
|
||||
|
||||
rules: AlertRules = field(default_factory=AlertRules)
|
||||
|
||||
|
||||
@dataclass
|
||||
class AppConfig:
|
||||
"""Complete application configuration."""
|
||||
|
||||
app: AppSettings = field(default_factory=AppSettings)
|
||||
weather: WeatherSettings = field(default_factory=WeatherSettings)
|
||||
alerts: AlertSettings = field(default_factory=AlertSettings)
|
||||
notifications: NotificationSettings = field(default_factory=NotificationSettings)
|
||||
state: StateSettings = field(default_factory=StateSettings)
|
||||
|
||||
|
||||
def load_config(
|
||||
config_path: Optional[str] = None,
|
||||
env_path: Optional[str] = None,
|
||||
) -> AppConfig:
|
||||
"""Load configuration from YAML file and environment variables.
|
||||
|
||||
Args:
|
||||
config_path: Path to the YAML config file. Defaults to app/config/settings.yaml.
|
||||
env_path: Path to the .env file. Defaults to .env in the project root.
|
||||
|
||||
Returns:
|
||||
A fully populated AppConfig instance.
|
||||
"""
|
||||
# Load environment variables from .env file
|
||||
if env_path:
|
||||
load_dotenv(env_path)
|
||||
else:
|
||||
load_dotenv()
|
||||
|
||||
# Determine config file path
|
||||
if config_path is None:
|
||||
config_path = os.environ.get(
|
||||
"WEATHER_ALERTS_CONFIG",
|
||||
str(Path(__file__).parent / "settings.yaml"),
|
||||
)
|
||||
|
||||
# Load YAML config
|
||||
config_data: dict[str, Any] = {}
|
||||
config_file = Path(config_path)
|
||||
if config_file.exists():
|
||||
with open(config_file) as f:
|
||||
config_data = yaml.safe_load(f) or {}
|
||||
|
||||
# Build configuration with defaults
|
||||
app_data = config_data.get("app", {})
|
||||
weather_data = config_data.get("weather", {})
|
||||
alerts_data = config_data.get("alerts", {})
|
||||
notifications_data = config_data.get("notifications", {})
|
||||
state_data = config_data.get("state", {})
|
||||
|
||||
# Build app settings
|
||||
app_settings = AppSettings(
|
||||
name=app_data.get("name", "weather-alerts"),
|
||||
version=app_data.get("version", "1.0.0"),
|
||||
log_level=app_data.get("log_level", "INFO"),
|
||||
)
|
||||
|
||||
# Build weather settings with API key from environment
|
||||
weather_settings = WeatherSettings(
|
||||
location=weather_data.get("location", "viola,tn"),
|
||||
hours_ahead=weather_data.get("hours_ahead", 24),
|
||||
unit_group=weather_data.get("unit_group", "us"),
|
||||
api_key=os.environ.get("VISUALCROSSING_API_KEY", ""),
|
||||
)
|
||||
|
||||
# Build alert settings
|
||||
rules_data = alerts_data.get("rules", {})
|
||||
alert_settings = AlertSettings(rules=AlertRules.from_dict(rules_data))
|
||||
|
||||
# Build notification settings with token from environment
|
||||
ntfy_data = notifications_data.get("ntfy", {})
|
||||
ntfy_settings = NtfySettings(
|
||||
server_url=ntfy_data.get("server_url", "https://ntfy.sneakygeek.net"),
|
||||
topic=ntfy_data.get("topic", "weather-alerts"),
|
||||
priority=ntfy_data.get("priority", "high"),
|
||||
tags=ntfy_data.get("tags", ["cloud", "warning"]),
|
||||
access_token=os.environ.get("NTFY_ACCESS_TOKEN", ""),
|
||||
)
|
||||
notification_settings = NotificationSettings(ntfy=ntfy_settings)
|
||||
|
||||
# Build state settings
|
||||
state_settings = StateSettings(
|
||||
file_path=state_data.get("file_path", "./data/state.json"),
|
||||
dedup_window_hours=state_data.get("dedup_window_hours", 24),
|
||||
)
|
||||
|
||||
return AppConfig(
|
||||
app=app_settings,
|
||||
weather=weather_settings,
|
||||
alerts=alert_settings,
|
||||
notifications=notification_settings,
|
||||
state=state_settings,
|
||||
)
|
||||
Reference in New Issue
Block a user