CLI nativa — cli.py¶
Parte da norma Padronização de CLI para Fetchers. Os números de seção (§) são os da norma completa.
Escopo restrito (ADR
2026-10-08):argparsecompleto aplica-se apenas a fetchers com comandos customizados fora doFetcherApp— hoje, somente osidra-fetcher. Todo fetcher novo baseado noFetcherAppnão escreve a CLI nesta seção: usa o wrapper fino (§1.3).
2.1 Esqueleto obrigatório¶
"""CLI standalone para <nome>-fetcher."""
from __future__ import annotations
import argparse
import sys
from <pacote> import __version__
from <pacote>.<modulo_principal> import <funcao_principal>
def get_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
prog="<pacote>-fetcher",
description="<Descrição curta do fetcher>.",
)
parser.add_argument(
"--version",
action="version",
version=f"%(prog)s {__version__}",
)
subparsers = parser.add_subparsers(dest="command", required=True)
# Alternativa quando não há subcomando padrão óbvio:
# parser.set_defaults(func=lambda _: parser.print_help())
# (sem required=True; mostra ajuda se nenhum subcomando for passado)
# --- subcomando: sync ---
sync = subparsers.add_parser("sync", help="Baixar/atualizar dados.")
sync.add_argument(
"-o",
"--output",
metavar="DIR",
default="/data/<fonte>",
help="Diretório de saída (padrão: /data/<fonte>).",
)
sync.add_argument(
"--verbose",
action="store_true",
default=False,
help="Exibir logs detalhados em vez de barra de progresso.",
)
return parser
def main(argv: list[str] | None = None) -> None:
parser = get_parser()
args = parser.parse_args(argv)
from quantilica.core.logging import configure_cli_logging
configure_cli_logging(verbose=args.verbose)
if args.command == "sync":
_cmd_sync(args)
def _cmd_sync(args: argparse.Namespace) -> None:
...
if __name__ == "__main__":
main()
Regras:
- A função que constrói o parser deve se chamar
get_parser()— nãoset_parser(), nãoget_args(). Isso permite que testes e outros módulos obtenham o parser sem parsear argv. main()deve aceitarargv: list[str] | None = Nonepara testabilidade viamain(["sync", ...]). É obrigatório em todos os fetchers — omitir impede testes unitários de CLI.
2.2 Opções padronizadas¶
Todas as CLIs nativas devem implementar as seguintes opções de forma consistente:
| Opção | Forma curta | Padrão | Tipo | Descrição |
|---|---|---|---|---|
--output |
-o |
/data/<fonte> |
Path |
Diretório de saída |
--verbose |
(nenhuma) | False |
bool |
Logs detalhados |
--version |
(nenhuma) | — | ação | Exibe versão e encerra |
Sem -v como atalho de --verbose
Nunca use -v como atalho de --verbose. Wrappers e orquestradores (Makefile, scripts shell, quantilica-cli) podem reservar -v para outros fins. Um único --verbose sem ambiguidade é o padrão do ecossistema.
2.3 Subcomandos recomendados¶
Os subcomandos seguem o vocabulário canônico definido em §1.2. A cli.py
nativa deve expor exatamente os mesmos verbos que o plugin.py do fetcher:
| Subcomando | Quando usar |
|---|---|
sync |
Baixar o conjunto principal de dados (tudo por padrão) |
list |
Listar o que está disponível (datasets, anos, etc.) |
check |
Verificar remoto × local sem baixar; --json gera plano consumível por sync --from-plan |
convert |
Converter de formato bruto para Parquet/JSON |
export |
Exportar para formatos externos (Excel, SQLite) |
info |
Exibir metadados de uma entidade |
pipeline |
Executar fluxo completo (vários passos encadeados) |
search |
Busca livre em índice ou catálogo remoto |
2.4 Registro no pyproject.toml¶
[project.scripts]
<pacote>-fetcher = "<modulo>.cli:main"
Exemplo real (comex-fetcher):
[project.scripts]
comex-fetcher = "comex_fetcher.cli:main"
2.5 Tratamento de erros¶
- Use
parser.error(mensagem)para erros de argumento — encerra com código 2 e exibe o help. - Para erros de execução, escreva em
sys.stderre encerre com código 1:
import sys
def _cmd_sync(args):
if not args.output.parent.exists():
print(f"Erro: diretório pai não existe: {args.output.parent}", file=sys.stderr)
sys.exit(1)
- Nunca use
raise SystemExit(1)diretamente em funções de negócio — reserve para o nível de CLI. - Não suprima exceções silenciosamente. Se não souber o que fazer com uma exceção, deixe propagar.
2.6 Silenciar logs INFO quando exibindo progresso¶
Só se aplica a fetcher customizado (hoje, apenas
sidra-fetcher, ADR2026-10-08) — wrappers finos não exibem progresso próprio nem tocam em loggers (§1.3).
configure_cli_logging(verbose=False) define o nível raiz em INFO. Isso faz com que mensagens internas do core (como log_step em quantilica.core.http) apareçam no terminal e corrompam a saída de barras tqdm.
Quando cli.py exibe progresso (barra tqdm ou output limpo), adicione após configure_cli_logging:
def main(argv: list[str] | None = None) -> None:
parser = get_parser()
args = parser.parse_args(argv)
configure_cli_logging(verbose=args.verbose)
if not args.verbose:
# Suprime log_step (INFO) do core e logs de rastreamento do fetcher
logging.getLogger("quantilica.core").setLevel(logging.WARNING)
logging.getLogger("<pacote_fetcher>").setLevel(logging.WARNING)
args.func(args)
Onde <pacote_fetcher> é o nome do pacote do fetcher (ex: "comex_fetcher", "pdet_fetcher"). São necessários dois setLevel porque quantilica.core.* usa o namespace "quantilica.core" e cada fetcher usa o namespace do seu próprio pacote — get_logger(__name__) retorna logging.getLogger(name) sem prefixo adicional. Evite logging.getLogger().setLevel(WARNING) (raiz) pois suprime loggers de terceiros como httpx2.