Typorithm · easy

Small command-line interfaces with argparse

pythonapi-clientsautomationindependent-practice
Saved on this device
Full lesson · 4 learning steps Open any exercise for focused practice
1

Step 1 of 4 · intro card

Small command-line interfaces with argparse

0%

Concept and contract

Parse predictable flags without hiding unknown arguments or terminating the calling process on validation errors.

Command-line arguments form another text boundary. argparse separates flag spelling, conversion and a readable usage model, but its normal error behavior is designed for a terminal program.

A reusable parser may need an exception instead of terminating the whole process. The small parser subclass makes that policy explicit. Help handling is intentionally disabled in this exercise so the helper has one bounded responsibility.

Mechanism and worked example

The worked parser uppercases the country before applying choices and converts the limit through int before checking its range.

The worked parser uppercases the country before applying choices and converts the limit through int before checking its range. parse_args receives the supplied list, not ambient sys.argv.

An unknown flag is rejected because silently ignoring a misspelling could start the client with unintended defaults. The top-level command can translate ValueError into a user message and exit status after the reusable helper returns or fails. Unambiguous flag abbreviations are disabled so only the declared option spellings are accepted.

Complete worked example

python

import argparse

class InputParser(argparse.ArgumentParser):
    def error(self, message):
        raise ValueError(message)

def options(arguments):
    parser = InputParser(add_help=False, allow_abbrev=False)
    parser.add_argument("--country", type=str.upper, choices=["US", "IL", "UA", "CA"], default="US")
    parser.add_argument("--limit", type=int, default=2)
    result = parser.parse_args(arguments)
    if not 1 <= result.limit <= 5:
        raise ValueError("limit must be 1..5")
    return {"country": result.country, "limit": result.limit}
Reasoning
Parse predictable flags without hiding unknown arguments or terminating the calling process on validation errors. Compare the stated values and failure boundary before changing the implementation.

Counterexamples and common mistakes

The counterexample uses parse_known_args and discards the unknown arguments.

The counterexample uses parse_known_args and discards the unknown arguments. A command containing a misspelled option then appears to succeed while using a default value.

Do not assume exit_on_error=False removes every possible SystemExit path across argparse features and versions. Define the error policy you actually need. Also avoid type=bool for flags: a nonempty string such as false still has true truthiness.

Counterexample: a runnable policy defect

python

import argparse

class InputParser(argparse.ArgumentParser):
    def error(self, message):
        raise ValueError(message)

def options(arguments):
    parser = InputParser(add_help=False, allow_abbrev=False)
    parser.add_argument("--country", type=str.upper, choices=["US", "IL", "UA", "CA"], default="US")
    parser.add_argument("--limit", type=int, default=2)
    result = parser.parse_known_args(arguments)[0]
    if not 1 <= result.limit <= 5:
        raise ValueError("limit must be 1..5")
    return {"country": result.country, "limit": result.limit}
Reasoning
This runnable counterexample violates the declared contract; the companion lab requires it to fail.

Independent practice: API client task

Add an optional --archive flag with action=store_true and include its boolean value in the result.

Add an optional --archive flag with action=store_true and include its boolean value in the result. Begin with no arguments, then supply the flag, then combine it with a country.

Keep unknown flags and invalid limits rejected. The task adds one common CLI feature without introducing subcommands, shell completion or project installation. The optional stretch can explore those separately after this smaller contract works.

Step 1: Add a --archive boolean presence flag. Step 2: Include archive in the returned mapping and keep existing validation.

Practice starter: solve before reading the solution

python

import argparse

class InputParser(argparse.ArgumentParser):
    def error(self, message):
        raise ValueError(message)

def options(arguments):
    # Remove this placeholder, then adapt the retained baseline:
    # Add a --archive boolean presence flag.
    raise NotImplementedError
    parser = InputParser(add_help=False, allow_abbrev=False)
    parser.add_argument("--country", type=str.upper, choices=["US", "IL", "UA", "CA"], default="US")
    parser.add_argument("--limit", type=int, default=2)
    result = parser.parse_args(arguments)
    if not 1 <= result.limit <= 5:
        raise ValueError("limit must be 1..5")
    return {"country": result.country, "limit": result.limit}
Reasoning
Add a --archive boolean presence flag. Include archive in the returned mapping and keep existing validation.
  • Hint 1: Use action="store_true", not type=bool.
  • Hint 2: Test both omitted and supplied flags plus an unknown spelling.

Solution, tests and trade-offs

The practice cases distinguish the presence of --archive from a textual value after it.

The practice cases distinguish the presence of --archive from a textual value after it. They also preserve the original arguments list and retain errors for invalid country choices and limits.

The reference suite checks that the deliberately permissive parser fails its unknown-flag case. A successful parse proves this helper contract; testing an installed console entry point would additionally require its real executable and exit-status behavior.

Practice solution and acceptance checks

python

import argparse

class InputParser(argparse.ArgumentParser):
    def error(self, message):
        raise ValueError(message)

def options(arguments):
    parser = InputParser(add_help=False, allow_abbrev=False)
    parser.add_argument("--country", type=str.upper, choices=["US", "IL", "UA", "CA"], default="US")
    parser.add_argument("--limit", type=int, default=2)
    parser.add_argument("--archive", action="store_true")
    result = parser.parse_args(arguments)
    if not 1 <= result.limit <= 5:
        raise ValueError("limit must be 1..5")
    return {"country": result.country, "limit": result.limit, "archive": result.archive}
Reasoning
Use action="store_true", not type=bool. Test both omitted and supplied flags plus an unknown spelling.

Interview reasoning and next step

In an interview, explain why argument parsing should happen near the program entry point.

In an interview, explain why argument parsing should happen near the program entry point. Keep the application operation callable without constructing a shell command or modifying sys.argv.

A production main can enable normal help, choose a consistent error stream and avoid printing secrets. The next lessons keep the same explicit-input approach for time, money and paths, where convenient conversions can otherwise conceal important assumptions.

Optional stretch — after the core task works: Design a main wrapper that maps ValueError to a nonzero exit status.

Computer practice: https://applydjinn.com/typorithm/python-automation-lab-20261105.zip. Run verify.py using your selected Python interpreter, then adapt the retained exercise baseline and check it with --practice followed by the lesson id. The standard-library lab uses owned temporary 127.0.0.1 loopback servers, fictional pages and disposable files; it never scans production feeds or opens the working ApplyDjinn database.

Parse predictable flags without hiding unknown arguments or terminating the calling process on validation errors.