Criando uma CLI profissional com argparse

1. Introdução ao argparse: por que escolhê-lo?

Ao construir uma aplicação de linha de comando em Python, o desenvolvedor se depara com várias opções: Click, Typer, Fire e o nativo argparse. Embora Click e Typer ofereçam sintaxe mais declarativa e recursos como decoradores, o argparse é a escolha ideal quando você precisa de uma solução sem dependências externas, total controle sobre o parsing e compatibilidade garantida com qualquer ambiente Python.

O argparse faz parte da biblioteca padrão desde Python 2.7/3.2, o que significa que sua CLI funcionará em qualquer instalação Python sem necessidade de pip install. Ele é particularmente recomendado para ferramentas de sistema, scripts de automação e projetos que serão distribuídos como pacotes.

Estrutura básica

import argparse

parser = argparse.ArgumentParser(description='Uma CLI de exemplo')
parser.add_argument('nome', help='Seu nome')
args = parser.parse_args()

print(f'Olá, {args.nome}!')

Salve como saudacao.py e execute:

$ python saudacao.py Maria
Olá, Maria!

Já temos uma CLI funcional com ajuda automática:

$ python saudacao.py --help
usage: saudacao.py [-h] nome

Uma CLI de exemplo

positional arguments:
  nome        Seu nome

options:
  -h, --help  show this help message and exit

2. Definindo argumentos posicionais e opcionais

No argparse, argumentos posicionais são obrigatórios por padrão e seguem a ordem em que são declarados. Argumentos opcionais usam flags como -v ou --verbose e podem ter valores padrão.

import argparse

parser = argparse.ArgumentParser(description='Ferramenta de processamento')
parser.add_argument('arquivo', help='Arquivo de entrada')
parser.add_argument('--saida', '-o', default='resultado.txt',
                    help='Arquivo de saída (padrão: resultado.txt)')
parser.add_argument('--verbose', '-v', action='store_true',
                    help='Modo verboso')

args = parser.parse_args()
print(f'Processando {args.arquivo}...')
if args.verbose:
    print(f'Saída definida para: {args.saida}')

Boas práticas de nomenclatura

  • Use metavar para exibir nomes descritivos na ajuda:
parser.add_argument('--limite', '-l', type=int, metavar='NUM',
                    help='Número máximo de itens (padrão: %(default)s)')

3. Tipos, validação e conversão automática

O argparse converte automaticamente strings para os tipos especificados:

parser.add_argument('--idade', type=int, help='Sua idade')
parser.add_argument('--peso', type=float, help='Seu peso em kg')
parser.add_argument('--arquivo', type=open, help='Arquivo para leitura')

Validação personalizada

def validar_email(valor):
    if '@' not in valor:
        raise argparse.ArgumentTypeError(f'Email inválido: {valor}')
    return valor

parser.add_argument('--email', type=validar_email, required=True,
                    help='Endereço de email válido')

Restringindo valores com choices e múltiplos argumentos com nargs

parser.add_argument('--modo', choices=['rápido', 'completo', 'teste'],
                    default='rápido', help='Modo de execução')
parser.add_argument('--arquivos', nargs='+', help='Lista de arquivos')

Uso:

$ python script.py --modo completo --arquivos a.txt b.txt c.txt

4. Subcomandos: organizando funcionalidades complexas

Para CLIs com múltiplas operações (como git commit, git push), usamos subparsers:

import argparse

def cmd_add(args):
    print(f'Adicionando tarefa: {args.tarefa}')

def cmd_list(args):
    print('Listando tarefas...')

def cmd_done(args):
    print(f'Marcando tarefa {args.id} como concluída')

parser = argparse.ArgumentParser(description='Gerenciador de tarefas')
parser.add_argument('--verbose', '-v', action='store_true',
                    help='Modo verboso')

subparsers = parser.add_subparsers(dest='comando', title='Comandos',
                                   description='Comandos disponíveis')

# Subcomando "add"
parser_add = subparsers.add_parser('add', help='Adicionar nova tarefa')
parser_add.add_argument('tarefa', help='Descrição da tarefa')
parser_add.set_defaults(func=cmd_add)

# Subcomando "list"
parser_list = subparsers.add_parser('list', help='Listar tarefas')
parser_list.set_defaults(func=cmd_list)

# Subcomando "done"
parser_done = subparsers.add_parser('done', help='Concluir tarefa')
parser_done.add_argument('id', type=int, help='ID da tarefa')
parser_done.set_defaults(func=cmd_done)

args = parser.parse_args()
if hasattr(args, 'func'):
    args.func(args)
else:
    parser.print_help()

