cognite-toolkit 0.6.97__py3-none-any.whl → 0.7.39__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.
Files changed (198) hide show
  1. cognite_toolkit/_cdf.py +21 -23
  2. cognite_toolkit/_cdf_tk/apps/__init__.py +4 -0
  3. cognite_toolkit/_cdf_tk/apps/_core_app.py +19 -5
  4. cognite_toolkit/_cdf_tk/apps/_data_app.py +1 -1
  5. cognite_toolkit/_cdf_tk/apps/_dev_app.py +86 -0
  6. cognite_toolkit/_cdf_tk/apps/_download_app.py +693 -25
  7. cognite_toolkit/_cdf_tk/apps/_dump_app.py +44 -102
  8. cognite_toolkit/_cdf_tk/apps/_import_app.py +41 -0
  9. cognite_toolkit/_cdf_tk/apps/_landing_app.py +18 -4
  10. cognite_toolkit/_cdf_tk/apps/_migrate_app.py +424 -9
  11. cognite_toolkit/_cdf_tk/apps/_modules_app.py +0 -3
  12. cognite_toolkit/_cdf_tk/apps/_purge.py +15 -43
  13. cognite_toolkit/_cdf_tk/apps/_run.py +11 -0
  14. cognite_toolkit/_cdf_tk/apps/_upload_app.py +45 -6
  15. cognite_toolkit/_cdf_tk/builders/__init__.py +2 -2
  16. cognite_toolkit/_cdf_tk/builders/_base.py +28 -42
  17. cognite_toolkit/_cdf_tk/builders/_raw.py +1 -1
  18. cognite_toolkit/_cdf_tk/cdf_toml.py +20 -1
  19. cognite_toolkit/_cdf_tk/client/_toolkit_client.py +32 -12
  20. cognite_toolkit/_cdf_tk/client/api/infield.py +114 -17
  21. cognite_toolkit/_cdf_tk/client/api/{canvas.py → legacy/canvas.py} +15 -7
  22. cognite_toolkit/_cdf_tk/client/api/{charts.py → legacy/charts.py} +1 -1
  23. cognite_toolkit/_cdf_tk/client/api/{extended_data_modeling.py → legacy/extended_data_modeling.py} +1 -1
  24. cognite_toolkit/_cdf_tk/client/api/{extended_files.py → legacy/extended_files.py} +2 -2
  25. cognite_toolkit/_cdf_tk/client/api/{extended_functions.py → legacy/extended_functions.py} +15 -18
  26. cognite_toolkit/_cdf_tk/client/api/{extended_raw.py → legacy/extended_raw.py} +1 -1
  27. cognite_toolkit/_cdf_tk/client/api/{extended_timeseries.py → legacy/extended_timeseries.py} +5 -2
  28. cognite_toolkit/_cdf_tk/client/api/{location_filters.py → legacy/location_filters.py} +1 -1
  29. cognite_toolkit/_cdf_tk/client/api/legacy/robotics/__init__.py +8 -0
  30. cognite_toolkit/_cdf_tk/client/api/{robotics → legacy/robotics}/capabilities.py +1 -1
  31. cognite_toolkit/_cdf_tk/client/api/{robotics → legacy/robotics}/data_postprocessing.py +1 -1
  32. cognite_toolkit/_cdf_tk/client/api/{robotics → legacy/robotics}/frames.py +1 -1
  33. cognite_toolkit/_cdf_tk/client/api/{robotics → legacy/robotics}/locations.py +1 -1
  34. cognite_toolkit/_cdf_tk/client/api/{robotics → legacy/robotics}/maps.py +1 -1
  35. cognite_toolkit/_cdf_tk/client/api/{robotics → legacy/robotics}/robots.py +2 -2
  36. cognite_toolkit/_cdf_tk/client/api/{search_config.py → legacy/search_config.py} +5 -1
  37. cognite_toolkit/_cdf_tk/client/api/migration.py +177 -4
  38. cognite_toolkit/_cdf_tk/client/api/project.py +9 -8
  39. cognite_toolkit/_cdf_tk/client/api/search.py +2 -2
  40. cognite_toolkit/_cdf_tk/client/api/streams.py +88 -0
  41. cognite_toolkit/_cdf_tk/client/api/three_d.py +384 -0
  42. cognite_toolkit/_cdf_tk/client/data_classes/api_classes.py +13 -0
  43. cognite_toolkit/_cdf_tk/client/data_classes/base.py +37 -33
  44. cognite_toolkit/_cdf_tk/client/data_classes/charts_data.py +95 -213
  45. cognite_toolkit/_cdf_tk/client/data_classes/infield.py +32 -18
  46. cognite_toolkit/_cdf_tk/client/data_classes/instance_api.py +18 -13
  47. cognite_toolkit/_cdf_tk/client/data_classes/legacy/__init__.py +0 -0
  48. cognite_toolkit/_cdf_tk/client/data_classes/{canvas.py → legacy/canvas.py} +47 -4
  49. cognite_toolkit/_cdf_tk/client/data_classes/{charts.py → legacy/charts.py} +3 -3
  50. cognite_toolkit/_cdf_tk/client/data_classes/{migration.py → legacy/migration.py} +10 -2
  51. cognite_toolkit/_cdf_tk/client/data_classes/streams.py +90 -0
  52. cognite_toolkit/_cdf_tk/client/data_classes/three_d.py +112 -0
  53. cognite_toolkit/_cdf_tk/client/testing.py +42 -18
  54. cognite_toolkit/_cdf_tk/commands/__init__.py +7 -6
  55. cognite_toolkit/_cdf_tk/commands/_changes.py +3 -42
  56. cognite_toolkit/_cdf_tk/commands/_download.py +21 -11
  57. cognite_toolkit/_cdf_tk/commands/_migrate/__init__.py +0 -2
  58. cognite_toolkit/_cdf_tk/commands/_migrate/command.py +22 -20
  59. cognite_toolkit/_cdf_tk/commands/_migrate/conversion.py +140 -92
  60. cognite_toolkit/_cdf_tk/commands/_migrate/creators.py +1 -1
  61. cognite_toolkit/_cdf_tk/commands/_migrate/data_classes.py +108 -26
  62. cognite_toolkit/_cdf_tk/commands/_migrate/data_mapper.py +448 -45
  63. cognite_toolkit/_cdf_tk/commands/_migrate/data_model.py +1 -0
  64. cognite_toolkit/_cdf_tk/commands/_migrate/default_mappings.py +6 -6
  65. cognite_toolkit/_cdf_tk/commands/_migrate/issues.py +52 -1
  66. cognite_toolkit/_cdf_tk/commands/_migrate/migration_io.py +377 -11
  67. cognite_toolkit/_cdf_tk/commands/_migrate/selectors.py +9 -4
  68. cognite_toolkit/_cdf_tk/commands/_profile.py +1 -1
  69. cognite_toolkit/_cdf_tk/commands/_purge.py +36 -39
  70. cognite_toolkit/_cdf_tk/commands/_questionary_style.py +16 -0
  71. cognite_toolkit/_cdf_tk/commands/_upload.py +109 -86
  72. cognite_toolkit/_cdf_tk/commands/about.py +221 -0
  73. cognite_toolkit/_cdf_tk/commands/auth.py +19 -12
  74. cognite_toolkit/_cdf_tk/commands/build_cmd.py +16 -62
  75. cognite_toolkit/_cdf_tk/commands/build_v2/__init__.py +0 -0
  76. cognite_toolkit/_cdf_tk/commands/build_v2/build_cmd.py +241 -0
  77. cognite_toolkit/_cdf_tk/commands/build_v2/build_input.py +85 -0
  78. cognite_toolkit/_cdf_tk/commands/build_v2/build_issues.py +27 -0
  79. cognite_toolkit/_cdf_tk/commands/clean.py +63 -16
  80. cognite_toolkit/_cdf_tk/commands/deploy.py +20 -17
  81. cognite_toolkit/_cdf_tk/commands/dump_resource.py +10 -8
  82. cognite_toolkit/_cdf_tk/commands/init.py +225 -3
  83. cognite_toolkit/_cdf_tk/commands/modules.py +20 -44
  84. cognite_toolkit/_cdf_tk/commands/pull.py +6 -19
  85. cognite_toolkit/_cdf_tk/commands/resources.py +179 -0
  86. cognite_toolkit/_cdf_tk/commands/run.py +1 -1
  87. cognite_toolkit/_cdf_tk/constants.py +20 -1
  88. cognite_toolkit/_cdf_tk/cruds/__init__.py +19 -5
  89. cognite_toolkit/_cdf_tk/cruds/_base_cruds.py +14 -70
  90. cognite_toolkit/_cdf_tk/cruds/_data_cruds.py +10 -19
  91. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/__init__.py +4 -1
  92. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/agent.py +11 -9
  93. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/auth.py +5 -15
  94. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/classic.py +45 -44
  95. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/configuration.py +5 -12
  96. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/data_organization.py +4 -13
  97. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/datamodel.py +206 -67
  98. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/extraction_pipeline.py +6 -18
  99. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/fieldops.py +126 -35
  100. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/file.py +7 -28
  101. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/function.py +23 -30
  102. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/hosted_extractors.py +12 -30
  103. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/industrial_tool.py +4 -8
  104. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/location.py +4 -16
  105. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/migration.py +5 -13
  106. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/raw.py +5 -11
  107. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/relationship.py +3 -8
  108. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/robotics.py +16 -45
  109. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/streams.py +94 -0
  110. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/three_d_model.py +3 -7
  111. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/timeseries.py +5 -15
  112. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/transformation.py +75 -32
  113. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/workflow.py +20 -40
  114. cognite_toolkit/_cdf_tk/cruds/_worker.py +24 -36
  115. cognite_toolkit/_cdf_tk/data_classes/_module_toml.py +1 -0
  116. cognite_toolkit/_cdf_tk/feature_flags.py +16 -36
  117. cognite_toolkit/_cdf_tk/plugins.py +2 -1
  118. cognite_toolkit/_cdf_tk/resource_classes/__init__.py +4 -0
  119. cognite_toolkit/_cdf_tk/resource_classes/capabilities.py +12 -0
  120. cognite_toolkit/_cdf_tk/resource_classes/functions.py +3 -1
  121. cognite_toolkit/_cdf_tk/resource_classes/infield_cdm_location_config.py +109 -0
  122. cognite_toolkit/_cdf_tk/resource_classes/migration.py +8 -17
  123. cognite_toolkit/_cdf_tk/resource_classes/search_config.py +1 -1
  124. cognite_toolkit/_cdf_tk/resource_classes/streams.py +29 -0
  125. cognite_toolkit/_cdf_tk/resource_classes/workflow_version.py +164 -5
  126. cognite_toolkit/_cdf_tk/storageio/__init__.py +9 -21
  127. cognite_toolkit/_cdf_tk/storageio/_annotations.py +19 -16
  128. cognite_toolkit/_cdf_tk/storageio/_applications.py +340 -28
  129. cognite_toolkit/_cdf_tk/storageio/_asset_centric.py +67 -104
  130. cognite_toolkit/_cdf_tk/storageio/_base.py +61 -29
  131. cognite_toolkit/_cdf_tk/storageio/_datapoints.py +276 -20
  132. cognite_toolkit/_cdf_tk/storageio/_file_content.py +435 -0
  133. cognite_toolkit/_cdf_tk/storageio/_instances.py +35 -3
  134. cognite_toolkit/_cdf_tk/storageio/_raw.py +26 -0
  135. cognite_toolkit/_cdf_tk/storageio/selectors/__init__.py +71 -4
  136. cognite_toolkit/_cdf_tk/storageio/selectors/_base.py +14 -2
  137. cognite_toolkit/_cdf_tk/storageio/selectors/_canvas.py +14 -0
  138. cognite_toolkit/_cdf_tk/storageio/selectors/_charts.py +14 -0
  139. cognite_toolkit/_cdf_tk/storageio/selectors/_datapoints.py +23 -3
  140. cognite_toolkit/_cdf_tk/storageio/selectors/_file_content.py +164 -0
  141. cognite_toolkit/_cdf_tk/storageio/selectors/_three_d.py +34 -0
  142. cognite_toolkit/_cdf_tk/tk_warnings/other.py +4 -0
  143. cognite_toolkit/_cdf_tk/tracker.py +2 -2
  144. cognite_toolkit/_cdf_tk/utils/cdf.py +1 -1
  145. cognite_toolkit/_cdf_tk/utils/dtype_conversion.py +9 -3
  146. cognite_toolkit/_cdf_tk/utils/fileio/__init__.py +2 -0
  147. cognite_toolkit/_cdf_tk/utils/fileio/_base.py +5 -1
  148. cognite_toolkit/_cdf_tk/utils/fileio/_readers.py +112 -20
  149. cognite_toolkit/_cdf_tk/utils/fileio/_writers.py +15 -15
  150. cognite_toolkit/_cdf_tk/utils/http_client/__init__.py +28 -0
  151. cognite_toolkit/_cdf_tk/utils/http_client/_client.py +285 -18
  152. cognite_toolkit/_cdf_tk/utils/http_client/_data_classes.py +56 -4
  153. cognite_toolkit/_cdf_tk/utils/http_client/_data_classes2.py +247 -0
  154. cognite_toolkit/_cdf_tk/utils/http_client/_tracker.py +5 -2
  155. cognite_toolkit/_cdf_tk/utils/interactive_select.py +60 -18
  156. cognite_toolkit/_cdf_tk/utils/sql_parser.py +2 -3
  157. cognite_toolkit/_cdf_tk/utils/useful_types.py +6 -2
  158. cognite_toolkit/_cdf_tk/validation.py +83 -1
  159. cognite_toolkit/_repo_files/GitHub/.github/workflows/deploy.yaml +1 -1
  160. cognite_toolkit/_repo_files/GitHub/.github/workflows/dry-run.yaml +1 -1
  161. cognite_toolkit/_resources/cdf.toml +5 -4
  162. cognite_toolkit/_version.py +1 -1
  163. cognite_toolkit/config.dev.yaml +13 -0
  164. {cognite_toolkit-0.6.97.dist-info → cognite_toolkit-0.7.39.dist-info}/METADATA +24 -24
  165. cognite_toolkit-0.7.39.dist-info/RECORD +322 -0
  166. cognite_toolkit-0.7.39.dist-info/WHEEL +4 -0
  167. {cognite_toolkit-0.6.97.dist-info → cognite_toolkit-0.7.39.dist-info}/entry_points.txt +1 -0
  168. cognite_toolkit/_cdf_tk/client/api/robotics/__init__.py +0 -3
  169. cognite_toolkit/_cdf_tk/commands/_migrate/canvas.py +0 -201
  170. cognite_toolkit/_cdf_tk/commands/dump_data.py +0 -489
  171. cognite_toolkit/_cdf_tk/commands/featureflag.py +0 -27
  172. cognite_toolkit/_cdf_tk/prototypes/import_app.py +0 -41
  173. cognite_toolkit/_cdf_tk/utils/table_writers.py +0 -434
  174. cognite_toolkit-0.6.97.dist-info/RECORD +0 -306
  175. cognite_toolkit-0.6.97.dist-info/WHEEL +0 -4
  176. cognite_toolkit-0.6.97.dist-info/licenses/LICENSE +0 -18
  177. /cognite_toolkit/_cdf_tk/{prototypes/commands → client/api/legacy}/__init__.py +0 -0
  178. /cognite_toolkit/_cdf_tk/client/api/{dml.py → legacy/dml.py} +0 -0
  179. /cognite_toolkit/_cdf_tk/client/api/{fixed_transformations.py → legacy/fixed_transformations.py} +0 -0
  180. /cognite_toolkit/_cdf_tk/client/api/{robotics → legacy/robotics}/api.py +0 -0
  181. /cognite_toolkit/_cdf_tk/client/api/{robotics → legacy/robotics}/utlis.py +0 -0
  182. /cognite_toolkit/_cdf_tk/client/data_classes/{apm_config_v1.py → legacy/apm_config_v1.py} +0 -0
  183. /cognite_toolkit/_cdf_tk/client/data_classes/{extendable_cognite_file.py → legacy/extendable_cognite_file.py} +0 -0
  184. /cognite_toolkit/_cdf_tk/client/data_classes/{extended_filemetadata.py → legacy/extended_filemetadata.py} +0 -0
  185. /cognite_toolkit/_cdf_tk/client/data_classes/{extended_filemetdata.py → legacy/extended_filemetdata.py} +0 -0
  186. /cognite_toolkit/_cdf_tk/client/data_classes/{extended_timeseries.py → legacy/extended_timeseries.py} +0 -0
  187. /cognite_toolkit/_cdf_tk/client/data_classes/{functions.py → legacy/functions.py} +0 -0
  188. /cognite_toolkit/_cdf_tk/client/data_classes/{graphql_data_models.py → legacy/graphql_data_models.py} +0 -0
  189. /cognite_toolkit/_cdf_tk/client/data_classes/{instances.py → legacy/instances.py} +0 -0
  190. /cognite_toolkit/_cdf_tk/client/data_classes/{location_filters.py → legacy/location_filters.py} +0 -0
  191. /cognite_toolkit/_cdf_tk/client/data_classes/{pending_instances_ids.py → legacy/pending_instances_ids.py} +0 -0
  192. /cognite_toolkit/_cdf_tk/client/data_classes/{project.py → legacy/project.py} +0 -0
  193. /cognite_toolkit/_cdf_tk/client/data_classes/{raw.py → legacy/raw.py} +0 -0
  194. /cognite_toolkit/_cdf_tk/client/data_classes/{robotics.py → legacy/robotics.py} +0 -0
  195. /cognite_toolkit/_cdf_tk/client/data_classes/{search_config.py → legacy/search_config.py} +0 -0
  196. /cognite_toolkit/_cdf_tk/client/data_classes/{sequences.py → legacy/sequences.py} +0 -0
  197. /cognite_toolkit/_cdf_tk/client/data_classes/{streamlit_.py → legacy/streamlit_.py} +0 -0
  198. /cognite_toolkit/_cdf_tk/{prototypes/commands/import_.py → commands/_import_cmd.py} +0 -0
