cognite-toolkit 0.7.27__py3-none-any.whl → 0.7.29__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 +10 -3
- cognite_toolkit/_cdf_tk/client/api/extended_functions.py +6 -9
- cognite_toolkit/_cdf_tk/client/data_classes/charts.py +3 -3
- cognite_toolkit/_cdf_tk/client/data_classes/charts_data.py +94 -232
- cognite_toolkit/_cdf_tk/commands/_migrate/data_mapper.py +3 -5
- cognite_toolkit/_cdf_tk/commands/_migrate/migration_io.py +15 -0
- cognite_toolkit/_cdf_tk/cruds/_resource_cruds/function.py +2 -2
- cognite_toolkit/_cdf_tk/storageio/_annotations.py +2 -1
- cognite_toolkit/_cdf_tk/utils/http_client/_client.py +126 -11
- cognite_toolkit/_cdf_tk/utils/http_client/_data_classes2.py +97 -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.27.dist-info → cognite_toolkit-0.7.29.dist-info}/METADATA +1 -1
- {cognite_toolkit-0.7.27.dist-info → cognite_toolkit-0.7.29.dist-info}/RECORD +18 -17
- {cognite_toolkit-0.7.27.dist-info → cognite_toolkit-0.7.29.dist-info}/WHEEL +0 -0
- {cognite_toolkit-0.7.27.dist-info → cognite_toolkit-0.7.29.dist-info}/entry_points.txt +0 -0
|
@@ -35,19 +35,26 @@ class ToolAPI:
|
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
class ToolkitClient(CogniteClient):
|
|
38
|
-
def __init__(
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
config: ToolkitClientConfig | None = None,
|
|
41
|
+
enable_set_pending_ids: bool = False,
|
|
42
|
+
console: Console | None = None,
|
|
43
|
+
) -> None:
|
|
39
44
|
super().__init__(config=config)
|
|
40
45
|
http_client = HTTPClient(self.config)
|
|
41
46
|
self.http_client = http_client
|
|
42
47
|
toolkit_config = ToolkitClientConfig.from_client_config(self.config)
|
|
43
|
-
self.console = Console()
|
|
48
|
+
self.console = console or Console()
|
|
44
49
|
self.tool = ToolAPI(http_client, self.console)
|
|
45
50
|
self.search = SearchAPI(self._config, self._API_VERSION, self)
|
|
46
51
|
self.robotics = RoboticsAPI(self._config, self._API_VERSION, self)
|
|
47
52
|
self.dml = DMLAPI(self._config, self._API_VERSION, self)
|
|
48
53
|
self.verify = VerifyAPI(self._config, self._API_VERSION, self)
|
|
49
54
|
self.lookup = LookUpGroup(self._config, self._API_VERSION, self, self.console)
|
|
50
|
-
self.functions: ExtendedFunctionsAPI = ExtendedFunctionsAPI(
|
|
55
|
+
self.functions: ExtendedFunctionsAPI = ExtendedFunctionsAPI(
|
|
56
|
+
toolkit_config, self._API_VERSION, self, self.console
|
|
57
|
+
)
|
|
51
58
|
self.data_modeling: ExtendedDataModelingAPI = ExtendedDataModelingAPI(self._config, self._API_VERSION, self)
|
|
52
59
|
if enable_set_pending_ids:
|
|
53
60
|
self.time_series: ExtendedTimeSeriesAPI = ExtendedTimeSeriesAPI(self._config, self._API_VERSION, self)
|
|
@@ -17,15 +17,16 @@ class ExtendedFunctionsAPI(FunctionsAPI):
|
|
|
17
17
|
config: ToolkitClientConfig,
|
|
18
18
|
api_version: str | None,
|
|
19
19
|
cognite_client: CogniteClient,
|
|
20
|
+
console: Console | None = None,
|
|
20
21
|
) -> None:
|
|
21
22
|
"""
|
|
22
23
|
Extended Functions API to include custom headers and payload preparation.
|
|
23
24
|
"""
|
|
24
25
|
super().__init__(config, api_version, cognite_client)
|
|
25
26
|
self._toolkit_config = config
|
|
26
|
-
self._toolkit_http_client = HTTPClient(config, max_retries=global_config.max_retries)
|
|
27
|
+
self._toolkit_http_client = HTTPClient(config, max_retries=global_config.max_retries, console=console)
|
|
27
28
|
|
|
28
|
-
def create_with_429_retry(self, function: FunctionWrite
|
|
29
|
+
def create_with_429_retry(self, function: FunctionWrite) -> Function:
|
|
29
30
|
"""Create a function with manual retry handling for 429 Too Many Requests responses.
|
|
30
31
|
|
|
31
32
|
This method is a workaround for scenarios where the function creation API is temporarily unavailable
|
|
@@ -43,16 +44,13 @@ class ExtendedFunctionsAPI(FunctionsAPI):
|
|
|
43
44
|
endpoint_url=self._toolkit_config.create_api_url("/functions"),
|
|
44
45
|
method="POST",
|
|
45
46
|
body_content={"items": [function.dump(camel_case=True)]},
|
|
46
|
-
)
|
|
47
|
-
console=console,
|
|
47
|
+
)
|
|
48
48
|
)
|
|
49
49
|
result.raise_for_status()
|
|
50
50
|
# We assume the API response is one item on a successful creation
|
|
51
51
|
return Function._load(result.get_first_body()["items"][0], cognite_client=self._cognite_client) # type: ignore[arg-type,index]
|
|
52
52
|
|
|
53
|
-
def delete_with_429_retry(
|
|
54
|
-
self, external_id: SequenceNotStr[str], ignore_unknown_ids: bool = False, console: Console | None = None
|
|
55
|
-
) -> None:
|
|
53
|
+
def delete_with_429_retry(self, external_id: SequenceNotStr[str], ignore_unknown_ids: bool = False) -> None:
|
|
56
54
|
"""Delete one or more functions with retry handling for 429 Too Many Requests responses.
|
|
57
55
|
|
|
58
56
|
This method is an improvement over the standard delete method in the FunctionsAPI.
|
|
@@ -77,7 +75,6 @@ class ExtendedFunctionsAPI(FunctionsAPI):
|
|
|
77
75
|
endpoint_url=self._toolkit_config.create_api_url("/functions/delete"),
|
|
78
76
|
method="POST",
|
|
79
77
|
body_content=body_content,
|
|
80
|
-
)
|
|
81
|
-
console=console,
|
|
78
|
+
)
|
|
82
79
|
).raise_for_status()
|
|
83
80
|
return None
|
|
@@ -36,7 +36,7 @@ class ChartCore(WriteableCogniteResource["ChartWrite"], ABC):
|
|
|
36
36
|
def dump(self, camel_case: bool = True) -> dict[str, Any]:
|
|
37
37
|
"""Convert the chart to a dictionary representation."""
|
|
38
38
|
output = super().dump(camel_case=camel_case)
|
|
39
|
-
output["data"] = self.data.
|
|
39
|
+
output["data"] = self.data.model_dump(mode="json", by_alias=camel_case, exclude_unset=True)
|
|
40
40
|
return output
|
|
41
41
|
|
|
42
42
|
|
|
@@ -58,7 +58,7 @@ class ChartWrite(ChartCore):
|
|
|
58
58
|
return cls(
|
|
59
59
|
external_id=resource["externalId"],
|
|
60
60
|
visibility=resource["visibility"],
|
|
61
|
-
data=ChartData._load(resource["data"]
|
|
61
|
+
data=ChartData._load(resource["data"]),
|
|
62
62
|
)
|
|
63
63
|
|
|
64
64
|
|
|
@@ -98,7 +98,7 @@ class Chart(ChartCore):
|
|
|
98
98
|
created_time=resource["createdTime"],
|
|
99
99
|
last_updated_time=resource["lastUpdatedTime"],
|
|
100
100
|
visibility=resource["visibility"],
|
|
101
|
-
data=ChartData._load(resource["data"]
|
|
101
|
+
data=ChartData._load(resource["data"]),
|
|
102
102
|
owner_id=resource["ownerId"],
|
|
103
103
|
)
|
|
104
104
|
|
|
@@ -1,70 +1,30 @@
|
|
|
1
|
-
import sys
|
|
2
|
-
from dataclasses import dataclass, field, fields
|
|
3
|
-
from functools import lru_cache
|
|
4
1
|
from typing import Any
|
|
5
2
|
|
|
6
|
-
from cognite.client import CogniteClient
|
|
7
|
-
from cognite.client.data_classes._base import CogniteObject
|
|
8
3
|
from cognite.client.data_classes.data_modeling import NodeId, ViewId
|
|
9
|
-
from
|
|
4
|
+
from pydantic import JsonValue, field_serializer, field_validator
|
|
10
5
|
|
|
11
|
-
|
|
12
|
-
from typing import Self
|
|
13
|
-
else:
|
|
14
|
-
from typing_extensions import Self
|
|
6
|
+
from .base import BaseModelObject
|
|
15
7
|
|
|
16
8
|
|
|
17
|
-
|
|
18
|
-
class ChartObject(CogniteObject):
|
|
19
|
-
# ChartObjects are used in the frontend and the backend does not do any validation of these fields.
|
|
20
|
-
# Therefore, to ensure that we do not lose any data, we store unknown fields in a separate dictionary.
|
|
21
|
-
# This allows unknown fields to be preserved when loading and dumping ChartObjects
|
|
22
|
-
# (serialization and deserialization).
|
|
23
|
-
_unknown_fields: dict[str, object] | None = field(default=None, init=False, repr=False)
|
|
24
|
-
|
|
25
|
-
@classmethod
|
|
26
|
-
def _load(cls, resource: dict[str, Any], cognite_client: CogniteClient | None = None) -> Self:
|
|
27
|
-
"""Load a ChartObject from a dictionary."""
|
|
28
|
-
instance = super()._load(resource, cognite_client=cognite_client)
|
|
29
|
-
instance._unknown_fields = {k: v for k, v in resource.items() if k not in cls._known_camel_case_props()}
|
|
30
|
-
return instance
|
|
31
|
-
|
|
32
|
-
@classmethod
|
|
33
|
-
@lru_cache(maxsize=1)
|
|
34
|
-
def _known_camel_case_props(cls) -> set[str]:
|
|
35
|
-
return {to_camel_case(f.name) for f in fields(cls)}
|
|
36
|
-
|
|
37
|
-
def dump(self, camel_case: bool = True) -> dict[str, Any]:
|
|
38
|
-
"""Dump the ChartObject to a dictionary."""
|
|
39
|
-
data = super().dump(camel_case=camel_case)
|
|
40
|
-
if self._unknown_fields:
|
|
41
|
-
data.update(self._unknown_fields)
|
|
42
|
-
return data
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
@dataclass
|
|
46
|
-
class UserInfo(ChartObject):
|
|
9
|
+
class UserInfo(BaseModelObject):
|
|
47
10
|
id: str | None = None
|
|
48
11
|
email: str | None = None
|
|
49
12
|
display_name: str | None = None
|
|
50
13
|
|
|
51
14
|
|
|
52
|
-
|
|
53
|
-
class ChartSettings(ChartObject):
|
|
15
|
+
class ChartSettings(BaseModelObject):
|
|
54
16
|
show_y_axis: bool = True
|
|
55
17
|
show_min_max: bool = True
|
|
56
18
|
show_gridlines: bool = True
|
|
57
19
|
merge_units: bool = False
|
|
58
20
|
|
|
59
21
|
|
|
60
|
-
|
|
61
|
-
class ThresholdFilter(ChartObject):
|
|
22
|
+
class ThresholdFilter(BaseModelObject):
|
|
62
23
|
min_unit: str | None = None
|
|
63
24
|
max_unit: str | None = None
|
|
64
25
|
|
|
65
26
|
|
|
66
|
-
|
|
67
|
-
class ChartCall(ChartObject):
|
|
27
|
+
class ChartCall(BaseModelObject):
|
|
68
28
|
id: str | None = None
|
|
69
29
|
hash: int | None = None
|
|
70
30
|
call_id: str | None = None
|
|
@@ -72,182 +32,143 @@ class ChartCall(ChartObject):
|
|
|
72
32
|
status: str | None = None
|
|
73
33
|
|
|
74
34
|
|
|
75
|
-
|
|
76
|
-
class SubSetting(ChartObject):
|
|
35
|
+
class SubSetting(BaseModelObject):
|
|
77
36
|
auto_align: bool | None = None
|
|
78
37
|
|
|
79
38
|
|
|
80
|
-
|
|
81
|
-
|
|
39
|
+
class ChartPosition(BaseModelObject):
|
|
40
|
+
x: float | None = None
|
|
41
|
+
y: float | None = None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class FlowElement(BaseModelObject):
|
|
82
45
|
id: str | None = None
|
|
83
46
|
type: str | None = None
|
|
84
|
-
position:
|
|
85
|
-
data:
|
|
47
|
+
position: ChartPosition | None = None
|
|
48
|
+
data: JsonValue | None = None
|
|
49
|
+
source: str | None = None
|
|
50
|
+
target: str | None = None
|
|
51
|
+
source_handle: str | None = None
|
|
52
|
+
target_handle: str | None = None
|
|
86
53
|
|
|
87
54
|
|
|
88
|
-
|
|
89
|
-
class Flow(ChartObject):
|
|
55
|
+
class Flow(BaseModelObject):
|
|
90
56
|
zoom: float | None = None
|
|
91
57
|
elements: list[FlowElement] | None = None
|
|
92
58
|
position: tuple[float | None, float | None] | None = None
|
|
93
59
|
|
|
94
|
-
def dump(self, camel_case: bool = True) -> dict[str, Any]:
|
|
95
|
-
data = super().dump(camel_case=camel_case)
|
|
96
|
-
if self.elements:
|
|
97
|
-
data["elements"] = [el.dump(camel_case=camel_case) for el in self.elements]
|
|
98
|
-
return data
|
|
99
60
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
instance = super()._load(resource, cognite_client=cognite_client)
|
|
104
|
-
if "elements" in resource:
|
|
105
|
-
instance.elements = [FlowElement._load(el, cognite_client=cognite_client) for el in resource["elements"]]
|
|
106
|
-
return instance
|
|
61
|
+
class ChartElement(BaseModelObject):
|
|
62
|
+
id: str | None = None
|
|
63
|
+
type: str | None = None
|
|
107
64
|
|
|
108
65
|
|
|
109
|
-
|
|
110
|
-
class ChartSource(ChartObject):
|
|
111
|
-
type: str | None = None
|
|
112
|
-
id: str | None = None
|
|
66
|
+
class ChartSource(ChartElement): ...
|
|
113
67
|
|
|
114
68
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
name: str | None = None
|
|
69
|
+
class ChartCoreTimeseries(ChartElement):
|
|
70
|
+
node_reference: NodeId | None = None
|
|
71
|
+
view_reference: ViewId | None = None
|
|
72
|
+
display_mode: str | None = None
|
|
120
73
|
color: str | None = None
|
|
74
|
+
created_at: int | None = None
|
|
121
75
|
enabled: bool | None = None
|
|
122
|
-
line_weight: float | None = None
|
|
123
|
-
line_style: str | None = None
|
|
124
76
|
interpolation: str | None = None
|
|
125
|
-
|
|
77
|
+
line_style: str | None = None
|
|
78
|
+
line_weight: float | None = None
|
|
79
|
+
name: str | None = None
|
|
126
80
|
preferred_unit: str | None = None
|
|
127
|
-
|
|
128
|
-
range: tuple[float | None, float | None] | None = None
|
|
129
|
-
description: str | None = None
|
|
81
|
+
range: list[float | None] | None = None
|
|
130
82
|
|
|
83
|
+
@field_serializer("node_reference", when_used="always")
|
|
84
|
+
def serialize_node_reference(self, node_reference: NodeId | None) -> dict[str, Any] | None:
|
|
85
|
+
if node_reference:
|
|
86
|
+
return node_reference.dump(include_instance_type=False)
|
|
87
|
+
return None
|
|
131
88
|
|
|
132
|
-
@
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
89
|
+
@field_serializer("view_reference", when_used="always")
|
|
90
|
+
def serialize_view_reference(self, view_reference: ViewId | None) -> dict[str, Any] | None:
|
|
91
|
+
if view_reference:
|
|
92
|
+
return view_reference.dump(include_type=False)
|
|
93
|
+
return None
|
|
137
94
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
key = "viewReference" if camel_case else "view_reference"
|
|
145
|
-
data[key] = self.view_reference.dump(include_type=False)
|
|
146
|
-
return data
|
|
95
|
+
@field_validator("node_reference", mode="before")
|
|
96
|
+
@classmethod
|
|
97
|
+
def validate_node_reference(cls, value: Any) -> NodeId | None:
|
|
98
|
+
if value is None or isinstance(value, NodeId):
|
|
99
|
+
return value
|
|
100
|
+
return NodeId.load(value)
|
|
147
101
|
|
|
102
|
+
@field_validator("view_reference", mode="before")
|
|
148
103
|
@classmethod
|
|
149
|
-
def
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
104
|
+
def validate_view_reference(cls, value: Any) -> ViewId | None:
|
|
105
|
+
if value is None or isinstance(value, ViewId):
|
|
106
|
+
return value
|
|
107
|
+
return ViewId.load(value)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class ChartTimeseries(ChartElement):
|
|
111
|
+
color: str | None = None
|
|
112
|
+
created_at: int | None = None
|
|
113
|
+
enabled: bool | None = None
|
|
114
|
+
interpolation: str | None = None
|
|
115
|
+
line_style: str | None = None
|
|
116
|
+
line_weight: float | None = None
|
|
117
|
+
name: str | None = None
|
|
118
|
+
preferred_unit: str | None = None
|
|
119
|
+
range: list[float | None] | None = None
|
|
120
|
+
unit: str | None = None
|
|
161
121
|
ts_id: int | None = None
|
|
162
122
|
ts_external_id: str | None = None
|
|
163
123
|
display_mode: str | None = None
|
|
164
124
|
original_unit: str | None = None
|
|
125
|
+
description: str | None = None
|
|
165
126
|
|
|
166
127
|
|
|
167
|
-
|
|
168
|
-
class ChartWorkflow(BaseChartElement):
|
|
128
|
+
class ChartWorkflow(ChartElement):
|
|
169
129
|
version: str | None = None
|
|
130
|
+
name: str | None = None
|
|
131
|
+
color: str | None = None
|
|
132
|
+
enabled: bool | None = None
|
|
133
|
+
line_weight: float | None = None
|
|
134
|
+
line_style: str | None = None
|
|
135
|
+
interpolation: str | None = None
|
|
136
|
+
unit: str | None = None
|
|
137
|
+
preferred_unit: str | None = None
|
|
138
|
+
range: list[float | None] | None = None
|
|
139
|
+
created_at: int | None = None
|
|
170
140
|
settings: SubSetting | None = None
|
|
171
141
|
flow: Flow | None = None
|
|
172
142
|
calls: list[ChartCall] | None = None
|
|
173
143
|
|
|
174
|
-
def dump(self, camel_case: bool = True) -> dict[str, Any]:
|
|
175
|
-
data = super().dump(camel_case=camel_case)
|
|
176
|
-
if self.settings:
|
|
177
|
-
data["settings"] = self.settings.dump(camel_case=camel_case)
|
|
178
|
-
if self.flow:
|
|
179
|
-
data["flow"] = self.flow.dump(camel_case=camel_case)
|
|
180
|
-
if self.calls:
|
|
181
|
-
data["calls"] = [c.dump(camel_case=camel_case) for c in self.calls]
|
|
182
|
-
return data
|
|
183
144
|
|
|
184
|
-
|
|
185
|
-
def _load(cls, resource: dict[str, Any], cognite_client: CogniteClient | None = None) -> Self:
|
|
186
|
-
"""Load a ChartWorkflow object from a dictionary."""
|
|
187
|
-
instance = super()._load(resource, cognite_client=cognite_client)
|
|
188
|
-
if "settings" in resource:
|
|
189
|
-
instance.settings = SubSetting._load(resource["settings"], cognite_client=cognite_client)
|
|
190
|
-
if "flow" in resource:
|
|
191
|
-
instance.flow = Flow._load(resource["flow"], cognite_client=cognite_client)
|
|
192
|
-
if "calls" in resource:
|
|
193
|
-
instance.calls = [ChartCall._load(call, cognite_client=cognite_client) for call in resource["calls"]]
|
|
194
|
-
return instance
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
@dataclass
|
|
198
|
-
class ChartThreshold(BaseChartElement):
|
|
145
|
+
class ChartThreshold(ChartElement):
|
|
199
146
|
visible: bool | None = None
|
|
147
|
+
name: str | None = None
|
|
200
148
|
source_id: str | None = None
|
|
201
149
|
upper_limit: float | None = None
|
|
202
150
|
filter: ThresholdFilter | None = None
|
|
203
151
|
calls: list[ChartCall] | None = None
|
|
204
152
|
|
|
205
|
-
def dump(self, camel_case: bool = True) -> dict[str, Any]:
|
|
206
|
-
data = super().dump(camel_case=camel_case)
|
|
207
|
-
if self.filter:
|
|
208
|
-
data["filter"] = self.filter.dump(camel_case=camel_case)
|
|
209
|
-
if self.calls:
|
|
210
|
-
data["calls"] = [c.dump(camel_case=camel_case) for c in self.calls]
|
|
211
|
-
return data
|
|
212
153
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
class ChartScheduledCalculation(BaseChartElement):
|
|
154
|
+
class ChartScheduledCalculation(ChartElement):
|
|
155
|
+
color: str | None = None
|
|
156
|
+
created_at: int | None = None
|
|
157
|
+
description: str | None = None
|
|
158
|
+
enabled: bool | None = None
|
|
159
|
+
interpolation: str | None = None
|
|
160
|
+
line_style: str | None = None
|
|
161
|
+
line_weight: float | None = None
|
|
162
|
+
name: str | None = None
|
|
163
|
+
preferred_unit: str | None = None
|
|
164
|
+
range: list[float | None] | None = None
|
|
165
|
+
unit: str | None = None
|
|
226
166
|
version: str | None = None
|
|
227
167
|
settings: SubSetting | None = None
|
|
228
168
|
flow: Flow | None = None
|
|
229
169
|
|
|
230
|
-
def dump(self, camel_case: bool = True) -> dict[str, Any]:
|
|
231
|
-
data = super().dump(camel_case=camel_case)
|
|
232
|
-
if self.settings:
|
|
233
|
-
data["settings"] = self.settings.dump(camel_case=camel_case)
|
|
234
|
-
if self.flow:
|
|
235
|
-
data["flow"] = self.flow.dump(camel_case=camel_case)
|
|
236
|
-
return data
|
|
237
170
|
|
|
238
|
-
|
|
239
|
-
def _load(cls, resource: dict[str, Any], cognite_client: CogniteClient | None = None) -> Self:
|
|
240
|
-
"""Load a ChartScheduledCalculation object from a dictionary."""
|
|
241
|
-
instance = super()._load(resource, cognite_client=cognite_client)
|
|
242
|
-
if "settings" in resource:
|
|
243
|
-
instance.settings = SubSetting._load(resource["settings"], cognite_client=cognite_client)
|
|
244
|
-
if "flow" in resource:
|
|
245
|
-
instance.flow = Flow._load(resource["flow"], cognite_client=cognite_client)
|
|
246
|
-
return instance
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
@dataclass
|
|
250
|
-
class ChartData(ChartObject):
|
|
171
|
+
class ChartData(BaseModelObject):
|
|
251
172
|
version: int | None = None
|
|
252
173
|
name: str | None = None
|
|
253
174
|
date_from: str | None = None
|
|
@@ -261,62 +182,3 @@ class ChartData(ChartObject):
|
|
|
261
182
|
threshold_collection: list[ChartThreshold] | None = None
|
|
262
183
|
scheduled_calculation_collection: list[ChartScheduledCalculation] | None = None
|
|
263
184
|
settings: ChartSettings | None = None
|
|
264
|
-
|
|
265
|
-
def dump(self, camel_case: bool = True) -> dict[str, Any]:
|
|
266
|
-
"""Dump the ChartData object to a dictionary."""
|
|
267
|
-
data = super().dump(camel_case=camel_case)
|
|
268
|
-
list_attrs = [
|
|
269
|
-
"time_series_collection",
|
|
270
|
-
"core_timeseries_collection",
|
|
271
|
-
"workflow_collection",
|
|
272
|
-
"source_collection",
|
|
273
|
-
"threshold_collection",
|
|
274
|
-
"scheduled_calculation_collection",
|
|
275
|
-
]
|
|
276
|
-
for attr_name in list_attrs:
|
|
277
|
-
if collection := getattr(self, attr_name):
|
|
278
|
-
key = to_camel_case(attr_name) if camel_case else attr_name
|
|
279
|
-
data[key] = [item.dump(camel_case=camel_case) for item in collection]
|
|
280
|
-
|
|
281
|
-
single_attrs_map = {
|
|
282
|
-
"user_info": "userInfo",
|
|
283
|
-
"settings": "settings",
|
|
284
|
-
}
|
|
285
|
-
for attr_name, camel_key in single_attrs_map.items():
|
|
286
|
-
if item := getattr(self, attr_name):
|
|
287
|
-
key = camel_key if camel_case else attr_name
|
|
288
|
-
data[key] = item.dump(camel_case=camel_case)
|
|
289
|
-
return data
|
|
290
|
-
|
|
291
|
-
@classmethod
|
|
292
|
-
def _load(cls, resource: dict[str, Any], cognite_client: CogniteClient | None = None) -> Self:
|
|
293
|
-
"""Load a ChartData object from a dictionary."""
|
|
294
|
-
instance = super()._load(resource, cognite_client=cognite_client)
|
|
295
|
-
collections_map = [
|
|
296
|
-
("timeSeriesCollection", "time_series_collection", ChartTimeseries),
|
|
297
|
-
("coreTimeseriesCollection", "core_timeseries_collection", ChartCoreTimeseries),
|
|
298
|
-
("workflowCollection", "workflow_collection", ChartWorkflow),
|
|
299
|
-
("sourceCollection", "source_collection", ChartSource),
|
|
300
|
-
("thresholdCollection", "threshold_collection", ChartThreshold),
|
|
301
|
-
("scheduledCalculationCollection", "scheduled_calculation_collection", ChartScheduledCalculation),
|
|
302
|
-
]
|
|
303
|
-
for resource_key, attr_name, subclass in collections_map:
|
|
304
|
-
if resource_key in resource:
|
|
305
|
-
setattr(
|
|
306
|
-
instance,
|
|
307
|
-
attr_name,
|
|
308
|
-
[subclass._load(item, cognite_client=cognite_client) for item in resource[resource_key]], # type: ignore[attr-defined]
|
|
309
|
-
)
|
|
310
|
-
attribute_map = [
|
|
311
|
-
("userInfo", "user_info", UserInfo),
|
|
312
|
-
("settings", "settings", ChartSettings),
|
|
313
|
-
]
|
|
314
|
-
for resource_key, attr_name, subclass in attribute_map:
|
|
315
|
-
if resource_key in resource:
|
|
316
|
-
setattr(
|
|
317
|
-
instance,
|
|
318
|
-
attr_name,
|
|
319
|
-
subclass._load(resource[resource_key], cognite_client=cognite_client), # type: ignore[attr-defined]
|
|
320
|
-
)
|
|
321
|
-
|
|
322
|
-
return instance
|
|
@@ -210,16 +210,14 @@ class ChartMapper(DataMapper[ChartSelector, Chart, ChartWrite]):
|
|
|
210
210
|
def _create_new_timeseries_core(
|
|
211
211
|
self, ts_item: ChartTimeseries, node_id: NodeId, consumer_view_id: ViewId | None
|
|
212
212
|
) -> ChartCoreTimeseries:
|
|
213
|
-
dumped = ts_item.
|
|
214
|
-
for asset_centric_key in ["tsId", "tsExternalId", "originalUnit"]:
|
|
215
|
-
dumped.pop(asset_centric_key, None)
|
|
216
|
-
|
|
213
|
+
dumped = ts_item.model_dump(mode="json", by_alias=True, exclude_unset=True)
|
|
217
214
|
dumped["nodeReference"] = node_id
|
|
218
215
|
dumped["viewReference"] = consumer_view_id
|
|
219
216
|
new_uuid = str(uuid4())
|
|
220
217
|
dumped["id"] = new_uuid
|
|
221
218
|
dumped["type"] = "coreTimeseries"
|
|
222
|
-
|
|
219
|
+
# We ignore extra here to only include the fields that are shared between ChartTimeseries and ChartCoreTimeseries
|
|
220
|
+
core_timeseries = ChartCoreTimeseries.model_validate(dumped, extra="ignore")
|
|
223
221
|
return core_timeseries
|
|
224
222
|
|
|
225
223
|
def _get_node_id_consumer_view_id(self, ts_item: ChartTimeseries) -> tuple[NodeId | None, ViewId | None]:
|
|
@@ -231,6 +231,8 @@ class AnnotationMigrationIO(
|
|
|
231
231
|
CHUNK_SIZE = 1000
|
|
232
232
|
UPLOAD_ENDPOINT = InstanceIO.UPLOAD_ENDPOINT
|
|
233
233
|
|
|
234
|
+
SUPPORTED_ANNOTATION_TYPES = frozenset({"diagrams.AssetLink", "diagrams.FileLink"})
|
|
235
|
+
|
|
234
236
|
def __init__(
|
|
235
237
|
self,
|
|
236
238
|
client: ToolkitClient,
|
|
@@ -272,6 +274,10 @@ class AnnotationMigrationIO(
|
|
|
272
274
|
for data_chunk in self.annotation_io.stream_data(asset_centric_selector, limit):
|
|
273
275
|
mapping_list = AssetCentricMappingList[Annotation]([])
|
|
274
276
|
for resource in data_chunk.items:
|
|
277
|
+
if resource.annotation_type not in self.SUPPORTED_ANNOTATION_TYPES:
|
|
278
|
+
# This should not happen, as the annotation_io should already filter these out.
|
|
279
|
+
# This is just in case.
|
|
280
|
+
continue
|
|
275
281
|
mapping = AnnotationMapping(
|
|
276
282
|
instance_id=EdgeId(space=self.instance_space, external_id=f"annotation_{resource.id!r}"),
|
|
277
283
|
id=resource.id,
|
|
@@ -294,11 +300,15 @@ class AnnotationMigrationIO(
|
|
|
294
300
|
resources = self.client.annotations.retrieve_multiple(current_batch.get_ids())
|
|
295
301
|
resources_by_id = {resource.id: resource for resource in resources}
|
|
296
302
|
not_found = 0
|
|
303
|
+
incorrect_type_count = 0
|
|
297
304
|
for mapping in current_batch:
|
|
298
305
|
resource = resources_by_id.get(mapping.id)
|
|
299
306
|
if resource is None:
|
|
300
307
|
not_found += 1
|
|
301
308
|
continue
|
|
309
|
+
if resource.annotation_type not in self.SUPPORTED_ANNOTATION_TYPES:
|
|
310
|
+
incorrect_type_count += 1
|
|
311
|
+
continue
|
|
302
312
|
mapping.ingestion_view = self._get_mapping(mapping.ingestion_view, resource)
|
|
303
313
|
chunk.append(AssetCentricMapping(mapping=mapping, resource=resource))
|
|
304
314
|
if chunk:
|
|
@@ -308,6 +318,11 @@ class AnnotationMigrationIO(
|
|
|
308
318
|
MediumSeverityWarning(
|
|
309
319
|
f"Could not find {not_found} annotations referenced in the CSV file. They will be skipped during migration."
|
|
310
320
|
).print_warning(include_timestamp=True, console=self.client.console)
|
|
321
|
+
if incorrect_type_count:
|
|
322
|
+
MediumSeverityWarning(
|
|
323
|
+
f"Found {incorrect_type_count} annotations with unsupported types. Only 'diagrams.AssetLink' and "
|
|
324
|
+
"'diagrams.FileLink' are supported. These annotations will be skipped during migration."
|
|
325
|
+
).print_warning(include_timestamp=True, console=self.client.console)
|
|
311
326
|
|
|
312
327
|
def _get_mapping(self, current_mapping: str | None, resource: Annotation) -> str:
|
|
313
328
|
try:
|
|
@@ -320,7 +320,7 @@ class FunctionCRUD(ResourceCRUD[str, FunctionWrite, Function]):
|
|
|
320
320
|
" problem persists, please contact Cognite support."
|
|
321
321
|
)
|
|
322
322
|
item.file_id = file_id
|
|
323
|
-
created_item = self.client.functions.create_with_429_retry(item
|
|
323
|
+
created_item = self.client.functions.create_with_429_retry(item)
|
|
324
324
|
self._warn_if_cpu_or_memory_changed(created_item, item)
|
|
325
325
|
created.append(created_item)
|
|
326
326
|
return created
|
|
@@ -404,7 +404,7 @@ class FunctionCRUD(ResourceCRUD[str, FunctionWrite, Function]):
|
|
|
404
404
|
def delete(self, ids: SequenceNotStr[str]) -> int:
|
|
405
405
|
functions = self.retrieve(ids)
|
|
406
406
|
|
|
407
|
-
self.client.functions.delete_with_429_retry(external_id=ids, ignore_unknown_ids=True
|
|
407
|
+
self.client.functions.delete_with_429_retry(external_id=ids, ignore_unknown_ids=True)
|
|
408
408
|
file_ids = {func.file_id for func in functions if func.file_id}
|
|
409
409
|
self.client.files.delete(id=list(file_ids), ignore_unknown_ids=True)
|
|
410
410
|
return len(ids)
|
|
@@ -33,7 +33,8 @@ class AnnotationIO(StorageIO[AssetCentricSelector, Annotation]):
|
|
|
33
33
|
annotated_resource_type="file",
|
|
34
34
|
annotated_resource_ids=[{"id": file_metadata.id} for file_metadata in file_chunk.items],
|
|
35
35
|
annotation_type=annotation_type,
|
|
36
|
-
)
|
|
36
|
+
),
|
|
37
|
+
limit=-1,
|
|
37
38
|
)
|
|
38
39
|
if limit is not None and total + len(results) > limit:
|
|
39
40
|
results = results[: limit - total]
|
|
@@ -23,6 +23,15 @@ from cognite_toolkit._cdf_tk.utils.http_client._data_classes import (
|
|
|
23
23
|
ResponseList,
|
|
24
24
|
ResponseMessage,
|
|
25
25
|
)
|
|
26
|
+
from cognite_toolkit._cdf_tk.utils.http_client._data_classes2 import (
|
|
27
|
+
BaseRequestMessage,
|
|
28
|
+
ErrorDetails2,
|
|
29
|
+
FailedRequest2,
|
|
30
|
+
FailedResponse2,
|
|
31
|
+
HTTPResult2,
|
|
32
|
+
RequestMessage2,
|
|
33
|
+
SuccessResponse2,
|
|
34
|
+
)
|
|
26
35
|
from cognite_toolkit._cdf_tk.utils.useful_types import PrimitiveType
|
|
27
36
|
|
|
28
37
|
if sys.version_info >= (3, 11):
|
|
@@ -48,6 +57,7 @@ class HTTPClient:
|
|
|
48
57
|
Default is {408, 429, 502, 503, 504}.
|
|
49
58
|
split_items_status_codes (frozenset[int]): In the case of ItemRequest with multiple
|
|
50
59
|
items, these status codes will trigger splitting the request into smaller batches.
|
|
60
|
+
console (Console | None): Optional Rich Console for printing warnings.
|
|
51
61
|
|
|
52
62
|
"""
|
|
53
63
|
|
|
@@ -59,6 +69,7 @@ class HTTPClient:
|
|
|
59
69
|
pool_maxsize: int = 20,
|
|
60
70
|
retry_status_codes: Set[int] = frozenset({408, 429, 502, 503, 504}),
|
|
61
71
|
split_items_status_codes: Set[int] = frozenset({400, 408, 409, 422, 502, 503, 504}),
|
|
72
|
+
console: Console | None = None,
|
|
62
73
|
):
|
|
63
74
|
self.config = config
|
|
64
75
|
self._max_retries = max_retries
|
|
@@ -66,6 +77,7 @@ class HTTPClient:
|
|
|
66
77
|
self._pool_maxsize = pool_maxsize
|
|
67
78
|
self._retry_status_codes = retry_status_codes
|
|
68
79
|
self._split_items_status_codes = split_items_status_codes
|
|
80
|
+
self._console = console
|
|
69
81
|
|
|
70
82
|
# Thread-safe session for connection pooling
|
|
71
83
|
self.session = self._create_thread_safe_session()
|
|
@@ -80,12 +92,11 @@ class HTTPClient:
|
|
|
80
92
|
self.session.close()
|
|
81
93
|
return False # Do not suppress exceptions
|
|
82
94
|
|
|
83
|
-
def request(self, message: RequestMessage
|
|
95
|
+
def request(self, message: RequestMessage) -> Sequence[HTTPMessage]:
|
|
84
96
|
"""Send an HTTP request and return the response.
|
|
85
97
|
|
|
86
98
|
Args:
|
|
87
99
|
message (RequestMessage): The request message to send.
|
|
88
|
-
console (Console | None): The rich console to use for printing warnings.
|
|
89
100
|
|
|
90
101
|
Returns:
|
|
91
102
|
Sequence[HTTPMessage]: The response message(s). This can also
|
|
@@ -98,12 +109,12 @@ class HTTPClient:
|
|
|
98
109
|
return message.create_failed_request(error_msg)
|
|
99
110
|
try:
|
|
100
111
|
response = self._make_request(message)
|
|
101
|
-
results = self._handle_response(response, message
|
|
112
|
+
results = self._handle_response(response, message)
|
|
102
113
|
except Exception as e:
|
|
103
114
|
results = self._handle_error(e, message)
|
|
104
115
|
return results
|
|
105
116
|
|
|
106
|
-
def request_with_retries(self, message: RequestMessage
|
|
117
|
+
def request_with_retries(self, message: RequestMessage) -> ResponseList:
|
|
107
118
|
"""Send an HTTP request and handle retries.
|
|
108
119
|
|
|
109
120
|
This method will keep retrying the request until it either succeeds or
|
|
@@ -114,7 +125,6 @@ class HTTPClient:
|
|
|
114
125
|
|
|
115
126
|
Args:
|
|
116
127
|
message (RequestMessage): The request message to send.
|
|
117
|
-
console (Console | None): The rich console to use for printing warnings.
|
|
118
128
|
|
|
119
129
|
Returns:
|
|
120
130
|
Sequence[ResponseMessage | FailedRequestMessage]: The final response
|
|
@@ -127,7 +137,7 @@ class HTTPClient:
|
|
|
127
137
|
final_responses = ResponseList([])
|
|
128
138
|
while pending_requests:
|
|
129
139
|
current_request = pending_requests.popleft()
|
|
130
|
-
results = self.request(current_request
|
|
140
|
+
results = self.request(current_request)
|
|
131
141
|
|
|
132
142
|
for result in results:
|
|
133
143
|
if isinstance(result, RequestMessage):
|
|
@@ -194,9 +204,7 @@ class HTTPClient:
|
|
|
194
204
|
follow_redirects=False,
|
|
195
205
|
)
|
|
196
206
|
|
|
197
|
-
def _handle_response(
|
|
198
|
-
self, response: httpx.Response, request: RequestMessage, console: Console | None = None
|
|
199
|
-
) -> Sequence[HTTPMessage]:
|
|
207
|
+
def _handle_response(self, response: httpx.Response, request: RequestMessage) -> Sequence[HTTPMessage]:
|
|
200
208
|
if 200 <= response.status_code < 300:
|
|
201
209
|
return request.create_success_response(response)
|
|
202
210
|
elif (
|
|
@@ -216,11 +224,11 @@ class HTTPClient:
|
|
|
216
224
|
|
|
217
225
|
retry_after = self._get_retry_after_in_header(response)
|
|
218
226
|
if retry_after is not None and response.status_code == 429 and request.status_attempt < self._max_retries:
|
|
219
|
-
if
|
|
227
|
+
if self._console is not None:
|
|
220
228
|
short_url = request.endpoint_url.removeprefix(self.config.base_api_url)
|
|
221
229
|
HighSeverityWarning(
|
|
222
230
|
f"Rate limit exceeded for the {short_url!r} endpoint. Retrying after {retry_after} seconds."
|
|
223
|
-
).print_warning(console=
|
|
231
|
+
).print_warning(console=self._console)
|
|
224
232
|
request.status_attempt += 1
|
|
225
233
|
time.sleep(retry_after)
|
|
226
234
|
return [request]
|
|
@@ -273,3 +281,110 @@ class HTTPClient:
|
|
|
273
281
|
error_msg = f"RequestException after {request.total_attempts - 1} attempts ({error_type} error): {e!s}"
|
|
274
282
|
|
|
275
283
|
return request.create_failed_request(error_msg)
|
|
284
|
+
|
|
285
|
+
def request_single(self, message: RequestMessage2) -> RequestMessage2 | HTTPResult2:
|
|
286
|
+
"""Send an HTTP request and return the response.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
message (RequestMessage2): The request message to send.
|
|
290
|
+
Returns:
|
|
291
|
+
HTTPMessage: The response message.
|
|
292
|
+
"""
|
|
293
|
+
try:
|
|
294
|
+
response = self._make_request2(message)
|
|
295
|
+
result = self._handle_response_single(response, message)
|
|
296
|
+
except Exception as e:
|
|
297
|
+
result = self._handle_error_single(e, message)
|
|
298
|
+
return result
|
|
299
|
+
|
|
300
|
+
def request_single_retries(self, message: RequestMessage2) -> HTTPResult2:
|
|
301
|
+
"""Send an HTTP request and handle retries.
|
|
302
|
+
|
|
303
|
+
This method will keep retrying the request until it either succeeds or
|
|
304
|
+
exhausts the maximum number of retries.
|
|
305
|
+
|
|
306
|
+
Note this method will use the current thread to process all request, thus
|
|
307
|
+
it is blocking.
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
message (RequestMessage2): The request message to send.
|
|
311
|
+
Returns:
|
|
312
|
+
HTTPMessage2: The final response message, which can be either successful response or failed request.
|
|
313
|
+
"""
|
|
314
|
+
if message.total_attempts > 0:
|
|
315
|
+
raise RuntimeError(f"RequestMessage has already been attempted {message.total_attempts} times.")
|
|
316
|
+
current_request = message
|
|
317
|
+
while True:
|
|
318
|
+
result = self.request_single(current_request)
|
|
319
|
+
if isinstance(result, RequestMessage2):
|
|
320
|
+
current_request = result
|
|
321
|
+
elif isinstance(result, HTTPResult2):
|
|
322
|
+
return result
|
|
323
|
+
else:
|
|
324
|
+
raise TypeError(f"Unexpected result type: {type(result)}")
|
|
325
|
+
|
|
326
|
+
def _make_request2(self, message: BaseRequestMessage) -> httpx.Response:
|
|
327
|
+
headers = self._create_headers(message.api_version, message.content_type, message.accept)
|
|
328
|
+
return self.session.request(
|
|
329
|
+
method=message.method,
|
|
330
|
+
url=message.endpoint_url,
|
|
331
|
+
content=message.content,
|
|
332
|
+
headers=headers,
|
|
333
|
+
params=message.parameters,
|
|
334
|
+
timeout=self.config.timeout,
|
|
335
|
+
follow_redirects=False,
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
def _handle_response_single(
|
|
339
|
+
self, response: httpx.Response, request: RequestMessage2
|
|
340
|
+
) -> RequestMessage2 | HTTPResult2:
|
|
341
|
+
if 200 <= response.status_code < 300:
|
|
342
|
+
return SuccessResponse2(
|
|
343
|
+
status_code=response.status_code,
|
|
344
|
+
body=response.text,
|
|
345
|
+
content=response.content,
|
|
346
|
+
)
|
|
347
|
+
retry_after = self._get_retry_after_in_header(response)
|
|
348
|
+
if retry_after is not None and response.status_code == 429 and request.status_attempt < self._max_retries:
|
|
349
|
+
if self._console is not None:
|
|
350
|
+
short_url = request.endpoint_url.removeprefix(self.config.base_api_url)
|
|
351
|
+
HighSeverityWarning(
|
|
352
|
+
f"Rate limit exceeded for the {short_url!r} endpoint. Retrying after {retry_after} seconds."
|
|
353
|
+
).print_warning(console=self._console)
|
|
354
|
+
request.status_attempt += 1
|
|
355
|
+
time.sleep(retry_after)
|
|
356
|
+
return request
|
|
357
|
+
|
|
358
|
+
if request.status_attempt < self._max_retries and response.status_code in self._retry_status_codes:
|
|
359
|
+
request.status_attempt += 1
|
|
360
|
+
time.sleep(self._backoff_time(request.total_attempts))
|
|
361
|
+
return request
|
|
362
|
+
else:
|
|
363
|
+
# Permanent failure
|
|
364
|
+
return FailedResponse2(
|
|
365
|
+
status_code=response.status_code,
|
|
366
|
+
body=response.text,
|
|
367
|
+
error=ErrorDetails2.from_response(response),
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
def _handle_error_single(self, e: Exception, request: RequestMessage2) -> RequestMessage2 | HTTPResult2:
|
|
371
|
+
if isinstance(e, httpx.ReadTimeout | httpx.TimeoutException):
|
|
372
|
+
error_type = "read"
|
|
373
|
+
request.read_attempt += 1
|
|
374
|
+
attempts = request.read_attempt
|
|
375
|
+
elif isinstance(e, ConnectionError | httpx.ConnectError | httpx.ConnectTimeout):
|
|
376
|
+
error_type = "connect"
|
|
377
|
+
request.connect_attempt += 1
|
|
378
|
+
attempts = request.connect_attempt
|
|
379
|
+
else:
|
|
380
|
+
error_msg = f"Unexpected exception: {e!s}"
|
|
381
|
+
return FailedRequest2(error=error_msg)
|
|
382
|
+
|
|
383
|
+
if attempts <= self._max_retries:
|
|
384
|
+
time.sleep(self._backoff_time(request.total_attempts))
|
|
385
|
+
return request
|
|
386
|
+
else:
|
|
387
|
+
# We have already incremented the attempt count, so we subtract 1 here
|
|
388
|
+
error_msg = f"RequestException after {request.total_attempts - 1} attempts ({error_type} error): {e!s}"
|
|
389
|
+
|
|
390
|
+
return FailedRequest2(error=error_msg)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import gzip
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from typing import Any, Literal
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
from cognite.client import global_config
|
|
7
|
+
from pydantic import BaseModel, JsonValue, TypeAdapter, model_validator
|
|
8
|
+
|
|
9
|
+
from cognite_toolkit._cdf_tk.utils.useful_types import PrimitiveType
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class HTTPResult2(BaseModel): ...
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class FailedRequest2(HTTPResult2):
|
|
16
|
+
error: str
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SuccessResponse2(HTTPResult2):
|
|
20
|
+
status_code: int
|
|
21
|
+
body: str
|
|
22
|
+
content: bytes
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ErrorDetails2(BaseModel):
|
|
26
|
+
"""This is the expected structure of error details in the CDF API"""
|
|
27
|
+
|
|
28
|
+
code: int
|
|
29
|
+
message: str
|
|
30
|
+
missing: list[JsonValue] | None = None
|
|
31
|
+
duplicated: list[JsonValue] | None = None
|
|
32
|
+
is_auto_retryable: bool | None = None
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def from_response(cls, response: httpx.Response) -> "ErrorDetails2":
|
|
36
|
+
"""Populate the error details from a httpx response."""
|
|
37
|
+
try:
|
|
38
|
+
res = TypeAdapter(dict[Literal["error"], ErrorDetails2]).validate_json(response.text)
|
|
39
|
+
except ValueError:
|
|
40
|
+
return cls(code=response.status_code, message=response.text)
|
|
41
|
+
return res["error"]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class FailedResponse2(HTTPResult2):
|
|
45
|
+
status_code: int
|
|
46
|
+
body: str
|
|
47
|
+
error: ErrorDetails2
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class BaseRequestMessage(BaseModel, ABC):
|
|
51
|
+
endpoint_url: str
|
|
52
|
+
method: Literal["GET", "POST", "PATCH", "DELETE", "PUT"]
|
|
53
|
+
connect_attempt: int = 0
|
|
54
|
+
read_attempt: int = 0
|
|
55
|
+
status_attempt: int = 0
|
|
56
|
+
api_version: str | None = None
|
|
57
|
+
content_type: str = "application/json"
|
|
58
|
+
accept: str = "application/json"
|
|
59
|
+
|
|
60
|
+
parameters: dict[str, PrimitiveType] | None = None
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def total_attempts(self) -> int:
|
|
64
|
+
return self.connect_attempt + self.read_attempt + self.status_attempt
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
@abstractmethod
|
|
68
|
+
def content(self) -> str | bytes | None: ...
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class RequestMessage2(BaseRequestMessage):
|
|
72
|
+
data_content: bytes | None = None
|
|
73
|
+
body_content: dict[str, JsonValue] | None = None
|
|
74
|
+
|
|
75
|
+
@model_validator(mode="before")
|
|
76
|
+
def check_data_or_body(cls, values: dict[str, Any]) -> dict[str, Any]:
|
|
77
|
+
if values.get("data_content") is not None and values.get("body_content") is not None:
|
|
78
|
+
raise ValueError("Only one of data_content or body_content can be set.")
|
|
79
|
+
return values
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def content(self) -> str | bytes | None:
|
|
83
|
+
data: str | bytes | None = None
|
|
84
|
+
if self.data_content is not None:
|
|
85
|
+
data = self.data_content
|
|
86
|
+
if not global_config.disable_gzip:
|
|
87
|
+
data = gzip.compress(data)
|
|
88
|
+
elif self.body_content is not None:
|
|
89
|
+
# We serialize using pydantic instead of json.dumps. This is because pydantic is faster
|
|
90
|
+
# and handles more complex types such as datetime, float('nan'), etc.
|
|
91
|
+
data = _BODY_SERIALIZER.dump_json(self.body_content)
|
|
92
|
+
if not global_config.disable_gzip and isinstance(data, str):
|
|
93
|
+
data = gzip.compress(data.encode("utf-8"))
|
|
94
|
+
return data
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
_BODY_SERIALIZER = TypeAdapter(dict[str, JsonValue])
|
cognite_toolkit/_version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.7.
|
|
1
|
+
__version__ = "0.7.29"
|
|
@@ -28,14 +28,14 @@ cognite_toolkit/_cdf_tk/builders/_transformation.py,sha256=STB42zhzOW5M_-b8cKOQ_
|
|
|
28
28
|
cognite_toolkit/_cdf_tk/cdf_toml.py,sha256=VSWV9h44HusWIaKpWgjrOMrc3hDoPTTXBXlp6-NOrIM,9079
|
|
29
29
|
cognite_toolkit/_cdf_tk/client/__init__.py,sha256=a6rQXDGfW2g7K5WwrOW5oakh1TdFlBjUVjf9wusOox8,135
|
|
30
30
|
cognite_toolkit/_cdf_tk/client/_constants.py,sha256=COUGcea37mDF2sf6MGqJXWmecTY_6aCImslxXrYW1I0,73
|
|
31
|
-
cognite_toolkit/_cdf_tk/client/_toolkit_client.py,sha256=
|
|
31
|
+
cognite_toolkit/_cdf_tk/client/_toolkit_client.py,sha256=y_GGNlCtu_qcjFVIb3bwLUS9zCqzJ0BK0_K-paw1Gt4,3417
|
|
32
32
|
cognite_toolkit/_cdf_tk/client/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
33
33
|
cognite_toolkit/_cdf_tk/client/api/canvas.py,sha256=i2NwyhvmklTPx3e-yd4lvSxyn6JEjSpv8WXa1SxtmV8,8789
|
|
34
34
|
cognite_toolkit/_cdf_tk/client/api/charts.py,sha256=t-VOrRGwpjmYUtUqGObQWYwGb5gOHVp4cHZBm8ZVGn0,4953
|
|
35
35
|
cognite_toolkit/_cdf_tk/client/api/dml.py,sha256=8b1lo86JdvfEsz9mP2rx0Mp9fyWsU6mbXHqLBtvSidU,3546
|
|
36
36
|
cognite_toolkit/_cdf_tk/client/api/extended_data_modeling.py,sha256=T08lXIrgDRGKhF-44FYoBMd4oJRYiWRzYhHsNkLyLAo,12967
|
|
37
37
|
cognite_toolkit/_cdf_tk/client/api/extended_files.py,sha256=azdPnCqXUVuPLTuiV9WZ97VJTJ6mN2hOEtD9LklLw8M,9191
|
|
38
|
-
cognite_toolkit/_cdf_tk/client/api/extended_functions.py,sha256=
|
|
38
|
+
cognite_toolkit/_cdf_tk/client/api/extended_functions.py,sha256=_MbkztZskZAt43ZERddhmeZYKHeQSDteFlnaCLL5J6k,3523
|
|
39
39
|
cognite_toolkit/_cdf_tk/client/api/extended_raw.py,sha256=9DVbM2aWmIyzbaW-lh10_pzVYJUEQFnIKnxvt413Bjk,2118
|
|
40
40
|
cognite_toolkit/_cdf_tk/client/api/extended_timeseries.py,sha256=xK7XhTfe4W9FvaueUIfR7Q64JOIDwq_svHRjORM76Q4,17774
|
|
41
41
|
cognite_toolkit/_cdf_tk/client/api/fixed_transformations.py,sha256=m66cqbx4oCtjv5TBQOWLNFrz475qVTCXBu_pTxbdCD4,5589
|
|
@@ -67,8 +67,8 @@ cognite_toolkit/_cdf_tk/client/data_classes/apm_config_v1.py,sha256=0bPq7R0qvdf8
|
|
|
67
67
|
cognite_toolkit/_cdf_tk/client/data_classes/base.py,sha256=QG4S0HlByMB6zwxUXWaVHwP-DrA2Y97XGN_o6QsL6FY,2776
|
|
68
68
|
cognite_toolkit/_cdf_tk/client/data_classes/canvas.py,sha256=DrE-7HOLnk1ELhydySsEhw-VOjriUqB_zzon5qb7CDk,50721
|
|
69
69
|
cognite_toolkit/_cdf_tk/client/data_classes/capabilities.py,sha256=muqpAC2JLCFcEpRPzuh_3sS3o_q42WFyfsGzl-LfB_U,8773
|
|
70
|
-
cognite_toolkit/_cdf_tk/client/data_classes/charts.py,sha256=
|
|
71
|
-
cognite_toolkit/_cdf_tk/client/data_classes/charts_data.py,sha256
|
|
70
|
+
cognite_toolkit/_cdf_tk/client/data_classes/charts.py,sha256=4ZSZDJhDP8uNubXfzphuLJzKJhL1F01grB4UesxtSbQ,3745
|
|
71
|
+
cognite_toolkit/_cdf_tk/client/data_classes/charts_data.py,sha256=-dFfY53cos5DwASLU18aBfYF1VC6bfaUshC2HiGJ2uI,5571
|
|
72
72
|
cognite_toolkit/_cdf_tk/client/data_classes/extendable_cognite_file.py,sha256=0iyLiXEzB4WBU-DL6DZS6nD5E526cDsftMGEYXwI8r8,9764
|
|
73
73
|
cognite_toolkit/_cdf_tk/client/data_classes/extended_filemetadata.py,sha256=8zfXl_bhkums3quJzdOwAjxVNY6B0hpAs6jbkekn79o,5488
|
|
74
74
|
cognite_toolkit/_cdf_tk/client/data_classes/extended_filemetdata.py,sha256=gKA5UcDKweH7SlzXfyZCspMyHUo0t8R5DbzeCPpzInM,6002
|
|
@@ -103,11 +103,11 @@ cognite_toolkit/_cdf_tk/commands/_migrate/command.py,sha256=l2P0Em05aEJvNZH4WkEI
|
|
|
103
103
|
cognite_toolkit/_cdf_tk/commands/_migrate/conversion.py,sha256=Ew9JRYrd-Ol9G9csTzpnhXAgCFnX67MwDYOTsdJLP3E,16803
|
|
104
104
|
cognite_toolkit/_cdf_tk/commands/_migrate/creators.py,sha256=FTu7w3G8KyPY8pagG3KdPpOmpLcjehaAg2auEy6iM7A,9605
|
|
105
105
|
cognite_toolkit/_cdf_tk/commands/_migrate/data_classes.py,sha256=_vMS_qAPj4yup1VnmmojPVigAZtyPQH7PM0Raby5tao,10619
|
|
106
|
-
cognite_toolkit/_cdf_tk/commands/_migrate/data_mapper.py,sha256=
|
|
106
|
+
cognite_toolkit/_cdf_tk/commands/_migrate/data_mapper.py,sha256=b_6_yYibtzWiBFrYq5pB8NZUi1TRuf-DIy_GRroj4wg,18551
|
|
107
107
|
cognite_toolkit/_cdf_tk/commands/_migrate/data_model.py,sha256=i1eUsNX6Dueol9STIEwyksBnBsWUk13O8qHIjW964pM,7860
|
|
108
108
|
cognite_toolkit/_cdf_tk/commands/_migrate/default_mappings.py,sha256=ERn3qFrJFXdtXaMjHq3Gk7MxH03MGFk3FrtWCOBJQts,5544
|
|
109
109
|
cognite_toolkit/_cdf_tk/commands/_migrate/issues.py,sha256=n8en744-r7GL9eUyxEojFes1yk69V04SnlpVXHrdPOQ,6972
|
|
110
|
-
cognite_toolkit/_cdf_tk/commands/_migrate/migration_io.py,sha256=
|
|
110
|
+
cognite_toolkit/_cdf_tk/commands/_migrate/migration_io.py,sha256=nc77Eu1a2sGS8Pb0bkMgqWebYmWlLbQe8xPCay5aoNo,17208
|
|
111
111
|
cognite_toolkit/_cdf_tk/commands/_migrate/prepare.py,sha256=RfqaNoso5CyBwc-p6ckwcYqBfZXKhdJgdGIyd0TATaI,2635
|
|
112
112
|
cognite_toolkit/_cdf_tk/commands/_migrate/selectors.py,sha256=N1H_-rBpPUD6pbrlcofn1uEK1bA694EUXEe1zIXeqyo,2489
|
|
113
113
|
cognite_toolkit/_cdf_tk/commands/_profile.py,sha256=_4iX3AHAI6eLmRVUlWXCSvVHx1BZW2yDr_i2i9ECg6U,43120
|
|
@@ -143,7 +143,7 @@ cognite_toolkit/_cdf_tk/cruds/_resource_cruds/datamodel.py,sha256=SagiSp3JERgEU3
|
|
|
143
143
|
cognite_toolkit/_cdf_tk/cruds/_resource_cruds/extraction_pipeline.py,sha256=a2HywkruYNJGLZxqOjlp8mrpRGtJDPqIb6qY00eUbEI,17701
|
|
144
144
|
cognite_toolkit/_cdf_tk/cruds/_resource_cruds/fieldops.py,sha256=dcC850Vyvc5Hfi9Z3MfXE8s_q14Hqq4EqegFz_V6aCI,20662
|
|
145
145
|
cognite_toolkit/_cdf_tk/cruds/_resource_cruds/file.py,sha256=vyeRsiIOEbUeYslBsgXoyCk5hozDsubUilA7bdjqS5c,14855
|
|
146
|
-
cognite_toolkit/_cdf_tk/cruds/_resource_cruds/function.py,sha256=
|
|
146
|
+
cognite_toolkit/_cdf_tk/cruds/_resource_cruds/function.py,sha256=4J7ObIRT1fKl1542-byaRs3lUfx2LiNbNg55tQkIg-g,28395
|
|
147
147
|
cognite_toolkit/_cdf_tk/cruds/_resource_cruds/group_scoped.py,sha256=WEg8-CxMP64WfE_XXIlH114zM51K0uLaYa4atd992zI,1690
|
|
148
148
|
cognite_toolkit/_cdf_tk/cruds/_resource_cruds/hosted_extractors.py,sha256=P0hlXK0_FmO86U-gDHMHz8N0vpDtPoKupiQfhNP5KLE,14619
|
|
149
149
|
cognite_toolkit/_cdf_tk/cruds/_resource_cruds/industrial_tool.py,sha256=QrgSCcLN0NtpQuP7zcCUYaWoiq3JiUB2j0A15d8MNNc,7856
|
|
@@ -238,7 +238,7 @@ cognite_toolkit/_cdf_tk/resource_classes/workflow.py,sha256=fMNfW93D8tdVwO7YgEYY
|
|
|
238
238
|
cognite_toolkit/_cdf_tk/resource_classes/workflow_trigger.py,sha256=aSN0WFPupQ383A7RT-0Monw-inkVdYYSsK3UwHXW1HA,5216
|
|
239
239
|
cognite_toolkit/_cdf_tk/resource_classes/workflow_version.py,sha256=ui724EaM9Nlm3wTnm7Givgv6GLQ-xbsfZgidyRKv09U,2991
|
|
240
240
|
cognite_toolkit/_cdf_tk/storageio/__init__.py,sha256=h5Wr4i7zNIgsslrsRJxmp7ls4bNRKl0uZzQ7GLRMP7g,1920
|
|
241
|
-
cognite_toolkit/_cdf_tk/storageio/_annotations.py,sha256=
|
|
241
|
+
cognite_toolkit/_cdf_tk/storageio/_annotations.py,sha256=QcFrikDgz-9VjNy4Xq7wchM4VOQh-z2JaHcWR2C1sEs,4879
|
|
242
242
|
cognite_toolkit/_cdf_tk/storageio/_applications.py,sha256=M7FEK4xC0BjP2i6FyYs1589zEA3afJiOKCzY56RV6NU,19685
|
|
243
243
|
cognite_toolkit/_cdf_tk/storageio/_asset_centric.py,sha256=TirKLSNPoLqKjczsw0djWAsR0VvopwmU23aUxrBOJN8,32464
|
|
244
244
|
cognite_toolkit/_cdf_tk/storageio/_base.py,sha256=ElvqhIEBnhcz0yY1Ds164wVN0_7CFNK-uT0-z7LcR9U,13067
|
|
@@ -281,8 +281,9 @@ cognite_toolkit/_cdf_tk/utils/fileio/_writers.py,sha256=mc23m0kJgl57FUDvwLmS7yR3
|
|
|
281
281
|
cognite_toolkit/_cdf_tk/utils/graphql_parser.py,sha256=2i2wDjg_Uw3hJ-pHtPK8hczIuCj5atrK8HZbgWJB-Pk,11532
|
|
282
282
|
cognite_toolkit/_cdf_tk/utils/hashing.py,sha256=3NyNfljyYNTqAyAFBd6XlyWaj43jRzENxIuPdOY6nqo,2116
|
|
283
283
|
cognite_toolkit/_cdf_tk/utils/http_client/__init__.py,sha256=G8b7Bg4yIet5R4Igh3dS2SntWzE6I0iTGBeNlNsSxkQ,857
|
|
284
|
-
cognite_toolkit/_cdf_tk/utils/http_client/_client.py,sha256=
|
|
284
|
+
cognite_toolkit/_cdf_tk/utils/http_client/_client.py,sha256=OrrGq3GjusxPPzhFoW8iyiphpdbWOWAoaYOeOy9kqjQ,16212
|
|
285
285
|
cognite_toolkit/_cdf_tk/utils/http_client/_data_classes.py,sha256=8KEDyRRaOLhwN2eA2vaBAzZ__JDUicUDyir6x_PE5lk,14817
|
|
286
|
+
cognite_toolkit/_cdf_tk/utils/http_client/_data_classes2.py,sha256=mUrmTqTm60WP4JpMmNO07-NfgWQM8Wby7OLCo02I71U,3031
|
|
286
287
|
cognite_toolkit/_cdf_tk/utils/http_client/_exception.py,sha256=fC9oW6BN0HbUe2AkYABMP7Kj0-9dNYXVFBY5RQztq2c,126
|
|
287
288
|
cognite_toolkit/_cdf_tk/utils/http_client/_tracker.py,sha256=EBBnd-JZ7nc_jYNFJokCHN2UZ9sx0McFLZvlceUYYic,1215
|
|
288
289
|
cognite_toolkit/_cdf_tk/utils/interactive_select.py,sha256=dP_ZFHvzQRPQxRt6EzURY3Z3Ld_otJtCz-nGqUNtt1k,35725
|
|
@@ -302,14 +303,14 @@ cognite_toolkit/_repo_files/.gitignore,sha256=ip9kf9tcC5OguF4YF4JFEApnKYw0nG0vPi
|
|
|
302
303
|
cognite_toolkit/_repo_files/AzureDevOps/.devops/README.md,sha256=OLA0D7yCX2tACpzvkA0IfkgQ4_swSd-OlJ1tYcTBpsA,240
|
|
303
304
|
cognite_toolkit/_repo_files/AzureDevOps/.devops/deploy-pipeline.yml,sha256=brULcs8joAeBC_w_aoWjDDUHs3JheLMIR9ajPUK96nc,693
|
|
304
305
|
cognite_toolkit/_repo_files/AzureDevOps/.devops/dry-run-pipeline.yml,sha256=OBFDhFWK1mlT4Dc6mDUE2Es834l8sAlYG50-5RxRtHk,723
|
|
305
|
-
cognite_toolkit/_repo_files/GitHub/.github/workflows/deploy.yaml,sha256=
|
|
306
|
-
cognite_toolkit/_repo_files/GitHub/.github/workflows/dry-run.yaml,sha256=
|
|
307
|
-
cognite_toolkit/_resources/cdf.toml,sha256=
|
|
308
|
-
cognite_toolkit/_version.py,sha256=
|
|
306
|
+
cognite_toolkit/_repo_files/GitHub/.github/workflows/deploy.yaml,sha256=FQFw5rkuhjbBY-cBoP38pdrcUZF6qkK2Y9YKbab8LzU,667
|
|
307
|
+
cognite_toolkit/_repo_files/GitHub/.github/workflows/dry-run.yaml,sha256=e87zJqrsOVfo7QqSnIZY5U32szqyvq5Bjq-e_16xHiQ,2430
|
|
308
|
+
cognite_toolkit/_resources/cdf.toml,sha256=tJP7KRNhWsrkXisd58pe5ZlzUBOAfe4zR5TTAP63tzg,475
|
|
309
|
+
cognite_toolkit/_version.py,sha256=XdC0R1utqmwH52az-uz3mk0vLZyV2OyWiin3soRIguo,23
|
|
309
310
|
cognite_toolkit/config.dev.yaml,sha256=M33FiIKdS3XKif-9vXniQ444GTZ-bLXV8aFH86u9iUQ,332
|
|
310
311
|
cognite_toolkit/demo/__init__.py,sha256=-m1JoUiwRhNCL18eJ6t7fZOL7RPfowhCuqhYFtLgrss,72
|
|
311
312
|
cognite_toolkit/demo/_base.py,sha256=6xKBUQpXZXGQ3fJ5f7nj7oT0s2n7OTAGIa17ZlKHZ5U,8052
|
|
312
|
-
cognite_toolkit-0.7.
|
|
313
|
-
cognite_toolkit-0.7.
|
|
314
|
-
cognite_toolkit-0.7.
|
|
315
|
-
cognite_toolkit-0.7.
|
|
313
|
+
cognite_toolkit-0.7.29.dist-info/WHEEL,sha256=93kfTGt3a0Dykt_T-gsjtyS5_p8F_d6CE1NwmBOirzo,79
|
|
314
|
+
cognite_toolkit-0.7.29.dist-info/entry_points.txt,sha256=EtZ17K2mUjh-AY0QNU1CPIB_aDSSOdmtNI_4Fj967mA,84
|
|
315
|
+
cognite_toolkit-0.7.29.dist-info/METADATA,sha256=71UXU4S9UVcZVxhDGazAQOCbTSYyQzD6Kj2FkxNJHYs,4507
|
|
316
|
+
cognite_toolkit-0.7.29.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|