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.
Files changed (198) hide show
  1. cognite_toolkit/_cdf.py +21 -23
  2. cognite_toolkit/_cdf_tk/apps/__init__.py +4 -0
  3. cognite_toolkit/_cdf_tk/apps/_core_app.py +19 -5
  4. cognite_toolkit/_cdf_tk/apps/_data_app.py +1 -1
  5. cognite_toolkit/_cdf_tk/apps/_dev_app.py +86 -0
  6. cognite_toolkit/_cdf_tk/apps/_download_app.py +693 -25
  7. cognite_toolkit/_cdf_tk/apps/_dump_app.py +44 -102
  8. cognite_toolkit/_cdf_tk/apps/_import_app.py +41 -0
  9. cognite_toolkit/_cdf_tk/apps/_landing_app.py +18 -4
  10. cognite_toolkit/_cdf_tk/apps/_migrate_app.py +424 -9
  11. cognite_toolkit/_cdf_tk/apps/_modules_app.py +0 -3
  12. cognite_toolkit/_cdf_tk/apps/_purge.py +15 -43
  13. cognite_toolkit/_cdf_tk/apps/_run.py +11 -0
  14. cognite_toolkit/_cdf_tk/apps/_upload_app.py +45 -6
  15. cognite_toolkit/_cdf_tk/builders/__init__.py +2 -2
  16. cognite_toolkit/_cdf_tk/builders/_base.py +28 -42
  17. cognite_toolkit/_cdf_tk/builders/_raw.py +1 -1
  18. cognite_toolkit/_cdf_tk/cdf_toml.py +20 -1
  19. cognite_toolkit/_cdf_tk/client/_toolkit_client.py +32 -12
  20. cognite_toolkit/_cdf_tk/client/api/infield.py +114 -17
  21. cognite_toolkit/_cdf_tk/client/api/{canvas.py → legacy/canvas.py} +15 -7
  22. cognite_toolkit/_cdf_tk/client/api/{charts.py → legacy/charts.py} +1 -1
  23. cognite_toolkit/_cdf_tk/client/api/{extended_data_modeling.py → legacy/extended_data_modeling.py} +1 -1
  24. cognite_toolkit/_cdf_tk/client/api/{extended_files.py → legacy/extended_files.py} +2 -2
  25. cognite_toolkit/_cdf_tk/client/api/{extended_functions.py → legacy/extended_functions.py} +15 -18
  26. cognite_toolkit/_cdf_tk/client/api/{extended_raw.py → legacy/extended_raw.py} +1 -1
  27. cognite_toolkit/_cdf_tk/client/api/{extended_timeseries.py → legacy/extended_timeseries.py} +5 -2
  28. cognite_toolkit/_cdf_tk/client/api/{location_filters.py → legacy/location_filters.py} +1 -1
  29. cognite_toolkit/_cdf_tk/client/api/legacy/robotics/__init__.py +8 -0
  30. cognite_toolkit/_cdf_tk/client/api/{robotics → legacy/robotics}/capabilities.py +1 -1
  31. cognite_toolkit/_cdf_tk/client/api/{robotics → legacy/robotics}/data_postprocessing.py +1 -1
  32. cognite_toolkit/_cdf_tk/client/api/{robotics → legacy/robotics}/frames.py +1 -1
  33. cognite_toolkit/_cdf_tk/client/api/{robotics → legacy/robotics}/locations.py +1 -1
  34. cognite_toolkit/_cdf_tk/client/api/{robotics → legacy/robotics}/maps.py +1 -1
  35. cognite_toolkit/_cdf_tk/client/api/{robotics → legacy/robotics}/robots.py +2 -2
  36. cognite_toolkit/_cdf_tk/client/api/{search_config.py → legacy/search_config.py} +5 -1
  37. cognite_toolkit/_cdf_tk/client/api/migration.py +177 -4
  38. cognite_toolkit/_cdf_tk/client/api/project.py +9 -8
  39. cognite_toolkit/_cdf_tk/client/api/search.py +2 -2
  40. cognite_toolkit/_cdf_tk/client/api/streams.py +88 -0
  41. cognite_toolkit/_cdf_tk/client/api/three_d.py +384 -0
  42. cognite_toolkit/_cdf_tk/client/data_classes/api_classes.py +13 -0
  43. cognite_toolkit/_cdf_tk/client/data_classes/base.py +37 -33
  44. cognite_toolkit/_cdf_tk/client/data_classes/charts_data.py +95 -213
  45. cognite_toolkit/_cdf_tk/client/data_classes/infield.py +32 -18
  46. cognite_toolkit/_cdf_tk/client/data_classes/instance_api.py +18 -13
  47. cognite_toolkit/_cdf_tk/client/data_classes/legacy/__init__.py +0 -0
  48. cognite_toolkit/_cdf_tk/client/data_classes/{canvas.py → legacy/canvas.py} +47 -4
  49. cognite_toolkit/_cdf_tk/client/data_classes/{charts.py → legacy/charts.py} +3 -3
  50. cognite_toolkit/_cdf_tk/client/data_classes/{migration.py → legacy/migration.py} +10 -2
  51. cognite_toolkit/_cdf_tk/client/data_classes/streams.py +90 -0
  52. cognite_toolkit/_cdf_tk/client/data_classes/three_d.py +112 -0
  53. cognite_toolkit/_cdf_tk/client/testing.py +42 -18
  54. cognite_toolkit/_cdf_tk/commands/__init__.py +7 -6
  55. cognite_toolkit/_cdf_tk/commands/_changes.py +3 -42
  56. cognite_toolkit/_cdf_tk/commands/_download.py +21 -11
  57. cognite_toolkit/_cdf_tk/commands/_migrate/__init__.py +0 -2
  58. cognite_toolkit/_cdf_tk/commands/_migrate/command.py +22 -20
  59. cognite_toolkit/_cdf_tk/commands/_migrate/conversion.py +140 -92
  60. cognite_toolkit/_cdf_tk/commands/_migrate/creators.py +1 -1
  61. cognite_toolkit/_cdf_tk/commands/_migrate/data_classes.py +108 -26
  62. cognite_toolkit/_cdf_tk/commands/_migrate/data_mapper.py +448 -45
  63. cognite_toolkit/_cdf_tk/commands/_migrate/data_model.py +1 -0
  64. cognite_toolkit/_cdf_tk/commands/_migrate/default_mappings.py +6 -6
  65. cognite_toolkit/_cdf_tk/commands/_migrate/issues.py +52 -1
  66. cognite_toolkit/_cdf_tk/commands/_migrate/migration_io.py +377 -11
  67. cognite_toolkit/_cdf_tk/commands/_migrate/selectors.py +9 -4
  68. cognite_toolkit/_cdf_tk/commands/_profile.py +1 -1
  69. cognite_toolkit/_cdf_tk/commands/_purge.py +36 -39
  70. cognite_toolkit/_cdf_tk/commands/_questionary_style.py +16 -0
  71. cognite_toolkit/_cdf_tk/commands/_upload.py +109 -86
  72. cognite_toolkit/_cdf_tk/commands/about.py +221 -0
  73. cognite_toolkit/_cdf_tk/commands/auth.py +19 -12
  74. cognite_toolkit/_cdf_tk/commands/build_cmd.py +16 -62
  75. cognite_toolkit/_cdf_tk/commands/build_v2/__init__.py +0 -0
  76. cognite_toolkit/_cdf_tk/commands/build_v2/build_cmd.py +241 -0
  77. cognite_toolkit/_cdf_tk/commands/build_v2/build_input.py +85 -0
  78. cognite_toolkit/_cdf_tk/commands/build_v2/build_issues.py +27 -0
  79. cognite_toolkit/_cdf_tk/commands/clean.py +63 -16
  80. cognite_toolkit/_cdf_tk/commands/deploy.py +20 -17
  81. cognite_toolkit/_cdf_tk/commands/dump_resource.py +10 -8
  82. cognite_toolkit/_cdf_tk/commands/init.py +225 -3
  83. cognite_toolkit/_cdf_tk/commands/modules.py +20 -44
  84. cognite_toolkit/_cdf_tk/commands/pull.py +6 -19
  85. cognite_toolkit/_cdf_tk/commands/resources.py +179 -0
  86. cognite_toolkit/_cdf_tk/commands/run.py +1 -1
  87. cognite_toolkit/_cdf_tk/constants.py +20 -1
  88. cognite_toolkit/_cdf_tk/cruds/__init__.py +19 -5
  89. cognite_toolkit/_cdf_tk/cruds/_base_cruds.py +14 -70
  90. cognite_toolkit/_cdf_tk/cruds/_data_cruds.py +10 -19
  91. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/__init__.py +4 -1
  92. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/agent.py +11 -9
  93. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/auth.py +5 -15
  94. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/classic.py +45 -44
  95. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/configuration.py +5 -12
  96. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/data_organization.py +4 -13
  97. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/datamodel.py +206 -67
  98. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/extraction_pipeline.py +6 -18
  99. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/fieldops.py +126 -35
  100. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/file.py +7 -28
  101. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/function.py +23 -30
  102. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/hosted_extractors.py +12 -30
  103. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/industrial_tool.py +4 -8
  104. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/location.py +4 -16
  105. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/migration.py +5 -13
  106. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/raw.py +5 -11
  107. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/relationship.py +3 -8
  108. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/robotics.py +16 -45
  109. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/streams.py +94 -0
  110. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/three_d_model.py +3 -7
  111. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/timeseries.py +5 -15
  112. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/transformation.py +75 -32
  113. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/workflow.py +20 -40
  114. cognite_toolkit/_cdf_tk/cruds/_worker.py +24 -36
  115. cognite_toolkit/_cdf_tk/data_classes/_module_toml.py +1 -0
  116. cognite_toolkit/_cdf_tk/feature_flags.py +16 -36
  117. cognite_toolkit/_cdf_tk/plugins.py +2 -1
  118. cognite_toolkit/_cdf_tk/resource_classes/__init__.py +4 -0
  119. cognite_toolkit/_cdf_tk/resource_classes/capabilities.py +12 -0
  120. cognite_toolkit/_cdf_tk/resource_classes/functions.py +3 -1
  121. cognite_toolkit/_cdf_tk/resource_classes/infield_cdm_location_config.py +109 -0
  122. cognite_toolkit/_cdf_tk/resource_classes/migration.py +8 -17
  123. cognite_toolkit/_cdf_tk/resource_classes/search_config.py +1 -1
  124. cognite_toolkit/_cdf_tk/resource_classes/streams.py +29 -0
  125. cognite_toolkit/_cdf_tk/resource_classes/workflow_version.py +164 -5
  126. cognite_toolkit/_cdf_tk/storageio/__init__.py +9 -21
  127. cognite_toolkit/_cdf_tk/storageio/_annotations.py +19 -16
  128. cognite_toolkit/_cdf_tk/storageio/_applications.py +340 -28
  129. cognite_toolkit/_cdf_tk/storageio/_asset_centric.py +67 -104
  130. cognite_toolkit/_cdf_tk/storageio/_base.py +61 -29
  131. cognite_toolkit/_cdf_tk/storageio/_datapoints.py +276 -20
  132. cognite_toolkit/_cdf_tk/storageio/_file_content.py +435 -0
  133. cognite_toolkit/_cdf_tk/storageio/_instances.py +35 -3
  134. cognite_toolkit/_cdf_tk/storageio/_raw.py +26 -0
  135. cognite_toolkit/_cdf_tk/storageio/selectors/__init__.py +71 -4
  136. cognite_toolkit/_cdf_tk/storageio/selectors/_base.py +14 -2
  137. cognite_toolkit/_cdf_tk/storageio/selectors/_canvas.py +14 -0
  138. cognite_toolkit/_cdf_tk/storageio/selectors/_charts.py +14 -0
  139. cognite_toolkit/_cdf_tk/storageio/selectors/_datapoints.py +23 -3
  140. cognite_toolkit/_cdf_tk/storageio/selectors/_file_content.py +164 -0
  141. cognite_toolkit/_cdf_tk/storageio/selectors/_three_d.py +34 -0
  142. cognite_toolkit/_cdf_tk/tk_warnings/other.py +4 -0
  143. cognite_toolkit/_cdf_tk/tracker.py +2 -2
  144. cognite_toolkit/_cdf_tk/utils/cdf.py +1 -1
  145. cognite_toolkit/_cdf_tk/utils/dtype_conversion.py +9 -3
  146. cognite_toolkit/_cdf_tk/utils/fileio/__init__.py +2 -0
  147. cognite_toolkit/_cdf_tk/utils/fileio/_base.py +5 -1
  148. cognite_toolkit/_cdf_tk/utils/fileio/_readers.py +112 -20
  149. cognite_toolkit/_cdf_tk/utils/fileio/_writers.py +15 -15
  150. cognite_toolkit/_cdf_tk/utils/http_client/__init__.py +28 -0
  151. cognite_toolkit/_cdf_tk/utils/http_client/_client.py +285 -18
  152. cognite_toolkit/_cdf_tk/utils/http_client/_data_classes.py +56 -4
  153. cognite_toolkit/_cdf_tk/utils/http_client/_data_classes2.py +247 -0
  154. cognite_toolkit/_cdf_tk/utils/http_client/_tracker.py +5 -2
  155. cognite_toolkit/_cdf_tk/utils/interactive_select.py +60 -18
  156. cognite_toolkit/_cdf_tk/utils/sql_parser.py +2 -3
  157. cognite_toolkit/_cdf_tk/utils/useful_types.py +6 -2
  158. cognite_toolkit/_cdf_tk/validation.py +83 -1
  159. cognite_toolkit/_repo_files/GitHub/.github/workflows/deploy.yaml +1 -1
  160. cognite_toolkit/_repo_files/GitHub/.github/workflows/dry-run.yaml +1 -1
  161. cognite_toolkit/_resources/cdf.toml +5 -4
  162. cognite_toolkit/_version.py +1 -1
  163. cognite_toolkit/config.dev.yaml +13 -0
  164. {cognite_toolkit-0.6.97.dist-info → cognite_toolkit-0.7.39.dist-info}/METADATA +24 -24
  165. cognite_toolkit-0.7.39.dist-info/RECORD +322 -0
  166. cognite_toolkit-0.7.39.dist-info/WHEEL +4 -0
  167. {cognite_toolkit-0.6.97.dist-info → cognite_toolkit-0.7.39.dist-info}/entry_points.txt +1 -0
  168. cognite_toolkit/_cdf_tk/client/api/robotics/__init__.py +0 -3
  169. cognite_toolkit/_cdf_tk/commands/_migrate/canvas.py +0 -201
  170. cognite_toolkit/_cdf_tk/commands/dump_data.py +0 -489
  171. cognite_toolkit/_cdf_tk/commands/featureflag.py +0 -27
  172. cognite_toolkit/_cdf_tk/prototypes/import_app.py +0 -41
  173. cognite_toolkit/_cdf_tk/utils/table_writers.py +0 -434
  174. cognite_toolkit-0.6.97.dist-info/RECORD +0 -306
  175. cognite_toolkit-0.6.97.dist-info/WHEEL +0 -4
  176. cognite_toolkit-0.6.97.dist-info/licenses/LICENSE +0 -18
  177. /cognite_toolkit/_cdf_tk/{prototypes/commands → client/api/legacy}/__init__.py +0 -0
  178. /cognite_toolkit/_cdf_tk/client/api/{dml.py → legacy/dml.py} +0 -0
  179. /cognite_toolkit/_cdf_tk/client/api/{fixed_transformations.py → legacy/fixed_transformations.py} +0 -0
  180. /cognite_toolkit/_cdf_tk/client/api/{robotics → legacy/robotics}/api.py +0 -0
  181. /cognite_toolkit/_cdf_tk/client/api/{robotics → legacy/robotics}/utlis.py +0 -0
  182. /cognite_toolkit/_cdf_tk/client/data_classes/{apm_config_v1.py → legacy/apm_config_v1.py} +0 -0
  183. /cognite_toolkit/_cdf_tk/client/data_classes/{extendable_cognite_file.py → legacy/extendable_cognite_file.py} +0 -0
  184. /cognite_toolkit/_cdf_tk/client/data_classes/{extended_filemetadata.py → legacy/extended_filemetadata.py} +0 -0
  185. /cognite_toolkit/_cdf_tk/client/data_classes/{extended_filemetdata.py → legacy/extended_filemetdata.py} +0 -0
  186. /cognite_toolkit/_cdf_tk/client/data_classes/{extended_timeseries.py → legacy/extended_timeseries.py} +0 -0
  187. /cognite_toolkit/_cdf_tk/client/data_classes/{functions.py → legacy/functions.py} +0 -0
  188. /cognite_toolkit/_cdf_tk/client/data_classes/{graphql_data_models.py → legacy/graphql_data_models.py} +0 -0
  189. /cognite_toolkit/_cdf_tk/client/data_classes/{instances.py → legacy/instances.py} +0 -0
  190. /cognite_toolkit/_cdf_tk/client/data_classes/{location_filters.py → legacy/location_filters.py} +0 -0
  191. /cognite_toolkit/_cdf_tk/client/data_classes/{pending_instances_ids.py → legacy/pending_instances_ids.py} +0 -0
  192. /cognite_toolkit/_cdf_tk/client/data_classes/{project.py → legacy/project.py} +0 -0
  193. /cognite_toolkit/_cdf_tk/client/data_classes/{raw.py → legacy/raw.py} +0 -0
  194. /cognite_toolkit/_cdf_tk/client/data_classes/{robotics.py → legacy/robotics.py} +0 -0
  195. /cognite_toolkit/_cdf_tk/client/data_classes/{search_config.py → legacy/search_config.py} +0 -0
  196. /cognite_toolkit/_cdf_tk/client/data_classes/{sequences.py → legacy/sequences.py} +0 -0
  197. /cognite_toolkit/_cdf_tk/client/data_classes/{streamlit_.py → legacy/streamlit_.py} +0 -0
  198. /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.data_classes.canvas import (
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 AllChartsSelector, CanvasSelector, ChartOwnerSelector, ChartSelector
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
- return [chart.as_write().dump() for chart in data_chunk]
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
- raise ToolkitNotImplementedError("Streaming canvases is not implemented yet.")
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
- raise ToolkitNotImplementedError("Counting canvases is not implemented yet.")
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
- # Need to do lookup to get external IDs for all asset-centric resources.
92
- raise ToolkitNotImplementedError("Exporting canvases is not implemented yet.")
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
- # Need to do lookup to get external IDs for all asset-centric resources.
96
- raise ToolkitNotImplementedError("Importing canvases is not implemented yet.")
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>"