cognite-toolkit 0.7.30__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.
- cognite_toolkit/_cdf.py +5 -6
- cognite_toolkit/_cdf_tk/apps/__init__.py +2 -0
- cognite_toolkit/_cdf_tk/apps/_core_app.py +7 -1
- cognite_toolkit/_cdf_tk/apps/_download_app.py +2 -2
- cognite_toolkit/_cdf_tk/apps/_dump_app.py +1 -1
- cognite_toolkit/_cdf_tk/apps/_import_app.py +41 -0
- cognite_toolkit/_cdf_tk/apps/_migrate_app.py +177 -2
- cognite_toolkit/_cdf_tk/builders/_raw.py +1 -1
- cognite_toolkit/_cdf_tk/client/_toolkit_client.py +9 -9
- cognite_toolkit/_cdf_tk/client/api/infield.py +38 -33
- cognite_toolkit/_cdf_tk/client/api/{canvas.py → legacy/canvas.py} +15 -7
- cognite_toolkit/_cdf_tk/client/api/{charts.py → legacy/charts.py} +1 -1
- cognite_toolkit/_cdf_tk/client/api/{extended_data_modeling.py → legacy/extended_data_modeling.py} +1 -1
- cognite_toolkit/_cdf_tk/client/api/{extended_files.py → legacy/extended_files.py} +2 -2
- cognite_toolkit/_cdf_tk/client/api/{extended_functions.py → legacy/extended_functions.py} +9 -9
- cognite_toolkit/_cdf_tk/client/api/{extended_raw.py → legacy/extended_raw.py} +1 -1
- cognite_toolkit/_cdf_tk/client/api/{extended_timeseries.py → legacy/extended_timeseries.py} +5 -2
- cognite_toolkit/_cdf_tk/client/api/{location_filters.py → legacy/location_filters.py} +1 -1
- cognite_toolkit/_cdf_tk/client/api/legacy/robotics/__init__.py +8 -0
- cognite_toolkit/_cdf_tk/client/api/{robotics → legacy/robotics}/capabilities.py +1 -1
- cognite_toolkit/_cdf_tk/client/api/{robotics → legacy/robotics}/data_postprocessing.py +1 -1
- cognite_toolkit/_cdf_tk/client/api/{robotics → legacy/robotics}/frames.py +1 -1
- cognite_toolkit/_cdf_tk/client/api/{robotics → legacy/robotics}/locations.py +1 -1
- cognite_toolkit/_cdf_tk/client/api/{robotics → legacy/robotics}/maps.py +1 -1
- cognite_toolkit/_cdf_tk/client/api/{robotics → legacy/robotics}/robots.py +2 -2
- cognite_toolkit/_cdf_tk/client/api/{search_config.py → legacy/search_config.py} +5 -1
- cognite_toolkit/_cdf_tk/client/api/migration.py +2 -3
- cognite_toolkit/_cdf_tk/client/api/project.py +9 -8
- cognite_toolkit/_cdf_tk/client/api/search.py +2 -2
- cognite_toolkit/_cdf_tk/client/api/streams.py +21 -17
- cognite_toolkit/_cdf_tk/client/api/three_d.py +343 -9
- cognite_toolkit/_cdf_tk/client/data_classes/api_classes.py +13 -0
- cognite_toolkit/_cdf_tk/client/data_classes/base.py +12 -32
- cognite_toolkit/_cdf_tk/client/data_classes/instance_api.py +18 -13
- cognite_toolkit/_cdf_tk/client/data_classes/legacy/__init__.py +0 -0
- cognite_toolkit/_cdf_tk/client/data_classes/{canvas.py → legacy/canvas.py} +1 -1
- cognite_toolkit/_cdf_tk/client/data_classes/three_d.py +65 -0
- cognite_toolkit/_cdf_tk/client/testing.py +24 -16
- cognite_toolkit/_cdf_tk/commands/__init__.py +1 -0
- cognite_toolkit/_cdf_tk/commands/_migrate/conversion.py +8 -2
- cognite_toolkit/_cdf_tk/commands/_migrate/creators.py +1 -1
- cognite_toolkit/_cdf_tk/commands/_migrate/data_classes.py +35 -4
- cognite_toolkit/_cdf_tk/commands/_migrate/data_mapper.py +149 -14
- cognite_toolkit/_cdf_tk/commands/_migrate/data_model.py +1 -0
- cognite_toolkit/_cdf_tk/commands/_migrate/default_mappings.py +1 -1
- cognite_toolkit/_cdf_tk/commands/_migrate/issues.py +19 -1
- cognite_toolkit/_cdf_tk/commands/_migrate/migration_io.py +220 -3
- cognite_toolkit/_cdf_tk/commands/_profile.py +1 -1
- cognite_toolkit/_cdf_tk/commands/_purge.py +9 -11
- cognite_toolkit/_cdf_tk/commands/build_cmd.py +1 -1
- cognite_toolkit/_cdf_tk/commands/build_v2/__init__.py +0 -0
- cognite_toolkit/_cdf_tk/commands/build_v2/build_cmd.py +241 -0
- cognite_toolkit/_cdf_tk/commands/build_v2/build_input.py +85 -0
- cognite_toolkit/_cdf_tk/commands/build_v2/build_issues.py +27 -0
- cognite_toolkit/_cdf_tk/commands/dump_resource.py +4 -4
- cognite_toolkit/_cdf_tk/commands/run.py +1 -1
- cognite_toolkit/_cdf_tk/cruds/_data_cruds.py +2 -2
- cognite_toolkit/_cdf_tk/cruds/_resource_cruds/auth.py +1 -1
- cognite_toolkit/_cdf_tk/cruds/_resource_cruds/classic.py +1 -1
- cognite_toolkit/_cdf_tk/cruds/_resource_cruds/configuration.py +1 -1
- cognite_toolkit/_cdf_tk/cruds/_resource_cruds/datamodel.py +1 -1
- cognite_toolkit/_cdf_tk/cruds/_resource_cruds/extraction_pipeline.py +1 -1
- cognite_toolkit/_cdf_tk/cruds/_resource_cruds/fieldops.py +22 -20
- cognite_toolkit/_cdf_tk/cruds/_resource_cruds/file.py +1 -1
- cognite_toolkit/_cdf_tk/cruds/_resource_cruds/function.py +14 -2
- cognite_toolkit/_cdf_tk/cruds/_resource_cruds/industrial_tool.py +1 -1
- cognite_toolkit/_cdf_tk/cruds/_resource_cruds/location.py +1 -1
- cognite_toolkit/_cdf_tk/cruds/_resource_cruds/migration.py +1 -1
- cognite_toolkit/_cdf_tk/cruds/_resource_cruds/raw.py +1 -1
- cognite_toolkit/_cdf_tk/cruds/_resource_cruds/robotics.py +1 -1
- cognite_toolkit/_cdf_tk/cruds/_resource_cruds/transformation.py +49 -14
- cognite_toolkit/_cdf_tk/data_classes/_module_toml.py +1 -0
- cognite_toolkit/_cdf_tk/resource_classes/search_config.py +1 -1
- cognite_toolkit/_cdf_tk/resource_classes/workflow_version.py +164 -5
- cognite_toolkit/_cdf_tk/storageio/_applications.py +2 -2
- cognite_toolkit/_cdf_tk/storageio/_file_content.py +1 -2
- cognite_toolkit/_cdf_tk/storageio/_instances.py +1 -1
- cognite_toolkit/_cdf_tk/storageio/selectors/__init__.py +10 -1
- cognite_toolkit/_cdf_tk/storageio/selectors/_three_d.py +34 -0
- cognite_toolkit/_cdf_tk/utils/cdf.py +1 -1
- cognite_toolkit/_cdf_tk/utils/http_client/__init__.py +28 -0
- cognite_toolkit/_cdf_tk/utils/http_client/_client.py +3 -2
- cognite_toolkit/_cdf_tk/utils/http_client/_data_classes.py +6 -0
- cognite_toolkit/_cdf_tk/utils/http_client/_data_classes2.py +67 -7
- cognite_toolkit/_cdf_tk/utils/http_client/_tracker.py +5 -2
- cognite_toolkit/_cdf_tk/utils/interactive_select.py +51 -4
- cognite_toolkit/_cdf_tk/validation.py +4 -0
- cognite_toolkit/_repo_files/GitHub/.github/workflows/deploy.yaml +1 -1
- cognite_toolkit/_repo_files/GitHub/.github/workflows/dry-run.yaml +1 -1
- cognite_toolkit/_resources/cdf.toml +1 -1
- cognite_toolkit/_version.py +1 -1
- {cognite_toolkit-0.7.30.dist-info → cognite_toolkit-0.7.39.dist-info}/METADATA +1 -1
- {cognite_toolkit-0.7.30.dist-info → cognite_toolkit-0.7.39.dist-info}/RECORD +119 -113
- {cognite_toolkit-0.7.30.dist-info → cognite_toolkit-0.7.39.dist-info}/WHEEL +1 -1
- cognite_toolkit/_cdf_tk/client/api/robotics/__init__.py +0 -3
- cognite_toolkit/_cdf_tk/prototypes/import_app.py +0 -41
- /cognite_toolkit/_cdf_tk/{prototypes/commands → client/api/legacy}/__init__.py +0 -0
- /cognite_toolkit/_cdf_tk/client/api/{dml.py → legacy/dml.py} +0 -0
- /cognite_toolkit/_cdf_tk/client/api/{fixed_transformations.py → legacy/fixed_transformations.py} +0 -0
- /cognite_toolkit/_cdf_tk/client/api/{robotics → legacy/robotics}/api.py +0 -0
- /cognite_toolkit/_cdf_tk/client/api/{robotics → legacy/robotics}/utlis.py +0 -0
- /cognite_toolkit/_cdf_tk/client/data_classes/{apm_config_v1.py → legacy/apm_config_v1.py} +0 -0
- /cognite_toolkit/_cdf_tk/client/data_classes/{charts.py → legacy/charts.py} +0 -0
- /cognite_toolkit/_cdf_tk/client/data_classes/{extendable_cognite_file.py → legacy/extendable_cognite_file.py} +0 -0
- /cognite_toolkit/_cdf_tk/client/data_classes/{extended_filemetadata.py → legacy/extended_filemetadata.py} +0 -0
- /cognite_toolkit/_cdf_tk/client/data_classes/{extended_filemetdata.py → legacy/extended_filemetdata.py} +0 -0
- /cognite_toolkit/_cdf_tk/client/data_classes/{extended_timeseries.py → legacy/extended_timeseries.py} +0 -0
- /cognite_toolkit/_cdf_tk/client/data_classes/{functions.py → legacy/functions.py} +0 -0
- /cognite_toolkit/_cdf_tk/client/data_classes/{graphql_data_models.py → legacy/graphql_data_models.py} +0 -0
- /cognite_toolkit/_cdf_tk/client/data_classes/{instances.py → legacy/instances.py} +0 -0
- /cognite_toolkit/_cdf_tk/client/data_classes/{location_filters.py → legacy/location_filters.py} +0 -0
- /cognite_toolkit/_cdf_tk/client/data_classes/{migration.py → legacy/migration.py} +0 -0
- /cognite_toolkit/_cdf_tk/client/data_classes/{pending_instances_ids.py → legacy/pending_instances_ids.py} +0 -0
- /cognite_toolkit/_cdf_tk/client/data_classes/{project.py → legacy/project.py} +0 -0
- /cognite_toolkit/_cdf_tk/client/data_classes/{raw.py → legacy/raw.py} +0 -0
- /cognite_toolkit/_cdf_tk/client/data_classes/{robotics.py → legacy/robotics.py} +0 -0
- /cognite_toolkit/_cdf_tk/client/data_classes/{search_config.py → legacy/search_config.py} +0 -0
- /cognite_toolkit/_cdf_tk/client/data_classes/{sequences.py → legacy/sequences.py} +0 -0
- /cognite_toolkit/_cdf_tk/client/data_classes/{streamlit_.py → legacy/streamlit_.py} +0 -0
- /cognite_toolkit/_cdf_tk/{prototypes/commands/import_.py → commands/_import_cmd.py} +0 -0
- {cognite_toolkit-0.7.30.dist-info → cognite_toolkit-0.7.39.dist-info}/entry_points.txt +0 -0
|
@@ -1,19 +1,80 @@
|
|
|
1
|
+
from collections import defaultdict
|
|
2
|
+
from collections.abc import Iterable, Sequence
|
|
3
|
+
from typing import Any, TypeVar
|
|
4
|
+
|
|
5
|
+
from pydantic import TypeAdapter
|
|
1
6
|
from rich.console import Console
|
|
2
7
|
|
|
3
|
-
from cognite_toolkit._cdf_tk.client.data_classes.api_classes import PagedResponse
|
|
4
|
-
from cognite_toolkit._cdf_tk.client.data_classes.three_d import
|
|
5
|
-
|
|
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
|
|
17
|
+
from cognite_toolkit._cdf_tk.utils.http_client import (
|
|
18
|
+
HTTPClient,
|
|
19
|
+
ItemsRequest2,
|
|
20
|
+
RequestMessage2,
|
|
21
|
+
)
|
|
6
22
|
from cognite_toolkit._cdf_tk.utils.useful_types import PrimitiveType
|
|
7
23
|
|
|
8
24
|
|
|
9
25
|
class ThreeDModelAPI:
|
|
10
26
|
ENDPOINT = "/3d/models"
|
|
27
|
+
MAX_CLASSIC_MODELS_PER_CREATE_REQUEST = 1000
|
|
28
|
+
MAX_MODELS_PER_DELETE_REQUEST = 1000
|
|
29
|
+
_LIST_REQUEST_MAX_LIMIT = 1000
|
|
11
30
|
|
|
12
31
|
def __init__(self, http_client: HTTPClient, console: Console) -> None:
|
|
13
32
|
self._http_client = http_client
|
|
14
33
|
self._console = console
|
|
15
34
|
self._config = http_client.config
|
|
16
35
|
|
|
36
|
+
def create(self, models: Sequence[ThreeDModelClassicRequest]) -> list[ThreeDModelResponse]:
|
|
37
|
+
"""Create 3D models in classic format.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
models (Sequence[ThreeDModelClassicRequest]): The 3D model(s) to create.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
list[ThreeDModelResponse]: The created 3D model(s).
|
|
44
|
+
"""
|
|
45
|
+
if not models:
|
|
46
|
+
return []
|
|
47
|
+
if len(models) > self.MAX_CLASSIC_MODELS_PER_CREATE_REQUEST:
|
|
48
|
+
raise ValueError("Cannot create more than 1000 3D models in a single request.")
|
|
49
|
+
responses = self._http_client.request_items_retries(
|
|
50
|
+
ItemsRequest2(
|
|
51
|
+
endpoint_url=self._config.create_api_url(self.ENDPOINT),
|
|
52
|
+
method="POST",
|
|
53
|
+
items=models,
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
responses.raise_for_status()
|
|
57
|
+
return TypeAdapter(list[ThreeDModelResponse]).validate_python(responses.get_items())
|
|
58
|
+
|
|
59
|
+
def delete(self, ids: Sequence[int]) -> None:
|
|
60
|
+
"""Delete 3D models by their IDs.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
ids (Sequence[int]): The IDs of the 3D models to delete.
|
|
64
|
+
"""
|
|
65
|
+
if not ids:
|
|
66
|
+
return None
|
|
67
|
+
if len(ids) > self.MAX_MODELS_PER_DELETE_REQUEST:
|
|
68
|
+
raise ValueError("Cannot delete more than 1000 3D models in a single request.")
|
|
69
|
+
responses = self._http_client.request_items_retries(
|
|
70
|
+
ItemsRequest2(
|
|
71
|
+
endpoint_url=self._config.create_api_url(self.ENDPOINT + "/delete"),
|
|
72
|
+
method="POST",
|
|
73
|
+
items=InternalIdRequest.from_ids(list(ids)),
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
responses.raise_for_status()
|
|
77
|
+
|
|
17
78
|
def iterate(
|
|
18
79
|
self,
|
|
19
80
|
published: bool | None = None,
|
|
@@ -21,8 +82,8 @@ class ThreeDModelAPI:
|
|
|
21
82
|
limit: int = 100,
|
|
22
83
|
cursor: str | None = None,
|
|
23
84
|
) -> PagedResponse[ThreeDModelResponse]:
|
|
24
|
-
if not (0 < limit <=
|
|
25
|
-
raise ValueError("Limit must be between 1 and
|
|
85
|
+
if not (0 < limit <= self._LIST_REQUEST_MAX_LIMIT):
|
|
86
|
+
raise ValueError(f"Limit must be between 1 and {self._LIST_REQUEST_MAX_LIMIT}, got {limit}.")
|
|
26
87
|
parameters: dict[str, PrimitiveType] = {
|
|
27
88
|
# There is a bug in the API. The parameter includeRevisionInfo is expected to be lower case and not
|
|
28
89
|
# camel case as documented. You get error message: Unrecognized query parameter includeRevisionInfo,
|
|
@@ -34,17 +95,290 @@ class ThreeDModelAPI:
|
|
|
34
95
|
parameters["published"] = published
|
|
35
96
|
if cursor is not None:
|
|
36
97
|
parameters["cursor"] = cursor
|
|
37
|
-
responses = self._http_client.
|
|
38
|
-
|
|
98
|
+
responses = self._http_client.request_single_retries(
|
|
99
|
+
RequestMessage2(
|
|
39
100
|
endpoint_url=self._config.create_api_url(self.ENDPOINT),
|
|
40
101
|
method="GET",
|
|
41
102
|
parameters=parameters,
|
|
42
103
|
)
|
|
43
104
|
)
|
|
44
|
-
responses.
|
|
45
|
-
return PagedResponse[ThreeDModelResponse].model_validate(
|
|
105
|
+
success_response = responses.get_success_or_raise()
|
|
106
|
+
return PagedResponse[ThreeDModelResponse].model_validate(success_response.body_json)
|
|
107
|
+
|
|
108
|
+
def list(
|
|
109
|
+
self,
|
|
110
|
+
published: bool | None = None,
|
|
111
|
+
include_revision_info: bool = False,
|
|
112
|
+
limit: int | None = 100,
|
|
113
|
+
cursor: str | None = None,
|
|
114
|
+
) -> list[ThreeDModelResponse]:
|
|
115
|
+
results: list[ThreeDModelResponse] = []
|
|
116
|
+
while True:
|
|
117
|
+
request_limit = (
|
|
118
|
+
self._LIST_REQUEST_MAX_LIMIT
|
|
119
|
+
if limit is None
|
|
120
|
+
else min(limit - len(results), self._LIST_REQUEST_MAX_LIMIT)
|
|
121
|
+
)
|
|
122
|
+
if request_limit <= 0:
|
|
123
|
+
break
|
|
124
|
+
page = self.iterate(
|
|
125
|
+
published=published,
|
|
126
|
+
include_revision_info=include_revision_info,
|
|
127
|
+
limit=request_limit,
|
|
128
|
+
cursor=cursor,
|
|
129
|
+
)
|
|
130
|
+
results.extend(page.items)
|
|
131
|
+
if page.next_cursor is None:
|
|
132
|
+
break
|
|
133
|
+
cursor = page.next_cursor
|
|
134
|
+
return results
|
|
135
|
+
|
|
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
|
|
46
379
|
|
|
47
380
|
|
|
48
381
|
class ThreeDAPI:
|
|
49
382
|
def __init__(self, http_client: HTTPClient, console: Console) -> None:
|
|
50
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,9 +3,11 @@ 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
|
|
6
|
+
from pydantic import ConfigDict
|
|
7
7
|
from pydantic.alias_generators import to_camel
|
|
8
8
|
|
|
9
|
+
from cognite_toolkit._cdf_tk.utils.http_client._data_classes2 import BaseModelObject, RequestResource
|
|
10
|
+
|
|
9
11
|
if TYPE_CHECKING:
|
|
10
12
|
from cognite.client import CogniteClient
|
|
11
13
|
|
|
@@ -15,28 +17,6 @@ else:
|
|
|
15
17
|
from typing_extensions import Self
|
|
16
18
|
|
|
17
19
|
|
|
18
|
-
class BaseModelObject(BaseModel):
|
|
19
|
-
"""Base class for all object. This includes resources and nested objects."""
|
|
20
|
-
|
|
21
|
-
# We allow extra fields to support forward compatibility.
|
|
22
|
-
model_config = ConfigDict(alias_generator=to_camel, extra="allow")
|
|
23
|
-
|
|
24
|
-
def dump(self, camel_case: bool = True) -> dict[str, Any]:
|
|
25
|
-
"""Dump the resource to a dictionary.
|
|
26
|
-
|
|
27
|
-
This is the default serialization method for request resources.
|
|
28
|
-
"""
|
|
29
|
-
return self.model_dump(mode="json", by_alias=camel_case)
|
|
30
|
-
|
|
31
|
-
@classmethod
|
|
32
|
-
def _load(cls, resource: dict[str, Any]) -> "Self":
|
|
33
|
-
"""Load method to match CogniteResource signature."""
|
|
34
|
-
return cls.model_validate(resource)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
class RequestResource(BaseModelObject): ...
|
|
38
|
-
|
|
39
|
-
|
|
40
20
|
T_RequestResource = TypeVar("T_RequestResource", bound=RequestResource)
|
|
41
21
|
|
|
42
22
|
|
|
@@ -46,22 +26,22 @@ class ResponseResource(BaseModelObject, Generic[T_RequestResource], ABC):
|
|
|
46
26
|
"""Convert the response resource to a request resource."""
|
|
47
27
|
...
|
|
48
28
|
|
|
29
|
+
def as_write(self) -> T_RequestResource:
|
|
30
|
+
"""Alias for as_request_resource to match protocol signature."""
|
|
31
|
+
return self.as_request_resource()
|
|
49
32
|
|
|
50
|
-
|
|
33
|
+
|
|
34
|
+
class Identifier(RequestResource, ABC):
|
|
51
35
|
"""Base class for all identifier classes."""
|
|
52
36
|
|
|
53
37
|
model_config = ConfigDict(alias_generator=to_camel, extra="ignore", populate_by_name=True, frozen=True)
|
|
54
38
|
|
|
55
|
-
def dump(self, include_type: bool = True) -> dict[str, Any]:
|
|
56
|
-
"""Dump the
|
|
57
|
-
|
|
58
|
-
Args:
|
|
59
|
-
include_type (bool): Whether to include the type of the identifier in the output.
|
|
39
|
+
def dump(self, camel_case: bool = True, include_type: bool = True) -> dict[str, Any]:
|
|
40
|
+
"""Dump the resource to a dictionary.
|
|
60
41
|
|
|
61
|
-
|
|
62
|
-
dict[str, Any]: The dumped identifier.
|
|
42
|
+
This is the default serialization method for request resources.
|
|
63
43
|
"""
|
|
64
|
-
return self.model_dump(mode="json", by_alias=
|
|
44
|
+
return self.model_dump(mode="json", by_alias=camel_case, exclude_unset=not include_type)
|
|
65
45
|
|
|
66
46
|
def as_id(self) -> Self:
|
|
67
47
|
return self
|
|
@@ -7,7 +7,7 @@ from .base import BaseModelObject, Identifier, RequestResource
|
|
|
7
7
|
InstanceType: TypeAlias = Literal["node", "edge"]
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
class
|
|
10
|
+
class TypedInstanceIdentifier(Identifier):
|
|
11
11
|
"""Identifier for an Instance instance."""
|
|
12
12
|
|
|
13
13
|
instance_type: InstanceType
|
|
@@ -15,14 +15,19 @@ class InstanceIdentifier(Identifier):
|
|
|
15
15
|
external_id: str
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
class
|
|
18
|
+
class TypedNodeIdentifier(TypedInstanceIdentifier):
|
|
19
19
|
instance_type: Literal["node"] = "node"
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
class
|
|
22
|
+
class TypedEdgeIdentifier(TypedInstanceIdentifier):
|
|
23
23
|
instance_type: Literal["edge"] = "edge"
|
|
24
24
|
|
|
25
25
|
|
|
26
|
+
class InstanceIdentifier(Identifier):
|
|
27
|
+
space: str
|
|
28
|
+
external_id: str
|
|
29
|
+
|
|
30
|
+
|
|
26
31
|
class InstanceResult(BaseModelObject):
|
|
27
32
|
instance_type: InstanceType
|
|
28
33
|
version: int
|
|
@@ -32,8 +37,8 @@ class InstanceResult(BaseModelObject):
|
|
|
32
37
|
created_time: int
|
|
33
38
|
last_updated_time: int
|
|
34
39
|
|
|
35
|
-
def as_id(self) ->
|
|
36
|
-
return
|
|
40
|
+
def as_id(self) -> TypedInstanceIdentifier:
|
|
41
|
+
return TypedInstanceIdentifier(
|
|
37
42
|
instance_type=self.instance_type,
|
|
38
43
|
space=self.space,
|
|
39
44
|
external_id=self.external_id,
|
|
@@ -62,8 +67,8 @@ class InstanceRequestResource(RequestResource):
|
|
|
62
67
|
space: str
|
|
63
68
|
external_id: str
|
|
64
69
|
|
|
65
|
-
def as_id(self) ->
|
|
66
|
-
return
|
|
70
|
+
def as_id(self) -> TypedInstanceIdentifier:
|
|
71
|
+
return TypedInstanceIdentifier(
|
|
67
72
|
instance_type=self.instance_type,
|
|
68
73
|
space=self.space,
|
|
69
74
|
external_id=self.external_id,
|
|
@@ -107,7 +112,7 @@ class InstanceSource(BaseModelObject):
|
|
|
107
112
|
return value
|
|
108
113
|
|
|
109
114
|
|
|
110
|
-
class InstanceRequestItem(
|
|
115
|
+
class InstanceRequestItem(RequestResource):
|
|
111
116
|
model_config = ConfigDict(populate_by_name=True)
|
|
112
117
|
instance_type: InstanceType
|
|
113
118
|
space: str
|
|
@@ -115,8 +120,8 @@ class InstanceRequestItem(BaseModelObject):
|
|
|
115
120
|
existing_version: int | None = None
|
|
116
121
|
sources: list[InstanceSource] | None = None
|
|
117
122
|
|
|
118
|
-
def as_id(self) ->
|
|
119
|
-
return
|
|
123
|
+
def as_id(self) -> TypedInstanceIdentifier:
|
|
124
|
+
return TypedInstanceIdentifier(
|
|
120
125
|
instance_type=self.instance_type,
|
|
121
126
|
space=self.space,
|
|
122
127
|
external_id=self.external_id,
|
|
@@ -128,7 +133,7 @@ class InstanceResponseItem(BaseModelObject):
|
|
|
128
133
|
space: str
|
|
129
134
|
external_id: str
|
|
130
135
|
version: int
|
|
131
|
-
type:
|
|
136
|
+
type: TypedInstanceIdentifier | None = None
|
|
132
137
|
created_time: int
|
|
133
138
|
last_updated_time: int
|
|
134
139
|
deleted_time: int | None = None
|
|
@@ -149,8 +154,8 @@ class InstanceResponseItem(BaseModelObject):
|
|
|
149
154
|
output.update(space_properties.get(view_version, {}))
|
|
150
155
|
return output
|
|
151
156
|
|
|
152
|
-
def as_id(self) ->
|
|
153
|
-
return
|
|
157
|
+
def as_id(self) -> TypedInstanceIdentifier:
|
|
158
|
+
return TypedInstanceIdentifier(
|
|
154
159
|
instance_type=self.instance_type,
|
|
155
160
|
space=self.space,
|
|
156
161
|
external_id=self.external_id,
|
|
File without changes
|
|
@@ -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
|
|
|
@@ -19,11 +22,17 @@ class RevisionStatus(BaseModelObject):
|
|
|
19
22
|
class ThreeDModelRequest(RequestResource):
|
|
20
23
|
name: str
|
|
21
24
|
|
|
25
|
+
def as_id(self) -> str:
|
|
26
|
+
return self.name
|
|
27
|
+
|
|
22
28
|
|
|
23
29
|
class ThreeDModelClassicRequest(ThreeDModelRequest):
|
|
24
30
|
data_set_id: int | None = None
|
|
25
31
|
metadata: dict[str, str] | None = None
|
|
26
32
|
|
|
33
|
+
def as_id(self) -> str:
|
|
34
|
+
return self.name
|
|
35
|
+
|
|
27
36
|
|
|
28
37
|
class ThreeDModelDMSRequest(ThreeDModelRequest):
|
|
29
38
|
space: str
|
|
@@ -45,3 +54,59 @@ class ThreeDModelResponse(ResponseResource[ThreeDModelRequest]):
|
|
|
45
54
|
return ThreeDModelClassicRequest._load(self.dump())
|
|
46
55
|
else:
|
|
47
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
|
+
)
|