Validate Python Environment Variables: Booleans, Empty Values, and Integers
On this page
A scheduled job receives TASK_DRY_RUN=false, yet Python still selects the preview branch. An empty batch size fails only after work has started. Environment variables arrive as strings, so convert and validate them at the task entry point.
This beginner-to-intermediate tutorial builds a standard-library configuration checker that distinguishes unset, empty, and invalid values, with explicit command-line overrides. It prints the resulting configuration for inspection before a real batch job starts; the checker itself does not run batches or change data files.
1. Method: define the environment variable contract
The Python os.environ documentation describes a mapping with string keys and values. Both bool("false") and bool("0") return True: nonempty strings are truthy under Python’s truth value rules. Python does not interpret the words.
This tool defines the following configuration contract:
| Setting | When unset | Accepted values when set |
|---|---|---|
TASK_DRY_RUN |
True |
true, false, 1, or 0, ignoring case and surrounding whitespace |
TASK_BATCH_SIZE |
100 |
After trimming whitespace, 1–4 ASCII digits with a numeric value of 1–1000 |
- Only an unset variable receives its default. Empty and whitespace-only values fail, so a deployment omission cannot silently disappear.
- Validate both environment variables before applying CLI overrides. Invalid environment settings still fail even when an override is supplied.
- For valid settings, precedence is: defaults < environment variables < explicit CLI arguments.
For example, 0001 becomes integer 1, while +1, 1_000, decimal fractions, and full-width digits are rejected. The tool reads only the two named variables; it does not automatically load .env files or detect misspellings in other variable names.
2. Setup
Use an existing Python 3.9 or newer installation and bash or zsh on Linux. No third-party packages are needed. These examples were verified with Python 3.11.4.
- Create a demo directory:
python3 --version
mkdir env-settings-demo
cd env-settings-demo
- Save the complete implementation below as
env_settings.py. If failures occur only in a scheduled job, first check the launcher and interpreter using the scheduled Python job troubleshooting guide.
3. Implementation: read once and convert strictly
"""Validate task environment settings and print JSON. Python 3.9+."""
import argparse
import json
import os
import re
def parse_bool(value):
token = value.casefold()
if token in ("true", "1"):
return True
if token in ("false", "0"):
return False
raise ValueError("expected true, false, 1, or 0")
def parse_batch(value):
message = "expected 1..1000 using 1..4 ASCII digits"
if not re.fullmatch(r"[0-9]{1,4}", value):
raise ValueError(message)
number = int(value)
if not 1 <= number <= 1000:
raise ValueError(message)
return number
def env_value(name, default, convert):
raw = os.environ.get(name)
if raw is None:
return default
value = raw.strip()
if not value:
raise ValueError(f"{name}: empty value; unset it to use the default")
try:
return convert(value)
except ValueError as exc:
raise ValueError(f"{name}: {exc}") from exc
def main():
parser = argparse.ArgumentParser(description=__doc__, allow_abbrev=False)
parser.add_argument(
"--dry-run", action=argparse.BooleanOptionalAction, default=None,
help="override TASK_DRY_RUN; defaults to true when unset",
)
parser.add_argument("--batch-size", help="override TASK_BATCH_SIZE (1..1000)")
args = parser.parse_args()
try:
settings = {
"dry_run": env_value("TASK_DRY_RUN", True, parse_bool),
"batch_size": env_value("TASK_BATCH_SIZE", 100, parse_batch),
}
if args.dry_run is not None:
settings["dry_run"] = args.dry_run
if args.batch_size is not None:
settings["batch_size"] = parse_batch(args.batch_size.strip())
except ValueError as exc:
parser.error(str(exc))
print(json.dumps(settings, sort_keys=True))
if __name__ == "__main__":
main()
raw is None distinguishes an unset variable from an empty one. Using os.environ.get(name) or default would silently replace an empty string with the default. Integer conversion checks characters and length before the numeric range, giving even an extremely long number a consistent error message.
The CLI uses BooleanOptionalAction to provide --dry-run and --no-dry-run. A None default distinguishes an omitted flag from an explicit choice; if args.dry_run would miss an explicit false value. Do not declare boolean arguments with type=bool.
The tool prints only the two converted fields, rather than dumping the process environment. When adapting it, call your batch function after configuration validation and implement dry_run in the business logic. The setting’s name does not prevent writes by itself.
4. Usage: defaults, environment, and overrides
- Inspect help, then verify defaults in a subprocess without either variable:
python3 env_settings.py --help
env -u TASK_DRY_RUN -u TASK_BATCH_SIZE python3 env_settings.py
Default output:
{"batch_size": 100, "dry_run": true}
env -u removes the specified variables only from this subprocess environment, leaving the current shell unchanged. Previously exported values cannot interfere with this check.
- Supply both environment variables. These assignments apply only to this command:
TASK_DRY_RUN=false TASK_BATCH_SIZE=25 python3 env_settings.py
Output:
{"batch_size": 25, "dry_run": false}
- Override valid environment settings with CLI arguments:
TASK_DRY_RUN=false TASK_BATCH_SIZE=25 \
python3 env_settings.py --dry-run --batch-size 7
Output:
{"batch_size": 7, "dry_run": true}
- Check empty and out-of-range values. Inspect the exit status immediately after each run; both commands should fail:
TASK_DRY_RUN='' TASK_BATCH_SIZE=25 python3 env_settings.py
printf 'exit=%s\n' "$?"
TASK_DRY_RUN=true TASK_BATCH_SIZE=1001 python3 env_settings.py
printf 'exit=%s\n' "$?"
Both exit with status 2. Stderr contains TASK_DRY_RUN: empty value and TASK_BATCH_SIZE: expected 1..1000, respectively, with no configuration JSON on stdout. Success returns 0. Do not hide startup configuration failures with || true.
5. Validation: test boundaries in isolated subprocesses
Save the following as check_env_settings.py beside the implementation and run python3 check_env_settings.py. It removes existing values for these variables from the subprocess environment, so shell settings do not affect the checks. It does not alter the environment outside its own process.
import json
import os
from pathlib import Path
import subprocess
import sys
script = Path(__file__).with_name("env_settings.py")
base = dict(os.environ)
for name in ("TASK_DRY_RUN", "TASK_BATCH_SIZE"):
base.pop(name, None)
def run(values, args=()):
return subprocess.run(
[sys.executable, str(script), *args],
env={**base, **values}, capture_output=True, text=True, timeout=5,
)
valid = [
({}, (), {"batch_size": 100, "dry_run": True}),
({"TASK_DRY_RUN": "false", "TASK_BATCH_SIZE": "25"}, (),
{"batch_size": 25, "dry_run": False}),
({"TASK_DRY_RUN": " TrUe ", "TASK_BATCH_SIZE": " 0001 "}, (),
{"batch_size": 1, "dry_run": True}),
({"TASK_DRY_RUN": "0", "TASK_BATCH_SIZE": "1000"}, ("--dry-run",),
{"batch_size": 1000, "dry_run": True}),
({"TASK_DRY_RUN": "1"}, ("--no-dry-run", "--batch-size", "7"),
{"batch_size": 7, "dry_run": False}),
]
for values, args, expected in valid:
result = run(values, args)
assert result.returncode == 0, result.stderr
payload = json.loads(result.stdout)
assert payload == expected
assert type(payload["dry_run"]) is bool
assert type(payload["batch_size"]) is int
assert result.stderr == ""
invalid = [
("TASK_DRY_RUN", ""), ("TASK_DRY_RUN", " "),
("TASK_DRY_RUN", "flase"), ("TASK_DRY_RUN", "yes"),
("TASK_BATCH_SIZE", ""), ("TASK_BATCH_SIZE", "0"),
("TASK_BATCH_SIZE", "1001"), ("TASK_BATCH_SIZE", "-1"),
("TASK_BATCH_SIZE", "2.5"), ("TASK_BATCH_SIZE", "1_000"),
("TASK_BATCH_SIZE", "12"), ("TASK_BATCH_SIZE", "9" * 5000),
]
for name, value in invalid:
result = run({name: value})
assert result.returncode == 2
assert result.stdout == ""
assert name in result.stderr
result = run({"TASK_DRY_RUN": "flase"}, ("--dry-run",))
assert result.returncode == 2 and result.stdout == ""
assert "TASK_DRY_RUN" in result.stderr
result = run({}, ("--batch-size", "0"))
assert result.returncode == 2 and result.stdout == ""
result = run({"TASK_DRY_RUN": "flase"}, ("--help",))
assert result.returncode == 0 and "--no-dry-run" in result.stdout
print("OK: defaults, booleans, boundaries, overrides, and errors")
Expected output:
OK: defaults, booleans, boundaries, overrides, and errors
The checks inspect status, actual JSON boolean types, stdout, and stderr. They cover empty values, misspellings, numeric boundaries, extremely long digits, explicit false flags, and the policy that overrides cannot conceal invalid environment settings. Help also remains available when the environment is invalid.
6. Troubleshooting
| Symptom | Cause and fix |
|---|---|
false still becomes true |
Replace bool(raw) or type=bool with conversion using an explicit set of accepted values. |
empty value |
The variable exists without content. Supply a valid value; for defaults, remove the corresponding variable with unset TASK_DRY_RUN or unset TASK_BATCH_SIZE. |
expected true, false, 1, or 0 |
Check spelling in the launcher. This tool does not accept alternatives such as yes or off. |
expected 1..1000 |
Remove signs, separators, and fractional parts; supply ASCII decimal digits within the range. |
| Changing shell variables does not update a running job | Set variables in the launcher and restart the process. Changes in another terminal do not update an existing process environment. |
A .env file has no effect |
This tool has no .env loader. Pass the two variables explicitly through the launcher. |
For more settings or nested structures, use the TOML task configuration tutorial instead of inventing a complex environment-variable delimiter format. If validation succeeds but Chinese log messages fail to print, check standard output encoding.
7. Summary
Distinguish unset, empty, and invalid strings at startup, then consistently convert booleans and integers to catch configuration problems early. Define precedence between environment variables and CLI arguments, and verify actual output and exit status in isolated subprocesses. Pass validated settings to the business logic before starting the real batch job.
- Author: yanzhi
- Original article: https://www.bobobk.com/en/python-environment-variable-validation.html
- License: This work is licensed under CC BY-NC-ND 4.0. See the license for attribution, noncommercial use and no-derivatives terms.
Related articles
- Create Reproducible gzip Files with Python: Control Headers and Verify Hashes
- Build URL Query Parameters in Python: Unicode, Plus Signs, and Repeated Keys
- Fix Duplicate Python Logs: Repeated Handlers and Logger Propagation
- Fix Python CSV Field Limits: Set a Character Cap and Test Boundaries
- Fix Python UnicodeEncodeError in Linux Jobs: Inspect stdout and Select UTF-8