Skip to content

feat: SDK oficial NFE.io para Python v0.1.0 (nfe-io) - #1

Merged
andrenfe merged 17 commits into
masterfrom
feat/sdk-foundation
Oct 9, 2026
Merged

andrenfe merged 17 commits into
masterfrom
feat/sdk-foundation

Conversation

@andrenfe

@andrenfe andrenfe commented Oct 9, 2026

Copy link
Copy Markdown
Member

Resumo

Primeira versão (v0.1.0) do SDK oficial da NFE.io para Python: pacote nfe-io no PyPI, import nfeio. Substitui o README vazio de 2015.

  • Zero dependências de runtime (só stdlib), Python 3.10–3.14, tipado (py.typed, mypy --strict).
  • NfeClient (sync) e AsyncNfeClient (async via asyncio.to_thread) com assinaturas idênticas, sobre um núcleo compartilhado.
  • Desenho e decisões em openspec/changes/add-sdk-foundation-v0-1/ e docs/contrato/.

Escopo do v0.1

  • NFS-e: emitir, listar, consultar, buscar por external_id, cancelar, reenviar e-mail, baixar PDF/XML, wait/create_and_wait/cancel_and_wait.
  • Empresas (v2, cursor): CRUD. delete desativa (status == "Inactive"), como a API faz.
  • Certificados: upload multipart (campo file), status.
  • Webhooks: CRUD, verify_signature (HMAC-SHA1, tempo constante, nunca lança), construct_event.
  • Consultas (client.lookups): CNPJ v3 (pronto para CNPJ alfanumérico), inscrições estaduais, CPF, CEP.

Segurança e confiabilidade

  • Retry ciente de método e de fase: POST de emissão nunca é repetido depois de poder ter chegado à API (lição do incidente 504). Erros incertos saem com outcome_unknown=True e a receita de reconciliação por external_id está no README.
  • Redirect de PDF/XML seguido sem reenviar Authorization; TLS ≥ 1.2; timeouts finitos; resposta limitada a 10 MiB; IDs validados antes de entrar no path; segredos e documentos mascarados em repr/logs/erros.
  • Revisão de segurança: docs/contrato/revisao-seguranca-v0.1.md.

Evidência

  • 388 testes unitários sem rede, cobertura de 97%; ruff, mypy --strict, bandit, pip-audit, twine check; wheel instalado e testado em venv limpo.
  • Sondas na API real (leitura e escrita controlada em homologação): docs/contrato/probe-2026-10-08.md e docs/contrato/probe-fase2.md, com evidência redigida em tests/fixtures/live-contracts/.

Antes de publicar

  • CI verde neste PR
  • Configurar no PyPI o Trusted Publisher (nfe/client-python, workflow release.yml, environment pypi) e no repo o environment pypi com aprovação
  • Ativar Private Vulnerability Reporting e CodeQL no repo
  • Dry-run no TestPyPI
  • Renovar o certificado da empresa de teste (vence em 03/11/2026)

Proposta, design, specs delta e tasks da change add-sdk-foundation-v0-1,
sonda de leitura de 2026-10-08 e registro de decisões. Desenho alinhado ao
registro: D1 'D enxuta' (Mapping imutável, propriedades só onde tipam),
auth Authorization sem prefixo, companies v2, CNPJ v3, lookups agrupados.
Pacote nfe-io importado como nfeio, Python >= 3.10, sem dependências de
runtime, versão única em src/nfeio/_version.py, py.typed, licença MIT e
SECURITY.md com reporte via GitHub Private Vulnerability Reporting.
…ipados

- operações como geradores de efeitos, com drivers sync e async
- transporte sobre http.client: fase da falha, pool LIFO, POST sempre em
  conexão nova, TLS >= 1.2 verificado, timeouts finitos, limite de resposta
- retry ciente de método e fase; POST nunca é repetido após possível envio
- Retry-After em segundos ou data HTTP, com teto
- hierarquia de erros com request_id, trace_id e outcome_unknown
- extração de mensagem dos 5 envelopes de erro observados na API
- validação de ids, externalId, CNPJ alfanumérico, CPF, CEP e UF em path
- JSON com Decimal exato e multipart sem dependências
- configuração imutável com segredos mascarados
- NfeObject: Mapping imutável do fio com propriedades tipadas enxutas (D1)
- NFS-e: emitir, listar, consultar, buscar por externalId, cancelar,
  reenviar e-mail, baixar PDF/XML (redirect sem credencial), wait,
  create_and_wait e cancel_and_wait
- empresas v2 com cursor e CRUD; certificados com upload multipart
- webhooks da conta (CRUD, ping, tipos de evento) e verify_signature /
  construct_event em tempo constante
- consultas de CNPJ v3, inscrições estaduais, CPF e CEP com chave de dados
- paginação offset 1-based e cursor, iteração sync e async
- transporte e relógio falsos; mesmos roteiros nos clientes sync e async
- tabela completa de retry por método, status e fase
- envelopes de erro reais da sonda de 2026-10-08 (redigidos)
- vetores reais de assinatura de webhook e casos negativos
- transporte stdlib contra sockets locais (timeouts, limite, TLS)
- paridade de assinaturas sync x async
- alinhamento de propriedades e TypedDicts com a OpenAPI (snapshot)
- varredura de vazamento de chave, senha e dados pessoais em logs/repr

