Bem-vindo à aula 87! Nesta aula, vamos explorar a criação de aplicações de linha de comando (CLI) em Python. CLIs são ferramentas essenciais para automatizar tarefas, interagir com sistemas e fornecer interfaces poderosas para usuários avançados. Você aprenderá a usar a biblioteca padrão argparse e a biblioteca de terceiros click, além de entender a estrutura ideal de um projeto CLI e as melhores práticas para tornar seu código robusto e utilizável.

Vamos começar com uma visão geral do que é uma CLI e por que Python é uma excelente escolha para desenvolvê-las. Em seguida, mergulharemos em argparse, mostrando como criar argumentos, opções e subcomandos. Depois, apresentaremos click, que oferece uma abordagem mais elegante e produtiva. Finalmente, discutiremos a estrutura de um projeto e boas práticas, incluindo testes, documentação e empacotamento.

argparse

O módulo argparse é a ferramenta padrão do Python para criar interfaces de linha de comando. Ele permite definir argumentos posicionais, opções (flags) e subcomandos, além de gerar automaticamente mensagens de ajuda e validação de entrada. É uma escolha sólida para ferramentas que precisam de uma interface robusta sem dependências externas.

Vamos criar um exemplo simples: uma calculadora de linha de comando que soma ou multiplica dois números. Primeiro, importamos argparse, criamos um parser e adicionamos argumentos. O método parse_args() lê os argumentos da linha de comando e os converte em atributos do objeto retornado.

import argparse

parser = argparse.ArgumentParser(description='Calculadora simples.')
parser.add_argument('num1', type=float, help='primeiro número')
parser.add_argument('num2', type=float, help='segundo número')
parser.add_argument('-o', '--operation', choices=['add', 'mul'], default='add',
                    help='operação: add (soma) ou mul (multiplicação)')

args = parser.parse_args()

if args.operation == 'add':
    result = args.num1 + args.num2
else:
    result = args.num1 * args.num2

print(f'Resultado: {result}')

Execute o script com python calc.py 3 4 -o mul e veja o resultado. O argparse também oferece suporte a subcomandos, úteis para ferramentas com múltiplas operações, como um gerenciador de tarefas com comandos add, list, done. Para isso, usamos add_subparsers().

import argparse

parser = argparse.ArgumentParser(description='Gerenciador de tarefas')
subparsers = parser.add_subparsers(dest='command', help='comando disponível')

# Subcomando add
parser_add = subparsers.add_parser('add', help='adiciona uma tarefa')
parser_add.add_argument('task', help='descrição da tarefa')

# Subcomando list
parser_list = subparsers.add_parser('list', help='lista todas as tarefas')

args = parser.parse_args()

if args.command == 'add':
    print(f'Tarefa adicionada: {args.task}')
elif args.command == 'list':
    print('Lista de tarefas: ...')
else:
    parser.print_help()

O argparse é poderoso e flexível, mas pode se tornar verboso para CLIs complexas. Para projetos maiores, bibliotecas como click oferecem uma sintaxe mais concisa e decoradores que aceleram o desenvolvimento.

click (visão geral)

O click é uma biblioteca de terceiros que simplifica a criação de CLIs. Ele usa decoradores para definir comandos e opções, reduzindo a quantidade de código boilerplate. Além disso, integra-se bem com o ecossistema Python, como o Flask, e oferece recursos avançados como prompts interativos, cores e paginação.

Para instalar, use pip install click. O exemplo da calculadora fica assim:

import click

@click.command()
@click.argument('num1', type=float)
@click.argument('num2', type=float)
@click.option('-o', '--operation', type=click.Choice(['add', 'mul']), default='add',
              help='Operação: add ou mul')
def calc(num1, num2, operation):
    """Calculadora simples."""
    if operation == 'add':
        result = num1 + num2
    else:
        result = num1 * num2
    click.echo(f'Resultado: {result}')

if __name__ == '__main__':
    calc()

Observe como os argumentos e opções são definidos como decoradores e a função recebe os valores como parâmetros. O click também suporta grupos de comandos, permitindo criar CLIs com subcomandos de forma intuitiva:

import click

@click.group()
def cli():
    """Gerenciador de tarefas."""
    pass

@cli.command()
@click.argument('task')
def add(task):
    """Adiciona uma tarefa."""
    click.echo(f'Tarefa adicionada: {task}')

@cli.command()
def list():
    """Lista todas as tarefas."""
    click.echo('Lista de tarefas: ...')

if __name__ == '__main__':
    cli()

O click é amplamente adotado na comunidade e é a base de ferramentas como o Flask CLI. Sua curva de aprendizado é suave e a documentação é excelente.

Estrutura

Uma boa estrutura de projeto é fundamental para a manutenibilidade de uma CLI. Vamos considerar a organização de um projeto Python típico:

meu_projeto/
├── meu_projeto/
│   ├── __init__.py
│   ├── cli.py
│   └── core.py
├── tests/
│   └── test_cli.py
├── pyproject.toml
└── README.md

O diretório meu_projeto contém o código-fonte, com cli.py responsável pela interface de linha de comando e core.py com a lógica de negócio. Essa separação permite testar a lógica independentemente da interface. O arquivo pyproject.toml define os metadados e dependências, e README.md documenta o uso.

Para executar a CLI durante o desenvolvimento, você pode usar python -m meu_projeto.cli ou criar um entry point em pyproject.toml que instale o comando globalmente. Por exemplo, com setuptools:

