Sincronizador de documentos para webservices SOAP legados, com autenticação WS-Security UsernameToken / PasswordDigest, cache local e resolução de titular em cadeia.
Nasceu de um problema real e chato: um sistema corporativo antigo que só aceita upload por SOAP, exige um par de códigos internos que o nome do documento não traz, e responde devagar. A parte interessante não é o upload — é como descobrir esses códigos sem transformar cada lote em trabalho manual.
Chega uma pasta com centenas de PDFs nomeados assim:
ATESTADO 140422 MARIA DAS GRACAS.pdf
EXAME 030525 JOAO PEREIRA DA SILVA.pdf
O webservice não aceita "MARIA DAS GRACAS". Ele quer codigoTitular e
codigoUnidade — dois números que só existem no cadastro do sistema. Descobrir
esses números é a maior parte do trabalho.
Ler o nome do arquivo, nunca o conteúdo. Um lote de mil documentos é classificado em milissegundos. Só o que não casa com o padrão paga o custo de abrir o PDF.
Resolver o titular em cadeia, do barato para o caro. Quatro estratégias com o mesmo protocolo; a primeira que responder vence:
| # | Estratégia | Custo | Cobertura |
|---|---|---|---|
| 1 | Cache SQLite local | microssegundos, offline | o que já foi visto antes |
| 2 | Consulta REST | ~200 ms | parcial |
| 3 | Consulta SOAP | segundos | completa |
| 4 | Pergunta ao operador | humano | tudo, mas não escala |
O detalhe que faz a diferença: todo acerto vindo de fora é gravado no cache. A estratégia 4 alimenta a 1. Na primeira execução quase tudo cai na consulta remota ou no operador; no décimo lote a maioria resolve offline, e a intervenção humana vira exceção em vez de rotina.
Nunca escrever por acidente. MODO_SIMULACAO vem ligado por padrão. A
ferramenta faz o percurso completo, loga o que faria, e não envia nada. Quem
liga o envio é uma flag explícita (--enviar). Existe ainda UNIDADE_PERMITIDA,
uma trava que recusa qualquer upload fora de uma unidade específica — útil para
homologar em produção sem risco de espalhar documento errado.
Cada arquivo termina em exatamente uma pasta. O destino conta o que houve:
_resultado/
├── processados/ enviado com sucesso
├── sem_padrao/ nome fora da convenção
├── titular_nao_resolvido/ nome lido, sem correspondência
├── erros/ falhou o envio após as tentativas
└── log_envio.csv uma linha por arquivo
Reprocessar é apontar a ferramenta para erros/ depois de corrigir a causa.
A parte que costuma travar integrações com SOAP corporativo. A senha não trafega em texto claro:
Digest = Base64( SHA1( nonce + created + senha ) )
Três armadilhas, todas cobertas por teste em tests/test_wsse.py:
- o
nonceentra na concatenação como bytes crus, não como a string Base64 que aparece no XML — trocar isso gera um digest bem formado que o servidor rejeita com 401, sem pista nenhuma no log; - o
createdprecisa ser UTC em ISO 8601 com sufixoZ; - a ordem
nonce + created + senhaé fixa.
Como o nonce muda a cada requisição, o digest também muda — o que impede replay de uma chamada capturada.
O zeep tem suporte nativo a UsernameToken, mas servidores legados costumam ser
rígidos quanto à ordem dos elementos e ao mustUnderstand. Por isso o cabeçalho
é montado à mão em src/wsse.py.
src/
├── wsse.py assinatura WS-Security (PasswordDigest)
├── config.py configuração via ambiente, sem segredo em código
├── nomes_arquivo.py leitura de "TIPO DDMMAA NOME"
├── cache.py cache SQLite de titulares
├── resolvedor.py cadeia de estratégias
├── cliente_soap.py zeep + retry + modo simulação
├── pipeline.py orquestração do lote e log CSV
└── cli.py linha de comando
tests/ 36 testes, sem rede
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt -r requirements-dev.txt
cp .env.example .env # e preencha
pytest -q # 36 testes, nenhuma credencial necessária
python -m src.cli ./pdfs # simulação: não envia nada
python -m src.cli ./pdfs --enviarOs testes rodam sem .env e sem rede: toda I/O externa entra por injeção de
dependência, então a lógica é testada com funções falsas em vez de mocks de
biblioteca.
Tudo por variável de ambiente. .env está no .gitignore; o .env.example
documenta as chaves sem conter valor.
| Variável | Para quê |
|---|---|
WSDL_UPLOAD |
endereço do WSDL de upload |
WSSE_USUARIO / WSSE_SENHA |
credenciais WS-Security |
CONSULTA_URL / CONSULTA_CODIGO / CONSULTA_CHAVE |
endpoint REST opcional |
MODO_SIMULACAO |
true (padrão) não envia nada |
UNIDADE_PERMITIDA |
trava: recusa upload fora desta unidade |
TIMEOUT_S / TENTATIVAS |
comportamento de rede |
Config.resumo_seguro() é o que vai para o log — senha e chave saem
mascaradas.
Reimplementação genérica e independente de uma automação que escrevi no trabalho. Não contém código, credencial, endpoint, nome de empresa nem dado de pessoa do sistema original: o domínio aqui é neutro ("titulares" e "unidades") e o código foi escrito do zero para publicação. Serve como amostra de como eu estruturo uma integração — separação de responsabilidades, configuração fora do código, falha explícita e teste sem rede.
MIT.
In English — Batch uploader for legacy SOAP webservices. Implements WS-Security UsernameToken with PasswordDigest, a four-step owner-resolution chain (SQLite cache → REST → SOAP → human fallback) where every external hit feeds the cache, dry-run by default, per-unit safety lock, and per-file outcome folders with a CSV audit log. 36 tests, no network required.