Inclui correções do núcleo achadas na revisão: validadores com
fullmatch (rejeitam quebra de linha final) e paths mascarados em logs
e repr (sem CPF, data de nascimento, CNPJ ou externalId).
- opt-in por NFE_RUN_INTEGRATION/NFE_LIVE_WRITE ou --run-integration e
  --live-write; .env lido com parser stdlib; trava de escrita de NFS-e
  na NFE_COMPANY_ID; limpeza em finally inclusive após falha ambígua
- leitura: auth por família, varredura v2, NFS-e, downloads, certificados,
  webhooks, CNPJ v3, CEP, CPF e cliente async
- escrita: 2 NFS-e emitidas e canceladas, 1 empresa descartável (soft
  delete), CRUD de webhook de teste
- fixtures redigidas em tests/fixtures/live-contracts e teste de replay

docs(companies): DELETE da v2 é soft delete (empresa fica Inactive).
…ublishing

- CI: ruff, format, mypy --strict, testes 3.10–3.14 (Linux) e 3.12
  (macOS, Windows), mínimos de cobertura, build, twine check, wheel sem
  Requires-Dist e smoke test em venv limpo
- segurança: bandit, pip-audit sobre o lock, verificação dos pins de
  actions contra as tags
- release por tag: build único, environment pypi protegido, OIDC e
  attestations PEP 740
- dependabot só para actions e dependências de dev
- permissions vazias por padrão; uv instalado por pipx com versão fixa
- sdist com includes ancorados; assert removido do código do pacote
README com instalação, chaves por família, início rápido, receita de
reconciliação por externalId, paginação, empresas, certificados,
consultas, webhooks (Flask, Django e FastAPI), cliente async, erros e
configuração. Exemplos verificados por teste contra o transporte falso.
- find_by_external_id só conclui 'não existe' com lista explícita vazia;
  corpo vazio, forma desconhecida ou redirect viram UnexpectedResponseError
  (a reconciliação nunca lê lixo como 'seguro reemitir')
- 3xx fora de download é erro explícito; só downloads seguem redirect
- RecursionError de JSON muito aninhado fica dentro da hierarquia NfeError
- auto_paging_iter detecta paginação que não avança (cursor ou página
  repetidos) em vez de entrar em laço infinito
…rança

- probe-fase2.md: escritas executadas e resultado provado x inferência
  (emissão e cancelamento 202 + Location, soft delete de empresa v2,
  verificação de URI na criação de webhook, upload v2 com arquivo falso)
- DECISOES-FASE2.md: decisões de API pública tomadas de forma conservadora
  para confirmação
- revisao-seguranca-v0.1.md: checklist do design §11 item a item e
  correções feitas na revisão
- design, specs delta e tasks atualizados com os fatos da sonda; README
  com aviso sobre cancelamento de task async durante emissão
A proteção contra JSON muito aninhado dependia do limite de recursão do
interpretador. No Python 3.14 o json.loads decodifica 100 mil níveis sem
RecursionError, e test_deeply_nested_json_stays_inside_the_hierarchy
falhava só nessa versão.

jsonutil.loads passa a ser o decodificador único de JSON não confiável
(respostas, corpos de erro e webhooks) e recusa mais de MAX_JSON_DEPTH = 128
níveis com ValueError antes do json.loads. A verificação é linear: remove as
strings (respeitando aspas escapadas; string sem fechamento consome o resto
do texto, sem reescanear) e varre só [ ] { }. errors.py, webhooks.py e
error_mapping.py passam a usá-lo; RecursionError continua no except como
defesa extra.
… jobs

No Windows o job de testes travou: test_certificate_local_validation é
parametrizado com b"x" * (1024 * 1024 + 1), o pytest usa o valor no id do
teste e grava o id em PYTEST_CURRENT_TEST, e o Windows recusa variável de
ambiente com mais de 32.767 caracteres (ValueError no setup/teardown).

Essa parametrização e as de test_opaque_id_rejects e test_free_text_rejects
(strings de 65 e 256 caracteres) ganham ids= curtos e legíveis; o maior id
coletado agora tem 101 caracteres.

Todos os jobs do ci.yml ganham timeout-minutes: 15 e os do security.yml,
timeout-minutes: 10, para que uma trava falhe rápido em vez de consumir o
limite padrão de 6 horas.
…tHub)

O upload do SARIF do codeql.yml falhava com "configurations cannot be
processed when the default setup is enabled": o repositório já roda o
CodeQL pelo default setup do GitHub, que não convive com a configuração
avançada.

Remove .github/workflows/codeql.yml e ajusta CONTRIBUTING,
docs/contrato/DECISOES-FASE2.md (F17/F18) e o design, as tasks e a spec
packaging-release do openspec para dizer que o CodeQL roda pelo default
setup do repositório. README e SECURITY não citavam o workflow.
@andrenfe
andrenfe merged commit 3dc6dd6 into master Oct 9, 2026
12 checks passed
@andrenfe
andrenfe deleted the feat/sdk-foundation branch October 9, 2026 04:01
@andrenfe andrenfe self-assigned this Oct 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant