cognite-toolkit 0.7.47__py3-none-any.whl → 0.7.48__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 (59) hide show
  1. cognite_toolkit/_cdf_tk/apps/_migrate_app.py +6 -6
  2. cognite_toolkit/_cdf_tk/client/_toolkit_client.py +6 -4
  3. cognite_toolkit/_cdf_tk/client/api/instances.py +139 -0
  4. cognite_toolkit/_cdf_tk/client/api/location_filters.py +177 -0
  5. cognite_toolkit/_cdf_tk/client/api/raw.py +2 -2
  6. cognite_toolkit/_cdf_tk/client/api/robotics.py +19 -0
  7. cognite_toolkit/_cdf_tk/client/api/robotics_capabilities.py +127 -0
  8. cognite_toolkit/_cdf_tk/client/api/robotics_data_postprocessing.py +138 -0
  9. cognite_toolkit/_cdf_tk/client/api/robotics_frames.py +122 -0
  10. cognite_toolkit/_cdf_tk/client/api/robotics_locations.py +127 -0
  11. cognite_toolkit/_cdf_tk/client/api/robotics_maps.py +122 -0
  12. cognite_toolkit/_cdf_tk/client/api/robotics_robots.py +122 -0
  13. cognite_toolkit/_cdf_tk/client/api/search_config.py +101 -0
  14. cognite_toolkit/_cdf_tk/client/api/streams.py +63 -55
  15. cognite_toolkit/_cdf_tk/client/api/three_d.py +293 -277
  16. cognite_toolkit/_cdf_tk/client/cdf_client/api.py +34 -5
  17. cognite_toolkit/_cdf_tk/client/http_client/_client.py +5 -2
  18. cognite_toolkit/_cdf_tk/client/http_client/_data_classes2.py +4 -3
  19. cognite_toolkit/_cdf_tk/client/request_classes/filters.py +45 -1
  20. cognite_toolkit/_cdf_tk/client/resource_classes/apm_config.py +128 -0
  21. cognite_toolkit/_cdf_tk/client/resource_classes/cognite_file.py +53 -0
  22. cognite_toolkit/_cdf_tk/client/resource_classes/data_modeling/__init__.py +4 -0
  23. cognite_toolkit/_cdf_tk/client/resource_classes/data_modeling/_instance.py +22 -11
  24. cognite_toolkit/_cdf_tk/client/resource_classes/identifiers.py +7 -0
  25. cognite_toolkit/_cdf_tk/client/resource_classes/location_filter.py +9 -2
  26. cognite_toolkit/_cdf_tk/client/resource_classes/resource_view_mapping.py +38 -0
  27. cognite_toolkit/_cdf_tk/client/resource_classes/robotics/_map.py +6 -1
  28. cognite_toolkit/_cdf_tk/client/resource_classes/robotics/_robot.py +10 -5
  29. cognite_toolkit/_cdf_tk/client/resource_classes/streams.py +1 -20
  30. cognite_toolkit/_cdf_tk/client/resource_classes/three_d.py +30 -9
  31. cognite_toolkit/_cdf_tk/client/testing.py +2 -2
  32. cognite_toolkit/_cdf_tk/commands/_migrate/data_mapper.py +5 -5
  33. cognite_toolkit/_cdf_tk/commands/_migrate/migration_io.py +11 -7
  34. cognite_toolkit/_cdf_tk/commands/build_v2/_module_parser.py +138 -0
  35. cognite_toolkit/_cdf_tk/commands/build_v2/_modules_parser.py +163 -0
  36. cognite_toolkit/_cdf_tk/commands/build_v2/build_cmd.py +83 -96
  37. cognite_toolkit/_cdf_tk/commands/build_v2/{build_input.py → build_parameters.py} +8 -22
  38. cognite_toolkit/_cdf_tk/commands/build_v2/data_classes/_modules.py +27 -0
  39. cognite_toolkit/_cdf_tk/commands/build_v2/data_classes/_resource.py +22 -0
  40. cognite_toolkit/_cdf_tk/cruds/__init__.py +11 -5
  41. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/streams.py +14 -30
  42. cognite_toolkit/_cdf_tk/data_classes/__init__.py +3 -0
  43. cognite_toolkit/_cdf_tk/data_classes/_issues.py +36 -0
  44. cognite_toolkit/_cdf_tk/data_classes/_module_directories.py +2 -1
  45. cognite_toolkit/_cdf_tk/storageio/_base.py +2 -0
  46. cognite_toolkit/_cdf_tk/storageio/logger.py +163 -0
  47. cognite_toolkit/_cdf_tk/utils/__init__.py +8 -1
  48. cognite_toolkit/_cdf_tk/utils/interactive_select.py +3 -1
  49. cognite_toolkit/_cdf_tk/utils/modules.py +7 -0
  50. cognite_toolkit/_repo_files/GitHub/.github/workflows/deploy.yaml +1 -1
  51. cognite_toolkit/_repo_files/GitHub/.github/workflows/dry-run.yaml +1 -1
  52. cognite_toolkit/_resources/cdf.toml +1 -1
  53. cognite_toolkit/_version.py +1 -1
  54. {cognite_toolkit-0.7.47.dist-info → cognite_toolkit-0.7.48.dist-info}/METADATA +1 -1
  55. {cognite_toolkit-0.7.47.dist-info → cognite_toolkit-0.7.48.dist-info}/RECORD +58 -40
  56. cognite_toolkit/_cdf_tk/commands/build_v2/build_issues.py +0 -27
  57. /cognite_toolkit/_cdf_tk/client/resource_classes/{search_config_resource.py → search_config.py} +0 -0
  58. {cognite_toolkit-0.7.47.dist-info → cognite_toolkit-0.7.48.dist-info}/WHEEL +0 -0
  59. {cognite_toolkit-0.7.47.dist-info → cognite_toolkit-0.7.48.dist-info}/entry_points.txt +0 -0
