init commit

This commit is contained in:
2026-01-26 15:08:24 -06:00
commit 67225a725a
33 changed files with 3350 additions and 0 deletions

5
app/config/__init__.py Normal file
View File

@@ -0,0 +1,5 @@
"""Configuration loading and management."""
from app.config.loader import load_config, AppConfig
__all__ = ["load_config", "AppConfig"]

159
app/config/loader.py Normal file
View 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,
)

View File

@@ -0,0 +1,40 @@
# Weather Alerts Configuration Example
# Copy this file to settings.yaml and customize as needed.
# Secrets (API keys, tokens) should be in .env file, not here.
app:
name: "weather-alerts"
version: "1.0.0"
log_level: "INFO" # DEBUG, INFO, WARNING, ERROR
weather:
location: "viola,tn" # City,State or ZIP code
hours_ahead: 24 # Number of forecast hours to check
unit_group: "us" # "us" for Fahrenheit/mph, "metric" for Celsius/kph
alerts:
rules:
temperature:
enabled: true
below: 32 # Alert when temp falls below this (freezing)
above: 100 # Alert when temp exceeds this (extreme heat)
precipitation:
enabled: true
probability_above: 70 # Alert when precipitation chance exceeds this %
wind:
enabled: true
speed_above: 25 # Alert when sustained wind exceeds this (mph)
gust_above: 40 # Alert when wind gusts exceed this (mph)
severe_weather:
enabled: true # Forward severe weather alerts from the API
notifications:
ntfy:
server_url: "https://ntfy.sneakygeek.net"
topic: "weather-alerts"
priority: "high" # min, low, default, high, urgent
tags: ["cloud", "warning"] # Emoji tags for notification
state:
file_path: "./data/state.json"
dedup_window_hours: 24 # Don't repeat same alert within this window

36
app/config/settings.yaml Normal file
View File

@@ -0,0 +1,36 @@
app:
name: "weather-alerts"
version: "1.0.0"
log_level: "INFO"
weather:
location: "viola,tn"
hours_ahead: 24
unit_group: "us"
alerts:
rules:
temperature:
enabled: true
below: 32
above: 95
precipitation:
enabled: true
probability_above: 60
wind:
enabled: true
speed_above: 25
gust_above: 30
severe_weather:
enabled: true
notifications:
ntfy:
server_url: "https://ntfy.sneakygeek.net"
topic: "weather-alerts"
priority: "high"
tags: ["cloud", "warning"]
state:
file_path: "./data/state.json"
dedup_window_hours: 24