@@ -19,7 +19,7 @@ from cognite_toolkit._cdf_tk.exceptions import ToolkitMissingDependencyError, To
19
19
  from cognite_toolkit._cdf_tk.utils._auxiliary import get_concrete_subclasses
20
20
  from cognite_toolkit._cdf_tk.utils.collection import humanize_collection
21
21
  from cognite_toolkit._cdf_tk.utils.file import sanitize_filename
22
- from cognite_toolkit._cdf_tk.utils.table_writers import DataType
22
+ from cognite_toolkit._cdf_tk.utils.useful_types import DataType
23
23
 
24
24
  from ._base import T_IO, CellValue, Chunk, FileIO, SchemaColumn
25
25
  from ._compression import Compression, Uncompressed
@@ -154,7 +154,7 @@ class TableWriter(FileWriter[T_IO], ABC):
154
154
 
155
155
 
156
156
  class NDJsonWriter(FileWriter[TextIOWrapper]):
157
- format = ".ndjson"
157
+ FORMAT = ".ndjson"
158
158
 
159
159
  class _DateTimeEncoder(json.JSONEncoder):
160
160
  def default(self, obj: object) -> object:
@@ -181,15 +181,15 @@ class YAMLBaseWriter(FileWriter[TextIOWrapper], ABC):
181
181
 
