Build a Small CLI with argparse
Define a repeatable argument parser for a report command and validate its parsed options.
Example check: Worked and guided code samples are checked against CPython 3.13. Practice uses Pyodide 0.29.5 (Python 3.13) in your browser. How we check lessons
Video lesson: Build a Small CLI with argparse
The preview is stored on this site. YouTube loads only when you press Play. Watch on YouTube
Jump to a chapter
Read the video transcript
Select a timestamp to watch that moment on YouTube.
- 00:00
A useful command-line tool needs to understand options, defaults, and mistakes. A user may ask for JSON output and a limit of five rows. The shell passes those tokens as strings; it does not give our Python function a ready-made integer or validated format. We could manually scan a list and convert values, but that quickly becomes fragile when options arrive in a different order or someone misspells one. Python's standard-library argparse gives us a parser that describes the command's grammar. In this lesson, we will run a deliberately unsupported format, read the error, build a small report parser, then test defaults, conversions, and a positive-number rule.
- 00:44
The browser console cannot rely on a local shell's arguments, so its practical task passes an explicit list like `['--format', 'json', '--limit', '5']`. A real script can use the same parser with process arguments at its entry point. We keep parsing separate from any file or network work so the examples run the same way on every machine. Think of the parser as the boundary between untrusted text supplied by a person and the typed values the rest of the report program is allowed to use. The parser refuses xml because we declared only text and json. In a normal terminal, argparse would also print a usage line and an invalid-choice explanation, then exit with status two.
- 01:31
This demonstration catches the exit and redirects its usage text only to make the output stable for our recording. The important behavior remains: a wrong option is an error. Quietly changing xml to text would mislead the user into thinking their request was honored. Choices handle this kind of syntax-level restriction. We also want an integer limit, so we can declare `type=int`; argparse will reject a token like “many.” But it will happily parse zero or negative two, because both are valid integers. The application still needs its own rule that a report limit must be positive.
- 02:11
Keep those layers separate: argparse converts and checks the command grammar, then our function validates the domain meaning. This separation makes error handling and testing much clearer. Nothing has crashed because zero is a perfectly valid integer token. `type=int` answered only the conversion question: can these characters become an int? It did not know that a zero-row report is meaningless for our example. We need a second check after parsing. A reusable `parse_report_args(argv)` function can raise ValueError with a specific message when limit is below one. A full command-line entry point might call `parser.error` instead, which displays usage and exits; the reusable core can let the caller choose how to present the failure.
- 03:00
We should also decide whether unknown flags are accepted. Here they are not. `parse_args` rejects them, catching typos like `--limt`. `parse_known_args` has a real use when forwarding leftovers to another program, but silently discarding unknown tokens in this tool would hide mistakes. Finally, the browser task must pass an explicit argv list. Calling `parse_args()` with no argument would read whatever arguments launched the Python process, which is unrelated to the sample command and makes tests unreliable. The parser creates named options from the supplied tokens. The format choice is limited to text or json, with text as the default.
- 03:43
The limit token is converted to int and defaults to ten. We then apply the positive-number rule and return a small tuple that the report-writing code can use. The first call gives an explicit format and limit; the second shows both defaults. Each call builds its own parser here for clarity, although a larger application could construct the parser once and reuse it. The order of named options is not important: `--limit 5 --format json` yields the same result. Keep help enabled in a normal command-line program so a user can request `--help`; add_help=False only keeps this exercise's output focused.
- 04:24
Also think about where errors are displayed. argparse's own errors usually print to stderr and exit; our added ValueError should be turned into a clear user-facing message by the entry point, not swallowed and replaced with a mysterious empty report. The assertions cover defaults, a different option order, an invalid positive range, and a format outside choices. They test the parser function without writing a report file or reading any shell-specific state. That makes the exercise repeatable in the browser and locally. If a hidden check fails, inspect which layer owns the rule. Argparse already converts a supplied integer and rejects an unknown format.
- 05:08
Your code must still reject zero and negatives and return the parsed values rather than placeholders. A full CLI needs more than this core: help text, a useful exit message, actual report generation, and tests for its output. We have deliberately isolated the part that turns tokens into trusted options. The same approach works for batch utilities and maintenance scripts: describe the command grammar explicitly, validate domain ranges after parsing, and keep command-line input separate from the work the command performs. Try the solved examples on the matching PythonLessonLab page, then complete the checked function and quiz before extending it with another option of your own.
- 05:52
A useful next test is a misspelled flag such as `--limt`; let the parser reject it so the user can correct the command rather than wondering why the report ignored their request.
Understand the concept
A command-line interface turns text tokens into structured options. Python's argparse creates help text, supports defaults, converts types, and rejects choices that are not allowed. A script usually calls parser.parse_args() to read its process arguments. In the browser console, there is no meaningful user shell command line, so this lesson passes an explicit list such as ['--format', 'json', '--limit', '5']. The same parser works in a local script when given sys.argv[1:].
Define each option once: --format may choose text or json, while --limit converts to int and defaults to ten. argparse catches a non-integer like 'many' at parsing time. Type conversion alone does not enforce a positive number; zero is still an int. For this reusable function, raise ValueError after parsing if limit is below one. A production CLI can instead call parser.error to show usage and exit with a nonzero status.
The parser is configured with add_help=False in the exercise so the sample stays focused on the two options and deterministic in a browser. A normal CLI should keep help enabled and use a descriptive prog value. parse_args rejects unknown arguments rather than silently discarding them. If you need to pass through unknown options to another program, that is a different contract and parse_known_args may be appropriate. Do not silently accept a misspelled option.
The checked task implements parse_report_args(argv). It returns a two-item tuple (format, limit) and never reads the browser process's arguments. Hidden checks cover defaults, reversed option order, invalid formats, and nonpositive limits. Separate parsing from the report-writing function so tests can reason about options without touching files or network services.
- ArgumentParser defines a command-line grammar
- Explicit argv lists make parsing deterministic in a browser
- type conversion does not replace domain validation
- choices and unknown-argument errors catch input mistakes
See it step by step
Read the code, predict the output, then compare it with the result.
01. Parse an explicit list
import argparse
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument("--name", default="world")
options = parser.parse_args(["--name", "Ada"])
print(options.name)AdaExplicit argv makes the example independent of the host process's arguments.
02. Convert an integer and use a default
import argparse
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument("--count", type=int, default=2)
print(parser.parse_args([]).count)
print(parser.parse_args(["--count", "4"]).count)2
4The default applies when the flag is absent; type=int converts the supplied string.
03. Validate a domain rule after parsing
import argparse
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument("--limit", type=int, default=10)
value = parser.parse_args(["--limit", "0"]).limit
try:
if value < 1:
raise ValueError("limit must be positive")
except ValueError as error:
print(error)limit must be positiveZero parses as an integer but violates the report command's rule.
A closer look
Follow the reasoning, inspect each result, then try the suggested changes in the console below.
Turn a list of tokens into named options
A command line is a sequence of strings, not a dictionary of already typed values. argparse supplies a grammar for those strings and returns an object with named fields. In a browser lesson, pass a literal list to parse_args so the example does not depend on the process that launched the Python runtime.
The first parser gives --format a default and a restricted set of choices. A missing flag becomes text; an explicit json becomes json. A real script can call parse_args without an argument to read its process command line, but this exercise keeps input visible and repeatable. That makes both explanations and tests much easier to verify.
import argparse
parser = argparse.ArgumentParser(prog="report", add_help=False)
parser.add_argument("--format", choices=("text", "json"), default="text")
print(parser.parse_args([]).format)
print(parser.parse_args(["--format", "json"]).format)text
json- Read the default when no flag is supplied.
- Pass an explicit option list and observe the parsed attribute.
Separate type conversion from a positive-number rule
Adding `type=int` turns a token like '5' into an integer. It also accepts zero and negative numbers because those are valid integers. The report command, however, needs a positive item limit. Put that domain check immediately after parsing and raise a clear ValueError when the value is below one.
Keep the parser and validation in a function that receives argv. It can be called repeatedly with independent lists, so a test can cover defaults, a supplied value, and an invalid boundary without touching the shell. A normal command-line entry point might convert the ValueError into parser.error for a usage message. This reusable core leaves that presentation decision to the caller.
import argparse
def parse_report_args(argv):
parser = argparse.ArgumentParser(prog="report", add_help=False)
parser.add_argument("--format", choices=("text", "json"), default="text")
parser.add_argument("--limit", type=int, default=10)
options = parser.parse_args(argv)
if options.limit < 1:
raise ValueError("limit must be positive")
return options.format, options.limit
print(parse_report_args(["--limit", "5", "--format", "json"]))
try:
parse_report_args(["--limit", "0"])
except ValueError as error:
print(error)('json', 5)
limit must be positive- Notice that option order does not change the parsed result.
- Confirm zero parses as int but fails the separate application rule.
Treat unsupported options as errors
choices helps catch a mistyped format before report generation starts. parse_args also rejects unknown flag names. Do not quietly switch to parse_known_args and discard the leftovers unless the command intentionally forwards arguments to another tool. Silent acceptance can make a user believe a flag took effect when it did not.
The local demonstration suppresses argparse's usage text only so the expected output remains stable; it still catches SystemExit and prints the exception type. In a real CLI, keep help enabled and let argparse display usage. Tests should focus on supported options, documented defaults, and a few representative failure cases rather than asserting every character of a platform-specific error message.
import argparse
import contextlib
import io
parser = argparse.ArgumentParser(prog="report", add_help=False)
parser.add_argument("--format", choices=("text", "json"), default="text")
with contextlib.redirect_stderr(io.StringIO()):
try:
parser.parse_args(["--format", "xml"])
except SystemExit as error:
print(type(error).__name__, error.code)
print(parser.parse_args(["--format", "json"]).format)SystemExit 2
json- See an unsupported format fail instead of silently falling back.
- Run a supported value afterward to confirm the parser is reusable.
Try it in Python
Edit the example and run it. Python starts in your browser the first time you click Run.
Python console
Ready to runNeed input()? Add one value per line
Your output appears here.
Need a hint?
Use `if options.limit < 1: raise ValueError('limit must be positive')`, then `return (options.format, options.limit)`. argparse already handles the listed choices and integer conversion.
Complete the task and select Check task to verify your code.
Quick quiz
Three questions. You can change your answers and try again.
Typical mistakes
Everyone meets these errors. See what causes them and how to fix them.
Reading ambient argv in a browser task
options = parser.parse_args()options = parser.parse_args(argv)What happens: The result depends on the process running the example, not the supplied exercise input.
Inject tokens for deterministic practice and testing.
Assuming integer conversion enforces positivity
parser.add_argument('--limit', type=int)
# assume 0 is rejectedif options.limit < 1: raise ValueError('limit must be positive')What happens: Zero and negative integers parse successfully.
Validate business rules after syntax conversion.
Ignoring unknown options
options, extra = parser.parse_known_args(argv)
# silently discard extraoptions = parser.parse_args(argv)What happens: A misspelled flag can disappear without warning.
For this command, unknown arguments should be errors.