---
title: "CLI com Python em 2026: argparse, Typer, Click e uv | Python Brasil"
url: "https://python.dev.br/blog/criando-cli-com-python/"
markdown_url: "https://python.dev.br/blog/criando-cli-com-python.MD"
description: "Aprenda a criar ferramentas de linha de comando com Python usando argparse, Typer, Click e uv. Guia prático com subcomandos, testes, CI e empacotamento."
date: "2025-10-12"
author: "Equipe Python Brasil"
---

# CLI com Python em 2026: argparse, Typer, Click e uv | Python Brasil

Aprenda a criar ferramentas de linha de comando com Python usando argparse, Typer, Click e uv. Guia prático com subcomandos, testes, CI e empacotamento.


Ferramentas de linha de comando continuam sendo um dos melhores projetos para aprender Python de forma profissional. Uma boa CLI automatiza tarefas repetitivas, organiza dados, chama APIs, roda checks de qualidade, processa arquivos e vira evidência concreta em entrevistas. Em 2026, o caminho mais forte não é apenas escrever um script que funciona: é entregar uma ferramenta instalável, testada, documentada e fácil de usar por outra pessoa.

Este guia atualiza a abordagem clássica de `argparse` e Click com o fluxo moderno de [uv](/blog/uv-gerenciador-pacotes-python/), [Ruff](/blog/ruff-linter-formatador-python/), [pytest](/guias/testes-com-pytest/) e Typer. Se você está montando um [portfólio Python](/carreira/projetos-portfolio-python/) ou procurando uma forma prática de demonstrar automação para [vagas Python](/vagas/), uma CLI pequena e bem acabada pode valer mais do que um projeto web genérico sem uso real.

## Quando Criar uma CLI com Python

CLIs são ideais quando a tarefa precisa ser repetida, versionada ou integrada a outros comandos. Alguns exemplos bons para portfólio:

- Organizar notas fiscais, CSVs ou planilhas em uma pasta.
- Consultar uma API pública e salvar o resultado em JSON ou SQLite.
- Validar arquivos antes de um deploy.
- Gerar relatórios locais para times de dados, QA ou RevOps.
- Automatizar rotinas de scraping, conversão de documentos ou limpeza de dados.

Python é uma escolha natural porque combina biblioteca padrão rica, ecossistema de pacotes, boa legibilidade e integração simples com sistemas operacionais. Para comparar com outras linguagens, Go costuma brilhar em binários únicos e CLIs de infraestrutura; Rust se destaca quando performance e distribuição leve são prioridades. Ainda assim, Python vence quando a ferramenta precisa conversar rapidamente com APIs, dados, notebooks, automações e bibliotecas de negócio.

## Escolha Rápida: argparse, Typer ou Click

| Cenário | Melhor escolha | Por quê |
|---|---|---|
| Script simples, sem dependências | `argparse` | Já vem com Python e resolve comandos básicos. |
| CLI moderna para projeto novo | Typer | Usa type hints, gera ajuda automaticamente e é confortável para FastAPI/Pydantic users. |
| Projeto já baseado em Click | Click | Maduro, estável e muito usado em ferramentas existentes. |
| CLI para empacotar e instalar | Typer ou Click | Melhor ergonomia para subcomandos, testes e mensagens. |

Se você está começando, aprenda `argparse` para entender a base. Para um projeto que vai para GitHub, README e currículo, Typer costuma ser o ponto ideal em 2026.

## Começando com argparse

O `argparse` já vem incluso no Python. Ele é perfeito para scripts pequenos, automações internas e comandos com poucas opções:

```python
#!/usr/bin/env python3
"""Organizador simples de arquivos por extensão."""

import argparse
from pathlib import Path


def organizar(pasta: Path, aplicar: bool) -> None:
    for arquivo in pasta.iterdir():
        if not arquivo.is_file():
            continue

        extensao = arquivo.suffix.removeprefix(".") or "sem-extensao"
        destino = pasta / extensao / arquivo.name
        print(f"{arquivo.name} -> {destino.relative_to(pasta)}")

        if aplicar:
            destino.parent.mkdir(exist_ok=True)
            arquivo.rename(destino)


def main() -> None:
    parser = argparse.ArgumentParser(
        description="Organiza arquivos de uma pasta por extensão."
    )
    parser.add_argument("pasta", type=Path, help="Pasta que será organizada")
    parser.add_argument(
        "--aplicar",
        action="store_true",
        help="Move os arquivos de verdade; sem isso, apenas simula",
    )
    args = parser.parse_args()

    organizar(args.pasta, args.aplicar)


if __name__ == "__main__":
    main()
```

