cognite-toolkit 0.7.31__py3-none-any.whl → 0.7.33__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 +5 -6
- cognite_toolkit/_cdf_tk/apps/__init__.py +2 -0
- cognite_toolkit/_cdf_tk/apps/_import_app.py +41 -0
- cognite_toolkit/_cdf_tk/apps/_migrate_app.py +76 -2
- cognite_toolkit/_cdf_tk/client/api/extended_functions.py +9 -9
- cognite_toolkit/_cdf_tk/client/api/infield.py +23 -17
- cognite_toolkit/_cdf_tk/client/api/project.py +8 -7
- cognite_toolkit/_cdf_tk/client/api/streams.py +19 -14
- cognite_toolkit/_cdf_tk/client/api/three_d.py +89 -8
- cognite_toolkit/_cdf_tk/client/data_classes/base.py +6 -22
- cognite_toolkit/_cdf_tk/client/data_classes/instance_api.py +18 -13
- cognite_toolkit/_cdf_tk/client/data_classes/three_d.py +6 -0
- cognite_toolkit/_cdf_tk/client/testing.py +6 -0
- cognite_toolkit/_cdf_tk/commands/_migrate/data_classes.py +32 -1
- cognite_toolkit/_cdf_tk/commands/_migrate/data_mapper.py +93 -7
- cognite_toolkit/_cdf_tk/commands/_migrate/issues.py +18 -0
- cognite_toolkit/_cdf_tk/commands/_migrate/migration_io.py +107 -1
- cognite_toolkit/_cdf_tk/resource_classes/workflow_version.py +164 -5
- cognite_toolkit/_cdf_tk/storageio/selectors/__init__.py +10 -1
- cognite_toolkit/_cdf_tk/storageio/selectors/_three_d.py +34 -0
- cognite_toolkit/_cdf_tk/utils/http_client/__init__.py +28 -0
- cognite_toolkit/_cdf_tk/utils/http_client/_client.py +3 -2
- cognite_toolkit/_cdf_tk/utils/http_client/_data_classes2.py +69 -7
- cognite_toolkit/_cdf_tk/utils/interactive_select.py +47 -0
- cognite_toolkit/_cdf_tk/validation.py +4 -0
- cognite_toolkit/_repo_files/GitHub/.github/workflows/deploy.yaml +1 -1
- cognite_toolkit/_repo_files/GitHub/.github/workflows/dry-run.yaml +1 -1
- cognite_toolkit/_resources/cdf.toml +1 -1
- cognite_toolkit/_version.py +1 -1
- {cognite_toolkit-0.7.31.dist-info → cognite_toolkit-0.7.33.dist-info}/METADATA +1 -1
- {cognite_toolkit-0.7.31.dist-info → cognite_toolkit-0.7.33.dist-info}/RECORD +34 -34
- {cognite_toolkit-0.7.31.dist-info → cognite_toolkit-0.7.33.dist-info}/WHEEL +1 -1
- cognite_toolkit/_cdf_tk/prototypes/commands/__init__.py +0 -0
- cognite_toolkit/_cdf_tk/prototypes/import_app.py +0 -41
- /cognite_toolkit/_cdf_tk/{prototypes/commands/import_.py → commands/_import_cmd.py} +0 -0
- {cognite_toolkit-0.7.31.dist-info → cognite_toolkit-0.7.33.dist-info}/entry_points.txt +0 -0
cognite_toolkit/_cdf.py
CHANGED
|
@@ -27,6 +27,7 @@ from cognite_toolkit._cdf_tk.apps import (
|
|
|
27
27
|
DataApp,
|
|
28
28
|
DevApp,
|
|
29
29
|
DumpApp,
|
|
30
|
+
ImportApp,
|
|
30
31
|
LandingApp,
|
|
31
32
|
MigrateApp,
|
|
32
33
|
ModulesApp,
|
|
@@ -42,7 +43,7 @@ from cognite_toolkit._cdf_tk.constants import HINT_LEAD_TEXT, URL, USE_SENTRY
|
|
|
42
43
|
from cognite_toolkit._cdf_tk.exceptions import (
|
|
43
44
|
ToolkitError,
|
|
44
45
|
)
|
|
45
|
-
from cognite_toolkit._cdf_tk.feature_flags import
|
|
46
|
+
from cognite_toolkit._cdf_tk.feature_flags import Flags
|
|
46
47
|
from cognite_toolkit._cdf_tk.plugins import Plugins
|
|
47
48
|
from cognite_toolkit._cdf_tk.tracker import Tracker
|
|
48
49
|
from cognite_toolkit._cdf_tk.utils import (
|
|
@@ -108,6 +109,9 @@ if Flags.PROFILE.is_enabled():
|
|
|
108
109
|
if Flags.MIGRATE.is_enabled():
|
|
109
110
|
_app.add_typer(MigrateApp(**default_typer_kws), name="migrate")
|
|
110
111
|
|
|
112
|
+
if Flags.IMPORT_CMD.is_enabled():
|
|
113
|
+
_app.add_typer(ImportApp(**default_typer_kws), name="import")
|
|
114
|
+
|
|
111
115
|
if Plugins.data.value.is_enabled():
|
|
112
116
|
_app.add_typer(DataApp(**default_typer_kws), name="data")
|
|
113
117
|
|
|
@@ -126,11 +130,6 @@ def app() -> NoReturn:
|
|
|
126
130
|
# --- Main entry point ---
|
|
127
131
|
# Users run 'app()' directly, but that doesn't allow us to control excepton handling:
|
|
128
132
|
try:
|
|
129
|
-
if FeatureFlag.is_enabled(Flags.IMPORT_CMD):
|
|
130
|
-
from cognite_toolkit._cdf_tk.prototypes.import_app import import_app
|
|
131
|
-
|
|
132
|
-
_app.add_typer(import_app, name="import")
|
|
133
|
-
|
|
134
133
|
_app()
|
|
135
134
|
except ToolkitError as err:
|
|
136
135
|
if "--verbose" in sys.argv:
|
|
@@ -4,6 +4,7 @@ from ._data_app import DataApp
|
|
|
4
4
|
from ._dev_app import DevApp
|
|
5
5
|
from ._download_app import DownloadApp
|
|
6
6
|
from ._dump_app import DumpApp
|
|
7
|
+
from ._import_app import ImportApp
|
|
7
8
|
from ._landing_app import LandingApp
|
|
8
9
|
from ._migrate_app import MigrateApp
|
|
9
10
|
from ._modules_app import ModulesApp
|
|
@@ -20,6 +21,7 @@ __all__ = [
|
|
|
20
21
|
"DevApp",
|
|
21
22
|
"DownloadApp",
|
|
22
23
|
"DumpApp",
|
|
24
|
+
"ImportApp",
|
|
23
25
|
"LandingApp",
|
|
24
26
|
"MigrateApp",
|
|
25
27
|
"ModulesApp",
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
import typer
|
|
5
|
+
|
|
6
|
+
from cognite_toolkit._cdf_tk.client import ToolkitClient
|
|
7
|
+
from cognite_toolkit._cdf_tk.commands._import_cmd import ImportTransformationCLI
|
|
8
|
+
from cognite_toolkit._cdf_tk.utils.auth import EnvironmentVariables
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ImportApp(typer.Typer):
|
|
12
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
13
|
+
super().__init__(*args, **kwargs)
|
|
14
|
+
self.callback(invoke_without_command=True)(self.main)
|
|
15
|
+
self.command("transformation-cli")(self.transformation_cli)
|
|
16
|
+
|
|
17
|
+
def main(self, ctx: typer.Context) -> None:
|
|
18
|
+
"""PREVIEW FEATURE Import resources into Cognite-Toolkit."""
|
|
19
|
+
if ctx.invoked_subcommand is None:
|
|
20
|
+
print("Use [bold yellow]cdf-tk import --help[/] for more information.")
|
|
21
|
+
return None
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
def transformation_cli(
|
|
25
|
+
source: Path = typer.Argument(..., help="Path to the transformation CLI manifest directory or files."),
|
|
26
|
+
destination: Path = typer.Argument(..., help="Path to the destination directory."),
|
|
27
|
+
overwrite: bool = typer.Option(False, help="Overwrite destination if it already exists."),
|
|
28
|
+
flatten: bool = typer.Option(False, help="Flatten the directory structure."),
|
|
29
|
+
clean: bool = typer.Option(False, help="Remove the source directory after import."),
|
|
30
|
+
verbose: bool = typer.Option(False, help="Turn on to get more verbose output when running the command"),
|
|
31
|
+
) -> None:
|
|
32
|
+
"""Import transformation CLI manifests into Cognite-Toolkit modules."""
|
|
33
|
+
|
|
34
|
+
# We are lazy loading the client as we only need it if we need to look up dataset ids.
|
|
35
|
+
# This is to ensure the command can be executed without a client if the user does not need to look up dataset ids.
|
|
36
|
+
# (which is likely 99% of the time)
|
|
37
|
+
def get_client() -> ToolkitClient:
|
|
38
|
+
return EnvironmentVariables.create_from_environment().get_client()
|
|
39
|
+
|
|
40
|
+
cmd = ImportTransformationCLI(print_warning=True, get_client=get_client)
|
|
41
|
+
cmd.execute(source, destination, overwrite, flatten, clean, verbose=verbose)
|
|
@@ -15,10 +15,16 @@ from cognite_toolkit._cdf_tk.commands._migrate.creators import (
|
|
|
15
15
|
InstanceSpaceCreator,
|
|
16
16
|
SourceSystemCreator,
|
|
17
17
|
)
|
|
18
|
-
from cognite_toolkit._cdf_tk.commands._migrate.data_mapper import
|
|
18
|
+
from cognite_toolkit._cdf_tk.commands._migrate.data_mapper import (
|
|
19
|
+
AssetCentricMapper,
|
|
20
|
+
CanvasMapper,
|
|
21
|
+
ChartMapper,
|
|
22
|
+
ThreeDMapper,
|
|
23
|
+
)
|
|
19
24
|
from cognite_toolkit._cdf_tk.commands._migrate.migration_io import (
|
|
20
25
|
AnnotationMigrationIO,
|
|
21
26
|
AssetCentricMigrationIO,
|
|
27
|
+
ThreeDMigrationIO,
|
|
22
28
|
)
|
|
23
29
|
from cognite_toolkit._cdf_tk.commands._migrate.selectors import (
|
|
24
30
|
AssetCentricMigrationSelector,
|
|
@@ -26,7 +32,11 @@ from cognite_toolkit._cdf_tk.commands._migrate.selectors import (
|
|
|
26
32
|
MigrationCSVFileSelector,
|
|
27
33
|
)
|
|
28
34
|
from cognite_toolkit._cdf_tk.storageio import CanvasIO, ChartIO
|
|
29
|
-
from cognite_toolkit._cdf_tk.storageio.selectors import
|
|
35
|
+
from cognite_toolkit._cdf_tk.storageio.selectors import (
|
|
36
|
+
CanvasExternalIdSelector,
|
|
37
|
+
ChartExternalIdSelector,
|
|
38
|
+
ThreeDModelIdSelector,
|
|
39
|
+
)
|
|
30
40
|
from cognite_toolkit._cdf_tk.utils.auth import EnvironmentVariables
|
|
31
41
|
from cognite_toolkit._cdf_tk.utils.cli_args import parse_view_str
|
|
32
42
|
from cognite_toolkit._cdf_tk.utils.interactive_select import (
|
|
@@ -36,6 +46,7 @@ from cognite_toolkit._cdf_tk.utils.interactive_select import (
|
|
|
36
46
|
InteractiveCanvasSelect,
|
|
37
47
|
InteractiveChartSelect,
|
|
38
48
|
ResourceViewMappingInteractiveSelect,
|
|
49
|
+
ThreeDInteractiveSelect,
|
|
39
50
|
)
|
|
40
51
|
from cognite_toolkit._cdf_tk.utils.useful_types import AssetCentricKind
|
|
41
52
|
|
|
@@ -56,6 +67,7 @@ class MigrateApp(typer.Typer):
|
|
|
56
67
|
self.command("annotations")(self.annotations)
|
|
57
68
|
self.command("canvas")(self.canvas)
|
|
58
69
|
self.command("charts")(self.charts)
|
|
70
|
+
self.command("3d")(self.three_d)
|
|
59
71
|
# Uncomment when infield v2 config migration is ready
|
|
60
72
|
# self.command("infield-configs")(self.infield_configs)
|
|
61
73
|
|
|
@@ -980,6 +992,68 @@ class MigrateApp(typer.Typer):
|
|
|
980
992
|
)
|
|
981
993
|
)
|
|
982
994
|
|
|
995
|
+
@staticmethod
|
|
996
|
+
def three_d(
|
|
997
|
+
ctx: typer.Context,
|
|
998
|
+
id: Annotated[
|
|
999
|
+
list[int] | None,
|
|
1000
|
+
typer.Argument(
|
|
1001
|
+
help="The ID of the 3D Model to migrate. If not provided, an interactive selection will be "
|
|
1002
|
+
"performed to select the 3D Models to migrate."
|
|
1003
|
+
),
|
|
1004
|
+
] = None,
|
|
1005
|
+
log_dir: Annotated[
|
|
1006
|
+
Path,
|
|
1007
|
+
typer.Option(
|
|
1008
|
+
"--log-dir",
|
|
1009
|
+
"-l",
|
|
1010
|
+
help="Path to the directory where migration logs will be stored.",
|
|
1011
|
+
),
|
|
1012
|
+
] = Path(f"migration_logs_{TODAY}"),
|
|
1013
|
+
dry_run: Annotated[
|
|
1014
|
+
bool,
|
|
1015
|
+
typer.Option(
|
|
1016
|
+
"--dry-run",
|
|
1017
|
+
"-d",
|
|
1018
|
+
help="If set, the migration will not be executed, but only a report of "
|
|
1019
|
+
"what would be done is printed. This is useful for checking that all resources referenced by the 3D Models "
|
|
1020
|
+
"have been migrated to the new data modeling resources in CDF.",
|
|
1021
|
+
),
|
|
1022
|
+
] = False,
|
|
1023
|
+
verbose: Annotated[
|
|
1024
|
+
bool,
|
|
1025
|
+
typer.Option(
|
|
1026
|
+
"--verbose",
|
|
1027
|
+
"-v",
|
|
1028
|
+
help="Turn on to get more verbose output when running the command",
|
|
1029
|
+
),
|
|
1030
|
+
] = False,
|
|
1031
|
+
) -> None:
|
|
1032
|
+
"""Migrate 3D Models from Asset-Centric to data modeling in CDF.
|
|
1033
|
+
|
|
1034
|
+
This command expects that the CogniteMigration data model is already deployed, and that the Mapping view
|
|
1035
|
+
is populated with the mapping from Asset-Centric resources to the new data modeling resources.
|
|
1036
|
+
"""
|
|
1037
|
+
client = EnvironmentVariables.create_from_environment().get_client()
|
|
1038
|
+
selected_ids: list[int]
|
|
1039
|
+
if id:
|
|
1040
|
+
selected_ids = id
|
|
1041
|
+
else:
|
|
1042
|
+
selected_models = ThreeDInteractiveSelect(client, "migrate").select_three_d_models("classic")
|
|
1043
|
+
selected_ids = [model.id for model in selected_models]
|
|
1044
|
+
|
|
1045
|
+
cmd = MigrationCommand()
|
|
1046
|
+
cmd.run(
|
|
1047
|
+
lambda: cmd.migrate(
|
|
1048
|
+
selected=ThreeDModelIdSelector(ids=tuple(selected_ids)),
|
|
1049
|
+
data=ThreeDMigrationIO(client),
|
|
1050
|
+
mapper=ThreeDMapper(client),
|
|
1051
|
+
log_dir=log_dir,
|
|
1052
|
+
dry_run=dry_run,
|
|
1053
|
+
verbose=verbose,
|
|
1054
|
+
)
|
|
1055
|
+
)
|
|
1056
|
+
|
|
983
1057
|
@staticmethod
|
|
984
1058
|
def infield_configs(
|
|
985
1059
|
ctx: typer.Context,
|
|
@@ -7,7 +7,7 @@ from rich.console import Console
|
|
|
7
7
|
|
|
8
8
|
from cognite_toolkit._cdf_tk.client.config import ToolkitClientConfig
|
|
9
9
|
from cognite_toolkit._cdf_tk.utils.collection import chunker
|
|
10
|
-
from cognite_toolkit._cdf_tk.utils.http_client import HTTPClient,
|
|
10
|
+
from cognite_toolkit._cdf_tk.utils.http_client import HTTPClient, RequestMessage2
|
|
11
11
|
from cognite_toolkit._cdf_tk.utils.useful_types import JsonVal
|
|
12
12
|
|
|
13
13
|
|
|
@@ -34,21 +34,20 @@ class ExtendedFunctionsAPI(FunctionsAPI):
|
|
|
34
34
|
|
|
35
35
|
Args:
|
|
36
36
|
function (FunctionWrite): The function to create.
|
|
37
|
-
console (Console | None): The rich console to use for printing warnings.
|
|
38
37
|
|
|
39
38
|
Returns:
|
|
40
39
|
Function: The created function object.
|
|
41
40
|
"""
|
|
42
|
-
result = self._toolkit_http_client.
|
|
43
|
-
message=
|
|
41
|
+
result = self._toolkit_http_client.request_single_retries(
|
|
42
|
+
message=RequestMessage2(
|
|
44
43
|
endpoint_url=self._toolkit_config.create_api_url("/functions"),
|
|
45
44
|
method="POST",
|
|
46
45
|
body_content={"items": [function.dump(camel_case=True)]},
|
|
47
46
|
)
|
|
48
47
|
)
|
|
49
|
-
result.
|
|
48
|
+
success = result.get_success_or_raise()
|
|
50
49
|
# We assume the API response is one item on a successful creation
|
|
51
|
-
return Function._load(
|
|
50
|
+
return Function._load(success.body_json["items"][0], cognite_client=self._cognite_client)
|
|
52
51
|
|
|
53
52
|
def delete_with_429_retry(self, external_id: SequenceNotStr[str], ignore_unknown_ids: bool = False) -> None:
|
|
54
53
|
"""Delete one or more functions with retry handling for 429 Too Many Requests responses.
|
|
@@ -70,11 +69,12 @@ class ExtendedFunctionsAPI(FunctionsAPI):
|
|
|
70
69
|
}
|
|
71
70
|
if ignore_unknown_ids:
|
|
72
71
|
body_content["ignoreUnknownIds"] = True
|
|
73
|
-
self._toolkit_http_client.
|
|
74
|
-
message=
|
|
72
|
+
result = self._toolkit_http_client.request_single_retries(
|
|
73
|
+
message=RequestMessage2(
|
|
75
74
|
endpoint_url=self._toolkit_config.create_api_url("/functions/delete"),
|
|
76
75
|
method="POST",
|
|
77
76
|
body_content=body_content,
|
|
78
77
|
)
|
|
79
|
-
)
|
|
78
|
+
)
|
|
79
|
+
result.get_success_or_raise()
|
|
80
80
|
return None
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from collections.abc import Sequence
|
|
2
2
|
from typing import Any, cast
|
|
3
3
|
|
|
4
|
+
from pydantic import TypeAdapter
|
|
4
5
|
from rich.console import Console
|
|
5
6
|
|
|
6
7
|
from cognite_toolkit._cdf_tk.client.data_classes.api_classes import PagedResponse, QueryResponse
|
|
@@ -15,7 +16,12 @@ from cognite_toolkit._cdf_tk.client.data_classes.instance_api import (
|
|
|
15
16
|
NodeIdentifier,
|
|
16
17
|
)
|
|
17
18
|
from cognite_toolkit._cdf_tk.tk_warnings import HighSeverityWarning
|
|
18
|
-
from cognite_toolkit._cdf_tk.utils.http_client import
|
|
19
|
+
from cognite_toolkit._cdf_tk.utils.http_client import (
|
|
20
|
+
HTTPClient,
|
|
21
|
+
ItemsRequest,
|
|
22
|
+
ItemsRequest2,
|
|
23
|
+
RequestMessage2,
|
|
24
|
+
)
|
|
19
25
|
|
|
20
26
|
|
|
21
27
|
class InfieldConfigAPI:
|
|
@@ -40,31 +46,31 @@ class InfieldConfigAPI:
|
|
|
40
46
|
else [item.as_request_item(), item.data_exploration_config.as_request_item()]
|
|
41
47
|
for item in items
|
|
42
48
|
)
|
|
43
|
-
responses = self._http_client.
|
|
44
|
-
|
|
49
|
+
responses = self._http_client.request_items_retries(
|
|
50
|
+
ItemsRequest2(
|
|
45
51
|
endpoint_url=self._config.create_api_url(self.ENDPOINT),
|
|
46
52
|
method="POST",
|
|
47
53
|
items=[item for sublist in request_items for item in sublist],
|
|
48
54
|
)
|
|
49
55
|
)
|
|
50
56
|
responses.raise_for_status()
|
|
51
|
-
return
|
|
57
|
+
return TypeAdapter(list[InstanceResult]).validate_python(responses.get_items())
|
|
52
58
|
|
|
53
59
|
def retrieve(self, items: Sequence[NodeIdentifier]) -> list[InfieldLocationConfig]:
|
|
54
60
|
if len(items) > 100:
|
|
55
61
|
raise ValueError("Cannot retrieve more than 100 InfieldLocationConfig items at once.")
|
|
56
62
|
if not items:
|
|
57
63
|
return []
|
|
58
|
-
|
|
59
|
-
|
|
64
|
+
response = self._http_client.request_single_retries(
|
|
65
|
+
RequestMessage2(
|
|
60
66
|
# We use the query endpoint to be able to retrieve linked DataExplorationConfig items
|
|
61
67
|
endpoint_url=self._config.create_api_url(f"{self.ENDPOINT}/query"),
|
|
62
68
|
method="POST",
|
|
63
69
|
body_content=self._retrieve_query(items),
|
|
64
70
|
)
|
|
65
71
|
)
|
|
66
|
-
|
|
67
|
-
parsed_response = QueryResponse[InstanceResponseItem].model_validate(
|
|
72
|
+
success = response.get_success_or_raise()
|
|
73
|
+
parsed_response = QueryResponse[InstanceResponseItem].model_validate(success.body_json)
|
|
68
74
|
return self._parse_retrieve_response(parsed_response)
|
|
69
75
|
|
|
70
76
|
def delete(self, items: Sequence[InfieldLocationConfig]) -> list[NodeIdentifier]:
|
|
@@ -168,30 +174,30 @@ class InFieldCDMConfigAPI:
|
|
|
168
174
|
raise ValueError("Cannot apply more than 500 InFieldCDMLocationConfig items at once.")
|
|
169
175
|
|
|
170
176
|
request_items = [item.as_request_item() for item in items]
|
|
171
|
-
|
|
172
|
-
|
|
177
|
+
results = self._http_client.request_items_retries(
|
|
178
|
+
ItemsRequest2(
|
|
173
179
|
endpoint_url=self._config.create_api_url(self.ENDPOINT),
|
|
174
180
|
method="POST",
|
|
175
|
-
items=request_items,
|
|
181
|
+
items=request_items,
|
|
176
182
|
)
|
|
177
183
|
)
|
|
178
|
-
|
|
179
|
-
return
|
|
184
|
+
results.raise_for_status()
|
|
185
|
+
return TypeAdapter(list[InstanceResult]).validate_python(results.get_items())
|
|
180
186
|
|
|
181
187
|
def retrieve(self, items: Sequence[NodeIdentifier]) -> list[InFieldCDMLocationConfig]:
|
|
182
188
|
if len(items) > 100:
|
|
183
189
|
raise ValueError("Cannot retrieve more than 100 InFieldCDMLocationConfig items at once.")
|
|
184
190
|
if not items:
|
|
185
191
|
return []
|
|
186
|
-
|
|
187
|
-
|
|
192
|
+
result = self._http_client.request_single_retries(
|
|
193
|
+
RequestMessage2(
|
|
188
194
|
endpoint_url=self._config.create_api_url(f"{self.ENDPOINT}/query"),
|
|
189
195
|
method="POST",
|
|
190
196
|
body_content=self._retrieve_query(items),
|
|
191
197
|
)
|
|
192
198
|
)
|
|
193
|
-
|
|
194
|
-
parsed_response = QueryResponse[InstanceResponseItem].model_validate(
|
|
199
|
+
success = result.get_success_or_raise()
|
|
200
|
+
parsed_response = QueryResponse[InstanceResponseItem].model_validate(success.body_json)
|
|
195
201
|
return self._parse_retrieve_response(parsed_response)
|
|
196
202
|
|
|
197
203
|
def delete(self, items: Sequence[InFieldCDMLocationConfig]) -> list[NodeIdentifier]:
|
|
@@ -2,7 +2,7 @@ from cognite.client import CogniteClient
|
|
|
2
2
|
|
|
3
3
|
from cognite_toolkit._cdf_tk.client.config import ToolkitClientConfig
|
|
4
4
|
from cognite_toolkit._cdf_tk.client.data_classes.project import ProjectStatusList
|
|
5
|
-
from cognite_toolkit._cdf_tk.utils.http_client import HTTPClient,
|
|
5
|
+
from cognite_toolkit._cdf_tk.utils.http_client import HTTPClient, RequestMessage2
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class ProjectAPI:
|
|
@@ -13,11 +13,12 @@ class ProjectAPI:
|
|
|
13
13
|
|
|
14
14
|
def status(self) -> ProjectStatusList:
|
|
15
15
|
"""Retrieve information about the current project."""
|
|
16
|
-
response = self._http_client.
|
|
17
|
-
|
|
18
|
-
endpoint_url=f"{self._config.base_url}/api/v1/projects
|
|
16
|
+
response = self._http_client.request_single_retries(
|
|
17
|
+
RequestMessage2(
|
|
18
|
+
endpoint_url=f"{self._config.base_url}/api/v1/projects",
|
|
19
|
+
method="GET",
|
|
20
|
+
parameters={"withDataModelingStatus": True},
|
|
19
21
|
)
|
|
20
22
|
)
|
|
21
|
-
response.
|
|
22
|
-
|
|
23
|
-
return ProjectStatusList._load(body["items"], cognite_client=self._cognite_client) # type: ignore[arg-type]
|
|
23
|
+
success = response.get_success_or_raise()
|
|
24
|
+
return ProjectStatusList._load(success.body_json["items"], cognite_client=self._cognite_client)
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
from collections.abc import Sequence
|
|
2
2
|
|
|
3
|
+
from pydantic import TypeAdapter
|
|
3
4
|
from rich.console import Console
|
|
4
5
|
|
|
5
6
|
from cognite_toolkit._cdf_tk.client.data_classes.api_classes import PagedResponse
|
|
6
7
|
from cognite_toolkit._cdf_tk.client.data_classes.streams import StreamRequest, StreamResponse
|
|
7
|
-
from cognite_toolkit._cdf_tk.utils.http_client import
|
|
8
|
+
from cognite_toolkit._cdf_tk.utils.http_client import (
|
|
9
|
+
HTTPClient,
|
|
10
|
+
ItemsRequest2,
|
|
11
|
+
ParamRequest,
|
|
12
|
+
RequestMessage2,
|
|
13
|
+
)
|
|
8
14
|
|
|
9
15
|
|
|
10
16
|
class StreamsAPI:
|
|
@@ -24,15 +30,15 @@ class StreamsAPI:
|
|
|
24
30
|
Returns:
|
|
25
31
|
List of created StreamResponse items.
|
|
26
32
|
"""
|
|
27
|
-
responses = self._http_client.
|
|
28
|
-
|
|
33
|
+
responses = self._http_client.request_items_retries(
|
|
34
|
+
ItemsRequest2(
|
|
29
35
|
endpoint_url=self._config.create_api_url(self.ENDPOINT),
|
|
30
36
|
method="POST",
|
|
31
|
-
items=
|
|
37
|
+
items=items,
|
|
32
38
|
)
|
|
33
39
|
)
|
|
34
40
|
responses.raise_for_status()
|
|
35
|
-
return
|
|
41
|
+
return TypeAdapter(list[StreamResponse]).validate_python(responses.get_items())
|
|
36
42
|
|
|
37
43
|
def delete(self, external_id: str) -> None:
|
|
38
44
|
"""Delete stream using its external ID.
|
|
@@ -54,14 +60,14 @@ class StreamsAPI:
|
|
|
54
60
|
Returns:
|
|
55
61
|
StreamResponseList containing the listed streams.
|
|
56
62
|
"""
|
|
57
|
-
|
|
58
|
-
|
|
63
|
+
response = self._http_client.request_single_retries(
|
|
64
|
+
RequestMessage2(
|
|
59
65
|
endpoint_url=self._config.create_api_url(self.ENDPOINT),
|
|
60
66
|
method="GET",
|
|
61
67
|
)
|
|
62
68
|
)
|
|
63
|
-
|
|
64
|
-
return PagedResponse[StreamResponse].model_validate(
|
|
69
|
+
success = response.get_success_or_raise()
|
|
70
|
+
return PagedResponse[StreamResponse].model_validate(success.body_json).items
|
|
65
71
|
|
|
66
72
|
def retrieve(self, external_id: str, include_statistics: bool = True) -> StreamResponse:
|
|
67
73
|
"""Retrieve a stream by its external ID.
|
|
@@ -72,13 +78,12 @@ class StreamsAPI:
|
|
|
72
78
|
Returns:
|
|
73
79
|
StreamResponse item.
|
|
74
80
|
"""
|
|
75
|
-
|
|
76
|
-
|
|
81
|
+
response = self._http_client.request_single_retries(
|
|
82
|
+
RequestMessage2(
|
|
77
83
|
endpoint_url=self._config.create_api_url(f"{self.ENDPOINT}/{external_id}"),
|
|
78
84
|
method="GET",
|
|
79
85
|
parameters={"includeStatistics": include_statistics},
|
|
80
86
|
)
|
|
81
87
|
)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return StreamResponse.model_validate(response_body)
|
|
88
|
+
success = response.get_success_or_raise()
|
|
89
|
+
return StreamResponse.model_validate(success.body_json)
|
|
@@ -1,19 +1,72 @@
|
|
|
1
|
+
from collections.abc import Sequence
|
|
2
|
+
|
|
1
3
|
from rich.console import Console
|
|
2
4
|
|
|
3
5
|
from cognite_toolkit._cdf_tk.client.data_classes.api_classes import PagedResponse
|
|
4
|
-
from cognite_toolkit._cdf_tk.client.data_classes.three_d import ThreeDModelResponse
|
|
5
|
-
from cognite_toolkit._cdf_tk.utils.http_client import
|
|
6
|
+
from cognite_toolkit._cdf_tk.client.data_classes.three_d import ThreeDModelClassicRequest, ThreeDModelResponse
|
|
7
|
+
from cognite_toolkit._cdf_tk.utils.http_client import (
|
|
8
|
+
HTTPClient,
|
|
9
|
+
ItemsRequest,
|
|
10
|
+
RequestMessage2,
|
|
11
|
+
SimpleBodyRequest,
|
|
12
|
+
)
|
|
6
13
|
from cognite_toolkit._cdf_tk.utils.useful_types import PrimitiveType
|
|
7
14
|
|
|
8
15
|
|
|
9
16
|
class ThreeDModelAPI:
|
|
10
17
|
ENDPOINT = "/3d/models"
|
|
18
|
+
MAX_CLASSIC_MODELS_PER_CREATE_REQUEST = 1000
|
|
19
|
+
MAX_MODELS_PER_DELETE_REQUEST = 1000
|
|
20
|
+
_LIST_REQUEST_MAX_LIMIT = 1000
|
|
11
21
|
|
|
12
22
|
def __init__(self, http_client: HTTPClient, console: Console) -> None:
|
|
13
23
|
self._http_client = http_client
|
|
14
24
|
self._console = console
|
|
15
25
|
self._config = http_client.config
|
|
16
26
|
|
|
27
|
+
def create(self, models: Sequence[ThreeDModelClassicRequest]) -> list[ThreeDModelResponse]:
|
|
28
|
+
"""Create 3D models in classic format.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
models (Sequence[ThreeDModelClassicRequest]): The 3D model(s) to create.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
list[ThreeDModelResponse]: The created 3D model(s).
|
|
35
|
+
"""
|
|
36
|
+
if not models:
|
|
37
|
+
return []
|
|
38
|
+
if len(models) > self.MAX_CLASSIC_MODELS_PER_CREATE_REQUEST:
|
|
39
|
+
raise ValueError("Cannot create more than 1000 3D models in a single request.")
|
|
40
|
+
responses = self._http_client.request_with_retries(
|
|
41
|
+
ItemsRequest(
|
|
42
|
+
endpoint_url=self._config.create_api_url(self.ENDPOINT),
|
|
43
|
+
method="POST",
|
|
44
|
+
items=list(models),
|
|
45
|
+
)
|
|
46
|
+
)
|
|
47
|
+
responses.raise_for_status()
|
|
48
|
+
body = responses.get_first_body()
|
|
49
|
+
return PagedResponse[ThreeDModelResponse].model_validate(body).items
|
|
50
|
+
|
|
51
|
+
def delete(self, ids: Sequence[int]) -> None:
|
|
52
|
+
"""Delete 3D models by their IDs.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
ids (Sequence[int]): The IDs of the 3D models to delete.
|
|
56
|
+
"""
|
|
57
|
+
if not ids:
|
|
58
|
+
return None
|
|
59
|
+
if len(ids) > self.MAX_MODELS_PER_DELETE_REQUEST:
|
|
60
|
+
raise ValueError("Cannot delete more than 1000 3D models in a single request.")
|
|
61
|
+
responses = self._http_client.request_with_retries(
|
|
62
|
+
SimpleBodyRequest(
|
|
63
|
+
endpoint_url=self._config.create_api_url(self.ENDPOINT + "/delete"),
|
|
64
|
+
method="POST",
|
|
65
|
+
body_content={"items": [{"id": id_} for id_ in ids]},
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
responses.raise_for_status()
|
|
69
|
+
|
|
17
70
|
def iterate(
|
|
18
71
|
self,
|
|
19
72
|
published: bool | None = None,
|
|
@@ -21,8 +74,8 @@ class ThreeDModelAPI:
|
|
|
21
74
|
limit: int = 100,
|
|
22
75
|
cursor: str | None = None,
|
|
23
76
|
) -> PagedResponse[ThreeDModelResponse]:
|
|
24
|
-
if not (0 < limit <=
|
|
25
|
-
raise ValueError("Limit must be between 1 and
|
|
77
|
+
if not (0 < limit <= self._LIST_REQUEST_MAX_LIMIT):
|
|
78
|
+
raise ValueError(f"Limit must be between 1 and {self._LIST_REQUEST_MAX_LIMIT}, got {limit}.")
|
|
26
79
|
parameters: dict[str, PrimitiveType] = {
|
|
27
80
|
# There is a bug in the API. The parameter includeRevisionInfo is expected to be lower case and not
|
|
28
81
|
# camel case as documented. You get error message: Unrecognized query parameter includeRevisionInfo,
|
|
@@ -34,15 +87,43 @@ class ThreeDModelAPI:
|
|
|
34
87
|
parameters["published"] = published
|
|
35
88
|
if cursor is not None:
|
|
36
89
|
parameters["cursor"] = cursor
|
|
37
|
-
responses = self._http_client.
|
|
38
|
-
|
|
90
|
+
responses = self._http_client.request_single_retries(
|
|
91
|
+
RequestMessage2(
|
|
39
92
|
endpoint_url=self._config.create_api_url(self.ENDPOINT),
|
|
40
93
|
method="GET",
|
|
41
94
|
parameters=parameters,
|
|
42
95
|
)
|
|
43
96
|
)
|
|
44
|
-
responses.
|
|
45
|
-
return PagedResponse[ThreeDModelResponse].model_validate(
|
|
97
|
+
success_response = responses.get_success_or_raise()
|
|
98
|
+
return PagedResponse[ThreeDModelResponse].model_validate(success_response.body_json)
|
|
99
|
+
|
|
100
|
+
def list(
|
|
101
|
+
self,
|
|
102
|
+
published: bool | None = None,
|
|
103
|
+
include_revision_info: bool = False,
|
|
104
|
+
limit: int | None = 100,
|
|
105
|
+
cursor: str | None = None,
|
|
106
|
+
) -> list[ThreeDModelResponse]:
|
|
107
|
+
results: list[ThreeDModelResponse] = []
|
|
108
|
+
while True:
|
|
109
|
+
request_limit = (
|
|
110
|
+
self._LIST_REQUEST_MAX_LIMIT
|
|
111
|
+
if limit is None
|
|
112
|
+
else min(limit - len(results), self._LIST_REQUEST_MAX_LIMIT)
|
|
113
|
+
)
|
|
114
|
+
if request_limit <= 0:
|
|
115
|
+
break
|
|
116
|
+
page = self.iterate(
|
|
117
|
+
published=published,
|
|
118
|
+
include_revision_info=include_revision_info,
|
|
119
|
+
limit=request_limit,
|
|
120
|
+
cursor=cursor,
|
|
121
|
+
)
|
|
122
|
+
results.extend(page.items)
|
|
123
|
+
if page.next_cursor is None:
|
|
124
|
+
break
|
|
125
|
+
cursor = page.next_cursor
|
|
126
|
+
return results
|
|
46
127
|
|
|
47
128
|
|
|
48
129
|
class ThreeDAPI:
|
|
@@ -6,6 +6,8 @@ from typing import TYPE_CHECKING, Any, Generic, TypeVar
|
|
|
6
6
|
from pydantic import BaseModel, ConfigDict
|
|
7
7
|
from pydantic.alias_generators import to_camel
|
|
8
8
|
|
|
9
|
+
from cognite_toolkit._cdf_tk.utils.http_client._data_classes2 import BaseModelObject, RequestResource
|
|
10
|
+
|
|
9
11
|
if TYPE_CHECKING:
|
|
10
12
|
from cognite.client import CogniteClient
|
|
11
13
|
|
|
@@ -15,28 +17,6 @@ else:
|
|
|
15
17
|
from typing_extensions import Self
|
|
16
18
|
|
|
17
19
|
|
|
18
|
-
class BaseModelObject(BaseModel):
|
|
19
|
-
"""Base class for all object. This includes resources and nested objects."""
|
|
20
|
-
|
|
21
|
-
# We allow extra fields to support forward compatibility.
|
|
22
|
-
model_config = ConfigDict(alias_generator=to_camel, extra="allow")
|
|
23
|
-
|
|
24
|
-
def dump(self, camel_case: bool = True) -> dict[str, Any]:
|
|
25
|
-
"""Dump the resource to a dictionary.
|
|
26
|
-
|
|
27
|
-
This is the default serialization method for request resources.
|
|
28
|
-
"""
|
|
29
|
-
return self.model_dump(mode="json", by_alias=camel_case)
|
|
30
|
-
|
|
31
|
-
@classmethod
|
|
32
|
-
def _load(cls, resource: dict[str, Any]) -> "Self":
|
|
33
|
-
"""Load method to match CogniteResource signature."""
|
|
34
|
-
return cls.model_validate(resource)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
class RequestResource(BaseModelObject): ...
|
|
38
|
-
|
|
39
|
-
|
|
40
20
|
T_RequestResource = TypeVar("T_RequestResource", bound=RequestResource)
|
|
41
21
|
|
|
42
22
|
|
|
@@ -46,6 +26,10 @@ class ResponseResource(BaseModelObject, Generic[T_RequestResource], ABC):
|
|
|
46
26
|
"""Convert the response resource to a request resource."""
|
|
47
27
|
...
|
|
48
28
|
|
|
29
|
+
def as_write(self) -> T_RequestResource:
|
|
30
|
+
"""Alias for as_request_resource to match protocol signature."""
|
|
31
|
+
return self.as_request_resource()
|
|
32
|
+
|
|
49
33
|
|
|
50
34
|
class Identifier(BaseModel):
|
|
51
35
|
"""Base class for all identifier classes."""
|