flashpi 0.1.0__py3-none-any.whl
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.
- flashpi/__init__.py +74 -0
- flashpi/_client.py +405 -0
- flashpi/_errors.py +249 -0
- flashpi/_http.py +666 -0
- flashpi/_network.py +114 -0
- flashpi/_pagination.py +130 -0
- flashpi/_resource.py +100 -0
- flashpi/_response.py +95 -0
- flashpi/_validate.py +89 -0
- flashpi/_version.py +3 -0
- flashpi/_webhooks.py +264 -0
- flashpi/py.typed +0 -0
- flashpi/resources/__init__.py +80 -0
- flashpi/resources/documents.py +320 -0
- flashpi/resources/events.py +73 -0
- flashpi/resources/issuer_onboardings.py +130 -0
- flashpi/resources/issuers.py +604 -0
- flashpi/resources/status.py +47 -0
- flashpi/resources/uploads.py +117 -0
- flashpi/resources/webhook_endpoints.py +341 -0
- flashpi/types/__init__.py +336 -0
- flashpi/types/_generated.py +2841 -0
- flashpi-0.1.0.dist-info/METADATA +472 -0
- flashpi-0.1.0.dist-info/RECORD +26 -0
- flashpi-0.1.0.dist-info/WHEEL +4 -0
- flashpi-0.1.0.dist-info/licenses/LICENSE +21 -0
flashpi/__init__.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""The official Python SDK for Flashpi, the electronic invoicing API for Ecuador's SRI."""
|
|
2
|
+
|
|
3
|
+
from ._client import DEFAULT_BASE_URL, DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT, AsyncFlashpi, Flashpi
|
|
4
|
+
from ._errors import (
|
|
5
|
+
APIConnectionError,
|
|
6
|
+
APIError,
|
|
7
|
+
APITimeoutError,
|
|
8
|
+
AuthenticationError,
|
|
9
|
+
BadRequestError,
|
|
10
|
+
ConflictError,
|
|
11
|
+
FlashpiError,
|
|
12
|
+
InternalServerError,
|
|
13
|
+
NotFoundError,
|
|
14
|
+
PermissionDeniedError,
|
|
15
|
+
RateLimitError,
|
|
16
|
+
UnprocessableEntityError,
|
|
17
|
+
WebhookVerificationError,
|
|
18
|
+
WebhookVerificationReason,
|
|
19
|
+
)
|
|
20
|
+
from ._http import RequestOptions
|
|
21
|
+
from ._pagination import AsyncPage, AsyncPaginator, Page
|
|
22
|
+
from ._response import APIResponse
|
|
23
|
+
from ._version import VERSION
|
|
24
|
+
from ._webhooks import (
|
|
25
|
+
WEBHOOK_TOLERANCE_SECONDS,
|
|
26
|
+
WebhookHeaders,
|
|
27
|
+
WebhookPayload,
|
|
28
|
+
Webhooks,
|
|
29
|
+
unwrap_webhook,
|
|
30
|
+
verify_webhook_signature,
|
|
31
|
+
)
|
|
32
|
+
from .types import ApiErrorCode, ApiFieldErrorCode, FieldError, Language, Problem
|
|
33
|
+
|
|
34
|
+
__version__ = VERSION
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
"DEFAULT_BASE_URL",
|
|
38
|
+
"DEFAULT_MAX_RETRIES",
|
|
39
|
+
"DEFAULT_TIMEOUT",
|
|
40
|
+
"VERSION",
|
|
41
|
+
"WEBHOOK_TOLERANCE_SECONDS",
|
|
42
|
+
"APIConnectionError",
|
|
43
|
+
"APIError",
|
|
44
|
+
"APIResponse",
|
|
45
|
+
"APITimeoutError",
|
|
46
|
+
"ApiErrorCode",
|
|
47
|
+
"ApiFieldErrorCode",
|
|
48
|
+
"AsyncFlashpi",
|
|
49
|
+
"AsyncPage",
|
|
50
|
+
"AsyncPaginator",
|
|
51
|
+
"AuthenticationError",
|
|
52
|
+
"BadRequestError",
|
|
53
|
+
"ConflictError",
|
|
54
|
+
"FieldError",
|
|
55
|
+
"Flashpi",
|
|
56
|
+
"FlashpiError",
|
|
57
|
+
"InternalServerError",
|
|
58
|
+
"Language",
|
|
59
|
+
"NotFoundError",
|
|
60
|
+
"Page",
|
|
61
|
+
"PermissionDeniedError",
|
|
62
|
+
"Problem",
|
|
63
|
+
"RateLimitError",
|
|
64
|
+
"RequestOptions",
|
|
65
|
+
"UnprocessableEntityError",
|
|
66
|
+
"WebhookHeaders",
|
|
67
|
+
"WebhookPayload",
|
|
68
|
+
"WebhookVerificationError",
|
|
69
|
+
"WebhookVerificationReason",
|
|
70
|
+
"Webhooks",
|
|
71
|
+
"__version__",
|
|
72
|
+
"unwrap_webhook",
|
|
73
|
+
"verify_webhook_signature",
|
|
74
|
+
]
|
flashpi/_client.py
ADDED
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
"""The Flashpi clients: `Flashpi` (sync) and `AsyncFlashpi` (asyncio), with the same API."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from types import TracebackType
|
|
7
|
+
from typing import Self, cast
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
|
|
11
|
+
from ._errors import FlashpiError
|
|
12
|
+
from ._http import AsyncEngine, ClientConfig, SyncEngine
|
|
13
|
+
from ._network import limit_to_attempt_deadlines
|
|
14
|
+
from ._validate import checked_headers, header_value, valid_retries, valid_timeout
|
|
15
|
+
from ._webhooks import Webhooks
|
|
16
|
+
from .resources.documents import (
|
|
17
|
+
AsyncCreditNotes,
|
|
18
|
+
AsyncDebitNotes,
|
|
19
|
+
AsyncDocuments,
|
|
20
|
+
AsyncInvoices,
|
|
21
|
+
AsyncPurchaseSettlements,
|
|
22
|
+
AsyncWaybills,
|
|
23
|
+
AsyncWithholdings,
|
|
24
|
+
CreditNotes,
|
|
25
|
+
DebitNotes,
|
|
26
|
+
Documents,
|
|
27
|
+
Invoices,
|
|
28
|
+
PurchaseSettlements,
|
|
29
|
+
Waybills,
|
|
30
|
+
Withholdings,
|
|
31
|
+
)
|
|
32
|
+
from .resources.events import AsyncEvents, Events
|
|
33
|
+
from .resources.issuer_onboardings import AsyncIssuerOnboardings, IssuerOnboardings
|
|
34
|
+
from .resources.issuers import AsyncIssuers, Issuers
|
|
35
|
+
from .resources.status import AsyncStatusResource, StatusResource
|
|
36
|
+
from .resources.webhook_endpoints import AsyncWebhookEndpoints, WebhookEndpoints
|
|
37
|
+
from .types import Environment, Language
|
|
38
|
+
|
|
39
|
+
DEFAULT_BASE_URL = "https://api.flashpi.com.ec"
|
|
40
|
+
DEFAULT_TIMEOUT = 60.0
|
|
41
|
+
"""Seconds each attempt of a request may take, from sending it to reading the whole answer (the
|
|
42
|
+
Node SDK's 60 000 ms)."""
|
|
43
|
+
DEFAULT_MAX_RETRIES = 2
|
|
44
|
+
|
|
45
|
+
_API_KEY_CHARACTERS = re.compile(r"[\x21-\x7e]+")
|
|
46
|
+
"""What an API key is made of: visible ASCII, without spaces."""
|
|
47
|
+
_JS_SPACE = r"[\t\n\v\f\r \xa0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]"
|
|
48
|
+
_AROUND = re.compile(rf"\A{_JS_SPACE}+|{_JS_SPACE}+\Z")
|
|
49
|
+
"""What JavaScript's `trim()` removes around a string, the Node SDK's key included: its white
|
|
50
|
+
space and line breaks, and the byte order mark (U+FEFF) that `str.strip()` keeps."""
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _configure(
|
|
54
|
+
api_key: str | None,
|
|
55
|
+
*,
|
|
56
|
+
base_url: str | None,
|
|
57
|
+
timeout: float | None,
|
|
58
|
+
max_retries: int | None,
|
|
59
|
+
language: Language | None,
|
|
60
|
+
default_headers: Mapping[str, str] | None,
|
|
61
|
+
) -> tuple[ClientConfig, Environment]:
|
|
62
|
+
# The key is checked first, so a missing or bad key is the error reported, then read again
|
|
63
|
+
# once every other setting is checked: no local of this frame keeps it, since error trackers
|
|
64
|
+
# record the local variables of the frames an error passes through (and scrub `api_key` by
|
|
65
|
+
# name at best).
|
|
66
|
+
_api_key(api_key)
|
|
67
|
+
headers = checked_headers("default_headers", default_headers)
|
|
68
|
+
if language is not None:
|
|
69
|
+
language = cast("Language", header_value("language", language))
|
|
70
|
+
if any(name.lower() == "idempotency-key" for name in headers):
|
|
71
|
+
raise FlashpiError(
|
|
72
|
+
"default_headers cannot carry an Idempotency-Key: it must be unique per operation. "
|
|
73
|
+
"Pass idempotency_key= to each create() instead"
|
|
74
|
+
)
|
|
75
|
+
url = DEFAULT_BASE_URL if base_url is None else base_url
|
|
76
|
+
if not _is_http_url(url):
|
|
77
|
+
raise FlashpiError(f"base_url must be an http:// or https:// URL, got {url!r}")
|
|
78
|
+
seconds = valid_timeout("timeout", DEFAULT_TIMEOUT if timeout is None else timeout)
|
|
79
|
+
retries = valid_retries(
|
|
80
|
+
"max_retries", DEFAULT_MAX_RETRIES if max_retries is None else max_retries
|
|
81
|
+
)
|
|
82
|
+
config = ClientConfig(
|
|
83
|
+
api_key=_api_key(api_key),
|
|
84
|
+
base_url=url,
|
|
85
|
+
timeout=seconds,
|
|
86
|
+
max_retries=retries,
|
|
87
|
+
language=language,
|
|
88
|
+
default_headers=headers,
|
|
89
|
+
)
|
|
90
|
+
return config, "test" if config.api_key.startswith("fp_test_") else "live"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _is_http_url(url: object) -> bool:
|
|
94
|
+
if not isinstance(url, str):
|
|
95
|
+
return False
|
|
96
|
+
try:
|
|
97
|
+
parsed = httpx.URL(url)
|
|
98
|
+
except httpx.InvalidURL:
|
|
99
|
+
return False
|
|
100
|
+
return parsed.scheme in ("http", "https") and bool(parsed.host)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _api_key(api_key: str | None) -> str:
|
|
104
|
+
"""The key: the argument or FLASHPI_API_KEY, trimmed as JavaScript's `trim()`, the byte order
|
|
105
|
+
mark included. A key that cannot be used raises FlashpiError, with no local keeping it."""
|
|
106
|
+
value: object = os.environ.get("FLASHPI_API_KEY") if api_key is None else api_key
|
|
107
|
+
key = _AROUND.sub("", value) if isinstance(value, str) else ""
|
|
108
|
+
problem = _key_problem(key)
|
|
109
|
+
if problem is None:
|
|
110
|
+
return key
|
|
111
|
+
del value, key
|
|
112
|
+
raise FlashpiError(problem)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _key_problem(key: str) -> str | None:
|
|
116
|
+
"""Why a trimmed key cannot be used, or None. It returns instead of raising, so its frame is
|
|
117
|
+
never part of an error."""
|
|
118
|
+
if not key:
|
|
119
|
+
return (
|
|
120
|
+
"Missing API key: pass it to Flashpi(api_key=...) or set the FLASHPI_API_KEY "
|
|
121
|
+
"environment variable"
|
|
122
|
+
)
|
|
123
|
+
if key.startswith("whsec_"):
|
|
124
|
+
return (
|
|
125
|
+
"That is a webhook signing secret (whsec_...), not an API key: use it with "
|
|
126
|
+
"flashpi.webhooks"
|
|
127
|
+
)
|
|
128
|
+
if not key.startswith(("fp_test_", "fp_live_")):
|
|
129
|
+
return "The API key must start with fp_test_ or fp_live_"
|
|
130
|
+
if not _API_KEY_CHARACTERS.fullmatch(key):
|
|
131
|
+
# Never the key: httpx would refuse it in the Authorization header and quote it.
|
|
132
|
+
return (
|
|
133
|
+
"The API key has spaces, line breaks or other characters an API key cannot have: "
|
|
134
|
+
"copy it again from the Flashpi console"
|
|
135
|
+
)
|
|
136
|
+
return None
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _describe(client: object, environment: Environment, config: ClientConfig) -> str:
|
|
140
|
+
# Never the API key.
|
|
141
|
+
return f"{type(client).__name__}(environment={environment!r}, base_url={config.base_url!r})"
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class Flashpi:
|
|
145
|
+
"""The Flashpi API client.
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
from flashpi import Flashpi
|
|
149
|
+
|
|
150
|
+
with Flashpi() as flashpi: # reads FLASHPI_API_KEY
|
|
151
|
+
invoice = flashpi.invoices.create({...})
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
An `fp_test_` key issues in the SRI test environment (pruebas); an `fp_live_` key issues real
|
|
155
|
+
documents in production. Use it on servers only: the key must stay secret.
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
environment: Environment
|
|
159
|
+
"""`test` for fp_test_ keys (SRI pruebas), `live` for fp_live_ keys (SRI production)."""
|
|
160
|
+
|
|
161
|
+
invoices: Invoices
|
|
162
|
+
"""Invoices (facturas)."""
|
|
163
|
+
|
|
164
|
+
credit_notes: CreditNotes
|
|
165
|
+
"""Credit notes (notas de crédito)."""
|
|
166
|
+
|
|
167
|
+
debit_notes: DebitNotes
|
|
168
|
+
"""Debit notes (notas de débito)."""
|
|
169
|
+
|
|
170
|
+
purchase_settlements: PurchaseSettlements
|
|
171
|
+
"""Purchase settlements (liquidaciones de compra)."""
|
|
172
|
+
|
|
173
|
+
waybills: Waybills
|
|
174
|
+
"""Waybills (guías de remisión)."""
|
|
175
|
+
|
|
176
|
+
withholdings: Withholdings
|
|
177
|
+
"""Withholding receipts (comprobantes de retención)."""
|
|
178
|
+
|
|
179
|
+
documents: Documents
|
|
180
|
+
"""Every issued document: search, XML, RIDE (PDF) and email."""
|
|
181
|
+
|
|
182
|
+
issuers: Issuers
|
|
183
|
+
"""Issuers (RUCs) with their establishments, emission points and certificates."""
|
|
184
|
+
|
|
185
|
+
issuer_onboardings: IssuerOnboardings
|
|
186
|
+
"""Create an issuer from its .p12 certificate."""
|
|
187
|
+
|
|
188
|
+
webhook_endpoints: WebhookEndpoints
|
|
189
|
+
"""The URLs Flashpi sends events to."""
|
|
190
|
+
|
|
191
|
+
events: Events
|
|
192
|
+
"""Events of documents, certificates and email sending."""
|
|
193
|
+
|
|
194
|
+
status: StatusResource
|
|
195
|
+
"""The API's status."""
|
|
196
|
+
|
|
197
|
+
webhooks: Webhooks
|
|
198
|
+
"""Verify the signature of the webhooks you receive (no API key needed)."""
|
|
199
|
+
|
|
200
|
+
def __init__(
|
|
201
|
+
self,
|
|
202
|
+
api_key: str | None = None,
|
|
203
|
+
*,
|
|
204
|
+
base_url: str | None = None,
|
|
205
|
+
timeout: float | None = None,
|
|
206
|
+
max_retries: int | None = None,
|
|
207
|
+
language: Language | None = None,
|
|
208
|
+
default_headers: Mapping[str, str] | None = None,
|
|
209
|
+
http_client: httpx.Client | None = None,
|
|
210
|
+
) -> None:
|
|
211
|
+
"""Creates a client.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
api_key: The API key (`fp_test_...` or `fp_live_...`). Defaults to the
|
|
215
|
+
FLASHPI_API_KEY environment variable.
|
|
216
|
+
base_url: The API's address (default https://api.flashpi.com.ec).
|
|
217
|
+
timeout: Seconds each attempt of a request may take, from sending it to reading the
|
|
218
|
+
whole answer (default 60). Issuing with `options.waitForAuthorization` raises it,
|
|
219
|
+
on both clients, to the wait plus 15 seconds. On `Flashpi`, only a DNS lookup,
|
|
220
|
+
which Python cannot interrupt, and connecting to a host that has several
|
|
221
|
+
addresses can run past it (but see `http_client`).
|
|
222
|
+
max_retries: Retries after network errors, timeouts, 429 and 5xx (default 2; 0 turns
|
|
223
|
+
them off).
|
|
224
|
+
language: Language of the API's messages: `en` (default) or `es`. Error codes do not
|
|
225
|
+
change.
|
|
226
|
+
default_headers: Headers sent with every request (not `Idempotency-Key`: that one is
|
|
227
|
+
per request).
|
|
228
|
+
http_client: An `httpx.Client` to send the requests with (proxies, tests). The SDK
|
|
229
|
+
never follows redirects, and leaves closing this client to you. With it, the
|
|
230
|
+
deadline is checked as each chunk of the answer's body arrives: before that,
|
|
231
|
+
httpx limits connecting, each write, waiting for a pooled connection and each
|
|
232
|
+
read of the status line and headers to the timeout one by one, so headers that
|
|
233
|
+
trickle in can keep an attempt going far past its timeout, and a body that this
|
|
234
|
+
client's own response hooks read is checked only once it has been read.
|
|
235
|
+
"""
|
|
236
|
+
config, self.environment = _configure(
|
|
237
|
+
api_key,
|
|
238
|
+
base_url=base_url,
|
|
239
|
+
timeout=timeout,
|
|
240
|
+
max_retries=max_retries,
|
|
241
|
+
language=language,
|
|
242
|
+
default_headers=default_headers,
|
|
243
|
+
)
|
|
244
|
+
if http_client is not None and not isinstance(cast("object", http_client), httpx.Client):
|
|
245
|
+
raise TypeError(
|
|
246
|
+
"http_client must be an httpx.Client (AsyncFlashpi takes an httpx.AsyncClient)"
|
|
247
|
+
)
|
|
248
|
+
self._owns_http_client = http_client is None
|
|
249
|
+
if http_client is None:
|
|
250
|
+
http_client = httpx.Client(follow_redirects=False)
|
|
251
|
+
limit_to_attempt_deadlines(http_client)
|
|
252
|
+
self._http_client = http_client
|
|
253
|
+
self._engine = SyncEngine(config, self._http_client)
|
|
254
|
+
self.invoices = Invoices(self._engine)
|
|
255
|
+
self.credit_notes = CreditNotes(self._engine)
|
|
256
|
+
self.debit_notes = DebitNotes(self._engine)
|
|
257
|
+
self.purchase_settlements = PurchaseSettlements(self._engine)
|
|
258
|
+
self.waybills = Waybills(self._engine)
|
|
259
|
+
self.withholdings = Withholdings(self._engine)
|
|
260
|
+
self.documents = Documents(self._engine)
|
|
261
|
+
self.issuers = Issuers(self._engine)
|
|
262
|
+
self.issuer_onboardings = IssuerOnboardings(self._engine)
|
|
263
|
+
self.webhook_endpoints = WebhookEndpoints(self._engine)
|
|
264
|
+
self.events = Events(self._engine)
|
|
265
|
+
self.status = StatusResource(self._engine)
|
|
266
|
+
self.webhooks = Webhooks()
|
|
267
|
+
|
|
268
|
+
def close(self) -> None:
|
|
269
|
+
"""Closes the connections, unless the http_client came from you."""
|
|
270
|
+
if self._owns_http_client:
|
|
271
|
+
self._http_client.close()
|
|
272
|
+
|
|
273
|
+
def __enter__(self) -> Self:
|
|
274
|
+
return self
|
|
275
|
+
|
|
276
|
+
def __exit__(
|
|
277
|
+
self,
|
|
278
|
+
exc_type: type[BaseException] | None,
|
|
279
|
+
exc: BaseException | None,
|
|
280
|
+
traceback: TracebackType | None,
|
|
281
|
+
) -> None:
|
|
282
|
+
self.close()
|
|
283
|
+
|
|
284
|
+
def __repr__(self) -> str:
|
|
285
|
+
return _describe(self, self.environment, self._engine.config)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
class AsyncFlashpi:
|
|
289
|
+
"""The Flashpi API client for asyncio, with the same API as `Flashpi`.
|
|
290
|
+
|
|
291
|
+
```python
|
|
292
|
+
from flashpi import AsyncFlashpi
|
|
293
|
+
|
|
294
|
+
async with AsyncFlashpi() as flashpi: # reads FLASHPI_API_KEY
|
|
295
|
+
invoice = await flashpi.invoices.create({...})
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
An `fp_test_` key issues in the SRI test environment (pruebas); an `fp_live_` key issues real
|
|
299
|
+
documents in production. Use it on servers only: the key must stay secret.
|
|
300
|
+
"""
|
|
301
|
+
|
|
302
|
+
environment: Environment
|
|
303
|
+
"""`test` for fp_test_ keys (SRI pruebas), `live` for fp_live_ keys (SRI production)."""
|
|
304
|
+
|
|
305
|
+
invoices: AsyncInvoices
|
|
306
|
+
"""Invoices (facturas)."""
|
|
307
|
+
|
|
308
|
+
credit_notes: AsyncCreditNotes
|
|
309
|
+
"""Credit notes (notas de crédito)."""
|
|
310
|
+
|
|
311
|
+
debit_notes: AsyncDebitNotes
|
|
312
|
+
"""Debit notes (notas de débito)."""
|
|
313
|
+
|
|
314
|
+
purchase_settlements: AsyncPurchaseSettlements
|
|
315
|
+
"""Purchase settlements (liquidaciones de compra)."""
|
|
316
|
+
|
|
317
|
+
waybills: AsyncWaybills
|
|
318
|
+
"""Waybills (guías de remisión)."""
|
|
319
|
+
|
|
320
|
+
withholdings: AsyncWithholdings
|
|
321
|
+
"""Withholding receipts (comprobantes de retención)."""
|
|
322
|
+
|
|
323
|
+
documents: AsyncDocuments
|
|
324
|
+
"""Every issued document: search, XML, RIDE (PDF) and email."""
|
|
325
|
+
|
|
326
|
+
issuers: AsyncIssuers
|
|
327
|
+
"""Issuers (RUCs) with their establishments, emission points and certificates."""
|
|
328
|
+
|
|
329
|
+
issuer_onboardings: AsyncIssuerOnboardings
|
|
330
|
+
"""Create an issuer from its .p12 certificate."""
|
|
331
|
+
|
|
332
|
+
webhook_endpoints: AsyncWebhookEndpoints
|
|
333
|
+
"""The URLs Flashpi sends events to."""
|
|
334
|
+
|
|
335
|
+
events: AsyncEvents
|
|
336
|
+
"""Events of documents, certificates and email sending."""
|
|
337
|
+
|
|
338
|
+
status: AsyncStatusResource
|
|
339
|
+
"""The API's status."""
|
|
340
|
+
|
|
341
|
+
webhooks: Webhooks
|
|
342
|
+
"""Verify the signature of the webhooks you receive (no API key needed)."""
|
|
343
|
+
|
|
344
|
+
def __init__(
|
|
345
|
+
self,
|
|
346
|
+
api_key: str | None = None,
|
|
347
|
+
*,
|
|
348
|
+
base_url: str | None = None,
|
|
349
|
+
timeout: float | None = None,
|
|
350
|
+
max_retries: int | None = None,
|
|
351
|
+
language: Language | None = None,
|
|
352
|
+
default_headers: Mapping[str, str] | None = None,
|
|
353
|
+
http_client: httpx.AsyncClient | None = None,
|
|
354
|
+
) -> None:
|
|
355
|
+
"""Creates a client. The arguments are the ones of `Flashpi`, with an
|
|
356
|
+
`httpx.AsyncClient` as `http_client`."""
|
|
357
|
+
config, self.environment = _configure(
|
|
358
|
+
api_key,
|
|
359
|
+
base_url=base_url,
|
|
360
|
+
timeout=timeout,
|
|
361
|
+
max_retries=max_retries,
|
|
362
|
+
language=language,
|
|
363
|
+
default_headers=default_headers,
|
|
364
|
+
)
|
|
365
|
+
if http_client is not None and not isinstance(
|
|
366
|
+
cast("object", http_client), httpx.AsyncClient
|
|
367
|
+
):
|
|
368
|
+
raise TypeError(
|
|
369
|
+
"http_client must be an httpx.AsyncClient (Flashpi takes an httpx.Client)"
|
|
370
|
+
)
|
|
371
|
+
self._owns_http_client = http_client is None
|
|
372
|
+
self._http_client = http_client or httpx.AsyncClient(follow_redirects=False)
|
|
373
|
+
self._engine = AsyncEngine(config, self._http_client)
|
|
374
|
+
self.invoices = AsyncInvoices(self._engine)
|
|
375
|
+
self.credit_notes = AsyncCreditNotes(self._engine)
|
|
376
|
+
self.debit_notes = AsyncDebitNotes(self._engine)
|
|
377
|
+
self.purchase_settlements = AsyncPurchaseSettlements(self._engine)
|
|
378
|
+
self.waybills = AsyncWaybills(self._engine)
|
|
379
|
+
self.withholdings = AsyncWithholdings(self._engine)
|
|
380
|
+
self.documents = AsyncDocuments(self._engine)
|
|
381
|
+
self.issuers = AsyncIssuers(self._engine)
|
|
382
|
+
self.issuer_onboardings = AsyncIssuerOnboardings(self._engine)
|
|
383
|
+
self.webhook_endpoints = AsyncWebhookEndpoints(self._engine)
|
|
384
|
+
self.events = AsyncEvents(self._engine)
|
|
385
|
+
self.status = AsyncStatusResource(self._engine)
|
|
386
|
+
self.webhooks = Webhooks()
|
|
387
|
+
|
|
388
|
+
async def aclose(self) -> None:
|
|
389
|
+
"""Closes the connections, unless the http_client came from you."""
|
|
390
|
+
if self._owns_http_client:
|
|
391
|
+
await self._http_client.aclose()
|
|
392
|
+
|
|
393
|
+
async def __aenter__(self) -> Self:
|
|
394
|
+
return self
|
|
395
|
+
|
|
396
|
+
async def __aexit__(
|
|
397
|
+
self,
|
|
398
|
+
exc_type: type[BaseException] | None,
|
|
399
|
+
exc: BaseException | None,
|
|
400
|
+
traceback: TracebackType | None,
|
|
401
|
+
) -> None:
|
|
402
|
+
await self.aclose()
|
|
403
|
+
|
|
404
|
+
def __repr__(self) -> str:
|
|
405
|
+
return _describe(self, self.environment, self._engine.config)
|