Contribuindo¶
Tudo o que você precisa para desenvolver, testar e lançar esta biblioteca.
Veja também: Uso · Referência da API · o
CONTRIBUTING.mdna raiz do repositório contém a política autoritativa de branch/PR e de mensagem de commit.
Preparando o ambiente de desenvolvimento¶
O projeto traz um Makefile e um tasks.sh equivalente, então use o que couber na sua máquina —
make init ou bash tasks.sh init quando make não estiver disponível (por exemplo, num
shell padrão do Windows).
make init # cria o virtualenv do Poetry + instala deps + instala hooks de pre-commit
# ou, sem make:
bash tasks.sh init
init compõe venv (cria o virtualenv do Poetry e instala todas as dependências, inclusive
dev + docs) e precommit (instala os git hooks). O Poetry é instalado automaticamente se estiver
faltando.
Testes e lint¶
make unit_tests # poetry run pytest tests/unit/
make integration_tests # poetry run pytest tests/integration/
make lint # ruff + mypy + codespell + pydocstyle + gates de shell/sql/yaml
A CI roda os mesmos gates em cada pull request; mantenha-os verdes localmente antes de dar push.
Servindo a documentação localmente¶
Documentação versionada (mike + GitHub Pages)¶
O site é versionado com mike: cada release publica a sua
própria versão navegável na branch gh-pages, e o leitor alterna entre versões por um dropdown
no cabeçalho. O alias latest sempre aponta para a release mais recente, então a URL raiz do site
cai na versão mais nova.
Como cada peça se encaixa:
- Build-check (CI): o workflow
Docs - Build Checkrodamkdocs build --stricta cada push e PR — sem publicar. É só a garantia de que a doc sempre compila; um release nunca descobre uma árvore de docs quebrada na hora de publicar. - Deploy (CI): o job
Deploy Versioned Docs (mike)doRelease to PyPIroda depois do publish no PyPI (só documenta versão que efetivamente subiu):mike deploy --push X.Y latest+mike set-default --push latest, empurrando paragh-pagescom oGITHUB_TOKENembutido (contents: write) — sem token externo. A granularidade é minor (X.Y): um patch herda o slot da sua minor em vez de multiplicar entradas. Prereleases (rc1,b1, …) não movemlatest(o job é pulado para elas).
Configuração do mantenedor — uma única vez, com admin no repositório: apontar o GitHub Pages
para a branch gh-pages (o modelo mike), em vez da fonte GitHub Actions. O workflow não faz
isso: o GITHUB_TOKEN é um token de GitHub App que não altera as configurações do Pages.
Já roda dentro de make init / bash tasks.sh init. É idempotente e não-bloqueante, e — para
nunca deixar o site em 404 — só troca a fonte para gh-pages depois que essa branch existir
(o primeiro mike deploy do release a cria). Se rodar antes disso, ele avisa e não mexe no Pages;
basta rodar make enable_pages de novo após o primeiro release. Alternativa manual:
Settings → Pages → Build and deployment → Source: Deploy from a branch → gh-pages / (root).
Semear a versão atual imediatamente (opcional): se quiser publicar a versão já lançada sem esperar o próximo release, rode uma vez, com a branch limpa:
git config user.name "<você>" && git config user.email "<seu-email>"
poetry run mike deploy --push --update-aliases 0.18 latest
poetry run mike set-default --push latest
make enable_pages # agora que gh-pages existe, aponta o Pages para ela
Verificando o pacote construído¶
Antes de abrir um PR de release, confirme que a wheel realmente constrói e importa — isso pega
erros de empacotamento (um __init__ faltando, um subpacote _internal/ não incluído) que os
testes na árvore de fontes nunca revelam:
Pull requests¶
- Crie a branch a partir da branch padrão seguindo a política de prefixo (
feat/…,fix/…, …). - Preencha o template de PR por completo.
- Garanta que os checks de CI (testes, lint, build da documentação) passem — eles são o gate de merge.
O ruleset pr-quality-gate (revisão automática e proteção da branch)¶
A branch padrão é protegida por um branch ruleset chamado pr-quality-gate, provisionado
inteiramente por código — nenhuma caixinha marcada à mão:
Já roda dentro de make init / bash tasks.sh init. É idempotente (um ruleset existente é
atualizado no lugar, não duplicado) e não-bloqueante (sem gh, sem auth ou sem permissão de
admin, ele avisa e sai com 0 — o init completa). Exige gh autenticado com direitos de admin no
repositório: o GITHUB_TOKEN do CI não consegue escrever rulesets, por isso isto é um passo do
mantenedor, não um job de workflow.
O que o ruleset aplica:
| Regra | Efeito |
|---|---|
pull_request |
Toda mudança passa por PR. 0 aprovações exigidas — o GitHub não deixa o autor aprovar o próprio PR, então exigir ≥1 travaria um mantenedor solo. As conversas precisam estar resolvidas (required_review_thread_resolution), o que torna os comentários do Copilot vinculantes. |
required_status_checks |
Os testes (Run Automated Tests, nos 3 SOs) e o build da documentação bloqueiam o merge de fato. |
code_scanning |
CodeQL: alertas de segurança high_or_higher e alertas de erro bloqueiam o merge. |
copilot_code_review |
Revisão automática do Copilot em todo PR, revisando cada novo push (review_on_push). Grátis em repositórios públicos. |
non_fast_forward + deletion |
Sem force-push e sem deletar a branch padrão. |
Automático × manual — a fronteira é repositório × conta. Tudo que é configuração do
repositório é gravável pela REST API e já vem no script — inclusive a revisão do Copilot, que é um
rule type próprio (copilot_code_review), e não um parâmetro de pull_request (essa grafia
devolve HTTP 422, o que faz a regra parecer exclusiva da UI). Nenhuma caixinha precisa ser
marcada à mão. O outro pré-requisito de repositório — habilitar o CodeQL (default setup), sem o
qual a regra code_scanning não tem ferramenta para checar — também é API:
O que não dá para automatizar daqui é o direito de uso na conta, não no repositório: a regra
copilot_code_review só dispara "se o autor tiver acesso ao Copilot code review" — e o code
review não está incluído no Copilot Free (a própria tela de planos lista "AI reviews" como
recurso de upgrade). Sem um plano que o inclua (Pro / Pro+ / Business), a regra fica configurada e
correta, porém inerte: nenhuma revisão aparece e nada dá erro — o silêncio é a armadilha.
Como obter revisão automática de fato:
- Copilot Pro gratuito — é gratuito para estudantes, professores e mantenedores de projetos open-source populares (verificação em github.com/settings/billing).
- Sem plano nenhum — um workflow de
pull_requestchamando um LLM de tier gratuito (ex.: Gemini Flash) para comentar o PR. Independe do Copilot.
Regra geral: configuração de repositório → script (é o que este helper faz, 100%). Direito de uso de conta → depende do plano, e não se resolve do repositório. As demais regras do gate (PR obrigatório, CI verde, CodeQL limpo) funcionam independentemente do Copilot.
O painel de Quality Gate e o auto-merge¶
Todo PR recebe, automaticamente (workflow pr-gate.yaml → bin/pr_gate.py):
- Rótulos:
risk:*(classe de risco pelos caminhos alterados),size:*(XS…XL, pelo volume do diff) egate:passing/gate:pending/gate:failing—pendingnão éfailing: um check ainda rodando não diz nada sobre o resultado. - Um comentário fixo (atualizado no lugar, nunca empilhado) com a tabela por eixo — testes nos 3 SOs, build da documentação, CodeQL.
Idioma. O bot escreve em inglês, como todo o resto voltado a quem contribui (código, mensagens de commit, saída de CI). Só a documentação publicada segue o idioma do site (
mkdocs.yml→theme.language, pt-BR). A fronteira é o público, não o repositório.
Auto-merge — por caminho, nunca por tamanho. Um diff pequeno não é um diff seguro aqui: a
falha real desta biblioteca é semântica (uma coluna de FileContract aterrada errado, um date_ref
pegando a partição errada). Uma mudança de um caractere em _internal/config/contracts/ é
minúscula e catastrófica — e todos os testes passam, porque os testes afirmam o contrato que foi
escrito. Por isso a classificação é por caminho:
| Classe | Caminhos | Auto-merge? |
|---|---|---|
docs |
docs/**, *.md, mkdocs.yml |
✅ |
ci |
.github/**, bin/**, Makefile, tasks.sh, configs de lint |
✅ |
deps |
poetry.lock, pyproject.toml |
✅ (os testes são o gate) |
tests |
tests/** |
❌ — definem o que "passar" significa |
src |
src/filings_cvm/** |
❌ nunca, em nenhum tamanho |
other |
qualquer outro | ❌ (desconhecido = inseguro) |
Numa classe auto-fundível, o auto-merge é opt-out: a classificação é o consentimento, então
nenhum rótulo é preciso para armá-lo — as bumps semanais do Dependabot fundem sozinhas. O rótulo
do-not-merge é a válvula de escape que segura um PR específico.
Um diff XL também não funde — exceto quando o único arquivo alterado é o poetry.lock. O veto
por tamanho pergunta "esse diff é grande o bastante para um humano olhar?"; num lockfile regenerado
a pergunta não significa nada, porque o tamanho acompanha quantos hashes de dependência se
moveram, não quanto risco chegou — uma bump de 3 ferramentas de dev deu 579 linhas (XL, vetada)
enquanto uma de 2 pacotes deu menos de 500 (L, fundida). O pyproject.toml não entra na
exceção: é lá, num range editado à mão, que o risco de dependência de fato mora.
E ele não burla nada: usa o auto-merge nativo do GitHub, que segura o merge até todos os checks obrigatórios do ruleset ficarem verdes. O script decide apenas elegibilidade; quem decide se passou continua sendo o ruleset. (Auto-aprovação seria inútil aqui: o ruleset exige 0 aprovações, então uma aprovação de bot não destravaria nada.)
Duas regras da UI ficam deliberadamente desligadas, para não criar uma segunda fonte de verdade:
Require code quality results (severidade subjetiva de IA no caminho do merge — ruff, mypy e os
gates de bin/check_*.py já cobrem qualidade de forma determinística) e Restrict code coverage (o
piso já é single-source em .coveragerc, aplicado por pre-commit + CI).
Higiene de branches — apagar depois do merge, arquivar as de incidente¶
Depois do merge, a branch vai embora. Só o main e a gh-pages vivem no remoto.
⚠️ Ancestralidade não diz nada aqui — o repo faz squash-merge. Um squash cria um commit novo, então os commits originais de uma branch nunca são ancestrais do
main, por mais que o conteúdo tenha entrado por completo. Portantogit branch -dsempre recusa,git log main..branchsempre mostra "N commits à frente" e ogit cherrysempre marca+(o squash muda o patch-id). Nenhum dos três é evidência de perda. Os testes honestos são: a PR dela mergeou? (gh pr list --head <branch> --state all) e a comparação de conteúdo (o blob de cada arquivo tocado está nomain?).
Branch de incidente vira tag de arquivo. Se a branch tem história que vale preservar — um merge parcial, commits órfãos, uma recuperação — não é preciso escolher entre guardar o registro e limpar a lista:
git tag archive/<issue>-<slug> <sha-do-tip>e publique a tag;- só então apague a branch.
A tag deixa os commits alcançáveis para sempre (imunes ao GC) e a lista de branches limpa. Depois,
git log archive/<issue>-<slug> devolve tudo. Precedente: archive/49-retry-strategy (o incidente do
49 — mergeou com 1 de 3 commits; o resto foi recuperado pelo #50). Custa ~10 segundos e transforma¶
"posso apagar essa branch?" de decisão corajosa em decisão trivial.
⚠️ Uma PR auto-fundida não apaga a própria branch nem fecha a issue linkada — o merge é feito pelo bot, e ações do
GITHUB_TOKENnão cascateiam para as automações a jusante. Até isso ser resolvido, depois de toda PR auto-fundida: feche a issue (gh issue close <N>) e apague a ref (gh api -X DELETE repos/:owner/:repo/git/refs/heads/<branch>). Sem o fechamento da issue, o card nunca chega a "Done" — o workflow nativo do Projects depende dele.ℹ️
git push origin --delete <branch>é bloqueado pelo hook local de proteção de branch quando omainestá em checkout (falso positivo — apagar a ref de outra branch não escreve nomain). Use a API dogh, como acima.
Lançando¶
Os releases são dirigidos por tag quando o projeto está conectado a um remoto no GitHub:
- A versão é a tag git (via
poetry-dynamic-versioning); opyproject.tomlguarda um placeholder0.0.0. Não edite à mão. Dispare um release pela aba Actions (Release to PyPI/Release to Test PyPI,workflow_dispatchcom a versão) ou empurrando uma tagvX.Y.Z. - O workflow de release roda a suíte completa de testes como gate rígido, constrói com
python -m builde publica via trusted publishing (OIDC) (pypa/gh-action-pypi-publish) — semPYPI_TOKENarmazenado. - O changelog é regenerado a partir das tags no momento do build (
make changeloglocalmente); a CI nunca faz commit deCHANGELOG.mdde volta na branch padrão protegida.
Configuração do mantenedor — trusted publisher (uma vez, antes do primeiro release)¶
Registre um trusted publisher tanto em pypi.org quanto em
test.pypi.org. Cada claim precisa bater exatamente com o workflow, ou o
upload falha com um invalid-publisher opaco:
| Claim | Valor |
|---|---|
| Owner / repository | seu <owner> / <repo> no GitHub |
| Workflow filename | release-pypi.yaml (PyPI) / release-test-pypi.yaml (Test PyPI) |
| Environment | release-pypi / release-test-pypi |
| PyPI Project Name | deve ser igual ao nome de distribuição (name no pyproject.toml) |
Para o primeiro upload o projeto ainda não existe — registre um pending publisher no nível da conta (não dentro das configurações de um projeto existente). Publicar de um laptop em vez da CI é o único caso que ainda precisa de um API token; OIDC só funciona a partir do GitHub Actions.