Files
demeter/server/app/config.py

100 lines
2.4 KiB
Python

"""
Demeter Server — Configuration
Loads settings from environment variables and the device registry
from config/devices.yaml.
"""
from __future__ import annotations
import logging
from pathlib import Path
from typing import Optional
import yaml
from pydantic import BaseModel, Field
from pydantic_settings import BaseSettings
logger = logging.getLogger(__name__)
# ── Paths ──
BASE_DIR = Path(__file__).resolve().parent.parent
CONFIG_DIR = BASE_DIR / "config"
TEMPLATES_DIR = BASE_DIR / "templates"
# ── Device Config Models ──
class ResourceConfig(BaseModel):
"""A single CoAP resource on a device."""
uri: str
name: str
type: str = "periodic" # "periodic" or "event"
class DeviceConfig(BaseModel):
"""A single ESP sensor node."""
id: str
name: str
ip: str
port: int = 5683
enabled: bool = True
resources: list[ResourceConfig] = Field(default_factory=list)
class DevicesConfig(BaseModel):
"""Top-level device registry loaded from YAML."""
devices: list[DeviceConfig] = Field(default_factory=list)
# ── Application Settings ──
class Settings(BaseSettings):
"""Server settings loaded from environment variables."""
# FastAPI
app_title: str = "Demeter IoT Server"
app_version: str = "0.1.0"
debug: bool = False
# Server
host: str = "0.0.0.0"
port: int = 8000
# Loki
loki_url: Optional[str] = None # e.g. "http://localhost:3100/loki/api/v1/push"
# CoAP
coap_request_timeout: float = 10.0 # seconds
coap_reconnect_base: float = 5.0 # base backoff seconds
coap_reconnect_max: float = 60.0 # max backoff seconds
# Device config path
devices_config_path: str = str(CONFIG_DIR / "devices.yaml")
model_config = {"env_prefix": "DEMETER_", "env_file": ".env"}
def load_devices_config(path: str | Path | None = None) -> DevicesConfig:
"""Load and validate the device registry from YAML."""
if path is None:
path = CONFIG_DIR / "devices.yaml"
path = Path(path)
if not path.exists():
logger.warning("Device config not found at %s, using empty registry", path)
return DevicesConfig(devices=[])
with open(path) as f:
raw = yaml.safe_load(f)
if raw is None:
return DevicesConfig(devices=[])
return DevicesConfig.model_validate(raw)
# Singleton settings instance
settings = Settings()