Validate Environment Config At Startup
A typed environment loader that fails fast on missing or malformed variables instead of surfacing None or wrong types deep in the code.
Code
import os
from dataclasses import dataclass, field
from typing import Any, Callable, Dict, List, Mapping, Optional
class ConfigError(ValueError):
"""Raised when environment configuration is invalid."""
@dataclass(frozen=True)
class EnvVar:
name: str
parser: Callable[[str], Any]
required: bool = True
default: Optional[Any] = None
minimum: Optional[float] = None
maximum: Optional[float] = None
@dataclass(frozen=True)
class Config:
values: Dict[str, Any] = field(default_factory=dict)
def __getitem__(self, key: str) -> Any:
return self.values[key]
def load_config(schema: List[EnvVar], env: Optional[Mapping[str, str]] = None) -> Config:
"""Load and validate configuration from env, reporting all problems at once."""
source = os.environ if env is None else env
problems: List[str] = []
values: Dict[str, Any] = {}
for var in schema:
raw = source.get(var.name)
if raw is None or raw == "":
if var.required:
problems.append(f"{var.name}: required but missing")
else:
values[var.name] = var.default
continue
try:
parsed = var.parser(raw)
except (ValueError, TypeError) as exc:
problems.append(f"{var.name}: cannot parse {raw!r} ({exc})")
continue
if isinstance(parsed, (int, float)):
if var.minimum is not None and parsed < var.minimum:
problems.append(f"{var.name}: {parsed} below minimum {var.minimum}")
continue
if var.maximum is not None and parsed > var.maximum:
problems.append(f"{var.name}: {parsed} above maximum {var.maximum}")
continue
values[var.name] = parsed
if problems:
raise ConfigError("invalid configuration:\n " + "\n ".join(problems))
return Config(values=values)Report a problem with this exampleOpens a pre-filled issue on GitHub.