Settings from environment variables
Some settings belong to the machine, not to one run: where reports go, an API key, whether to send emails. Environment variables carry them, always as text, and a script reads them with a default.
- Read a variable with
os.environ.get(name, default)and explain whyos.environ[name]can fail - Convert the text to the type the program needs, and treat "0" and "no" as off
- Combine sources in a clear order: command line, then environment, then a default
Every running program receives a set of named text values from whatever started it: the environment. A shell sets one for a single run with REPORT_LIMIT=50 python report.py, or for the whole session with export REPORT_LIMIT=50. Servers and schedulers set them in their configuration. They suit settings that differ per machine (a folder, a database address) and secrets that shouldn't be typed on a command line where they end up in the shell's history.
Python shows the environment as os.environ, which behaves like a dictionary from text to text. Assigning to it, as the first line below does, sets a variable for this program (and any program it starts), just as the shell would have.
Run each example to see what it prints. Running examples doesn't count toward progress.
import os os.environ["EXPORT_FOLDER"] = "exports/daily" print(os.environ["EXPORT_FOLDER"]) print(os.environ.get("EXPORT_BATCH_LIMIT")) print(os.environ.get("EXPORT_BATCH_LIMIT", "25")) limit = int(os.environ.get("EXPORT_BATCH_LIMIT", "25")) print(limit + 5)
import os folder = os.environ["EXPORT_ARCHIVE_FOLDER"] print("archiving to", folder)
A function that reads settings is easier to test when it takes the environment as a parameter. The program passes os.environ; a test passes an ordinary dictionary, so it doesn't depend on what happens to be set on the machine running it.
On/off settings need care. Every non-empty text is true in an if, including "0" and "no", so compare against the words that mean *on* after strip() and lower().
def load_settings(env): limit = int(env.get("REPORT_LIMIT", "25")) send = env.get("SEND_EMAIL", "no").strip().lower() return { "folder": env.get("REPORT_FOLDER", "reports"), "limit": limit, "send_email": send in ["1", "true", "yes", "on"], } print(load_settings({})) print(load_settings({"REPORT_LIMIT": "100", "SEND_EMAIL": " Yes ", "REPORT_FOLDER": "/srv/reports"})) print(load_settings({"SEND_EMAIL": "0"}))
When to use it: for settings that belong to the machine or the deployment, and for secrets. A value someone chooses per run belongs on the command line instead. When both exist, the usual order is: an explicit command-line value wins, then the environment, then the default in the code.
Mistakes to avoid: os.environ[name] for an optional setting (KeyError when it's missing); using the text without converting it ("10" > "9" is False, because text compares letter by letter); and if env.get("DEBUG"):, which is true for "0".
os.environ.get(name, default)reads a setting; brackets raise KeyError when it's missing.- Environment values are always text: convert numbers, and compare on/off words after
stripandlower. - Take the environment as a parameter, and use a clear order: command line, environment, default.
- Next: starting other programs from Python, and keeping that call replaceable.