Click: CLIs modernas e composíveis
1. Introdução ao Click e seus diferenciais
Se você já precisou criar uma interface de linha de comando (CLI) em Python, provavelmente conhece o argparse da biblioteca padrão. Embora funcional, o argparse frequentemente resulta em código verboso, difícil de manter e com pouca flexibilidade para composição. É aí que entra o Click.
Click (Command Line Interface Creation Kit) é um framework criado por Armin Ronacher (mesmo criador do Flask) que revoluciona a construção de CLIs. Sua filosofia é baseada em três pilares: composição (comandos podem ser combinados livremente), decoração (uso intensivo de decoradores Python) e simplicidade (menos boilerplate).
Para instalar:
pip install click
Um exemplo mínimo:
import click
@click.command()
def hello():
"""Comando simples que cumprimenta o usuário."""
click.echo("Olá, mundo!")
if __name__ == "__main__":
hello()
2. Definindo parâmetros de linha de comando
Click oferece duas formas principais de receber dados do usuário: opções e argumentos.
Opções com @click.option()
@click.command()
@click.option("--nome", default="Mundo", help="Nome para cumprimentar")
@click.option("--idade", type=int, prompt="Sua idade")
@click.option("--ativo/--inativo", default=True, help="Status do usuário")
def saudacao(nome, idade, ativo):
click.echo(f"Olá {nome}, você tem {idade} anos")
if ativo:
click.echo("Status: ativo")
Argumentos com @click.argument()
@click.command()
@click.argument("arquivo", type=click.Path(exists=True))
@click.argument("linhas", type=int, default=10)
@click.argument("palavras", nargs=-1) # Múltiplos valores
def ler_arquivo(arquivo, linhas, palavras):
"""Lê um arquivo e busca palavras específicas."""
with open(arquivo) as f:
conteudo = f.readlines()[:linhas]
for palavra in palavras:
click.echo(f"Buscando: {palavra}")
3. Agrupamento e composição de comandos
Um dos recursos mais poderosos do Click é a capacidade de criar grupos de comandos.
@click.group()
def cli():
"""Ferramenta de gerenciamento de projetos."""
pass
@cli.command()
@click.argument("nome")
def init(nome):
"""Inicializa um novo projeto."""
click.echo(f"Projeto {nome} criado!")
@cli.command()
@click.argument("nome")
def build(nome):
"""Compila o projeto."""
click.echo(f"Compilando {nome}...")
@cli.command()
@click.argument("nome")
@click.option("--force", is_flag=True)
def deploy(nome, force):
"""Faz deploy do projeto."""
if force:
click.echo("Forçando deploy...")
click.echo(f"Deploy de {nome} realizado!")
if __name__ == "__main__":
cli()
Para usar:
python app.py init meu-projeto
python app.py build meu-projeto
python app.py deploy meu-projeto --force
4. Validação e conversão de tipos
Click oferece tipos nativos e personalizados para validação robusta.
def validar_email(ctx, param, value):
"""Callback de validação personalizado."""
if "@" not in value:
raise click.BadParameter("Email inválido: deve conter @")
return value.lower()
@click.command()
@click.option("--tamanho", type=click.IntRange(1, 100))
@click.option("--modo", type=click.Choice(["dev", "prod", "test"]))
@click.option("--caminho", type=click.Path(file_okay=True, dir_okay=False))
@click.option("--email", callback=validar_email)
def configurar(tamanho, modo, caminho, email):
click.echo(f"Configuração: {tamanho}, {modo}, {caminho}, {email}")
5. Contexto e estado compartilhado
O objeto Context permite compartilhar dados entre comandos em um grupo.
@click.group()
@click.option("--verbose", is_flag=True)
@click.pass_context
def cli(ctx, verbose):
"""CLI principal com contexto compartilhado."""
ctx.ensure_object(dict)
ctx.obj["verbose"] = verbose
ctx.obj["config"] = {"timeout": 30, "retry": 3}
@cli.command()
@click.argument("servidor")
@click.pass_context
def ping(ctx, servidor):
"""Testa conectividade com um servidor."""
config = ctx.obj["config"]
verbose = ctx.obj["verbose"]
if verbose:
click.echo(f"Timeout: {config['timeout']}s")
click.echo(f"Retentativas: {config['retry']}")
click.echo(f"Pingando {servidor}...")
6. Saída formatada e interatividade
Click facilita a criação de interfaces ricas e interativas.
@click.command()
@click.option("--nome", prompt="Digite seu nome")
@click.password_option()
def login(nome, password):
"""Sistema de login interativo."""
# Saída colorida
click.secho("Bem-vindo!", fg="green", bold=True)
click.echo(click.style(f"Usuário: {nome}", fg="cyan"))
# Confirmação
if click.confirm("Deseja continuar?"):
# Barra de progresso
with click.progressbar(range(10), label="Carregando") as bar:
for item in bar:
import time
time.sleep(0.1)
click.echo("Operação concluída!")
else:
click.echo("Operação cancelada.")
7. Tratamento de erros e testes
Click fornece exceções específicas e um runner para testes.
import click
from click.testing import CliRunner
@click.command()
@click.argument("numero", type=int)
def dividir(numero):
"""Divide 10 pelo número fornecido."""
if numero == 0:
raise click.ClickException("Divisão por zero não permitida!")
resultado = 10 / numero
click.echo(f"Resultado: {resultado}")
# Teste unitário
def test_dividir():
runner = CliRunner()
# Teste bem-sucedido
result = runner.invoke(dividir, ["5"])
assert result.exit_code == 0
assert "Resultado: 2.0" in result.output
# Teste de erro
result = runner.invoke(dividir, ["0"])
assert result.exit_code != 0
assert "Divisão por zero" in result.output
# Teste de tipo inválido
result = runner.invoke(dividir, ["abc"])
assert result.exit_code != 0
8. Boas práticas e composição avançada
Para aplicações mais sofisticadas, considere estas práticas:
# Decorador reutilizável para opções comuns
def opcoes_comuns(func):
@click.option("--verbose", is_flag=True, help="Modo verboso")
@click.option("--config", type=click.Path(), help="Arquivo de configuração")
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
# Plugin com descoberta automática
def descobrir_plugins():
"""Descobre comandos em pacotes instalados."""
import pkg_resources
plugins = {}
for entry_point in pkg_resources.iter_entry_points("meuapp.commands"):
plugins[entry_point.name] = entry_point.load()
return plugins
@click.group()
def cli():
"""CLI principal com plugins."""
pass
# Registro dinâmico de comandos
for nome, cmd in descobrir_plugins().items():
cli.add_command(cmd, nome)
Para publicar sua CLI no PyPI, estruture seu setup.py ou pyproject.toml com entry points:
# setup.py
setup(
name="minha-cli",
entry_points={
"console_scripts": [
"minha-cli=meupacote.cli:cli",
],
},
)
Click se destaca por transformar a criação de CLIs em Python em uma experiência agradável e produtiva. Sua filosofia de composição, combinada com decoradores elegantes e suporte nativo a testes, faz dele a escolha ideal para projetos de qualquer escala — desde scripts simples até ferramentas corporativas complexas.
Referências
- Documentação oficial do Click — Guia completo com todos os recursos, exemplos e referência da API
- Click GitHub - Código fonte e issues — Repositório oficial para contribuições, relatórios de bugs e discussões
- Real Python - Building Python CLIs with Click — Tutorial prático cobrindo desde conceitos básicos até técnicas avançadas
- Click vs argparse: Why Click is Better — Comparação detalhada entre Click e argparse com exemplos práticos
- Click Testing Documentation — Guia oficial sobre como testar CLIs construídas com Click usando CliRunner
- Python Packaging - Entry Points — Documentação sobre como criar entry points para distribuir CLIs via PyPI
- Click and Rich - Better CLI Output — Integração entre Click e Rich para saídas formatadas e coloridas avançadas