convertaja 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Convertajá
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,234 @@
1
+ Metadata-Version: 2.4
2
+ Name: convertaja
3
+ Version: 0.1.0
4
+ Summary: SDK oficial da API do Convertajá: PDF, DANFE, etiquetas ZPL, assinatura eletrônica, CNPJ, CEP, boleto e Pix.
5
+ Author-email: Convertajá <contato@convertaja.com.br>
6
+ License: MIT
7
+ Project-URL: Homepage, https://convertaja.com.br/api
8
+ Project-URL: Documentation, https://convertaja.com.br/api/docs
9
+ Keywords: convertaja,pdf,danfe,nfe,zpl,cnpj,cep,boleto,pix,assinatura eletronica,api,sdk
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3 :: Only
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Natural Language :: Portuguese (Brazilian)
15
+ Classifier: Typing :: Typed
16
+ Requires-Python: >=3.9
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Dynamic: license-file
20
+
21
+ # convertaja (Python)
22
+
23
+ SDK oficial da [API do Convertajá](https://convertaja.com.br/api) para Python: comprimir e converter PDF, gerar DANFE a partir do XML da NF-e, converter etiquetas ZPL, assinatura eletrônica, Central de Expedição e consultas de CNPJ, CEP, boleto e Pix.
24
+
25
+ - Só biblioteca padrão (`urllib`), sem `requests`. Python 3.9 ou mais novo, com type hints (`py.typed`).
26
+ - Repete sozinho em falha de rede, 429 (respeitando `Retry-After`) e 5xx, **sem cobrar duas vezes** (Idempotency-Key automática).
27
+
28
+ > **Ainda não publicado no PyPI.** Até a publicação, instale pelo caminho local ou pelo repositório git (veja abaixo).
29
+
30
+ ## Instalação
31
+
32
+ ```bash
33
+ # caminho local
34
+ pip install ./caminho/para/integrations/sdk-python
35
+
36
+ # ou direto do git (quando o repositório estiver disponível)
37
+ pip install "git+https://github.com/convertaja/sdk-python.git"
38
+
39
+ # depois da publicação
40
+ pip install convertaja
41
+ ```
42
+
43
+ ## Primeiros passos
44
+
45
+ Crie a chave em **Minha conta → API** (`/conta/api`). Use `cj_test_…` para testar (não consome créditos, até 20 chamadas por dia) e `cj_live_…` em produção.
46
+
47
+ ```python
48
+ import os
49
+ from convertaja import Convertaja
50
+
51
+ cj = Convertaja(api_key=os.environ["CONVERTAJA_API_KEY"]) # ou só Convertaja() lendo CONVERTAJA_API_KEY
52
+
53
+ me = cj.me()
54
+ print(me["plan"]["label"], me["credits"]["remaining"])
55
+ print("Créditos depois da última chamada:", cj.credits_remaining)
56
+ ```
57
+
58
+ Opções: `base_url` (padrão `https://convertaja.com.br`), `max_retries` (padrão 2), `timeout` em segundos (padrão 120), `retry_delay` (padrão 0,5 s).
59
+
60
+ ### Comprimir um PDF
61
+
62
+ ```python
63
+ # wait=True segura a resposta até 60 s; se ainda não terminou, use jobs.wait
64
+ job = cj.tools.run(
65
+ "comprimir-pdf",
66
+ files=["contrato.pdf"], # caminho, bytes, arquivo aberto, {"data", "name"}, {"url"} ou {"base64", "name"}
67
+ options={"level": "high"}, # low | medium | high
68
+ wait=True,
69
+ )
70
+ if job["status"] not in ("done", "error"):
71
+ job = cj.jobs.wait(job["id"])
72
+ if job["status"] == "error":
73
+ raise RuntimeError(job["error"]["message"])
74
+
75
+ arquivo = cj.jobs.download(job["id"]) # um arquivo, ou ZIP se houver vários
76
+ arquivo.save(arquivo.filename or "comprimido.pdf")
77
+ ```
78
+
79
+ ### Consultar CNPJ e CEP (grátis)
80
+
81
+ ```python
82
+ empresa = cj.lookups.cnpj("11.222.333/0001-81") # aceita o CNPJ alfanumérico
83
+ print(empresa["razaoSocial"], empresa["situacao"])
84
+
85
+ endereco = cj.lookups.cep("01310-100")
86
+ print(endereco["logradouro"], endereco["cidade"], endereco["uf"])
87
+ ```
88
+
89
+ ### DANFE a partir do XML da NF-e
90
+
91
+ ```python
92
+ job = cj.tools.run(
93
+ "xml-nfe-para-pdf",
94
+ files=["nota.xml"], # vários XMLs ou um ZIP
95
+ options={"layout": "a4"}, # a4 | simplificado (10×15, etiqueta)
96
+ wait=True,
97
+ )
98
+ cj.jobs.download(job["id"]).save("danfe.pdf")
99
+ ```
100
+
101
+ ### ZPL para PDF
102
+
103
+ ```python
104
+ # código ZPL direto nas opções (sem arquivo)...
105
+ job = cj.tools.run(
106
+ "zpl-para-pdf",
107
+ options={"zpl": "^XA^FO50,50^A0N,40,40^FDOlá^FS^XZ", "width_mm": 100, "height_mm": 150, "dpmm": "8"},
108
+ wait=True,
109
+ )
110
+ # ...ou arquivos .zpl/.txt
111
+ job = cj.tools.run("zpl-para-pdf", files=["etiquetas.zpl"], wait=True)
112
+ cj.jobs.download(job["id"]).save("etiquetas.pdf")
113
+ ```
114
+
115
+ Liste as ferramentas, extensões aceitas e opções (JSON Schema) com `cj.tools.list()` ou `cj.tools.get("comprimir-pdf")`.
116
+
117
+ ### Assinatura eletrônica (envelope)
118
+
119
+ ```python
120
+ envelope = cj.esign.envelopes.create(
121
+ "contrato.pdf", # ou {"url": "https://…/contrato.pdf"}
122
+ signers=[
123
+ {"name": "Ana Souza", "email": "ana@cliente.com.br", "role": "sign"},
124
+ {"name": "Bruno Lima", "email": "bruno@empresa.com.br", "role": "witness"},
125
+ ],
126
+ fields=[
127
+ {"signer": 0, "type": "signature", "page": 0, "x": 0.1, "y": 0.75, "w": 0.3, "h": 0.07},
128
+ {"signer": 1, "type": "signature", "page": 0, "x": 0.55, "y": 0.75, "w": 0.3, "h": 0.07},
129
+ ],
130
+ name="Contrato de prestação de serviços",
131
+ message="Olá! Segue o contrato para assinatura.",
132
+ webhook_url="https://seusite.com.br/webhooks/convertaja",
133
+ )
134
+ print(envelope["id"], envelope["status"]) # K7QD-9WZ2-M4HT-R8PA pending
135
+
136
+ # depois que todos assinarem:
137
+ cj.esign.envelopes.download_final(envelope["id"]).save("contrato-assinado.pdf")
138
+ ```
139
+
140
+ Também há `list`, `list_all`, `get(id, events=True, links=True)`, `send` (rascunhos criados com `send=False`), `remind`, `cancel`, `delete` e `download_audit`.
141
+
142
+ ### Outros recursos
143
+
144
+ ```python
145
+ cj.validate("12.ABC.345/01DE-35", type="cnpj")
146
+ cj.validate(values=["123.456.789-09", "11.222.333/0001-81"]) # lote: 1 crédito a cada 50
147
+ cj.boleto.parse("23793.38128 86000.782713 36950.000632 1 10000000012345")
148
+ cj.pix.brcode("contato@empresa.com.br", "Empresa", "SAO PAULO", amount=10.5, format="png")
149
+ cj.qrcode("https://convertaja.com.br", format="svg")
150
+ cj.barcode("ean13", "789123456789", format="png")
151
+
152
+ # Tarefas
153
+ page = cj.jobs.list(status="done", limit=20) # page["next_cursor"] → starting_after
154
+ for job in cj.jobs.list_all(tool="comprimir-pdf"):
155
+ print(job["id"])
156
+ cj.jobs.download_file(job_id, 0).save("saida.pdf")
157
+ cj.jobs.delete(job_id) # apaga os arquivos antes de 1 hora (LGPD)
158
+
159
+ # Central de Expedição: etiquetas + XMLs das notas
160
+ lote = cj.expedicao.lotes.create(["etiquetas.pdf"], ["notas.zip"], intercalar=True, wait=True)
161
+ lote = cj.expedicao.lotes.wait(lote["id"])
162
+ cj.expedicao.lotes.download(lote["id"]).save("expedicao.zip")
163
+
164
+ # Webhook de teste (envia um evento assinado para a sua URL)
165
+ cj.webhooks.test("https://seusite.com.br/webhooks/convertaja")
166
+ ```
167
+
168
+ ## Webhooks: verificação da assinatura
169
+
170
+ Cada aviso chega com `X-Convertaja-Signature: t=<unix>,v1=<hex>`, onde `v1 = HMAC-SHA256(segredo, "<t>.<corpo bruto>")`. O segredo (`whsec_…`) fica em **Minha conta → API**. Verifique sempre com o **corpo bruto** e recuse assinaturas com mais de 300 s.
171
+
172
+ ```python
173
+ import os
174
+ from flask import Flask, request, abort
175
+ from convertaja import webhooks, ConvertajaSignatureError
176
+
177
+ app = Flask(__name__)
178
+
179
+ @app.post("/webhooks/convertaja")
180
+ def convertaja_webhook():
181
+ try:
182
+ event = webhooks.construct_event(
183
+ request.get_data(), # bytes exatos recebidos
184
+ request.headers.get("X-Convertaja-Signature"),
185
+ os.environ["CONVERTAJA_WEBHOOK_SECRET"],
186
+ )
187
+ except ConvertajaSignatureError:
188
+ abort(400)
189
+ if event["type"] == "job.completed":
190
+ tarefa = event["data"] # id, outputs, download_url…
191
+ elif event["type"] == "esign.envelope.completed":
192
+ envelope = event["data"]
193
+ return "", 200 # responda 2xx rápido; o Convertajá tenta até 3 vezes
194
+ ```
195
+
196
+ `webhooks.verify(raw_body, header, secret, tolerance_sec=300)` devolve `True`/`False` se você preferir tratar sozinho. Use o cabeçalho `X-Convertaja-Delivery` para descartar entregas repetidas.
197
+
198
+ ## Erros
199
+
200
+ Toda falha vira `ConvertajaError`:
201
+
202
+ ```python
203
+ from convertaja import ConvertajaError
204
+
205
+ try:
206
+ cj.tools.run("comprimir-pdf", files=["grande.pdf"])
207
+ except ConvertajaError as e:
208
+ print(e.status) # 402
209
+ print(e.code) # "insufficient_credits" (estável, para tratar no código)
210
+ print(e.message) # mensagem em português
211
+ print(e.request_id) # informe ao suporte
212
+ print(e.details) # corpo de erro completo (ex.: credits_required)
213
+ ```
214
+
215
+ Códigos do SDK, além dos da API: `network_error`, `timeout`, `wait_timeout` (em `jobs.wait`), `missing_api_key`, `invalid_signature`. Lista completa em [/api/docs](https://convertaja.com.br/api/docs).
216
+
217
+ ## Novas tentativas e idempotência
218
+
219
+ - Repete até `max_retries` vezes (padrão 2) em falha de rede, tempo esgotado, 408, 429 (exceto `test_limit` e `reminder_too_soon`), 5xx e 409 `idempotency_in_progress`.
220
+ - Em 429 espera o `Retry-After` da API (até 60 s); nos outros casos usa recuo exponencial (0,5 s, 1 s, 2 s… até 8 s).
221
+ - Todo POST leva uma `Idempotency-Key` (UUID) gerada **uma vez por chamada** e repetida em todas as tentativas: se a primeira chegou a ser processada, a repetição devolve a mesma resposta, sem novo débito.
222
+ - Para repetir com segurança entre processos, passe a sua chave: `cj.tools.run("comprimir-pdf", files=[...], idempotency_key=f"pedido-{pedido.id}")`. A mesma chave com outro conteúdo em 24 h dá 409 `idempotency_key_reused`.
223
+ - Depois de cada chamada: `cj.credits_remaining`, `cj.last_rate_limit`, `cj.last_request_id` e `cj.last_response` (`status`, `attempts`, `idempotency_key`, `replayed`).
224
+ - `timeout` vale por operação de rede (conexão e cada leitura), como no `urllib`.
225
+
226
+ ## Testes
227
+
228
+ ```bash
229
+ python3 -I -m unittest discover -s tests -v # servidor HTTP local numa thread; nenhuma chamada sai da máquina
230
+ ```
231
+
232
+ ## Licença
233
+
234
+ MIT
@@ -0,0 +1,214 @@
1
+ # convertaja (Python)
2
+
3
+ SDK oficial da [API do Convertajá](https://convertaja.com.br/api) para Python: comprimir e converter PDF, gerar DANFE a partir do XML da NF-e, converter etiquetas ZPL, assinatura eletrônica, Central de Expedição e consultas de CNPJ, CEP, boleto e Pix.
4
+
5
+ - Só biblioteca padrão (`urllib`), sem `requests`. Python 3.9 ou mais novo, com type hints (`py.typed`).
6
+ - Repete sozinho em falha de rede, 429 (respeitando `Retry-After`) e 5xx, **sem cobrar duas vezes** (Idempotency-Key automática).
7
+
8
+ > **Ainda não publicado no PyPI.** Até a publicação, instale pelo caminho local ou pelo repositório git (veja abaixo).
9
+
10
+ ## Instalação
11
+
12
+ ```bash
13
+ # caminho local
14
+ pip install ./caminho/para/integrations/sdk-python
15
+
16
+ # ou direto do git (quando o repositório estiver disponível)
17
+ pip install "git+https://github.com/convertaja/sdk-python.git"
18
+
19
+ # depois da publicação
20
+ pip install convertaja
21
+ ```
22
+
23
+ ## Primeiros passos
24
+
25
+ Crie a chave em **Minha conta → API** (`/conta/api`). Use `cj_test_…` para testar (não consome créditos, até 20 chamadas por dia) e `cj_live_…` em produção.
26
+
27
+ ```python
28
+ import os
29
+ from convertaja import Convertaja
30
+
31
+ cj = Convertaja(api_key=os.environ["CONVERTAJA_API_KEY"]) # ou só Convertaja() lendo CONVERTAJA_API_KEY
32
+
33
+ me = cj.me()
34
+ print(me["plan"]["label"], me["credits"]["remaining"])
35
+ print("Créditos depois da última chamada:", cj.credits_remaining)
36
+ ```
37
+
38
+ Opções: `base_url` (padrão `https://convertaja.com.br`), `max_retries` (padrão 2), `timeout` em segundos (padrão 120), `retry_delay` (padrão 0,5 s).
39
+
40
+ ### Comprimir um PDF
41
+
42
+ ```python
43
+ # wait=True segura a resposta até 60 s; se ainda não terminou, use jobs.wait
44
+ job = cj.tools.run(
45
+ "comprimir-pdf",
46
+ files=["contrato.pdf"], # caminho, bytes, arquivo aberto, {"data", "name"}, {"url"} ou {"base64", "name"}
47
+ options={"level": "high"}, # low | medium | high
48
+ wait=True,
49
+ )
50
+ if job["status"] not in ("done", "error"):
51
+ job = cj.jobs.wait(job["id"])
52
+ if job["status"] == "error":
53
+ raise RuntimeError(job["error"]["message"])
54
+
55
+ arquivo = cj.jobs.download(job["id"]) # um arquivo, ou ZIP se houver vários
56
+ arquivo.save(arquivo.filename or "comprimido.pdf")
57
+ ```
58
+
59
+ ### Consultar CNPJ e CEP (grátis)
60
+
61
+ ```python
62
+ empresa = cj.lookups.cnpj("11.222.333/0001-81") # aceita o CNPJ alfanumérico
63
+ print(empresa["razaoSocial"], empresa["situacao"])
64
+
65
+ endereco = cj.lookups.cep("01310-100")
66
+ print(endereco["logradouro"], endereco["cidade"], endereco["uf"])
67
+ ```
68
+
69
+ ### DANFE a partir do XML da NF-e
70
+
71
+ ```python
72
+ job = cj.tools.run(
73
+ "xml-nfe-para-pdf",
74
+ files=["nota.xml"], # vários XMLs ou um ZIP
75
+ options={"layout": "a4"}, # a4 | simplificado (10×15, etiqueta)
76
+ wait=True,
77
+ )
78
+ cj.jobs.download(job["id"]).save("danfe.pdf")
79
+ ```
80
+
81
+ ### ZPL para PDF
82
+
83
+ ```python
84
+ # código ZPL direto nas opções (sem arquivo)...
85
+ job = cj.tools.run(
86
+ "zpl-para-pdf",
87
+ options={"zpl": "^XA^FO50,50^A0N,40,40^FDOlá^FS^XZ", "width_mm": 100, "height_mm": 150, "dpmm": "8"},
88
+ wait=True,
89
+ )
90
+ # ...ou arquivos .zpl/.txt
91
+ job = cj.tools.run("zpl-para-pdf", files=["etiquetas.zpl"], wait=True)
92
+ cj.jobs.download(job["id"]).save("etiquetas.pdf")
93
+ ```
94
+
95
+ Liste as ferramentas, extensões aceitas e opções (JSON Schema) com `cj.tools.list()` ou `cj.tools.get("comprimir-pdf")`.
96
+
97
+ ### Assinatura eletrônica (envelope)
98
+
99
+ ```python
100
+ envelope = cj.esign.envelopes.create(
101
+ "contrato.pdf", # ou {"url": "https://…/contrato.pdf"}
102
+ signers=[
103
+ {"name": "Ana Souza", "email": "ana@cliente.com.br", "role": "sign"},
104
+ {"name": "Bruno Lima", "email": "bruno@empresa.com.br", "role": "witness"},
105
+ ],
106
+ fields=[
107
+ {"signer": 0, "type": "signature", "page": 0, "x": 0.1, "y": 0.75, "w": 0.3, "h": 0.07},
108
+ {"signer": 1, "type": "signature", "page": 0, "x": 0.55, "y": 0.75, "w": 0.3, "h": 0.07},
109
+ ],
110
+ name="Contrato de prestação de serviços",
111
+ message="Olá! Segue o contrato para assinatura.",
112
+ webhook_url="https://seusite.com.br/webhooks/convertaja",
113
+ )
114
+ print(envelope["id"], envelope["status"]) # K7QD-9WZ2-M4HT-R8PA pending
115
+
116
+ # depois que todos assinarem:
117
+ cj.esign.envelopes.download_final(envelope["id"]).save("contrato-assinado.pdf")
118
+ ```
119
+
120
+ Também há `list`, `list_all`, `get(id, events=True, links=True)`, `send` (rascunhos criados com `send=False`), `remind`, `cancel`, `delete` e `download_audit`.
121
+
122
+ ### Outros recursos
123
+
124
+ ```python
125
+ cj.validate("12.ABC.345/01DE-35", type="cnpj")
126
+ cj.validate(values=["123.456.789-09", "11.222.333/0001-81"]) # lote: 1 crédito a cada 50
127
+ cj.boleto.parse("23793.38128 86000.782713 36950.000632 1 10000000012345")
128
+ cj.pix.brcode("contato@empresa.com.br", "Empresa", "SAO PAULO", amount=10.5, format="png")
129
+ cj.qrcode("https://convertaja.com.br", format="svg")
130
+ cj.barcode("ean13", "789123456789", format="png")
131
+
132
+ # Tarefas
133
+ page = cj.jobs.list(status="done", limit=20) # page["next_cursor"] → starting_after
134
+ for job in cj.jobs.list_all(tool="comprimir-pdf"):
135
+ print(job["id"])
136
+ cj.jobs.download_file(job_id, 0).save("saida.pdf")
137
+ cj.jobs.delete(job_id) # apaga os arquivos antes de 1 hora (LGPD)
138
+
139
+ # Central de Expedição: etiquetas + XMLs das notas
140
+ lote = cj.expedicao.lotes.create(["etiquetas.pdf"], ["notas.zip"], intercalar=True, wait=True)
141
+ lote = cj.expedicao.lotes.wait(lote["id"])
142
+ cj.expedicao.lotes.download(lote["id"]).save("expedicao.zip")
143
+
144
+ # Webhook de teste (envia um evento assinado para a sua URL)
145
+ cj.webhooks.test("https://seusite.com.br/webhooks/convertaja")
146
+ ```
147
+
148
+ ## Webhooks: verificação da assinatura
149
+
150
+ Cada aviso chega com `X-Convertaja-Signature: t=<unix>,v1=<hex>`, onde `v1 = HMAC-SHA256(segredo, "<t>.<corpo bruto>")`. O segredo (`whsec_…`) fica em **Minha conta → API**. Verifique sempre com o **corpo bruto** e recuse assinaturas com mais de 300 s.
151
+
152
+ ```python
153
+ import os
154
+ from flask import Flask, request, abort
155
+ from convertaja import webhooks, ConvertajaSignatureError
156
+
157
+ app = Flask(__name__)
158
+
159
+ @app.post("/webhooks/convertaja")
160
+ def convertaja_webhook():
161
+ try:
162
+ event = webhooks.construct_event(
163
+ request.get_data(), # bytes exatos recebidos
164
+ request.headers.get("X-Convertaja-Signature"),
165
+ os.environ["CONVERTAJA_WEBHOOK_SECRET"],
166
+ )
167
+ except ConvertajaSignatureError:
168
+ abort(400)
169
+ if event["type"] == "job.completed":
170
+ tarefa = event["data"] # id, outputs, download_url…
171
+ elif event["type"] == "esign.envelope.completed":
172
+ envelope = event["data"]
173
+ return "", 200 # responda 2xx rápido; o Convertajá tenta até 3 vezes
174
+ ```
175
+
176
+ `webhooks.verify(raw_body, header, secret, tolerance_sec=300)` devolve `True`/`False` se você preferir tratar sozinho. Use o cabeçalho `X-Convertaja-Delivery` para descartar entregas repetidas.
177
+
178
+ ## Erros
179
+
180
+ Toda falha vira `ConvertajaError`:
181
+
182
+ ```python
183
+ from convertaja import ConvertajaError
184
+
185
+ try:
186
+ cj.tools.run("comprimir-pdf", files=["grande.pdf"])
187
+ except ConvertajaError as e:
188
+ print(e.status) # 402
189
+ print(e.code) # "insufficient_credits" (estável, para tratar no código)
190
+ print(e.message) # mensagem em português
191
+ print(e.request_id) # informe ao suporte
192
+ print(e.details) # corpo de erro completo (ex.: credits_required)
193
+ ```
194
+
195
+ Códigos do SDK, além dos da API: `network_error`, `timeout`, `wait_timeout` (em `jobs.wait`), `missing_api_key`, `invalid_signature`. Lista completa em [/api/docs](https://convertaja.com.br/api/docs).
196
+
197
+ ## Novas tentativas e idempotência
198
+
199
+ - Repete até `max_retries` vezes (padrão 2) em falha de rede, tempo esgotado, 408, 429 (exceto `test_limit` e `reminder_too_soon`), 5xx e 409 `idempotency_in_progress`.
200
+ - Em 429 espera o `Retry-After` da API (até 60 s); nos outros casos usa recuo exponencial (0,5 s, 1 s, 2 s… até 8 s).
201
+ - Todo POST leva uma `Idempotency-Key` (UUID) gerada **uma vez por chamada** e repetida em todas as tentativas: se a primeira chegou a ser processada, a repetição devolve a mesma resposta, sem novo débito.
202
+ - Para repetir com segurança entre processos, passe a sua chave: `cj.tools.run("comprimir-pdf", files=[...], idempotency_key=f"pedido-{pedido.id}")`. A mesma chave com outro conteúdo em 24 h dá 409 `idempotency_key_reused`.
203
+ - Depois de cada chamada: `cj.credits_remaining`, `cj.last_rate_limit`, `cj.last_request_id` e `cj.last_response` (`status`, `attempts`, `idempotency_key`, `replayed`).
204
+ - `timeout` vale por operação de rede (conexão e cada leitura), como no `urllib`.
205
+
206
+ ## Testes
207
+
208
+ ```bash
209
+ python3 -I -m unittest discover -s tests -v # servidor HTTP local numa thread; nenhuma chamada sai da máquina
210
+ ```
211
+
212
+ ## Licença
213
+
214
+ MIT
@@ -0,0 +1,35 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "convertaja"
7
+ version = "0.1.0"
8
+ description = "SDK oficial da API do Convertajá: PDF, DANFE, etiquetas ZPL, assinatura eletrônica, CNPJ, CEP, boleto e Pix."
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ authors = [{ name = "Convertajá", email = "contato@convertaja.com.br" }]
12
+ requires-python = ">=3.9"
13
+ dependencies = []
14
+ keywords = ["convertaja", "pdf", "danfe", "nfe", "zpl", "cnpj", "cep", "boleto", "pix", "assinatura eletronica", "api", "sdk"]
15
+ classifiers = [
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3 :: Only",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Operating System :: OS Independent",
20
+ "Natural Language :: Portuguese (Brazilian)",
21
+ "Typing :: Typed",
22
+ ]
23
+
24
+ [project.urls]
25
+ Homepage = "https://convertaja.com.br/api"
26
+ Documentation = "https://convertaja.com.br/api/docs"
27
+
28
+ [tool.setuptools]
29
+ package-dir = { "" = "src" }
30
+
31
+ [tool.setuptools.packages.find]
32
+ where = ["src"]
33
+
34
+ [tool.setuptools.package-data]
35
+ convertaja = ["py.typed"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,23 @@
1
+ """SDK oficial da API do Convertajá (https://convertaja.com.br/api).
2
+
3
+ >>> import convertaja
4
+ >>> client = convertaja.Convertaja(api_key="cj_live_...")
5
+ >>> client.lookups.cnpj("11.222.333/0001-81")["razaoSocial"]
6
+ """
7
+ from . import webhooks
8
+ from .client import DEFAULT_BASE_URL, BinaryFile, Convertaja, LastResponse, __version__
9
+ from .errors import ConvertajaError, ConvertajaSignatureError
10
+
11
+ Client = Convertaja
12
+
13
+ __all__ = [
14
+ "Convertaja",
15
+ "Client",
16
+ "ConvertajaError",
17
+ "ConvertajaSignatureError",
18
+ "BinaryFile",
19
+ "LastResponse",
20
+ "webhooks",
21
+ "DEFAULT_BASE_URL",
22
+ "__version__",
23
+ ]