corvo-nfse 0.1.1__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.
Files changed (43) hide show
  1. corvo_nfse-0.1.1/LICENSE +21 -0
  2. corvo_nfse-0.1.1/PKG-INFO +235 -0
  3. corvo_nfse-0.1.1/README.md +204 -0
  4. corvo_nfse-0.1.1/pyproject.toml +54 -0
  5. corvo_nfse-0.1.1/setup.cfg +4 -0
  6. corvo_nfse-0.1.1/src/corvo_nfse/__init__.py +90 -0
  7. corvo_nfse-0.1.1/src/corvo_nfse/client.py +98 -0
  8. corvo_nfse-0.1.1/src/corvo_nfse/error.py +41 -0
  9. corvo_nfse-0.1.1/src/corvo_nfse/py.typed +0 -0
  10. corvo_nfse-0.1.1/src/corvo_nfse/resources/__init__.py +24 -0
  11. corvo_nfse-0.1.1/src/corvo_nfse/resources/billing.py +18 -0
  12. corvo_nfse-0.1.1/src/corvo_nfse/resources/certificates.py +43 -0
  13. corvo_nfse-0.1.1/src/corvo_nfse/resources/companies.py +45 -0
  14. corvo_nfse-0.1.1/src/corvo_nfse/resources/credits.py +42 -0
  15. corvo_nfse-0.1.1/src/corvo_nfse/resources/health.py +21 -0
  16. corvo_nfse-0.1.1/src/corvo_nfse/resources/invoices.py +147 -0
  17. corvo_nfse-0.1.1/src/corvo_nfse/resources/plans.py +31 -0
  18. corvo_nfse-0.1.1/src/corvo_nfse/resources/webhooks.py +111 -0
  19. corvo_nfse-0.1.1/src/corvo_nfse/transport.py +335 -0
  20. corvo_nfse-0.1.1/src/corvo_nfse/types/__init__.py +94 -0
  21. corvo_nfse-0.1.1/src/corvo_nfse/types/billing.py +67 -0
  22. corvo_nfse-0.1.1/src/corvo_nfse/types/certificate.py +22 -0
  23. corvo_nfse-0.1.1/src/corvo_nfse/types/common.py +18 -0
  24. corvo_nfse-0.1.1/src/corvo_nfse/types/company.py +88 -0
  25. corvo_nfse-0.1.1/src/corvo_nfse/types/credits.py +85 -0
  26. corvo_nfse-0.1.1/src/corvo_nfse/types/invoice.py +191 -0
  27. corvo_nfse-0.1.1/src/corvo_nfse/types/webhook.py +81 -0
  28. corvo_nfse-0.1.1/src/corvo_nfse/webhooks.py +34 -0
  29. corvo_nfse-0.1.1/src/corvo_nfse.egg-info/PKG-INFO +235 -0
  30. corvo_nfse-0.1.1/src/corvo_nfse.egg-info/SOURCES.txt +41 -0
  31. corvo_nfse-0.1.1/src/corvo_nfse.egg-info/dependency_links.txt +1 -0
  32. corvo_nfse-0.1.1/src/corvo_nfse.egg-info/requires.txt +7 -0
  33. corvo_nfse-0.1.1/src/corvo_nfse.egg-info/top_level.txt +1 -0
  34. corvo_nfse-0.1.1/test/test_calculate_plans.py +103 -0
  35. corvo_nfse-0.1.1/test/test_certificates.py +78 -0
  36. corvo_nfse-0.1.1/test/test_client.py +94 -0
  37. corvo_nfse-0.1.1/test/test_companies.py +116 -0
  38. corvo_nfse-0.1.1/test/test_error.py +46 -0
  39. corvo_nfse-0.1.1/test/test_invoices.py +236 -0
  40. corvo_nfse-0.1.1/test/test_smoke.py +6 -0
  41. corvo_nfse-0.1.1/test/test_transport.py +322 -0
  42. corvo_nfse-0.1.1/test/test_webhooks_resource.py +299 -0
  43. corvo_nfse-0.1.1/test/test_webhooks_verify.py +50 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Corvo Technology
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,235 @@
1
+ Metadata-Version: 2.4
2
+ Name: corvo-nfse
3
+ Version: 0.1.1
4
+ Summary: Python SDK for the Corvo NFS-e API: issue Brazilian service invoices, with result-style errors, cursor pagination and webhook verification.
5
+ Author: Corvo Technology
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/corvo-technology/corvo-nfse-python
8
+ Project-URL: Documentation, https://corvo.tech/en/api-docs
9
+ Project-URL: Issues, https://github.com/corvo-technology/corvo-nfse-python/issues
10
+ Project-URL: Repository, https://github.com/corvo-technology/corvo-nfse-python
11
+ Keywords: nfse,nota fiscal,brazil,fiscal,invoice,corvo
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: httpx>=0.27
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest>=8; extra == "dev"
27
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
28
+ Requires-Dist: respx>=0.21; extra == "dev"
29
+ Requires-Dist: mypy>=1.10; extra == "dev"
30
+ Dynamic: license-file
31
+
32
+ # corvo-nfse
33
+
34
+ **The Python SDK for the [Corvo](https://corvo.tech) NFS-e API.** Register companies, calculate taxes, issue Brazilian service invoices (NFS-e), follow them by webhook and download the PDF and XML, from Python 3.10+.
35
+
36
+ - **Async-first** on `httpx`, fully typed (`py.typed`, `mypy --strict` clean). Library only, no CLI.
37
+ - **Result-style errors.** API, network and timeout failures return `(None, error)`: a `CorvoApiError` carrying the server's stable `code`, the HTTP `status`, the `request_id`, field-level `details` and `retry_after`.
38
+ - **Pagination built in.** Every list returns `({data, pagination}, None)`, and `iterate()` walks all pages for you.
39
+ - **Safe retries.** Issuing an invoice and sending an email accept an idempotency key.
40
+ - **Webhook signature check** in constant time.
41
+
42
+ Full API reference: <https://corvo.tech/en/api-docs>.
43
+
44
+ ## Install
45
+
46
+ ```bash
47
+ pip install corvo-nfse
48
+ ```
49
+
50
+ ## Quickstart
51
+
52
+ Start with a `corvo_test_...` key: invoices issued with it have no fiscal value.
53
+
54
+ ```python
55
+ import asyncio
56
+
57
+ from corvo_nfse import Corvo
58
+
59
+
60
+ async def main() -> None:
61
+ async with Corvo(api_key="corvo_test_...") as corvo: # or set CORVO_API_KEY
62
+ page, error = await corvo.companies.list(limit=10)
63
+ if error is not None:
64
+ # CorvoApiError: code, status, request_id, details, retry_after
65
+ print(error.code, error.status, error.request_id, error.details)
66
+ else:
67
+ print(page["data"], page["pagination"])
68
+
69
+
70
+ asyncio.run(main())
71
+ ```
72
+
73
+ `Corvo(...)` is the only call that raises: it fails fast with `ValueError` when no API key is configured or the base URL is invalid.
74
+
75
+ ## Result style
76
+
77
+ Every API method returns `(data, error)` where exactly one is `None`:
78
+
79
+ ```python
80
+ company, error = await corvo.companies.get("c_1")
81
+ if error is not None:
82
+ handle(error) # error.code, error.status, error.request_id, error.details
83
+ else:
84
+ use(company) # the API's JSON, camelCase as the server sends it
85
+ ```
86
+
87
+ A `204 No Content` (deletions) returns `(None, None)`.
88
+
89
+ The one documented exception: the `iterate()` async generators raise `CorvoApiError` when a page fails, since a generator has no slot for a tuple.
90
+
91
+ ```python
92
+ try:
93
+ async for invoice in corvo.invoices.iterate(companyId=company_id):
94
+ print(invoice["nfseNumber"])
95
+ except CorvoApiError as err:
96
+ print(err.code, err.status)
97
+ ```
98
+
99
+ ## Register a company
100
+
101
+ ```python
102
+ company, error = await corvo.companies.create(
103
+ {
104
+ "legalName": "Empresa Exemplo Ltda",
105
+ "taxId": "11222333000181",
106
+ "taxRegime": "Simples Nacional", # "MEI" | "Simples Nacional" | "Lucro Presumido" | "Lucro Real"
107
+ "municipalityCode": "3550308", # IBGE code, 7 digits
108
+ "defaultServiceCode": "010101", # optional: used when an invoice omits service.code
109
+ }
110
+ )
111
+ ```
112
+
113
+ In `live`, also upload the A1 certificate (multipart is built for you). The company is ready when `company["status"] == "ready"` and `company["setupGaps"]` is empty.
114
+
115
+ ```python
116
+ with open("certificado.pfx", "rb") as fh:
117
+ cert, error = await corvo.certificates.upload(company["id"], fh.read(), password="...")
118
+ ```
119
+
120
+ ## Estimate the taxes before issuing
121
+
122
+ Nothing is issued, stored or charged.
123
+
124
+ ```python
125
+ estimate, error = await corvo.invoices.calculate(
126
+ {
127
+ "companyTaxId": "11222333000181",
128
+ "service": {"description": "Consultoria"},
129
+ "amount": 1500,
130
+ "competenceDate": "2026-10-16",
131
+ }
132
+ )
133
+ # estimate["status"] is "ready" or "blocked": a fiscal gap is data, not an error.
134
+ ```
135
+
136
+ ## Issue an invoice
137
+
138
+ Issuing is asynchronous: a successful call means the request was accepted, not that the invoice is authorized. Follow the status with `invoices.get()` or webhooks.
139
+
140
+ ```python
141
+ invoice, error = await corvo.invoices.create(
142
+ {
143
+ "companyTaxId": "11222333000181", # or "companyId"
144
+ # "customer" is optional; when sent, "taxId" (CPF or CNPJ) is required.
145
+ "customer": {"taxId": "52998224725", "name": "Cliente", "email": "cliente@example.com"},
146
+ "service": {"description": "Consultoria"}, # code and city default to the company's
147
+ "amount": 150.5,
148
+ "competenceDate": "2026-10-16",
149
+ "options": {"email": {"send": True}},
150
+ },
151
+ idempotency_key="order-42", # the same key never issues twice
152
+ )
153
+ ```
154
+
155
+ Use `"options": {"type": "SCHEDULE"}` with a future `competenceDate` to schedule the issuance. Request bodies and responses are wire pass-through: camelCase stays camelCase.
156
+
157
+ ## Walk every page
158
+
159
+ ```python
160
+ async for invoice in corvo.invoices.iterate(companyId=company_id, status="authorized"):
161
+ print(invoice["nfseNumber"])
162
+ ```
163
+
164
+ ## Download the PDF or XML
165
+
166
+ The invoice must be `authorized` and have its documents ready.
167
+
168
+ ```python
169
+ pdf, error = await corvo.invoices.pdf(invoice["id"])
170
+ if pdf is not None:
171
+ with open("nfse.pdf", "wb") as fh:
172
+ fh.write(pdf.content) # also pdf.mime_type and pdf.file_name
173
+ ```
174
+
175
+ ## Verify webhook signatures
176
+
177
+ ```python
178
+ from corvo_nfse import CorvoWebhooks
179
+
180
+
181
+ async def webhook_handler(request) -> None:
182
+ # Use the raw body: re-serialized JSON will never match the HMAC.
183
+ signature = request.headers.get("X-Corvo-Signature")
184
+ if not CorvoWebhooks.verify_signature(await request.body(), signature, secret):
185
+ return # reject with 400
186
+ # Deduplicate by the event id: the same event can be delivered more than once.
187
+ ```
188
+
189
+ Webhook events you can subscribe to are exported as `corvo_nfse.WEBHOOK_EVENTS`. The `webhook.test` event is only sent by `corvo.webhooks.test()`.
190
+
191
+ ## Resources
192
+
193
+ | Namespace | Operations |
194
+ | --- | --- |
195
+ | `corvo.companies` | `create`, `get`, `update`, `remove`, `list`, `iterate` |
196
+ | `corvo.certificates` | `upload`, `list`, `remove` |
197
+ | `corvo.invoices` | `create`, `calculate`, `get`, `list`, `iterate`, `events`, `pdf`, `xml`, `download_url`, `send_email` |
198
+ | `corvo.webhooks` | `create`, `remove`, `test`, `list`, `iterate`, `.deliveries.list`, `.deliveries.iterate`, `.deliveries.stats`, `.deliveries.retry` |
199
+ | `corvo.credits` | `balance`, `plans`, `history`, `iterate_history` |
200
+ | `corvo.billing` | `plan`, `usage` |
201
+ | `corvo.plans` | `list`, `get`, `quote` (public price catalog) |
202
+ | `corvo.health` | `live`, `ready` |
203
+
204
+ Changing plans and the billing portal exist only in the dashboard, so they are not in the SDK.
205
+
206
+ ## Configuration
207
+
208
+ | Option | Env fallback | Default |
209
+ | --- | --- | --- |
210
+ | `api_key` | `CORVO_API_KEY` | required |
211
+ | `base_url` | `CORVO_BASE_URL` | `https://api.corvo.tech` |
212
+ | `prefix` | none | `v1` |
213
+ | `timeout` | none | `30.0` seconds |
214
+ | `allow_insecure_http` | none | `False` (`http://` only for loopback and private hosts) |
215
+ | `http_client` | none | an internal `httpx.AsyncClient` |
216
+
217
+ ## Behavior
218
+
219
+ - **Auth.** Every request sends `X-API-Key`. Keys are `corvo_test_...` or `corvo_live_...` and belong to one account and environment; the key decides the environment.
220
+ - **Responses.** Single resources and list items come back as the API's `data`; lists add `pagination`.
221
+ - **Errors.** Network failures, timeouts, non-2xx responses and malformed JSON all return a `CorvoApiError`. `code` is the API's `ERROR_*` code, falling back to `http_<status>`; transport failures use `network_error`, `timeout` and `invalid_json`. On `429`, `retry_after` holds the seconds to wait.
222
+
223
+ ## Development
224
+
225
+ ```bash
226
+ python -m pip install -e ".[dev]"
227
+ python -m pytest # unit tests; the contract test skips without env
228
+ python -m mypy src
229
+ ```
230
+
231
+ The contract test in `test/contract/` runs only when `CORVO_API_KEY` and `CORVO_BASE_URL` point to a reachable instance.
232
+
233
+ ## License
234
+
235
+ MIT, see [LICENSE](LICENSE).
@@ -0,0 +1,204 @@
1
+ # corvo-nfse
2
+
3
+ **The Python SDK for the [Corvo](https://corvo.tech) NFS-e API.** Register companies, calculate taxes, issue Brazilian service invoices (NFS-e), follow them by webhook and download the PDF and XML, from Python 3.10+.
4
+
5
+ - **Async-first** on `httpx`, fully typed (`py.typed`, `mypy --strict` clean). Library only, no CLI.
6
+ - **Result-style errors.** API, network and timeout failures return `(None, error)`: a `CorvoApiError` carrying the server's stable `code`, the HTTP `status`, the `request_id`, field-level `details` and `retry_after`.
7
+ - **Pagination built in.** Every list returns `({data, pagination}, None)`, and `iterate()` walks all pages for you.
8
+ - **Safe retries.** Issuing an invoice and sending an email accept an idempotency key.
9
+ - **Webhook signature check** in constant time.
10
+
11
+ Full API reference: <https://corvo.tech/en/api-docs>.
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ pip install corvo-nfse
17
+ ```
18
+
19
+ ## Quickstart
20
+
21
+ Start with a `corvo_test_...` key: invoices issued with it have no fiscal value.
22
+
23
+ ```python
24
+ import asyncio
25
+
26
+ from corvo_nfse import Corvo
27
+
28
+
29
+ async def main() -> None:
30
+ async with Corvo(api_key="corvo_test_...") as corvo: # or set CORVO_API_KEY
31
+ page, error = await corvo.companies.list(limit=10)
32
+ if error is not None:
33
+ # CorvoApiError: code, status, request_id, details, retry_after
34
+ print(error.code, error.status, error.request_id, error.details)
35
+ else:
36
+ print(page["data"], page["pagination"])
37
+
38
+
39
+ asyncio.run(main())
40
+ ```
41
+
42
+ `Corvo(...)` is the only call that raises: it fails fast with `ValueError` when no API key is configured or the base URL is invalid.
43
+
44
+ ## Result style
45
+
46
+ Every API method returns `(data, error)` where exactly one is `None`:
47
+
48
+ ```python
49
+ company, error = await corvo.companies.get("c_1")
50
+ if error is not None:
51
+ handle(error) # error.code, error.status, error.request_id, error.details
52
+ else:
53
+ use(company) # the API's JSON, camelCase as the server sends it
54
+ ```
55
+
56
+ A `204 No Content` (deletions) returns `(None, None)`.
57
+
58
+ The one documented exception: the `iterate()` async generators raise `CorvoApiError` when a page fails, since a generator has no slot for a tuple.
59
+
60
+ ```python
61
+ try:
62
+ async for invoice in corvo.invoices.iterate(companyId=company_id):
63
+ print(invoice["nfseNumber"])
64
+ except CorvoApiError as err:
65
+ print(err.code, err.status)
66
+ ```
67
+
68
+ ## Register a company
69
+
70
+ ```python
71
+ company, error = await corvo.companies.create(
72
+ {
73
+ "legalName": "Empresa Exemplo Ltda",
74
+ "taxId": "11222333000181",
75
+ "taxRegime": "Simples Nacional", # "MEI" | "Simples Nacional" | "Lucro Presumido" | "Lucro Real"
76
+ "municipalityCode": "3550308", # IBGE code, 7 digits
77
+ "defaultServiceCode": "010101", # optional: used when an invoice omits service.code
78
+ }
79
+ )
80
+ ```
81
+
82
+ In `live`, also upload the A1 certificate (multipart is built for you). The company is ready when `company["status"] == "ready"` and `company["setupGaps"]` is empty.
83
+
84
+ ```python
85
+ with open("certificado.pfx", "rb") as fh:
86
+ cert, error = await corvo.certificates.upload(company["id"], fh.read(), password="...")
87
+ ```
88
+
89
+ ## Estimate the taxes before issuing
90
+
91
+ Nothing is issued, stored or charged.
92
+
93
+ ```python
94
+ estimate, error = await corvo.invoices.calculate(
95
+ {
96
+ "companyTaxId": "11222333000181",
97
+ "service": {"description": "Consultoria"},
98
+ "amount": 1500,
99
+ "competenceDate": "2026-10-16",
100
+ }
101
+ )
102
+ # estimate["status"] is "ready" or "blocked": a fiscal gap is data, not an error.
103
+ ```
104
+
105
+ ## Issue an invoice
106
+
107
+ Issuing is asynchronous: a successful call means the request was accepted, not that the invoice is authorized. Follow the status with `invoices.get()` or webhooks.
108
+
109
+ ```python
110
+ invoice, error = await corvo.invoices.create(
111
+ {
112
+ "companyTaxId": "11222333000181", # or "companyId"
113
+ # "customer" is optional; when sent, "taxId" (CPF or CNPJ) is required.
114
+ "customer": {"taxId": "52998224725", "name": "Cliente", "email": "cliente@example.com"},
115
+ "service": {"description": "Consultoria"}, # code and city default to the company's
116
+ "amount": 150.5,
117
+ "competenceDate": "2026-10-16",
118
+ "options": {"email": {"send": True}},
119
+ },
120
+ idempotency_key="order-42", # the same key never issues twice
121
+ )
122
+ ```
123
+
124
+ Use `"options": {"type": "SCHEDULE"}` with a future `competenceDate` to schedule the issuance. Request bodies and responses are wire pass-through: camelCase stays camelCase.
125
+
126
+ ## Walk every page
127
+
128
+ ```python
129
+ async for invoice in corvo.invoices.iterate(companyId=company_id, status="authorized"):
130
+ print(invoice["nfseNumber"])
131
+ ```
132
+
133
+ ## Download the PDF or XML
134
+
135
+ The invoice must be `authorized` and have its documents ready.
136
+
137
+ ```python
138
+ pdf, error = await corvo.invoices.pdf(invoice["id"])
139
+ if pdf is not None:
140
+ with open("nfse.pdf", "wb") as fh:
141
+ fh.write(pdf.content) # also pdf.mime_type and pdf.file_name
142
+ ```
143
+
144
+ ## Verify webhook signatures
145
+
146
+ ```python
147
+ from corvo_nfse import CorvoWebhooks
148
+
149
+
150
+ async def webhook_handler(request) -> None:
151
+ # Use the raw body: re-serialized JSON will never match the HMAC.
152
+ signature = request.headers.get("X-Corvo-Signature")
153
+ if not CorvoWebhooks.verify_signature(await request.body(), signature, secret):
154
+ return # reject with 400
155
+ # Deduplicate by the event id: the same event can be delivered more than once.
156
+ ```
157
+
158
+ Webhook events you can subscribe to are exported as `corvo_nfse.WEBHOOK_EVENTS`. The `webhook.test` event is only sent by `corvo.webhooks.test()`.
159
+
160
+ ## Resources
161
+
162
+ | Namespace | Operations |
163
+ | --- | --- |
164
+ | `corvo.companies` | `create`, `get`, `update`, `remove`, `list`, `iterate` |
165
+ | `corvo.certificates` | `upload`, `list`, `remove` |
166
+ | `corvo.invoices` | `create`, `calculate`, `get`, `list`, `iterate`, `events`, `pdf`, `xml`, `download_url`, `send_email` |
167
+ | `corvo.webhooks` | `create`, `remove`, `test`, `list`, `iterate`, `.deliveries.list`, `.deliveries.iterate`, `.deliveries.stats`, `.deliveries.retry` |
168
+ | `corvo.credits` | `balance`, `plans`, `history`, `iterate_history` |
169
+ | `corvo.billing` | `plan`, `usage` |
170
+ | `corvo.plans` | `list`, `get`, `quote` (public price catalog) |
171
+ | `corvo.health` | `live`, `ready` |
172
+
173
+ Changing plans and the billing portal exist only in the dashboard, so they are not in the SDK.
174
+
175
+ ## Configuration
176
+
177
+ | Option | Env fallback | Default |
178
+ | --- | --- | --- |
179
+ | `api_key` | `CORVO_API_KEY` | required |
180
+ | `base_url` | `CORVO_BASE_URL` | `https://api.corvo.tech` |
181
+ | `prefix` | none | `v1` |
182
+ | `timeout` | none | `30.0` seconds |
183
+ | `allow_insecure_http` | none | `False` (`http://` only for loopback and private hosts) |
184
+ | `http_client` | none | an internal `httpx.AsyncClient` |
185
+
186
+ ## Behavior
187
+
188
+ - **Auth.** Every request sends `X-API-Key`. Keys are `corvo_test_...` or `corvo_live_...` and belong to one account and environment; the key decides the environment.
189
+ - **Responses.** Single resources and list items come back as the API's `data`; lists add `pagination`.
190
+ - **Errors.** Network failures, timeouts, non-2xx responses and malformed JSON all return a `CorvoApiError`. `code` is the API's `ERROR_*` code, falling back to `http_<status>`; transport failures use `network_error`, `timeout` and `invalid_json`. On `429`, `retry_after` holds the seconds to wait.
191
+
192
+ ## Development
193
+
194
+ ```bash
195
+ python -m pip install -e ".[dev]"
196
+ python -m pytest # unit tests; the contract test skips without env
197
+ python -m mypy src
198
+ ```
199
+
200
+ The contract test in `test/contract/` runs only when `CORVO_API_KEY` and `CORVO_BASE_URL` point to a reachable instance.
201
+
202
+ ## License
203
+
204
+ MIT, see [LICENSE](LICENSE).
@@ -0,0 +1,54 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "corvo-nfse"
7
+ version = "0.1.1"
8
+ description = "Python SDK for the Corvo NFS-e API: issue Brazilian service invoices, with result-style errors, cursor pagination and webhook verification."
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ requires-python = ">=3.10"
12
+ authors = [{ name = "Corvo Technology" }]
13
+ keywords = ["nfse", "nota fiscal", "brazil", "fiscal", "invoice", "corvo"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Typing :: Typed",
24
+ ]
25
+ dependencies = ["httpx>=0.27"]
26
+
27
+ [project.urls]
28
+ Homepage = "https://github.com/corvo-technology/corvo-nfse-python"
29
+ Documentation = "https://corvo.tech/en/api-docs"
30
+ Issues = "https://github.com/corvo-technology/corvo-nfse-python/issues"
31
+ Repository = "https://github.com/corvo-technology/corvo-nfse-python"
32
+
33
+ [project.optional-dependencies]
34
+ dev = [
35
+ "pytest>=8",
36
+ "pytest-asyncio>=0.23",
37
+ "respx>=0.21",
38
+ "mypy>=1.10",
39
+ ]
40
+
41
+ [tool.setuptools.packages.find]
42
+ where = ["src"]
43
+
44
+ [tool.setuptools.package-data]
45
+ corvo_nfse = ["py.typed"]
46
+
47
+ [tool.pytest.ini_options]
48
+ testpaths = ["test"]
49
+ asyncio_mode = "auto"
50
+ addopts = "-q"
51
+
52
+ [tool.mypy]
53
+ strict = true
54
+ python_version = "3.10"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,90 @@
1
+ """corvo-nfse — the Python SDK for the Corvo NFS-e API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .client import Corvo
6
+ from .error import CorvoApiError, CorvoErrorDetail, Result
7
+ from .transport import DownloadedFile, Query, Unwrap
8
+ from .types import (
9
+ WEBHOOK_EVENTS,
10
+ BillingPlanResponse,
11
+ Certificate,
12
+ Company,
13
+ CompanyAddressInput,
14
+ CompanyCreateParams,
15
+ CompanyUpdateParams,
16
+ CreditBalance,
17
+ CreditPlan,
18
+ CreditTransaction,
19
+ Invoice,
20
+ InvoiceCalculation,
21
+ InvoiceCreateParams,
22
+ InvoiceCustomerInput,
23
+ InvoiceDownloadUrl,
24
+ InvoiceEmailResult,
25
+ InvoiceEvent,
26
+ InvoiceListParams,
27
+ InvoiceStage,
28
+ InvoiceStatus,
29
+ Paginated,
30
+ Pagination,
31
+ PlanQuote,
32
+ PlanRecommendation,
33
+ QuotaStatus,
34
+ TaxRegime,
35
+ Webhook,
36
+ WebhookCreateParams,
37
+ WebhookDelivery,
38
+ WebhookDeliveryStats,
39
+ WebhookEvent,
40
+ WebhookWithSecret,
41
+ )
42
+ from .webhooks import CorvoWebhooks
43
+
44
+ VERSION = "0.1.1"
45
+ __version__ = VERSION
46
+
47
+ __all__ = [
48
+ "VERSION",
49
+ "__version__",
50
+ "WEBHOOK_EVENTS",
51
+ "BillingPlanResponse",
52
+ "Certificate",
53
+ "Company",
54
+ "CompanyAddressInput",
55
+ "CompanyCreateParams",
56
+ "CompanyUpdateParams",
57
+ "Corvo",
58
+ "CorvoApiError",
59
+ "CorvoErrorDetail",
60
+ "CorvoWebhooks",
61
+ "CreditBalance",
62
+ "CreditPlan",
63
+ "CreditTransaction",
64
+ "DownloadedFile",
65
+ "Invoice",
66
+ "InvoiceCalculation",
67
+ "InvoiceCreateParams",
68
+ "InvoiceCustomerInput",
69
+ "InvoiceDownloadUrl",
70
+ "InvoiceEmailResult",
71
+ "InvoiceEvent",
72
+ "InvoiceListParams",
73
+ "InvoiceStage",
74
+ "InvoiceStatus",
75
+ "Paginated",
76
+ "Pagination",
77
+ "PlanQuote",
78
+ "PlanRecommendation",
79
+ "Query",
80
+ "QuotaStatus",
81
+ "Result",
82
+ "TaxRegime",
83
+ "Unwrap",
84
+ "Webhook",
85
+ "WebhookCreateParams",
86
+ "WebhookDelivery",
87
+ "WebhookDeliveryStats",
88
+ "WebhookEvent",
89
+ "WebhookWithSecret",
90
+ ]