Bobobk

Practical Python, Linux and automation guides

Validate Python Environment Variables: Booleans, Empty Values, and Integers

2026-10-25 Technology
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
  1. Only an unset variable receives its default. Empty and whitespace-only values fail, so a deployment omission cannot silently disappear.
  2. Validate both environment variables before applying CLI overrides. Invalid environment settings still fail even when an override is supplied.
  3. 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.

  1. Create a demo directory:
python3 --version
mkdir env-settings-demo
cd env-settings-demo
  1. 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

  1. 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.

  1. 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}
  1. 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}
  1. 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.

Tags

1024 2fa ablang adsense agents.md ai ai-agent ai-agents ai-seo algorithm amp antibodies apparmor argparse automation background backup bash batch-processing binarycif bioinformatics biopython blockchain boltz bootstrapping boxes bubblewrap c-index cca cdn checkpoint cli cloudflare codex cofoldarena concurrency configparser configuration copy cprofile cpu monitoring cron csv cuda curl data-leakage data-processing data-quality data-validation datascience datavisualization datetime debugging decimal deployment desktop-app devtools difflib disk-space disown dns docker dovecot download economics electron encoding esm esm2 esm3 esmc esmfold2 etag evaluation fable faceswap fasta fastmcp faulthandler ffmpeg file-descriptors file-io file-management filecmp filenames files flashppi flask folium frontend game generator git github-actions google google-research gpt-6.1-sol grep gzip harness heapq hls html http hugo imports indexnow ipaddress jailbreak javascript jev json jsonl just k-means kaggle langfuse leecode linux list litellm llm llms.txt logging logs lollipop lsof m3u8 machine-learning macos manacher matplotlib mcp memory mirror mmcif model-evaluation monitoring mp3 mp4 mpnn multiomics multiprocessing mutation mysql nanobert nanobodies networking networkx nginx normalize numpy ollama omegatherm pandas password pathlib pdb pep-723 performance permissions phaser pillow pip postfix preprocessing print probability prompt-engineering protein-alignment protein-design protein-embeddings protein-interactions protein-language-models protein-stability protein-structure proxy pydantic pyecharts pyqt python python3 r raincloud reproducibility requests reservoir-sampling rfoptimization rg ripgrep rosettafold3 roundcube rrsi rsi rsync s-tui sampling scale scikit-learn scrapy screen seaborn security seo sequence-identity sha256 shell signals sitemap sklearn socket solana somaticsignatures sorting spl sql sqlite ssh standardize static-site subprocess symlink sysadmin system-one tarfile tcp tempfile tensorflow tkinter tls toml totp tracemalloc trade tron tronpy troubleshooting turtle typesafe-ai ubuntu unicode upsert urllib usdt utf-8 uv venv vhhbert vite wallet web-tools webp wordcloud wordpress workflow xml yaml zipfile zoneinfo 迅雷解析

友情链接