Criar uma CLI em Python com Typer: comandos, autocompletar e distribuir com pipx
Por John Pires · 20 de março de 2026

Por que aprender isso em 2026
Automação prática e produtividade
CLI (Command Line Interface) continua sendo a forma mais direta de automatizar tarefas repetitivas, integrar ferramentas e criar utilitários rápidos para times de desenvolvimento e operações. Em 2026, pipelines, devtools e integrações locais ainda dependem fortemente de scripts. Aprender a criar uma CLI com Typer permite transformar scripts Python em ferramentas confiáveis e distribuíveis.
Razões técnicas para escolher Typer
Typer é construído sobre Click, trazendo tipagem e autogerenciamento de opções com base em anotações de tipo Python. Isso significa menos boilerplate, documentação automática (ajuda via --help) e melhor ergonomia para quem está começando. Além disso, Typer incentivou boas práticas: separar funções, usar typing e fornecer ajuda clara ao usuário.
Benefícios diretos:
- Declaração de comandos com funções Python e type hints.
- Ajuda automática e validação de tipos.
- Integração simples com installers (pipx) para distribuição local sem contaminação do sistema.
Foco: transformar ideias rápidas em ferramentas reutilizáveis com pouca sobrecarga. Typer faz a ponte entre script e ferramenta.
Configuração do ambiente
Requisitos mínimos
Recomendo Python 3.10+ por causa de melhorias de typing e compatibilidade com as dependências mais recentes. Instale pip e pipx (para distribuir localmente) antes de começar. pipx isola cada CLI em um virtualenv, permitindo instalação global sem conflitos.
Instalação de dependências
Crie um ambiente virtual enquanto desenvolve, mas veja que o objetivo é empacotar e instalar com pipx depois. No terminal:
python -m venv .venv
source .venv/bin/activate # mac/linux
.venv\Scripts\activate # windows
pip install --upgrade pip
pip install typer[all] click-completion
O comando acima instala Typer e click-completion (usado para trabalhar com autocompletar de forma explícita). Depois de desenvolvido, você distribuirá com pipx.
Instalando e usando pipx
pipx permite instalar pacotes Python que expõem comandos CLI sem poluir o Python global. Instale pipx se ainda não tiver:
python -m pip install --user pipx
python -m pipx ensurepath
Após reiniciar o shell, você pode instalar seu pacote com:
Local (diretório do projeto): pipx install .
Do PyPI: pipx install nome-do-pacote
Hello World progressivo
Primeiro comando: hello básico
Vamos começar criando um arquivo main.py com um comando simples que imprime uma mensagem personalizada. Esse é o esqueleto mínimo para uma CLI Typer.
# main.py
import typer
app = typer.Typer()
@app.command()
def hello(name: str):
"""Imprime 'Olá, <name>' no terminal."""
print(f"Olá, {name}")
if __name__ == "__main__":
app()
O que esse bloco faz: registra um comando chamado hello que recebe um argumento name tipado como str. Ao executar python main.py hello Alice, o programa imprime "Olá, Alice".
Adicionando opções e tipos
Agora vamos transformar o argumento em uma opção, aceitar inteiros e oferecer um flag --caps para deixar a saída em maiúsculas.
# main.py (atualizado)
import typer
app = typer.Typer()
@app.command()
def hello(name: str = typer.Option("mundo", "-n", "--name"), repeat: int = 1, caps: bool = False):
"""Imprime uma saudação repetida de acordo com os parâmetros."""
text = f"Olá, {name}"
if caps:
text = text.upper()
for _ in range(repeat):
print(text)
if __name__ == "__main__":
app()
O que acontece: agora name é uma opção com atalho -n/--name, repeat é convertido automaticamente para int graças ao type hint, e caps é um booleano ativado por --caps. Typer cuida da validação e da ajuda.
Mini-projeto prático
Objetivo do mini-projeto
Criar um utilitário chamado filetool que tenha três comandos: info (mostra metadados do arquivo), grep (procura uma string em linhas) e head (exibe primeiras linhas). O projeto mostra organização, subcomandos e autocompletar.
Estrutura do projeto
Estruture o projeto assim:
- mycli/
- __init__.py
- main.py
- pyproject.toml
O arquivo main.py terá a lógica e exportará app para registro como console script.
Implementação: comandos do filetool
Exemplo funcional com 3 comandos. Cada bloco abaixo tem contexto e explicação.
# mycli/main.py
import typer
from pathlib import Path
from typing import Optional
import os
app = typer.Typer()
@app.command()
def info(path: Path):
"""Imprime informações básicas sobre o arquivo/diretório."""
p = Path(path)
print(f"Path: {p}")
print(f"Exists: {p.exists()}")
if p.exists():
print(f"Size: {p.stat().st_size} bytes")
print(f"Is file: {p.is_file()}")
print(f"Is dir: {p.is_dir()}")
@app.command()
def grep(path: Path, query: str, ignore_case: bool = False):
"""Procura 'query' em cada linha do arquivo e imprime linhas encontradas.
Não é recursivo; funciona em arquivos texto.
"""
flags = 'i' if ignore_case else ''
if not path.exists():
typer.echo("Arquivo não encontrado", err=True)
raise typer.Exit(code=1)
with open(path, 'r', encoding='utf-8', errors='ignore') as f:
for n, line in enumerate(f, start=1):
hay = line.lower() if ignore_case else line
term = query.lower() if ignore_case else query
if term in hay:
print(f"{n}: {line.rstrip()}")
@app.command()
def head(path: Path, lines: int = 10):
"""Imprime as primeiras 'lines' linhas do arquivo."""
if not path.exists() or not path.is_file():
typer.echo("Arquivo inválido", err=True)
raise typer.Exit(code=2)
with open(path, 'r', encoding='utf-8', errors='ignore') as f:
for _ in range(lines):
line = f.readline()
if not line:
break
print(line.rstrip())
if __name__ == "__main__":
app()
O que acontece: temos comandos isolados, cada um com responsabilidades claras. Typer cria automaticamente a ajuda para cada comando (python -m mycli main info --help).
Autocompletar com click-completion
Para ativar comandos de instalação de autocompletar, inicialize click-completion antes de app(). Assim você terá opções como --install-completion.
# mycli/main.py (trecho de inicialização)
import click_completion
click_completion.init()
# resto do arquivo (app = typer.Typer() e comandos)
Depois de instalar click-completion (pip install click-completion), execute:
- bash: python -m mycli --install-completion bash
- zsh: python -m mycli --install-completion zsh
Isso escreve o script de completions no local adequado do shell. Reinicie o shell ou recarregue o arquivo de configuração.
Empacotando para distribuição
Mostrarei um pyproject.toml mínimo para empacotar com setuptools. Esse arquivo permite instalar com pipx diretamente do diretório do projeto.
# pyproject.toml
[build-system]
requires = ["setuptools>=61", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "mycli"
version = "0.1.0"
description = "Utilitário de linha de comando exemplo com Typer"
requires-python = ">=3.10"
dependencies = ["typer[all]", "click-completion"]
[project.scripts]
mycli = "mycli.main:app"
O que acontece: o campo project.scripts registra o comando mycli que será executável após instalação. O valor aponta para a Typer app (que é chamável).
Instalando com pipx
Com o pyproject válido e estando no diretório do projeto:
pipx install .
# ou para atualizar depois de alterações
pipx reinstall --editable .
Após isso, rode mycli info arquivo.txt diretamente no terminal. pipx cria um ambiente isolado e coloca o executável no PATH do usuário.
Erros comuns, armadilhas e dicas práticas
Erros de tipagem e valores padrão
Uma armadilha frequente é declarar parâmetros opcionais com typing.Optional sem fornecer default. Por exemplo def cmd(x: Optional[str]): fará x requerido. Para ter opcional, sempre def cmd(x: Optional[str] = None).
Problemas com entry points
Ao empacotar, erros comuns:
- Erro: comando não encontrado — verifique se o package foi instalado no PATH (pipx é recomendado).
- Entry point incorreto — o valor deve ser module:callable. Para Typer, apontar para a instância app funciona.
- Versão do Python incompatível — confirme requires-python em pyproject.toml.
Autocompletar não ativa
Se o autocompletar não carregar após --install-completion:
- Verifique se o script foi escrito no arquivo de configuração do shell (~/.bashrc, ~/.zshrc).
- Reinicie o shell ou recarregue com source.
- Confirme a instalação do pacote click-completion na mesma venv onde o comando foi instalado.
Dica prática: testando completions em tempo de desenvolvimento, use o modo editable e reinicie o shell sempre que alterar pontos que geram completions.
Recursos para continuar estudando
Documentação oficial e leituras
Consulte Typer docs para padrões de uso avançado: subcomandos agrupados, callbacks, validações customizadas e integração com Pydantic quando necessário. Também verifique a documentação do Click (base do Typer) para entender como eventos e contextos funcionam.
Ferramentas e bibliotecas complementares
Recomendações:
- pydantic — validação estruturada quando você precisa de modelos complexos.
- rich — melhorar saída de terminal com formatação e tabelas.
- pytest — testar comandos CLI via CliRunner de Click ou subprocess para integração.
Exemplo rápido de teste usando CliRunner (útil para CI):
# tests/test_main.py
from typer.testing import CliRunner
from mycli.main import app
runner = CliRunner()
def test_hello():
result = runner.invoke(app, ["hello", "test"])
assert result.exit_code == 0
assert "Olá, test" in result.output
Isso automatiza verificações básicas antes de publicar ou distribuir com pipx.
Conclusão prática
Typer reduz fricção entre script e ferramenta: com type hints você ganha validação, ajuda automática e comandos claros. Investir alguns minutos em estruturar um projeto, adicionar entry points e usar pipx para distribuição entrega utilitários portáveis e seguros para seu time.
Checklist prático:
- Use type hints e defaults corretos (Optional = None quando for opcional).
- Adicione click-completion.init() para facilitar instalação de completions.
- Empacote com pyproject.toml e registre o script em project.scripts.
- Instale com pipx para isolamento e facilidade de distribuição.
Termine um utilitário simples hoje: crie main.py, adicione app, implemente 2 comandos e registre no pyproject. Em seguida, pipx install . e teste o comando globalmente. Isso entrega um ciclo completo de desenvolvimento — do script local para uma CLI instalada — com menos de uma hora de trabalho para um iniciante.
Próximo passo: adicione logging estruturado, testes automatizados e explore rich para saídas mais amigáveis.
Assuntos: Python, typer, cli, pipx, automation
Leia também
ETL incremental em Python com Pandas, DuckDB e Parquet (checkpoints)
Implementação prática de ETL incremental em Python: checkpoints em JSON, extração delta com pandas/sqlalchemy, escrita particionada em Parquet via pyarrow e…
FastAPI: endpoints idempotentes com Idempotency-Key e Redis (retries seguros)
Guia prático para implementar Idempotency-Key em FastAPI com Redis: locks atomicos, persistencia de respostas, scripts Lua, decorator para endpoints e boas…
FastAPI: inferência de modelos em streaming com asyncio, chunking e SSE
Padrões práticos para inferência em streaming com FastAPI: use asyncio.Queue, chunking e SSE para reduzir latência percebida, controlar concorrência e…
Servidor de embeddings com FastAPI, Transformers e FAISS: batching e quantização
Implementação prática de um servidor de embeddings com FastAPI, transformers e FAISS: mostro anti-patterns e correções, batching assíncrono, execução em…
Configuração segura em Python: Pydantic v2 + HashiCorp Vault na prática
Pydantic v2 + HashiCorp Vault: técnicas práticas para evitar vazamento de segredos, integrar Vault como fonte de configuração, e patterns corretos para…
Automatizar Google Sheets com Python: OAuth 2.0, gspread e exemplos práticos
Guia prático para automatizar Google Sheets com Python em 2026: configure OAuth 2.0, use gspread, exemplos com service account, fluxo InstalledAppFlow e uma…