Scripting Basics
10 examples to get you started with System Scripting - 7 basic and 3 intermediate.
Search across all documentation pages
10 examples to get you started with System Scripting - 7 basic and 3 intermediate.
uv venv && source .venv/bin/activate
# stdlib only for most examples; typer optional:
uv pip install "typer>=0.15"Guard side effects so imports stay safe.
def main() -> int:
print("running")
return 0
if __name__ == "__main__":
raise SystemExit(main())main() returns int exit code for cron and CI.raise SystemExit avoids sys.exit in library-adjacent code.Related: Robust Scripts - exit codes and logging
Parse flags and positional args without extra dependencies.
import argparse
def main() -> int:
parser = argparse.ArgumentParser(description="Archive logs")
parser.add_argument("--dry-run", action="store_true")
parser.add_argument("path")
args = parser.parse_args()
print(args.path, args.dry_run)
return 0
if __name__ == "__main__":
raise SystemExit(main())action="store_true" for boolean flags.ArgumentParser.add_argument for complex rules.Related: File & Directory Automation - path handling
Object-oriented paths beat string concatenation.
from pathlib import Path
root = Path("data")
for csv in root.glob("**/*.csv"):
print(csv.resolve())/ operator joins path segments portably.glob("**/*.csv") recurses with ** (follow symlinks carefully)..read_text(encoding="utf-8") instead of open() when whole file fits in memory.Related: File & Directory Automation - safe writes
Structured logs survive cron email and log aggregators.
import logging
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s")
log = logging.getLogger("backup")
def main() -> int:
log.info("start backup")
return 0--verbose flag.Related: Robust Scripts - operational logging
Callers distinguish failure modes via exit status.
EXIT_OK = 0
EXIT_USAGE = 2
EXIT_FAIL = 1
def main() -> int:
return EXIT_OK--help or README for operators.2 commonly signals usage errors (argparse uses this).Related: Robust Scripts - failure handling
Read config from the environment for 12-factor scripts.
import os
API_URL = os.environ.get("API_URL", "https://api.example.com")
TOKEN = os.environ["API_TOKEN"] # required - raises KeyError if missingRelated: Working with APIs & Webhooks - authenticated calls
Delegate to external tools with explicit argument lists.
import subprocess
def run_git_status() -> int:
proc = subprocess.run(["git", "status", "--short"], check=False)
return proc.returncodeshell=True unless unavoidable.check=False lets you map return codes yourself.capture_output=True when parsing results.Related: subprocess & Shell Interop - capture and timeouts
Typed CLI with less boilerplate than raw argparse.
import typer
app = typer.Typer()
@app.command()
def greet(name: str, loud: bool = False) -> None:
msg = f"hello {name}"
typer.echo(msg.upper() if loud else msg)
if __name__ == "__main__":
app()uv run script.py in pyproject.toml scripts table.Related: Scheduling & Cron - scheduled typer CLIs
Merge defaults, file config, and CLI flags (last wins).
from dataclasses import dataclass
@dataclass
class Config:
retries: int = 3
dry_run: bool = False
def resolve_config(cli_retries: int | None, base: Config) -> Config:
if cli_retries is not None:
return Config(retries=cli_retries, dry_run=base.dry_run)
return baseConfig dataclasses simplify testing.Related: Robust Scripts - dry-run mode
Skip work when output already exists unless --force.
from pathlib import Path
def build_report(out: Path, force: bool) -> int:
if out.exists() and not force:
print(f"skip existing {out}")
return 0
out.write_text("report data\n", encoding="utf-8")
return 0--force documents intentional overwrite.Related: File & Directory Automation - atomic writes
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+.
Reviewed by Chris St. John·Last updated Jul 16, 2026