cognite-toolkit 0.7.34__py3-none-any.whl → 0.7.36__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 (101) hide show
  1. cognite_toolkit/_cdf_tk/apps/_download_app.py +2 -2
  2. cognite_toolkit/_cdf_tk/apps/_dump_app.py +1 -1
  3. cognite_toolkit/_cdf_tk/apps/_migrate_app.py +101 -0
  4. cognite_toolkit/_cdf_tk/builders/_raw.py +1 -1
  5. cognite_toolkit/_cdf_tk/client/_toolkit_client.py +9 -9
  6. cognite_toolkit/_cdf_tk/client/api/infield.py +16 -17
  7. cognite_toolkit/_cdf_tk/client/api/legacy/__init__.py +0 -0
  8. cognite_toolkit/_cdf_tk/client/api/{canvas.py → legacy/canvas.py} +3 -4
  9. cognite_toolkit/_cdf_tk/client/api/{charts.py → legacy/charts.py} +1 -1
  10. cognite_toolkit/_cdf_tk/client/api/{extended_data_modeling.py → legacy/extended_data_modeling.py} +1 -1
  11. cognite_toolkit/_cdf_tk/client/api/{extended_files.py → legacy/extended_files.py} +2 -2
  12. cognite_toolkit/_cdf_tk/client/api/{extended_raw.py → legacy/extended_raw.py} +1 -1
  13. cognite_toolkit/_cdf_tk/client/api/{extended_timeseries.py → legacy/extended_timeseries.py} +5 -2
  14. cognite_toolkit/_cdf_tk/client/api/{location_filters.py → legacy/location_filters.py} +1 -1
  15. cognite_toolkit/_cdf_tk/client/api/legacy/robotics/__init__.py +8 -0
  16. cognite_toolkit/_cdf_tk/client/api/{robotics → legacy/robotics}/capabilities.py +1 -1
  17. cognite_toolkit/_cdf_tk/client/api/{robotics → legacy/robotics}/data_postprocessing.py +1 -1
  18. cognite_toolkit/_cdf_tk/client/api/{robotics → legacy/robotics}/frames.py +1 -1
  19. cognite_toolkit/_cdf_tk/client/api/{robotics → legacy/robotics}/locations.py +1 -1
  20. cognite_toolkit/_cdf_tk/client/api/{robotics → legacy/robotics}/maps.py +1 -1
  21. cognite_toolkit/_cdf_tk/client/api/{robotics → legacy/robotics}/robots.py +2 -2
  22. cognite_toolkit/_cdf_tk/client/api/{search_config.py → legacy/search_config.py} +5 -1
  23. cognite_toolkit/_cdf_tk/client/api/migration.py +2 -3
  24. cognite_toolkit/_cdf_tk/client/api/project.py +1 -1
  25. cognite_toolkit/_cdf_tk/client/api/search.py +2 -2
  26. cognite_toolkit/_cdf_tk/client/api/streams.py +3 -4
  27. cognite_toolkit/_cdf_tk/client/api/three_d.py +266 -13
  28. cognite_toolkit/_cdf_tk/client/data_classes/api_classes.py +13 -0
  29. cognite_toolkit/_cdf_tk/client/data_classes/base.py +6 -10
  30. cognite_toolkit/_cdf_tk/client/data_classes/instance_api.py +7 -7
  31. cognite_toolkit/_cdf_tk/client/data_classes/legacy/__init__.py +0 -0
  32. cognite_toolkit/_cdf_tk/client/data_classes/{canvas.py → legacy/canvas.py} +1 -1
  33. cognite_toolkit/_cdf_tk/client/data_classes/three_d.py +59 -0
  34. cognite_toolkit/_cdf_tk/client/testing.py +18 -16
  35. cognite_toolkit/_cdf_tk/commands/_migrate/conversion.py +1 -1
  36. cognite_toolkit/_cdf_tk/commands/_migrate/creators.py +1 -1
  37. cognite_toolkit/_cdf_tk/commands/_migrate/data_classes.py +3 -3
  38. cognite_toolkit/_cdf_tk/commands/_migrate/data_mapper.py +60 -12
  39. cognite_toolkit/_cdf_tk/commands/_migrate/default_mappings.py +1 -1
  40. cognite_toolkit/_cdf_tk/commands/_migrate/issues.py +1 -1
  41. cognite_toolkit/_cdf_tk/commands/_migrate/migration_io.py +120 -9
  42. cognite_toolkit/_cdf_tk/commands/_profile.py +1 -1
  43. cognite_toolkit/_cdf_tk/commands/_purge.py +9 -11
  44. cognite_toolkit/_cdf_tk/commands/build_cmd.py +1 -1
  45. cognite_toolkit/_cdf_tk/commands/dump_resource.py +4 -4
  46. cognite_toolkit/_cdf_tk/commands/run.py +1 -1
  47. cognite_toolkit/_cdf_tk/cruds/_data_cruds.py +2 -2
  48. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/auth.py +1 -1
  49. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/classic.py +1 -1
  50. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/configuration.py +1 -1
  51. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/datamodel.py +1 -1
  52. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/extraction_pipeline.py +1 -1
  53. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/fieldops.py +22 -20
  54. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/file.py +1 -1
  55. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/function.py +1 -1
  56. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/industrial_tool.py +1 -1
  57. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/location.py +1 -1
  58. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/migration.py +1 -1
  59. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/raw.py +1 -1
  60. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/robotics.py +1 -1
  61. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/transformation.py +1 -1
  62. cognite_toolkit/_cdf_tk/resource_classes/search_config.py +1 -1
  63. cognite_toolkit/_cdf_tk/storageio/_applications.py +2 -2
  64. cognite_toolkit/_cdf_tk/storageio/_file_content.py +1 -2
  65. cognite_toolkit/_cdf_tk/storageio/_instances.py +1 -1
  66. cognite_toolkit/_cdf_tk/utils/cdf.py +1 -1
  67. cognite_toolkit/_cdf_tk/utils/http_client/_data_classes.py +6 -0
  68. cognite_toolkit/_cdf_tk/utils/http_client/_data_classes2.py +1 -3
  69. cognite_toolkit/_cdf_tk/utils/interactive_select.py +4 -4
  70. cognite_toolkit/_repo_files/GitHub/.github/workflows/deploy.yaml +1 -1
  71. cognite_toolkit/_repo_files/GitHub/.github/workflows/dry-run.yaml +1 -1
  72. cognite_toolkit/_resources/cdf.toml +1 -1
  73. cognite_toolkit/_version.py +1 -1
  74. {cognite_toolkit-0.7.34.dist-info → cognite_toolkit-0.7.36.dist-info}/METADATA +1 -1
  75. {cognite_toolkit-0.7.34.dist-info → cognite_toolkit-0.7.36.dist-info}/RECORD +100 -98
  76. cognite_toolkit/_cdf_tk/client/api/robotics/__init__.py +0 -3
  77. /cognite_toolkit/_cdf_tk/client/api/{dml.py → legacy/dml.py} +0 -0
  78. /cognite_toolkit/_cdf_tk/client/api/{extended_functions.py → legacy/extended_functions.py} +0 -0
  79. /cognite_toolkit/_cdf_tk/client/api/{fixed_transformations.py → legacy/fixed_transformations.py} +0 -0
  80. /cognite_toolkit/_cdf_tk/client/api/{robotics → legacy/robotics}/api.py +0 -0
  81. /cognite_toolkit/_cdf_tk/client/api/{robotics → legacy/robotics}/utlis.py +0 -0
  82. /cognite_toolkit/_cdf_tk/client/data_classes/{apm_config_v1.py → legacy/apm_config_v1.py} +0 -0
  83. /cognite_toolkit/_cdf_tk/client/data_classes/{charts.py → legacy/charts.py} +0 -0
  84. /cognite_toolkit/_cdf_tk/client/data_classes/{extendable_cognite_file.py → legacy/extendable_cognite_file.py} +0 -0
  85. /cognite_toolkit/_cdf_tk/client/data_classes/{extended_filemetadata.py → legacy/extended_filemetadata.py} +0 -0
  86. /cognite_toolkit/_cdf_tk/client/data_classes/{extended_filemetdata.py → legacy/extended_filemetdata.py} +0 -0
  87. /cognite_toolkit/_cdf_tk/client/data_classes/{extended_timeseries.py → legacy/extended_timeseries.py} +0 -0
  88. /cognite_toolkit/_cdf_tk/client/data_classes/{functions.py → legacy/functions.py} +0 -0
  89. /cognite_toolkit/_cdf_tk/client/data_classes/{graphql_data_models.py → legacy/graphql_data_models.py} +0 -0
  90. /cognite_toolkit/_cdf_tk/client/data_classes/{instances.py → legacy/instances.py} +0 -0
  91. /cognite_toolkit/_cdf_tk/client/data_classes/{location_filters.py → legacy/location_filters.py} +0 -0
  92. /cognite_toolkit/_cdf_tk/client/data_classes/{migration.py → legacy/migration.py} +0 -0
  93. /cognite_toolkit/_cdf_tk/client/data_classes/{pending_instances_ids.py → legacy/pending_instances_ids.py} +0 -0
  94. /cognite_toolkit/_cdf_tk/client/data_classes/{project.py → legacy/project.py} +0 -0
  95. /cognite_toolkit/_cdf_tk/client/data_classes/{raw.py → legacy/raw.py} +0 -0
  96. /cognite_toolkit/_cdf_tk/client/data_classes/{robotics.py → legacy/robotics.py} +0 -0
  97. /cognite_toolkit/_cdf_tk/client/data_classes/{search_config.py → legacy/search_config.py} +0 -0
  98. /cognite_toolkit/_cdf_tk/client/data_classes/{sequences.py → legacy/sequences.py} +0 -0
  99. /cognite_toolkit/_cdf_tk/client/data_classes/{streamlit_.py → legacy/streamlit_.py} +0 -0
  100. {cognite_toolkit-0.7.34.dist-info → cognite_toolkit-0.7.36.dist-info}/WHEEL +0 -0
  101. {cognite_toolkit-0.7.34.dist-info → cognite_toolkit-0.7.36.dist-info}/entry_points.txt +0 -0