182
182
 
183
183
  class YAMLWriter(YAMLBaseWriter):
184
- format = ".yaml"
184
+ FORMAT = ".yaml"
185
185
 
186
186
 
187
187
  class YMLWriter(YAMLBaseWriter):
188
- format = ".yml"
188
+ FORMAT = ".yml"
189
189
 
190
190
 
191
191
  class CSVWriter(TableWriter[TextIOWrapper]):
192
- format = ".csv"
192
+ FORMAT = ".csv"
193
193
 
194
194
  def __init__(
195
195
  self,
@@ -241,7 +241,7 @@ class CSVWriter(TableWriter[TextIOWrapper]):
241
241
 
242
242
 
243
243
  class ParquetWriter(TableWriter["pq.ParquetWriter"]):
244
- format = ".parquet"
244
+ FORMAT = ".parquet"
245
245
 
246
246
  def _create_writer(self, filepath: Path) -> "pq.ParquetWriter":
247
247
  import pyarrow.parquet as pq
@@ -411,19 +411,19 @@ class ParquetWriter(TableWriter["pq.ParquetWriter"]):
411
411
  FILE_WRITE_CLS_BY_FORMAT: Mapping[str, type[FileWriter]] = {}
412
412
  TABLE_WRITE_CLS_BY_FORMAT: Mapping[str, type[TableWriter]] = {}
413
413
  for subclass in get_concrete_subclasses(FileWriter): # type: ignore[type-abstract]
414
- if not getattr(subclass, "format", None):
414
+ if not getattr(subclass, "FORMAT", None):
415
415
  continue
416
- if subclass.format in FILE_WRITE_CLS_BY_FORMAT:
416
+ if subclass.FORMAT in FILE_WRITE_CLS_BY_FORMAT:
417
417
  raise TypeError(
418
- f"Duplicate file format {subclass.format!r} found for classes "
419
- f"{FILE_WRITE_CLS_BY_FORMAT[subclass.format].__name__!r} and {subclass.__name__!r}."
418
+ f"Duplicate file format {subclass.FORMAT!r} found for classes "
419
+ f"{FILE_WRITE_CLS_BY_FORMAT[subclass.FORMAT].__name__!r} and {subclass.__name__!r}."
420
420
  )
421
421
  # We know we have a dict, but we want to expose FILE_WRITE_CLS_BY_FORMAT as a Mapping
422
- FILE_WRITE_CLS_BY_FORMAT[subclass.format] = subclass # type: ignore[index]
422
+ FILE_WRITE_CLS_BY_FORMAT[subclass.FORMAT] = subclass # type: ignore[index]
423
423
  if issubclass(subclass, TableWriter):
424
- if subclass.format in TABLE_WRITE_CLS_BY_FORMAT:
424
+ if subclass.FORMAT in TABLE_WRITE_CLS_BY_FORMAT:
425
425
  raise TypeError(
426
- f"Duplicate table file format {subclass.format!r} found for classes "
427
- f"{TABLE_WRITE_CLS_BY_FORMAT[subclass.format].__name__!r} and {subclass.__name__!r}."
426
+ f"Duplicate table file format {subclass.FORMAT!r} found for classes "
427
+ f"{TABLE_WRITE_CLS_BY_FORMAT[subclass.FORMAT].__name__!r} and {subclass.__name__!r}."
428
428
  )
429
- TABLE_WRITE_CLS_BY_FORMAT[subclass.format] = subclass # type: ignore[index]
429
+ TABLE_WRITE_CLS_BY_FORMAT[subclass.FORMAT] = subclass # type: ignore[index]
@@ -17,25 +17,53 @@ from ._data_classes import (
17
17
  SuccessResponse,
18
18
  SuccessResponseItems,
19
19
  )
20
+ from ._data_classes2 import (
21
+ BaseModelObject,
22
+ ErrorDetails2,
23
+ FailedRequest2,
24
+ FailedResponse2,
25
+ HTTPResult2,
26
+ ItemsFailedRequest2,
27
+ ItemsFailedResponse2,
28
+ ItemsRequest2,
29
+ ItemsResultMessage2,
30
+ ItemsSuccessResponse2,
31
+ RequestMessage2,
32
+ RequestResource,
33
+ SuccessResponse2,
34
+ )
20
35
  from ._exception import ToolkitAPIError
21
36
 
22
37
  __all__ = [
38
+ "BaseModelObject",
23
39
  "DataBodyRequest",
24
40
  "ErrorDetails",
41
+ "ErrorDetails2",
42
+ "FailedRequest2",
25
43
  "FailedRequestItems",
26
44
  "FailedRequestMessage",
27
45
  "FailedResponse",
46
+ "FailedResponse2",
28
47
  "FailedResponseItems",
29
48
  "HTTPClient",
30
49
  "HTTPMessage",
50
+ "HTTPResult2",
31
51
  "ItemMessage",
52
+ "ItemsFailedRequest2",
53
+ "ItemsFailedResponse2",
32
54
  "ItemsRequest",
55
+ "ItemsRequest2",
56
+ "ItemsResultMessage2",
57
+ "ItemsSuccessResponse2",
33
58
  "ParamRequest",
34
59
  "RequestMessage",
60
+ "RequestMessage2",
61
+ "RequestResource",
35
62
  "ResponseList",
36
63
  "ResponseMessage",
37
64
  "SimpleBodyRequest",
38
65
  "SuccessResponse",
66
+ "SuccessResponse2",
39
67
  "SuccessResponseItems",
40
68
  "ToolkitAPIError",
41
69
  ]
@@ -4,7 +4,7 @@ import sys
4
4
  import time
5
5
  from collections import deque
6
6
  from collections.abc import MutableMapping, Sequence, Set
7
- from typing import Literal
7
+ from typing import Literal, TypeVar
8
8
 
9
9
  import httpx
10
10
  from cognite.client import global_config
@@ -23,6 +23,21 @@ from cognite_toolkit._cdf_tk.utils.http_client._data_classes import (
23
23
  ResponseList,
24
24
  ResponseMessage,
25
25
  )
26
+ from cognite_toolkit._cdf_tk.utils.http_client._data_classes2 import (
27
+ BaseRequestMessage,
28
+ ErrorDetails2,
29
+ FailedRequest2,
30
+ FailedResponse2,
31
+ HTTPResult2,
32
+ ItemsFailedRequest2,
33
+ ItemsFailedResponse2,
34
+ ItemsRequest2,
35
+ ItemsResultList,
36
+ ItemsResultMessage2,
37
+ ItemsSuccessResponse2,
38
+ RequestMessage2,
39
+ SuccessResponse2,
40
+ )
26
41
  from cognite_toolkit._cdf_tk.utils.useful_types import PrimitiveType
27
42
 
28
43
  if sys.version_info >= (3, 11):
@@ -32,6 +47,8 @@ else:
32
47
 
33
48
  from cognite_toolkit._cdf_tk.client.config import ToolkitClientConfig
34
49
 
50
+ _T_Request_Message = TypeVar("_T_Request_Message", bound=BaseRequestMessage)
51
+
35
52
 
36
53
  class HTTPClient:
37
54
  """An HTTP client.
@@ -48,6 +65,7 @@ class HTTPClient:
48
65
  Default is {408, 429, 502, 503, 504}.
49
66
  split_items_status_codes (frozenset[int]): In the case of ItemRequest with multiple
50
67
  items, these status codes will trigger splitting the request into smaller batches.
68
+ console (Console | None): Optional Rich Console for printing warnings.
51
69
 
52
70
  """
53
71
 
@@ -59,6 +77,7 @@ class HTTPClient:
59
77
  pool_maxsize: int = 20,
60
78
  retry_status_codes: Set[int] = frozenset({408, 429, 502, 503, 504}),
61
79
  split_items_status_codes: Set[int] = frozenset({400, 408, 409, 422, 502, 503, 504}),
80
+ console: Console | None = None,
62
81
  ):
