silon-sdk 0.2.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.
- silon/__init__.py +63 -0
- silon/_client.py +424 -0
- silon/_exceptions.py +228 -0
- silon/_models.py +14 -0
- silon/_pagination.py +117 -0
- silon/_utils.py +52 -0
- silon/_version.py +1 -0
- silon/py.typed +0 -0
- silon/resources/__init__.py +46 -0
- silon/resources/_base.py +16 -0
- silon/resources/account.py +207 -0
- silon/resources/broadcasts.py +218 -0
- silon/resources/bulk.py +301 -0
- silon/resources/crm.py +405 -0
- silon/resources/events.py +50 -0
- silon/resources/messages.py +399 -0
- silon/resources/otp.py +62 -0
- silon/resources/push.py +159 -0
- silon/resources/reports.py +288 -0
- silon/resources/suppressions.py +128 -0
- silon/resources/templates.py +185 -0
- silon/resources/webhook_endpoints.py +187 -0
- silon/resources/whatsapp_templates.py +45 -0
- silon/types/__init__.py +94 -0
- silon/types/_shared.py +3 -0
- silon/types/account.py +29 -0
- silon/types/broadcasts.py +79 -0
- silon/types/bulk.py +113 -0
- silon/types/crm.py +34 -0
- silon/types/events.py +59 -0
- silon/types/messages.py +217 -0
- silon/types/otp.py +42 -0
- silon/types/push.py +49 -0
- silon/types/reports.py +29 -0
- silon/types/suppressions.py +36 -0
- silon/types/templates.py +57 -0
- silon/types/webhook_endpoints.py +106 -0
- silon/types/whatsapp_templates.py +51 -0
- silon/webhooks.py +104 -0
- silon_sdk-0.2.0.dist-info/METADATA +563 -0
- silon_sdk-0.2.0.dist-info/RECORD +43 -0
- silon_sdk-0.2.0.dist-info/WHEEL +4 -0
- silon_sdk-0.2.0.dist-info/licenses/LICENSE +21 -0
silon/__init__.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Python SDK for the Silon messaging platform API.
|
|
2
|
+
|
|
3
|
+
Quickstart::
|
|
4
|
+
|
|
5
|
+
from silon import Silon
|
|
6
|
+
|
|
7
|
+
client = Silon(api_key="sk_live_...", workspace="acme")
|
|
8
|
+
sent = client.messages.send(
|
|
9
|
+
channel="whatsapp",
|
|
10
|
+
to={"client_id": "cust_001"},
|
|
11
|
+
content={"body": "Your order has shipped"},
|
|
12
|
+
)
|
|
13
|
+
print(sent.id, sent.status)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from . import types, webhooks
|
|
17
|
+
from ._client import DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT, AsyncSilon, Silon
|
|
18
|
+
from ._exceptions import (
|
|
19
|
+
APIConnectionError,
|
|
20
|
+
APIStatusError,
|
|
21
|
+
APITimeoutError,
|
|
22
|
+
AuthenticationError,
|
|
23
|
+
BadRequestError,
|
|
24
|
+
ConflictError,
|
|
25
|
+
ErrorDetail,
|
|
26
|
+
GoneError,
|
|
27
|
+
InternalServerError,
|
|
28
|
+
NotFoundError,
|
|
29
|
+
PermissionDeniedError,
|
|
30
|
+
RateLimitError,
|
|
31
|
+
SilonError,
|
|
32
|
+
UnprocessableEntityError,
|
|
33
|
+
WebhookSignatureVerificationError,
|
|
34
|
+
)
|
|
35
|
+
from ._pagination import AsyncCursorPage, SyncCursorPage
|
|
36
|
+
from ._version import __version__
|
|
37
|
+
|
|
38
|
+
__all__ = [
|
|
39
|
+
"DEFAULT_MAX_RETRIES",
|
|
40
|
+
"DEFAULT_TIMEOUT",
|
|
41
|
+
"APIConnectionError",
|
|
42
|
+
"APIStatusError",
|
|
43
|
+
"APITimeoutError",
|
|
44
|
+
"AsyncCursorPage",
|
|
45
|
+
"AsyncSilon",
|
|
46
|
+
"AuthenticationError",
|
|
47
|
+
"BadRequestError",
|
|
48
|
+
"ConflictError",
|
|
49
|
+
"ErrorDetail",
|
|
50
|
+
"GoneError",
|
|
51
|
+
"InternalServerError",
|
|
52
|
+
"NotFoundError",
|
|
53
|
+
"PermissionDeniedError",
|
|
54
|
+
"RateLimitError",
|
|
55
|
+
"Silon",
|
|
56
|
+
"SilonError",
|
|
57
|
+
"SyncCursorPage",
|
|
58
|
+
"UnprocessableEntityError",
|
|
59
|
+
"WebhookSignatureVerificationError",
|
|
60
|
+
"__version__",
|
|
61
|
+
"types",
|
|
62
|
+
"webhooks",
|
|
63
|
+
]
|
silon/_client.py
ADDED
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import os
|
|
5
|
+
import platform
|
|
6
|
+
import random
|
|
7
|
+
import time
|
|
8
|
+
from collections.abc import Mapping
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
import httpx
|
|
12
|
+
|
|
13
|
+
from ._exceptions import APIConnectionError, APITimeoutError, SilonError, make_status_error
|
|
14
|
+
from ._utils import parse_retry_after
|
|
15
|
+
from ._version import __version__
|
|
16
|
+
|
|
17
|
+
__all__ = ["DEFAULT_MAX_RETRIES", "DEFAULT_TIMEOUT", "AsyncSilon", "Silon"]
|
|
18
|
+
|
|
19
|
+
DEFAULT_TIMEOUT = httpx.Timeout(30.0, connect=10.0)
|
|
20
|
+
DEFAULT_MAX_RETRIES = 2
|
|
21
|
+
|
|
22
|
+
_RETRYABLE_STATUSES = frozenset({429, 500, 502, 503, 504})
|
|
23
|
+
_IDEMPOTENT_METHODS = frozenset({"GET", "HEAD", "OPTIONS", "PUT", "DELETE"})
|
|
24
|
+
_MAX_RETRY_DELAY = 30.0
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class _BaseClient:
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
*,
|
|
31
|
+
api_key: str | None = None,
|
|
32
|
+
workspace: str | None = None,
|
|
33
|
+
base_url: str | None = None,
|
|
34
|
+
timeout: float | httpx.Timeout = DEFAULT_TIMEOUT,
|
|
35
|
+
max_retries: int = DEFAULT_MAX_RETRIES,
|
|
36
|
+
default_headers: Mapping[str, str] | None = None,
|
|
37
|
+
) -> None:
|
|
38
|
+
self.api_key = api_key or os.environ.get("SILON_API_KEY")
|
|
39
|
+
if not self.api_key:
|
|
40
|
+
raise SilonError(
|
|
41
|
+
"No API key provided. Pass api_key=... or set the SILON_API_KEY environment "
|
|
42
|
+
"variable. Create a key in the dashboard under Settings > API keys."
|
|
43
|
+
)
|
|
44
|
+
base_url = base_url or os.environ.get("SILON_BASE_URL")
|
|
45
|
+
workspace = workspace or os.environ.get("SILON_WORKSPACE")
|
|
46
|
+
if base_url is None and workspace:
|
|
47
|
+
base_url = f"https://{workspace}.silon.tech"
|
|
48
|
+
if base_url is None:
|
|
49
|
+
raise SilonError(
|
|
50
|
+
"No base URL. Pass workspace='<your-workspace>' (=> https://<workspace>"
|
|
51
|
+
".silon.tech), base_url=..., or set SILON_WORKSPACE / SILON_BASE_URL."
|
|
52
|
+
)
|
|
53
|
+
self.base_url = base_url.rstrip("/")
|
|
54
|
+
self.timeout = timeout
|
|
55
|
+
self.max_retries = max_retries
|
|
56
|
+
self.default_headers = dict(default_headers or {})
|
|
57
|
+
|
|
58
|
+
def _headers(self, extra: Mapping[str, str] | None = None) -> dict[str, str]:
|
|
59
|
+
headers = {
|
|
60
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
61
|
+
"Accept": "application/json",
|
|
62
|
+
"User-Agent": f"silon-python/{__version__} python/{platform.python_version()}",
|
|
63
|
+
}
|
|
64
|
+
headers.update(self.default_headers)
|
|
65
|
+
if extra:
|
|
66
|
+
headers.update(extra)
|
|
67
|
+
return headers
|
|
68
|
+
|
|
69
|
+
def _url(self, path: str) -> str:
|
|
70
|
+
return f"{self.base_url}{path}"
|
|
71
|
+
|
|
72
|
+
def _should_retry(
|
|
73
|
+
self,
|
|
74
|
+
*,
|
|
75
|
+
method: str,
|
|
76
|
+
headers: Mapping[str, str],
|
|
77
|
+
attempt: int,
|
|
78
|
+
response: httpx.Response | None = None,
|
|
79
|
+
) -> bool:
|
|
80
|
+
if attempt >= self.max_retries:
|
|
81
|
+
return False
|
|
82
|
+
# POST/PATCH are only replayed when the request carries an
|
|
83
|
+
# Idempotency-Key, so a retry can never double-send.
|
|
84
|
+
if method.upper() not in _IDEMPOTENT_METHODS and "Idempotency-Key" not in headers:
|
|
85
|
+
return False
|
|
86
|
+
return response is None or response.status_code in _RETRYABLE_STATUSES
|
|
87
|
+
|
|
88
|
+
def _retry_delay(self, response: httpx.Response | None, attempt: int) -> float:
|
|
89
|
+
delay = min(0.5 * 2**attempt, 8.0) + random.uniform(0, 0.25)
|
|
90
|
+
if response is not None:
|
|
91
|
+
advertised = parse_retry_after(response)
|
|
92
|
+
if advertised is not None:
|
|
93
|
+
delay = max(delay, advertised)
|
|
94
|
+
return max(0.0, min(delay, _MAX_RETRY_DELAY))
|
|
95
|
+
|
|
96
|
+
@staticmethod
|
|
97
|
+
def _parse(response: httpx.Response) -> Any:
|
|
98
|
+
if response.status_code == 204 or not response.content:
|
|
99
|
+
return None
|
|
100
|
+
try:
|
|
101
|
+
return response.json()
|
|
102
|
+
except ValueError as exc:
|
|
103
|
+
raise SilonError(
|
|
104
|
+
f"Could not parse response body as JSON (HTTP {response.status_code})."
|
|
105
|
+
) from exc
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class Silon(_BaseClient):
|
|
109
|
+
"""Synchronous client for the Silon API.
|
|
110
|
+
|
|
111
|
+
Usage::
|
|
112
|
+
|
|
113
|
+
from silon import Silon
|
|
114
|
+
|
|
115
|
+
client = Silon(api_key="sk_live_...", workspace="acme")
|
|
116
|
+
sent = client.messages.send(
|
|
117
|
+
channel="whatsapp",
|
|
118
|
+
to={"client_id": "cust_001"},
|
|
119
|
+
content={"body": "Your order has shipped"},
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
Configuration falls back to the ``SILON_API_KEY``, ``SILON_WORKSPACE``
|
|
123
|
+
and ``SILON_BASE_URL`` environment variables.
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
def __init__(
|
|
127
|
+
self,
|
|
128
|
+
*,
|
|
129
|
+
api_key: str | None = None,
|
|
130
|
+
workspace: str | None = None,
|
|
131
|
+
base_url: str | None = None,
|
|
132
|
+
timeout: float | httpx.Timeout = DEFAULT_TIMEOUT,
|
|
133
|
+
max_retries: int = DEFAULT_MAX_RETRIES,
|
|
134
|
+
default_headers: Mapping[str, str] | None = None,
|
|
135
|
+
http_client: httpx.Client | None = None,
|
|
136
|
+
) -> None:
|
|
137
|
+
super().__init__(
|
|
138
|
+
api_key=api_key,
|
|
139
|
+
workspace=workspace,
|
|
140
|
+
base_url=base_url,
|
|
141
|
+
timeout=timeout,
|
|
142
|
+
max_retries=max_retries,
|
|
143
|
+
default_headers=default_headers,
|
|
144
|
+
)
|
|
145
|
+
self._http = http_client or httpx.Client(timeout=self.timeout)
|
|
146
|
+
|
|
147
|
+
from .resources import (
|
|
148
|
+
Auth,
|
|
149
|
+
Broadcasts,
|
|
150
|
+
Bulk,
|
|
151
|
+
ClientGroups,
|
|
152
|
+
Clients,
|
|
153
|
+
Events,
|
|
154
|
+
Messages,
|
|
155
|
+
Otp,
|
|
156
|
+
Profile,
|
|
157
|
+
Push,
|
|
158
|
+
Reports,
|
|
159
|
+
Suppressions,
|
|
160
|
+
Templates,
|
|
161
|
+
WebhookEndpoints,
|
|
162
|
+
WhatsAppTemplates,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
self.messages = Messages(self)
|
|
166
|
+
self.broadcasts = Broadcasts(self)
|
|
167
|
+
self.otp = Otp(self)
|
|
168
|
+
self.clients = Clients(self)
|
|
169
|
+
self.client_groups = ClientGroups(self)
|
|
170
|
+
self.bulk = Bulk(self)
|
|
171
|
+
self.reports = Reports(self)
|
|
172
|
+
self.whatsapp_templates = WhatsAppTemplates(self)
|
|
173
|
+
self.templates = Templates(self)
|
|
174
|
+
self.webhook_endpoints = WebhookEndpoints(self)
|
|
175
|
+
self.events = Events(self)
|
|
176
|
+
self.suppressions = Suppressions(self)
|
|
177
|
+
self.push = Push(self)
|
|
178
|
+
self.profile = Profile(self)
|
|
179
|
+
self.auth = Auth(self)
|
|
180
|
+
|
|
181
|
+
# -- transport ---------------------------------------------------------
|
|
182
|
+
|
|
183
|
+
def request(
|
|
184
|
+
self,
|
|
185
|
+
method: str,
|
|
186
|
+
path: str,
|
|
187
|
+
*,
|
|
188
|
+
params: Mapping[str, Any] | None = None,
|
|
189
|
+
json: Any = None,
|
|
190
|
+
files: Any = None,
|
|
191
|
+
headers: Mapping[str, str] | None = None,
|
|
192
|
+
) -> Any:
|
|
193
|
+
request_headers = self._headers(headers)
|
|
194
|
+
attempt = 0
|
|
195
|
+
while True:
|
|
196
|
+
try:
|
|
197
|
+
response = self._http.request(
|
|
198
|
+
method,
|
|
199
|
+
self._url(path),
|
|
200
|
+
params=params,
|
|
201
|
+
json=json,
|
|
202
|
+
files=files,
|
|
203
|
+
headers=request_headers,
|
|
204
|
+
)
|
|
205
|
+
except httpx.TimeoutException as exc:
|
|
206
|
+
if self._should_retry(method=method, headers=request_headers, attempt=attempt):
|
|
207
|
+
self._sleep(self._retry_delay(None, attempt))
|
|
208
|
+
attempt += 1
|
|
209
|
+
continue
|
|
210
|
+
raise APITimeoutError() from exc
|
|
211
|
+
except httpx.HTTPError as exc:
|
|
212
|
+
if self._should_retry(method=method, headers=request_headers, attempt=attempt):
|
|
213
|
+
self._sleep(self._retry_delay(None, attempt))
|
|
214
|
+
attempt += 1
|
|
215
|
+
continue
|
|
216
|
+
message = f"Connection error while requesting {path}: {exc}"
|
|
217
|
+
raise APIConnectionError(message) from exc
|
|
218
|
+
|
|
219
|
+
if response.status_code >= 400:
|
|
220
|
+
if self._should_retry(
|
|
221
|
+
method=method, headers=request_headers, attempt=attempt, response=response
|
|
222
|
+
):
|
|
223
|
+
self._sleep(self._retry_delay(response, attempt))
|
|
224
|
+
attempt += 1
|
|
225
|
+
continue
|
|
226
|
+
raise make_status_error(response)
|
|
227
|
+
return self._parse(response)
|
|
228
|
+
|
|
229
|
+
def get(self, path: str, *, params: Mapping[str, Any] | None = None) -> Any:
|
|
230
|
+
return self.request("GET", path, params=params)
|
|
231
|
+
|
|
232
|
+
def post(
|
|
233
|
+
self,
|
|
234
|
+
path: str,
|
|
235
|
+
*,
|
|
236
|
+
json: Any = None,
|
|
237
|
+
files: Any = None,
|
|
238
|
+
params: Mapping[str, Any] | None = None,
|
|
239
|
+
headers: Mapping[str, str] | None = None,
|
|
240
|
+
) -> Any:
|
|
241
|
+
return self.request("POST", path, json=json, files=files, params=params, headers=headers)
|
|
242
|
+
|
|
243
|
+
def patch(self, path: str, *, json: Any = None) -> Any:
|
|
244
|
+
return self.request("PATCH", path, json=json)
|
|
245
|
+
|
|
246
|
+
def put(self, path: str, *, json: Any = None) -> Any:
|
|
247
|
+
return self.request("PUT", path, json=json)
|
|
248
|
+
|
|
249
|
+
def delete(self, path: str) -> Any:
|
|
250
|
+
return self.request("DELETE", path)
|
|
251
|
+
|
|
252
|
+
# -- lifecycle ---------------------------------------------------------
|
|
253
|
+
|
|
254
|
+
@staticmethod
|
|
255
|
+
def _sleep(seconds: float) -> None:
|
|
256
|
+
time.sleep(seconds)
|
|
257
|
+
|
|
258
|
+
def close(self) -> None:
|
|
259
|
+
self._http.close()
|
|
260
|
+
|
|
261
|
+
def __enter__(self) -> Silon:
|
|
262
|
+
return self
|
|
263
|
+
|
|
264
|
+
def __exit__(self, *exc_info: object) -> None:
|
|
265
|
+
self.close()
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
class AsyncSilon(_BaseClient):
|
|
269
|
+
"""Asynchronous client for the Silon API.
|
|
270
|
+
|
|
271
|
+
Usage::
|
|
272
|
+
|
|
273
|
+
from silon import AsyncSilon
|
|
274
|
+
|
|
275
|
+
async with AsyncSilon(api_key="sk_live_...", workspace="acme") as client:
|
|
276
|
+
sent = await client.messages.send(
|
|
277
|
+
channel="sms",
|
|
278
|
+
to={"phone_number": "+96512345678"},
|
|
279
|
+
content={"body": "Your code is 424242"},
|
|
280
|
+
)
|
|
281
|
+
"""
|
|
282
|
+
|
|
283
|
+
def __init__(
|
|
284
|
+
self,
|
|
285
|
+
*,
|
|
286
|
+
api_key: str | None = None,
|
|
287
|
+
workspace: str | None = None,
|
|
288
|
+
base_url: str | None = None,
|
|
289
|
+
timeout: float | httpx.Timeout = DEFAULT_TIMEOUT,
|
|
290
|
+
max_retries: int = DEFAULT_MAX_RETRIES,
|
|
291
|
+
default_headers: Mapping[str, str] | None = None,
|
|
292
|
+
http_client: httpx.AsyncClient | None = None,
|
|
293
|
+
) -> None:
|
|
294
|
+
super().__init__(
|
|
295
|
+
api_key=api_key,
|
|
296
|
+
workspace=workspace,
|
|
297
|
+
base_url=base_url,
|
|
298
|
+
timeout=timeout,
|
|
299
|
+
max_retries=max_retries,
|
|
300
|
+
default_headers=default_headers,
|
|
301
|
+
)
|
|
302
|
+
self._http = http_client or httpx.AsyncClient(timeout=self.timeout)
|
|
303
|
+
|
|
304
|
+
from .resources import (
|
|
305
|
+
AsyncAuth,
|
|
306
|
+
AsyncBroadcasts,
|
|
307
|
+
AsyncBulk,
|
|
308
|
+
AsyncClientGroups,
|
|
309
|
+
AsyncClients,
|
|
310
|
+
AsyncEvents,
|
|
311
|
+
AsyncMessages,
|
|
312
|
+
AsyncOtp,
|
|
313
|
+
AsyncProfile,
|
|
314
|
+
AsyncPush,
|
|
315
|
+
AsyncReports,
|
|
316
|
+
AsyncSuppressions,
|
|
317
|
+
AsyncTemplates,
|
|
318
|
+
AsyncWebhookEndpoints,
|
|
319
|
+
AsyncWhatsAppTemplates,
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
self.messages = AsyncMessages(self)
|
|
323
|
+
self.broadcasts = AsyncBroadcasts(self)
|
|
324
|
+
self.otp = AsyncOtp(self)
|
|
325
|
+
self.clients = AsyncClients(self)
|
|
326
|
+
self.client_groups = AsyncClientGroups(self)
|
|
327
|
+
self.bulk = AsyncBulk(self)
|
|
328
|
+
self.reports = AsyncReports(self)
|
|
329
|
+
self.whatsapp_templates = AsyncWhatsAppTemplates(self)
|
|
330
|
+
self.templates = AsyncTemplates(self)
|
|
331
|
+
self.webhook_endpoints = AsyncWebhookEndpoints(self)
|
|
332
|
+
self.events = AsyncEvents(self)
|
|
333
|
+
self.suppressions = AsyncSuppressions(self)
|
|
334
|
+
self.push = AsyncPush(self)
|
|
335
|
+
self.profile = AsyncProfile(self)
|
|
336
|
+
self.auth = AsyncAuth(self)
|
|
337
|
+
|
|
338
|
+
# -- transport ---------------------------------------------------------
|
|
339
|
+
|
|
340
|
+
async def request(
|
|
341
|
+
self,
|
|
342
|
+
method: str,
|
|
343
|
+
path: str,
|
|
344
|
+
*,
|
|
345
|
+
params: Mapping[str, Any] | None = None,
|
|
346
|
+
json: Any = None,
|
|
347
|
+
files: Any = None,
|
|
348
|
+
headers: Mapping[str, str] | None = None,
|
|
349
|
+
) -> Any:
|
|
350
|
+
request_headers = self._headers(headers)
|
|
351
|
+
attempt = 0
|
|
352
|
+
while True:
|
|
353
|
+
try:
|
|
354
|
+
response = await self._http.request(
|
|
355
|
+
method,
|
|
356
|
+
self._url(path),
|
|
357
|
+
params=params,
|
|
358
|
+
json=json,
|
|
359
|
+
files=files,
|
|
360
|
+
headers=request_headers,
|
|
361
|
+
)
|
|
362
|
+
except httpx.TimeoutException as exc:
|
|
363
|
+
if self._should_retry(method=method, headers=request_headers, attempt=attempt):
|
|
364
|
+
await self._sleep(self._retry_delay(None, attempt))
|
|
365
|
+
attempt += 1
|
|
366
|
+
continue
|
|
367
|
+
raise APITimeoutError() from exc
|
|
368
|
+
except httpx.HTTPError as exc:
|
|
369
|
+
if self._should_retry(method=method, headers=request_headers, attempt=attempt):
|
|
370
|
+
await self._sleep(self._retry_delay(None, attempt))
|
|
371
|
+
attempt += 1
|
|
372
|
+
continue
|
|
373
|
+
message = f"Connection error while requesting {path}: {exc}"
|
|
374
|
+
raise APIConnectionError(message) from exc
|
|
375
|
+
|
|
376
|
+
if response.status_code >= 400:
|
|
377
|
+
if self._should_retry(
|
|
378
|
+
method=method, headers=request_headers, attempt=attempt, response=response
|
|
379
|
+
):
|
|
380
|
+
await self._sleep(self._retry_delay(response, attempt))
|
|
381
|
+
attempt += 1
|
|
382
|
+
continue
|
|
383
|
+
raise make_status_error(response)
|
|
384
|
+
return self._parse(response)
|
|
385
|
+
|
|
386
|
+
async def get(self, path: str, *, params: Mapping[str, Any] | None = None) -> Any:
|
|
387
|
+
return await self.request("GET", path, params=params)
|
|
388
|
+
|
|
389
|
+
async def post(
|
|
390
|
+
self,
|
|
391
|
+
path: str,
|
|
392
|
+
*,
|
|
393
|
+
json: Any = None,
|
|
394
|
+
files: Any = None,
|
|
395
|
+
params: Mapping[str, Any] | None = None,
|
|
396
|
+
headers: Mapping[str, str] | None = None,
|
|
397
|
+
) -> Any:
|
|
398
|
+
return await self.request(
|
|
399
|
+
"POST", path, json=json, files=files, params=params, headers=headers
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
async def patch(self, path: str, *, json: Any = None) -> Any:
|
|
403
|
+
return await self.request("PATCH", path, json=json)
|
|
404
|
+
|
|
405
|
+
async def put(self, path: str, *, json: Any = None) -> Any:
|
|
406
|
+
return await self.request("PUT", path, json=json)
|
|
407
|
+
|
|
408
|
+
async def delete(self, path: str) -> Any:
|
|
409
|
+
return await self.request("DELETE", path)
|
|
410
|
+
|
|
411
|
+
# -- lifecycle ---------------------------------------------------------
|
|
412
|
+
|
|
413
|
+
@staticmethod
|
|
414
|
+
async def _sleep(seconds: float) -> None:
|
|
415
|
+
await asyncio.sleep(seconds)
|
|
416
|
+
|
|
417
|
+
async def close(self) -> None:
|
|
418
|
+
await self._http.aclose()
|
|
419
|
+
|
|
420
|
+
async def __aenter__(self) -> AsyncSilon:
|
|
421
|
+
return self
|
|
422
|
+
|
|
423
|
+
async def __aexit__(self, *exc_info: object) -> None:
|
|
424
|
+
await self.close()
|