A settings resolver that defaults to the present and fails safe on bad input
Shipped
This app is public, but a handful of coaching calls were baked into the code as my preferences: whether a recovery walk counts toward an easy day, how close to the target distance counts as done versus partial, and how far back to look for a best effort when projecting a race finish. Reasonable defaults, but mine, and anyone cloning the repo was stuck with them. v0.9.0 pulls five of those out of the code and makes them settings, each defaulting to exactly the old hardcoded value, so a fresh clone behaves the same and nobody has to configure anything to start.
The feature is small. The part worth writing about is the resolver underneath it: where a value comes from, what it defaults to, and what happens when it is wrong. Here is the full build, from the setting definitions through the call site and the test that keeps the defaults honest.
Setup: define the knobs in one typed registry
The wrong move would have been to scatter five new os.environ.get calls through the grading code, each with its own inline default and its own ad hoc parsing. Instead every knob is declared once, in a single registry, with its type-bearing default and an optional validator. The default value doubles as the type the resolver coerces to, so there is no separate schema to keep in sync.
# fitness/config_defs.py
from dataclasses import dataclass, field
from typing import Any, Callable
@dataclass(frozen=True)
class Setting:
key: str
default: Any # also defines the target type
validate: Callable[[Any], bool] = field(default=lambda v: True)
def is_fraction(v: float) -> bool:
return 0.0 <= v <= 1.0
def is_positive(v: int) -> bool:
return v > 0
# Each default is exactly the value that used to be hardcoded.
SETTINGS = {
"count_recovery_walks": Setting("count_recovery_walks", False),
"done_fraction": Setting("done_fraction", 0.95, is_fraction),
"partial_fraction": Setting("partial_fraction", 0.70, is_fraction),
"best_effort_lookback": Setting("best_effort_lookback", 90, is_positive), # days
"projection_window": Setting("projection_window", 42, is_positive), # days
}
This keeps the configuration in one place with a defined shape rather than spread through the code, which is the twelve-factor instinct of treating config as a first-class, separated concern (The Twelve-Factor App: Config).
Build: resolve in one clear order
The app already had three places configuration could live: environment variables for deployment and secrets, a small settings table for per-user values like the step goal, and a notes file for the coach’s conversational memory. Adding a fourth mechanism for these knobs would have been the easy mistake. The discipline is fewer config sources with a defined precedence, not more sources each read on its own.
So every knob resolves in one order: a value in the settings table wins, then an environment variable, then the built-in default. The resolver coerces the raw value to the type of the default and runs the validator, and anything missing, empty, malformed, or invalid falls back to the default rather than raising.
# fitness/config.py
import os
from fitness.config_defs import Setting
def resolve(s: Setting, db_settings: dict) -> object:
"""settings table > environment variable > built-in default."""
raw = db_settings.get(s.key)
if raw in (None, ""):
raw = os.environ.get(s.key.upper())
if raw in (None, ""): # missing or empty -> the safe default
return s.default
try:
value = _coerce(raw, s.default)
except (KeyError, ValueError, TypeError):
return s.default # unparseable -> fall back, never error
return value if s.validate(value) else s.default # out of range -> fall back
def _coerce(raw, default):
if isinstance(default, bool): # bool first: bool(raw) makes any non-empty string truthy, e.g. bool("false") == True
return {"true": True, "false": False}[str(raw).strip().lower()]
return type(default)(raw)
Two details here each prevent a different silent failure. The boolean branch is the one the review caught. The generic line is type(default)(raw), and for a bool default type(default) is exactly bool, so without the special case that line would call bool(raw), which is a trap: bool("false") is True, because every non-empty string is truthy, and bool("") is False. So a setting that defaults to on would flip off on an empty value, and the literal string "false" would read as on. The dict lookup avoids both: it maps "true" and "false" explicitly and raises KeyError on anything else (a typo, a number, "yes"), which the caller turns into the safe default. One classic gotcha does not bite here: the bool case is checked first with isinstance(default, bool) and returns before the generic type(default)(raw) line is ever reached, and there is no isinstance(default, int) branch for a bool to fall into, so the classic bool-is-a-subclass-of-int trap never applies. The trap only bites code that dispatches with isinstance(default, int) before handling bool.
The empty-string guards in resolve do a separate job. They are not what stops the bool("") bug; the dict branch already does that. Their job is resolution order: an empty value in the settings table (a cleared row, not an absent one) should fall through to the environment variable, and if that is empty too, on to the built-in default, rather than being coerced as if the user had typed something.
Build: fail safe, because the dangerous inputs are the quiet ones
Per-knob validation is half the job. The other half is the relationships between knobs. done_fraction must be at least partial_fraction, or the grade bands invert and a run that should read as “done” reads as “partial.” That cross-field rule cannot live in a single Setting, so the loader checks it after resolving everything and falls back to the paired defaults if the pair is incoherent.
# fitness/config.py (continued)
from fitness.config_defs import SETTINGS
def load_settings(db_settings: dict) -> dict:
cfg = {key: resolve(s, db_settings) for key, s in SETTINGS.items()}
# Cross-field invariant: the "done" band must sit above the "partial" band.
if cfg["done_fraction"] < cfg["partial_fraction"]:
cfg["done_fraction"] = SETTINGS["done_fraction"].default
cfg["partial_fraction"] = SETTINGS["partial_fraction"].default
return cfg
Every one of these failure modes shares a shape: none of them error, they just quietly change behavior. An empty value flipping a boolean. An inverted fraction pair reordering the grade bands. A negative lookback window erasing the projected finish. That is why each falls back to the default instead of taking effect, which is the fail-safe-defaults principle generalized from access control: on bad input, fall back to the safe state rather than the broken one (Saltzer & Schroeder: fail-safe defaults). A loud failure would have been fine, I would have seen it. The real risk was a malformed knob that does not crash and just grades you wrong forever.
Use it: set one knob, read them all
Defaulting to the present means the common path needs no setup at all. A fresh clone reproduces the old behavior with zero configuration, which is convention over configuration in practice (Convention over configuration). When you do want to change something, you set it once and read the whole resolved config at the call site.
# Per-user override: write to the settings table, takes effect immediately.
fitness config set count_recovery_walks true
# Or set it for a deployment via the environment instead.
export COUNT_RECOVERY_WALKS=true
# fitness/grade.py
from fitness.config import load_settings
from fitness import db
def grade_run(run) -> str:
cfg = load_settings(db.settings_dict()) # table > env > default, all resolved
ratio = run.distance_meters / run.target_meters
if ratio >= cfg["done_fraction"]:
return "done"
if ratio >= cfg["partial_fraction"]:
return "partial"
return "short"
# A 9.6 km run against a 10 km target, with stock defaults (done=0.95, partial=0.70):
# ratio = 0.96 -> grade_run(run) == "done"
# Set partial_fraction to an empty string and it does not break; it falls back to 0.70.
db.settings_dict() is the thin read side of the settings table: it returns a plain {key: str_value} map of whatever the user has set, all values as strings, which is exactly the raw shape resolve expects. The fitness config set command above is its write side. Both are project-specific adapters to my store, not part of the pattern: settings_dict() just returns a {str: str} map and the CLI just writes that store, so swap in your own (a config file, a different table, whatever you already have). No SQL or env parsing leaks into the grading logic. It asks load_settings for a resolved dictionary and trusts that every value is either a valid override or a safe default.
Verify it: pin the defaults and the fallbacks
The one property worth pinning is that the defaults reproduce the old behavior exactly. That is a golden, or regression, test: a hand-authored snapshot of the known-good values asserted against the resolver’s output, so a later change cannot alter them unnoticed (Regression testing). Its only job is to fail the day someone edits a default by accident. The second test covers the resolution order and the quiet failure modes: an empty settings-table value must fall through to the environment rather than error, and an inverted fraction pair must resolve back to the defaults.
# tests/test_config.py
from fitness.config import load_settings
from fitness.config_defs import SETTINGS
OLD_HARDCODED = { # what the values were before v0.9.0
"count_recovery_walks": False,
"done_fraction": 0.95,
"partial_fraction": 0.70,
"best_effort_lookback": 90,
"projection_window": 42,
}
def test_defaults_match_old_behavior(monkeypatch):
for key in SETTINGS:
monkeypatch.delenv(key.upper(), raising=False) # no env overrides in scope
cfg = load_settings(db_settings={}) # and no table overrides
assert cfg == OLD_HARDCODED # fails if any default drifts
def test_bad_input_falls_back_not_errors(monkeypatch):
for key in SETTINGS:
monkeypatch.delenv(key.upper(), raising=False) # clear any stray env vars first
monkeypatch.setenv("COUNT_RECOVERY_WALKS", "true") # an env override is present
cfg = load_settings(db_settings={
"count_recovery_walks": "", # empty table value -> fall through to env
"done_fraction": "0.40", # below partial -> pair resets
"partial_fraction": "0.70",
})
assert cfg["count_recovery_walks"] is True # empty table value fell through to env
assert cfg["done_fraction"] == SETTINGS["done_fraction"].default
assert cfg["partial_fraction"] == SETTINGS["partial_fraction"].default
The first test fails the moment a default changes meaning. The second fails if the empty-string fall-through guard is removed, because the empty table value would resolve to the default instead of the environment override, or if the cross-field check is removed, because the inverted pair would survive. Together they turn the two decisions that are easy to get quietly wrong into things the suite notices loudly.
Next
The same pattern fits the knobs I left hardcoded for now, the baseline window and the fatigue and fitness time constants. Those are the next candidates once there is a real reason to tune them.
Sources
- The Twelve-Factor App: Config — keep configuration in the environment, separate from code, with a defined shape.
- Convention over configuration — sensible defaults so the common case needs no setup.
- Saltzer & Schroeder: The Protection of Information in Computer Systems — fail-safe defaults, originally default-deny in access control; generalized here to: on bad input, fall back to the safe state.
- Regression testing — a golden test that pins known-good values so later changes cannot alter them unnoticed.