@@ -1,80 +1,95 @@
1
- from collections import defaultdict
2
1
  from collections.abc import Iterable, Sequence
3
- from typing import Any, TypeVar
2
+ from typing import TypeVar
4
3
 
5
- from pydantic import TypeAdapter
6
- from rich.console import Console
7
-
8
- from cognite_toolkit._cdf_tk.client.cdf_client.responses import PagedResponse
4
+ from cognite_toolkit._cdf_tk.client.cdf_client import CDFResourceAPI, PagedResponse
5
+ from cognite_toolkit._cdf_tk.client.cdf_client.api import Endpoint
9
6
  from cognite_toolkit._cdf_tk.client.http_client import (
10
7
  HTTPClient,
11
- ItemsRequest2,
12
- RequestMessage2,
8
+ ItemsSuccessResponse2,
9
+ SuccessResponse2,
13
10
  )
11
+ from cognite_toolkit._cdf_tk.client.request_classes.filters import ThreeDAssetMappingFilter
14
12
  from cognite_toolkit._cdf_tk.client.resource_classes.identifiers import InternalId
15
13
  from cognite_toolkit._cdf_tk.client.resource_classes.three_d import (
16
14
  AssetMappingClassicRequest,
15
+ AssetMappingClassicResponse,
17
16
  AssetMappingDMRequest,
18
- AssetMappingResponse,
17
+ AssetMappingDMResponse,
19
18
  ThreeDModelClassicRequest,
20
19
  ThreeDModelResponse,
21
20
  )
22
- from cognite_toolkit._cdf_tk.utils.collection import chunker_sequence
23
- from cognite_toolkit._cdf_tk.utils.useful_types import PrimitiveType
24
21
 
25
22
 
26
- class ThreeDModelAPI:
27
- ENDPOINT = "/3d/models"
28
- MAX_CLASSIC_MODELS_PER_CREATE_REQUEST = 1000
29
- MAX_MODELS_PER_DELETE_REQUEST = 1000
30
- _LIST_REQUEST_MAX_LIMIT = 1000
23
+ class ThreeDClassicModelsAPI(CDFResourceAPI[InternalId, ThreeDModelClassicRequest, ThreeDModelResponse]):
24
+ def __init__(self, http_client: HTTPClient) -> None:
25
+ super().__init__(
26
+ http_client=http_client,
27
+ method_endpoint_map={
28
+ "create": Endpoint(method="POST", path="/3d/models", item_limit=1000),
29
+ "delete": Endpoint(method="POST", path="/3d/models/delete", item_limit=1000),
30
+ "update": Endpoint(method="POST", path="/3d/models/update", item_limit=1000),
31
+ "retrieve": Endpoint(method="GET", path="/3d/models/{modelId}", item_limit=1000),
32
+ "list": Endpoint(method="GET", path="/3d/models", item_limit=1000),
33
+ },
34
+ )
31
35
 
32
- def __init__(self, http_client: HTTPClient, console: Console) -> None:
33
- self._http_client = http_client
34
- self._console = console
35
- self._config = http_client.config
36
+ def _validate_page_response(
37
+ self, response: SuccessResponse2 | ItemsSuccessResponse2
38
+ ) -> PagedResponse[ThreeDModelResponse]:
39
+ return PagedResponse[ThreeDModelResponse].model_validate_json(response.body)
36
40
 
