Repository navigation
feat: SDK oficial NFE.io para Python v0.1.0 (nfe-io) - #1
Merged
Merged
Conversation
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
…nos testes ao vivo)
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.
…ownload-artifact v8, pypi-publish v1.14)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Resumo
Primeira versão (v0.1.0) do SDK oficial da NFE.io para Python: pacote
nfe-iono PyPI, importnfeio. Substitui o README vazio de 2015.py.typed,mypy --strict).NfeClient(sync) eAsyncNfeClient(async viaasyncio.to_thread) com assinaturas idênticas, sobre um núcleo compartilhado.openspec/changes/add-sdk-foundation-v0-1/edocs/contrato/.Escopo do v0.1
external_id, cancelar, reenviar e-mail, baixar PDF/XML,wait/create_and_wait/cancel_and_wait.deletedesativa (status == "Inactive"), como a API faz.file), status.verify_signature(HMAC-SHA1, tempo constante, nunca lança),construct_event.client.lookups): CNPJ v3 (pronto para CNPJ alfanumérico), inscrições estaduais, CPF, CEP.Segurança e confiabilidade
outcome_unknown=Truee a receita de reconciliação porexternal_idestá no README.Authorization; TLS ≥ 1.2; timeouts finitos; resposta limitada a 10 MiB; IDs validados antes de entrar no path; segredos e documentos mascarados emrepr/logs/erros.docs/contrato/revisao-seguranca-v0.1.md.Evidência
twine check; wheel instalado e testado em venv limpo.docs/contrato/probe-2026-10-08.mdedocs/contrato/probe-fase2.md, com evidência redigida emtests/fixtures/live-contracts/.Antes de publicar
nfe/client-python, workflowrelease.yml, environmentpypi) e no repo o environmentpypicom aprovação