Topic 04

Settings and Secrets Outside the Code

Hands-on

Some values do not belong inside a script. The currency one person wants their reports in, the location of a file that is different on every computer, and above all passwords and API keys. Keeping them outside the code means the script can be shared without editing it, and a secret cannot leak just because someone saw the code.

Think of a house key. You keep it on your keyring, not taped to the front door. The door, like the code, can be shown to anyone; the key stays with the person it belongs to.

What Belongs Outside

Two kinds of value. Anything that differs between people or machines: a home currency, a folder, a preferred number of decimal places. And anything secret: passwords, API keys, tokens. The code describes what the program does; these values describe the situation it runs in.

Environment Variables

An environment variable is a named value that the terminal passes to every program it starts. Python reads them through os.environ, which behaves like a dictionary. Use its get method with a default, exactly as in Chapter 6, so the program still works when the variable is not set.

currency.py
import os

home = os.environ.get("HOME_CURRENCY", "USD")
print(f"Reports will be in {home}")
Output
Reports will be in USD

To set a variable for the current terminal session, macOS uses export and PowerShell uses a special $env: name. Run the script again afterwards and it picks the new value up. The setting lasts until that terminal window closes.

Setting it on macOS
you@mac python-course % export HOME_CURRENCY=EUR
you@mac python-course % python3 currency.py
Reports will be in EUR
Setting it on Windows
PS C:\Users\you\python-course> $env:HOME_CURRENCY = "EUR"
PS C:\Users\you\python-course> python currency.py
Reports will be in EUR

A Settings File

For several preferences, a small JSON file is friendlier than a handful of variables. The function below starts from a dictionary of defaults and, if a settings.json file exists, lays its values over the top with the dictionary method update. A missing file simply means the defaults. Notice dict(DEFAULTS): it copies the defaults first, so that updating the settings never changes the defaults themselves, which is Chapter 6's two-names-one-dictionary lesson in practice.

settings.json
{"currency": "EUR"}
settings.py
import json
from pathlib import Path

DEFAULTS = {"currency": "USD", "statement": "statement.csv"}


def load_settings():
    """Return the defaults, overridden by settings.json if it exists."""
    settings = dict(DEFAULTS)
    path = Path("settings.json")
    if path.exists():
        settings.update(json.loads(path.read_text(encoding="utf-8")))
    return settings


print(load_settings())
Output
{'currency': 'EUR', 'statement': 'statement.csv'}

Secrets Never Go in the Code

An API key written into a script ends up everywhere the script goes: a shared folder, a screenshot, an email to a colleague, a code-sharing website. Once it is out, it has to be cancelled and replaced. The rule is simple and absolute: read secrets from an environment variable, and never print them. The exchange-rate service in this chapter needs no key, so the pattern below uses a placeholder name. If the variable is not set, the program stops with a message saying what to do, rather than failing later with something cryptic.

secret.py
import os
import sys

api_key = os.environ.get("EXAMPLE_API_KEY")
if api_key is None:
    sys.exit("Set EXAMPLE_API_KEY before running this script.")

print("Key found; it is used, never printed.")
Output
Set EXAMPLE_API_KEY before running this script.

Many projects keep their local secrets in a file called .env, loaded by a small package when the program starts. If you use one, it must never be shared or uploaded anywhere, for exactly the reason above.

What lives where
Inside the code
The logic: how to read a statement, how to convert an amount.
Outside the code
Your settings in settings.json, your secrets in environment variables.
Common Confusions
  • "A key in the code is fine if the code is private." Private things get shared, pasted and uploaded. Keep keys out of the code from the first line.
  • "Environment variables are permanent." Set with export or $env: in a terminal, they last for that session. To keep one, add the export line to the file ~/.zshrc on a Mac, or set it in Windows' environment-variable settings.
  • "Settings need a special library." The standard library covers both approaches: os.environ and a JSON file.
Why It Matters
  • A script with its settings outside can be handed to someone else and run without editing a single line.
  • Keeping secrets out of code is a habit that prevents real, expensive leaks, and it is expected in every professional project.

Knowledge Check

What does os.environ.get("HOME_CURRENCY", "USD") give when the variable is not set?

  • "USD", the default that was passed
  • An error, because the variable does not exist
  • None, whatever default is passed to get
  • An empty string, because variables start empty

Why must an API key never be written into a script?

  • Keys stop working when they are stored in a .py file
  • Python cannot read long strings like keys from a file
  • The script gets shared and copied, and the key leaks with it
  • Environment variables make the program run noticeably faster than keys

Why does load_settings start with dict(DEFAULTS)?

  • Because update only works on a dictionary that starts empty
  • To sort the defaults into order before the file is read
  • Because DEFAULTS cannot be read from inside a function without a copy
  • To copy the defaults, so updating never changes DEFAULTS

How long does export HOME_CURRENCY=EUR last on macOS?

  • Until the computer is next restarted
  • For every terminal window opened from then on
  • Until that terminal window is closed
  • Only for the very next command that is run

You got correct