@@ -1,14 +1,23 @@
1
- from collections.abc import Sequence
1
+ from collections import defaultdict
2
+ from collections.abc import Iterable, Sequence
3
+ from typing import Any, TypeVar
2
4
 
5
+ from pydantic import TypeAdapter
3
6
  from rich.console import Console
4
7
 
5
- from cognite_toolkit._cdf_tk.client.data_classes.api_classes import PagedResponse
6
- from cognite_toolkit._cdf_tk.client.data_classes.three_d import ThreeDModelClassicRequest, ThreeDModelResponse
8
+ from cognite_toolkit._cdf_tk.client.data_classes.api_classes import InternalIdRequest, PagedResponse
9
+ from cognite_toolkit._cdf_tk.client.data_classes.three_d import (
10
+ AssetMappingClassicRequest,
11
+ AssetMappingDMRequest,
12
+ AssetMappingResponse,
13
+ ThreeDModelClassicRequest,
14
+ ThreeDModelResponse,
15
+ )
16
+ from cognite_toolkit._cdf_tk.utils.collection import chunker_sequence
7
17
  from cognite_toolkit._cdf_tk.utils.http_client import (
8
18
  HTTPClient,
9
- ItemsRequest,
19
+ ItemsRequest2,
10
20
  RequestMessage2,
11
- SimpleBodyRequest,
12
21
  )