37
- def create(self, models: Sequence[ThreeDModelClassicRequest]) -> list[ThreeDModelResponse]:
41
+ def create(self, items: Sequence[ThreeDModelClassicRequest]) -> list[ThreeDModelResponse]:
38
42
  """Create 3D models in classic format.
39
43
 
40
44
  Args:
41
- models (Sequence[ThreeDModelClassicRequest]): The 3D model(s) to create.
45
+ items (Sequence[ThreeDModelClassicRequest]): The 3D model(s) to create.
42
46
 
43
47
  Returns:
44
48
  list[ThreeDModelResponse]: The created 3D model(s).
45
49
  """
46
- if not models:
47
- return []
48
- if len(models) > self.MAX_CLASSIC_MODELS_PER_CREATE_REQUEST:
49
- raise ValueError("Cannot create more than 1000 3D models in a single request.")
50
- responses = self._http_client.request_items_retries(
51
- ItemsRequest2(
52
- endpoint_url=self._config.create_api_url(self.ENDPOINT),
53
- method="POST",
54
- items=models,
55
- )
56
- )
57
- responses.raise_for_status()
58
- return TypeAdapter(list[ThreeDModelResponse]).validate_python(responses.get_items())
50
+ return self._request_item_response(items, "create")
51
+
52
+ def retrieve(self, ids: Sequence[InternalId]) -> list[ThreeDModelResponse]:
53
+ """Retrieve 3D models by their IDs.
54
+
55
+ Args:
56
+ ids (Sequence[int]): The IDs of the 3D models to retrieve.
57
+
58
+ Returns:
59
+ list[ThreeDModelResponse]: The retrieved 3D model(s).
60
+ """
61
+ return self._request_item_response(ids, "retrieve")
62
+
63
+ def update(self, items: Sequence[ThreeDModelClassicRequest]) -> list[ThreeDModelResponse]:
64
+ """Update 3D models in classic format.
65
+
66
+ Args:
67
+ items (Sequence[ThreeDModelClassicRequest]): The 3D model(s) to update.
68
+
69
+ Returns:
70
+ list[ThreeDModelResponse]: The updated 3D model(s).
71
+ """
72
+ return self._request_item_response(items, "update")
59
73
 
60
- def delete(self, ids: Sequence[int]) -> None:
74
+ def delete(self, ids: Sequence[InternalId]) -> None:
61
75
  """Delete 3D models by their IDs.
62
76
 
63
77
  Args:
64
78
  ids (Sequence[int]): The IDs of the 3D models to delete.
65
79
  """
66
- if not ids:
67
- return None
68
- if len(ids) > self.MAX_MODELS_PER_DELETE_REQUEST:
69
- raise ValueError("Cannot delete more than 1000 3D models in a single request.")
70
- responses = self._http_client.request_items_retries(
71
- ItemsRequest2(
72
- endpoint_url=self._config.create_api_url(self.ENDPOINT + "/delete"),
73
- method="POST",
74
- items=InternalId.from_ids(list(ids)),
75
- )
76
- )
77
- responses.raise_for_status()
80
+ self._request_no_response(ids, "delete")
81
+
82
+ @staticmethod
83
+ def _create_list_filter(include_revision_info: bool, published: bool | None) -> dict[str, bool]:
84
+ params = {
85
+ # There is a bug in the API. The parameter includeRevisionInfo is expected to be lower case and not
86
+ # camel case as documented. You get error message: Unrecognized query parameter includeRevisionInfo,
87
+ # did you mean includerevisioninfo?
88
+ "includerevisioninfo": include_revision_info,
89
+ }
90
+ if published is not None:
91
+ params["published"] = published
92
+ return params
78
93
 
79
94
  def paginate(
80
95
  self,
@@ -83,75 +98,54 @@ class ThreeDModelAPI:
83
98
  limit: int = 100,
84
99
  cursor: str | None = None,
85
100
  ) -> PagedResponse[ThreeDModelResponse]:
86
- if not (0 < limit <= self._LIST_REQUEST_MAX_LIMIT):
87
- raise ValueError(f"Limit must be between 1 and {self._LIST_REQUEST_MAX_LIMIT}, got {limit}.")
88
- parameters: dict[str, PrimitiveType] = {
89
- # There is a bug in the API. The parameter includeRevisionInfo is expected to be lower case and not
90
- # camel case as documented. You get error message: Unrecognized query parameter includeRevisionInfo,
91
- # did you mean includerevisioninfo?
92
- "includerevisioninfo": include_revision_info,
93
- "limit": limit,
94
- }
95
- if published is not None:
96
- parameters["published"] = published
97
- if cursor is not None:
98
- parameters["cursor"] = cursor
99
- responses = self._http_client.request_single_retries(
100
- RequestMessage2(
101
- endpoint_url=self._config.create_api_url(self.ENDPOINT),
102
- method="GET",
103
- parameters=parameters,
104
- )
105
- )
106
- success_response = responses.get_success_or_raise()
107
- return PagedResponse[ThreeDModelResponse].model_validate(success_response.body_json)
101
+ params = self._create_list_filter(include_revision_info, published)
102
+ return self._paginate(limit=limit, cursor=cursor, params=params)
103
+
104
+ def iterate(
105
+ self,
106
+ published: bool | None = None,
107
+ include_revision_info: bool = False,
108
+ limit: int = 100,
109
+ cursor: str | None = None,
110
+ ) -> Iterable[list[ThreeDModelResponse]]:
111
+ params = self._create_list_filter(include_revision_info, published)
112
+ return self._iterate(limit=limit, cursor=cursor, params=params)
108
113
 
