Skip to main content

Return useful exit codes from a Python command

2026-11-11 15:57:32.513737+00:00
Return useful exit codes from a Python command

A scheduler needs an exit status it can interpret. An empty input can be a successful run, while an unreadable input is a failure. Printing "no rows" for both cases makes monitoring unreliable.

Scheduler reading an exit-code decision table.

Define the command contract

This small command counts nonblank lines in a synthetic input file. Its documented statuses are zero for success, two for invalid command arguments and one for an operating-system or decoding failure.

import argparse
import sys
from pathlib import Path

def main(argv=None):
    parser = argparse.ArgumentParser()
    parser.add_argument("input", type=Path)
    args = parser.parse_args(argv)
    try:
        with args.input.open(encoding="utf-8") as handle:
            count = sum(1 for line in handle if line.strip())
    except (OSError, UnicodeError) as error:
        print(f"Cannot read input: {error}", file=sys.stderr)
        return 1

    print(count)
    return 0

if __name__ == "__main__":
    raise SystemExit(main())

Argument parsing owns usage errors. The function returns an integer for expected operational results, and the entry point converts that integer into a process status. Returning one from main() without SystemExit would leave the process status unchanged.

A scheduler can collect stdout as the result and stderr as the diagnostic. Avoid including sensitive file content in an error message. Some filesystem errors reveal a path, so choose log access and retention accordingly.

Test through a process boundary

Create an empty fixture and run the saved script using subprocess.run() with a list of arguments. Check returncode is zero and stdout contains "0". Run it against a missing path and check returncode is one with a nonempty stderr. Finally omit the input argument and check the parser's usage status.

The process-level test catches a forgotten SystemExit that a direct unit test of main() would miss. It also exercises paths containing spaces without requiring shell quoting.

Only catch failures the command can explain. A programming error should still produce a failing process and enough diagnostic information for an operator. A blanket "except Exception: return 0" would turn defects into reported success.

For a multi-step command, define whether partial completion counts as a distinct status and document it before callers depend on it. Changing exit codes later can break automation even when the printed text looks better.

Postzyo provides related scheduling context. The interfaces are documented in Python's argparse and subprocess references.