Uso:

```bash
python organiza.py ~/Downloads
python organiza.py ~/Downloads --aplicar
```

O detalhe importante é o modo seguro por padrão. O comando primeiro mostra o que faria e só move arquivos quando `--aplicar` é usado. Essa decisão melhora a confiança do usuário e evita acidentes.

## CLI Moderna com Typer

Typer usa type hints para gerar validação, ajuda e conversão de argumentos. Instale com uv:

```bash
uv add typer
```

Exemplo de uma CLI de tarefas com subcomandos:

```python
from __future__ import annotations

import json
from pathlib import Path

import typer

app = typer.Typer(help="Gerenciador de tarefas pelo terminal.")
ARQUIVO = Path.home() / ".tarefas-python.json"


def carregar_tarefas() -> list[dict]:
    if not ARQUIVO.exists():
        return []
    return json.loads(ARQUIVO.read_text(encoding="utf-8"))


def salvar_tarefas(tarefas: list[dict]) -> None:
    ARQUIVO.write_text(
        json.dumps(tarefas, indent=2, ensure_ascii=False),
        encoding="utf-8",
    )


@app.command()
def adicionar(titulo: str, prioridade: str = "média") -> None:
    """Adiciona uma nova tarefa."""
    tarefas = carregar_tarefas()
    tarefa = {
        "id": len(tarefas) + 1,
        "titulo": titulo,
        "prioridade": prioridade,
        "concluida": False,
    }
    tarefas.append(tarefa)
    salvar_tarefas(tarefas)
    typer.secho(f"Tarefa #{tarefa['id']} criada.", fg=typer.colors.GREEN)


@app.command()
def listar(todas: bool = False) -> None:
    """Lista tarefas pendentes ou todas as tarefas."""
    tarefas = carregar_tarefas()
    for tarefa in tarefas:
        if tarefa["concluida"] and not todas:
            continue
        status = "ok" if tarefa["concluida"] else "pendente"
        typer.echo(f"[{tarefa['id']}] {tarefa['titulo']} - {status}")


if __name__ == "__main__":
    app()
```

Rode localmente:

```bash
uv run python tarefas.py adicionar "Revisar currículo Python" --prioridade alta
uv run python tarefas.py listar
```

Typer fica especialmente bom quando você já usa [type hints](/glossario/type-hints/), [Pydantic](/blog/pydantic-validacao-dados-python/) ou [FastAPI](/glossario/fastapi/), porque o estilo mental é parecido: tipos claros, validação explícita e documentação gerada a partir da assinatura das funções.

## Quando Click Ainda Faz Sentido

Click continua excelente e aparece em ferramentas importantes do ecossistema Python. Ele usa decoradores, oferece grupos de comandos, opções coloridas, prompts e testes com `CliRunner`:

```python
import click


@click.group()
@click.version_option(version="1.0.0")
def cli():
    """Ferramenta de produtividade para projetos Python."""


@cli.command()
@click.argument("nome")
@click.option("--privado/--publico", default=True, help="Tipo do projeto")
def criar(nome: str, privado: bool) -> None:
    modo = "privado" if privado else "público"
    click.echo(click.style(f"Criando {nome} como projeto {modo}.", fg="green"))
```

Se você está mantendo uma CLI existente em Click, não precisa migrar só por moda. Atualize documentação, testes, empacotamento e fluxo de instalação antes de trocar biblioteca.

## Empacotando com pyproject.toml

Para transformar a ferramenta em comando instalável, configure `pyproject.toml`:

```toml
[project]
name = "tarefas-python"
version = "0.1.0"
description = "CLI de tarefas para demonstrar automação com Python"
requires-python = ">=3.11"
dependencies = ["typer>=0.12"]

[project.scripts]
tarefas = "tarefas:app"

[tool.ruff]
line-length = 88
target-version = "py311"

[tool.pytest.ini_options]
testpaths = ["tests"]
```

