CLI Tools with argparse & Click
debt(d7/e3/b3/t5)
Closest to 'only careful code review or runtime testing' (d7). Pylint/mypy won't flag manual sys.argv parsing as a problem — it's a stylistic/UX issue spotted by review or when users complain about missing --help.
Closest to 'simple parameterised fix' (e3). Per quick_fix, replacing sys.argv parsing with argparse/Click is a localised rewrite of the entry point — not one line, but contained to the CLI bootstrap of one tool.
Closest to 'localised tax' (b3). applies_to is cli context only; the choice of parser affects the CLI entry layer but doesn't ripple through business logic.
Closest to 'notable trap' (t5). The misconception that sys.argv suffices is a common documented gotcha — devs eventually learn argparse handles types/help/validation, but the wrong instinct is widespread among beginners.
Also Known As
TL;DR
Explanation
argparse (stdlib): define arguments programmatically, automatic --help, type conversion, positional vs optional arguments. Click: decorator-based (@click.command, @click.option, @click.argument), composable subcommand groups, automatic prompting, password hiding, file handling, and better error messages. Typer: Click wrapper with type hints — Python type annotations define CLI arguments. Use argparse for simple scripts, Click/Typer for complex tools distributed as packages. Both generate --help automatically from function docstrings and argument definitions.
Common Misconception
Why It Matters
Common Mistakes
- Parsing sys.argv manually instead of using argparse/Click.
- No input validation — argparse type= parameter handles this automatically.
- Missing default values — always provide defaults or mark arguments as required explicitly.
- No subcommands for multi-action tools — Click's @click.group() organises complex CLIs cleanly.
Code Examples
# Manual sys.argv parsing — fragile:
import sys
if len(sys.argv) < 3:
print('Usage: tool.py input output')
sys.exit(1)
input_file = sys.argv[1]
output_file = sys.argv[2]
# No type checking, no --help, no --verbose flag support
import click
@click.group()
@click.option('--verbose', '-v', is_flag=True, help='Enable verbose output')
@click.pass_context
def cli(ctx, verbose):
ctx.ensure_object(dict)
ctx.obj['VERBOSE'] = verbose
@cli.command()
@click.argument('input', type=click.Path(exists=True))
@click.argument('output', type=click.Path())
@click.option('--format', type=click.Choice(['json', 'csv']), default='json')
@click.pass_context
def process(ctx, input, output, format):
if ctx.obj['VERBOSE']: click.echo(f'Processing {input} -> {output}')
# Auto --help generated from docstring
if __name__ == '__main__': cli()