stackin-python-sdk 0.2.0__tar.gz → 0.3.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stackin-python-sdk
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Python SDK for issuing, consulting and cancelling electronic invoices.
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -47,7 +47,7 @@ Description-Content-Type: text/markdown
47
47
 
48
48
  Python SDK for fiscal document issuance — a handful of business fields, nothing about certificates, XML, XSD, signing or SOAP. The API resolves all of that from the issuer's own configuration, identified by `api_key`.
49
49
 
50
- **One class, `Invoice`** — `issue()`/`consult()`/`cancel()`/`reissue()`, nothing else to instantiate. Each line item is a `Product` (`stackin.br`) — `description`/`amount` are universal, everything else (`ncm`/`cfop`/`cest`/tax groups...) is Brazil-specific and only required for NFE; NFSE ignores it.
50
+ **One class, `Invoice`** — `issue()`/`consult()`/`cancel()`/`reissue()`/`correct()`/`invalidate()`/`pdf()`, nothing else to instantiate. Each line item is a `Product` (`stackin.br`) — `description`/`amount` are universal, everything else (`ncm`/`cfop`/`cest`/tax groups...) is Brazil-specific and only required for NFE; NFSE ignores it.
51
51
 
52
52
  ## Install
53
53
 
@@ -80,6 +80,11 @@ client.cancel(
80
80
  )
81
81
  client.reissue(invoice["id"]) # retries a rejected/failed submission
82
82
 
83
+ # The authorizer's PDF, as raw bytes. NFS-e only; the XML stays the
84
+ # legally valid document, and a 502 here means the authorizer is down.
85
+ with open("nota.pdf", "wb") as file:
86
+ file.write(client.pdf(access_key, document_type=DocumentType.NFSE))
87
+
83
88
  # NFE requires ncm/cfop on every item, plus the buyer's full recipient_address:
84
89
  client.issue(
85
90
  document_type=DocumentType.NFE,
@@ -18,7 +18,7 @@
18
18
 
19
19
  Python SDK for fiscal document issuance — a handful of business fields, nothing about certificates, XML, XSD, signing or SOAP. The API resolves all of that from the issuer's own configuration, identified by `api_key`.
20
20
 
21
- **One class, `Invoice`** — `issue()`/`consult()`/`cancel()`/`reissue()`, nothing else to instantiate. Each line item is a `Product` (`stackin.br`) — `description`/`amount` are universal, everything else (`ncm`/`cfop`/`cest`/tax groups...) is Brazil-specific and only required for NFE; NFSE ignores it.
21
+ **One class, `Invoice`** — `issue()`/`consult()`/`cancel()`/`reissue()`/`correct()`/`invalidate()`/`pdf()`, nothing else to instantiate. Each line item is a `Product` (`stackin.br`) — `description`/`amount` are universal, everything else (`ncm`/`cfop`/`cest`/tax groups...) is Brazil-specific and only required for NFE; NFSE ignores it.
22
22
 
23
23
  ## Install
24
24
 
@@ -51,6 +51,11 @@ client.cancel(
51
51
  )
52
52
  client.reissue(invoice["id"]) # retries a rejected/failed submission
53
53
 
54
+ # The authorizer's PDF, as raw bytes. NFS-e only; the XML stays the
55
+ # legally valid document, and a 502 here means the authorizer is down.
56
+ with open("nota.pdf", "wb") as file:
57
+ file.write(client.pdf(access_key, document_type=DocumentType.NFSE))
58
+
54
59
  # NFE requires ncm/cfop on every item, plus the buyer's full recipient_address:
55
60
  client.issue(
56
61
  document_type=DocumentType.NFE,
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "stackin-python-sdk"
3
- version = "0.2.0"
3
+ version = "0.3.0"
4
4
  authors = [
5
5
  { name = "Fernando Celmer", email = "email@fernandocelmer.com" },
6
6
  ]
@@ -45,7 +45,7 @@ Changelog = "https://github.com/stackin-io/stackin-python-sdk/releases"
45
45
 
46
46
  [tool.poetry]
47
47
  name = "stackin-python-sdk"
48
- version = "0.2.0"
48
+ version = "0.3.0"
49
49
  description = "Python SDK for issuing, consulting and cancelling electronic invoices."
50
50
  authors = ["Fernando Celmer <email@fernandocelmer.com>"]
51
51
  readme = "README.md"
@@ -1,6 +1,6 @@
1
1
  """Invoice __init__ module."""
2
2
 
3
- __version__ = "0.2.0"
3
+ __version__ = "0.3.0"
4
4
  __description__ = (
5
5
  "Python SDK for issuing, consulting and cancelling electronic invoices."
6
6
  )
@@ -204,6 +204,26 @@ class Invoice:
204
204
  idempotency_key=idempotency_key,
205
205
  )
206
206
 
207
+ def pdf(
208
+ self,
209
+ access_key: str,
210
+ *,
211
+ document_type: DocumentType,
212
+ ) -> bytes:
213
+ """The authorizer's rendering of an authorized document.
214
+
215
+ Returns raw bytes — the only method that does not return a
216
+ parsed object. The XML is the legally valid document; this is a
217
+ convenience, and the authorizer's endpoint for it is unstable,
218
+ so an APIError with status 502 means the authorizer is
219
+ unavailable, not that the invoice is wrong. NFS-e only.
220
+ """
221
+ params = {"document_type": document_type.value}
222
+ response = self._send(
223
+ "GET", f"/invoices/{access_key}/pdf", params=params
224
+ )
225
+ return response.content
226
+
207
227
  def _headers(self, idempotency_key: str | None = None) -> dict:
208
228
  headers = {}
209
229
  if self.api_key:
@@ -221,6 +241,28 @@ class Invoice:
221
241
  params: dict | None = None,
222
242
  idempotency_key: str | None = None,
223
243
  ) -> dict:
244
+ response = self._send(
245
+ method,
246
+ path,
247
+ json=json,
248
+ params=params,
249
+ idempotency_key=idempotency_key,
250
+ )
251
+ try:
252
+ body = response.json() if response.content else {}
253
+ except ValueError:
254
+ body = {}
255
+ return body.get("result", body)
256
+
257
+ def _send(
258
+ self,
259
+ method: str,
260
+ path: str,
261
+ *,
262
+ json: dict | None = None,
263
+ params: dict | None = None,
264
+ idempotency_key: str | None = None,
265
+ ) -> requests.Response:
224
266
  url = f"{self.base_url}/api/v1{path}"
225
267
 
226
268
  try:
@@ -235,15 +277,14 @@ class Invoice:
235
277
  except requests.RequestException as error:
236
278
  raise ConnectionFailedError(str(error)) from error
237
279
 
238
- try:
239
- body = response.json() if response.content else {}
240
- except ValueError:
241
- body = {}
242
-
243
280
  if not response.ok:
281
+ try:
282
+ body = response.json() if response.content else {}
283
+ except ValueError:
284
+ body = {}
244
285
  raise APIError(
245
286
  status_code=response.status_code,
246
287
  detail=body.get("detail", response.text),
247
288
  )
248
289
 
249
- return body.get("result", body)
290
+ return response