A small safe command-line interface — DedSec Project
Recipe: CLI contract with typed and required options Intended use and acceptance criteria A small notes tool accepts --category with exactly two valid values and --limit from 1 through 100…
Recipe: CLI contract with typed and required options
Intended use and acceptance criteria
A small notes tool accepts --category with exactly two valid values and --limit from 1 through 100. By parsing a supplied argument list, the same logic can be tested without consuming the host process's own command-line options. Missing required options, unsupported categories and out-of-range limits must produce argparse's normal exit code 2.
Complete runnable example
Save this as 03_CLI.py and execute with Python 3. The fixture is disposable and has no network dependency.
import argparse
def positive(text):
value = int(text)
if not 1 <= value <= 100:
raise argparse.ArgumentTypeError("limit must be 1..100")
return value
def parser():
p = argparse.ArgumentParser(prog="notes")
p.add_argument("--limit", type=positive, default=10)
p.add_argument("--category", choices=("code", "ideas"), required=True)
return p
def selection(argv):
args = parser().parse_args(argv)
return {"limit": args.limit, "category": args.category}
assert selection(["--category", "code"]) == {"limit":10,"category":"code"}
assert selection(["--category", "ideas","--limit","100"])["limit"] == 100
for bad in (["--category","nope"],["--category","code","--limit","0"],
["--limit","5"],["--category","code","--limit","101"]):
try: selection(bad)
except SystemExit as exc:
assert exc.code == 2
else: raise AssertionError("bad CLI accepted")
print("PASS bounded CLI parsing")
Guarantees and limitations
Define the parser in a function and separate argument validation from the action that reads files or queries a database. choices guards the category value and an argument type function imposes the numeric bound. In this deliberately bounded interface, invalid input raises SystemExit instead of returning silent defaults. A complete application can convert such errors into a UI message but must not replace invalid limits with arbitrary numbers.
Adaptation lab
Add --format with choices json and text, then test both defaults and wrong values. Run python -m compileall over the fixture and compare programmatic parse_args tests with actual terminal invocation. Add a value with Greek Unicode text only if your stated category contract permits it; do not silently widen fixed choices.
Troubleshooting and release check
If running inside Termux, check Python interpreter version and how the invoking shell quotes spaces. Always pass command arguments as a vector to subprocess, not a shell-built string. If a script exits 2, use the usage message to identify a user-input problem rather than treating it as a server outage.