63
82
  self.config = config
64
83
  self._max_retries = max_retries
@@ -66,6 +85,7 @@ class HTTPClient:
66
85
  self._pool_maxsize = pool_maxsize
67
86
  self._retry_status_codes = retry_status_codes
68
87
  self._split_items_status_codes = split_items_status_codes
88
+ self._console = console
69
89
 
70
90
  # Thread-safe session for connection pooling
71
91
  self.session = self._create_thread_safe_session()
@@ -80,12 +100,11 @@ class HTTPClient:
80
100
  self.session.close()
81
101
  return False # Do not suppress exceptions
82
102
 
83
- def request(self, message: RequestMessage, console: Console | None = None) -> Sequence[HTTPMessage]:
103
+ def request(self, message: RequestMessage) -> Sequence[HTTPMessage]:
84
104
  """Send an HTTP request and return the response.
85
105
 
86
106
  Args:
87
107
  message (RequestMessage): The request message to send.
88
- console (Console | None): The rich console to use for printing warnings.
89
108
 
90
109
  Returns:
91
110
  Sequence[HTTPMessage]: The response message(s). This can also
@@ -98,12 +117,12 @@ class HTTPClient:
98
117
  return message.create_failed_request(error_msg)
99
118
  try:
100
119
  response = self._make_request(message)
101
- results = self._handle_response(response, message, console)
120
+ results = self._handle_response(response, message)
102
121
  except Exception as e:
103
122
  results = self._handle_error(e, message)
104
123
  return results
105
124
 
106
- def request_with_retries(self, message: RequestMessage, console: Console | None = None) -> ResponseList:
125
+ def request_with_retries(self, message: RequestMessage) -> ResponseList:
107
126
  """Send an HTTP request and handle retries.
