cognite-toolkit 0.7.43__py3-none-any.whl → 0.7.45__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cognite_toolkit/_cdf_tk/client/_toolkit_client.py +6 -0
- cognite_toolkit/_cdf_tk/client/api/assets.py +2 -2
- cognite_toolkit/_cdf_tk/client/api/events.py +2 -2
- cognite_toolkit/_cdf_tk/client/api/filemetadata.py +150 -0
- cognite_toolkit/_cdf_tk/client/api/raw.py +174 -0
- cognite_toolkit/_cdf_tk/client/api/simulator_models.py +128 -0
- cognite_toolkit/_cdf_tk/client/api/simulators.py +8 -0
- cognite_toolkit/_cdf_tk/client/api/timeseries.py +2 -2
- cognite_toolkit/_cdf_tk/client/cdf_client/__init__.py +2 -1
- cognite_toolkit/_cdf_tk/client/cdf_client/api.py +114 -8
- cognite_toolkit/_cdf_tk/client/data_classes/annotation.py +79 -0
- cognite_toolkit/_cdf_tk/client/data_classes/base.py +13 -3
- cognite_toolkit/_cdf_tk/client/data_classes/data_modeling/__init__.py +16 -0
- cognite_toolkit/_cdf_tk/client/data_classes/data_modeling/_instance.py +143 -0
- cognite_toolkit/_cdf_tk/client/data_classes/data_modeling/_references.py +8 -0
- cognite_toolkit/_cdf_tk/client/data_classes/dataset.py +35 -0
- cognite_toolkit/_cdf_tk/client/data_classes/extraction_pipeline.py +59 -0
- cognite_toolkit/_cdf_tk/client/data_classes/filemetadata.py +7 -1
- cognite_toolkit/_cdf_tk/client/data_classes/hosted_extractor_destination.py +34 -0
- cognite_toolkit/_cdf_tk/client/data_classes/hosted_extractor_job.py +134 -0
- cognite_toolkit/_cdf_tk/client/data_classes/hosted_extractor_mapping.py +72 -0
- cognite_toolkit/_cdf_tk/client/data_classes/hosted_extractor_source/__init__.py +63 -0
- cognite_toolkit/_cdf_tk/client/data_classes/hosted_extractor_source/_auth.py +63 -0
- cognite_toolkit/_cdf_tk/client/data_classes/hosted_extractor_source/_base.py +26 -0
- cognite_toolkit/_cdf_tk/client/data_classes/hosted_extractor_source/_certificate.py +20 -0
- cognite_toolkit/_cdf_tk/client/data_classes/hosted_extractor_source/_eventhub.py +31 -0
- cognite_toolkit/_cdf_tk/client/data_classes/hosted_extractor_source/_kafka.py +53 -0
- cognite_toolkit/_cdf_tk/client/data_classes/hosted_extractor_source/_mqtt.py +36 -0
- cognite_toolkit/_cdf_tk/client/data_classes/hosted_extractor_source/_rest.py +49 -0
- cognite_toolkit/_cdf_tk/client/data_classes/identifiers.py +8 -0
- cognite_toolkit/_cdf_tk/client/data_classes/label.py +27 -0
- cognite_toolkit/_cdf_tk/client/data_classes/raw.py +3 -2
- cognite_toolkit/_cdf_tk/client/data_classes/securitycategory.py +24 -0
- cognite_toolkit/_cdf_tk/client/data_classes/sequence.py +45 -0
- cognite_toolkit/_cdf_tk/client/data_classes/transformation.py +140 -0
- cognite_toolkit/_cdf_tk/client/data_classes/workflow.py +27 -0
- cognite_toolkit/_cdf_tk/client/data_classes/workflow_trigger.py +63 -0
- cognite_toolkit/_cdf_tk/client/data_classes/workflow_version.py +155 -0
- cognite_toolkit/_cdf_tk/client/testing.py +5 -0
- cognite_toolkit/_cdf_tk/commands/_migrate/conversion.py +4 -3
- cognite_toolkit/_cdf_tk/cruds/__init__.py +5 -1
- cognite_toolkit/_cdf_tk/cruds/_data_cruds.py +7 -3
- cognite_toolkit/_cdf_tk/cruds/_resource_cruds/__init__.py +2 -0
- cognite_toolkit/_cdf_tk/cruds/_resource_cruds/file.py +56 -59
- cognite_toolkit/_cdf_tk/cruds/_resource_cruds/relationship.py +1 -1
- cognite_toolkit/_cdf_tk/cruds/_resource_cruds/simulators.py +119 -0
- cognite_toolkit/_cdf_tk/feature_flags.py +4 -0
- cognite_toolkit/_cdf_tk/storageio/_asset_centric.py +34 -29
- cognite_toolkit/_cdf_tk/storageio/_file_content.py +22 -19
- 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.43.dist-info → cognite_toolkit-0.7.45.dist-info}/METADATA +11 -1
- {cognite_toolkit-0.7.43.dist-info → cognite_toolkit-0.7.45.dist-info}/RECORD +57 -30
- {cognite_toolkit-0.7.43.dist-info → cognite_toolkit-0.7.45.dist-info}/WHEEL +1 -1
- {cognite_toolkit-0.7.43.dist-info → cognite_toolkit-0.7.45.dist-info}/entry_points.txt +0 -0
|
@@ -5,6 +5,7 @@ that handle CRUD operations for CDF Data Modeling API resources.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from abc import ABC, abstractmethod
|
|
8
|
+
from collections import defaultdict
|
|
8
9
|
from collections.abc import Callable, Iterable, Sequence
|
|
9
10
|
from dataclasses import dataclass
|
|
10
11
|
from functools import partial
|
|
@@ -18,7 +19,13 @@ from cognite_toolkit._cdf_tk.client.data_classes.base import (
|
|
|
18
19
|
T_RequestResource,
|
|
19
20
|
T_ResponseResource,
|
|
20
21
|
)
|
|
21
|
-
from cognite_toolkit._cdf_tk.client.http_client import
|
|
22
|
+
from cognite_toolkit._cdf_tk.client.http_client import (
|
|
23
|
+
HTTPClient,
|
|
24
|
+
ItemsRequest2,
|
|
25
|
+
ItemsSuccessResponse2,
|
|
26
|
+
RequestMessage2,
|
|
27
|
+
SuccessResponse2,
|
|
28
|
+
)
|
|
22
29
|
from cognite_toolkit._cdf_tk.utils.collection import chunker_sequence
|
|
23
30
|
|
|
24
31
|
from .responses import PagedResponse, ResponseItems
|
|
@@ -67,7 +74,7 @@ class CDFResourceAPI(Generic[T_Identifier, T_RequestResource, T_ResponseResource
|
|
|
67
74
|
return [item.as_update(mode=mode) for item in items]
|
|
68
75
|
|
|
69
76
|
@abstractmethod
|
|
70
|
-
def _page_response(self, response: SuccessResponse2) -> PagedResponse[T_ResponseResource]:
|
|
77
|
+
def _page_response(self, response: SuccessResponse2 | ItemsSuccessResponse2) -> PagedResponse[T_ResponseResource]:
|
|
71
78
|
"""Parse a single item response."""
|
|
72
79
|
raise NotImplementedError()
|
|
73
80
|
|
|
@@ -97,9 +104,10 @@ class CDFResourceAPI(Generic[T_Identifier, T_RequestResource, T_ResponseResource
|
|
|
97
104
|
method: APIMethod,
|
|
98
105
|
params: dict[str, Any] | None = None,
|
|
99
106
|
extra_body: dict[str, Any] | None = None,
|
|
107
|
+
endpoint: str | None = None,
|
|
100
108
|
) -> list[T_ResponseResource]:
|
|
101
109
|
response_items: list[T_ResponseResource] = []
|
|
102
|
-
for response in self._chunk_requests(items, method, self._serialize_items, params, extra_body):
|
|
110
|
+
for response in self._chunk_requests(items, method, self._serialize_items, params, extra_body, endpoint):
|
|
103
111
|
response_items.extend(self._page_response(response).items)
|
|
104
112
|
return response_items
|
|
105
113
|
|
|
@@ -121,8 +129,9 @@ class CDFResourceAPI(Generic[T_Identifier, T_RequestResource, T_ResponseResource
|
|
|
121
129
|
method: APIMethod,
|
|
122
130
|
params: dict[str, Any] | None = None,
|
|
123
131
|
extra_body: dict[str, Any] | None = None,
|
|
132
|
+
endpoint: str | None = None,
|
|
124
133
|
) -> None:
|
|
125
|
-
list(self._chunk_requests(items, method, self._serialize_items, params, extra_body))
|
|
134
|
+
list(self._chunk_requests(items, method, self._serialize_items, params, extra_body, endpoint))
|
|
126
135
|
return None
|
|
127
136
|
|
|
128
137
|
def _chunk_requests(
|
|
@@ -132,14 +141,28 @@ class CDFResourceAPI(Generic[T_Identifier, T_RequestResource, T_ResponseResource
|
|
|
132
141
|
serialization: Callable[[Sequence[_T_BaseModel]], list[dict[str, JsonValue]]],
|
|
133
142
|
params: dict[str, Any] | None = None,
|
|
134
143
|
extra_body: dict[str, Any] | None = None,
|
|
144
|
+
endpoint_path: str | None = None,
|
|
135
145
|
) -> Iterable[SuccessResponse2]:
|
|
146
|
+
"""Send requests in chunks and yield responses.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
items: The items to process.
|
|
150
|
+
method: The API method to use. This is used ot look the up the endpoint.
|
|
151
|
+
serialization: A function to serialize the items to JSON-compatible dicts.
|
|
152
|
+
params: Optional query parameters for the request.
|
|
153
|
+
extra_body: Optional extra body content to include in the request.
|
|
154
|
+
endpoint_path: Optional override for the endpoint path.
|
|
155
|
+
|
|
156
|
+
Yields:
|
|
157
|
+
The successful responses from the API.
|
|
158
|
+
"""
|
|
136
159
|
# Filter out None
|
|
137
160
|
request_params = self._filter_out_none_values(params)
|
|
138
161
|
endpoint = self._method_endpoint_map[method]
|
|
139
162
|
|
|
140
163
|
for chunk in chunker_sequence(items, endpoint.item_limit):
|
|
141
164
|
request = RequestMessage2(
|
|
142
|
-
endpoint_url=f"{self._make_url(endpoint.path)}",
|
|
165
|
+
endpoint_url=f"{self._make_url(endpoint_path or endpoint.path)}",
|
|
143
166
|
method=endpoint.method,
|
|
144
167
|
body_content={"items": serialization(chunk), **(extra_body or {})}, # type: ignore[dict-item]
|
|
145
168
|
parameters=request_params,
|
|
@@ -147,6 +170,72 @@ class CDFResourceAPI(Generic[T_Identifier, T_RequestResource, T_ResponseResource
|
|
|
147
170
|
response = self._http_client.request_single_retries(request)
|
|
148
171
|
yield response.get_success_or_raise()
|
|
149
172
|
|
|
173
|
+
def _request_item_split_retries(
|
|
174
|
+
self,
|
|
175
|
+
items: Sequence[_T_BaseModel],
|
|
176
|
+
method: APIMethod,
|
|
177
|
+
params: dict[str, Any] | None = None,
|
|
178
|
+
extra_body: dict[str, Any] | None = None,
|
|
179
|
+
) -> list[T_ResponseResource]:
|
|
180
|
+
"""Request items with retries, splitting on failures.
|
|
181
|
+
|
|
182
|
+
This method handles large batches of items by chunking them according to the endpoint's item limit.
|
|
183
|
+
If a single item fails, it splits the request into individual item requests to isolate the failure.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
items: Sequence of items to request.
|
|
187
|
+
method: API method to use for the request.
|
|
188
|
+
params: Optional query parameters for the request.
|
|
189
|
+
extra_body: Optional additional body fields for the request.
|
|
190
|
+
Returns:
|
|
191
|
+
List of response items.
|
|
192
|
+
"""
|
|
193
|
+
response_items: list[T_ResponseResource] = []
|
|
194
|
+
for response in self._chunk_requests_items_split_retries(items, method, params, extra_body):
|
|
195
|
+
response_items.extend(self._page_response(response).items)
|
|
196
|
+
return response_items
|
|
197
|
+
|
|
198
|
+
def _chunk_requests_items_split_retries(
|
|
199
|
+
self,
|
|
200
|
+
items: Sequence[_T_BaseModel],
|
|
201
|
+
method: APIMethod,
|
|
202
|
+
params: dict[str, Any] | None = None,
|
|
203
|
+
extra_body: dict[str, Any] | None = None,
|
|
204
|
+
) -> Iterable[ItemsSuccessResponse2]:
|
|
205
|
+
"""Request items with retries and splitting on failures.
|
|
206
|
+
|
|
207
|
+
This method handles large batches of items by chunking them according to the endpoint's item limit.
|
|
208
|
+
If a single item fails, it splits the request into individual item requests to isolate the failure.
|
|
209
|
+
|
|
210
|
+
This can be useful to create ignore unknown IDs behavior when retrieving items for API endpoints that
|
|
211
|
+
do not support it natively.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
items: Sequence of items to request.
|
|
215
|
+
method: API method to use for the request.
|
|
216
|
+
params: Optional query parameters for the request.
|
|
217
|
+
extra_body: Optional additional body fields for the request.
|
|
218
|
+
Yields
|
|
219
|
+
SuccessResponse2: Successful responses from the API. All failed items are skipped.
|
|
220
|
+
|
|
221
|
+
"""
|
|
222
|
+
# Filter out None
|
|
223
|
+
request_params = self._filter_out_none_values(params)
|
|
224
|
+
endpoint = self._method_endpoint_map[method]
|
|
225
|
+
|
|
226
|
+
for chunk in chunker_sequence(items, endpoint.item_limit):
|
|
227
|
+
request = ItemsRequest2(
|
|
228
|
+
endpoint_url=f"{self._make_url(endpoint.path)}",
|
|
229
|
+
method=endpoint.method,
|
|
230
|
+
parameters=request_params,
|
|
231
|
+
items=chunk,
|
|
232
|
+
extra_body_fields=extra_body,
|
|
233
|
+
)
|
|
234
|
+
responses = self._http_client.request_items_retries(request)
|
|
235
|
+
for response in responses:
|
|
236
|
+
if isinstance(response, ItemsSuccessResponse2):
|
|
237
|
+
yield response
|
|
238
|
+
|
|
150
239
|
@classmethod
|
|
151
240
|
def _filter_out_none_values(cls, params: dict[str, Any] | None) -> dict[str, Any] | None:
|
|
152
241
|
request_params: dict[str, Any] | None = None
|
|
@@ -154,12 +243,24 @@ class CDFResourceAPI(Generic[T_Identifier, T_RequestResource, T_ResponseResource
|
|
|
154
243
|
request_params = {k: v for k, v in params.items() if v is not None}
|
|
155
244
|
return request_params
|
|
156
245
|
|
|
246
|
+
@classmethod
|
|
247
|
+
def _group_items_by_text_field(
|
|
248
|
+
cls, items: Sequence[_T_BaseModel], field_name: str
|
|
249
|
+
) -> dict[str, list[_T_BaseModel]]:
|
|
250
|
+
"""Group items by a text field."""
|
|
251
|
+
grouped_items: dict[str, list[_T_BaseModel]] = defaultdict(list)
|
|
252
|
+
for item in items:
|
|
253
|
+
key = str(getattr(item, field_name))
|
|
254
|
+
grouped_items[key].append(item)
|
|
255
|
+
return grouped_items
|
|
256
|
+
|
|
157
257
|
def _iterate(
|
|
158
258
|
self,
|
|
159
259
|
limit: int,
|
|
160
260
|
cursor: str | None = None,
|
|
161
261
|
params: dict[str, Any] | None = None,
|
|
162
262
|
body: dict[str, Any] | None = None,
|
|
263
|
+
endpoint_path: str | None = None,
|
|
163
264
|
) -> PagedResponse[T_ResponseResource]:
|
|
164
265
|
"""Fetch a single page of resources.
|
|
165
266
|
|
|
@@ -171,8 +272,11 @@ class CDFResourceAPI(Generic[T_Identifier, T_RequestResource, T_ResponseResource
|
|
|
171
272
|
- space: Filter by space
|
|
172
273
|
- includeGlobal: Whether to include global resources
|
|
173
274
|
body : Body content for the request, if applicable.
|
|
275
|
+
limit: Maximum number of items to return in the page.
|
|
276
|
+
cursor: Cursor for pagination.
|
|
174
277
|
limit: Maximum number of items to return in the page.
|
|
175
278
|
cursor: Cursor for pagination.
|
|
279
|
+
|
|
176
280
|
Returns:
|
|
177
281
|
A Page containing the items and the cursor for the next page.
|
|
178
282
|
"""
|
|
@@ -194,7 +298,7 @@ class CDFResourceAPI(Generic[T_Identifier, T_RequestResource, T_ResponseResource
|
|
|
194
298
|
raise NotImplementedError(f"Unsupported method {endpoint.method} for pagination.")
|
|
195
299
|
|
|
196
300
|
request = RequestMessage2(
|
|
197
|
-
endpoint_url=self._make_url(endpoint.path),
|
|
301
|
+
endpoint_url=self._make_url(endpoint_path or endpoint.path),
|
|
198
302
|
method=endpoint.method,
|
|
199
303
|
parameters=request_params,
|
|
200
304
|
body_content=body,
|
|
@@ -203,7 +307,9 @@ class CDFResourceAPI(Generic[T_Identifier, T_RequestResource, T_ResponseResource
|
|
|
203
307
|
response = result.get_success_or_raise()
|
|
204
308
|
return self._page_response(response)
|
|
205
309
|
|
|
206
|
-
def _list(
|
|
310
|
+
def _list(
|
|
311
|
+
self, limit: int | None = None, params: dict[str, Any] | None = None, endpoint_path: str | None = None
|
|
312
|
+
) -> list[T_ResponseResource]:
|
|
207
313
|
"""List all resources, handling pagination automatically."""
|
|
208
314
|
all_items: list[T_ResponseResource] = []
|
|
209
315
|
next_cursor: str | None = None
|
|
@@ -211,7 +317,7 @@ class CDFResourceAPI(Generic[T_Identifier, T_RequestResource, T_ResponseResource
|
|
|
211
317
|
endpoint = self._method_endpoint_map["list"]
|
|
212
318
|
while True:
|
|
213
319
|
page_limit = endpoint.item_limit if limit is None else min(limit - total, endpoint.item_limit)
|
|
214
|
-
page = self._iterate(limit=page_limit, cursor=next_cursor, params=params)
|
|
320
|
+
page = self._iterate(limit=page_limit, cursor=next_cursor, params=params, endpoint_path=endpoint_path)
|
|
215
321
|
all_items.extend(page.items)
|
|
216
322
|
total += len(page.items)
|
|
217
323
|
if page.next_cursor is None or (limit is not None and total >= limit):
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from typing import Any, Literal, TypeAlias
|
|
2
|
+
|
|
3
|
+
from pydantic import Field, JsonValue
|
|
4
|
+
|
|
5
|
+
from cognite_toolkit._cdf_tk.client.data_classes.base import BaseModelObject, RequestUpdateable, ResponseResource
|
|
6
|
+
|
|
7
|
+
from .identifiers import InternalId
|
|
8
|
+
|
|
9
|
+
AnnotationStatus: TypeAlias = Literal["suggested", "rejected", "approved"]
|
|
10
|
+
AnnotationType: TypeAlias = Literal[
|
|
11
|
+
"diagrams.AssetLink",
|
|
12
|
+
"diagrams.FileLink",
|
|
13
|
+
"diagrams.InstanceLink",
|
|
14
|
+
"diagrams.Junction",
|
|
15
|
+
"diagrams.Line",
|
|
16
|
+
"diagrams.UnhandledSymbolObject",
|
|
17
|
+
"diagrams.UnhandledTextObject",
|
|
18
|
+
"documents.ExtractedText",
|
|
19
|
+
"forms.Detection",
|
|
20
|
+
"images.AssetLink",
|
|
21
|
+
"images.Classification",
|
|
22
|
+
"images.InstanceLink",
|
|
23
|
+
"images.KeypointCollection",
|
|
24
|
+
"images.ObjectDetection",
|
|
25
|
+
"images.TextRegion",
|
|
26
|
+
"isoplan.IsoPlanAnnotation",
|
|
27
|
+
"pointcloud.BoundingVolume",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Annotation(BaseModelObject):
|
|
32
|
+
annotated_resource_type: str
|
|
33
|
+
annotated_resource_id: int
|
|
34
|
+
annotation_type: AnnotationType
|
|
35
|
+
creating_app: str
|
|
36
|
+
creating_app_version: str
|
|
37
|
+
creating_user: str | None
|
|
38
|
+
data: dict[str, JsonValue]
|
|
39
|
+
status: AnnotationStatus
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class AnnotationRequest(Annotation, RequestUpdateable):
|
|
43
|
+
"""Request data class for annotations."""
|
|
44
|
+
|
|
45
|
+
# The 'id' field is not part of the request when creating a new resource,
|
|
46
|
+
# but is needed when updating an existing resource.
|
|
47
|
+
id: int | None = Field(default=None, exclude=True)
|
|
48
|
+
|
|
49
|
+
def as_id(self) -> InternalId:
|
|
50
|
+
if self.id is None:
|
|
51
|
+
raise ValueError("Cannot convert AnnotationRequest to InternalId when id is None")
|
|
52
|
+
return InternalId(id=self.id)
|
|
53
|
+
|
|
54
|
+
def as_update(self, mode: Literal["patch", "replace"]) -> dict[str, Any]:
|
|
55
|
+
"""Converts the request to an update payload for the API."""
|
|
56
|
+
if self.id is None:
|
|
57
|
+
raise ValueError("id must be provided to create an update dictionary")
|
|
58
|
+
return {
|
|
59
|
+
"id": self.id,
|
|
60
|
+
"update": {
|
|
61
|
+
"annotationType": {"set": self.annotation_type},
|
|
62
|
+
"data": {"set": self.data},
|
|
63
|
+
"status": {"set": self.status},
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class AnnotationResponse(Annotation, ResponseResource[AnnotationRequest]):
|
|
69
|
+
"""Response data class for annotations."""
|
|
70
|
+
|
|
71
|
+
id: int
|
|
72
|
+
created_time: int
|
|
73
|
+
last_updated_time: int
|
|
74
|
+
|
|
75
|
+
def as_id(self) -> InternalId:
|
|
76
|
+
return InternalId(id=self.id)
|
|
77
|
+
|
|
78
|
+
def as_request_resource(self) -> AnnotationRequest:
|
|
79
|
+
return AnnotationRequest.model_validate(self.dump(), extra="ignore")
|
|
@@ -20,11 +20,21 @@ class BaseModelObject(BaseModel):
|
|
|
20
20
|
# We allow extra fields to support forward compatibility.
|
|
21
21
|
model_config = ConfigDict(alias_generator=to_camel, extra="allow", populate_by_name=True)
|
|
22
22
|
|
|
23
|
-
def dump(self, camel_case: bool = True) -> dict[str, Any]:
|
|
23
|
+
def dump(self, camel_case: bool = True, exclude_extra: bool = False) -> dict[str, Any]:
|
|
24
24
|
"""Dump the resource to a dictionary.
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
Args:
|
|
27
|
+
camel_case (bool): Whether to use camelCase for the keys. Default is True.
|
|
28
|
+
exclude_extra (bool): Whether to exclude extra fields not defined in the model. Default is False.
|
|
29
|
+
|
|
27
30
|
"""
|
|
31
|
+
if exclude_extra:
|
|
32
|
+
return self.model_dump(
|
|
33
|
+
mode="json",
|
|
34
|
+
by_alias=camel_case,
|
|
35
|
+
exclude_unset=True,
|
|
36
|
+
exclude=set(self.__pydantic_extra__) if self.__pydantic_extra__ else None,
|
|
37
|
+
)
|
|
28
38
|
return self.model_dump(mode="json", by_alias=camel_case, exclude_unset=True)
|
|
29
39
|
|
|
30
40
|
@classmethod
|
|
@@ -39,7 +49,7 @@ class Identifier(BaseModelObject):
|
|
|
39
49
|
|
|
40
50
|
model_config = ConfigDict(alias_generator=to_camel, extra="ignore", populate_by_name=True, frozen=True)
|
|
41
51
|
|
|
42
|
-
def dump(self, camel_case: bool = True) -> dict[str, Any]:
|
|
52
|
+
def dump(self, camel_case: bool = True, exclude_extra: bool = False) -> dict[str, Any]:
|
|
43
53
|
"""Dump the resource to a dictionary.
|
|
44
54
|
|
|
45
55
|
This is the default serialization method for request resources.
|
|
@@ -42,6 +42,15 @@ from ._indexes import (
|
|
|
42
42
|
IndexDefinition,
|
|
43
43
|
InvertedIndex,
|
|
44
44
|
)
|
|
45
|
+
from ._instance import (
|
|
46
|
+
EdgeRequest,
|
|
47
|
+
EdgeResponse,
|
|
48
|
+
InstanceDefinition,
|
|
49
|
+
InstanceResponseDefinition,
|
|
50
|
+
InstanceSource,
|
|
51
|
+
NodeRequest,
|
|
52
|
+
NodeResponse,
|
|
53
|
+
)
|
|
45
54
|
from ._references import (
|
|
46
55
|
ContainerConstraintReference,
|
|
47
56
|
ContainerDirectReference,
|
|
@@ -99,6 +108,8 @@ __all__ = [
|
|
|
99
108
|
"DateProperty",
|
|
100
109
|
"DirectNodeRelation",
|
|
101
110
|
"EdgeProperty",
|
|
111
|
+
"EdgeRequest",
|
|
112
|
+
"EdgeResponse",
|
|
102
113
|
"EnumProperty",
|
|
103
114
|
"EnumValue",
|
|
104
115
|
"FileCDFExternalIdReference",
|
|
@@ -108,6 +119,9 @@ __all__ = [
|
|
|
108
119
|
"Index",
|
|
109
120
|
"IndexAdapter",
|
|
110
121
|
"IndexDefinition",
|
|
122
|
+
"InstanceDefinition",
|
|
123
|
+
"InstanceResponseDefinition",
|
|
124
|
+
"InstanceSource",
|
|
111
125
|
"Int32Property",
|
|
112
126
|
"Int64Property",
|
|
113
127
|
"InvertedIndex",
|
|
@@ -117,6 +131,8 @@ __all__ = [
|
|
|
117
131
|
"MultiReverseDirectRelationPropertyRequest",
|
|
118
132
|
"MultiReverseDirectRelationPropertyResponse",
|
|
119
133
|
"NodeReference",
|
|
134
|
+
"NodeRequest",
|
|
135
|
+
"NodeResponse",
|
|
120
136
|
"PropertyTypeDefinition",
|
|
121
137
|
"RequiresConstraintDefinition",
|
|
122
138
|
"ReverseDirectRelationProperty",
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
from abc import ABC
|
|
2
|
+
from typing import Any, Generic, Literal
|
|
3
|
+
|
|
4
|
+
from pydantic import JsonValue, field_serializer, field_validator
|
|
5
|
+
|
|
6
|
+
from cognite_toolkit._cdf_tk.client.data_classes.base import (
|
|
7
|
+
BaseModelObject,
|
|
8
|
+
RequestResource,
|
|
9
|
+
ResponseResource,
|
|
10
|
+
T_RequestResource,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from ._references import ContainerReference, EdgeReference, NodeReference, ViewReference
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class InstanceDefinition(BaseModelObject, ABC):
|
|
17
|
+
"""Base class for node and edge instances."""
|
|
18
|
+
|
|
19
|
+
instance_type: str # "node" | "edge"
|
|
20
|
+
space: str
|
|
21
|
+
external_id: str
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class InstanceSource(BaseModelObject):
|
|
25
|
+
source: ViewReference | ContainerReference
|
|
26
|
+
properties: dict[str, JsonValue] | None = None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class InstanceRequestDefinition(InstanceDefinition, RequestResource, ABC):
|
|
30
|
+
existing_version: int | None = None
|
|
31
|
+
sources: list[InstanceSource]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class InstanceResponseDefinition(InstanceDefinition, ResponseResource, Generic[T_RequestResource], ABC):
|
|
35
|
+
version: int
|
|
36
|
+
created_time: int
|
|
37
|
+
last_updated_time: int
|
|
38
|
+
deleted_time: int | None = None
|
|
39
|
+
properties: dict[ViewReference | ContainerReference, dict[str, JsonValue]] | None = None
|
|
40
|
+
|
|
41
|
+
@field_validator("properties", mode="before")
|
|
42
|
+
def parse_reference(cls, value: Any) -> Any:
|
|
43
|
+
if not isinstance(value, dict):
|
|
44
|
+
return value
|
|
45
|
+
parsed: dict[ViewReference | ContainerReference, dict[str, Any]] = {}
|
|
46
|
+
for space, inner_dict in value.items():
|
|
47
|
+
if not isinstance(inner_dict, dict) or not isinstance(space, str):
|
|
48
|
+
raise ValueError(
|
|
49
|
+
f"Invalid properties format expected dict[str, dict[...]], got: dict[{type(space).__name__}, {type(inner_dict).__name__}]"
|
|
50
|
+
)
|
|
51
|
+
for view_or_container_identifier, prop in inner_dict.items():
|
|
52
|
+
if not isinstance(view_or_container_identifier, str):
|
|
53
|
+
raise ValueError(
|
|
54
|
+
"Invalid properties format expected dict[str, dict[str, ...]]], "
|
|
55
|
+
f"got: dict[{type(space).__name__}, "
|
|
56
|
+
f"dict[{type(view_or_container_identifier).__name__}, ...]]"
|
|
57
|
+
)
|
|
58
|
+
source_ref: ViewReference | ContainerReference
|
|
59
|
+
if "/" in view_or_container_identifier:
|
|
60
|
+
external_id, version = view_or_container_identifier.split("/", 1)
|
|
61
|
+
source_ref = ViewReference(space=space, external_id=external_id, version=version)
|
|
62
|
+
else:
|
|
63
|
+
source_ref = ContainerReference(space=space, external_id=view_or_container_identifier)
|
|
64
|
+
parsed[source_ref] = prop
|
|
65
|
+
return parsed
|
|
66
|
+
|
|
67
|
+
@field_serializer("properties", mode="plain")
|
|
68
|
+
def serialize_properties(self, value: dict[ViewReference | ContainerReference, dict[str, Any]] | None) -> Any:
|
|
69
|
+
if value is None:
|
|
70
|
+
return None
|
|
71
|
+
serialized: dict[str, dict[str, Any]] = {}
|
|
72
|
+
for source_ref, props in value.items():
|
|
73
|
+
space = source_ref.space
|
|
74
|
+
if space not in serialized:
|
|
75
|
+
serialized[space] = {}
|
|
76
|
+
if isinstance(source_ref, ViewReference):
|
|
77
|
+
identifier = f"{source_ref.external_id}/{source_ref.version}"
|
|
78
|
+
else:
|
|
79
|
+
identifier = source_ref.external_id
|
|
80
|
+
serialized[space][identifier] = props
|
|
81
|
+
return serialized
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class NodeRequest(InstanceRequestDefinition):
|
|
85
|
+
"""A node request resource."""
|
|
86
|
+
|
|
87
|
+
instance_type: Literal["node"] = "node"
|
|
88
|
+
type: NodeReference | None = None
|
|
89
|
+
|
|
90
|
+
def as_id(self) -> NodeReference:
|
|
91
|
+
return NodeReference(space=self.space, external_id=self.external_id)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class EdgeRequest(InstanceRequestDefinition):
|
|
95
|
+
"""An edge request resource."""
|
|
96
|
+
|
|
97
|
+
instance_type: Literal["edge"] = "edge"
|
|
98
|
+
type: NodeReference
|
|
99
|
+
start_node: NodeReference
|
|
100
|
+
end_node: NodeReference
|
|
101
|
+
|
|
102
|
+
def as_id(self) -> EdgeReference:
|
|
103
|
+
return EdgeReference(space=self.space, external_id=self.external_id)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class NodeResponse(InstanceResponseDefinition[NodeRequest]):
|
|
107
|
+
"""A node response from the API."""
|
|
108
|
+
|
|
109
|
+
instance_type: Literal["node"] = "node"
|
|
110
|
+
type: NodeReference | None = None
|
|
111
|
+
|
|
112
|
+
def as_id(self) -> NodeReference:
|
|
113
|
+
return NodeReference(space=self.space, external_id=self.external_id)
|
|
114
|
+
|
|
115
|
+
def as_request_resource(self) -> NodeRequest:
|
|
116
|
+
dumped = self.dump()
|
|
117
|
+
if self.properties:
|
|
118
|
+
dumped["sources"] = [
|
|
119
|
+
InstanceSource(source=source_ref, properties=props) for source_ref, props in self.properties.items()
|
|
120
|
+
]
|
|
121
|
+
dumped["existingVersion"] = dumped.pop("version", None)
|
|
122
|
+
return NodeRequest.model_validate(dumped, extra="ignore")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class EdgeResponse(InstanceResponseDefinition[EdgeRequest]):
|
|
126
|
+
"""An edge response from the API."""
|
|
127
|
+
|
|
128
|
+
instance_type: Literal["edge"] = "edge"
|
|
129
|
+
type: NodeReference
|
|
130
|
+
start_node: NodeReference
|
|
131
|
+
end_node: NodeReference
|
|
132
|
+
|
|
133
|
+
def as_id(self) -> EdgeReference:
|
|
134
|
+
return EdgeReference(space=self.space, external_id=self.external_id)
|
|
135
|
+
|
|
136
|
+
def as_request_resource(self) -> EdgeRequest:
|
|
137
|
+
dumped = self.dump()
|
|
138
|
+
if self.properties:
|
|
139
|
+
dumped["sources"] = [
|
|
140
|
+
InstanceSource(source=source_ref, properties=props) for source_ref, props in self.properties.items()
|
|
141
|
+
]
|
|
142
|
+
dumped["existingVersion"] = dumped.pop("version", None)
|
|
143
|
+
return EdgeRequest.model_validate(dumped, extra="ignore")
|
|
@@ -48,6 +48,14 @@ class NodeReference(Identifier):
|
|
|
48
48
|
return f"{self.space}:{self.external_id}"
|
|
49
49
|
|
|
50
50
|
|
|
51
|
+
class EdgeReference(Identifier):
|
|
52
|
+
space: str
|
|
53
|
+
external_id: str
|
|
54
|
+
|
|
55
|
+
def __str__(self) -> str:
|
|
56
|
+
return f"{self.space}:{self.external_id}"
|
|
57
|
+
|
|
58
|
+
|
|
51
59
|
class ContainerDirectReference(Identifier):
|
|
52
60
|
source: ContainerReference
|
|
53
61
|
identifier: str
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from typing import ClassVar
|
|
2
|
+
|
|
3
|
+
from cognite_toolkit._cdf_tk.client.data_classes.base import (
|
|
4
|
+
BaseModelObject,
|
|
5
|
+
RequestUpdateable,
|
|
6
|
+
ResponseResource,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
from .identifiers import ExternalId
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DataSet(BaseModelObject):
|
|
13
|
+
external_id: str | None = None
|
|
14
|
+
name: str | None = None
|
|
15
|
+
description: str | None = None
|
|
16
|
+
metadata: dict[str, str] | None = None
|
|
17
|
+
write_protected: bool = False
|
|
18
|
+
|
|
19
|
+
def as_id(self) -> ExternalId:
|
|
20
|
+
if self.external_id is None:
|
|
21
|
+
raise ValueError("Cannot convert DataSet to ExternalId when external_id is None")
|
|
22
|
+
return ExternalId(external_id=self.external_id)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class DataSetRequest(DataSet, RequestUpdateable):
|
|
26
|
+
container_fields: ClassVar[frozenset[str]] = frozenset({"metadata"})
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class DataSetResponse(DataSet, ResponseResource[DataSetRequest]):
|
|
30
|
+
id: int
|
|
31
|
+
created_time: int
|
|
32
|
+
last_updated_time: int
|
|
33
|
+
|
|
34
|
+
def as_request_resource(self) -> DataSetRequest:
|
|
35
|
+
return DataSetRequest.model_validate(self.dump(), extra="ignore")
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from typing import ClassVar
|
|
2
|
+
|
|
3
|
+
from cognite_toolkit._cdf_tk.client.data_classes.base import (
|
|
4
|
+
BaseModelObject,
|
|
5
|
+
RequestUpdateable,
|
|
6
|
+
ResponseResource,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
from .identifiers import ExternalId
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class RawTable(BaseModelObject):
|
|
13
|
+
db_name: str
|
|
14
|
+
table_name: str
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Contact(BaseModelObject):
|
|
18
|
+
name: str | None = None
|
|
19
|
+
email: str | None = None
|
|
20
|
+
role: str | None = None
|
|
21
|
+
send_notification: bool | None = None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class NotificationConfig(BaseModelObject):
|
|
25
|
+
allowed_not_seen_range_in_minutes: int | None = None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ExtractionPipeline(BaseModelObject):
|
|
29
|
+
external_id: str
|
|
30
|
+
name: str
|
|
31
|
+
description: str | None = None
|
|
32
|
+
data_set_id: int
|
|
33
|
+
raw_tables: list[RawTable] | None = None
|
|
34
|
+
schedule: str | None = None
|
|
35
|
+
contacts: list[Contact] | None = None
|
|
36
|
+
metadata: dict[str, str] | None = None
|
|
37
|
+
source: str | None = None
|
|
38
|
+
documentation: str | None = None
|
|
39
|
+
notification_config: NotificationConfig | None = None
|
|
40
|
+
created_by: str | None = None
|
|
41
|
+
|
|
42
|
+
def as_id(self) -> ExternalId:
|
|
43
|
+
return ExternalId(external_id=self.external_id)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ExtractionPipelineRequest(ExtractionPipeline, RequestUpdateable):
|
|
47
|
+
container_fields: ClassVar[frozenset[str]] = frozenset({"raw_tables", "contacts", "metadata"})
|
|
48
|
+
non_nullable_fields: ClassVar[frozenset[str]] = frozenset(
|
|
49
|
+
{"documentation", "source", "notification_config", "schedule", "description"}
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class ExtractionPipelineResponse(ExtractionPipeline, ResponseResource[ExtractionPipelineRequest]):
|
|
54
|
+
id: int
|
|
55
|
+
created_time: int
|
|
56
|
+
last_updated_time: int
|
|
57
|
+
|
|
58
|
+
def as_request_resource(self) -> ExtractionPipelineRequest:
|
|
59
|
+
return ExtractionPipelineRequest.model_validate(self.dump(), extra="ignore")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from typing import ClassVar, Literal
|
|
2
2
|
|
|
3
|
-
from pydantic import JsonValue
|
|
3
|
+
from pydantic import Field, JsonValue
|
|
4
4
|
|
|
5
5
|
from cognite_toolkit._cdf_tk.client.data_classes.base import BaseModelObject, RequestUpdateable, ResponseResource
|
|
6
6
|
|
|
@@ -32,6 +32,10 @@ class FileMetadata(BaseModelObject):
|
|
|
32
32
|
class FileMetadataRequest(FileMetadata, RequestUpdateable):
|
|
33
33
|
container_fields: ClassVar[frozenset[str]] = frozenset({"metadata", "labels", "asset_ids", "security_categories"})
|
|
34
34
|
non_nullable_fields: ClassVar[frozenset[str]] = frozenset({"asset_ids", "security_categories"})
|
|
35
|
+
# This field is not part of the request when creating or updating a resource
|
|
36
|
+
# but we added it here for convenience so that it is available when converting
|
|
37
|
+
# from response to request.
|
|
38
|
+
instance_id: NodeReference | None = Field(default=None, exclude=True)
|
|
35
39
|
|
|
36
40
|
|
|
37
41
|
class FileMetadataResponse(FileMetadata, ResponseResource[FileMetadataRequest]):
|
|
@@ -41,6 +45,8 @@ class FileMetadataResponse(FileMetadata, ResponseResource[FileMetadataRequest]):
|
|
|
41
45
|
uploaded: bool
|
|
42
46
|
id: int
|
|
43
47
|
instance_id: NodeReference | None = None
|
|
48
|
+
# This field is required in the upload endpoint response, but not in any other file metadata response
|
|
49
|
+
upload_url: str | None = None
|
|
44
50
|
|
|
45
51
|
def as_request_resource(self) -> FileMetadataRequest:
|
|
46
52
|
return FileMetadataRequest.model_validate(self.dump(), extra="ignore")
|