160 lines
4.5 KiB
Python
160 lines
4.5 KiB
Python
"""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,
|
|
)
|