108
127
 
109
128
  This method will keep retrying the request until it either succeeds or
@@ -114,7 +133,6 @@ class HTTPClient:
114
133
 
115
134
  Args:
116
135
  message (RequestMessage): The request message to send.
117
- console (Console | None): The rich console to use for printing warnings.
118
136
 
119
137
  Returns:
120
138
  Sequence[ResponseMessage | FailedRequestMessage]: The final response
@@ -127,7 +145,7 @@ class HTTPClient:
127
145
  final_responses = ResponseList([])
128
146
  while pending_requests:
129
147
  current_request = pending_requests.popleft()
130
- results = self.request(current_request, console)
148
+ results = self.request(current_request)
131
149
 
132
150
  for result in results:
133
151
  if isinstance(result, RequestMessage):
@@ -149,34 +167,40 @@ class HTTPClient:
149
167
  )
150
168
 
151
169
  def _create_headers(
152
- self, api_version: str | None = None, content_type: str = "application/json", accept: str = "application/json"
170
+ self,
171
+ api_version: str | None = None,
172
+ content_type: str = "application/json",
173
+ accept: str = "application/json",
174
+ content_length: int | None = None,
153
175
  ) -> MutableMapping[str, str]:
154
176
  headers: MutableMapping[str, str] = {}
155
177
  headers["User-Agent"] = f"httpx/{httpx.__version__} {get_user_agent()}"
156
178
  auth_name, auth_value = self.config.credentials.authorization_header()
157
179
  headers[auth_name] = auth_value
158
- headers["content-type"] = content_type
180
+ headers["Content-Type"] = content_type
181
+ if content_length is not None:
182
+ headers["Content-Length"] = str(content_length)
159
183
  headers["accept"] = accept
160
184
  headers["x-cdp-sdk"] = f"CogniteToolkit:{get_current_toolkit_version()}"
