runtime
244 / 250
lesson 244 Application Delivery · advanced

Check deployment settings at startup

The same image runs in development, staging and production; only its settings differ, and they arrive as environment variables, which are always text. Convert and check every setting when the program starts, report all the problems at once, and hand the rest of the program a frozen, typed settings object.

You'll be able to
  • Explain why bool("false") is True and convert flags, numbers and durations from text explicitly
  • Validate all settings, including rules that involve two of them, and report every problem in one error
  • Freeze the settings in a dataclass so nothing changes them while the program runs
The idea

The Software Architecture module merged defaults, a file and environment overrides into one configuration. At deployment time the environment is usually the whole story: the platform sets APP_ENV=prod, PORT=8080, DATABASE_URL=… for each environment, and one image serves them all (the twelve-factor rule: *config lives in the environment*). Two facts make that tricky. Every environment variable is text. And a mistake in one (a typo, a missing value) should stop the deployment at startup, not crash an hour later on the first request that reads it.

So a deployable program loads its settings once, at startup: read each variable, convert it explicitly (never with bool(), which is True for any non-empty text, "false" included), check its range or allowed values, check the rules *between* settings (debug mode must be off in production), and collect every problem before raising, so one deploy attempt shows them all. The result goes into a frozen dataclass (see Data Models & Typing): typed fields, and an error if code later tries to change one.

Run each example to see what it prints. Running examples doesn't count toward progress.

example-1.py · Environment values are text
import os

os.environ["DEBUG"] = "false"
os.environ["WORKERS"] = " 4 "
print(bool(os.environ["DEBUG"]), int(os.environ["WORKERS"]))

TRUE = {"1", "true", "yes", "on"}
FALSE = {"0", "false", "no", "off"}

def parse_bool(text):
    value = text.strip().lower()
    if value in TRUE:
        return True
    if value in FALSE:
        return False
    raise ValueError(f"not a true/false value: {text!r}")

print(parse_bool(os.environ["DEBUG"]), parse_bool("Yes"))
try:
    parse_bool("flase")
except ValueError as err:
    print(err)

Once loaded, settings shouldn't change. A frozen dataclass enforces that: assigning to a field raises an error, so no part of the program can quietly switch production into debug mode.

example-2.py · Changing a frozen setting
from dataclasses import dataclass

@dataclass(frozen=True)
class Settings:
    app_env: str
    debug: bool

settings = Settings(app_env="prod", debug=False)
settings.debug = True
example-3.py · Load, validate and freeze
from dataclasses import dataclass

@dataclass(frozen=True)
class Settings:
    app_env: str
    port: int
    debug: bool
    database_url: str

def parse_bool(text):
    value = text.strip().lower()
    if value in {"1", "true", "yes", "on"}:
        return True
    if value in {"0", "false", "no", "off"}:
        return False
    raise ValueError

def load_settings(environ):
    errors = []
    app_env = environ.get("APP_ENV", "")
    if app_env not in {"dev", "staging", "prod"}:
        errors.append(f"APP_ENV must be dev, staging or prod, not {app_env!r}")
    port = 8000
    try:
        port = int(environ.get("PORT", "8000"))
        if not 1 <= port <= 65535:
            errors.append("PORT must be 1 to 65535")
    except ValueError:
        errors.append("PORT must be a whole number")
    debug = False
    try:
        debug = parse_bool(environ.get("DEBUG", "false"))
    except ValueError:
        errors.append("DEBUG must be true or false")
    if app_env == "prod" and debug:
        errors.append("DEBUG must be off in prod")
    database_url = environ.get("DATABASE_URL", "")
    if not database_url:
        errors.append("DATABASE_URL is required")
    if errors:
        raise ValueError("; ".join(errors))
    return Settings(app_env, port, debug, database_url)

print(load_settings({"APP_ENV": "staging", "PORT": "8080", "DATABASE_URL": "sqlite:///market.db"}))
try:
    load_settings({"APP_ENV": "prod", "PORT": "80800", "DEBUG": "true"})
except ValueError as err:
    print(err)

When to use it: once, at the start of every deployable program; the result is passed to the code that needs it. Mistakes to avoid: bool() on flags; reading os.environ deep inside the code (settings are then checked only when that code runs); stopping at the first problem; defaults for things that must differ per environment (a database URL, a secret: the Secure Python module); and printing the settings object without masking secrets.

Review
  • Environment settings are text: convert them explicitly; flags use an allow-list, never bool().
  • Check every setting and the rules between them at startup, and report all problems in one error.
  • Freeze the result in a dataclass and pass it to the code that needs it.
  • Next: before switching traffic to a new version, check that it is actually ready.
Practise this

Loading your progress…

To master this lesson, solve both challenges without hints or the answer, and pass the concept check.

Check deployment settings at startup — Application Delivery · Runtime