Uso:

$ python todo.py add "Estudar argparse"
$ python todo.py list
$ python todo.py done 1

Os subcomandos herdam automaticamente o argumento --verbose definido no parser principal.

5. Melhorando a experiência do usuário

Personalização da ajuda

from argparse import RawDescriptionHelpFormatter

parser = argparse.ArgumentParser(
    description='Ferramenta de backup automático',
    epilog='Exemplo: backup.py --origem /home --destino /mnt/backup',
    formatter_class=RawDescriptionHelpFormatter
)

Integrando com Rich para cores

from rich.console import Console
from rich.table import Table

console = Console()

def exibir_tabela(args):
    tabela = Table(title="Configuração")
    tabela.add_column("Parâmetro", style="cyan")
    tabela.add_column("Valor", style="green")
    tabela.add_row("Origem", args.origem)
    tabela.add_row("Destino", args.destino)
    console.print(tabela)

Tratamento de erros amigável

class CLIErrorParser(argparse.ArgumentParser):
    def error(self, message):
        self.print_usage()
        print(f'\033[91mErro: {message}\033[0m')
        self.exit(2)

parser = CLIErrorParser(description='CLI com erros coloridos')

6. Padrões avançados

Grupos mutuamente exclusivos

grupo = parser.add_mutually_exclusive_group(required=True)
grupo.add_argument('--compacto', action='store_true', help='Formato compacto')
grupo.add_argument('--detalhado', action='store_true', help='Formato detalhado')

Ações customizadas

class AçãoContador(argparse.Action):
    def __call__(self, parser, namespace, values, option_string=None):
        setattr(namespace, self.dest, values + 1)

parser.add_argument('--debug', action=AçãoContador, nargs=0,
                    help='Nível de debug (use múltiplas vezes)')

Argumentos com contagem

parser.add_argument('-v', '--verbose', action='count', default=0,
                    help='Aumentar verbosidade')

Uso: -v = nível 1, -vv = nível 2, -vvv = nível 3.

7. Testando e depurando sua CLI

Simulação de argumentos

# Em vez de parse_args() sem argumentos, use uma lista
args = parser.parse_args(['--verbose', '--modo', 'rápido', 'arquivo.txt'])
assert args.verbose == True
assert args.modo == 'rápido'

Testes com pytest

import pytest

def test_parser_modo_padrao():
    parser = criar_parser()
    args = parser.parse_args(['arquivo.txt'])
    assert args.modo == 'rápido'

def test_parser_modo_completo():
    parser = criar_parser()
    args = parser.parse_args(['--modo', 'completo', 'arquivo.txt'])
    assert args.modo == 'completo'

def test_parser_modo_invalido():
    parser = criar_parser()
    with pytest.raises(SystemExit):
        parser.parse_args(['--modo', 'invalido', 'arquivo.txt'])

Logging com verbose

import logging

logging.basicConfig(level=logging.WARNING)
if args.verbose >= 1:
    logging.getLogger().setLevel(logging.INFO)
if args.verbose >= 2:
    logging.getLogger().setLevel(logging.DEBUG)

logging.debug('Iniciando processamento...')
logging.info(f'Arquivo: {args.arquivo}')

8. Empacotamento e distribuição profissional

Configuração com pyproject.toml

[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.backends._legacy:_Backend"

[project]
name = "minha-cli"
version = "1.0.0"
description = "Uma CLI profissional"

[project.scripts]
minha-cli = "minha_cli.main:main"

Alternativa com setup.py

from setuptools import setup, find_packages

setup(
    name='minha-cli',
    version='1.0.0',
    packages=find_packages(),
    entry_points={
        'console_scripts': [
            'minha-cli=minha_cli.main:main',
        ],
    },
)

Após instalar com pip install -e ., o comando minha-cli estará disponível globalmente.

Checklist final para produção

  • [ ] Documentação completa com --help descritivo
  • [ ] Tratamento de edge cases (arquivos inexistentes, permissões)
  • [ ] Compatibilidade Python 3.8+
  • [ ] Testes unitários para todos os subcomandos
  • [ ] Mensagens de erro claras e acionáveis
  • [ ] Suporte a variáveis de ambiente para configuração
  • [ ] Logging configurável via --verbose

O argparse oferece um equilíbrio perfeito entre simplicidade e poder. Embora bibliotecas como Click e Typer sejam excelentes para projetos maiores, o argparse é a escolha certa quando você precisa de uma CLI robusta, sem dependências e com controle total. Comece pequeno, adicione subcomandos conforme necessário e mantenha a experiência do usuário sempre em mente.

Referências