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
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import mimetypes
|
|
3
|
+
from collections.abc import Iterable, MutableSequence, Sequence
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import cast
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
from cognite.client.data_classes import FileMetadata, FileMetadataWrite
|
|
10
|
+
from cognite.client.data_classes.data_modeling import NodeId, ViewId
|
|
11
|
+
|
|
12
|
+
from cognite_toolkit._cdf_tk.client import ToolkitClient
|
|
13
|
+
from cognite_toolkit._cdf_tk.cruds import FileMetadataCRUD
|
|
14
|
+
from cognite_toolkit._cdf_tk.exceptions import ToolkitNotImplementedError
|
|
15
|
+
from cognite_toolkit._cdf_tk.protocols import ResourceResponseProtocol
|
|
16
|
+
from cognite_toolkit._cdf_tk.utils import sanitize_filename
|
|
17
|
+
from cognite_toolkit._cdf_tk.utils.collection import chunker, chunker_sequence
|
|
18
|
+
from cognite_toolkit._cdf_tk.utils.fileio import MultiFileReader
|
|
19
|
+
from cognite_toolkit._cdf_tk.utils.http_client import (
|
|
20
|
+
DataBodyRequest,
|
|
21
|
+
ErrorDetails,
|
|
22
|
+
FailedResponse,
|
|
23
|
+
FailedResponseItems,
|
|
24
|
+
HTTPClient,
|
|
25
|
+
HTTPMessage,
|
|
26
|
+
ResponseList,
|
|
27
|
+
SimpleBodyRequest,
|
|
28
|
+
)
|
|
29
|
+
from cognite_toolkit._cdf_tk.utils.useful_types import JsonVal
|
|
30
|
+
|
|
31
|
+
from ._base import Page, UploadableStorageIO, UploadItem
|
|
32
|
+
from .selectors import FileContentSelector, FileIdentifierSelector, FileMetadataTemplateSelector
|
|
33
|
+
from .selectors._file_content import (
|
|
34
|
+
FILEPATH,
|
|
35
|
+
FileDataModelingTemplateSelector,
|
|
36
|
+
FileExternalID,
|
|
37
|
+
FileIdentifier,
|
|
38
|
+
FileInstanceID,
|
|
39
|
+
FileInternalID,
|
|
40
|
+
FileTemplateSelector,
|
|
41
|
+
)
|
|
42
|
+
from .selectors._file_content import NodeId as SelectorNodeId
|
|
43
|
+
|
|
44
|
+
COGNITE_FILE_VIEW = ViewId("cdf_cdm", "CogniteFile", "v1")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class UploadFileContentItem(UploadItem[FileMetadataWrite]):
|
|
49
|
+
file_path: Path
|
|
50
|
+
mime_type: str
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass
|
|
54
|
+
class MetadataWithFilePath(ResourceResponseProtocol):
|
|
55
|
+
metadata: FileMetadata
|
|
56
|
+
file_path: Path
|
|
57
|
+
|
|
58
|
+
def as_write(self) -> FileMetadataWrite:
|
|
59
|
+
return self.metadata.as_write()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class FileContentIO(UploadableStorageIO[FileContentSelector, MetadataWithFilePath, FileMetadataWrite]):
|
|
63
|
+
SUPPORTED_DOWNLOAD_FORMATS = frozenset({".ndjson"})
|
|
64
|
+
SUPPORTED_COMPRESSIONS = frozenset({".gz"})
|
|
65
|
+
CHUNK_SIZE = 10
|
|
66
|
+
BASE_SELECTOR = FileContentSelector
|
|
67
|
+
KIND = "FileContent"
|
|
68
|
+
SUPPORTED_READ_FORMATS = frozenset({".ndjson"})
|
|
69
|
+
UPLOAD_ENDPOINT = "/files"
|
|
70
|
+
|
|
71
|
+
def __init__(self, client: ToolkitClient, target_dir: Path = Path.cwd()) -> None:
|
|
72
|
+
super().__init__(client)
|
|
73
|
+
self._crud = FileMetadataCRUD(client, None, None)
|
|
74
|
+
self._target_dir = target_dir
|
|
75
|
+
|
|
76
|
+
def as_id(self, item: MetadataWithFilePath) -> str:
|
|
77
|
+
return item.metadata.external_id or str(item.metadata.id)
|
|
78
|
+
|
|
79
|
+
def stream_data(
|
|
80
|
+
self, selector: FileContentSelector, limit: int | None = None
|
|
81
|
+
) -> Iterable[Page[MetadataWithFilePath]]:
|
|
82
|
+
if not isinstance(selector, FileIdentifierSelector):
|
|
83
|
+
raise ToolkitNotImplementedError(
|
|
84
|
+
f"Download with the manifest, {type(selector).__name__}, is not supported for FileContentIO"
|
|
85
|
+
)
|
|
86
|
+
selected_identifiers = selector.identifiers
|
|
87
|
+
if limit is not None and limit < len(selected_identifiers):
|
|
88
|
+
selected_identifiers = selected_identifiers[:limit]
|
|
89
|
+
for identifiers in chunker_sequence(selected_identifiers, self.CHUNK_SIZE):
|
|
90
|
+
metadata = self._retrieve_metadata(identifiers)
|
|
91
|
+
if metadata is None:
|
|
92
|
+
continue
|
|
93
|
+
identifiers_map = self._as_metadata_map(metadata)
|
|
94
|
+
downloaded_files: list[MetadataWithFilePath] = []
|
|
95
|
+
for identifier in identifiers:
|
|
96
|
+
if identifier not in identifiers_map:
|
|
97
|
+
continue
|
|
98
|
+
|
|
99
|
+
meta = identifiers_map[identifier]
|
|
100
|
+
filepath = self._create_filepath(meta, selector)
|
|
101
|
+
download_url = self._retrieve_download_url(identifier)
|
|
102
|
+
if download_url is None:
|
|
103
|
+
continue
|
|
104
|
+
filepath.parent.mkdir(parents=True, exist_ok=True)
|
|
105
|
+
with httpx.stream("GET", download_url) as response:
|
|
106
|
+
if response.status_code != 200:
|
|
107
|
+
continue
|
|
108
|
+
with filepath.open(mode="wb") as file_stream:
|
|
109
|
+
for chunk in response.iter_bytes(chunk_size=8192):
|
|
110
|
+
file_stream.write(chunk)
|
|
111
|
+
downloaded_files.append(
|
|
112
|
+
MetadataWithFilePath(
|
|
113
|
+
metadata=meta,
|
|
114
|
+
file_path=filepath.relative_to(self._target_dir),
|
|
115
|
+
)
|
|
116
|
+
)
|
|
117
|
+
yield Page(items=downloaded_files, worker_id="Main")
|
|
118
|
+
|
|
119
|
+
def _retrieve_metadata(self, identifiers: Sequence[FileIdentifier]) -> Sequence[FileMetadata] | None:
|
|
120
|
+
config = self.client.config
|
|
121
|
+
responses = self.client.http_client.request_with_retries(
|
|
122
|
+
message=SimpleBodyRequest(
|
|
123
|
+
endpoint_url=config.create_api_url("/files/byids"),
|
|
124
|
+
method="POST",
|
|
125
|
+
body_content={
|
|
126
|
+
"items": [
|
|
127
|
+
identifier.model_dump(mode="json", by_alias=True, exclude={"id_type"})
|
|
128
|
+
for identifier in identifiers
|
|
129
|
+
],
|
|
130
|
+
"ignoreUnknownIds": True,
|
|
131
|
+
},
|
|
132
|
+
)
|
|
133
|
+
)
|
|
134
|
+
if responses.has_failed:
|
|
135
|
+
return None
|
|
136
|
+
body = responses.get_first_body()
|
|
137
|
+
items_data = body.get("items", [])
|
|
138
|
+
if not isinstance(items_data, list):
|
|
139
|
+
return None
|
|
140
|
+
# MyPy does not understand that JsonVal is valid dict[Any, Any]
|
|
141
|
+
return [FileMetadata._load(item) for item in items_data] # type: ignore[arg-type]
|
|
142
|
+
|
|
143
|
+
@staticmethod
|
|
144
|
+
def _as_metadata_map(metadata: Sequence[FileMetadata]) -> dict[FileIdentifier, FileMetadata]:
|
|
145
|
+
identifiers_map: dict[FileIdentifier, FileMetadata] = {}
|
|
146
|
+
for item in metadata:
|
|
147
|
+
if item.id is not None:
|
|
148
|
+
identifiers_map[FileInternalID(internal_id=item.id)] = item
|
|
149
|
+
if item.external_id is not None:
|
|
150
|
+
identifiers_map[FileExternalID(external_id=item.external_id)] = item
|
|
151
|
+
if item.instance_id is not None:
|
|
152
|
+
identifiers_map[
|
|
153
|
+
FileInstanceID(
|
|
154
|
+
instance_id=SelectorNodeId(
|
|
155
|
+
space=item.instance_id.space, external_id=item.instance_id.external_id
|
|
156
|
+
)
|
|
157
|
+
)
|
|
158
|
+
] = item
|
|
159
|
+
return identifiers_map
|
|
160
|
+
|
|
161
|
+
def _create_filepath(self, meta: FileMetadata, selector: FileIdentifierSelector) -> Path:
|
|
162
|
+
# We now that metadata always have name set
|
|
163
|
+
filename = Path(sanitize_filename(cast(str, meta.name)))
|
|
164
|
+
if len(filename.suffix) == 0 and meta.mime_type:
|
|
165
|
+
if mime_ext := mimetypes.guess_extension(meta.mime_type):
|
|
166
|
+
filename = filename.with_suffix(mime_ext)
|
|
167
|
+
directory = sanitize_filename(selector.file_directory)
|
|
168
|
+
if isinstance(meta.directory, str) and meta.directory != "":
|
|
169
|
+
directory = sanitize_filename(meta.directory.removeprefix("/"))
|
|
170
|
+
|
|
171
|
+
counter = 1
|
|
172
|
+
filepath = self._target_dir / directory / filename
|
|
173
|
+
while filepath.exists():
|
|
174
|
+
filepath = self._target_dir / directory / f"{filename} ({counter})"
|
|
175
|
+
counter += 1
|
|
176
|
+
|
|
177
|
+
return filepath
|
|
178
|
+
|
|
179
|
+
def _retrieve_download_url(self, identifier: FileIdentifier) -> str | None:
|
|
180
|
+
config = self.client.config
|
|
181
|
+
responses = self.client.http_client.request_with_retries(
|
|
182
|
+
message=SimpleBodyRequest(
|
|
183
|
+
endpoint_url=config.create_api_url("/files/downloadlink"),
|
|
184
|
+
method="POST",
|
|
185
|
+
body_content={"items": [identifier.model_dump(mode="json", by_alias=True, exclude={"id_type"})]},
|
|
186
|
+
)
|
|
187
|
+
)
|
|
188
|
+
try:
|
|
189
|
+
body = responses.get_first_body()
|
|
190
|
+
except ValueError:
|
|
191
|
+
return None
|
|
192
|
+
if "items" in body and isinstance(body["items"], list) and len(body["items"]) > 0:
|
|
193
|
+
# The API responses is not following the API docs, this is a workaround
|
|
194
|
+
body = body["items"][0] # type: ignore[assignment]
|
|
195
|
+
try:
|
|
196
|
+
return cast(str, body["downloadUrl"])
|
|
197
|
+
except (KeyError, IndexError):
|
|
198
|
+
return None
|
|
199
|
+
|
|
200
|
+
def count(self, selector: FileContentSelector) -> int | None:
|
|
201
|
+
if isinstance(selector, FileIdentifierSelector):
|
|
202
|
+
return len(selector.identifiers)
|
|
203
|
+
return None
|
|
204
|
+
|
|
205
|
+
def data_to_json_chunk(
|
|
206
|
+
self, data_chunk: Sequence[MetadataWithFilePath], selector: FileContentSelector | None = None
|
|
207
|
+
) -> list[dict[str, JsonVal]]:
|
|
208
|
+
"""Convert a writable Cognite resource list to a JSON-compatible chunk of data.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
data_chunk: A writable Cognite resource list representing the data.
|
|
212
|
+
selector: The selector used for the data. (Not used in this implementation)
|
|
213
|
+
Returns:
|
|
214
|
+
A list of dictionaries, each representing the data in a JSON-compatible format.
|
|
215
|
+
"""
|
|
216
|
+
result: list[dict[str, JsonVal]] = []
|
|
217
|
+
for item in data_chunk:
|
|
218
|
+
item_json = self._crud.dump_resource(item.metadata)
|
|
219
|
+
item_json[FILEPATH] = item.file_path.as_posix()
|
|
220
|
+
result.append(item_json)
|
|
221
|
+
return result
|
|
222
|
+
|
|
223
|
+
def json_chunk_to_data(self, data_chunk: list[tuple[str, dict[str, JsonVal]]]) -> Sequence[UploadFileContentItem]:
|
|
224
|
+
"""Convert a JSON-compatible chunk of data back to a writable Cognite resource list.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
data_chunk: A list of tuples, each containing a source ID and a dictionary representing
|
|
228
|
+
the data in a JSON-compatible format.
|
|
229
|
+
Returns:
|
|
230
|
+
A writable Cognite resource list representing the data.
|
|
231
|
+
"""
|
|
232
|
+
result: list[UploadFileContentItem] = []
|
|
233
|
+
for source_id, item_json in data_chunk:
|
|
234
|
+
item = self.json_to_resource(item_json)
|
|
235
|
+
filepath = Path(cast(str | Path, item_json[FILEPATH]))
|
|
236
|
+
mime_type, _ = mimetypes.guess_type(filepath)
|
|
237
|
+
# application/octet-stream is the standard fallback for binary data when the type is unknown. (at least Claude thinks so)
|
|
238
|
+
result.append(
|
|
239
|
+
UploadFileContentItem(
|
|
240
|
+
source_id=source_id,
|
|
241
|
+
item=item,
|
|
242
|
+
file_path=filepath,
|
|
243
|
+
mime_type=mime_type or "application/octet-stream",
|
|
244
|
+
)
|
|
245
|
+
)
|
|
246
|
+
return result
|
|
247
|
+
|
|
248
|
+
def json_to_resource(self, item_json: dict[str, JsonVal]) -> FileMetadataWrite:
|
|
249
|
+
return self._crud.load_resource(item_json)
|
|
250
|
+
|
|
251
|
+
def upload_items(
|
|
252
|
+
self,
|
|
253
|
+
data_chunk: Sequence[UploadItem[FileMetadataWrite]],
|
|
254
|
+
http_client: HTTPClient,
|
|
255
|
+
selector: FileContentSelector | None = None,
|
|
256
|
+
) -> Sequence[HTTPMessage]:
|
|
257
|
+
results: MutableSequence[HTTPMessage] = []
|
|
258
|
+
if isinstance(selector, FileMetadataTemplateSelector | FileIdentifierSelector):
|
|
259
|
+
upload_url_getter = self._upload_url_asset_centric
|
|
260
|
+
elif isinstance(selector, FileDataModelingTemplateSelector):
|
|
261
|
+
upload_url_getter = self._upload_url_data_modeling
|
|
262
|
+
elif selector is None:
|
|
263
|
+
raise ToolkitNotImplementedError("Selector must be provided for FileContentIO upload")
|
|
264
|
+
else:
|
|
265
|
+
raise ToolkitNotImplementedError(
|
|
266
|
+
f"Upload for the given selector, {type(selector).__name__}, is not supported for FileContentIO"
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
for item in cast(Sequence[UploadFileContentItem], data_chunk):
|
|
270
|
+
if not (upload_url := upload_url_getter(item, http_client, results)):
|
|
271
|
+
continue
|
|
272
|
+
|
|
273
|
+
content_bytes = item.file_path.read_bytes()
|
|
274
|
+
upload_response = http_client.request_with_retries(
|
|
275
|
+
message=DataBodyRequest(
|
|
276
|
+
endpoint_url=upload_url,
|
|
277
|
+
method="PUT",
|
|
278
|
+
content_type=item.mime_type,
|
|
279
|
+
data_content=content_bytes,
|
|
280
|
+
content_length=len(content_bytes),
|
|
281
|
+
)
|
|
282
|
+
)
|
|
283
|
+
results.extend(upload_response.as_item_responses(item.as_id()))
|
|
284
|
+
return results
|
|
285
|
+
|
|
286
|
+
def _upload_url_asset_centric(
|
|
287
|
+
self, item: UploadFileContentItem, http_client: HTTPClient, results: MutableSequence[HTTPMessage]
|
|
288
|
+
) -> str | None:
|
|
289
|
+
responses = http_client.request_with_retries(
|
|
290
|
+
message=SimpleBodyRequest(
|
|
291
|
+
endpoint_url=http_client.config.create_api_url(self.UPLOAD_ENDPOINT),
|
|
292
|
+
method="POST",
|
|
293
|
+
# MyPy does not understand that .dump is valid json
|
|
294
|
+
body_content=item.dump(), # type: ignore[arg-type]
|
|
295
|
+
)
|
|
296
|
+
)
|
|
297
|
+
return self._parse_upload_link_response(responses, item, results)
|
|
298
|
+
|
|
299
|
+
def _upload_url_data_modeling(
|
|
300
|
+
self,
|
|
301
|
+
item: UploadFileContentItem,
|
|
302
|
+
http_client: HTTPClient,
|
|
303
|
+
results: MutableSequence[HTTPMessage],
|
|
304
|
+
created_node: bool = False,
|
|
305
|
+
) -> str | None:
|
|
306
|
+
"""Get upload URL for data modeling file upload.
|
|
307
|
+
|
|
308
|
+
We first try to get the upload link assuming the CogniteFile node already exists.
|
|
309
|
+
If we get a "not found" error, we create the CogniteFile node and try again.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
item: The upload item containing file metadata.
|
|
313
|
+
http_client: The HTTP client to use for requests.
|
|
314
|
+
results: A mutable sequence to collect HTTP messages and errors.
|
|
315
|
+
created_node: A flag indicating whether the CogniteFile node has already been created.
|
|
316
|
+
This prevents infinite recursion.
|
|
317
|
+
|
|
318
|
+
Returns:
|
|
319
|
+
The upload URL as a string, or None if there was an error.
|
|
320
|
+
|
|
321
|
+
"""
|
|
322
|
+
# We know that instance_id is always set for data modeling uploads
|
|
323
|
+
instance_id = cast(NodeId, item.item.instance_id)
|
|
324
|
+
responses = http_client.request_with_retries(
|
|
325
|
+
message=SimpleBodyRequest(
|
|
326
|
+
endpoint_url=http_client.config.create_api_url("/files/uploadlink"),
|
|
327
|
+
method="POST",
|
|
328
|
+
body_content={"items": [{"instanceId": instance_id.dump(include_instance_type=False)}]}, # type: ignore[dict-item]
|
|
329
|
+
)
|
|
330
|
+
)
|
|
331
|
+
# We know there is only one response since we only requested one upload link
|
|
332
|
+
response = responses[0]
|
|
333
|
+
if isinstance(response, FailedResponse) and response.error.missing and not created_node:
|
|
334
|
+
if self._create_cognite_file_node(instance_id, http_client, item.as_id(), results):
|
|
335
|
+
return self._upload_url_data_modeling(item, http_client, results, created_node=True)
|
|
336
|
+
else:
|
|
337
|
+
return None
|
|
338
|
+
|
|
339
|
+
return self._parse_upload_link_response(responses, item, results)
|
|
340
|
+
|
|
341
|
+
@classmethod
|
|
342
|
+
def _create_cognite_file_node(
|
|
343
|
+
cls, instance_id: NodeId, http_client: HTTPClient, upload_id: str, results: MutableSequence[HTTPMessage]
|
|
344
|
+
) -> bool:
|
|
345
|
+
node_creation = http_client.request_with_retries(
|
|
346
|
+
message=SimpleBodyRequest(
|
|
347
|
+
endpoint_url=http_client.config.create_api_url("/models/instances"),
|
|
348
|
+
method="POST",
|
|
349
|
+
body_content={
|
|
350
|
+
"items": [
|
|
351
|
+
{
|
|
352
|
+
"space": instance_id.space,
|
|
353
|
+
"externalId": instance_id.external_id,
|
|
354
|
+
"instanceType": "node",
|
|
355
|
+
# When we create a node with properties in CogniteFile View even with empty properties,
|
|
356
|
+
# CDF will fill in empty values for all properties defined in the view (note this is only
|
|
357
|
+
# possible because CogniteFile view has all properties as optional). This includes properties
|
|
358
|
+
# in the CogniteFile container, which will trigger the file syncer to create a FileMetadata
|
|
359
|
+
# and link it to the CogniteFile node.
|
|
360
|
+
"sources": [{"source": COGNITE_FILE_VIEW.dump(include_type=True), "properties": {}}], # type: ignore[dict-item]
|
|
361
|
+
}
|
|
362
|
+
]
|
|
363
|
+
},
|
|
364
|
+
)
|
|
365
|
+
)
|
|
366
|
+
try:
|
|
367
|
+
_ = node_creation.get_first_body()
|
|
368
|
+
except ValueError:
|
|
369
|
+
results.extend(node_creation.as_item_responses(upload_id))
|
|
370
|
+
return False
|
|
371
|
+
return True
|
|
372
|
+
|
|
373
|
+
@classmethod
|
|
374
|
+
def _parse_upload_link_response(
|
|
375
|
+
cls, responses: ResponseList, item: UploadFileContentItem, results: MutableSequence[HTTPMessage]
|
|
376
|
+
) -> str | None:
|
|
377
|
+
try:
|
|
378
|
+
body = responses.get_first_body()
|
|
379
|
+
except ValueError:
|
|
380
|
+
results.extend(responses.as_item_responses(item.as_id()))
|
|
381
|
+
return None
|
|
382
|
+
|
|
383
|
+
if "items" in body and isinstance(body["items"], list) and len(body["items"]) > 0:
|
|
384
|
+
body = body["items"][0] # type: ignore[assignment]
|
|
385
|
+
try:
|
|
386
|
+
upload_url = cast(str, body["uploadUrl"])
|
|
387
|
+
except (KeyError, IndexError):
|
|
388
|
+
results.append(
|
|
389
|
+
FailedResponseItems(
|
|
390
|
+
status_code=200,
|
|
391
|
+
body=json.dumps(body),
|
|
392
|
+
error=ErrorDetails(code=200, message="Malformed response"),
|
|
393
|
+
ids=[item.as_id()],
|
|
394
|
+
)
|
|
395
|
+
)
|
|
396
|
+
return None
|
|
397
|
+
return upload_url
|
|
398
|
+
|
|
399
|
+
@classmethod
|
|
400
|
+
def read_chunks(
|
|
401
|
+
cls, reader: MultiFileReader, selector: FileContentSelector
|
|
402
|
+
) -> Iterable[list[tuple[str, dict[str, JsonVal]]]]:
|
|
403
|
+
if isinstance(selector, FileTemplateSelector):
|
|
404
|
+
for chunk in chunker_sequence(reader.input_files, cls.CHUNK_SIZE):
|
|
405
|
+
batch: list[tuple[str, dict[str, JsonVal]]] = []
|
|
406
|
+
for file_path in chunk:
|
|
407
|
+
metadata = selector.create_instance(file_path)
|
|
408
|
+
metadata[FILEPATH] = file_path
|
|
409
|
+
batch.append((file_path.as_posix(), metadata))
|
|
410
|
+
yield batch
|
|
411
|
+
elif isinstance(selector, FileIdentifierSelector):
|
|
412
|
+
for item_chunk in chunker(reader.read_chunks(), cls.CHUNK_SIZE):
|
|
413
|
+
batch = []
|
|
414
|
+
for item in item_chunk:
|
|
415
|
+
if FILEPATH not in item:
|
|
416
|
+
# Todo Log warning
|
|
417
|
+
continue
|
|
418
|
+
try:
|
|
419
|
+
file_path = Path(item[FILEPATH])
|
|
420
|
+
except KeyError:
|
|
421
|
+
# Todo Log warning
|
|
422
|
+
continue
|
|
423
|
+
if not file_path.is_absolute():
|
|
424
|
+
file_path = reader.input_file.parent / file_path
|
|
425
|
+
item[FILEPATH] = file_path
|
|
426
|
+
batch.append((file_path.as_posix(), item))
|
|
427
|
+
yield batch
|
|
428
|
+
else:
|
|
429
|
+
raise ToolkitNotImplementedError(
|
|
430
|
+
f"Reading with the manifest, {type(selector).__name__}, is not supported for FileContentIO"
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
@classmethod
|
|
434
|
+
def count_chunks(cls, reader: MultiFileReader) -> int:
|
|
435
|
+
return len(reader.input_files)
|
|
@@ -4,17 +4,20 @@ from typing import ClassVar
|
|
|
4
4
|
|
|
5
5
|
from cognite.client.data_classes.aggregations import Count
|
|
6
6
|
from cognite.client.data_classes.data_modeling import (
|
|
7
|
+
ContainerId,
|
|
7
8
|
ContainerList,
|
|
8
9
|
EdgeApply,
|
|
9
10
|
NodeApply,
|
|
10
11
|
SpaceList,
|
|
12
|
+
ViewId,
|
|
11
13
|
ViewList,
|
|
12
14
|
)
|
|
13
15
|
from cognite.client.data_classes.data_modeling.instances import Instance, InstanceApply
|
|
14
16
|
from cognite.client.utils._identifier import InstanceId
|
|
15
17
|
|
|
18
|
+
from cognite_toolkit._cdf_tk import constants
|
|
16
19
|
from cognite_toolkit._cdf_tk.client import ToolkitClient
|
|
17
|
-
from cognite_toolkit._cdf_tk.client.data_classes.instances import InstanceList
|
|
20
|
+
from cognite_toolkit._cdf_tk.client.data_classes.legacy.instances import InstanceList
|
|
18
21
|
from cognite_toolkit._cdf_tk.cruds import ContainerCRUD, SpaceCRUD, ViewCRUD
|
|
19
22
|
from cognite_toolkit._cdf_tk.utils import sanitize_filename
|
|
20
23
|
from cognite_toolkit._cdf_tk.utils.cdf import iterate_instances
|
|
@@ -54,10 +57,35 @@ class InstanceIO(
|
|
|
54
57
|
def __init__(self, client: ToolkitClient, remove_existing_version: bool = True) -> None:
|
|
55
58
|
super().__init__(client)
|
|
56
59
|
self._remove_existing_version = remove_existing_version
|
|
60
|
+
# Cache for view to read-only properties mapping
|
|
61
|
+
self._view_readonly_properties_cache: dict[ViewId, set[str]] = {}
|
|
62
|
+
self._view_crud = ViewCRUD.create_loader(self.client)
|
|
57
63
|
|
|
58
64
|
def as_id(self, item: Instance) -> str:
|
|
59
65
|
return f"{item.space}:{item.external_id}"
|
|
60
66
|
|
|
67
|
+
def _filter_readonly_properties(self, instance: InstanceApply) -> None:
|
|
68
|
+
"""
|
|
69
|
+
Filter out read-only properties from the instance.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
instance: The instance to filter readonly properties from
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
for source in instance.sources:
|
|
76
|
+
readonly_properties = set()
|
|
77
|
+
if isinstance(source.source, ViewId):
|
|
78
|
+
if source.source not in self._view_readonly_properties_cache:
|
|
79
|
+
self._view_readonly_properties_cache[source.source] = self._view_crud.get_readonly_properties(
|
|
80
|
+
source.source
|
|
81
|
+
)
|
|
82
|
+
readonly_properties = self._view_readonly_properties_cache[source.source]
|
|
83
|
+
elif isinstance(source.source, ContainerId):
|
|
84
|
+
if source.source in constants.READONLY_CONTAINER_PROPERTIES:
|
|
85
|
+
readonly_properties = constants.READONLY_CONTAINER_PROPERTIES[source.source]
|
|
86
|
+
|
|
87
|
+
source.properties = {k: v for k, v in source.properties.items() if k not in readonly_properties}
|
|
88
|
+
|
|
61
89
|
def stream_data(self, selector: InstanceSelector, limit: int | None = None) -> Iterable[Page]:
|
|
62
90
|
if isinstance(selector, InstanceViewSelector | InstanceSpaceSelector):
|
|
63
91
|
chunk = InstanceList([])
|
|
@@ -133,12 +161,16 @@ class InstanceIO(
|
|
|
133
161
|
item_to_load = dict(item_json)
|
|
134
162
|
if self._remove_existing_version and "existingVersion" in item_to_load:
|
|
135
163
|
del item_to_load["existingVersion"]
|
|
164
|
+
instance: InstanceApply
|
|
136
165
|
if instance_type == "node":
|
|
137
|
-
|
|
166
|
+
instance = NodeApply._load(item_to_load, cognite_client=self.client)
|
|
138
167
|
elif instance_type == "edge":
|
|
139
|
-
|
|
168
|
+
instance = EdgeApply._load(item_to_load, cognite_client=self.client)
|
|
140
169
|
else:
|
|
141
170
|
raise ValueError(f"Unknown instance type {instance_type!r}")
|
|
171
|
+
# Filter out read-only properties if applicable
|
|
172
|
+
self._filter_readonly_properties(instance)
|
|
173
|
+
return instance
|
|
142
174
|
|
|
143
175
|
def configurations(self, selector: InstanceSelector) -> Iterable[StorageIOConfig]:
|
|
144
176
|
if not isinstance(selector, InstanceViewSelector | InstanceSpaceSelector):
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from collections.abc import Iterable, Sequence
|
|
2
|
+
from itertools import chain
|
|
2
3
|
from uuid import uuid4
|
|
3
4
|
|
|
4
5
|
from cognite.client.data_classes import Row, RowWrite
|
|
@@ -6,6 +7,8 @@ from cognite.client.data_classes import Row, RowWrite
|
|
|
6
7
|
from cognite_toolkit._cdf_tk.cruds import RawDatabaseCRUD, RawTableCRUD
|
|
7
8
|
from cognite_toolkit._cdf_tk.exceptions import ToolkitValueError
|
|
8
9
|
from cognite_toolkit._cdf_tk.utils import sanitize_filename
|
|
10
|
+
from cognite_toolkit._cdf_tk.utils.collection import chunker
|
|
11
|
+
from cognite_toolkit._cdf_tk.utils.fileio import MultiFileReader
|
|
9
12
|
from cognite_toolkit._cdf_tk.utils.http_client import HTTPClient, HTTPMessage, ItemsRequest
|
|
10
13
|
from cognite_toolkit._cdf_tk.utils.useful_types import JsonVal
|
|
11
14
|
|
|
@@ -96,3 +99,26 @@ class RawIO(
|
|
|
96
99
|
if selector is not None and selector.key is not None and selector.key in row:
|
|
97
100
|
key = str(row.pop(selector.key))
|
|
98
101
|
return RowWrite(key=key, columns=row)
|
|
102
|
+
|
|
103
|
+
@classmethod
|
|
104
|
+
def read_chunks(
|
|
105
|
+
cls, reader: MultiFileReader, selector: RawTableSelector
|
|
106
|
+
) -> Iterable[list[tuple[str, dict[str, JsonVal]]]]:
|
|
107
|
+
if not reader.is_table or selector.key is None:
|
|
108
|
+
yield from super().read_chunks(reader, selector)
|
|
109
|
+
return
|
|
110
|
+
data_name = "row" if reader.is_table else "line"
|
|
111
|
+
# Validate that the key exists in all files
|
|
112
|
+
for input_file in sorted(reader.input_files, key=reader._part_no):
|
|
113
|
+
iterable = reader.reader_class(input_file).read_chunks()
|
|
114
|
+
try:
|
|
115
|
+
first = next(iterable)
|
|
116
|
+
except StopIteration:
|
|
117
|
+
continue
|
|
118
|
+
if selector.key not in first:
|
|
119
|
+
raise ToolkitValueError(
|
|
120
|
+
f"Column '{selector.key}' not found in file {input_file.as_posix()!r}. Please ensure the specified column exists."
|
|
121
|
+
)
|
|
122
|
+
full_iterator = chain([first], iterable)
|
|
123
|
+
line_numbered_iterator = ((f"{data_name} {i}", row) for i, row in enumerate(full_iterator, start=1))
|
|
124
|
+
yield from chunker(line_numbered_iterator, cls.CHUNK_SIZE)
|
|
@@ -1,18 +1,35 @@
|
|
|
1
|
+
from pathlib import Path
|
|
1
2
|
from typing import Annotated
|
|
2
3
|
|
|
3
|
-
from pydantic import Field, TypeAdapter
|
|
4
|
+
from pydantic import Field, TypeAdapter, ValidationError
|
|
5
|
+
|
|
6
|
+
from cognite_toolkit._cdf_tk.feature_flags import Flags
|
|
7
|
+
from cognite_toolkit._cdf_tk.tk_warnings import MediumSeverityWarning, ToolkitWarning
|
|
8
|
+
from cognite_toolkit._cdf_tk.tk_warnings.fileread import ResourceFormatWarning
|
|
9
|
+
from cognite_toolkit._cdf_tk.utils.file import read_yaml_file
|
|
10
|
+
from cognite_toolkit._cdf_tk.validation import humanize_validation_error
|
|
4
11
|
|
|
5
12
|
from ._asset_centric import AssetCentricFileSelector, AssetCentricSelector, AssetSubtreeSelector, DataSetSelector
|
|
6
13
|
from ._base import DataSelector
|
|
7
|
-
from ._canvas import CanvasSelector
|
|
8
|
-
from ._charts import AllChartsSelector, ChartOwnerSelector, ChartSelector
|
|
14
|
+
from ._canvas import CanvasExternalIdSelector, CanvasSelector
|
|
15
|
+
from ._charts import AllChartsSelector, ChartExternalIdSelector, ChartOwnerSelector, ChartSelector
|
|
9
16
|
from ._datapoints import (
|
|
17
|
+
DataPointsDataSetSelector,
|
|
10
18
|
DataPointsFileSelector,
|
|
19
|
+
DataPointsSelector,
|
|
11
20
|
ExternalIdColumn,
|
|
12
21
|
InstanceColumn,
|
|
13
22
|
InternalIdColumn,
|
|
14
23
|
TimeSeriesColumn,
|
|
15
24
|
)
|
|
25
|
+
from ._file_content import (
|
|
26
|
+
FileContentSelector,
|
|
27
|
+
FileDataModelingTemplate,
|
|
28
|
+
FileDataModelingTemplateSelector,
|
|
29
|
+
FileIdentifierSelector,
|
|
30
|
+
FileMetadataTemplate,
|
|
31
|
+
FileMetadataTemplateSelector,
|
|
32
|
+
)
|
|
16
33
|
from ._instances import (
|
|
17
34
|
InstanceFileSelector,
|
|
18
35
|
InstanceSelector,
|
|
@@ -21,9 +38,12 @@ from ._instances import (
|
|
|
21
38
|
SelectedView,
|
|
22
39
|
)
|
|
23
40
|
from ._raw import RawTableSelector, SelectedTable
|
|
41
|
+
from ._three_d import ThreeDModelFilteredSelector, ThreeDModelIdSelector, ThreeDSelector
|
|
24
42
|
|
|
25
43
|
Selector = Annotated[
|
|
26
44
|
RawTableSelector
|
|
45
|
+
| ThreeDModelIdSelector
|
|
46
|
+
| ThreeDModelFilteredSelector
|
|
27
47
|
| InstanceViewSelector
|
|
28
48
|
| InstanceFileSelector
|
|
29
49
|
| InstanceSpaceSelector
|
|
@@ -32,25 +52,69 @@ Selector = Annotated[
|
|
|
32
52
|
| AssetSubtreeSelector
|
|
33
53
|
| AssetCentricFileSelector
|
|
34
54
|
| DataSetSelector
|
|
35
|
-
|
|
|
55
|
+
| DataPointsDataSetSelector
|
|
56
|
+
| DataPointsFileSelector
|
|
57
|
+
| ChartExternalIdSelector
|
|
58
|
+
| CanvasExternalIdSelector
|
|
59
|
+
| FileMetadataTemplateSelector
|
|
60
|
+
| FileDataModelingTemplateSelector
|
|
61
|
+
| FileIdentifierSelector,
|
|
36
62
|
Field(discriminator="type"),
|
|
37
63
|
]
|
|
38
64
|
|
|
65
|
+
ALPHA_SELECTORS = {FileIdentifierSelector}
|
|
66
|
+
INTERNAL = {ThreeDModelIdSelector, ThreeDModelFilteredSelector}
|
|
39
67
|
SelectorAdapter: TypeAdapter[Selector] = TypeAdapter(Selector)
|
|
40
68
|
|
|
41
69
|
|
|
70
|
+
def load_selector(manifest_file: Path) -> Selector | ToolkitWarning:
|
|
71
|
+
"""Loads a selector from a manifest file.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
manifest_file: Path to the manifest file.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
A selector object or a toolkit warning if loading fails or the selector is an alpha feature that is not enabled.
|
|
78
|
+
"""
|
|
79
|
+
selector_dict = read_yaml_file(manifest_file, expected_output="dict")
|
|
80
|
+
try:
|
|
81
|
+
selector = SelectorAdapter.validate_python(selector_dict)
|
|
82
|
+
except ValidationError as e:
|
|
83
|
+
errors = humanize_validation_error(e)
|
|
84
|
+
return ResourceFormatWarning(manifest_file, tuple(errors), text="Invalid selector in metadata file, skipping.")
|
|
85
|
+
if not Flags.EXTEND_UPLOAD.is_enabled() and type(selector) in ALPHA_SELECTORS:
|
|
86
|
+
return MediumSeverityWarning(
|
|
87
|
+
f"Selector type '{type(selector).__name__}' in file '{manifest_file}' is in alpha. To enable it set the alpha flag 'extend-upload = true' in your CDF.toml file."
|
|
88
|
+
)
|
|
89
|
+
elif type(selector) in INTERNAL:
|
|
90
|
+
return MediumSeverityWarning(
|
|
91
|
+
f"Selector type '{type(selector).__name__}' in file '{manifest_file}' is for internal use only and cannot be used."
|
|
92
|
+
)
|
|
93
|
+
return selector
|
|
94
|
+
|
|
95
|
+
|
|
42
96
|
__all__ = [
|
|
43
97
|
"AllChartsSelector",
|
|
44
98
|
"AssetCentricFileSelector",
|
|
45
99
|
"AssetCentricSelector",
|
|
46
100
|
"AssetSubtreeSelector",
|
|
101
|
+
"CanvasExternalIdSelector",
|
|
47
102
|
"CanvasSelector",
|
|
103
|
+
"ChartExternalIdSelector",
|
|
48
104
|
"ChartOwnerSelector",
|
|
49
105
|
"ChartSelector",
|
|
106
|
+
"DataPointsDataSetSelector",
|
|
50
107
|
"DataPointsFileSelector",
|
|
108
|
+
"DataPointsSelector",
|
|
51
109
|
"DataSelector",
|
|
52
110
|
"DataSetSelector",
|
|
53
111
|
"ExternalIdColumn",
|
|
112
|
+
"FileContentSelector",
|
|
113
|
+
"FileDataModelingTemplate",
|
|
114
|
+
"FileDataModelingTemplateSelector",
|
|
115
|
+
"FileIdentifierSelector",
|
|
116
|
+
"FileMetadataTemplate",
|
|
117
|
+
"FileMetadataTemplateSelector",
|
|
54
118
|
"InstanceColumn",
|
|
55
119
|
"InstanceFileSelector",
|
|
56
120
|
"InstanceSelector",
|
|
@@ -62,5 +126,8 @@ __all__ = [
|
|
|
62
126
|
"SelectedView",
|
|
63
127
|
"Selector",
|
|
64
128
|
"SelectorAdapter",
|
|
129
|
+
"ThreeDModelIdSelector",
|
|
130
|
+
"ThreeDSelector",
|
|
65
131
|
"TimeSeriesColumn",
|
|
132
|
+
"load_selector",
|
|
66
133
|
]
|