[project]
name = "meu-projeto"
version = "0.1.0"

[project.scripts]
meu-cli = "meu_projeto.cli:main"

Assim, após pip install ., o comando meu-cli estará disponível no terminal. Essa estrutura é escalável e segue convenções da comunidade.

Boas práticas

Ao desenvolver CLIs, siga estas boas práticas para garantir qualidade e usabilidade:

  • Use nomes claros e consistentes para comandos, opções e argumentos.
  • Forneça mensagens de ajuda úteis descrevendo cada parâmetro.
  • Valide entradas e trate erros graciosamente, usando códigos de saída adequados.
  • Escreva testes para a lógica de negócio e, se possível, para a interface (usando CliRunner do click).
  • Mantenha a lógica de negócio separada da interface para facilitar a reutilização.
  • Documente o uso em um README ou página de ajuda.
  • Siga o princípio da menor surpresa: o comportamento deve ser intuitivo.

Além disso, considere usar logging em vez de print para mensagens de depuração, e ofereça a opção --verbose para aumentar a verbosidade. Essas práticas tornam sua ferramenta profissional e confiável.

Referências

Exercícios

  1. Crie uma CLI usando argparse que aceite um nome e uma idade, e imprima uma mensagem de boas-vindas personalizada. Valide que a idade é um número inteiro positivo.
  2. ✓ Resposta:
    import argparse
    
    parser = argparse.ArgumentParser(description='Mensagem de boas-vindas.')
    parser.add_argument('nome', help='nome do usuário')
    parser.add_argument('idade', type=int, help='idade do usuário')
    args = parser.parse_args()
    
    if args.idade < 0:
        parser.error('A idade deve ser um número positivo.')
    
    print(f'Olá, {args.nome}! Você tem {args.idade} anos.')
          
  3. Reescreva o exercício 1 usando click, com opções em vez de argumentos posicionais.
  4. ✓ Resposta:
    import click
    
    @click.command()
    @click.option('--nome', prompt='Seu nome', help='Nome do usuário')
    @click.option('--idade', type=int, prompt='Sua idade', help='Idade do usuário')
    def saudacao(nome, idade):
        """Mensagem de boas-vindas."""
        if idade < 0:
            click.echo('Idade inválida!', err=True)
            raise SystemExit(1)
        click.echo(f'Olá, {nome}! Você tem {idade} anos.')
    
    if __name__ == '__main__':
        saudacao()
          
  5. Implemente uma CLI com subcomandos add e list para um gerenciador de tarefas simples (pode usar uma lista em memória). Use argparse.
  6. ✓ Resposta:
    import argparse
    
    tarefas = []
    
    parser = argparse.ArgumentParser(description='Gerenciador de tarefas')
    subparsers = parser.add_subparsers(dest='comando', required=True)
    
    parser_add = subparsers.add_parser('add', help='adiciona tarefa')
    parser_add.add_argument('tarefa', help='descrição da tarefa')
    
    parser_list = subparsers.add_parser('list', help='lista tarefas')
    
    args = parser.parse_args()
    
    if args.comando == 'add':
        tarefas.append(args.tarefa)
        print(f'Tarefa adicionada: {args.tarefa}')
    elif args.comando == 'list':
        for i, tarefa in enumerate(tarefas, 1):
            print(f'{i}. {tarefa}')
          
  7. Usando click, crie um grupo de comandos com add e list para o mesmo gerenciador de tarefas.
  8. ✓ Resposta:
    import click
    
    tarefas = []
    
    @click.group()
    def cli():
        """Gerenciador de tarefas."""
        pass
    
    @cli.command()
    @click.argument('tarefa')
    def add(tarefa):
        """Adiciona uma tarefa."""
        tarefas.append(tarefa)
        click.echo(f'Tarefa adicionada: {tarefa}')
    
    @cli.command()
    def list():
        """Lista todas as tarefas."""
        for i, tarefa in enumerate(tarefas, 1):
            click.echo(f'{i}. {tarefa}')
    
    if __name__ == '__main__':
        cli()
          
  9. Estruture um projeto CLI conforme as boas práticas: crie um diretório com módulo principal e um arquivo de teste básico usando pytest para testar a lógica de negócio (por exemplo, uma função que valida dados).
  10. ✓ Resposta:

    Estrutura do projeto:

    meu_projeto/
    ├── meu_projeto/
    │   ├── __init__.py
    │   ├── core.py
    │   └── cli.py
    ├── tests/
    │   └── test_core.py
    └── pyproject.toml
          

    Em core.py:

    def validar_idade(idade):
        if idade < 0:
            raise ValueError('Idade deve ser positiva')
        return True
          

    Em tests/test_core.py:

    import pytest
    from meu_projeto.core import validar_idade
    
    def test_validar_idade_positiva():
        assert validar_idade(25) is True
    
    def test_validar_idade_negativa():
        with pytest.raises(ValueError):
            validar_idade(-1)
          

Observações finais

Nesta aula, você aprendeu a criar CLIs com Python usando argparse e click. A escolha entre eles depende do contexto: argparse é ideal para scripts simples e sem dependências, enquanto click oferece mais produtividade e recursos para projetos complexos. Lembre-se de sempre estruturar seu projeto de forma organizada e seguir boas práticas para garantir que sua ferramenta seja fácil de manter e usar. Pratique com os exercícios e explore mais a documentação para se aprofundar.