argparse & configparser
argparse builds CLI interfaces with --flags, subcommands, and help text. configparser reads INI-style files for layered defaults - pair with environment variables for twelve-factor apps.
Recipe
import argparse
parser = argparse.ArgumentParser(description="Demo CLI")
parser.add_argument("--verbose", action="store_true")
parser.add_argument("name")
args = parser.parse_args(["Ada", "--verbose"])When to reach for this:
- Internal CLI tools and dev scripts
- Legacy INI config (
setup.cfg, logging.ini) - Typer/click not yet justified
- Subcommand-based tools (
git-style) - Default config file + CLI override pattern
Working Example
import argparse
import configparser
from pathlib import Path
def build_parser() -> argparse.ArgumentParser:
p = argparse.ArgumentParser()
p.add_argument("--config", type=Path, default=Path("app.ini"))
p.add_argument("--port", type=int)
return p
def load_config(path: Path) -> configparser.ConfigParser:
cfg = configparser.ConfigParser()
if path.exists():
cfg.read(path, encoding="utf-8")
return cfg
def resolve_port(args: argparse.Namespace, cfg: configparser.ConfigParser) -> int:
if args.port is not None:
return args.port
if cfg.has_option("server", "port"):
return cfg.getint("server", "port")
return 8000
args = build_parser().parse_args(["--port", "9000"])
cfg = load_config(args.config)
print(resolve_port(args, cfg))What this demonstrates:
- CLI overrides beat file config beat defaults
- configparser sections and typed getters (
getint) - Path type for argparse paths
- Encoding explicit on read for portability
Deep Dive
argparse Features
nargs,choices,metavaradd_subparsersfor subcommandsArgumentDefaultsHelpFormattershows defaults
configparser Notes
- Interpolation
%(section)soptional ConfigParserdoes not support full TOML - usetomllibfor pyproject
Gotchas
- Boolean flags wrong -
type=boolbroken. Fix:action="store_true". - Required optional confusion - document positional vs optional clearly.
- Config silently missing - empty defaults surprise. Fix: validate required sections after read.
- Secrets in INI - world-readable files. Fix: env vars for secrets, not committed config.
- argparse in library import - parse_args on import side effect. Fix: guard in
main().
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| typer/click | Rich CLI UX | Zero-dep script |
| tomllib | pyproject TOML | INI legacy only |
| pydantic-settings | Validated env config | Tiny script |
FAQs
typer vs argparse?
typer nicer DX; argparse stdlib zero dependency.
env override pattern?
os.environ.get after argparse - document precedence order.
subcommands?
subparsers = parser.add_subparsers(dest="command", required=True).
configparser write?
cfg.write(open(path,"w")) - preserve user comments not guaranteed on roundtrip edits.
--help auto?
argparse generates help - fill description and epilog.
nargs=+?
One or more positional tokens collected to list.
INI vs TOML?
New config prefer tomllib/pyproject; configparser for legacy INI only.
pytest cli?
Invoke main(argv) with explicit list - do not rely on sys.argv mutation globally.
completion?
argcomplete PyPI optional; typer has built-in shell completion paths.
Django management commands?
Django wraps argparse-like API - separate from standalone scripts.
Related
- subprocess - CLI tools spawn processes
- pathlib & os - config paths
- Standard Library Overview - map
- Standard Library Best Practices - CLI/config policy
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.