python
52 lines · 6 steps
Loading typed config from environment variables
Typed helpers parse environment variables into an immutable, cached configuration object.
Explained by
highlit
1import os
2from dataclasses import dataclass
3from functools import lru_cache
4
5
6def _get_bool(key: str, default: bool) -> bool:
7 value = os.environ.get(key)
8 if value is None:
9 return default
10 return value.strip().lower() in {"1", "true", "yes", "on"}
11
12
13def _get_int(key: str, default: int) -> int:
14 value = os.environ.get(key)
15 if value is None or not value.strip():
16 return default
17 try:
18 return int(value)
19 except ValueError as exc:
20 raise RuntimeError(f"{key} must be an integer, got {value!r}") from exc
21
22
23def _get_list(key: str, default: list[str]) -> list[str]:
24 value = os.environ.get(key)
25 if not value:
26 return default
27 return [item.strip() for item in value.split(",") if item.strip()]
28
29
30@dataclass(frozen=True)
31class Config:
32 database_url: str
33 port: int
34 debug: bool
35 max_connections: int
36 allowed_hosts: list[str]
37
38
39@lru_cache(maxsize=1)
40def load_config() -> Config:
41 try:
42 database_url = os.environ["DATABASE_URL"]
43 except KeyError as exc:
44 raise RuntimeError("DATABASE_URL is required") from exc
45
46 return Config(
47 database_url=database_url,
48 port=_get_int("PORT", 8000),
49 debug=_get_bool("DEBUG", False),
50 max_connections=_get_int("MAX_CONNECTIONS", 10),
51 allowed_hosts=_get_list("ALLOWED_HOSTS", ["localhost"]),
52 )
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Centralizing env parsing into typed helpers keeps coercion logic consistent and testable.
- 2Failing loudly on missing or malformed required values catches misconfiguration at startup.
- 3A frozen dataclass plus lru_cache gives you an immutable config loaded exactly once.
Related explainers
ruby
class Order class InvalidTransition < StandardError; end TRANSITIONS = {
A state machine for order transitions in Ruby
state-machine
data-driven
error-handling
Intermediate
8 steps
python
from flask import Blueprint, jsonify, request, abort v1 = Blueprint("users_v1", __name__) v2 = Blueprint("users_v2", __name__)
Versioning a Flask API with Blueprints
api versioning
blueprints
rest
Intermediate
7 steps
python
import time import threading from enum import Enum from functools import wraps
Building a circuit breaker in Python
circuit-breaker
resilience
decorators
Advanced
7 steps
php
<?php namespace App\Http\Middleware;
Idempotent requests with Laravel middleware
idempotency
middleware
caching
Advanced
8 steps
python
from django.contrib.postgres.search import SearchQuery, SearchRank, SearchVector from django.core.cache import cache from django.db.models import F from django.http import JsonResponse
Ranked full-text search in Django
full-text-search
debouncing
caching
Advanced
9 steps
python
import os from datetime import timedelta
Class-based config in a Flask app factory
configuration
app-factory
inheritance
Intermediate
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/loading-typed-config-from-environment-variables-explained-python-2269/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.