DaemonConfig.from_env() now best-effort loads a .env file (find_dotenv usecwd, load_dotenv override=False) so operators can manage daemon env vars via .env, with real OS env taking precedence. Add python-dotenv dependency, gitignore .env, ship .env.example, and add config tests (load / OS-env precedence / absence regression / missing-DSN guard). Verification: 78 passed, ruff clean, .env ignored + .env.example tracked, and a .env-only DaemonConfig smoke. Processed via the minor-change fast path: REQ-010 (Configuration) -> Implemented, BAT-003 (batch-lite, no design) -> release-candidate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
43 lines
1.3 KiB
Python
43 lines
1.3 KiB
Python
"""Daemon configuration resolution (REQ-005, REQ-010)."""
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from dataclasses import dataclass
|
|
|
|
|
|
def _load_dotenv() -> None:
|
|
"""Best-effort load of a .env file into os.environ (REQ-010).
|
|
|
|
Real OS environment variables take precedence over .env (override=False),
|
|
matching 12-factor expectations. If python-dotenv is not installed or no
|
|
.env exists, this is a no-op (no regression).
|
|
"""
|
|
try:
|
|
from dotenv import find_dotenv, load_dotenv
|
|
except ImportError: # pragma: no cover - optional at runtime
|
|
return
|
|
load_dotenv(find_dotenv(usecwd=True), override=False)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class DaemonConfig:
|
|
dsn: str
|
|
total_slots: int
|
|
host: str
|
|
port: int
|
|
|
|
@staticmethod
|
|
def from_env() -> "DaemonConfig":
|
|
_load_dotenv()
|
|
dsn = os.environ.get("TREX_POSTGRESQL_DSN")
|
|
if not dsn:
|
|
raise RuntimeError(
|
|
"TREX_POSTGRESQL_DSN is required to start the slot-core daemon."
|
|
)
|
|
return DaemonConfig(
|
|
dsn=dsn,
|
|
total_slots=int(os.environ.get("TREX_TOTAL_SLOTS", "10")),
|
|
host=os.environ.get("TREX_API_HOST", "127.0.0.1"),
|
|
port=int(os.environ.get("TREX_API_PORT", "8080")),
|
|
)
|