Destino Dev

Blog

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:

  1. mycli/
  2. __init__.py
  3. main.py
  4. 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:

  1. Verifique se o script foi escrito no arquivo de configuração do shell (~/.bashrc, ~/.zshrc).
  2. Reinicie o shell ou recarregue com source.
  3. 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