109
114
  def list(
110
115
  self,
111
116
  published: bool | None = None,
112
117
  include_revision_info: bool = False,
113
118
  limit: int | None = 100,
114
- cursor: str | None = None,
115
119
  ) -> list[ThreeDModelResponse]:
116
- results: list[ThreeDModelResponse] = []
117
- while True:
118
- request_limit = (
119
- self._LIST_REQUEST_MAX_LIMIT
120
- if limit is None
121
- else min(limit - len(results), self._LIST_REQUEST_MAX_LIMIT)
122
- )
123
- if request_limit <= 0:
124
- break
125
- page = self.paginate(
126
- published=published,
127
- include_revision_info=include_revision_info,
128
- limit=request_limit,
129
- cursor=cursor,
130
- )
131
- results.extend(page.items)
132
- if page.next_cursor is None:
133
- break
134
- cursor = page.next_cursor
135
- return results
120
+ params = self._create_list_filter(include_revision_info, published)
121
+ return self._list(limit=limit, params=params)
136
122
 
137
123
 
138
124
  T_RequestMapping = TypeVar("T_RequestMapping", bound=AssetMappingClassicRequest | AssetMappingDMRequest)
139
125
 
140
126
 
141
- class ThreeDAssetMappingAPI:
127
+ class ThreeDClassicAssetMappingAPI(
128
+ CDFResourceAPI[AssetMappingClassicRequest, AssetMappingClassicRequest, AssetMappingClassicResponse]
129
+ ):
142
130
  ENDPOINT = "/3d/models/{modelId}/revisions/{revisionId}/mappings"
143
- CREATE_CLASSIC_MAX_MAPPINGS_PER_REQUEST = 1000
144
- CREATE_DM_MAX_MAPPINGS_PER_REQUEST = 100
145
- DELETE_CLASSIC_MAX_MAPPINGS_PER_REQUEST = 1000
146
- DELETE_DM_MAX_MAPPINGS_PER_REQUEST = 100
147
- LIST_REQUEST_MAX_LIMIT = 1000
148
-
149
- def __init__(self, http_client: HTTPClient, console: Console) -> None:
150
- self._http_client = http_client
151
- self._console = console
152
- self._config = http_client.config
153
-
154
- def create(self, mappings: Sequence[AssetMappingClassicRequest]) -> list[AssetMappingResponse]:
131
+
132
+ def __init__(self, http_client: HTTPClient) -> None:
133
+ super().__init__(
134
+ http_client=http_client,
135
+ method_endpoint_map={
136
+ # These endpoints are parameterized, so the paths are templates
137
+ "create": Endpoint(method="POST", path=self.ENDPOINT, item_limit=1000),
138
+ "delete": Endpoint(method="POST", path=f"{self.ENDPOINT}/delete", item_limit=1000),
139
+ "list": Endpoint(method="POST", path=f"{self.ENDPOINT}/list", item_limit=1000),
140
+ },
141
+ )
142
+
143
+ def _validate_page_response(
144
+ self, response: SuccessResponse2 | ItemsSuccessResponse2
145
+ ) -> PagedResponse[AssetMappingClassicResponse]:
146
+ return PagedResponse[AssetMappingClassicResponse].model_validate_json(response.body)
147
+
148
+ def create(self, mappings: Sequence[AssetMappingClassicRequest]) -> list[AssetMappingClassicResponse]:
155
149
  """Create 3D asset mappings.
156
150
 
157
151
  Args:
@@ -159,32 +153,123 @@ class ThreeDAssetMappingAPI:
159
153
  The 3D asset mapping(s) to create.
160
154
 
161
155
  Returns:
162
- list[AssetMappingResponse]: The created 3D asset mapping(s).
156
+ list[AssetMappingClassicResponse]: The created 3D asset mapping(s).
163
157
  """
