cognite-toolkit 0.6.97__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 +21 -23
- cognite_toolkit/_cdf_tk/apps/__init__.py +4 -0
- cognite_toolkit/_cdf_tk/apps/_core_app.py +19 -5
- cognite_toolkit/_cdf_tk/apps/_data_app.py +1 -1
- cognite_toolkit/_cdf_tk/apps/_dev_app.py +86 -0
- cognite_toolkit/_cdf_tk/apps/_download_app.py +693 -25
- cognite_toolkit/_cdf_tk/apps/_dump_app.py +44 -102
- cognite_toolkit/_cdf_tk/apps/_import_app.py +41 -0
- cognite_toolkit/_cdf_tk/apps/_landing_app.py +18 -4
- cognite_toolkit/_cdf_tk/apps/_migrate_app.py +424 -9
- cognite_toolkit/_cdf_tk/apps/_modules_app.py +0 -3
- cognite_toolkit/_cdf_tk/apps/_purge.py +15 -43
- cognite_toolkit/_cdf_tk/apps/_run.py +11 -0
- cognite_toolkit/_cdf_tk/apps/_upload_app.py +45 -6
- cognite_toolkit/_cdf_tk/builders/__init__.py +2 -2
- cognite_toolkit/_cdf_tk/builders/_base.py +28 -42
- cognite_toolkit/_cdf_tk/builders/_raw.py +1 -1
- cognite_toolkit/_cdf_tk/cdf_toml.py +20 -1
- cognite_toolkit/_cdf_tk/client/_toolkit_client.py +32 -12
- cognite_toolkit/_cdf_tk/client/api/infield.py +114 -17
- 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} +15 -18
- 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 +177 -4
- 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 +88 -0
- cognite_toolkit/_cdf_tk/client/api/three_d.py +384 -0
- cognite_toolkit/_cdf_tk/client/data_classes/api_classes.py +13 -0
- cognite_toolkit/_cdf_tk/client/data_classes/base.py +37 -33
- cognite_toolkit/_cdf_tk/client/data_classes/charts_data.py +95 -213
- cognite_toolkit/_cdf_tk/client/data_classes/infield.py +32 -18
- 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} +47 -4
- cognite_toolkit/_cdf_tk/client/data_classes/{charts.py → legacy/charts.py} +3 -3
- cognite_toolkit/_cdf_tk/client/data_classes/{migration.py → legacy/migration.py} +10 -2
- cognite_toolkit/_cdf_tk/client/data_classes/streams.py +90 -0
- cognite_toolkit/_cdf_tk/client/data_classes/three_d.py +112 -0
- cognite_toolkit/_cdf_tk/client/testing.py +42 -18
- cognite_toolkit/_cdf_tk/commands/__init__.py +7 -6
- cognite_toolkit/_cdf_tk/commands/_changes.py +3 -42
- cognite_toolkit/_cdf_tk/commands/_download.py +21 -11
- cognite_toolkit/_cdf_tk/commands/_migrate/__init__.py +0 -2
- cognite_toolkit/_cdf_tk/commands/_migrate/command.py +22 -20
- cognite_toolkit/_cdf_tk/commands/_migrate/conversion.py +140 -92
- cognite_toolkit/_cdf_tk/commands/_migrate/creators.py +1 -1
- cognite_toolkit/_cdf_tk/commands/_migrate/data_classes.py +108 -26
- cognite_toolkit/_cdf_tk/commands/_migrate/data_mapper.py +448 -45
- cognite_toolkit/_cdf_tk/commands/_migrate/data_model.py +1 -0
- cognite_toolkit/_cdf_tk/commands/_migrate/default_mappings.py +6 -6
- cognite_toolkit/_cdf_tk/commands/_migrate/issues.py +52 -1
- cognite_toolkit/_cdf_tk/commands/_migrate/migration_io.py +377 -11
- cognite_toolkit/_cdf_tk/commands/_migrate/selectors.py +9 -4
- cognite_toolkit/_cdf_tk/commands/_profile.py +1 -1
- cognite_toolkit/_cdf_tk/commands/_purge.py +36 -39
- cognite_toolkit/_cdf_tk/commands/_questionary_style.py +16 -0
- cognite_toolkit/_cdf_tk/commands/_upload.py +109 -86
- cognite_toolkit/_cdf_tk/commands/about.py +221 -0
- cognite_toolkit/_cdf_tk/commands/auth.py +19 -12
- cognite_toolkit/_cdf_tk/commands/build_cmd.py +16 -62
- 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/clean.py +63 -16
- cognite_toolkit/_cdf_tk/commands/deploy.py +20 -17
- cognite_toolkit/_cdf_tk/commands/dump_resource.py +10 -8
- cognite_toolkit/_cdf_tk/commands/init.py +225 -3
- cognite_toolkit/_cdf_tk/commands/modules.py +20 -44
- cognite_toolkit/_cdf_tk/commands/pull.py +6 -19
- cognite_toolkit/_cdf_tk/commands/resources.py +179 -0
- cognite_toolkit/_cdf_tk/commands/run.py +1 -1
- cognite_toolkit/_cdf_tk/constants.py +20 -1
- cognite_toolkit/_cdf_tk/cruds/__init__.py +19 -5
- cognite_toolkit/_cdf_tk/cruds/_base_cruds.py +14 -70
- cognite_toolkit/_cdf_tk/cruds/_data_cruds.py +10 -19
- cognite_toolkit/_cdf_tk/cruds/_resource_cruds/__init__.py +4 -1
- cognite_toolkit/_cdf_tk/cruds/_resource_cruds/agent.py +11 -9
- cognite_toolkit/_cdf_tk/cruds/_resource_cruds/auth.py +5 -15
- cognite_toolkit/_cdf_tk/cruds/_resource_cruds/classic.py +45 -44
- cognite_toolkit/_cdf_tk/cruds/_resource_cruds/configuration.py +5 -12
- cognite_toolkit/_cdf_tk/cruds/_resource_cruds/data_organization.py +4 -13
- cognite_toolkit/_cdf_tk/cruds/_resource_cruds/datamodel.py +206 -67
- cognite_toolkit/_cdf_tk/cruds/_resource_cruds/extraction_pipeline.py +6 -18
- cognite_toolkit/_cdf_tk/cruds/_resource_cruds/fieldops.py +126 -35
- cognite_toolkit/_cdf_tk/cruds/_resource_cruds/file.py +7 -28
- cognite_toolkit/_cdf_tk/cruds/_resource_cruds/function.py +23 -30
- cognite_toolkit/_cdf_tk/cruds/_resource_cruds/hosted_extractors.py +12 -30
- cognite_toolkit/_cdf_tk/cruds/_resource_cruds/industrial_tool.py +4 -8
- cognite_toolkit/_cdf_tk/cruds/_resource_cruds/location.py +4 -16
- cognite_toolkit/_cdf_tk/cruds/_resource_cruds/migration.py +5 -13
- cognite_toolkit/_cdf_tk/cruds/_resource_cruds/raw.py +5 -11
- cognite_toolkit/_cdf_tk/cruds/_resource_cruds/relationship.py +3 -8
- cognite_toolkit/_cdf_tk/cruds/_resource_cruds/robotics.py +16 -45
- cognite_toolkit/_cdf_tk/cruds/_resource_cruds/streams.py +94 -0
- cognite_toolkit/_cdf_tk/cruds/_resource_cruds/three_d_model.py +3 -7
- cognite_toolkit/_cdf_tk/cruds/_resource_cruds/timeseries.py +5 -15
- cognite_toolkit/_cdf_tk/cruds/_resource_cruds/transformation.py +75 -32
- cognite_toolkit/_cdf_tk/cruds/_resource_cruds/workflow.py +20 -40
- cognite_toolkit/_cdf_tk/cruds/_worker.py +24 -36
- cognite_toolkit/_cdf_tk/data_classes/_module_toml.py +1 -0
- cognite_toolkit/_cdf_tk/feature_flags.py +16 -36
- cognite_toolkit/_cdf_tk/plugins.py +2 -1
- cognite_toolkit/_cdf_tk/resource_classes/__init__.py +4 -0
- cognite_toolkit/_cdf_tk/resource_classes/capabilities.py +12 -0
- cognite_toolkit/_cdf_tk/resource_classes/functions.py +3 -1
- cognite_toolkit/_cdf_tk/resource_classes/infield_cdm_location_config.py +109 -0
- cognite_toolkit/_cdf_tk/resource_classes/migration.py +8 -17
- cognite_toolkit/_cdf_tk/resource_classes/search_config.py +1 -1
- cognite_toolkit/_cdf_tk/resource_classes/streams.py +29 -0
- cognite_toolkit/_cdf_tk/resource_classes/workflow_version.py +164 -5
- cognite_toolkit/_cdf_tk/storageio/__init__.py +9 -21
- cognite_toolkit/_cdf_tk/storageio/_annotations.py +19 -16
- cognite_toolkit/_cdf_tk/storageio/_applications.py +340 -28
- cognite_toolkit/_cdf_tk/storageio/_asset_centric.py +67 -104
- cognite_toolkit/_cdf_tk/storageio/_base.py +61 -29
- cognite_toolkit/_cdf_tk/storageio/_datapoints.py +276 -20
- cognite_toolkit/_cdf_tk/storageio/_file_content.py +435 -0
- cognite_toolkit/_cdf_tk/storageio/_instances.py +35 -3
- cognite_toolkit/_cdf_tk/storageio/_raw.py +26 -0
- cognite_toolkit/_cdf_tk/storageio/selectors/__init__.py +71 -4
- cognite_toolkit/_cdf_tk/storageio/selectors/_base.py +14 -2
- cognite_toolkit/_cdf_tk/storageio/selectors/_canvas.py +14 -0
- cognite_toolkit/_cdf_tk/storageio/selectors/_charts.py +14 -0
- cognite_toolkit/_cdf_tk/storageio/selectors/_datapoints.py +23 -3
- cognite_toolkit/_cdf_tk/storageio/selectors/_file_content.py +164 -0
- cognite_toolkit/_cdf_tk/storageio/selectors/_three_d.py +34 -0
- cognite_toolkit/_cdf_tk/tk_warnings/other.py +4 -0
- cognite_toolkit/_cdf_tk/tracker.py +2 -2
- cognite_toolkit/_cdf_tk/utils/cdf.py +1 -1
- cognite_toolkit/_cdf_tk/utils/dtype_conversion.py +9 -3
- cognite_toolkit/_cdf_tk/utils/fileio/__init__.py +2 -0
- cognite_toolkit/_cdf_tk/utils/fileio/_base.py +5 -1
- cognite_toolkit/_cdf_tk/utils/fileio/_readers.py +112 -20
- cognite_toolkit/_cdf_tk/utils/fileio/_writers.py +15 -15
- cognite_toolkit/_cdf_tk/utils/http_client/__init__.py +28 -0
- cognite_toolkit/_cdf_tk/utils/http_client/_client.py +285 -18
- cognite_toolkit/_cdf_tk/utils/http_client/_data_classes.py +56 -4
- cognite_toolkit/_cdf_tk/utils/http_client/_data_classes2.py +247 -0
- cognite_toolkit/_cdf_tk/utils/http_client/_tracker.py +5 -2
- cognite_toolkit/_cdf_tk/utils/interactive_select.py +60 -18
- cognite_toolkit/_cdf_tk/utils/sql_parser.py +2 -3
- cognite_toolkit/_cdf_tk/utils/useful_types.py +6 -2
- cognite_toolkit/_cdf_tk/validation.py +83 -1
- 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 +5 -4
- cognite_toolkit/_version.py +1 -1
- cognite_toolkit/config.dev.yaml +13 -0
- {cognite_toolkit-0.6.97.dist-info → cognite_toolkit-0.7.39.dist-info}/METADATA +24 -24
- cognite_toolkit-0.7.39.dist-info/RECORD +322 -0
- cognite_toolkit-0.7.39.dist-info/WHEEL +4 -0
- {cognite_toolkit-0.6.97.dist-info → cognite_toolkit-0.7.39.dist-info}/entry_points.txt +1 -0
- cognite_toolkit/_cdf_tk/client/api/robotics/__init__.py +0 -3
- cognite_toolkit/_cdf_tk/commands/_migrate/canvas.py +0 -201
- cognite_toolkit/_cdf_tk/commands/dump_data.py +0 -489
- cognite_toolkit/_cdf_tk/commands/featureflag.py +0 -27
- cognite_toolkit/_cdf_tk/prototypes/import_app.py +0 -41
- cognite_toolkit/_cdf_tk/utils/table_writers.py +0 -434
- cognite_toolkit-0.6.97.dist-info/RECORD +0 -306
- cognite_toolkit-0.6.97.dist-info/WHEEL +0 -4
- cognite_toolkit-0.6.97.dist-info/licenses/LICENSE +0 -18
- /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/{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/{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
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
from collections.abc import Iterable, Sequence
|
|
2
|
+
from typing import Any
|
|
2
3
|
|
|
3
|
-
from cognite_toolkit._cdf_tk.client
|
|
4
|
+
from cognite_toolkit._cdf_tk.client import ToolkitClient
|
|
5
|
+
from cognite_toolkit._cdf_tk.client.data_classes.legacy.canvas import (
|
|
4
6
|
IndustrialCanvas,
|
|
5
7
|
IndustrialCanvasApply,
|
|
6
8
|
)
|
|
7
|
-
from cognite_toolkit._cdf_tk.client.data_classes.charts import Chart, ChartList, ChartWrite
|
|
9
|
+
from cognite_toolkit._cdf_tk.client.data_classes.legacy.charts import Chart, ChartList, ChartWrite
|
|
8
10
|
from cognite_toolkit._cdf_tk.exceptions import ToolkitNotImplementedError
|
|
11
|
+
from cognite_toolkit._cdf_tk.tk_warnings import HighSeverityWarning, MediumSeverityWarning
|
|
9
12
|
from cognite_toolkit._cdf_tk.utils.collection import chunker_sequence
|
|
13
|
+
from cognite_toolkit._cdf_tk.utils.http_client import HTTPClient, HTTPMessage, SimpleBodyRequest
|
|
10
14
|
from cognite_toolkit._cdf_tk.utils.useful_types import JsonVal
|
|
11
15
|
|
|
12
|
-
from ._base import Page, UploadableStorageIO
|
|
13
|
-
from .selectors import
|
|
16
|
+
from ._base import Page, UploadableStorageIO, UploadItem
|
|
17
|
+
from .selectors import (
|
|
18
|
+
AllChartsSelector,
|
|
19
|
+
CanvasExternalIdSelector,
|
|
20
|
+
CanvasSelector,
|
|
21
|
+
ChartExternalIdSelector,
|
|
22
|
+
ChartOwnerSelector,
|
|
23
|
+
ChartSelector,
|
|
24
|
+
)
|
|
14
25
|
|
|
15
26
|
|
|
16
27
|
class ChartIO(UploadableStorageIO[ChartSelector, Chart, ChartWrite]):
|
|
@@ -20,6 +31,9 @@ class ChartIO(UploadableStorageIO[ChartSelector, Chart, ChartWrite]):
|
|
|
20
31
|
SUPPORTED_READ_FORMATS = frozenset({".ndjson"})
|
|
21
32
|
CHUNK_SIZE = 10
|
|
22
33
|
BASE_SELECTOR = ChartSelector
|
|
34
|
+
UPLOAD_ENDPOINT_TYPE = "app"
|
|
35
|
+
UPLOAD_ENDPOINT_METHOD = "PUT"
|
|
36
|
+
UPLOAD_ENDPOINT = "/storage/charts/charts"
|
|
23
37
|
|
|
24
38
|
def as_id(self, item: Chart) -> str:
|
|
25
39
|
return item.external_id
|
|
@@ -30,29 +44,15 @@ class ChartIO(UploadableStorageIO[ChartSelector, Chart, ChartWrite]):
|
|
|
30
44
|
...
|
|
31
45
|
elif isinstance(selector, ChartOwnerSelector):
|
|
32
46
|
selected_charts = ChartList([chart for chart in selected_charts if chart.owner_id == selector.owner_id])
|
|
47
|
+
elif isinstance(selector, ChartExternalIdSelector):
|
|
48
|
+
external_id_set = set(selector.external_ids)
|
|
49
|
+
selected_charts = ChartList([chart for chart in selected_charts if chart.external_id in external_id_set])
|
|
33
50
|
else:
|
|
34
51
|
raise ToolkitNotImplementedError(f"Unsupported selector type {type(selector).__name__!r} for ChartIO")
|
|
35
52
|
|
|
36
53
|
if limit is not None:
|
|
37
54
|
selected_charts = ChartList(selected_charts[:limit])
|
|
38
55
|
for chunk in chunker_sequence(selected_charts, self.CHUNK_SIZE):
|
|
39
|
-
ts_ids_to_lookup = {
|
|
40
|
-
ts_ref.ts_id
|
|
41
|
-
for chart in chunk
|
|
42
|
-
for ts_ref in chart.data.time_series_collection or []
|
|
43
|
-
if ts_ref.ts_external_id is None and ts_ref.ts_id is not None
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if ts_ids_to_lookup:
|
|
47
|
-
retrieved_ts = self.client.time_series.retrieve_multiple(
|
|
48
|
-
ids=list(ts_ids_to_lookup), ignore_unknown_ids=True
|
|
49
|
-
)
|
|
50
|
-
id_to_external_id = {ts.id: ts.external_id for ts in retrieved_ts}
|
|
51
|
-
|
|
52
|
-
for chart in chunk:
|
|
53
|
-
for ts_ref in chart.data.time_series_collection or []:
|
|
54
|
-
if ts_ref.ts_id in id_to_external_id:
|
|
55
|
-
ts_ref.ts_external_id = id_to_external_id[ts_ref.ts_id]
|
|
56
56
|
yield Page(worker_id="main", items=chunk)
|
|
57
57
|
|
|
58
58
|
def count(self, selector: ChartSelector) -> int | None:
|
|
@@ -62,13 +62,81 @@ class ChartIO(UploadableStorageIO[ChartSelector, Chart, ChartWrite]):
|
|
|
62
62
|
def data_to_json_chunk(
|
|
63
63
|
self, data_chunk: Sequence[Chart], selector: ChartSelector | None = None
|
|
64
64
|
) -> list[dict[str, JsonVal]]:
|
|
65
|
-
|
|
65
|
+
self._populate_timeseries_id_cache(data_chunk)
|
|
66
|
+
return [self._dump_resource(chart) for chart in data_chunk]
|
|
67
|
+
|
|
68
|
+
def _populate_timeseries_id_cache(self, data_chunk: Sequence[Chart]) -> None:
|
|
69
|
+
timeseries_ids: set[int] = set()
|
|
70
|
+
for chart in data_chunk:
|
|
71
|
+
for item in chart.data.time_series_collection or []:
|
|
72
|
+
if item.ts_id is not None and item.ts_external_id is None:
|
|
73
|
+
# We only look-up the internalID if the externalId is missing
|
|
74
|
+
timeseries_ids.add(item.ts_id)
|
|
75
|
+
if timeseries_ids:
|
|
76
|
+
self.client.lookup.time_series.external_id(list(timeseries_ids))
|
|
77
|
+
|
|
78
|
+
def _dump_resource(self, chart: Chart) -> dict[str, JsonVal]:
|
|
79
|
+
dumped = chart.as_write().dump()
|
|
80
|
+
if isinstance(data := dumped.get("data"), dict) and isinstance(
|
|
81
|
+
collection := data.get("timeSeriesCollection"), list
|
|
82
|
+
):
|
|
83
|
+
for item in collection:
|
|
84
|
+
ts_id = item.pop("tsId", None)
|
|
85
|
+
if ts_id and item.get("tsExternalId") is None:
|
|
86
|
+
# We only look-up the externalID if it is missing
|
|
87
|
+
ts_external_id = self.client.lookup.time_series.external_id(ts_id)
|
|
88
|
+
if ts_external_id is not None:
|
|
89
|
+
item["tsExternalId"] = ts_external_id
|
|
90
|
+
return dumped
|
|
91
|
+
|
|
92
|
+
def json_chunk_to_data(self, data_chunk: list[tuple[str, dict[str, JsonVal]]]) -> Sequence[UploadItem[ChartWrite]]:
|
|
93
|
+
self._populate_timeseries_external_id_cache([item_json for _, item_json in data_chunk])
|
|
94
|
+
return super().json_chunk_to_data(data_chunk)
|
|
66
95
|
|
|
67
96
|
def json_to_resource(self, item_json: dict[str, JsonVal]) -> ChartWrite:
|
|
97
|
+
return self._load_resource(item_json)
|
|
98
|
+
|
|
99
|
+
def _populate_timeseries_external_id_cache(self, item_jsons: Sequence[dict[str, JsonVal]]) -> None:
|
|
100
|
+
timeseries_external_ids: set[str] = set()
|
|
101
|
+
for item_json in item_jsons:
|
|
102
|
+
if isinstance(data := item_json.get("data"), dict) and isinstance(
|
|
103
|
+
collection := data.get("timeSeriesCollection"), list
|
|
104
|
+
):
|
|
105
|
+
for item in collection:
|
|
106
|
+
if not isinstance(item, dict):
|
|
107
|
+
continue
|
|
108
|
+
ts_external_id = item.get("tsExternalId")
|
|
109
|
+
if isinstance(ts_external_id, str):
|
|
110
|
+
timeseries_external_ids.add(ts_external_id)
|
|
111
|
+
if timeseries_external_ids:
|
|
112
|
+
self.client.lookup.time_series.id(list(timeseries_external_ids))
|
|
113
|
+
|
|
114
|
+
def _load_resource(self, item_json: dict[str, JsonVal]) -> ChartWrite:
|
|
115
|
+
if isinstance(data := item_json.get("data"), dict) and isinstance(
|
|
116
|
+
collection := data.get("timeSeriesCollection"), list
|
|
117
|
+
):
|
|
118
|
+
for item in collection:
|
|
119
|
+
if not isinstance(item, dict):
|
|
120
|
+
continue
|
|
121
|
+
ts_external_id = item.get("tsExternalId")
|
|
122
|
+
if isinstance(ts_external_id, str) and item.get("tsId") is None:
|
|
123
|
+
# We only look-up the internalID if it is missing
|
|
124
|
+
ts_id = self.client.lookup.time_series.id(ts_external_id)
|
|
125
|
+
if ts_id is not None:
|
|
126
|
+
item["tsId"] = ts_id
|
|
68
127
|
return ChartWrite._load(item_json)
|
|
69
128
|
|
|
70
129
|
|
|
71
130
|
class CanvasIO(UploadableStorageIO[CanvasSelector, IndustrialCanvas, IndustrialCanvasApply]):
|
|
131
|
+
"""Download and upload Industrial Canvases to/from CDF.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
client (ToolkitClient): The Cognite Toolkit client to use for API interactions.
|
|
135
|
+
exclude_existing_version (bool): Whether to exclude the 'existingVersion' field when uploading canvases.
|
|
136
|
+
Defaults to True. If you set this to False, the upload may fail if the existing version in CDF is
|
|
137
|
+
lower or equal to the one in the uploaded data.
|
|
138
|
+
"""
|
|
139
|
+
|
|
72
140
|
KIND = "IndustrialCanvas"
|
|
73
141
|
SUPPORTED_DOWNLOAD_FORMATS = frozenset({".ndjson"})
|
|
74
142
|
SUPPORTED_COMPRESSIONS = frozenset({".gz"})
|
|
@@ -76,21 +144,265 @@ class CanvasIO(UploadableStorageIO[CanvasSelector, IndustrialCanvas, IndustrialC
|
|
|
76
144
|
CHUNK_SIZE = 10
|
|
77
145
|
BASE_SELECTOR = CanvasSelector
|
|
78
146
|
|
|
147
|
+
def __init__(self, client: ToolkitClient, exclude_existing_version: bool = True) -> None:
|
|
148
|
+
super().__init__(client)
|
|
149
|
+
self.exclude_existing_version = exclude_existing_version
|
|
150
|
+
|
|
79
151
|
def as_id(self, item: IndustrialCanvas) -> str:
|
|
80
152
|
return item.as_id()
|
|
81
153
|
|
|
82
154
|
def stream_data(self, selector: CanvasSelector, limit: int | None = None) -> Iterable[Page]:
|
|
83
|
-
|
|
155
|
+
if not isinstance(selector, CanvasExternalIdSelector):
|
|
156
|
+
raise ToolkitNotImplementedError(f"Unsupported selector type {type(selector).__name__!r} for CanvasIO")
|
|
157
|
+
canvas_ids = selector.external_ids
|
|
158
|
+
if limit is not None and len(canvas_ids) > limit:
|
|
159
|
+
canvas_ids = canvas_ids[:limit]
|
|
160
|
+
|
|
161
|
+
for chunk in chunker_sequence(canvas_ids, self.CHUNK_SIZE):
|
|
162
|
+
items: list[IndustrialCanvas] = []
|
|
163
|
+
for canvas_id in chunk:
|
|
164
|
+
canvas = self.client.canvas.industrial.retrieve(canvas_id)
|
|
165
|
+
if canvas is not None:
|
|
166
|
+
items.append(canvas)
|
|
167
|
+
else:
|
|
168
|
+
MediumSeverityWarning("Canvas with external ID {canvas_id!r} not found. Skipping.").print_warning(
|
|
169
|
+
console=self.client.console
|
|
170
|
+
)
|
|
171
|
+
yield Page(worker_id="main", items=items)
|
|
84
172
|
|
|
85
173
|
def count(self, selector: CanvasSelector) -> int | None:
|
|
86
|
-
|
|
174
|
+
if not isinstance(selector, CanvasExternalIdSelector):
|
|
175
|
+
raise ToolkitNotImplementedError(f"Unsupported selector type {type(selector).__name__!r} for CanvasIO")
|
|
176
|
+
return len(selector.external_ids)
|
|
177
|
+
|
|
178
|
+
def upload_items(
|
|
179
|
+
self,
|
|
180
|
+
data_chunk: Sequence[UploadItem[IndustrialCanvasApply]],
|
|
181
|
+
http_client: HTTPClient,
|
|
182
|
+
selector: CanvasSelector | None = None,
|
|
183
|
+
) -> Sequence[HTTPMessage]:
|
|
184
|
+
config = http_client.config
|
|
185
|
+
results: list[HTTPMessage] = []
|
|
186
|
+
for item in data_chunk:
|
|
187
|
+
instances = item.item.as_instances()
|
|
188
|
+
items: list[dict[str, JsonVal]] = []
|
|
189
|
+
for instance in instances:
|
|
190
|
+
dumped = instance.dump()
|
|
191
|
+
if self.exclude_existing_version:
|
|
192
|
+
dumped.pop("existingVersion", None)
|
|
193
|
+
items.append(dumped)
|
|
194
|
+
|
|
195
|
+
responses = http_client.request_with_retries(
|
|
196
|
+
message=SimpleBodyRequest(
|
|
197
|
+
endpoint_url=config.create_api_url("/models/instances"),
|
|
198
|
+
method="POST",
|
|
199
|
+
# MyPy does not understand that .dump is valid json
|
|
200
|
+
body_content={"items": items}, # type: ignore[dict-item]
|
|
201
|
+
)
|
|
202
|
+
)
|
|
203
|
+
results.extend(responses.as_item_responses(item.source_id))
|
|
204
|
+
|
|
205
|
+
return results
|
|
87
206
|
|
|
88
207
|
def data_to_json_chunk(
|
|
89
208
|
self, data_chunk: Sequence[IndustrialCanvas], selector: CanvasSelector | None = None
|
|
90
209
|
) -> list[dict[str, JsonVal]]:
|
|
91
|
-
|
|
92
|
-
|
|
210
|
+
self._populate_id_cache(data_chunk)
|
|
211
|
+
return [self._dump_resource(canvas) for canvas in data_chunk]
|
|
212
|
+
|
|
213
|
+
def _populate_id_cache(self, data_chunk: Sequence[IndustrialCanvas]) -> None:
|
|
214
|
+
"""Populate the client's lookup cache with all referenced resources in the canvases."""
|
|
215
|
+
asset_ids: set[int] = set()
|
|
216
|
+
time_series_ids: set[int] = set()
|
|
217
|
+
event_ids: set[int] = set()
|
|
218
|
+
file_ids: set[int] = set()
|
|
219
|
+
for canvas in data_chunk:
|
|
220
|
+
for container_ref in canvas.container_references:
|
|
221
|
+
if container_ref.container_reference_type == "asset":
|
|
222
|
+
asset_ids.add(container_ref.resource_id)
|
|
223
|
+
elif container_ref.container_reference_type == "timeseries":
|
|
224
|
+
time_series_ids.add(container_ref.resource_id)
|
|
225
|
+
elif container_ref.container_reference_type == "event":
|
|
226
|
+
event_ids.add(container_ref.resource_id)
|
|
227
|
+
elif container_ref.container_reference_type == "file":
|
|
228
|
+
file_ids.add(container_ref.resource_id)
|
|
229
|
+
if asset_ids:
|
|
230
|
+
self.client.lookup.assets.external_id(list(asset_ids))
|
|
231
|
+
if time_series_ids:
|
|
232
|
+
self.client.lookup.time_series.external_id(list(time_series_ids))
|
|
233
|
+
if event_ids:
|
|
234
|
+
self.client.lookup.events.external_id(list(event_ids))
|
|
235
|
+
if file_ids:
|
|
236
|
+
self.client.lookup.files.external_id(list(file_ids))
|
|
237
|
+
|
|
238
|
+
def _dump_resource(self, canvas: IndustrialCanvas) -> dict[str, JsonVal]:
|
|
239
|
+
dumped = canvas.as_write().dump(keep_existing_version=False)
|
|
240
|
+
references = dumped.get("containerReferences", [])
|
|
241
|
+
if not isinstance(references, list):
|
|
242
|
+
return dumped
|
|
243
|
+
new_container_references: list[Any] = []
|
|
244
|
+
for container_ref in references:
|
|
245
|
+
if not isinstance(container_ref, dict):
|
|
246
|
+
new_container_references.append(container_ref)
|
|
247
|
+
continue
|
|
248
|
+
sources = container_ref.get("sources", [])
|
|
249
|
+
if not isinstance(sources, list) or len(sources) == 0:
|
|
250
|
+
new_container_references.append(container_ref)
|
|
251
|
+
continue
|
|
252
|
+
source = sources[0]
|
|
253
|
+
if not isinstance(source, dict) or "properties" not in source:
|
|
254
|
+
new_container_references.append(container_ref)
|
|
255
|
+
continue
|
|
256
|
+
properties = source["properties"]
|
|
257
|
+
if not isinstance(properties, dict):
|
|
258
|
+
new_container_references.append(container_ref)
|
|
259
|
+
continue
|
|
260
|
+
reference_type = properties.get("containerReferenceType")
|
|
261
|
+
if (
|
|
262
|
+
reference_type
|
|
263
|
+
in {
|
|
264
|
+
"charts",
|
|
265
|
+
"dataGrid",
|
|
266
|
+
}
|
|
267
|
+
): # These container reference types are special cases with a resourceId statically set to -1, which is why we skip them
|
|
268
|
+
new_container_references.append(container_ref)
|
|
269
|
+
continue
|
|
270
|
+
resource_id = properties.pop("resourceId", None)
|
|
271
|
+
if not isinstance(resource_id, int):
|
|
272
|
+
HighSeverityWarning(
|
|
273
|
+
f"Invalid resourceId {resource_id!r} in Canvas {canvas.canvas.name}. Skipping."
|
|
274
|
+
).print_warning(console=self.client.console)
|
|
275
|
+
continue
|
|
276
|
+
if reference_type == "asset":
|
|
277
|
+
external_id = self.client.lookup.assets.external_id(resource_id)
|
|
278
|
+
elif reference_type == "timeseries":
|
|
279
|
+
external_id = self.client.lookup.time_series.external_id(resource_id)
|
|
280
|
+
elif reference_type == "event":
|
|
281
|
+
external_id = self.client.lookup.events.external_id(resource_id)
|
|
282
|
+
elif reference_type == "file":
|
|
283
|
+
external_id = self.client.lookup.files.external_id(resource_id)
|
|
284
|
+
else:
|
|
285
|
+
new_container_references.append(container_ref)
|
|
286
|
+
continue
|
|
287
|
+
if external_id is None:
|
|
288
|
+
HighSeverityWarning(
|
|
289
|
+
f"Failed to look-up {reference_type} external ID for resource ID {resource_id!r}. Skipping resource in Canvas {canvas.canvas.name}"
|
|
290
|
+
).print_warning(console=self.client.console)
|
|
291
|
+
continue
|
|
292
|
+
properties["resourceExternalId"] = external_id
|
|
293
|
+
new_container_references.append(container_ref)
|
|
294
|
+
dumped["containerReferences"] = new_container_references
|
|
295
|
+
return dumped
|
|
296
|
+
|
|
297
|
+
def json_chunk_to_data(
|
|
298
|
+
self, data_chunk: list[tuple[str, dict[str, JsonVal]]]
|
|
299
|
+
) -> Sequence[UploadItem[IndustrialCanvasApply]]:
|
|
300
|
+
self._populate_external_id_cache([item_json for _, item_json in data_chunk])
|
|
301
|
+
return super().json_chunk_to_data(data_chunk)
|
|
302
|
+
|
|
303
|
+
def _populate_external_id_cache(self, item_jsons: Sequence[dict[str, JsonVal]]) -> None:
|
|
304
|
+
"""Populate the client's lookup cache with all referenced resources in the canvases."""
|
|
305
|
+
asset_external_ids: set[str] = set()
|
|
306
|
+
time_series_external_ids: set[str] = set()
|
|
307
|
+
event_external_ids: set[str] = set()
|
|
308
|
+
file_external_ids: set[str] = set()
|
|
309
|
+
for item_json in item_jsons:
|
|
310
|
+
references = item_json.get("containerReferences", [])
|
|
311
|
+
if not isinstance(references, list):
|
|
312
|
+
continue
|
|
313
|
+
for container_ref in references:
|
|
314
|
+
if not isinstance(container_ref, dict):
|
|
315
|
+
continue
|
|
316
|
+
sources = container_ref.get("sources", [])
|
|
317
|
+
if not isinstance(sources, list) or len(sources) == 0:
|
|
318
|
+
continue
|
|
319
|
+
source = sources[0]
|
|
320
|
+
if not isinstance(source, dict) or "properties" not in source:
|
|
321
|
+
continue
|
|
322
|
+
properties = source["properties"]
|
|
323
|
+
if not isinstance(properties, dict):
|
|
324
|
+
continue
|
|
325
|
+
|
|
326
|
+
resource_external_id = properties.get("resourceExternalId")
|
|
327
|
+
if not isinstance(resource_external_id, str):
|
|
328
|
+
continue
|
|
329
|
+
|
|
330
|
+
reference_type = properties.get("containerReferenceType")
|
|
331
|
+
if reference_type == "asset":
|
|
332
|
+
asset_external_ids.add(resource_external_id)
|
|
333
|
+
elif reference_type == "timeseries":
|
|
334
|
+
time_series_external_ids.add(resource_external_id)
|
|
335
|
+
elif reference_type == "event":
|
|
336
|
+
event_external_ids.add(resource_external_id)
|
|
337
|
+
elif reference_type == "file":
|
|
338
|
+
file_external_ids.add(resource_external_id)
|
|
339
|
+
|
|
340
|
+
if asset_external_ids:
|
|
341
|
+
self.client.lookup.assets.id(list(asset_external_ids))
|
|
342
|
+
if time_series_external_ids:
|
|
343
|
+
self.client.lookup.time_series.id(list(time_series_external_ids))
|
|
344
|
+
if event_external_ids:
|
|
345
|
+
self.client.lookup.events.id(list(event_external_ids))
|
|
346
|
+
if file_external_ids:
|
|
347
|
+
self.client.lookup.files.id(list(file_external_ids))
|
|
93
348
|
|
|
94
349
|
def json_to_resource(self, item_json: dict[str, JsonVal]) -> IndustrialCanvasApply:
|
|
95
|
-
|
|
96
|
-
|
|
350
|
+
return self._load_resource(item_json)
|
|
351
|
+
|
|
352
|
+
def _load_resource(self, item_json: dict[str, JsonVal]) -> IndustrialCanvasApply:
|
|
353
|
+
name = self._get_name(item_json)
|
|
354
|
+
references = item_json.get("containerReferences", [])
|
|
355
|
+
if not isinstance(references, list):
|
|
356
|
+
return IndustrialCanvasApply._load(item_json)
|
|
357
|
+
new_container_references: list[Any] = []
|
|
358
|
+
for container_ref in references:
|
|
359
|
+
if not isinstance(container_ref, dict):
|
|
360
|
+
new_container_references.append(container_ref)
|
|
361
|
+
continue
|
|
362
|
+
sources = container_ref.get("sources", [])
|
|
363
|
+
if not isinstance(sources, list) or len(sources) == 0:
|
|
364
|
+
new_container_references.append(container_ref)
|
|
365
|
+
continue
|
|
366
|
+
source = sources[0]
|
|
367
|
+
if not isinstance(source, dict) or "properties" not in source:
|
|
368
|
+
new_container_references.append(container_ref)
|
|
369
|
+
continue
|
|
370
|
+
properties = source["properties"]
|
|
371
|
+
if not isinstance(properties, dict):
|
|
372
|
+
new_container_references.append(container_ref)
|
|
373
|
+
continue
|
|
374
|
+
resource_external_id = properties.pop("resourceExternalId", None)
|
|
375
|
+
if not isinstance(resource_external_id, str):
|
|
376
|
+
new_container_references.append(container_ref)
|
|
377
|
+
continue
|
|
378
|
+
reference_type = properties.get("containerReferenceType")
|
|
379
|
+
if reference_type == "asset":
|
|
380
|
+
resource_id = self.client.lookup.assets.id(resource_external_id)
|
|
381
|
+
elif reference_type == "timeseries":
|
|
382
|
+
resource_id = self.client.lookup.time_series.id(resource_external_id)
|
|
383
|
+
elif reference_type == "event":
|
|
384
|
+
resource_id = self.client.lookup.events.id(resource_external_id)
|
|
385
|
+
elif reference_type == "file":
|
|
386
|
+
resource_id = self.client.lookup.files.id(resource_external_id)
|
|
387
|
+
else:
|
|
388
|
+
new_container_references.append(container_ref)
|
|
389
|
+
continue
|
|
390
|
+
if resource_id is None:
|
|
391
|
+
# Failed look-up, skip the resourceId setting
|
|
392
|
+
HighSeverityWarning(
|
|
393
|
+
f"Failed to look-up {reference_type} ID for external ID {resource_external_id!r}. Skipping resource in Canvas {name}"
|
|
394
|
+
).print_warning(console=self.client.console)
|
|
395
|
+
continue
|
|
396
|
+
properties["resourceId"] = resource_id
|
|
397
|
+
new_container_references.append(container_ref)
|
|
398
|
+
new_item = dict(item_json)
|
|
399
|
+
new_item["containerReferences"] = new_container_references
|
|
400
|
+
|
|
401
|
+
return IndustrialCanvasApply._load(new_item)
|
|
402
|
+
|
|
403
|
+
@classmethod
|
|
404
|
+
def _get_name(cls, item_json: dict[str, JsonVal]) -> str:
|
|
405
|
+
try:
|
|
406
|
+
return item_json["canvas"]["sources"][0]["properties"]["name"] # type: ignore[index,return-value, call-overload]
|
|
407
|
+
except (KeyError, IndexError, TypeError):
|
|
408
|
+
return "<unknown>"
|