Durante o desenvolvimento:

```bash
uv sync
uv run tarefas --help
uv run ruff check .
uv run pytest
```

Para instalar como ferramenta local, prefira `uv tool install .` ou `pipx install .`. Assim o comando fica disponível no terminal sem poluir o ambiente global do Python.

## Testando uma CLI

Teste de CLI precisa cobrir comportamento visível: argumentos, saída, arquivos criados e códigos de erro. Com Typer:

```python
from typer.testing import CliRunner

from tarefas import app


runner = CliRunner()


def test_help_mostra_comandos():
    resultado = runner.invoke(app, ["--help"])
    assert resultado.exit_code == 0
    assert "adicionar" in resultado.stdout


def test_adicionar_tarefa():
    resultado = runner.invoke(app, ["adicionar", "Estudar pytest"])
    assert resultado.exit_code == 0
    assert "criada" in resultado.stdout
```

Para um projeto de portfólio, combine esses testes com [pytest](/glossario/pytest/), [Ruff](/blog/ruff-linter-formatador-python/) e uma action de CI. Uma configuração mínima de GitHub Actions pode rodar `uv sync`, `uv run ruff check .` e `uv run pytest` a cada push.

## Boas Práticas para CLIs Profissionais

Ao criar ferramentas de linha de comando, siga estas recomendações:

- Inclua `--help` claro em todos os comandos e opções.
- Use `--dry-run` ou confirmação antes de operações destrutivas.
- Retorne códigos de saída adequados: `0` para sucesso, `1` ou outro código documentado para erro.
- Escreva mensagens em português claro quando a audiência for brasileira.
- Separe lógica de negócio da camada CLI para facilitar testes.
- Use `pathlib` em vez de manipular caminhos como strings.
- Registre erros com contexto quando a ferramenta roda em automações.
- Documente exemplos reais no README, não apenas flags isoladas.
- Publique prints ou GIFs curtos do terminal se o projeto for para portfólio.

## Ideias de Projeto para Portfólio

Uma CLI fica mais forte quando resolve uma dor concreta. Algumas ideias:

- `organiza-downloads`: separa arquivos por tipo, data e tamanho.
- `gsc-resumo`: lê CSV exportado do Google Search Console e mostra oportunidades de CTR.
- `vagas-python`: filtra vagas por tecnologia, senioridade e modelo remoto a partir de um JSON.
- `nota-fiscal-tools`: renomeia PDFs/XMLs por CNPJ, data e valor.
- `markdown-check`: valida links, títulos duplicados e tamanho de artigos.

Cada uma dessas ideias conversa com áreas reais de atuação: dados, automação, QA, conteúdo técnico e operações. Para fortalecer o currículo, conecte o projeto com um estudo de caso em [carreira Python](/carreira/) e mostre o antes/depois da automação.

## Conclusão

Python continua excelente para criar CLIs porque permite sair de um script simples para uma ferramenta instalável sem trocar de linguagem. Em 2026, a recomendação prática é: use `argparse` quando você quer zero dependências, Typer para novos projetos com type hints e Click quando já existe uma base madura nessa biblioteca.

O diferencial de uma CLI profissional não está apenas no parser de argumentos. Está em segurança por padrão, documentação, testes, empacotamento, CI e exemplos reais. Se você precisa de um projeto pequeno, útil e demonstrável para entrevistas, uma CLI bem construída é uma das melhores apostas do ecossistema Python.

> **CLIs em outras linguagens**: <a href="https://golang.com.br/" target="_blank" rel="noopener noreferrer" onclick="umami.track('portfolio-site-click', { destination: 'golang.com.br' })">Go</a> com Cobra é uma escolha comum para CLIs de infraestrutura como Docker, Kubernetes e GitHub CLI. <a href="https://rustlang.com.br/" target="_blank" rel="noopener noreferrer" onclick="umami.track('portfolio-site-click', { destination: 'rustlang.com.br' })">Rust</a> com clap também gera CLIs rápidas, com binários pequenos e ótima experiência de distribuição.