164
- results: list[AssetMappingResponse] = []
165
- for endpoint, model_id, revision_id, revision_mappings in self._chunk_mappings_by_endpoint(
166
- mappings, self.CREATE_CLASSIC_MAX_MAPPINGS_PER_REQUEST
167
- ):
168
- responses = self._http_client.request_items_retries(
169
- ItemsRequest2(
170
- endpoint_url=self._config.create_api_url(endpoint),
171
- method="POST",
172
- items=revision_mappings,
173
- )
174
- )
175
- responses.raise_for_status()
176
- items = responses.get_items()
177
- for item in items:
158
+ results: list[AssetMappingClassicResponse] = []
159
+ endpoint = self._method_endpoint_map["create"]
160
+ for (model_id, revision_id), group in self._group_items_by_text_field(
161
+ mappings, "model_id", "revision_id"
162
+ ).items():
163
+ path = endpoint.path.format(modelId=model_id, revisionId=revision_id)
164
+ result = self._request_item_response(group, "create", endpoint=path)
165
+ for item in result:
178
166
  # We append modelId and revisionId to each item since the API does not return them
179
167
  # this is needed to fully populate the AssetMappingResponse data class
180
- item["modelId"] = model_id
181
- item["revisionId"] = revision_id
182
- results.extend(TypeAdapter(list[AssetMappingResponse]).validate_python(items))
168
+ object.__setattr__(item, "model_id", int(model_id))
169
+ object.__setattr__(item, "revision_id", int(revision_id))
170
+ results.extend(result)
183
171
  return results
184
172
 
185
- def create_dm(
173
+ def delete(self, mappings: Sequence[AssetMappingClassicRequest]) -> None:
174
+ """Delete 3D asset mappings.
175
+
176
+ Args:
177
+ mappings (Sequence[AssetMappingClassicRequest]):
178
+ The 3D asset mapping(s) to delete.
179
+ """
180
+ endpoint = self._method_endpoint_map["delete"]
181
+ for (model_id, revision_id), group in self._group_items_by_text_field(
182
+ mappings, "model_id", "revision_id"
183
+ ).items():
184
+ path = endpoint.path.format(modelId=model_id, revisionId=revision_id)
185
+ self._request_no_response(group, "delete", endpoint=path)
186
+ return None
187
+
188
+ def paginate(
189
+ self,
190
+ model_id: int,
191
+ revision_id: int,
192
+ filter: ThreeDAssetMappingFilter | None = None,
193
+ limit: int = 100,
194
+ cursor: str | None = None,
195
+ ) -> PagedResponse[AssetMappingClassicResponse]:
196
+ endpoint = self._method_endpoint_map["list"]
197
+ path = endpoint.path.format(modelId=model_id, revisionId=revision_id)
198
+ page = self._paginate(
199
+ limit=limit,
200
+ cursor=cursor,
201
+ body={"filter": filter.dump() if filter else None, "getDmsInstances": False},
202
+ endpoint_path=path,
203
+ )
204
+ # Add modelId and revisionId to items since the API does not return them
205
+ for item in page.items:
206
+ object.__setattr__(item, "model_id", model_id)
207
+ object.__setattr__(item, "revision_id", revision_id)
208
+ return page
209
+
210
+ def iterate(
211
+ self,
212
+ model_id: int,
213
+ revision_id: int,
214
+ filter: ThreeDAssetMappingFilter | None = None,
215
+ limit: int = 100,
216
+ ) -> Iterable[list[AssetMappingClassicResponse]]:
217
+ endpoint = self._method_endpoint_map["list"]
218
+ path = endpoint.path.format(modelId=model_id, revisionId=revision_id)
219
+ for items in self._iterate(
220
+ body={"filter": filter.dump() if filter else None, "getDmsInstances": False},
221
+ limit=limit,
222
+ endpoint_path=path,
223
+ ):
224
+ # Add modelId and revisionId to items since the API does not return them
225
+ for item in items:
226
+ object.__setattr__(item, "model_id", model_id)
227
+ object.__setattr__(item, "revision_id", revision_id)
228
+ yield items
229
+
230
+ def list(
231
+ self,
232
+ model_id: int,
233
+ revision_id: int,
234
+ filter: ThreeDAssetMappingFilter | None = None,
235
+ limit: int | None = 100,
236
+ ) -> list[AssetMappingClassicResponse]:
237
+ endpoint = self._method_endpoint_map["list"]
238
+ path = endpoint.path.format(modelId=model_id, revisionId=revision_id)
239
+ items = self._list(
240
+ body={"filter": filter.dump() if filter else None, "getDmsInstances": False},
241
+ limit=limit,
242
+ endpoint_path=path,
243
+ )
244
+ # Add modelId and revisionId to items since the API does not return them
245
+ for item in items:
246
+ object.__setattr__(item, "model_id", model_id)
247
+ object.__setattr__(item, "revision_id", revision_id)
248
+ return items
249
+
250
+
251
+ class ThreeDDMAssetMappingAPI(CDFResourceAPI[AssetMappingDMRequest, AssetMappingDMRequest, AssetMappingDMResponse]):
252
+ ENDPOINT = "/3d/models/{modelId}/revisions/{revisionId}/mappings"
253
+
254
+ def __init__(self, http_client: HTTPClient) -> None:
255
+ super().__init__(
256
+ http_client=http_client,
257
+ method_endpoint_map={
258
+ # These endpoints are parameterized, so the paths are templates
259
+ "create": Endpoint(method="POST", path=self.ENDPOINT, item_limit=100),
260
+ "delete": Endpoint(method="POST", path=f"{self.ENDPOINT}/delete", item_limit=100),
261
+ "list": Endpoint(method="POST", path=f"{self.ENDPOINT}/list", item_limit=1000),
262
+ },
263
+ )
264
+
265
+ def _validate_page_response(
266
+ self, response: SuccessResponse2 | ItemsSuccessResponse2
267
+ ) -> PagedResponse[AssetMappingDMResponse]:
268
+ return PagedResponse[AssetMappingDMResponse].model_validate_json(response.body)
269
+
270
+ def create(
186
271
  self, mappings: Sequence[AssetMappingDMRequest], object_3d_space: str, cad_node_space: str
187
- ) -> list[AssetMappingResponse]:
272
+ ) -> list[AssetMappingDMResponse]:
188
273
  """Create 3D asset mappings in Data Modeling format.
189
274
 
190
275
  Args:
@@ -195,69 +280,33 @@ class ThreeDAssetMappingAPI:
195
280
  cad_node_space (str):
196
281
  The instance space where the CogniteCADNode are located.
197
282
  Returns:
198
- list[AssetMappingResponse]: The created 3D asset mapping(s).
283
+ list[AssetMappingDMResponse]: The created 3D asset mapping(s).
199
284
  """