161
185
  headers["x-cdp-app"] = self.config.client_name
162
186
  headers["cdf-version"] = api_version or self.config.api_subversion
163
- if not global_config.disable_gzip:
187
+ if not global_config.disable_gzip and content_length is None:
164
188
  headers["Content-Encoding"] = "gzip"
165
189
  return headers
166
190
 
167
191
  def _make_request(self, item: RequestMessage) -> httpx.Response:
168
- headers = self._create_headers(item.api_version, item.content_type, item.accept)
192
+ headers = self._create_headers(item.api_version, item.content_type, item.accept, item.content_length)
169
193
  params: dict[str, PrimitiveType] | None = None
170
194
  if isinstance(item, ParamRequest):
171
195
  params = item.parameters
172
196
  data: str | bytes | None = None
173
197
  if isinstance(item, BodyRequest):
174
198
  data = item.data()
175
- if not global_config.disable_gzip:
199
+ if not global_config.disable_gzip and item.content_length is None:
176
200
  data = gzip.compress(data.encode("utf-8"))
177
201
  elif isinstance(item, DataBodyRequest):
178
202
  data = item.data()
179
- if not global_config.disable_gzip:
203
+ if not global_config.disable_gzip and item.content_length is None:
180
204
  data = gzip.compress(data)
181
205
  return self.session.request(
182
206
  method=item.method,
@@ -188,9 +212,7 @@ class HTTPClient:
188
212
  follow_redirects=False,
189
213
  )
190
214
 
191
- def _handle_response(
192
- self, response: httpx.Response, request: RequestMessage, console: Console | None = None
193
- ) -> Sequence[HTTPMessage]:
215
+ def _handle_response(self, response: httpx.Response, request: RequestMessage) -> Sequence[HTTPMessage]:
194
216
  if 200 <= response.status_code < 300:
195
217
  return request.create_success_response(response)
196
218
  elif (
@@ -210,11 +232,11 @@ class HTTPClient:
210
232
 
211
233
  retry_after = self._get_retry_after_in_header(response)
212
234
  if retry_after is not None and response.status_code == 429 and request.status_attempt < self._max_retries:
213
- if console is not None:
235
+ if self._console is not None:
214
236
  short_url = request.endpoint_url.removeprefix(self.config.base_api_url)
215
237
  HighSeverityWarning(
216
238
  f"Rate limit exceeded for the {short_url!r} endpoint. Retrying after {retry_after} seconds."
217
- ).print_warning(console=console)
239
+ ).print_warning(console=self._console)
218
240
  request.status_attempt += 1
219
241
  time.sleep(retry_after)
220
242
  return [request]
@@ -267,3 +289,248 @@ class HTTPClient:
267
289
  error_msg = f"RequestException after {request.total_attempts - 1} attempts ({error_type} error): {e!s}"
268
290
 
269
291
  return request.create_failed_request(error_msg)