13
22
  from cognite_toolkit._cdf_tk.utils.useful_types import PrimitiveType
14
23
 
@@ -37,16 +46,15 @@ class ThreeDModelAPI:
37
46
  return []
38
47
  if len(models) > self.MAX_CLASSIC_MODELS_PER_CREATE_REQUEST:
39
48
  raise ValueError("Cannot create more than 1000 3D models in a single request.")
40
- responses = self._http_client.request_with_retries(
41
- ItemsRequest(
49
+ responses = self._http_client.request_items_retries(
50
+ ItemsRequest2(
42
51
  endpoint_url=self._config.create_api_url(self.ENDPOINT),
43
52
  method="POST",
44
- items=list(models),
53
+ items=models,
45
54
  )
46
55
  )
47
56
  responses.raise_for_status()
48
- body = responses.get_first_body()
49
- return PagedResponse[ThreeDModelResponse].model_validate(body).items
57
+ return TypeAdapter(list[ThreeDModelResponse]).validate_python(responses.get_items())
50
58
 
51
59
  def delete(self, ids: Sequence[int]) -> None:
52
60
  """Delete 3D models by their IDs.
@@ -58,11 +66,11 @@ class ThreeDModelAPI:
58
66
  return None
59
67
  if len(ids) > self.MAX_MODELS_PER_DELETE_REQUEST:
60
68
  raise ValueError("Cannot delete more than 1000 3D models in a single request.")
61
- responses = self._http_client.request_with_retries(
62
- SimpleBodyRequest(
69
+ responses = self._http_client.request_items_retries(
70
+ ItemsRequest2(
63
71
  endpoint_url=self._config.create_api_url(self.ENDPOINT + "/delete"),
64
72
  method="POST",
65
- body_content={"items": [{"id": id_} for id_ in ids]},
73
+ items=InternalIdRequest.from_ids(list(ids)),
66
74
  )
67
75
  )
68
76
  responses.raise_for_status()
@@ -126,6 +134,251 @@ class ThreeDModelAPI:
126
134
  return results
127
135
 
128
136
 
137
+ T_RequestMapping = TypeVar("T_RequestMapping", bound=AssetMappingClassicRequest | AssetMappingDMRequest)
138
+
139
+
140
+ class ThreeDAssetMappingAPI:
141
+ ENDPOINT = "/3d/models/{modelId}/revisions/{revisionId}/mappings"
142
+ CREATE_CLASSIC_MAX_MAPPINGS_PER_REQUEST = 1000
143
+ CREATE_DM_MAX_MAPPINGS_PER_REQUEST = 100
144
+ DELETE_CLASSIC_MAX_MAPPINGS_PER_REQUEST = 1000
145
+ DELETE_DM_MAX_MAPPINGS_PER_REQUEST = 100
146
+ LIST_REQUEST_MAX_LIMIT = 1000
147
+
148
+ def __init__(self, http_client: HTTPClient, console: Console) -> None:
149
+ self._http_client = http_client
150
+ self._console = console
151
+ self._config = http_client.config
152
+
153
+ def create(self, mappings: Sequence[AssetMappingClassicRequest]) -> list[AssetMappingResponse]:
154
+ """Create 3D asset mappings.
155
+
156
+ Args:
157
+ mappings (Sequence[AssetMappingClassicRequest]):
158
+ The 3D asset mapping(s) to create.
159
+
160
+ Returns:
161
+ list[AssetMappingResponse]: The created 3D asset mapping(s).
162
+ """
163
+ results: list[AssetMappingResponse] = []
164
+ for endpoint, model_id, revision_id, revision_mappings in self._chunk_mappings_by_endpoint(
165
+ mappings, self.CREATE_CLASSIC_MAX_MAPPINGS_PER_REQUEST
166
+ ):
167
+ responses = self._http_client.request_items_retries(
168
+ ItemsRequest2(
169
+ endpoint_url=self._config.create_api_url(endpoint),
170
+ method="POST",
171
+ items=revision_mappings,
172
+ )
173
+ )
174
+ responses.raise_for_status()
175
+ items = responses.get_items()
176
+ for item in items:
177
+ # We append modelId and revisionId to each item since the API does not return them
178
+ # this is needed to fully populate the AssetMappingResponse data class
179
+ item["modelId"] = model_id
180
+ item["revisionId"] = revision_id
181
+ results.extend(TypeAdapter(list[AssetMappingResponse]).validate_python(items))
182
+ return results
183
+
184
+ def create_dm(
185
+ self, mappings: Sequence[AssetMappingDMRequest], object_3d_space: str, cad_node_space: str
186
+ ) -> list[AssetMappingResponse]:
187
+ """Create 3D asset mappings in Data Modeling format.
188
+
189
+ Args:
190
+ mappings (Sequence[AssetMappingDMRequest]):
191
+ The 3D asset mapping(s) to create
192
+ object_3d_space (str):
193
+ The instance space where the Cognite3DObject are located.
194
+ cad_node_space (str):
195
+ The instance space where the CogniteCADNode are located.
196
+ Returns:
197
+ list[AssetMappingResponse]: The created 3D asset mapping(s).
198
+ """
199
+ results: list[AssetMappingResponse] = []
200
+ for endpoint, model_id, revision_id, revision_mappings in self._chunk_mappings_by_endpoint(
201
+ mappings, self.CREATE_DM_MAX_MAPPINGS_PER_REQUEST
202
+ ):
203
+ responses = self._http_client.request_items_retries(
204
+ ItemsRequest2(
205
+ endpoint_url=self._config.create_api_url(endpoint),
206
+ method="POST",
207
+ items=revision_mappings,
208
+ extra_body_fields={
209
+ "dmsContextualizationConfig": {
210
+ "object3DSpace": object_3d_space,
211
+ "cadNodeSpace": cad_node_space,
212
+ }
213
+ },
214
+ )
215
+ )
216
+ responses.raise_for_status()
217
+ items = responses.get_items()
218
+ for item in items:
219
+ # We append modelId and revisionId to each item since the API does not return them
220
+ # this is needed to fully populate the AssetMappingResponse data class
221
+ item["modelId"] = model_id
222
+ item["revisionId"] = revision_id
223
+ results.extend(TypeAdapter(list[AssetMappingResponse]).validate_python(items))
224
+ return results
225
+
226
+ @classmethod
227
+ def _chunk_mappings_by_endpoint(
228
+ cls, mappings: Sequence[T_RequestMapping], chunk_size: int
229
+ ) -> Iterable[tuple[str, int, int, list[T_RequestMapping]]]:
230
+ chunked_mappings: dict[tuple[int, int], list[T_RequestMapping]] = defaultdict(list)
231
+ for mapping in mappings:
232
+ key = mapping.model_id, mapping.revision_id
233
+ chunked_mappings[key].append(mapping)
234
+ for (model_id, revision_id), revision_mappings in chunked_mappings.items():
235
+ endpoint = cls.ENDPOINT.format(modelId=model_id, revisionId=revision_id)
236
+ for chunk in chunker_sequence(revision_mappings, chunk_size):
237
+ yield endpoint, model_id, revision_id, chunk
238
+
239
+ def delete(self, mappings: Sequence[AssetMappingClassicRequest]) -> None:
240
+ """Delete 3D asset mappings.
241
+
242
+ Args:
243
+ mappings (Sequence[AssetMappingClassicRequest]):
244
+ The 3D asset mapping(s) to delete.
245
+ """
246
+ for endpoint, *_, revision_mappings in self._chunk_mappings_by_endpoint(
247
+ mappings, self.DELETE_CLASSIC_MAX_MAPPINGS_PER_REQUEST
248
+ ):
249
+ responses = self._http_client.request_items_retries(
250
+ ItemsRequest2(
251
+ endpoint_url=self._config.create_api_url(f"{endpoint}/delete"),
252
+ method="DELETE",
253
+ items=revision_mappings,
254
+ )
255
+ )
256
+ responses.raise_for_status()
257
+ return None
258
+
259
+ def delete_dm(self, mappings: Sequence[AssetMappingDMRequest], object_3d_space: str, cad_node_space: str) -> None:
260
+ """Delete 3D asset mappings in Data Modeling format.
261
+
262
+ Args:
263
+ mappings (Sequence[AssetMappingDMRequest]):
264
+ The 3D asset mapping(s) to delete.
265
+ object_3d_space (str):
266
+ The instance space where the Cognite3DObject are located.
267
+ cad_node_space (str):
268
+ The instance space where the CogniteCADNode are located.
269
+ """
270
+ for endpoint, *_, revision_mappings in self._chunk_mappings_by_endpoint(
271
+ mappings, self.DELETE_DM_MAX_MAPPINGS_PER_REQUEST
272
+ ):
273
+ responses = self._http_client.request_items_retries(
274
+ ItemsRequest2(
275
+ endpoint_url=self._config.create_api_url(f"{endpoint}/delete"),
276
+ method="DELETE",
277
+ items=revision_mappings,
278
+ extra_body_fields={
279
+ "dmsContextualizationConfig": {
280
+ "object3DSpace": object_3d_space,
281
+ "cadNodeSpace": cad_node_space,
282
+ }
283
+ },
284
+ )
285
+ )
286
+ responses.raise_for_status()
287
+ return None
288
+
289
+ def iterate(
290
+ self,
291
+ model_id: int,
292
+ revision_id: int,
293
+ asset_ids: list[int] | None = None,
294
+ asset_instance_ids: list[str] | None = None,
295
+ node_ids: list[int] | None = None,
296
+ tree_indexes: list[int] | None = None,
297
+ get_dms_instances: bool = False,
298
+ limit: int = 100,
299
+ cursor: str | None = None,
300
+ ) -> PagedResponse[AssetMappingResponse]:
301
+ if not (0 < limit <= self.LIST_REQUEST_MAX_LIMIT):
302
+ raise ValueError(f"Limit must be between 1 and {self.LIST_REQUEST_MAX_LIMIT}, got {limit}.")
303
+ if sum(param is not None for param in [asset_ids, asset_instance_ids, node_ids, tree_indexes]) > 1:
304
+ raise ValueError("Only one of asset_ids, asset_instance_ids, node_ids, or tree_indexes can be provided.")
305
+ body: dict[str, Any] = {
306
+ "getDmsInstances": get_dms_instances,
307
+ "limit": limit,
308
+ }
309
+ if asset_ids is not None:
310
+ if not (0 < len(asset_ids) <= 100):
311
+ raise ValueError("asset_ids must contain between 1 and 100 IDs.")
312
+ body["filter"] = {"assetIds": asset_ids}
313
+ elif asset_instance_ids is not None:
314
+ if not (0 < len(asset_instance_ids) <= 100):
315
+ raise ValueError("asset_instance_ids must contain between 1 and 100 IDs.")
316
+ body["filter"] = {"assetInstanceIds": asset_instance_ids}
317
+ elif node_ids is not None:
318
+ if not (0 < len(node_ids) <= 100):
319
+ raise ValueError("node_ids must contain between 1 and 100 IDs.")
320
+ body["filter"] = {"nodeIds": node_ids}
321
+ elif tree_indexes is not None:
322
+ if not (0 < len(tree_indexes) <= 100):
323
+ raise ValueError("tree_indexes must contain between 1 and 100 indexes.")
324
+ body["filter"] = {"treeIndexes": tree_indexes}
325
+ if cursor is not None:
326
+ body["cursor"] = cursor
327
+
328
+ endpoint = self.ENDPOINT.format(modelId=model_id, revisionId=revision_id)
329
+ responses = self._http_client.request_single_retries(
330
+ RequestMessage2(
331
+ endpoint_url=self._config.create_api_url(f"{endpoint}/list"),
332
+ method="POST",
333
+ body_content=body,
334
+ )
335
+ )
336
+ success_response = responses.get_success_or_raise()
337
+ body_json = success_response.body_json
338
+ # Add modelId and revisionId to items since the API does not return them
339
+ for item in body_json.get("items", []):
340
+ item["modelId"] = model_id
341
+ item["revisionId"] = revision_id
342
+ return PagedResponse[AssetMappingResponse].model_validate(body_json)
343
+
344
+ def list(
345
+ self,
346
+ model_id: int,
347
+ revision_id: int,
348
+ asset_ids: list[int] | None = None,
349
+ asset_instance_ids: list[str] | None = None,
350
+ node_ids: list[int] | None = None,
351
+ tree_indexes: list[int] | None = None,
352
+ get_dms_instances: bool = False,
353
+ limit: int | None = 100,
354
+ ) -> list[AssetMappingResponse]:
355
+ results: list[AssetMappingResponse] = []
356
+ cursor: str | None = None
357
+ while True:
358
+ request_limit = (
359
+ self.LIST_REQUEST_MAX_LIMIT if limit is None else min(limit - len(results), self.LIST_REQUEST_MAX_LIMIT)
360
+ )
361
+ if request_limit <= 0:
362
+ break
363
+ page = self.iterate(
364
+ model_id=model_id,
365
+ revision_id=revision_id,
366
+ asset_ids=asset_ids,
367
+ asset_instance_ids=asset_instance_ids,
368
+ node_ids=node_ids,
369
+ tree_indexes=tree_indexes,
370
+ get_dms_instances=get_dms_instances,
371
+ limit=request_limit,
372
+ cursor=cursor,
373
+ )
374
+ results.extend(page.items)
375
+ if page.next_cursor is None:
376
+ break
377
+ cursor = page.next_cursor
378
+ return results
379
+
380
+
129
381
  class ThreeDAPI:
130
382
  def __init__(self, http_client: HTTPClient, console: Console) -> None:
131
383
  self.models = ThreeDModelAPI(http_client, console)
384
+ self.asset_mappings = ThreeDAssetMappingAPI(http_client, console)
@@ -2,6 +2,8 @@ from typing import Generic, TypeVar
2
2
 
3
3
  from pydantic import BaseModel, Field, JsonValue
4
4
 
5
+ from cognite_toolkit._cdf_tk.utils.http_client._data_classes2 import RequestResource
6
+
5
7
  T = TypeVar("T", bound=BaseModel)
6
8
 
7
9
 
@@ -15,3 +17,14 @@ class QueryResponse(BaseModel, Generic[T]):
15
17
  typing: dict[str, JsonValue] | None = None
16
18
  next_cursor: dict[str, str] = Field(alias="nextCursor")
17
19
  debug: dict[str, JsonValue] | None = None
20
+
21
+
22
+ class InternalIdRequest(RequestResource):
23
+ id: int
24
+
25
+ def as_id(self) -> int:
26
+ return self.id
27
+
28
+ @classmethod
29
+ def from_ids(cls, ids: list[int]) -> list["InternalIdRequest"]:
30
+ return [cls(id=id_) for id_ in ids]
@@ -3,7 +3,7 @@ from abc import ABC, abstractmethod
3
3
  from collections import UserList
4
4
  from typing import TYPE_CHECKING, Any, Generic, TypeVar
5
5
 
6
- from pydantic import BaseModel, ConfigDict
6
+ from pydantic import ConfigDict
7
7
  from pydantic.alias_generators import to_camel
8
8
 
9
9
  from cognite_toolkit._cdf_tk.utils.http_client._data_classes2 import BaseModelObject, RequestResource
@@ -31,21 +31,17 @@ class ResponseResource(BaseModelObject, Generic[T_RequestResource], ABC):
31
31
  return self.as_request_resource()
32
32
 
33
33
 
34
- class Identifier(BaseModel):
34
+ class Identifier(RequestResource, ABC):
35
35
  """Base class for all identifier classes."""
36
36
 
37
37
  model_config = ConfigDict(alias_generator=to_camel, extra="ignore", populate_by_name=True, frozen=True)
38
38
 
39
- def dump(self, include_type: bool = True) -> dict[str, Any]:
40
- """Dump the identifier to a dictionary.
39
+ def dump(self, camel_case: bool = True, include_type: bool = True) -> dict[str, Any]:
40
+ """Dump the resource to a dictionary.
41
41
 
42
- Args:
43
- include_type (bool): Whether to include the type of the identifier in the output.
44
-
45
- Returns:
46
- dict[str, Any]: The dumped identifier.
42
+ This is the default serialization method for request resources.
47
43
  """
48
- return self.model_dump(mode="json", by_alias=True, exclude_defaults=not include_type)
44
+ return self.model_dump(mode="json", by_alias=camel_case, exclude_unset=not include_type)
49
45
 
50
46
  def as_id(self) -> Self:
51
47
  return self
@@ -15,19 +15,19 @@ class TypedInstanceIdentifier(Identifier):
15
15
  external_id: str
16
16
 
17
17
 
18
- class InstanceIdentifier(Identifier):
19
- space: str
20
- external_id: str
21
-
22
-
23
- class NodeIdentifier(TypedInstanceIdentifier):
18
+ class TypedNodeIdentifier(TypedInstanceIdentifier):
24
19
  instance_type: Literal["node"] = "node"
25
20
 
26
21
 
27
- class EdgeIdentifier(TypedInstanceIdentifier):
22
+ class TypedEdgeIdentifier(TypedInstanceIdentifier):
28
23
  instance_type: Literal["edge"] = "edge"
29
24
 
30
25
 
26
+ class InstanceIdentifier(Identifier):
27
+ space: str
28
+ external_id: str
29
+
30
+
31
31
  class InstanceResult(BaseModelObject):
32
32
  instance_type: InstanceType
33
33
  version: int
@@ -30,7 +30,7 @@ from cognite.client.data_classes.data_modeling.instances import (
30
30
  TypedNodeApply,
31
31
  )
32
32
 
33
- from cognite_toolkit._cdf_tk.client.data_classes.migration import AssetCentricId
33
+ from cognite_toolkit._cdf_tk.client.data_classes.legacy.migration import AssetCentricId
34
34
  from cognite_toolkit._cdf_tk.utils.useful_types import JsonVal
35
35
 
36
36
  if sys.version_info >= (3, 11):
@@ -1,5 +1,8 @@
1
+ from collections.abc import Hashable
1
2
  from typing import Literal
2
3
 
4
+ from pydantic import Field
5
+
3
6
  from .base import BaseModelObject, RequestResource, ResponseResource
4
7
 
5
8
 
@@ -51,3 +54,59 @@ class ThreeDModelResponse(ResponseResource[ThreeDModelRequest]):
51
54
  return ThreeDModelClassicRequest._load(self.dump())
52
55
  else:
53
56
  return ThreeDModelDMSRequest._load(self.dump())
57
+
58
+
59
+ class AssetMappingDMRequest(RequestResource):
60
+ node_id: int
61
+ asset_instance_id: NodeReference
62
+ # These fields are part of the path request and not the body schema.
63
+ model_id: int = Field(exclude=True)
64
+ revision_id: int = Field(exclude=True)
65
+
66
+ def as_id(self) -> Hashable:
67
+ return (
68
+ self.model_id,
69
+ self.revision_id,
70
+ self.node_id,
71
+ self.asset_instance_id.space,
72
+ self.asset_instance_id.external_id,
73
+ )
74
+
75
+
76
+ class AssetMappingClassicRequest(RequestResource):
77
+ node_id: int
78
+ asset_id: int | None = None
79
+ asset_instance_id: NodeReference | None = None
80
+ # These fields are part of the path request and not the body schema.
81
+ model_id: int = Field(exclude=True)
82
+ revision_id: int = Field(exclude=True)
83
+
84
+ def as_id(self) -> Hashable:
85
+ if self.asset_id:
86
+ return self.model_id, self.revision_id, self.node_id, self.asset_id
87
+ elif self.asset_instance_id:
88
+ return (
89
+ self.model_id,
90
+ self.revision_id,
91
+ self.node_id,
92
+ self.asset_instance_id.space,
93
+ self.asset_instance_id.external_id,
94
+ )
95
+ else:
96
+ raise AttributeError("asset_id or asset_instance_id is required")
97
+
98
+
99
+ class AssetMappingResponse(ResponseResource[AssetMappingClassicRequest]):
100
+ node_id: int
101
+ asset_id: int | None = None
102
+ asset_instance_id: NodeReference | None = None
103
+ tree_index: int | None = None
104
+ subtree_size: int | None = None
105
+ # These fields are part of the path request and response, but they are included here for convenience.
106
+ model_id: int = Field(exclude=True)
107
+ revision_id: int = Field(exclude=True)
108
+
109
+ def as_request_resource(self) -> AssetMappingClassicRequest:
110
+ return AssetMappingClassicRequest.model_validate(
111
+ {**self.dump(), "modelId": self.model_id, "revisionId": self.revision_id}
112
+ )
@@ -12,18 +12,27 @@ from cognite.client.testing import CogniteClientMock
12
12
  from rich.console import Console
13
13
 
14
14
  from cognite_toolkit._cdf_tk.client._toolkit_client import ToolkitClient
15
+ from cognite_toolkit._cdf_tk.client.api.legacy.canvas import CanvasAPI, IndustrialCanvasAPI
16
+ from cognite_toolkit._cdf_tk.client.api.legacy.charts import ChartsAPI
17
+ from cognite_toolkit._cdf_tk.client.api.legacy.dml import DMLAPI
18
+ from cognite_toolkit._cdf_tk.client.api.legacy.extended_data_modeling import ExtendedInstancesAPI
19
+ from cognite_toolkit._cdf_tk.client.api.legacy.extended_files import ExtendedFileMetadataAPI
20
+ from cognite_toolkit._cdf_tk.client.api.legacy.extended_functions import ExtendedFunctionsAPI
21
+ from cognite_toolkit._cdf_tk.client.api.legacy.extended_raw import ExtendedRawAPI
22
+ from cognite_toolkit._cdf_tk.client.api.legacy.extended_timeseries import ExtendedTimeSeriesAPI
23
+ from cognite_toolkit._cdf_tk.client.api.legacy.location_filters import LocationFiltersAPI
24
+ from cognite_toolkit._cdf_tk.client.api.legacy.robotics import (
25
+ CapabilitiesAPI,
26
+ DataPostProcessingAPI,
27
+ FramesAPI,
28
+ MapsAPI,
29
+ RoboticsAPI,
30
+ )
31
+ from cognite_toolkit._cdf_tk.client.api.legacy.robotics import LocationsAPI as RoboticsLocationsAPI
32
+ from cognite_toolkit._cdf_tk.client.api.legacy.search_config import SearchConfigurationsAPI
15
33
 
16
34
  from ._toolkit_client import ToolAPI
17
- from .api.canvas import CanvasAPI, IndustrialCanvasAPI
18
- from .api.charts import ChartsAPI
19
- from .api.dml import DMLAPI
20
- from .api.extended_data_modeling import ExtendedInstancesAPI
21
- from .api.extended_files import ExtendedFileMetadataAPI
22
- from .api.extended_functions import ExtendedFunctionsAPI
23
- from .api.extended_raw import ExtendedRawAPI
24
- from .api.extended_timeseries import ExtendedTimeSeriesAPI
25
35
  from .api.infield import InfieldAPI, InFieldCDMConfigAPI, InfieldConfigAPI
26
- from .api.location_filters import LocationFiltersAPI
27
36
  from .api.lookup import (
28
37
  AssetLookUpAPI,
29
38
  DataSetLookUpAPI,
@@ -45,14 +54,7 @@ from .api.migration import (
45
54
  ResourceViewMappingAPI,
46
55
  )
47
56
  from .api.project import ProjectAPI
48
- from .api.robotics import RoboticsAPI
49
- from .api.robotics.capabilities import CapabilitiesAPI
50
- from .api.robotics.data_postprocessing import DataPostProcessingAPI
51
- from .api.robotics.frames import FramesAPI
52
- from .api.robotics.locations import LocationsAPI as RoboticsLocationsAPI
53
- from .api.robotics.maps import MapsAPI
54
57
  from .api.search import SearchAPI
55
- from .api.search_config import SearchConfigurationsAPI
56
58
  from .api.streams import StreamsAPI
57
59
  from .api.three_d import ThreeDAPI, ThreeDModelAPI
58
60
  from .api.token import TokenAPI
@@ -15,7 +15,7 @@ from cognite.client.data_classes.data_modeling.views import ViewProperty
15
15
  from cognite.client.utils._identifier import InstanceId
16
16
 
17
17
  from cognite_toolkit._cdf_tk.client import ToolkitClient
18
- from cognite_toolkit._cdf_tk.client.data_classes.migration import (
18
+ from cognite_toolkit._cdf_tk.client.data_classes.legacy.migration import (
19
19
  AssetCentricId,
20
20
  ResourceViewMappingApply,
21
21
  )
@@ -20,7 +20,7 @@ from cognite.client.data_classes.documents import SourceFileProperty
20
20
  from cognite.client.data_classes.events import EventProperty
21
21
 
22
22
  from cognite_toolkit._cdf_tk.client import ToolkitClient
23
- from cognite_toolkit._cdf_tk.client.data_classes.apm_config_v1 import APMConfig, APMConfigList
23
+ from cognite_toolkit._cdf_tk.client.data_classes.legacy.apm_config_v1 import APMConfig, APMConfigList
24
24
  from cognite_toolkit._cdf_tk.cruds import NodeCRUD, ResourceCRUD, SpaceCRUD
25
25
  from cognite_toolkit._cdf_tk.exceptions import ToolkitMissingResourceError, ToolkitRequiredValueError
26
26
  from cognite_toolkit._cdf_tk.utils import humanize_collection
@@ -13,9 +13,9 @@ from pydantic import BaseModel, BeforeValidator, Field, field_validator, model_v
13
13
 
14
14
  from cognite_toolkit._cdf_tk.client.data_classes.base import BaseModelObject, RequestResource
15
15
  from cognite_toolkit._cdf_tk.client.data_classes.instance_api import InstanceIdentifier
16
- from cognite_toolkit._cdf_tk.client.data_classes.instances import InstanceApplyList
17
- from cognite_toolkit._cdf_tk.client.data_classes.migration import AssetCentricId
18
- from cognite_toolkit._cdf_tk.client.data_classes.pending_instances_ids import PendingInstanceId
16
+ from cognite_toolkit._cdf_tk.client.data_classes.legacy.instances import InstanceApplyList
17
+ from cognite_toolkit._cdf_tk.client.data_classes.legacy.migration import AssetCentricId
18
+ from cognite_toolkit._cdf_tk.client.data_classes.legacy.pending_instances_ids import PendingInstanceId
19
19
  from cognite_toolkit._cdf_tk.commands._migrate.default_mappings import (
20
20
  ASSET_ANNOTATIONS_ID,
21
21
  FILE_ANNOTATIONS_ID,
@@ -15,21 +15,27 @@ from cognite.client.data_classes.data_modeling import (
15
15
  from cognite.client.exceptions import CogniteException
16
16
 
17
17
  from cognite_toolkit._cdf_tk.client import ToolkitClient
18
- from cognite_toolkit._cdf_tk.client.data_classes.canvas import (
19
- ContainerReferenceApply,
20
- FdmInstanceContainerReferenceApply,
21
- IndustrialCanvas,
22
- IndustrialCanvasApply,
23
- )
24
- from cognite_toolkit._cdf_tk.client.data_classes.charts import Chart, ChartWrite
25
18
  from cognite_toolkit._cdf_tk.client.data_classes.charts_data import (
26
19
  ChartCoreTimeseries,
27
20
  ChartSource,
28
21
  ChartTimeseries,
29
22
  )
30
23
  from cognite_toolkit._cdf_tk.client.data_classes.instance_api import InstanceIdentifier
31
- from cognite_toolkit._cdf_tk.client.data_classes.migration import ResourceViewMappingApply
32
- from cognite_toolkit._cdf_tk.client.data_classes.three_d import RevisionStatus, ThreeDModelResponse
24
+ from cognite_toolkit._cdf_tk.client.data_classes.legacy.canvas import (
25
+ ContainerReferenceApply,
26
+ FdmInstanceContainerReferenceApply,
27
+ IndustrialCanvas,
28
+ IndustrialCanvasApply,
29
+ )
30
+ from cognite_toolkit._cdf_tk.client.data_classes.legacy.charts import Chart, ChartWrite
31
+ from cognite_toolkit._cdf_tk.client.data_classes.legacy.migration import ResourceViewMappingApply
32
+ from cognite_toolkit._cdf_tk.client.data_classes.three_d import (
33
+ AssetMappingDMRequest,
34
+ AssetMappingResponse,
35
+ NodeReference,
36
+ RevisionStatus,
37
+ ThreeDModelResponse,
38
+ )
33
39
  from cognite_toolkit._cdf_tk.commands._migrate.conversion import DirectRelationCache, asset_centric_to_dm
34
40
  from cognite_toolkit._cdf_tk.commands._migrate.data_classes import (
35
41
  Model,
@@ -443,15 +449,15 @@ class ThreeDMapper(DataMapper[ThreeDSelector, ThreeDModelResponse, ThreeDMigrati
443
449
  return None, issue
444
450
 
445
451
  mapped_request = ThreeDMigrationRequest(
446
- model_id=item.id,
452
+ modelId=item.id,
447
453
  type=model_type,
448
454
  space=instance_space,
449
455
  revision=ThreeDRevisionMigrationRequest(
450
456
  space=instance_space,
451
457
  type=model_type,
452
- revision_id=last_revision_id,
458
+ revisionId=last_revision_id,
453
459
  model=Model(
454
- instance_id=InstanceIdentifier(
460
+ instanceId=InstanceIdentifier(
455
461
  space=instance_space,
456
462
  external_id=f"cog_3d_model_{item.id!s}",
457
463
  )
@@ -469,3 +475,45 @@ class ThreeDMapper(DataMapper[ThreeDSelector, ThreeDModelResponse, ThreeDMigrati
469
475
  return "PointCloud"
470
476
  else:
471
477
  return None
478
+
479
+
480
+ class ThreeDAssetMapper(DataMapper[ThreeDSelector, AssetMappingResponse, AssetMappingDMRequest]):
481
+ def __init__(self, client: ToolkitClient) -> None:
482
+ self.client = client
483
+
484
+ def map(
485
+ self, source: Sequence[AssetMappingResponse]
486
+ ) -> Sequence[tuple[AssetMappingDMRequest | None, MigrationIssue]]:
487
+ output: list[tuple[AssetMappingDMRequest | None, MigrationIssue]] = []
488
+ self._populate_cache(source)
489
+ for item in source:
490
+ mapped_item, issue = self._map_single_item(item)
491
+ output.append((mapped_item, issue))
492
+ return output
493
+
494
+ def _populate_cache(self, source: Sequence[AssetMappingResponse]) -> None:
495
+ asset_ids: set[int] = set()
496
+ for mapping in source:
497
+ if mapping.asset_id is not None:
498
+ asset_ids.add(mapping.asset_id)
499
+ self.client.migration.lookup.assets(list(asset_ids))
500
+
501
+ def _map_single_item(
502
+ self, item: AssetMappingResponse
503
+ ) -> tuple[AssetMappingDMRequest | None, ThreeDModelMigrationIssue]:
504
+ issue = ThreeDModelMigrationIssue(model_name=f"AssetMapping_{item.model_id}", model_id=item.model_id)
505
+ asset_instance_id = item.asset_instance_id
506
+ if item.asset_id and asset_instance_id is None:
507
+ asset_node_id = self.client.migration.lookup.assets(item.asset_id)
508
+ if asset_node_id is None:
509
+ issue.error_message.append(f"Missing asset instance for asset ID {item.asset_id!r}")
510
+ return None, issue
511
+ asset_instance_id = NodeReference(space=asset_node_id.space, externalId=asset_node_id.external_id)
512
+
513
+ if asset_instance_id is None:
514
+ issue.error_message.append("Neither assetInstanceId nor assetId provided for mapping.")
515
+ return None, issue
516
+ mapped_request = AssetMappingDMRequest(
517
+ modelId=item.model_id, revisionId=item.revision_id, nodeId=item.node_id, assetInstanceId=asset_instance_id
518
+ )
519
+ return mapped_request, issue