200
- results: list[AssetMappingResponse] = []
201
- for endpoint, model_id, revision_id, revision_mappings in self._chunk_mappings_by_endpoint(
202
- mappings, self.CREATE_DM_MAX_MAPPINGS_PER_REQUEST
203
- ):
204
- responses = self._http_client.request_items_retries(
205
- ItemsRequest2(
206
- endpoint_url=self._config.create_api_url(endpoint),
207
- method="POST",
208
- items=revision_mappings,
209
- extra_body_fields={
210
- "dmsContextualizationConfig": {
211
- "object3DSpace": object_3d_space,
212
- "cadNodeSpace": cad_node_space,
213
- }
214
- },
215
- )
285
+ results: list[AssetMappingDMResponse] = []
286
+ for (model_id, revision_id), group in self._group_items_by_text_field(
287
+ mappings, "model_id", "revision_id"
288
+ ).items():
289
+ path = self.ENDPOINT.format(modelId=model_id, revisionId=revision_id)
290
+ result = self._request_item_response(
291
+ group,
292
+ "create",
293
+ endpoint=path,
294
+ extra_body={
295
+ "dmsContextualizationConfig": {
296
+ "object3DSpace": object_3d_space,
297
+ "cadNodeSpace": cad_node_space,
298
+ }
299
+ },
216
300
  )
217
- responses.raise_for_status()
218
- items = responses.get_items()
219
- for item in items:
301
+ for item in result:
220
302
  # We append modelId and revisionId to each item since the API does not return them
221
- # this is needed to fully populate the AssetMappingResponse data class
222
- item["modelId"] = model_id
223
- item["revisionId"] = revision_id
224
- results.extend(TypeAdapter(list[AssetMappingResponse]).validate_python(items))
303
+ # this is needed to fully populate the AssetMappingDMResponse data class
304
+ object.__setattr__(item, "model_id", int(model_id))
305
+ object.__setattr__(item, "revision_id", int(revision_id))
306
+ results.extend(result)
225
307
  return results
226
308
 
227
- @classmethod
228
- def _chunk_mappings_by_endpoint(
229
- cls, mappings: Sequence[T_RequestMapping], chunk_size: int
230
- ) -> Iterable[tuple[str, int, int, list[T_RequestMapping]]]:
231
- chunked_mappings: dict[tuple[int, int], list[T_RequestMapping]] = defaultdict(list)
232
- for mapping in mappings:
233
- key = mapping.model_id, mapping.revision_id
234
- chunked_mappings[key].append(mapping)
235
- for (model_id, revision_id), revision_mappings in chunked_mappings.items():
236
- endpoint = cls.ENDPOINT.format(modelId=model_id, revisionId=revision_id)
237
- for chunk in chunker_sequence(revision_mappings, chunk_size):
238
- yield endpoint, model_id, revision_id, chunk
239
-
240
- def delete(self, mappings: Sequence[AssetMappingClassicRequest]) -> None:
241
- """Delete 3D asset mappings.
242
-
243
- Args:
244
- mappings (Sequence[AssetMappingClassicRequest]):
245
- The 3D asset mapping(s) to delete.
246
- """
247
- for endpoint, *_, revision_mappings in self._chunk_mappings_by_endpoint(
248
- mappings, self.DELETE_CLASSIC_MAX_MAPPINGS_PER_REQUEST
249
- ):
250
- responses = self._http_client.request_items_retries(
251
- ItemsRequest2(
252
- endpoint_url=self._config.create_api_url(f"{endpoint}/delete"),
253
- method="DELETE",
254
- items=revision_mappings,
255
- )
256
- )
257
- responses.raise_for_status()
258
- return None
259
-
260
- def delete_dm(self, mappings: Sequence[AssetMappingDMRequest], object_3d_space: str, cad_node_space: str) -> None:
309
+ def delete(self, mappings: Sequence[AssetMappingDMRequest], object_3d_space: str, cad_node_space: str) -> None:
261
310
  """Delete 3D asset mappings in Data Modeling format.
262
311
 
263
312
  Args:
@@ -268,118 +317,85 @@ class ThreeDAssetMappingAPI:
268
317
  cad_node_space (str):
269
318
  The instance space where the CogniteCADNode are located.
270
319
  """
271
- for endpoint, *_, revision_mappings in self._chunk_mappings_by_endpoint(
272
- mappings, self.DELETE_DM_MAX_MAPPINGS_PER_REQUEST
273
- ):
274
- responses = self._http_client.request_items_retries(
275
- ItemsRequest2(
276
- endpoint_url=self._config.create_api_url(f"{endpoint}/delete"),
277
- method="DELETE",
278
- items=revision_mappings,
279
- extra_body_fields={
280
- "dmsContextualizationConfig": {
281
- "object3DSpace": object_3d_space,
282
- "cadNodeSpace": cad_node_space,
283
- }
284
- },
285
- )
320
+ endpoint = self._method_endpoint_map["delete"]
321
+ for (model_id, revision_id), group in self._group_items_by_text_field(
322
+ mappings, "model_id", "revision_id"
323
+ ).items():
324
+ path = endpoint.path.format(modelId=model_id, revisionId=revision_id)
325
+ self._request_no_response(
326
+ group,
327
+ "delete",
328
+ endpoint=path,
329
+ extra_body={
330
+ "dmsContextualizationConfig": {
331
+ "object3DSpace": object_3d_space,
332
+ "cadNodeSpace": cad_node_space,
333
+ }
334
+ },
286
335
  )
287
- responses.raise_for_status()
288
336
  return None
289
337
 
290
338
  def paginate(
291
339
  self,
292
340
  model_id: int,
293
341
  revision_id: int,
294
- asset_ids: list[int] | None = None,
295
- asset_instance_ids: list[str] | None = None,
296
- node_ids: list[int] | None = None,
297
- tree_indexes: list[int] | None = None,
298
- get_dms_instances: bool = False,
342
+ filter: ThreeDAssetMappingFilter | None = None,
299
343
  limit: int = 100,
300
344
  cursor: str | None = None,
301
- ) -> PagedResponse[AssetMappingResponse]:
302
- if not (0 < limit <= self.LIST_REQUEST_MAX_LIMIT):
303
- raise ValueError(f"Limit must be between 1 and {self.LIST_REQUEST_MAX_LIMIT}, got {limit}.")
304
- if sum(param is not None for param in [asset_ids, asset_instance_ids, node_ids, tree_indexes]) > 1:
305
- raise ValueError("Only one of asset_ids, asset_instance_ids, node_ids, or tree_indexes can be provided.")
306
- body: dict[str, Any] = {
307
- "getDmsInstances": get_dms_instances,
308
- "limit": limit,
309
- }
310
- if asset_ids is not None:
311
- if not (0 < len(asset_ids) <= 100):
312
- raise ValueError("asset_ids must contain between 1 and 100 IDs.")
313
- body["filter"] = {"assetIds": asset_ids}
314
- elif asset_instance_ids is not None:
315
- if not (0 < len(asset_instance_ids) <= 100):
316
- raise ValueError("asset_instance_ids must contain between 1 and 100 IDs.")
317
- body["filter"] = {"assetInstanceIds": asset_instance_ids}
318
- elif node_ids is not None:
319
- if not (0 < len(node_ids) <= 100):
320
- raise ValueError("node_ids must contain between 1 and 100 IDs.")
321
- body["filter"] = {"nodeIds": node_ids}
322
- elif tree_indexes is not None:
323
- if not (0 < len(tree_indexes) <= 100):
324
- raise ValueError("tree_indexes must contain between 1 and 100 indexes.")
325
- body["filter"] = {"treeIndexes": tree_indexes}
326
- if cursor is not None:
327
- body["cursor"] = cursor
328
-
329
- endpoint = self.ENDPOINT.format(modelId=model_id, revisionId=revision_id)
330
- responses = self._http_client.request_single_retries(
331
- RequestMessage2(
332
- endpoint_url=self._config.create_api_url(f"{endpoint}/list"),
333
- method="POST",
334
- body_content=body,
335
- )
345
+ ) -> PagedResponse[AssetMappingDMResponse]:
346
+ endpoint = self._method_endpoint_map["list"]
347
+ path = endpoint.path.format(modelId=model_id, revisionId=revision_id)
348
+ page = self._paginate(
349
+ limit=limit,
350
+ cursor=cursor,
351
+ body={"filter": filter.dump() if filter else None, "getDmsInstances": True},
352
+ endpoint_path=path,
336
353
  )
337
- success_response = responses.get_success_or_raise()
338
- body_json = success_response.body_json
339
354
  # Add modelId and revisionId to items since the API does not return them
340
- for item in body_json.get("items", []):
341
- item["modelId"] = model_id
342
- item["revisionId"] = revision_id
343
- return PagedResponse[AssetMappingResponse].model_validate(body_json)
355
+ for item in page.items:
356
+ object.__setattr__(item, "model_id", model_id)
357
+ object.__setattr__(item, "revision_id", revision_id)
358
+ return page
359
+
360
+ def iterate(
361
+ self,
362
+ model_id: int,
363
+ revision_id: int,
364
+ filter: ThreeDAssetMappingFilter | None = None,
365
+ limit: int = 100,
366
+ ) -> Iterable[list[AssetMappingDMResponse]]:
367
+ endpoint = self._method_endpoint_map["list"]
368
+ path = endpoint.path.format(modelId=model_id, revisionId=revision_id)
369
+ for items in self._iterate(
370
+ body={"filter": filter.dump() if filter else None, "getDmsInstances": True}, limit=limit, endpoint_path=path
371
+ ):
372
+ # Add modelId and revisionId to items since the API does not return them
373
+ for item in items:
374
+ object.__setattr__(item, "model_id", model_id)
375
+ object.__setattr__(item, "revision_id", revision_id)
376
+ yield items
344
377
 
345
378
  def list(
346
379
  self,
347
380
  model_id: int,
348
381
  revision_id: int,
349
- asset_ids: list[int] | None = None,
350
- asset_instance_ids: list[str] | None = None,
351
- node_ids: list[int] | None = None,
352
- tree_indexes: list[int] | None = None,
353
- get_dms_instances: bool = False,
382
+ filter: ThreeDAssetMappingFilter | None = None,
354
383
  limit: int | None = 100,
355
- ) -> list[AssetMappingResponse]:
356
- results: list[AssetMappingResponse] = []
357
- cursor: str | None = None
358
- while True:
359
- request_limit = (
360
- self.LIST_REQUEST_MAX_LIMIT if limit is None else min(limit - len(results), self.LIST_REQUEST_MAX_LIMIT)
361
- )
362
- if request_limit <= 0:
363
- break
364
- page = self.paginate(
365
- model_id=model_id,
366
- revision_id=revision_id,
367
- asset_ids=asset_ids,
368
- asset_instance_ids=asset_instance_ids,
369
- node_ids=node_ids,
370
- tree_indexes=tree_indexes,
371
- get_dms_instances=get_dms_instances,
372
- limit=request_limit,
373
- cursor=cursor,
374
- )
375
- results.extend(page.items)
376
- if page.next_cursor is None:
377
- break
378
- cursor = page.next_cursor
379
- return results
384
+ ) -> list[AssetMappingDMResponse]:
385
+ endpoint = self._method_endpoint_map["list"]
386
+ path = endpoint.path.format(modelId=model_id, revisionId=revision_id)
387
+ items = self._list(
388
+ body={"filter": filter.dump() if filter else None, "getDmsInstances": True}, limit=limit, endpoint_path=path
389
+ )
390
+ # Add modelId and revisionId to items since the API does not return them
391
+ for item in items:
392
+ object.__setattr__(item, "model_id", model_id)
393
+ object.__setattr__(item, "revision_id", revision_id)
394
+ return items
380
395
 
381
396
 
382
397
  class ThreeDAPI:
383
- def __init__(self, http_client: HTTPClient, console: Console) -> None:
384
- self.models = ThreeDModelAPI(http_client, console)
385
- self.asset_mappings = ThreeDAssetMappingAPI(http_client, console)
398
+ def __init__(self, http_client: HTTPClient) -> None:
399
+ self.models_classic = ThreeDClassicModelsAPI(http_client)
400
+ self.asset_mappings_classic = ThreeDClassicAssetMappingAPI(http_client)
401
+ self.asset_mappings_dm = ThreeDDMAssetMappingAPI(http_client)