292
+
293
+ def request_single(self, message: RequestMessage2) -> RequestMessage2 | HTTPResult2:
294
+ """Send an HTTP request and return the response.
295
+
296
+ Args:
297
+ message (RequestMessage2): The request message to send.
298
+ Returns:
299
+ HTTPMessage: The response message.
300
+ """
301
+ try:
302
+ response = self._make_request2(message)
303
+ result = self._handle_response_single(response, message)
304
+ except Exception as e:
305
+ result = self._handle_error_single(e, message)
306
+ return result
307
+
308
+ def request_single_retries(self, message: RequestMessage2) -> HTTPResult2:
309
+ """Send an HTTP request and handle retries.
310
+
311
+ This method will keep retrying the request until it either succeeds or
312
+ exhausts the maximum number of retries.
313
+
314
+ Note this method will use the current thread to process all request, thus
315
+ it is blocking.
316
+
317
+ Args:
318
+ message (RequestMessage2): The request message to send.
319
+ Returns:
320
+ HTTPMessage2: The final response message, which can be either successful response or failed request.
321
+ """
322
+ if message.total_attempts > 0:
323
+ raise RuntimeError(f"RequestMessage has already been attempted {message.total_attempts} times.")
324
+ current_request = message
325
+ while True:
326
+ result = self.request_single(current_request)
327
+ if isinstance(result, RequestMessage2):
328
+ current_request = result
329
+ elif isinstance(result, HTTPResult2):
330
+ return result
331
+ else:
332
+ raise TypeError(f"Unexpected result type: {type(result)}")
333
+
334
+ def _make_request2(self, message: BaseRequestMessage) -> httpx.Response:
335
+ headers = self._create_headers(message.api_version, message.content_type, message.accept)
336
+ return self.session.request(
337
+ method=message.method,
338
+ url=message.endpoint_url,
339
+ content=message.content,
340
+ headers=headers,
341
+ params=message.parameters,
342
+ timeout=self.config.timeout,
343
+ follow_redirects=False,
344
+ )
345
+
346
+ def _handle_response_single(
347
+ self, response: httpx.Response, request: RequestMessage2
348
+ ) -> RequestMessage2 | HTTPResult2:
349
+ if 200 <= response.status_code < 300:
350
+ return SuccessResponse2(
351
+ status_code=response.status_code,
352
+ body=response.text,
353
+ content=response.content,
354
+ )
355
+ if retry_request := self._retry_request(response, request):
356
+ return retry_request
357
+ else:
358
+ # Permanent failure
359
+ return FailedResponse2(
360
+ status_code=response.status_code,
361
+ body=response.text,
362
+ error=ErrorDetails2.from_response(response),
363
+ )
364
+
365
+ def _retry_request(self, response: httpx.Response, request: _T_Request_Message) -> _T_Request_Message | None:
366
+ retry_after = self._get_retry_after_in_header(response)
367
+ if retry_after is not None and response.status_code == 429 and request.status_attempt < self._max_retries:
368
+ if self._console is not None:
369
+ short_url = request.endpoint_url.removeprefix(self.config.base_api_url)
370
+ HighSeverityWarning(
371
+ f"Rate limit exceeded for the {short_url!r} endpoint. Retrying after {retry_after} seconds."
372
+ ).print_warning(console=self._console)
373
+ request.status_attempt += 1
374
+ time.sleep(retry_after)
375
+ return request
376
+
377
+ if request.status_attempt < self._max_retries and response.status_code in self._retry_status_codes:
378
+ request.status_attempt += 1
379
+ time.sleep(self._backoff_time(request.total_attempts))
380
+ return request
381
+ return None
382
+
383
+ def _handle_error_single(self, e: Exception, request: RequestMessage2) -> RequestMessage2 | HTTPResult2:
384
+ if isinstance(e, httpx.ReadTimeout | httpx.TimeoutException):
385
+ error_type = "read"
386
+ request.read_attempt += 1
387
+ attempts = request.read_attempt
388
+ elif isinstance(e, ConnectionError | httpx.ConnectError | httpx.ConnectTimeout):
389
+ error_type = "connect"
390
+ request.connect_attempt += 1
391
+ attempts = request.connect_attempt
392
+ else:
393
+ error_msg = f"Unexpected exception: {e!s}"
394
+ return FailedRequest2(error=error_msg)
395
+
396
+ if attempts <= self._max_retries:
397
+ time.sleep(self._backoff_time(request.total_attempts))
398
+ return request
399
+ else:
400
+ # We have already incremented the attempt count, so we subtract 1 here
401
+ error_msg = f"RequestException after {request.total_attempts - 1} attempts ({error_type} error): {e!s}"
402
+
403
+ return FailedRequest2(error=error_msg)
404
+
405
+ def request_items(self, message: ItemsRequest2) -> Sequence[ItemsRequest2 | ItemsResultMessage2]:
406
+ """Send an HTTP request with multiple items and return the response.
407
+
408
+ Args:
409
+ message (ItemsRequest2): The request message to send.
410
+ Returns:
411
+ Sequence[ItemsRequest2 | ItemsResultMessage2]: The response message(s). This can also
412
+ include ItemsRequest2(s) to be retried or split.
413
+ """
414
+ if message.tracker and message.tracker.limit_reached():
415
+ return [
416
+ ItemsFailedRequest2(
417
+ ids=[item.as_id() for item in message.items],
418
+ error_message=f"Aborting further splitting of requests after {message.tracker.failed_split_count} failed attempts.",
419
+ )
420
+ ]
421
+ try:
422
+ response = self._make_request2(message)
423
+ results = self._handle_items_response(response, message)
424
+ except Exception as e:
425
+ results = self._handle_items_error(e, message)
426
+ return results
427
+
428
+ def request_items_retries(self, message: ItemsRequest2) -> ItemsResultList:
429
+ """Send an HTTP request with multiple items and handle retries.
430
+
431
+ This method will keep retrying the request until it either succeeds or
432
+ exhausts the maximum number of retries.
433
+
434
+ Note this method will use the current thread to process all request, thus
435
+ it is blocking.
436
+
437
+ Args:
438
+ message (ItemsRequest2): The request message to send.
439
+ Returns:
440
+ Sequence[ItemsResultMessage2]: The final response message, which can be either successful response or failed request.
441
+ """
442
+ if message.total_attempts > 0:
443
+ raise RuntimeError(f"ItemsRequest2 has already been attempted {message.total_attempts} times.")
444
+ pending_requests: deque[ItemsRequest2] = deque()
445
+ pending_requests.append(message)
446
+ final_responses = ItemsResultList([])
447
+ while pending_requests:
448
+ current_request = pending_requests.popleft()
449
+ results = self.request_items(current_request)
450
+
451
+ for result in results:
452
+ if isinstance(result, ItemsRequest2):
453
+ pending_requests.append(result)
454
+ elif isinstance(result, ItemsResultMessage2):
455
+ final_responses.append(result)
456
+ else:
457
+ raise TypeError(f"Unexpected result type: {type(result)}")
458
+
459
+ return final_responses
460
+
461
+ def _handle_items_response(
462
+ self, response: httpx.Response, request: ItemsRequest2
463
+ ) -> Sequence[ItemsRequest2 | ItemsResultMessage2]:
464
+ if 200 <= response.status_code < 300:
465
+ return [
466
+ ItemsSuccessResponse2(
467
+ ids=[item.as_id() for item in request.items],
468
+ status_code=response.status_code,
469
+ body=response.text,
470
+ content=response.content,
471
+ )
472
+ ]
473
+ elif len(request.items) > 1 and response.status_code in self._split_items_status_codes:
474
+ # 4XX: Status there is at least one item that is invalid, split the batch to get all valid items processed
475
+ # 5xx: Server error, split to reduce the number of items in each request, and count as a status attempt
476
+ status_attempts = request.status_attempt
477
+ if 500 <= response.status_code < 600:
478
+ status_attempts += 1
479
+ splits = request.split(status_attempts=status_attempts)
480
+ if splits[0].tracker and splits[0].tracker.limit_reached():
481
+ return [
482
+ ItemsFailedResponse2(
483
+ ids=[item.as_id() for item in request.items],
484
+ status_code=response.status_code,
485
+ body=response.text,
486
+ error=ErrorDetails2.from_response(response),
487
+ )
488
+ ]
489
+ return splits
490
+
491
+ if retry_request := self._retry_request(response, request):
492
+ return [retry_request]
493
+ else:
494
+ # Permanent failure
495
+ return [
496
+ ItemsFailedResponse2(
497
+ ids=[item.as_id() for item in request.items],
498
+ status_code=response.status_code,
499
+ body=response.text,
500
+ error=ErrorDetails2.from_response(response),
501
+ )
502
+ ]
503
+
504
+ def _handle_items_error(
505
+ self, e: Exception, request: ItemsRequest2
506
+ ) -> Sequence[ItemsRequest2 | ItemsResultMessage2]:
507
+ if isinstance(e, httpx.ReadTimeout | httpx.TimeoutException):
508
+ error_type = "read"
509
+ request.read_attempt += 1
510
+ attempts = request.read_attempt
511
+ elif isinstance(e, ConnectionError | httpx.ConnectError | httpx.ConnectTimeout):
512
+ error_type = "connect"
513
+ request.connect_attempt += 1
514
+ attempts = request.connect_attempt
515
+ else:
516
+ error_msg = f"Unexpected exception: {e!s}"
517
+ return [
518
+ ItemsFailedRequest2(
519
+ ids=[item.as_id() for item in request.items],
520
+ error_message=error_msg,
521
+ )
522
+ ]
523
+
524
+ if attempts <= self._max_retries:
525
+ time.sleep(self._backoff_time(request.total_attempts))
526
+ return [request]
527
+ else:
528
+ # We have already incremented the attempt count, so we subtract 1 here
529
+ error_msg = f"RequestException after {request.total_attempts - 1} attempts ({error_type} error): {e!s}"
530
+
531
+ return [
532
+ ItemsFailedRequest2(
533
+ ids=[item.as_id() for item in request.items],
534
+ error_message=error_msg,
535
+ )
536
+ ]
@@ -1,6 +1,6 @@
1
1
  from abc import ABC, abstractmethod
