b24api 0.3.10__tar.gz → 0.4.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.
- {b24api-0.3.10/b24api.egg-info → b24api-0.4.1}/PKG-INFO +1 -1
- {b24api-0.3.10 → b24api-0.4.1}/b24api/api.py +56 -16
- {b24api-0.3.10 → b24api-0.4.1}/b24api/entity.py +3 -1
- {b24api-0.3.10 → b24api-0.4.1}/b24api/error.py +4 -9
- {b24api-0.3.10 → b24api-0.4.1/b24api.egg-info}/PKG-INFO +1 -1
- {b24api-0.3.10 → b24api-0.4.1}/LICENSE +0 -0
- {b24api-0.3.10 → b24api-0.4.1}/MANIFEST.in +0 -0
- {b24api-0.3.10 → b24api-0.4.1}/README.md +0 -0
- {b24api-0.3.10 → b24api-0.4.1}/b24api/__init__.py +0 -0
- {b24api-0.3.10 → b24api-0.4.1}/b24api/query.py +0 -0
- {b24api-0.3.10 → b24api-0.4.1}/b24api/settings.py +0 -0
- {b24api-0.3.10 → b24api-0.4.1}/b24api/type.py +0 -0
- {b24api-0.3.10 → b24api-0.4.1}/b24api.egg-info/SOURCES.txt +0 -0
- {b24api-0.3.10 → b24api-0.4.1}/b24api.egg-info/dependency_links.txt +0 -0
- {b24api-0.3.10 → b24api-0.4.1}/b24api.egg-info/requires.txt +0 -0
- {b24api-0.3.10 → b24api-0.4.1}/b24api.egg-info/top_level.txt +0 -0
- {b24api-0.3.10 → b24api-0.4.1}/pyproject.toml +0 -0
- {b24api-0.3.10 → b24api-0.4.1}/setup.cfg +0 -0
|
@@ -3,6 +3,7 @@ import logging
|
|
|
3
3
|
from collections.abc import Generator, Iterable
|
|
4
4
|
from itertools import chain, islice
|
|
5
5
|
from operator import itemgetter
|
|
6
|
+
from typing import Any
|
|
6
7
|
|
|
7
8
|
import h2.exceptions
|
|
8
9
|
import httpx
|
|
@@ -58,7 +59,7 @@ class Bitrix24:
|
|
|
58
59
|
)
|
|
59
60
|
|
|
60
61
|
with contextlib.suppress(httpx.ResponseNotRead, ValidationError):
|
|
61
|
-
ErrorResponse.model_validate_json(http_response.content).raise_error(self.settings.retry_errors)
|
|
62
|
+
ErrorResponse.model_validate_json(http_response.content).raise_error(request, self.settings.retry_errors)
|
|
62
63
|
|
|
63
64
|
try:
|
|
64
65
|
http_response.raise_for_status()
|
|
@@ -78,16 +79,26 @@ class Bitrix24:
|
|
|
78
79
|
|
|
79
80
|
def batch(
|
|
80
81
|
self,
|
|
81
|
-
requests: Iterable[Request | dict],
|
|
82
|
+
requests: Iterable[Request | dict | tuple[Request | dict, Any]],
|
|
83
|
+
*,
|
|
82
84
|
batch_size: int | None = None,
|
|
83
|
-
|
|
85
|
+
with_payload: bool = False,
|
|
86
|
+
) -> Generator[ApiTypes | tuple[ApiTypes, Any]]:
|
|
84
87
|
"""Call unlimited sequence of methods within batches and return `result` from responses."""
|
|
85
88
|
batch_size = batch_size or self.settings.batch_size
|
|
86
89
|
|
|
87
90
|
tail_requests = iter(requests)
|
|
88
91
|
while batched_requests := list(islice(tail_requests, batch_size)):
|
|
89
|
-
|
|
90
|
-
|
|
92
|
+
if with_payload:
|
|
93
|
+
batched_requests, batched_payloads = zip(*batched_requests, strict=True)
|
|
94
|
+
else:
|
|
95
|
+
batched_payloads = None
|
|
96
|
+
|
|
97
|
+
for i, response in enumerate(self.retry(self._batch, batched_requests)):
|
|
98
|
+
if with_payload:
|
|
99
|
+
yield response.result, batched_payloads[i]
|
|
100
|
+
else:
|
|
101
|
+
yield response.result
|
|
91
102
|
|
|
92
103
|
def _batch(self, requests: Iterable[Request | dict]) -> list[Response]:
|
|
93
104
|
"""Call limited batch of methods and return full responses."""
|
|
@@ -108,7 +119,10 @@ class Bitrix24:
|
|
|
108
119
|
key = f"_{i}"
|
|
109
120
|
|
|
110
121
|
if key in result.result_error:
|
|
111
|
-
ErrorResponse.model_validate(result.result_error[key]).raise_error(
|
|
122
|
+
ErrorResponse.model_validate(result.result_error[key]).raise_error(
|
|
123
|
+
commands[key],
|
|
124
|
+
self.settings.retry_errors,
|
|
125
|
+
)
|
|
112
126
|
|
|
113
127
|
command = commands[key]
|
|
114
128
|
if key not in result.result:
|
|
@@ -134,6 +148,7 @@ class Bitrix24:
|
|
|
134
148
|
def list_sequential(
|
|
135
149
|
self,
|
|
136
150
|
request: Request | dict,
|
|
151
|
+
*,
|
|
137
152
|
list_size: int | None = None,
|
|
138
153
|
) -> Generator[ApiTypes]:
|
|
139
154
|
"""Call `list` method and return full `result`.
|
|
@@ -167,6 +182,7 @@ class Bitrix24:
|
|
|
167
182
|
def list_batched(
|
|
168
183
|
self,
|
|
169
184
|
request: Request | dict,
|
|
185
|
+
*,
|
|
170
186
|
list_size: int | None = None,
|
|
171
187
|
batch_size: int | None = None,
|
|
172
188
|
) -> Generator[ApiTypes]:
|
|
@@ -194,7 +210,7 @@ class Bitrix24:
|
|
|
194
210
|
tail_request.parameters["start"] = start
|
|
195
211
|
yield tail_request
|
|
196
212
|
|
|
197
|
-
tail_responses = self.batch(_tail_requests(), batch_size)
|
|
213
|
+
tail_responses = self.batch(_tail_requests(), batch_size=batch_size)
|
|
198
214
|
tail_responses = map(self._fix_list_result, tail_responses)
|
|
199
215
|
tail_responses = chain.from_iterable(tail_responses)
|
|
200
216
|
yield from tail_responses
|
|
@@ -202,6 +218,7 @@ class Bitrix24:
|
|
|
202
218
|
def list_batched_no_count(
|
|
203
219
|
self,
|
|
204
220
|
request: ListRequest | dict,
|
|
221
|
+
*,
|
|
205
222
|
id_key: str = "ID",
|
|
206
223
|
list_size: int | None = None,
|
|
207
224
|
batch_size: int | None = None,
|
|
@@ -238,7 +255,7 @@ class Bitrix24:
|
|
|
238
255
|
tail_request.parameters.start = -1
|
|
239
256
|
tail_request.parameters.order = {"ID": "DESC"}
|
|
240
257
|
|
|
241
|
-
head_tail_result = self.batch([head_request, tail_request])
|
|
258
|
+
head_tail_result = self.batch([head_request, tail_request], batch_size=batch_size)
|
|
242
259
|
head_result, tail_result = tuple(map(self._fix_list_result, head_tail_result))
|
|
243
260
|
yield from head_result
|
|
244
261
|
|
|
@@ -253,7 +270,7 @@ class Bitrix24:
|
|
|
253
270
|
yield body_request
|
|
254
271
|
|
|
255
272
|
if max_head_id and min_tail_id and max_head_id < min_tail_id:
|
|
256
|
-
body = self.batch(_body_requests(), batch_size)
|
|
273
|
+
body = self.batch(_body_requests(), batch_size=batch_size)
|
|
257
274
|
body = map(self._fix_list_result, body)
|
|
258
275
|
body = chain.from_iterable(body)
|
|
259
276
|
yield from body
|
|
@@ -265,11 +282,13 @@ class Bitrix24:
|
|
|
265
282
|
def reference_batched_no_count(
|
|
266
283
|
self,
|
|
267
284
|
request: ListRequest | dict,
|
|
268
|
-
updates: Iterable[dict],
|
|
285
|
+
updates: Iterable[dict | tuple[dict, Any]],
|
|
286
|
+
*,
|
|
269
287
|
id_key: str = "ID",
|
|
270
288
|
list_size: int | None = None,
|
|
271
289
|
batch_size: int | None = None,
|
|
272
|
-
|
|
290
|
+
with_payload: bool = False,
|
|
291
|
+
) -> Generator[ApiTypes | tuple[ApiTypes, Any]]:
|
|
273
292
|
"""Call `list` method with reference `updates` for `filter` and return full `result`.
|
|
274
293
|
|
|
275
294
|
Fastest (batched, no count) list gathering for methods with `filter` parameter and required `reference`
|
|
@@ -295,8 +314,13 @@ class Bitrix24:
|
|
|
295
314
|
if request.parameters.order:
|
|
296
315
|
raise ValueError("Ordering parameters are reserved `order`in `reference_batched_no_count`")
|
|
297
316
|
|
|
298
|
-
def _tail_requests() -> Generator[ListRequest]:
|
|
317
|
+
def _tail_requests() -> Generator[ListRequest | tuple[ListRequest, Any]]:
|
|
299
318
|
for update in updates:
|
|
319
|
+
if with_payload:
|
|
320
|
+
update, payload = update
|
|
321
|
+
else:
|
|
322
|
+
payload = None
|
|
323
|
+
|
|
300
324
|
if id_from in update:
|
|
301
325
|
raise ValueError(
|
|
302
326
|
f"Filter parameters `{id_from}` is reserved in `reference_batched_no_count`",
|
|
@@ -305,21 +329,37 @@ class Bitrix24:
|
|
|
305
329
|
tail_request.parameters.filter |= update
|
|
306
330
|
tail_request.parameters.start = -1
|
|
307
331
|
tail_request.parameters.order = {"ID": "ASC"}
|
|
308
|
-
|
|
332
|
+
|
|
333
|
+
if with_payload:
|
|
334
|
+
yield tail_request, payload
|
|
335
|
+
else:
|
|
336
|
+
yield tail_request
|
|
309
337
|
|
|
310
338
|
head_requests = []
|
|
311
339
|
tail_requests = iter(_tail_requests())
|
|
312
340
|
while body_requests := head_requests + list(islice(tail_requests, batch_size - len(head_requests))):
|
|
313
|
-
|
|
341
|
+
if with_payload:
|
|
342
|
+
body_requests, body_payloads = zip(*body_requests, strict=True)
|
|
343
|
+
else:
|
|
344
|
+
body_payloads = None
|
|
345
|
+
|
|
346
|
+
body_results = self.batch(body_requests, batch_size=batch_size)
|
|
314
347
|
body_results = map(self._fix_list_result, body_results)
|
|
315
348
|
|
|
316
349
|
head_requests = []
|
|
317
|
-
for body_request, body_result in zip(body_requests, body_results, strict=True):
|
|
350
|
+
for i, (body_request, body_result) in enumerate(zip(body_requests, body_results, strict=True)):
|
|
318
351
|
if len(body_result) == list_size:
|
|
319
352
|
max_id = max(map(int, map(get_id, body_result)), default=None)
|
|
320
353
|
head_request = body_request.model_copy(deep=True)
|
|
321
354
|
head_request.parameters.filter[id_from] = max_id
|
|
322
|
-
|
|
355
|
+
if with_payload:
|
|
356
|
+
head_requests.append((head_request, body_payloads[i]))
|
|
357
|
+
else:
|
|
358
|
+
head_requests.append(head_request)
|
|
359
|
+
|
|
360
|
+
if with_payload:
|
|
361
|
+
body_payload = [body_payloads[i]] * len(body_result)
|
|
362
|
+
body_result = zip(body_result, body_payload, strict=True)
|
|
323
363
|
|
|
324
364
|
yield from body_result
|
|
325
365
|
|
|
@@ -57,15 +57,17 @@ class ErrorResponse(BaseModel):
|
|
|
57
57
|
value = value.lower()
|
|
58
58
|
return value
|
|
59
59
|
|
|
60
|
-
def raise_error(self, retry_errors: list[str]) -> None:
|
|
60
|
+
def raise_error(self, request: Request, retry_errors: list[str]) -> None:
|
|
61
61
|
if self.error in retry_errors:
|
|
62
62
|
raise RetryApiResponseError(
|
|
63
63
|
code=self.error,
|
|
64
64
|
description=self.error_description,
|
|
65
|
+
request=str(request),
|
|
65
66
|
)
|
|
66
67
|
raise ApiResponseError(
|
|
67
68
|
code=self.error,
|
|
68
69
|
description=self.error_description,
|
|
70
|
+
request=str(request),
|
|
69
71
|
)
|
|
70
72
|
|
|
71
73
|
|
|
@@ -8,18 +8,13 @@ class RetryHTTPStatusError(httpx.HTTPStatusError):
|
|
|
8
8
|
class ApiResponseError(Exception):
|
|
9
9
|
"""API error with description."""
|
|
10
10
|
|
|
11
|
-
def __init__(
|
|
12
|
-
self,
|
|
13
|
-
*,
|
|
14
|
-
code: str,
|
|
15
|
-
description: str | None,
|
|
16
|
-
) -> None:
|
|
11
|
+
def __init__(self, *, code: str, description: str | None, request: str) -> None:
|
|
17
12
|
if code and description:
|
|
18
|
-
message = f"API error [{code}]: {description}"
|
|
13
|
+
message = f"API error [{code}]: {description} for request {request}"
|
|
19
14
|
elif code:
|
|
20
|
-
message = f"API error [{code}]"
|
|
15
|
+
message = f"API error [{code}] for request {request}"
|
|
21
16
|
else:
|
|
22
|
-
message = f"API error: {description}"
|
|
17
|
+
message = f"API error: {description} for request {request}"
|
|
23
18
|
super().__init__(message)
|
|
24
19
|
|
|
25
20
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|