Deliver BAT-003: daemon .env loading (REQ-010, minor-change)

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>
This commit is contained in:
2026-06-04 14:56:42 +09:00
co-authored by Claude Opus 4.8
parent a4e263c8e7
commit 575ba9f513
13 changed files with 346 additions and 5 deletions
+16 -1
View File
@@ -1,10 +1,24 @@
"""Daemon configuration resolution (REQ-005)."""
"""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
@@ -14,6 +28,7 @@ class DaemonConfig:
@staticmethod
def from_env() -> "DaemonConfig":
_load_dotenv()
dsn = os.environ.get("TREX_POSTGRESQL_DSN")
if not dsn:
raise RuntimeError(