2
2
  from collections import UserList
3
- from collections.abc import Sequence
3
+ from collections.abc import Hashable, Sequence
4
4
  from dataclasses import dataclass, field
5
5
  from typing import Generic, Literal, Protocol, TypeAlias, TypeVar
6
6
 
@@ -76,6 +76,9 @@ class ErrorDetails:
76
76
  class FailedRequestMessage(HTTPMessage):
77
77
  error: str
78
78
 
79
+ def __str__(self) -> str:
80
+ return self.error
81
+
79
82
 
80
83
  @dataclass
81
84
  class ResponseMessage(HTTPMessage):
@@ -87,13 +90,14 @@ class RequestMessage(HTTPMessage):
87
90
  """Base class for HTTP request messages"""
88
91
 
89
92
  endpoint_url: str
90
- method: Literal["GET", "POST", "PATCH", "DELETE"]
93
+ method: Literal["GET", "POST", "PATCH", "DELETE", "PUT"]
91
94
  connect_attempt: int = 0
92
95
  read_attempt: int = 0
93
96
  status_attempt: int = 0
94
97
  api_version: str | None = None
95
98
  content_type: str = "application/json"
96
99
  accept: str = "application/json"
100
+ content_length: int | None = None
97
101
 
98
102
  @property
99
103
  def total_attempts(self) -> int:
@@ -115,6 +119,13 @@ class RequestMessage(HTTPMessage):
115
119
  @dataclass
116
120
  class SuccessResponse(ResponseMessage):
117
121
  body: str
122
+ content: bytes
123
+
124
+ def dump(self) -> dict[str, JsonVal]:
125
+ output = super().dump()
126
+ # We cannot serialize bytes, so we indicate its presence instead
127
+ output["content"] = "<bytes>" if self.content else None
128
+ return output
118
129
 
119
130
 
120
131
  @dataclass
@@ -127,6 +138,9 @@ class FailedResponse(ResponseMessage):
127
138
  output["error"] = self.error.dump()
128
139
  return output
129
140
 
141
+ def __str__(self) -> str:
142
+ return f"{self.error.code} | {self.error.message}"
143
+
130
144
 
131
145
  @dataclass
132
146
  class SimpleRequest(RequestMessage):
@@ -134,7 +148,7 @@ class SimpleRequest(RequestMessage):
134
148
 
135
149
  @classmethod
136
150
  def create_success_response(cls, response: httpx.Response) -> Sequence[ResponseMessage]:
137
- return [SuccessResponse(status_code=response.status_code, body=response.text)]
151
+ return [SuccessResponse(status_code=response.status_code, body=response.text, content=response.content)]
138
152
 
139
153
  @classmethod
140
154
  def create_failure_response(cls, response: httpx.Response) -> Sequence[HTTPMessage]:
@@ -309,7 +323,11 @@ class ItemsRequest(Generic[T_COVARIANT_ID], BodyRequest):
309
323
 
310
324
  def create_success_response(self, response: httpx.Response) -> Sequence[HTTPMessage]:
311
325
  ids = [item.as_id() for item in self.items]
312
- return [SuccessResponseItems(status_code=response.status_code, ids=ids, body=response.text)]
326
+ return [
327
+ SuccessResponseItems(
328
+ status_code=response.status_code, ids=ids, body=response.text, content=response.content
329
+ )
330
+ ]
313
331
 
314
332
  def create_failure_response(self, response: httpx.Response) -> Sequence[HTTPMessage]:
315
333
  error = ErrorDetails.from_response(response)
@@ -338,6 +356,18 @@ class ResponseList(UserList[ResponseMessage | FailedRequestMessage]):
338
356
  error_messages += "; ".join(f"Request error: {err.error}" for err in failed_requests)
339
357
  raise ToolkitAPIError(f"One or more requests failed: {error_messages}")
340
358
 
359
+ @property
360
+ def has_failed(self) -> bool:
361
+ """Indicates whether any response in the list indicates a failure.
362
+
363
+ Returns:
364
+ bool: True if there are any failed responses or requests, False otherwise.
365
+ """
366
+ for resp in self.data:
367
+ if isinstance(resp, FailedResponse | FailedRequestMessage):
368
+ return True
369
+ return False
370
+
341
371
  def get_first_body(self) -> dict[str, JsonVal]:
342
372
  """Returns the body of the first successful response in the list.
343
373
 
@@ -352,6 +382,28 @@ class ResponseList(UserList[ResponseMessage | FailedRequestMessage]):
352
382
  return _json.loads(resp.body)
353
383
  raise ValueError("No successful responses with a body found.")
354
384
 
385
+ def as_item_responses(self, item_id: Hashable) -> list[ResponseMessage | FailedRequestMessage]:
386
+ # Convert the responses to per-item responses
387
+ results: list[ResponseMessage | FailedRequestMessage] = []
388
+ for message in self.data:
389
+ if isinstance(message, SuccessResponse):
390
+ results.append(
391
+ SuccessResponseItems(
392
+ status_code=message.status_code, content=message.content, ids=[item_id], body=message.body
393
+ )
394
+ )
395
+ elif isinstance(message, FailedResponse):
396
+ results.append(
397
+ FailedResponseItems(
398
+ status_code=message.status_code, ids=[item_id], body=message.body, error=message.error
399
+ )
400
+ )
401
+ elif isinstance(message, FailedRequestMessage):
402
+ results.append(FailedRequestItems(ids=[item_id], error=message.error))
403
+ else:
404
+ results.append(message)
405
+ return results
406
+
355
407
 
356
408
  def _dump_body(body: dict[str, JsonVal]) -> str:
357
409
  try: