cognite-toolkit 0.7.55__py3-none-any.whl → 0.7.57__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.
- cognite_toolkit/_cdf_tk/apps/_auth_app.py +2 -2
- cognite_toolkit/_cdf_tk/apps/_core_app.py +4 -4
- cognite_toolkit/_cdf_tk/apps/_dev_app.py +10 -1
- cognite_toolkit/_cdf_tk/apps/_download_app.py +13 -12
- cognite_toolkit/_cdf_tk/apps/_dump_app.py +13 -13
- cognite_toolkit/_cdf_tk/apps/_landing_app.py +10 -1
- cognite_toolkit/_cdf_tk/apps/_migrate_app.py +13 -13
- cognite_toolkit/_cdf_tk/apps/_modules_app.py +29 -5
- cognite_toolkit/_cdf_tk/apps/_profile_app.py +4 -4
- cognite_toolkit/_cdf_tk/apps/_purge.py +4 -5
- cognite_toolkit/_cdf_tk/apps/_repo_app.py +9 -2
- cognite_toolkit/_cdf_tk/apps/_run.py +5 -4
- cognite_toolkit/_cdf_tk/apps/_upload_app.py +2 -2
- cognite_toolkit/_cdf_tk/client/api/agents.py +2 -4
- cognite_toolkit/_cdf_tk/client/api/annotations.py +2 -2
- cognite_toolkit/_cdf_tk/client/api/assets.py +3 -5
- cognite_toolkit/_cdf_tk/client/api/containers.py +2 -2
- cognite_toolkit/_cdf_tk/client/api/data_models.py +2 -2
- cognite_toolkit/_cdf_tk/client/api/datasets.py +3 -3
- cognite_toolkit/_cdf_tk/client/api/events.py +3 -5
- cognite_toolkit/_cdf_tk/client/api/extraction_pipelines.py +3 -3
- cognite_toolkit/_cdf_tk/client/api/filemetadata.py +6 -6
- cognite_toolkit/_cdf_tk/client/api/function_schedules.py +2 -2
- cognite_toolkit/_cdf_tk/client/api/functions.py +2 -2
- cognite_toolkit/_cdf_tk/client/api/graphql_data_models.py +5 -5
- cognite_toolkit/_cdf_tk/client/api/groups.py +5 -7
- cognite_toolkit/_cdf_tk/client/api/hosted_extractor_destinations.py +3 -3
- cognite_toolkit/_cdf_tk/client/api/hosted_extractor_jobs.py +3 -3
- cognite_toolkit/_cdf_tk/client/api/hosted_extractor_mappings.py +3 -3
- cognite_toolkit/_cdf_tk/client/api/hosted_extractor_sources.py +4 -4
- cognite_toolkit/_cdf_tk/client/api/infield.py +8 -8
- cognite_toolkit/_cdf_tk/client/api/instances.py +3 -3
- cognite_toolkit/_cdf_tk/client/api/labels.py +3 -5
- cognite_toolkit/_cdf_tk/client/api/legacy/extended_functions.py +3 -3
- cognite_toolkit/_cdf_tk/client/api/location_filters.py +8 -8
- cognite_toolkit/_cdf_tk/client/api/project.py +14 -2
- cognite_toolkit/_cdf_tk/client/api/raw.py +5 -5
- cognite_toolkit/_cdf_tk/client/api/relationships.py +2 -2
- cognite_toolkit/_cdf_tk/client/api/robotics_capabilities.py +3 -3
- cognite_toolkit/_cdf_tk/client/api/robotics_data_postprocessing.py +3 -3
- cognite_toolkit/_cdf_tk/client/api/robotics_frames.py +3 -3
- cognite_toolkit/_cdf_tk/client/api/robotics_locations.py +3 -3
- cognite_toolkit/_cdf_tk/client/api/robotics_maps.py +3 -3
- cognite_toolkit/_cdf_tk/client/api/robotics_robots.py +3 -5
- cognite_toolkit/_cdf_tk/client/api/search_config.py +5 -5
- cognite_toolkit/_cdf_tk/client/api/security_categories.py +3 -3
- cognite_toolkit/_cdf_tk/client/api/sequences.py +3 -3
- cognite_toolkit/_cdf_tk/client/api/simulator_models.py +3 -3
- cognite_toolkit/_cdf_tk/client/api/spaces.py +2 -4
- cognite_toolkit/_cdf_tk/client/api/streams.py +6 -6
- cognite_toolkit/_cdf_tk/client/api/three_d.py +5 -5
- cognite_toolkit/_cdf_tk/client/api/timeseries.py +3 -3
- cognite_toolkit/_cdf_tk/client/api/transformations.py +3 -3
- cognite_toolkit/_cdf_tk/client/api/views.py +2 -4
- cognite_toolkit/_cdf_tk/client/api/workflow_triggers.py +3 -3
- cognite_toolkit/_cdf_tk/client/api/workflow_versions.py +3 -3
- cognite_toolkit/_cdf_tk/client/api/workflows.py +3 -3
- cognite_toolkit/_cdf_tk/client/cdf_client/api.py +11 -11
- cognite_toolkit/_cdf_tk/client/http_client/__init__.py +13 -51
- cognite_toolkit/_cdf_tk/client/http_client/_client.py +48 -209
- cognite_toolkit/_cdf_tk/client/http_client/_data_classes.py +106 -383
- cognite_toolkit/_cdf_tk/client/http_client/_item_classes.py +16 -16
- cognite_toolkit/_cdf_tk/client/resource_classes/filemetadata.py +7 -1
- cognite_toolkit/_cdf_tk/client/resource_classes/project.py +30 -0
- cognite_toolkit/_cdf_tk/commands/_base.py +18 -1
- cognite_toolkit/_cdf_tk/commands/_import_cmd.py +3 -1
- cognite_toolkit/_cdf_tk/commands/_migrate/command.py +8 -8
- cognite_toolkit/_cdf_tk/commands/_migrate/migration_io.py +25 -24
- cognite_toolkit/_cdf_tk/commands/_profile.py +10 -5
- cognite_toolkit/_cdf_tk/commands/_purge.py +30 -35
- cognite_toolkit/_cdf_tk/commands/_upload.py +4 -6
- cognite_toolkit/_cdf_tk/commands/build_cmd.py +2 -1
- cognite_toolkit/_cdf_tk/commands/build_v2/build_cmd.py +8 -2
- cognite_toolkit/_cdf_tk/commands/deploy.py +8 -2
- cognite_toolkit/_cdf_tk/commands/init.py +9 -2
- cognite_toolkit/_cdf_tk/commands/modules.py +3 -1
- cognite_toolkit/_cdf_tk/commands/pull.py +8 -2
- cognite_toolkit/_cdf_tk/commands/repo.py +3 -1
- cognite_toolkit/_cdf_tk/commands/resources.py +0 -3
- cognite_toolkit/_cdf_tk/data_classes/_tracking_info.py +1 -0
- cognite_toolkit/_cdf_tk/protocols.py +3 -1
- cognite_toolkit/_cdf_tk/storageio/_applications.py +9 -9
- cognite_toolkit/_cdf_tk/storageio/_base.py +15 -10
- cognite_toolkit/_cdf_tk/storageio/_datapoints.py +36 -24
- cognite_toolkit/_cdf_tk/storageio/_file_content.py +47 -43
- cognite_toolkit/_cdf_tk/storageio/_raw.py +5 -4
- cognite_toolkit/_repo_files/GitHub/.github/workflows/deploy.yaml +1 -1
- cognite_toolkit/_repo_files/GitHub/.github/workflows/dry-run.yaml +1 -1
- cognite_toolkit/_resources/cdf.toml +1 -1
- cognite_toolkit/_version.py +1 -1
- {cognite_toolkit-0.7.55.dist-info → cognite_toolkit-0.7.57.dist-info}/METADATA +1 -1
- {cognite_toolkit-0.7.55.dist-info → cognite_toolkit-0.7.57.dist-info}/RECORD +94 -94
- cognite_toolkit/_cdf_tk/client/http_client/_data_classes2.py +0 -151
- {cognite_toolkit-0.7.55.dist-info → cognite_toolkit-0.7.57.dist-info}/WHEEL +0 -0
- {cognite_toolkit-0.7.55.dist-info → cognite_toolkit-0.7.57.dist-info}/entry_points.txt +0 -0
|
@@ -22,10 +22,10 @@ from cognite_toolkit._cdf_tk.client._resource_base import (
|
|
|
22
22
|
)
|
|
23
23
|
from cognite_toolkit._cdf_tk.client.http_client import (
|
|
24
24
|
HTTPClient,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
ItemsRequest,
|
|
26
|
+
ItemsSuccessResponse,
|
|
27
|
+
RequestMessage,
|
|
28
|
+
SuccessResponse,
|
|
29
29
|
)
|
|
30
30
|
from cognite_toolkit._cdf_tk.utils.collection import chunker_sequence
|
|
31
31
|
|
|
@@ -79,7 +79,7 @@ class CDFResourceAPI(Generic[T_Identifier, T_RequestResource, T_ResponseResource
|
|
|
79
79
|
|
|
80
80
|
@abstractmethod
|
|
81
81
|
def _validate_page_response(
|
|
82
|
-
self, response:
|
|
82
|
+
self, response: SuccessResponse | ItemsSuccessResponse
|
|
83
83
|
) -> PagedResponse[T_ResponseResource]:
|
|
84
84
|
"""Parse a single item response."""
|
|
85
85
|
raise NotImplementedError()
|
|
@@ -131,7 +131,7 @@ class CDFResourceAPI(Generic[T_Identifier, T_RequestResource, T_ResponseResource
|
|
|
131
131
|
params: dict[str, Any] | None = None,
|
|
132
132
|
extra_body: dict[str, Any] | None = None,
|
|
133
133
|
endpoint_path: str | None = None,
|
|
134
|
-
) -> Iterable[
|
|
134
|
+
) -> Iterable[SuccessResponse]:
|
|
135
135
|
"""Send requests in chunks and yield responses.
|
|
136
136
|
|
|
137
137
|
Args:
|
|
@@ -150,7 +150,7 @@ class CDFResourceAPI(Generic[T_Identifier, T_RequestResource, T_ResponseResource
|
|
|
150
150
|
endpoint = self._method_endpoint_map[method]
|
|
151
151
|
|
|
152
152
|
for chunk in chunker_sequence(items, endpoint.item_limit):
|
|
153
|
-
request =
|
|
153
|
+
request = RequestMessage(
|
|
154
154
|
endpoint_url=f"{self._make_url(endpoint_path or endpoint.path)}",
|
|
155
155
|
method=endpoint.method,
|
|
156
156
|
body_content={"items": serialization(chunk), **(extra_body or {})}, # type: ignore[dict-item]
|
|
@@ -212,7 +212,7 @@ class CDFResourceAPI(Generic[T_Identifier, T_RequestResource, T_ResponseResource
|
|
|
212
212
|
method: APIMethod,
|
|
213
213
|
params: dict[str, Any] | None = None,
|
|
214
214
|
extra_body: dict[str, Any] | None = None,
|
|
215
|
-
) -> Iterable[
|
|
215
|
+
) -> Iterable[ItemsSuccessResponse]:
|
|
216
216
|
"""Request items with retries and splitting on failures.
|
|
217
217
|
|
|
218
218
|
This method handles large batches of items by chunking them according to the endpoint's item limit.
|
|
@@ -235,7 +235,7 @@ class CDFResourceAPI(Generic[T_Identifier, T_RequestResource, T_ResponseResource
|
|
|
235
235
|
endpoint = self._method_endpoint_map[method]
|
|
236
236
|
|
|
237
237
|
for chunk in chunker_sequence(items, endpoint.item_limit):
|
|
238
|
-
request =
|
|
238
|
+
request = ItemsRequest(
|
|
239
239
|
endpoint_url=f"{self._make_url(endpoint.path)}",
|
|
240
240
|
method=endpoint.method,
|
|
241
241
|
parameters=request_params,
|
|
@@ -245,7 +245,7 @@ class CDFResourceAPI(Generic[T_Identifier, T_RequestResource, T_ResponseResource
|
|
|
245
245
|
)
|
|
246
246
|
responses = self._http_client.request_items_retries(request)
|
|
247
247
|
for response in responses:
|
|
248
|
-
if isinstance(response,
|
|
248
|
+
if isinstance(response, ItemsSuccessResponse):
|
|
249
249
|
yield response
|
|
250
250
|
|
|
251
251
|
@classmethod
|
|
@@ -309,7 +309,7 @@ class CDFResourceAPI(Generic[T_Identifier, T_RequestResource, T_ResponseResource
|
|
|
309
309
|
else:
|
|
310
310
|
raise NotImplementedError(f"Unsupported method {endpoint.method} for pagination.")
|
|
311
311
|
|
|
312
|
-
request =
|
|
312
|
+
request = RequestMessage(
|
|
313
313
|
endpoint_url=self._make_url(endpoint_path or endpoint.path),
|
|
314
314
|
method=endpoint.method,
|
|
315
315
|
parameters=request_params,
|
|
@@ -1,71 +1,33 @@
|
|
|
1
1
|
from ._client import HTTPClient
|
|
2
2
|
from ._data_classes import (
|
|
3
|
-
T_COVARIANT_ID,
|
|
4
|
-
DataBodyRequest,
|
|
5
3
|
ErrorDetails,
|
|
6
|
-
|
|
7
|
-
FailedRequestMessage,
|
|
4
|
+
FailedRequest,
|
|
8
5
|
FailedResponse,
|
|
9
|
-
|
|
10
|
-
HTTPMessage,
|
|
11
|
-
ItemMessage,
|
|
12
|
-
ItemsRequest,
|
|
13
|
-
ParamRequest,
|
|
14
|
-
RequestItem,
|
|
6
|
+
HTTPResult,
|
|
15
7
|
RequestMessage,
|
|
16
|
-
ResponseList,
|
|
17
|
-
ResponseMessage,
|
|
18
|
-
SimpleBodyRequest,
|
|
19
8
|
SuccessResponse,
|
|
20
|
-
SuccessResponseItems,
|
|
21
|
-
)
|
|
22
|
-
from ._data_classes2 import (
|
|
23
|
-
ErrorDetails2,
|
|
24
|
-
FailedRequest2,
|
|
25
|
-
FailedResponse2,
|
|
26
|
-
HTTPResult2,
|
|
27
|
-
RequestMessage2,
|
|
28
|
-
SuccessResponse2,
|
|
29
9
|
)
|
|
30
10
|
from ._exception import ToolkitAPIError
|
|
31
11
|
from ._item_classes import (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
12
|
+
ItemsFailedRequest,
|
|
13
|
+
ItemsFailedResponse,
|
|
14
|
+
ItemsRequest,
|
|
15
|
+
ItemsResultMessage,
|
|
16
|
+
ItemsSuccessResponse,
|
|
37
17
|
)
|
|
38
18
|
|
|
39
19
|
__all__ = [
|
|
40
|
-
"T_COVARIANT_ID",
|
|
41
|
-
"DataBodyRequest",
|
|
42
20
|
"ErrorDetails",
|
|
43
|
-
"
|
|
44
|
-
"FailedRequest2",
|
|
45
|
-
"FailedRequestItems",
|
|
46
|
-
"FailedRequestMessage",
|
|
21
|
+
"FailedRequest",
|
|
47
22
|
"FailedResponse",
|
|
48
|
-
"FailedResponse2",
|
|
49
|
-
"FailedResponseItems",
|
|
50
23
|
"HTTPClient",
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"ItemsFailedRequest2",
|
|
55
|
-
"ItemsFailedResponse2",
|
|
24
|
+
"HTTPResult",
|
|
25
|
+
"ItemsFailedRequest",
|
|
26
|
+
"ItemsFailedResponse",
|
|
56
27
|
"ItemsRequest",
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"ItemsSuccessResponse2",
|
|
60
|
-
"ParamRequest",
|
|
61
|
-
"RequestItem",
|
|
28
|
+
"ItemsResultMessage",
|
|
29
|
+
"ItemsSuccessResponse",
|
|
62
30
|
"RequestMessage",
|
|
63
|
-
"RequestMessage2",
|
|
64
|
-
"ResponseList",
|
|
65
|
-
"ResponseMessage",
|
|
66
|
-
"SimpleBodyRequest",
|
|
67
31
|
"SuccessResponse",
|
|
68
|
-
"SuccessResponse2",
|
|
69
|
-
"SuccessResponseItems",
|
|
70
32
|
"ToolkitAPIError",
|
|
71
33
|
]
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import gzip
|
|
2
1
|
import random
|
|
3
2
|
import sys
|
|
4
3
|
import time
|
|
@@ -11,36 +10,24 @@ from cognite.client import global_config
|
|
|
11
10
|
from rich.console import Console
|
|
12
11
|
|
|
13
12
|
from cognite_toolkit._cdf_tk.client.http_client._data_classes import (
|
|
14
|
-
BodyRequest,
|
|
15
|
-
DataBodyRequest,
|
|
16
|
-
FailedRequestMessage,
|
|
17
|
-
HTTPMessage,
|
|
18
|
-
ItemsRequest,
|
|
19
|
-
ParamRequest,
|
|
20
|
-
RequestMessage,
|
|
21
|
-
ResponseList,
|
|
22
|
-
ResponseMessage,
|
|
23
|
-
)
|
|
24
|
-
from cognite_toolkit._cdf_tk.client.http_client._data_classes2 import (
|
|
25
13
|
BaseRequestMessage,
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
14
|
+
ErrorDetails,
|
|
15
|
+
FailedRequest,
|
|
16
|
+
FailedResponse,
|
|
17
|
+
HTTPResult,
|
|
18
|
+
RequestMessage,
|
|
19
|
+
SuccessResponse,
|
|
32
20
|
)
|
|
33
21
|
from cognite_toolkit._cdf_tk.client.http_client._item_classes import (
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
22
|
+
ItemsFailedRequest,
|
|
23
|
+
ItemsFailedResponse,
|
|
24
|
+
ItemsRequest,
|
|
37
25
|
ItemsResultList,
|
|
38
|
-
|
|
39
|
-
|
|
26
|
+
ItemsResultMessage,
|
|
27
|
+
ItemsSuccessResponse,
|
|
40
28
|
)
|
|
41
29
|
from cognite_toolkit._cdf_tk.tk_warnings import HighSeverityWarning
|
|
42
30
|
from cognite_toolkit._cdf_tk.utils.auxiliary import get_current_toolkit_version, get_user_agent
|
|
43
|
-
from cognite_toolkit._cdf_tk.utils.useful_types import PrimitiveType
|
|
44
31
|
|
|
45
32
|
if sys.version_info >= (3, 11):
|
|
46
33
|
from typing import Self
|
|
@@ -102,63 +89,6 @@ class HTTPClient:
|
|
|
102
89
|
self.session.close()
|
|
103
90
|
return False # Do not suppress exceptions
|
|
104
91
|
|
|
105
|
-
def request(self, message: RequestMessage) -> Sequence[HTTPMessage]:
|
|
106
|
-
"""Send an HTTP request and return the response.
|
|
107
|
-
|
|
108
|
-
Args:
|
|
109
|
-
message (RequestMessage): The request message to send.
|
|
110
|
-
|
|
111
|
-
Returns:
|
|
112
|
-
Sequence[HTTPMessage]: The response message(s). This can also
|
|
113
|
-
include RequestMessage(s) to be retried.
|
|
114
|
-
"""
|
|
115
|
-
if isinstance(message, ItemsRequest) and message.tracker and message.tracker.limit_reached():
|
|
116
|
-
error_msg = (
|
|
117
|
-
f"Aborting further splitting of requests after {message.tracker.failed_split_count} failed attempts."
|
|
118
|
-
)
|
|
119
|
-
return message.create_failed_request(error_msg)
|
|
120
|
-
try:
|
|
121
|
-
response = self._make_request(message)
|
|
122
|
-
results = self._handle_response(response, message)
|
|
123
|
-
except Exception as e:
|
|
124
|
-
results = self._handle_error(e, message)
|
|
125
|
-
return results
|
|
126
|
-
|
|
127
|
-
def request_with_retries(self, message: RequestMessage) -> ResponseList:
|
|
128
|
-
"""Send an HTTP request and handle retries.
|
|
129
|
-
|
|
130
|
-
This method will keep retrying the request until it either succeeds or
|
|
131
|
-
exhausts the maximum number of retries.
|
|
132
|
-
|
|
133
|
-
Note this method will use the current thread to process all request, thus
|
|
134
|
-
it is blocking.
|
|
135
|
-
|
|
136
|
-
Args:
|
|
137
|
-
message (RequestMessage): The request message to send.
|
|
138
|
-
|
|
139
|
-
Returns:
|
|
140
|
-
Sequence[ResponseMessage | FailedRequestMessage]: The final response
|
|
141
|
-
messages, which can be either successful responses or failed requests.
|
|
142
|
-
"""
|
|
143
|
-
if message.total_attempts > 0:
|
|
144
|
-
raise RuntimeError(f"RequestMessage has already been attempted {message.total_attempts} times.")
|
|
145
|
-
pending_requests: deque[RequestMessage] = deque()
|
|
146
|
-
pending_requests.append(message)
|
|
147
|
-
final_responses = ResponseList([])
|
|
148
|
-
while pending_requests:
|
|
149
|
-
current_request = pending_requests.popleft()
|
|
150
|
-
results = self.request(current_request)
|
|
151
|
-
|
|
152
|
-
for result in results:
|
|
153
|
-
if isinstance(result, RequestMessage):
|
|
154
|
-
pending_requests.append(result)
|
|
155
|
-
elif isinstance(result, ResponseMessage | FailedRequestMessage):
|
|
156
|
-
final_responses.append(result)
|
|
157
|
-
else:
|
|
158
|
-
raise TypeError(f"Unexpected result type: {type(result)}")
|
|
159
|
-
|
|
160
|
-
return final_responses
|
|
161
|
-
|
|
162
92
|
def _create_thread_safe_session(self) -> httpx.Client:
|
|
163
93
|
return httpx.Client(
|
|
164
94
|
limits=httpx.Limits(
|
|
@@ -191,67 +121,6 @@ class HTTPClient:
|
|
|
191
121
|
headers["Content-Encoding"] = "gzip"
|
|
192
122
|
return headers
|
|
193
123
|
|
|
194
|
-
def _make_request(self, item: RequestMessage) -> httpx.Response:
|
|
195
|
-
headers = self._create_headers(item.api_version, item.content_type, item.accept, item.content_length)
|
|
196
|
-
params: dict[str, PrimitiveType] | None = None
|
|
197
|
-
if isinstance(item, ParamRequest):
|
|
198
|
-
params = item.parameters
|
|
199
|
-
data: str | bytes | None = None
|
|
200
|
-
if isinstance(item, BodyRequest):
|
|
201
|
-
data = item.data()
|
|
202
|
-
if not global_config.disable_gzip and item.content_length is None:
|
|
203
|
-
data = gzip.compress(data.encode("utf-8"))
|
|
204
|
-
elif isinstance(item, DataBodyRequest):
|
|
205
|
-
data = item.data()
|
|
206
|
-
if not global_config.disable_gzip and item.content_length is None:
|
|
207
|
-
data = gzip.compress(data)
|
|
208
|
-
return self.session.request(
|
|
209
|
-
method=item.method,
|
|
210
|
-
url=item.endpoint_url,
|
|
211
|
-
content=data,
|
|
212
|
-
headers=headers,
|
|
213
|
-
params=params,
|
|
214
|
-
timeout=self.config.timeout,
|
|
215
|
-
follow_redirects=False,
|
|
216
|
-
)
|
|
217
|
-
|
|
218
|
-
def _handle_response(self, response: httpx.Response, request: RequestMessage) -> Sequence[HTTPMessage]:
|
|
219
|
-
if 200 <= response.status_code < 300:
|
|
220
|
-
return request.create_success_response(response)
|
|
221
|
-
elif (
|
|
222
|
-
isinstance(request, ItemsRequest)
|
|
223
|
-
and len(request.items) > 1
|
|
224
|
-
and response.status_code in self._split_items_status_codes
|
|
225
|
-
):
|
|
226
|
-
# 4XX: Status there is at least one item that is invalid, split the batch to get all valid items processed
|
|
227
|
-
# 5xx: Server error, split to reduce the number of items in each request, and count as a status attempt
|
|
228
|
-
status_attempts = request.status_attempt
|
|
229
|
-
if 500 <= response.status_code < 600:
|
|
230
|
-
status_attempts += 1
|
|
231
|
-
splits = request.split(status_attempts=status_attempts)
|
|
232
|
-
if splits[0].tracker and splits[0].tracker.limit_reached():
|
|
233
|
-
return request.create_failure_response(response)
|
|
234
|
-
return splits
|
|
235
|
-
|
|
236
|
-
retry_after = self._get_retry_after_in_header(response)
|
|
237
|
-
if retry_after is not None and response.status_code == 429 and request.status_attempt < self._max_retries:
|
|
238
|
-
if self._console is not None:
|
|
239
|
-
short_url = request.endpoint_url.removeprefix(self.config.base_api_url)
|
|
240
|
-
HighSeverityWarning(
|
|
241
|
-
f"Rate limit exceeded for the {short_url!r} endpoint. Retrying after {retry_after} seconds."
|
|
242
|
-
).print_warning(console=self._console)
|
|
243
|
-
request.status_attempt += 1
|
|
244
|
-
time.sleep(retry_after)
|
|
245
|
-
return [request]
|
|
246
|
-
|
|
247
|
-
if request.status_attempt < self._max_retries and response.status_code in self._retry_status_codes:
|
|
248
|
-
request.status_attempt += 1
|
|
249
|
-
time.sleep(self._backoff_time(request.total_attempts))
|
|
250
|
-
return [request]
|
|
251
|
-
else:
|
|
252
|
-
# Permanent failure
|
|
253
|
-
return request.create_failure_response(response)
|
|
254
|
-
|
|
255
124
|
@staticmethod
|
|
256
125
|
def _get_retry_after_in_header(response: httpx.Response) -> float | None:
|
|
257
126
|
if "Retry-After" not in response.headers:
|
|
@@ -267,48 +136,22 @@ class HTTPClient:
|
|
|
267
136
|
backoff_time = 0.5 * (2**attempts)
|
|
268
137
|
return min(backoff_time, global_config.max_retry_backoff) * random.uniform(0, 1.0)
|
|
269
138
|
|
|
270
|
-
def
|
|
271
|
-
self,
|
|
272
|
-
e: Exception,
|
|
273
|
-
request: RequestMessage,
|
|
274
|
-
) -> Sequence[HTTPMessage]:
|
|
275
|
-
if isinstance(e, httpx.ReadTimeout | httpx.TimeoutException):
|
|
276
|
-
error_type = "read"
|
|
277
|
-
request.read_attempt += 1
|
|
278
|
-
attempts = request.read_attempt
|
|
279
|
-
elif isinstance(e, ConnectionError | httpx.ConnectError | httpx.ConnectTimeout):
|
|
280
|
-
error_type = "connect"
|
|
281
|
-
request.connect_attempt += 1
|
|
282
|
-
attempts = request.connect_attempt
|
|
283
|
-
else:
|
|
284
|
-
error_msg = f"Unexpected exception: {e!s}"
|
|
285
|
-
return request.create_failed_request(error_msg)
|
|
286
|
-
|
|
287
|
-
if attempts <= self._max_retries:
|
|
288
|
-
time.sleep(self._backoff_time(request.total_attempts))
|
|
289
|
-
return [request]
|
|
290
|
-
else:
|
|
291
|
-
# We have already incremented the attempt count, so we subtract 1 here
|
|
292
|
-
error_msg = f"RequestException after {request.total_attempts - 1} attempts ({error_type} error): {e!s}"
|
|
293
|
-
|
|
294
|
-
return request.create_failed_request(error_msg)
|
|
295
|
-
|
|
296
|
-
def request_single(self, message: RequestMessage2) -> RequestMessage2 | HTTPResult2:
|
|
139
|
+
def request_single(self, message: RequestMessage) -> RequestMessage | HTTPResult:
|
|
297
140
|
"""Send an HTTP request and return the response.
|
|
298
141
|
|
|
299
142
|
Args:
|
|
300
|
-
message (
|
|
143
|
+
message (RequestMessage): The request message to send.
|
|
301
144
|
Returns:
|
|
302
|
-
|
|
145
|
+
RequestMessage2 | HTTPResult: The response message.
|
|
303
146
|
"""
|
|
304
147
|
try:
|
|
305
|
-
response = self.
|
|
148
|
+
response = self._make_request(message)
|
|
306
149
|
result = self._handle_response_single(response, message)
|
|
307
150
|
except Exception as e:
|
|
308
151
|
result = self._handle_error_single(e, message)
|
|
309
152
|
return result
|
|
310
153
|
|
|
311
|
-
def request_single_retries(self, message:
|
|
154
|
+
def request_single_retries(self, message: RequestMessage) -> HTTPResult:
|
|
312
155
|
"""Send an HTTP request and handle retries.
|
|
313
156
|
|
|
314
157
|
This method will keep retrying the request until it either succeeds or
|
|
@@ -318,7 +161,7 @@ class HTTPClient:
|
|
|
318
161
|
it is blocking.
|
|
319
162
|
|
|
320
163
|
Args:
|
|
321
|
-
message (
|
|
164
|
+
message (RequestMessage): The request message to send.
|
|
322
165
|
Returns:
|
|
323
166
|
HTTPMessage2: The final response message, which can be either successful response or failed request.
|
|
324
167
|
"""
|
|
@@ -327,14 +170,14 @@ class HTTPClient:
|
|
|
327
170
|
current_request = message
|
|
328
171
|
while True:
|
|
329
172
|
result = self.request_single(current_request)
|
|
330
|
-
if isinstance(result,
|
|
173
|
+
if isinstance(result, RequestMessage):
|
|
331
174
|
current_request = result
|
|
332
|
-
elif isinstance(result,
|
|
175
|
+
elif isinstance(result, HTTPResult):
|
|
333
176
|
return result
|
|
334
177
|
else:
|
|
335
178
|
raise TypeError(f"Unexpected result type: {type(result)}")
|
|
336
179
|
|
|
337
|
-
def
|
|
180
|
+
def _make_request(self, message: BaseRequestMessage) -> httpx.Response:
|
|
338
181
|
headers = self._create_headers(
|
|
339
182
|
message.api_version,
|
|
340
183
|
message.content_type,
|
|
@@ -352,11 +195,9 @@ class HTTPClient:
|
|
|
352
195
|
follow_redirects=False,
|
|
353
196
|
)
|
|
354
197
|
|
|
355
|
-
def _handle_response_single(
|
|
356
|
-
self, response: httpx.Response, request: RequestMessage2
|
|
357
|
-
) -> RequestMessage2 | HTTPResult2:
|
|
198
|
+
def _handle_response_single(self, response: httpx.Response, request: RequestMessage) -> RequestMessage | HTTPResult:
|
|
358
199
|
if 200 <= response.status_code < 300:
|
|
359
|
-
return
|
|
200
|
+
return SuccessResponse(
|
|
360
201
|
status_code=response.status_code,
|
|
361
202
|
body=response.text,
|
|
362
203
|
content=response.content,
|
|
@@ -365,10 +206,10 @@ class HTTPClient:
|
|
|
365
206
|
return retry_request
|
|
366
207
|
else:
|
|
367
208
|
# Permanent failure
|
|
368
|
-
return
|
|
209
|
+
return FailedResponse(
|
|
369
210
|
status_code=response.status_code,
|
|
370
211
|
body=response.text,
|
|
371
|
-
error=
|
|
212
|
+
error=ErrorDetails.from_response(response),
|
|
372
213
|
)
|
|
373
214
|
|
|
374
215
|
def _retry_request(self, response: httpx.Response, request: _T_Request_Message) -> _T_Request_Message | None:
|
|
@@ -389,7 +230,7 @@ class HTTPClient:
|
|
|
389
230
|
return request
|
|
390
231
|
return None
|
|
391
232
|
|
|
392
|
-
def _handle_error_single(self, e: Exception, request:
|
|
233
|
+
def _handle_error_single(self, e: Exception, request: RequestMessage) -> RequestMessage | HTTPResult:
|
|
393
234
|
if isinstance(e, httpx.ReadTimeout | httpx.TimeoutException):
|
|
394
235
|
error_type = "read"
|
|
395
236
|
request.read_attempt += 1
|
|
@@ -400,7 +241,7 @@ class HTTPClient:
|
|
|
400
241
|
attempts = request.connect_attempt
|
|
401
242
|
else:
|
|
402
243
|
error_msg = f"Unexpected exception: {e!s}"
|
|
403
|
-
return
|
|
244
|
+
return FailedRequest(error=error_msg)
|
|
404
245
|
|
|
405
246
|
if attempts <= self._max_retries:
|
|
406
247
|
time.sleep(self._backoff_time(request.total_attempts))
|
|
@@ -409,32 +250,32 @@ class HTTPClient:
|
|
|
409
250
|
# We have already incremented the attempt count, so we subtract 1 here
|
|
410
251
|
error_msg = f"RequestException after {request.total_attempts - 1} attempts ({error_type} error): {e!s}"
|
|
411
252
|
|
|
412
|
-
return
|
|
253
|
+
return FailedRequest(error=error_msg)
|
|
413
254
|
|
|
414
|
-
def request_items(self, message:
|
|
255
|
+
def request_items(self, message: ItemsRequest) -> Sequence[ItemsRequest | ItemsResultMessage]:
|
|
415
256
|
"""Send an HTTP request with multiple items and return the response.
|
|
416
257
|
|
|
417
258
|
Args:
|
|
418
|
-
message (
|
|
259
|
+
message (ItemsRequest): The request message to send.
|
|
419
260
|
Returns:
|
|
420
|
-
Sequence[ItemsRequest2 |
|
|
261
|
+
Sequence[ItemsRequest2 | ItemsResultMessage]: The response message(s). This can also
|
|
421
262
|
include ItemsRequest2(s) to be retried or split.
|
|
422
263
|
"""
|
|
423
264
|
if message.tracker and message.tracker.limit_reached():
|
|
424
265
|
return [
|
|
425
|
-
|
|
266
|
+
ItemsFailedRequest(
|
|
426
267
|
ids=[str(item) for item in message.items],
|
|
427
268
|
error_message=f"Aborting further splitting of requests after {message.tracker.failed_split_count} failed attempts.",
|
|
428
269
|
)
|
|
429
270
|
]
|
|
430
271
|
try:
|
|
431
|
-
response = self.
|
|
272
|
+
response = self._make_request(message)
|
|
432
273
|
results = self._handle_items_response(response, message)
|
|
433
274
|
except Exception as e:
|
|
434
275
|
results = self._handle_items_error(e, message)
|
|
435
276
|
return results
|
|
436
277
|
|
|
437
|
-
def request_items_retries(self, message:
|
|
278
|
+
def request_items_retries(self, message: ItemsRequest) -> ItemsResultList:
|
|
438
279
|
"""Send an HTTP request with multiple items and handle retries.
|
|
439
280
|
|
|
440
281
|
This method will keep retrying the request until it either succeeds or
|
|
@@ -444,13 +285,13 @@ class HTTPClient:
|
|
|
444
285
|
it is blocking.
|
|
445
286
|
|
|
446
287
|
Args:
|
|
447
|
-
message (
|
|
288
|
+
message (ItemsRequest): The request message to send.
|
|
448
289
|
Returns:
|
|
449
|
-
Sequence[
|
|
290
|
+
Sequence[ItemsResultMessage]: The final response message, which can be either successful response or failed request.
|
|
450
291
|
"""
|
|
451
292
|
if message.total_attempts > 0:
|
|
452
293
|
raise RuntimeError(f"ItemsRequest2 has already been attempted {message.total_attempts} times.")
|
|
453
|
-
pending_requests: deque[
|
|
294
|
+
pending_requests: deque[ItemsRequest] = deque()
|
|
454
295
|
pending_requests.append(message)
|
|
455
296
|
final_responses = ItemsResultList([])
|
|
456
297
|
while pending_requests:
|
|
@@ -458,9 +299,9 @@ class HTTPClient:
|
|
|
458
299
|
results = self.request_items(current_request)
|
|
459
300
|
|
|
460
301
|
for result in results:
|
|
461
|
-
if isinstance(result,
|
|
302
|
+
if isinstance(result, ItemsRequest):
|
|
462
303
|
pending_requests.append(result)
|
|
463
|
-
elif isinstance(result,
|
|
304
|
+
elif isinstance(result, ItemsResultMessage):
|
|
464
305
|
final_responses.append(result)
|
|
465
306
|
else:
|
|
466
307
|
raise TypeError(f"Unexpected result type: {type(result)}")
|
|
@@ -468,11 +309,11 @@ class HTTPClient:
|
|
|
468
309
|
return final_responses
|
|
469
310
|
|
|
470
311
|
def _handle_items_response(
|
|
471
|
-
self, response: httpx.Response, request:
|
|
472
|
-
) -> Sequence[
|
|
312
|
+
self, response: httpx.Response, request: ItemsRequest
|
|
313
|
+
) -> Sequence[ItemsRequest | ItemsResultMessage]:
|
|
473
314
|
if 200 <= response.status_code < 300:
|
|
474
315
|
return [
|
|
475
|
-
|
|
316
|
+
ItemsSuccessResponse(
|
|
476
317
|
ids=[str(item) for item in request.items],
|
|
477
318
|
status_code=response.status_code,
|
|
478
319
|
body=response.text,
|
|
@@ -488,11 +329,11 @@ class HTTPClient:
|
|
|
488
329
|
splits = request.split(status_attempts=status_attempts)
|
|
489
330
|
if splits[0].tracker and splits[0].tracker.limit_reached():
|
|
490
331
|
return [
|
|
491
|
-
|
|
332
|
+
ItemsFailedResponse(
|
|
492
333
|
ids=[str(item) for item in request.items],
|
|
493
334
|
status_code=response.status_code,
|
|
494
335
|
body=response.text,
|
|
495
|
-
error=
|
|
336
|
+
error=ErrorDetails.from_response(response),
|
|
496
337
|
)
|
|
497
338
|
]
|
|
498
339
|
return splits
|
|
@@ -502,17 +343,15 @@ class HTTPClient:
|
|
|
502
343
|
else:
|
|
503
344
|
# Permanent failure
|
|
504
345
|
return [
|
|
505
|
-
|
|
346
|
+
ItemsFailedResponse(
|
|
506
347
|
ids=[str(item) for item in request.items],
|
|
507
348
|
status_code=response.status_code,
|
|
508
349
|
body=response.text,
|
|
509
|
-
error=
|
|
350
|
+
error=ErrorDetails.from_response(response),
|
|
510
351
|
)
|
|
511
352
|
]
|
|
512
353
|
|
|
513
|
-
def _handle_items_error(
|
|
514
|
-
self, e: Exception, request: ItemsRequest2
|
|
515
|
-
) -> Sequence[ItemsRequest2 | ItemsResultMessage2]:
|
|
354
|
+
def _handle_items_error(self, e: Exception, request: ItemsRequest) -> Sequence[ItemsRequest | ItemsResultMessage]:
|
|
516
355
|
if isinstance(e, httpx.ReadTimeout | httpx.TimeoutException):
|
|
517
356
|
error_type = "read"
|
|
518
357
|
request.read_attempt += 1
|
|
@@ -524,7 +363,7 @@ class HTTPClient:
|
|
|
524
363
|
else:
|
|
525
364
|
error_msg = f"Unexpected exception: {e!s}"
|
|
526
365
|
return [
|
|
527
|
-
|
|
366
|
+
ItemsFailedRequest(
|
|
528
367
|
ids=[str(item) for item in request.items],
|
|
529
368
|
error_message=error_msg,
|
|
530
369
|
)
|
|
@@ -538,7 +377,7 @@ class HTTPClient:
|
|
|
538
377
|
error_msg = f"RequestException after {request.total_attempts - 1} attempts ({error_type} error): {e!s}"
|
|
539
378
|
|
|
540
379
|
return [
|
|
541
|
-
|
|
380
|
+
ItemsFailedRequest(
|
|
542
381
|
ids=[str(item) for item in request.items],
|
|
543
382
|
error_message=error_msg,
|
|
544
383
|
)
|