cognite-toolkit 0.7.29__py3-none-any.whl → 0.7.31__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cognite_toolkit/_cdf_tk/apps/_core_app.py +7 -1
- cognite_toolkit/_cdf_tk/commands/__init__.py +1 -0
- cognite_toolkit/_cdf_tk/commands/_upload.py +6 -16
- cognite_toolkit/_cdf_tk/commands/build_cmd.py +6 -61
- 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/cruds/_resource_cruds/transformation.py +48 -13
- cognite_toolkit/_cdf_tk/feature_flags.py +4 -0
- cognite_toolkit/_cdf_tk/storageio/selectors/__init__.py +33 -1
- cognite_toolkit/_cdf_tk/utils/http_client/_client.py +153 -8
- cognite_toolkit/_cdf_tk/utils/http_client/_data_classes2.py +91 -1
- cognite_toolkit/_cdf_tk/validation.py +79 -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 +1 -1
- cognite_toolkit/_version.py +1 -1
- {cognite_toolkit-0.7.29.dist-info → cognite_toolkit-0.7.31.dist-info}/METADATA +1 -1
- {cognite_toolkit-0.7.29.dist-info → cognite_toolkit-0.7.31.dist-info}/RECORD +22 -18
- {cognite_toolkit-0.7.29.dist-info → cognite_toolkit-0.7.31.dist-info}/WHEEL +0 -0
- {cognite_toolkit-0.7.29.dist-info → cognite_toolkit-0.7.31.dist-info}/entry_points.txt +0 -0
|
@@ -13,8 +13,10 @@ from rich.panel import Panel
|
|
|
13
13
|
from cognite_toolkit._cdf_tk.cdf_toml import CDFToml
|
|
14
14
|
from cognite_toolkit._cdf_tk.client import ToolkitClient
|
|
15
15
|
from cognite_toolkit._cdf_tk.commands import BuildCommand, CleanCommand, DeployCommand
|
|
16
|
+
from cognite_toolkit._cdf_tk.commands.build_v2.build_cmd import BuildCommand as BuildCommandV2
|
|
16
17
|
from cognite_toolkit._cdf_tk.commands.clean import AVAILABLE_DATA_TYPES
|
|
17
18
|
from cognite_toolkit._cdf_tk.exceptions import ToolkitFileNotFoundError
|
|
19
|
+
from cognite_toolkit._cdf_tk.feature_flags import Flags
|
|
18
20
|
from cognite_toolkit._cdf_tk.utils import get_cicd_environment
|
|
19
21
|
from cognite_toolkit._cdf_tk.utils.auth import EnvironmentVariables
|
|
20
22
|
from cognite_toolkit._version import __version__ as current_version
|
|
@@ -207,7 +209,11 @@ class CoreApp(typer.Typer):
|
|
|
207
209
|
if exit_on_warning:
|
|
208
210
|
print_warning = False
|
|
209
211
|
|
|
210
|
-
cmd =
|
|
212
|
+
cmd = (
|
|
213
|
+
BuildCommandV2(print_warning=print_warning)
|
|
214
|
+
if Flags.v08.is_enabled()
|
|
215
|
+
else BuildCommand(print_warning=print_warning)
|
|
216
|
+
)
|
|
211
217
|
cmd.run(
|
|
212
218
|
lambda: cmd.execute(
|
|
213
219
|
verbose,
|
|
@@ -6,7 +6,6 @@ from pathlib import Path
|
|
|
6
6
|
from cognite.client.data_classes.data_modeling import (
|
|
7
7
|
ViewId,
|
|
8
8
|
)
|
|
9
|
-
from pydantic import ValidationError
|
|
10
9
|
from rich.console import Console
|
|
11
10
|
|
|
12
11
|
from cognite_toolkit._cdf_tk.client import ToolkitClient
|
|
@@ -20,18 +19,15 @@ from cognite_toolkit._cdf_tk.storageio import (
|
|
|
20
19
|
get_upload_io,
|
|
21
20
|
)
|
|
22
21
|
from cognite_toolkit._cdf_tk.storageio._base import TableUploadableStorageIO, UploadItem
|
|
23
|
-
from cognite_toolkit._cdf_tk.storageio.selectors import Selector,
|
|
22
|
+
from cognite_toolkit._cdf_tk.storageio.selectors import Selector, load_selector
|
|
24
23
|
from cognite_toolkit._cdf_tk.storageio.selectors._instances import InstanceSpaceSelector
|
|
25
|
-
from cognite_toolkit._cdf_tk.tk_warnings import HighSeverityWarning, MediumSeverityWarning
|
|
26
|
-
from cognite_toolkit._cdf_tk.tk_warnings.fileread import ResourceFormatWarning
|
|
24
|
+
from cognite_toolkit._cdf_tk.tk_warnings import HighSeverityWarning, MediumSeverityWarning, ToolkitWarning
|
|
27
25
|
from cognite_toolkit._cdf_tk.utils.auth import EnvironmentVariables
|
|
28
|
-
from cognite_toolkit._cdf_tk.utils.file import read_yaml_file
|
|
29
26
|
from cognite_toolkit._cdf_tk.utils.fileio import MultiFileReader
|
|
30
27
|
from cognite_toolkit._cdf_tk.utils.http_client import HTTPClient, ItemMessage, SuccessResponseItems
|
|
31
28
|
from cognite_toolkit._cdf_tk.utils.producer_worker import ProducerWorkerExecutor
|
|
32
29
|
from cognite_toolkit._cdf_tk.utils.progress_tracker import ProgressTracker
|
|
33
30
|
from cognite_toolkit._cdf_tk.utils.useful_types import JsonVal
|
|
34
|
-
from cognite_toolkit._cdf_tk.validation import humanize_validation_error
|
|
35
31
|
|
|
36
32
|
from ._base import ToolkitCommand
|
|
37
33
|
from .deploy import DeployCommand
|
|
@@ -141,17 +137,11 @@ class UploadCommand(ToolkitCommand):
|
|
|
141
137
|
"""Finds data files and their corresponding metadata files in the input directory."""
|
|
142
138
|
data_files_by_metadata: dict[Selector, list[Path]] = {}
|
|
143
139
|
for manifest_file in input_dir.glob(f"*{DATA_MANIFEST_SUFFIX}"):
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
except ValidationError as e:
|
|
148
|
-
errors = humanize_validation_error(e)
|
|
149
|
-
self.warn(
|
|
150
|
-
ResourceFormatWarning(
|
|
151
|
-
manifest_file, tuple(errors), text="Invalid selector in metadata file, skipping."
|
|
152
|
-
)
|
|
153
|
-
)
|
|
140
|
+
selector_or_warning = load_selector(manifest_file)
|
|
141
|
+
if isinstance(selector_or_warning, ToolkitWarning):
|
|
142
|
+
self.warn(selector_or_warning)
|
|
154
143
|
continue
|
|
144
|
+
selector: Selector = selector_or_warning
|
|
155
145
|
data_files = selector.find_data_files(input_dir, manifest_file)
|
|
156
146
|
if not data_files:
|
|
157
147
|
self.warn(
|
|
@@ -20,7 +20,6 @@ from cognite_toolkit._cdf_tk.constants import (
|
|
|
20
20
|
_RUNNING_IN_BROWSER,
|
|
21
21
|
BUILD_FOLDER_ENCODING,
|
|
22
22
|
DEFAULT_ENV,
|
|
23
|
-
DEV_ONLY_MODULES,
|
|
24
23
|
HINT_LEAD_TEXT,
|
|
25
24
|
ROOT_MODULES,
|
|
26
25
|
TEMPLATE_VARS_FILE_SUFFIXES,
|
|
@@ -59,10 +58,7 @@ from cognite_toolkit._cdf_tk.data_classes import (
|
|
|
59
58
|
SourceLocationLazy,
|
|
60
59
|
)
|
|
61
60
|
from cognite_toolkit._cdf_tk.exceptions import (
|
|
62
|
-
ToolkitDuplicatedModuleError,
|
|
63
|
-
ToolkitEnvError,
|
|
64
61
|
ToolkitError,
|
|
65
|
-
ToolkitMissingModuleError,
|
|
66
62
|
ToolkitYAMLFormatError,
|
|
67
63
|
)
|
|
68
64
|
from cognite_toolkit._cdf_tk.hints import Hint, ModuleDefinition, verify_module_directory
|
|
@@ -89,6 +85,7 @@ from cognite_toolkit._cdf_tk.utils.file import safe_rmtree
|
|
|
89
85
|
from cognite_toolkit._cdf_tk.utils.modules import parse_user_selected_modules
|
|
90
86
|
from cognite_toolkit._cdf_tk.validation import (
|
|
91
87
|
validate_data_set_is_set,
|
|
88
|
+
validate_module_selection,
|
|
92
89
|
validate_modules_variables,
|
|
93
90
|
validate_resource_yaml_pydantic,
|
|
94
91
|
)
|
|
@@ -207,7 +204,11 @@ class BuildCommand(ToolkitCommand):
|
|
|
207
204
|
|
|
208
205
|
user_selected_modules = config.environment.get_selected_modules(packages)
|
|
209
206
|
modules = ModuleDirectories.load(organization_dir, user_selected_modules)
|
|
210
|
-
|
|
207
|
+
module_warnings = validate_module_selection(modules, config, packages, user_selected_modules, organization_dir)
|
|
208
|
+
if module_warnings:
|
|
209
|
+
self.warning_list.extend(module_warnings)
|
|
210
|
+
if self.print_warning:
|
|
211
|
+
print(str(module_warnings))
|
|
211
212
|
|
|
212
213
|
if verbose:
|
|
213
214
|
self.console("Selected packages:")
|
|
@@ -422,62 +423,6 @@ class BuildCommand(ToolkitCommand):
|
|
|
422
423
|
builder = self._builder_by_resource_folder[resource_name]
|
|
423
424
|
return builder
|
|
424
425
|
|
|
425
|
-
def _validate_modules(
|
|
426
|
-
self,
|
|
427
|
-
modules: ModuleDirectories,
|
|
428
|
-
config: BuildConfigYAML,
|
|
429
|
-
packages: dict[str, list[str]],
|
|
430
|
-
selected_modules: set[str | Path],
|
|
431
|
-
organization_dir: Path,
|
|
432
|
-
) -> None:
|
|
433
|
-
# Validations: Ambiguous selection.
|
|
434
|
-
selected_names = {s for s in config.environment.selected if isinstance(s, str)}
|
|
435
|
-
if duplicate_modules := {
|
|
436
|
-
module_name: paths
|
|
437
|
-
for module_name, paths in modules.as_path_by_name().items()
|
|
438
|
-
if len(paths) > 1 and module_name in selected_names
|
|
439
|
-
}:
|
|
440
|
-
# If the user has selected a module by name, and there are multiple modules with that name, raise an error.
|
|
441
|
-
# Note, if the user uses a path to select a module, this error will not be raised.
|
|
442
|
-
raise ToolkitDuplicatedModuleError(
|
|
443
|
-
f"Ambiguous module selected in config.{config.environment.name}.yaml:", duplicate_modules
|
|
444
|
-
)
|
|
445
|
-
# Package Referenced Modules Exists
|
|
446
|
-
for package, package_modules in packages.items():
|
|
447
|
-
if package not in selected_names:
|
|
448
|
-
# We do not check packages that are not selected.
|
|
449
|
-
# Typically, the user will delete the modules that are irrelevant for them;
|
|
450
|
-
# thus we only check the selected packages.
|
|
451
|
-
continue
|
|
452
|
-
if missing_packages := set(package_modules) - modules.available_names:
|
|
453
|
-
ToolkitMissingModuleError(
|
|
454
|
-
f"Package {package} defined in {CDFToml.file_name!s} is referring "
|
|
455
|
-
f"the following missing modules {missing_packages}."
|
|
456
|
-
)
|
|
457
|
-
|
|
458
|
-
# Selected modules does not exists
|
|
459
|
-
if missing_modules := set(selected_modules) - modules.available:
|
|
460
|
-
hint = ModuleDefinition.long(missing_modules, organization_dir)
|
|
461
|
-
raise ToolkitMissingModuleError(
|
|
462
|
-
f"The following selected modules are missing, please check path: {missing_modules}.\n{hint}"
|
|
463
|
-
)
|
|
464
|
-
|
|
465
|
-
# Nothing is Selected
|
|
466
|
-
if not modules.selected:
|
|
467
|
-
raise ToolkitEnvError(
|
|
468
|
-
f"No selected modules specified in {config.filepath!s}, have you configured "
|
|
469
|
-
f"the environment ({config.environment.name})?"
|
|
470
|
-
)
|
|
471
|
-
|
|
472
|
-
dev_modules = modules.available_names & DEV_ONLY_MODULES
|
|
473
|
-
if dev_modules and config.environment.validation_type != "dev":
|
|
474
|
-
self.warn(
|
|
475
|
-
MediumSeverityWarning(
|
|
476
|
-
"The following modules should [bold]only[/bold] be used a in CDF Projects designated as dev (development): "
|
|
477
|
-
f"{humanize_collection(dev_modules)!r}",
|
|
478
|
-
)
|
|
479
|
-
)
|
|
480
|
-
|
|
481
426
|
def _replace_variables(
|
|
482
427
|
self,
|
|
483
428
|
resource_files: Sequence[Path],
|
|
File without changes
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Any, Literal, TypedDict
|
|
3
|
+
|
|
4
|
+
from rich import print
|
|
5
|
+
from rich.panel import Panel
|
|
6
|
+
|
|
7
|
+
from cognite_toolkit._cdf_tk.client import ToolkitClient
|
|
8
|
+
from cognite_toolkit._cdf_tk.commands._base import ToolkitCommand
|
|
9
|
+
from cognite_toolkit._cdf_tk.commands.build_cmd import BuildCommand as OldBuildCommand
|
|
10
|
+
from cognite_toolkit._cdf_tk.commands.build_v2.build_input import BuildInput
|
|
11
|
+
from cognite_toolkit._cdf_tk.commands.build_v2.build_issues import BuildIssue, BuildIssueList
|
|
12
|
+
from cognite_toolkit._cdf_tk.data_classes import (
|
|
13
|
+
BuildConfigYAML,
|
|
14
|
+
BuildVariables,
|
|
15
|
+
BuiltModuleList,
|
|
16
|
+
ModuleDirectories,
|
|
17
|
+
)
|
|
18
|
+
from cognite_toolkit._cdf_tk.exceptions import ToolkitError
|
|
19
|
+
from cognite_toolkit._cdf_tk.hints import verify_module_directory
|
|
20
|
+
from cognite_toolkit._cdf_tk.tk_warnings import ToolkitWarning, WarningList
|
|
21
|
+
from cognite_toolkit._cdf_tk.utils.file import safe_rmtree
|
|
22
|
+
from cognite_toolkit._cdf_tk.validation import validate_module_selection, validate_modules_variables
|
|
23
|
+
from cognite_toolkit._version import __version__
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class BuildWarnings(TypedDict):
|
|
27
|
+
warning: ToolkitWarning
|
|
28
|
+
location: list[Path]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class BuildCommand(ToolkitCommand):
|
|
32
|
+
def __init__(self, print_warning: bool = True, skip_tracking: bool = False, silent: bool = False) -> None:
|
|
33
|
+
super().__init__(print_warning, skip_tracking, silent)
|
|
34
|
+
self.issues = BuildIssueList()
|
|
35
|
+
|
|
36
|
+
def execute(
|
|
37
|
+
self,
|
|
38
|
+
verbose: bool,
|
|
39
|
+
organization_dir: Path,
|
|
40
|
+
build_dir: Path,
|
|
41
|
+
selected: list[str | Path] | None,
|
|
42
|
+
build_env_name: str | None,
|
|
43
|
+
no_clean: bool,
|
|
44
|
+
client: ToolkitClient | None = None,
|
|
45
|
+
on_error: Literal["continue", "raise"] = "continue",
|
|
46
|
+
) -> BuiltModuleList:
|
|
47
|
+
"""
|
|
48
|
+
Build the resources into deployable artifacts in the build directory.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
self.verbose = verbose
|
|
52
|
+
self.on_error = on_error
|
|
53
|
+
|
|
54
|
+
# Tracking the project and cluster for the build.
|
|
55
|
+
if client:
|
|
56
|
+
self._additional_tracking_info.project = client.config.project
|
|
57
|
+
self._additional_tracking_info.cluster = client.config.cdf_cluster
|
|
58
|
+
|
|
59
|
+
# Setting the parameters for the build.
|
|
60
|
+
input = BuildInput.load(organization_dir, build_dir, build_env_name, client, selected)
|
|
61
|
+
|
|
62
|
+
# Print the build input.
|
|
63
|
+
if self.verbose:
|
|
64
|
+
self._print_build_input(input)
|
|
65
|
+
|
|
66
|
+
# Capture warnings from module structure integrity
|
|
67
|
+
if module_selection_issues := self._validate_modules(input):
|
|
68
|
+
self.issues.extend(module_selection_issues)
|
|
69
|
+
|
|
70
|
+
# Logistics: clean and create build directory
|
|
71
|
+
if prepare_issues := self._prepare_target_directory(input, not no_clean):
|
|
72
|
+
self.issues.extend(prepare_issues)
|
|
73
|
+
|
|
74
|
+
# Compile the configuration and variables,
|
|
75
|
+
# check syntax on module and resource level
|
|
76
|
+
# for any "compilation errors and warnings"
|
|
77
|
+
built_modules, build_integrity_issues = self._build_configuration(input)
|
|
78
|
+
if build_integrity_issues:
|
|
79
|
+
self.issues.extend(build_integrity_issues)
|
|
80
|
+
|
|
81
|
+
# This is where we would add any recommendations for the user to improve the build.
|
|
82
|
+
if build_quality_issues := self._verify_build_quality(built_modules):
|
|
83
|
+
self.issues.extend(build_quality_issues)
|
|
84
|
+
|
|
85
|
+
# Finally, print warnings grouped by category/code and location.
|
|
86
|
+
self._print_or_log_warnings_by_category(self.issues)
|
|
87
|
+
|
|
88
|
+
return built_modules
|
|
89
|
+
|
|
90
|
+
def _print_build_input(self, input: BuildInput) -> None:
|
|
91
|
+
print(
|
|
92
|
+
Panel(
|
|
93
|
+
f"Building {input.organization_dir!s}:\n - Toolkit Version '{__version__!s}'\n"
|
|
94
|
+
f" - Environment name {input.build_env_name!r}, validation-type {input.config.environment.validation_type!r}.\n"
|
|
95
|
+
f" - Config '{input.config.filepath!s}'",
|
|
96
|
+
expand=False,
|
|
97
|
+
)
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
def _prepare_target_directory(self, input: BuildInput, clean: bool = False) -> BuildIssueList:
|
|
101
|
+
"""
|
|
102
|
+
Directory logistics
|
|
103
|
+
"""
|
|
104
|
+
issues = BuildIssueList()
|
|
105
|
+
if input.build_dir.exists() and any(input.build_dir.iterdir()):
|
|
106
|
+
if not clean:
|
|
107
|
+
raise ToolkitError("Build directory is not empty. Run without --no-clean to remove existing files.")
|
|
108
|
+
|
|
109
|
+
if self.verbose:
|
|
110
|
+
issues.append(BuildIssue(description=f"Build directory {input.build_dir!s} is not empty. Clearing."))
|
|
111
|
+
safe_rmtree(input.build_dir)
|
|
112
|
+
input.build_dir.mkdir(parents=True, exist_ok=True)
|
|
113
|
+
return issues
|
|
114
|
+
|
|
115
|
+
def _validate_modules(self, input: BuildInput) -> BuildIssueList:
|
|
116
|
+
issues = BuildIssueList()
|
|
117
|
+
# Verify that the modules exists, are not duplicates,
|
|
118
|
+
# and at least one is selected
|
|
119
|
+
verify_module_directory(input.organization_dir, input.build_env_name)
|
|
120
|
+
|
|
121
|
+
# Validate module selection
|
|
122
|
+
user_selected_modules = input.config.environment.get_selected_modules({})
|
|
123
|
+
module_warnings = validate_module_selection(
|
|
124
|
+
modules=input.modules,
|
|
125
|
+
config=input.config,
|
|
126
|
+
packages={},
|
|
127
|
+
selected_modules=user_selected_modules,
|
|
128
|
+
organization_dir=input.organization_dir,
|
|
129
|
+
)
|
|
130
|
+
if module_warnings:
|
|
131
|
+
issues.extend(BuildIssueList.from_warning_list(module_warnings))
|
|
132
|
+
|
|
133
|
+
# Validate variables. Note: this looks for non-replaced template
|
|
134
|
+
# variables <.*?> and can be improved in the future.
|
|
135
|
+
# Keeping for reference.
|
|
136
|
+
variables_warnings = validate_modules_variables(input.variables, input.config.filepath)
|
|
137
|
+
if variables_warnings:
|
|
138
|
+
issues.extend(BuildIssueList.from_warning_list(variables_warnings))
|
|
139
|
+
|
|
140
|
+
# Track LOC of managed configuration
|
|
141
|
+
# Note: _track is not implemented yet, so we skip it for now
|
|
142
|
+
# self._track(input)
|
|
143
|
+
|
|
144
|
+
return issues
|
|
145
|
+
|
|
146
|
+
def _build_configuration(self, input: BuildInput) -> tuple[BuiltModuleList, BuildIssueList]:
|
|
147
|
+
issues = BuildIssueList()
|
|
148
|
+
# Use input.modules.selected directly (it's already a ModuleDirectories)
|
|
149
|
+
if not input.modules.selected:
|
|
150
|
+
return BuiltModuleList(), issues
|
|
151
|
+
|
|
152
|
+
# first collect variables into practical lookup
|
|
153
|
+
# TODO: parallelism is not implemented yet. I'm sure there are optimizations to be had here, but we'll focus on process parallelism since we believe loading yaml and file i/O are the biggest bottlenecks.
|
|
154
|
+
|
|
155
|
+
old_build_command = OldBuildCommand(print_warning=False, skip_tracking=False)
|
|
156
|
+
built_modules = old_build_command.build_config(
|
|
157
|
+
build_dir=input.build_dir,
|
|
158
|
+
organization_dir=input.organization_dir,
|
|
159
|
+
config=input.config,
|
|
160
|
+
packages={},
|
|
161
|
+
clean=False,
|
|
162
|
+
verbose=self.verbose,
|
|
163
|
+
client=input.client,
|
|
164
|
+
progress_bar=False,
|
|
165
|
+
on_error=self.on_error,
|
|
166
|
+
)
|
|
167
|
+
# Copy tracking info from old command to self
|
|
168
|
+
self._additional_tracking_info.package_ids.update(old_build_command._additional_tracking_info.package_ids)
|
|
169
|
+
self._additional_tracking_info.module_ids.update(old_build_command._additional_tracking_info.module_ids)
|
|
170
|
+
|
|
171
|
+
# Collect warnings from the old build command and convert to issues
|
|
172
|
+
# Always convert warnings to issues, even if the list appears empty
|
|
173
|
+
# (WarningList might have custom __bool__ behavior)
|
|
174
|
+
if old_build_command.warning_list:
|
|
175
|
+
converted_issues = BuildIssueList.from_warning_list(old_build_command.warning_list)
|
|
176
|
+
issues.extend(converted_issues)
|
|
177
|
+
return built_modules, issues
|
|
178
|
+
|
|
179
|
+
def _verify_build_quality(self, built_modules: BuiltModuleList) -> BuildIssueList:
|
|
180
|
+
issues = BuildIssueList()
|
|
181
|
+
return issues
|
|
182
|
+
|
|
183
|
+
def _write(self, input: BuildInput) -> None:
|
|
184
|
+
# Write the build to the build directory.
|
|
185
|
+
# Track lines of code built.
|
|
186
|
+
raise NotImplementedError()
|
|
187
|
+
|
|
188
|
+
def _track(self, input: BuildInput) -> None:
|
|
189
|
+
raise NotImplementedError()
|
|
190
|
+
|
|
191
|
+
def _print_or_log_warnings_by_category(self, issues: BuildIssueList) -> None:
|
|
192
|
+
pass
|
|
193
|
+
|
|
194
|
+
# Delegate to old BuildCommand for backward compatibility with tests
|
|
195
|
+
def build_modules(
|
|
196
|
+
self,
|
|
197
|
+
modules: ModuleDirectories,
|
|
198
|
+
build_dir: Path,
|
|
199
|
+
variables: BuildVariables,
|
|
200
|
+
verbose: bool = False,
|
|
201
|
+
progress_bar: bool = False,
|
|
202
|
+
on_error: Literal["continue", "raise"] = "continue",
|
|
203
|
+
) -> BuiltModuleList:
|
|
204
|
+
"""Delegate to old BuildCommand for backward compatibility."""
|
|
205
|
+
old_cmd = OldBuildCommand()
|
|
206
|
+
|
|
207
|
+
built_modules = old_cmd.build_modules(modules, build_dir, variables, verbose, progress_bar, on_error)
|
|
208
|
+
self._additional_tracking_info.package_ids.update(old_cmd._additional_tracking_info.package_ids)
|
|
209
|
+
self._additional_tracking_info.module_ids.update(old_cmd._additional_tracking_info.module_ids)
|
|
210
|
+
self.issues.extend(BuildIssueList.from_warning_list(old_cmd.warning_list or WarningList[ToolkitWarning]()))
|
|
211
|
+
return built_modules
|
|
212
|
+
|
|
213
|
+
def build_config(
|
|
214
|
+
self,
|
|
215
|
+
build_dir: Path,
|
|
216
|
+
organization_dir: Path,
|
|
217
|
+
config: BuildConfigYAML,
|
|
218
|
+
packages: dict[str, list[str]],
|
|
219
|
+
clean: bool = False,
|
|
220
|
+
verbose: bool = False,
|
|
221
|
+
client: ToolkitClient | None = None,
|
|
222
|
+
progress_bar: bool = False,
|
|
223
|
+
on_error: Literal["continue", "raise"] = "continue",
|
|
224
|
+
) -> BuiltModuleList:
|
|
225
|
+
"""Delegate to old BuildCommand for backward compatibility."""
|
|
226
|
+
old_cmd = OldBuildCommand()
|
|
227
|
+
return old_cmd.build_config(
|
|
228
|
+
build_dir, organization_dir, config, packages, clean, verbose, client, progress_bar, on_error
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
def _replace_variables(
|
|
232
|
+
self,
|
|
233
|
+
resource_files: list[Path],
|
|
234
|
+
variables: BuildVariables,
|
|
235
|
+
resource_name: str,
|
|
236
|
+
module_dir: Path,
|
|
237
|
+
verbose: bool = False,
|
|
238
|
+
) -> list[Any]:
|
|
239
|
+
"""Delegate to old BuildCommand for backward compatibility."""
|
|
240
|
+
old_cmd = OldBuildCommand()
|
|
241
|
+
return old_cmd._replace_variables(resource_files, variables, resource_name, module_dir, verbose)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from functools import cached_property
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
if sys.version_info >= (3, 11):
|
|
6
|
+
from typing import Self
|
|
7
|
+
else:
|
|
8
|
+
from typing_extensions import Self
|
|
9
|
+
|
|
10
|
+
from pydantic import BaseModel, ConfigDict
|
|
11
|
+
|
|
12
|
+
from cognite_toolkit._cdf_tk.client import ToolkitClient
|
|
13
|
+
from cognite_toolkit._cdf_tk.constants import DEFAULT_ENV
|
|
14
|
+
from cognite_toolkit._cdf_tk.data_classes import (
|
|
15
|
+
BuildConfigYAML,
|
|
16
|
+
BuildVariables,
|
|
17
|
+
ModuleDirectories,
|
|
18
|
+
)
|
|
19
|
+
from cognite_toolkit._cdf_tk.tk_warnings import ToolkitWarning, WarningList
|
|
20
|
+
from cognite_toolkit._cdf_tk.utils.modules import parse_user_selected_modules
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class BuildInput(BaseModel):
|
|
24
|
+
"""Input to the build process."""
|
|
25
|
+
|
|
26
|
+
# need this until we turn BuildConfigYaml and ToolkitClient into Pydantic models
|
|
27
|
+
model_config = ConfigDict(frozen=True, arbitrary_types_allowed=True)
|
|
28
|
+
|
|
29
|
+
organization_dir: Path
|
|
30
|
+
build_dir: Path
|
|
31
|
+
build_env_name: str
|
|
32
|
+
config: BuildConfigYAML
|
|
33
|
+
client: ToolkitClient | None = None
|
|
34
|
+
selected: list[str | Path] | None = None
|
|
35
|
+
warnings: WarningList[ToolkitWarning] | None = None
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def load(
|
|
39
|
+
cls,
|
|
40
|
+
organization_dir: Path,
|
|
41
|
+
build_dir: Path,
|
|
42
|
+
build_env_name: str | None,
|
|
43
|
+
client: ToolkitClient | None,
|
|
44
|
+
selected: list[str | Path] | None = None,
|
|
45
|
+
) -> Self:
|
|
46
|
+
resolved_org_dir = Path.cwd() if organization_dir in {Path("."), Path("./")} else organization_dir
|
|
47
|
+
resolved_env = build_env_name or DEFAULT_ENV
|
|
48
|
+
config, warnings = cls._load_config(resolved_org_dir, resolved_env, selected)
|
|
49
|
+
return cls(
|
|
50
|
+
organization_dir=resolved_org_dir,
|
|
51
|
+
build_dir=build_dir,
|
|
52
|
+
build_env_name=resolved_env,
|
|
53
|
+
config=config,
|
|
54
|
+
client=client,
|
|
55
|
+
selected=selected,
|
|
56
|
+
warnings=warnings,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
@classmethod
|
|
60
|
+
def _load_config(
|
|
61
|
+
cls, organization_dir: Path, build_env_name: str, selected: list[str | Path] | None
|
|
62
|
+
) -> tuple[BuildConfigYAML, WarningList[ToolkitWarning]]:
|
|
63
|
+
warnings: WarningList[ToolkitWarning] = WarningList[ToolkitWarning]()
|
|
64
|
+
if (organization_dir / BuildConfigYAML.get_filename(build_env_name or DEFAULT_ENV)).exists():
|
|
65
|
+
config = BuildConfigYAML.load_from_directory(organization_dir, build_env_name or DEFAULT_ENV)
|
|
66
|
+
else:
|
|
67
|
+
# Loads the default environment
|
|
68
|
+
config = BuildConfigYAML.load_default(organization_dir)
|
|
69
|
+
if selected:
|
|
70
|
+
config.environment.selected = parse_user_selected_modules(selected, organization_dir)
|
|
71
|
+
config.set_environment_variables()
|
|
72
|
+
if environment_warning := config.validate_environment():
|
|
73
|
+
warnings.append(environment_warning)
|
|
74
|
+
return config, warnings
|
|
75
|
+
|
|
76
|
+
@cached_property
|
|
77
|
+
def modules(self) -> ModuleDirectories:
|
|
78
|
+
user_selected_modules = self.config.environment.get_selected_modules({})
|
|
79
|
+
return ModuleDirectories.load(self.organization_dir, user_selected_modules)
|
|
80
|
+
|
|
81
|
+
@cached_property
|
|
82
|
+
def variables(self) -> BuildVariables:
|
|
83
|
+
return BuildVariables.load_raw(
|
|
84
|
+
self.config.variables, self.modules.available_paths, self.modules.selected.available_paths
|
|
85
|
+
)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
if sys.version_info >= (3, 11):
|
|
4
|
+
from typing import Self
|
|
5
|
+
else:
|
|
6
|
+
from typing_extensions import Self
|
|
7
|
+
|
|
8
|
+
from collections import UserList
|
|
9
|
+
|
|
10
|
+
from pydantic import BaseModel
|
|
11
|
+
|
|
12
|
+
from cognite_toolkit._cdf_tk.tk_warnings import ToolkitWarning, WarningList
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BuildIssue(BaseModel):
|
|
16
|
+
"""Issue with the build. Can have a recommendation for the user to improve the build."""
|
|
17
|
+
|
|
18
|
+
description: str
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class BuildIssueList(UserList[BuildIssue]):
|
|
22
|
+
"""List of build issues."""
|
|
23
|
+
|
|
24
|
+
@classmethod
|
|
25
|
+
def from_warning_list(cls, warning_list: WarningList[ToolkitWarning]) -> Self:
|
|
26
|
+
"""Create a BuildIssueList from a WarningList."""
|
|
27
|
+
return cls([BuildIssue(description=warning.get_message()) for warning in warning_list])
|
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
# limitations under the License.
|
|
27
27
|
|
|
28
28
|
|
|
29
|
+
import random
|
|
30
|
+
import time
|
|
29
31
|
import warnings
|
|
30
32
|
from collections import defaultdict
|
|
31
33
|
from collections.abc import Callable, Hashable, Iterable, Sequence
|
|
@@ -427,25 +429,58 @@ class TransformationCRUD(ResourceCRUD[str, TransformationWrite, Transformation])
|
|
|
427
429
|
raise error from e
|
|
428
430
|
raise e
|
|
429
431
|
except CogniteAPIError as e:
|
|
430
|
-
if "Failed to bind session using nonce
|
|
431
|
-
|
|
432
|
-
f"Failed to create {len(chunk)} transformations in a batch due to nonce binding error. "
|
|
433
|
-
"Trying to recover by creating them one by one."
|
|
434
|
-
).print_warning(console=self.console)
|
|
435
|
-
# Retry one by one
|
|
436
|
-
for item in chunk:
|
|
437
|
-
recovered = self._execute_in_batches(items=[item], api_call=api_call)
|
|
438
|
-
results.extend(recovered)
|
|
439
|
-
if self.console:
|
|
440
|
-
self.console.print(
|
|
441
|
-
f" [bold green]RECOVERED:[/] Successfully created {len(chunk)} transformations one by one."
|
|
442
|
-
)
|
|
432
|
+
if "Failed to bind session using nonce" in e.message and len(chunk) > 1:
|
|
433
|
+
results.extend(self._execute_one_by_one(chunk, api_call))
|
|
443
434
|
else:
|
|
444
435
|
raise
|
|
445
436
|
else:
|
|
446
437
|
results.extend(chunk_results)
|
|
447
438
|
return results
|
|
448
439
|
|
|
440
|
+
def _execute_one_by_one(
|
|
441
|
+
self,
|
|
442
|
+
chunk: Sequence[TransformationWrite],
|
|
443
|
+
api_call: Callable[[Sequence[TransformationWrite]], TransformationList],
|
|
444
|
+
) -> TransformationList:
|
|
445
|
+
MediumSeverityWarning(
|
|
446
|
+
f"Failed to create {len(chunk)} transformations in a batch due to nonce binding error. "
|
|
447
|
+
"Trying to recover by creating them one by one."
|
|
448
|
+
).print_warning(console=self.client.console)
|
|
449
|
+
# Retry one by one
|
|
450
|
+
failed_ids: list[str] = []
|
|
451
|
+
success_count = 0
|
|
452
|
+
delay = 0.3
|
|
453
|
+
self._sleep_with_jitter(delay, delay + 0.3)
|
|
454
|
+
results = TransformationList([])
|
|
455
|
+
for item in chunk:
|
|
456
|
+
try:
|
|
457
|
+
recovered = api_call([item])
|
|
458
|
+
except CogniteAPIError as e:
|
|
459
|
+
if "Failed to bind session using nonce" in e.message:
|
|
460
|
+
failed_ids.append(item.external_id or "<missing>")
|
|
461
|
+
self._sleep_with_jitter(delay, delay + 0.3)
|
|
462
|
+
else:
|
|
463
|
+
raise
|
|
464
|
+
else:
|
|
465
|
+
results.extend(recovered)
|
|
466
|
+
success_count += 1
|
|
467
|
+
message = f" [bold]RECOVERY COMPLETE:[/] Successfully created {success_count:,} transformations"
|
|
468
|
+
if failed_ids:
|
|
469
|
+
message += f", failed to create {len(failed_ids):,} transformations: {humanize_collection(failed_ids)}"
|
|
470
|
+
else:
|
|
471
|
+
message += "."
|
|
472
|
+
if failed_ids:
|
|
473
|
+
HighSeverityWarning(message).print_warning(include_timestamp=True, console=self.client.console)
|
|
474
|
+
else:
|
|
475
|
+
self.client.console.print(message)
|
|
476
|
+
return results
|
|
477
|
+
|
|
478
|
+
@staticmethod
|
|
479
|
+
def _sleep_with_jitter(base_delay: float, max_delay: float) -> None:
|
|
480
|
+
"""Sleeps for a random duration between base_delay and max_delay (inclusive)."""
|
|
481
|
+
sleep_time = random.uniform(base_delay, max_delay)
|
|
482
|
+
time.sleep(sleep_time)
|
|
483
|
+
|
|
449
484
|
def _update_nonce(self, items: Sequence[TransformationWrite]) -> None:
|
|
450
485
|
for item in items:
|
|
451
486
|
if not item.external_id:
|
|
@@ -57,6 +57,10 @@ class Flags(Enum):
|
|
|
57
57
|
visible=True,
|
|
58
58
|
description="Enables extended download to support downloading file content and datapoints",
|
|
59
59
|
)
|
|
60
|
+
EXTEND_UPLOAD = FlagMetadata(
|
|
61
|
+
visible=True,
|
|
62
|
+
description="Enables extended upload to support uploading individual files",
|
|
63
|
+
)
|
|
60
64
|
|
|
61
65
|
def is_enabled(self) -> bool:
|
|
62
66
|
return FeatureFlag.is_enabled(self)
|
|
@@ -1,6 +1,13 @@
|
|
|
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
|
|
@@ -52,9 +59,33 @@ Selector = Annotated[
|
|
|
52
59
|
Field(discriminator="type"),
|
|
53
60
|
]
|
|
54
61
|
|
|
62
|
+
ALPHA_SELECTORS = {FileIdentifierSelector}
|
|
63
|
+
|
|
55
64
|
SelectorAdapter: TypeAdapter[Selector] = TypeAdapter(Selector)
|
|
56
65
|
|
|
57
66
|
|
|
67
|
+
def load_selector(manifest_file: Path) -> Selector | ToolkitWarning:
|
|
68
|
+
"""Loads a selector from a manifest file.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
manifest_file: Path to the manifest file.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
A selector object or a toolkit warning if loading fails or the selector is an alpha feature that is not enabled.
|
|
75
|
+
"""
|
|
76
|
+
selector_dict = read_yaml_file(manifest_file, expected_output="dict")
|
|
77
|
+
try:
|
|
78
|
+
selector = SelectorAdapter.validate_python(selector_dict)
|
|
79
|
+
except ValidationError as e:
|
|
80
|
+
errors = humanize_validation_error(e)
|
|
81
|
+
return ResourceFormatWarning(manifest_file, tuple(errors), text="Invalid selector in metadata file, skipping.")
|
|
82
|
+
if not Flags.EXTEND_UPLOAD.is_enabled() and type(selector) in ALPHA_SELECTORS:
|
|
83
|
+
return MediumSeverityWarning(
|
|
84
|
+
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."
|
|
85
|
+
)
|
|
86
|
+
return selector
|
|
87
|
+
|
|
88
|
+
|
|
58
89
|
__all__ = [
|
|
59
90
|
"AllChartsSelector",
|
|
60
91
|
"AssetCentricFileSelector",
|
|
@@ -89,4 +120,5 @@ __all__ = [
|
|
|
89
120
|
"Selector",
|
|
90
121
|
"SelectorAdapter",
|
|
91
122
|
"TimeSeriesColumn",
|
|
123
|
+
"load_selector",
|
|
92
124
|
]
|
|
@@ -4,7 +4,7 @@ import sys
|
|
|
4
4
|
import time
|
|
5
5
|
from collections import deque
|
|
6
6
|
from collections.abc import MutableMapping, Sequence, Set
|
|
7
|
-
from typing import Literal
|
|
7
|
+
from typing import Literal, TypeVar
|
|
8
8
|
|
|
9
9
|
import httpx
|
|
10
10
|
from cognite.client import global_config
|
|
@@ -29,6 +29,11 @@ from cognite_toolkit._cdf_tk.utils.http_client._data_classes2 import (
|
|
|
29
29
|
FailedRequest2,
|
|
30
30
|
FailedResponse2,
|
|
31
31
|
HTTPResult2,
|
|
32
|
+
ItemsFailedRequest2,
|
|
33
|
+
ItemsFailedResponse2,
|
|
34
|
+
ItemsRequest2,
|
|
35
|
+
ItemsResultMessage2,
|
|
36
|
+
ItemsSuccessResponse2,
|
|
32
37
|
RequestMessage2,
|
|
33
38
|
SuccessResponse2,
|
|
34
39
|
)
|
|
@@ -41,6 +46,8 @@ else:
|
|
|
41
46
|
|
|
42
47
|
from cognite_toolkit._cdf_tk.client.config import ToolkitClientConfig
|
|
43
48
|
|
|
49
|
+
_T_Request_Message = TypeVar("_T_Request_Message", bound=BaseRequestMessage)
|
|
50
|
+
|
|
44
51
|
|
|
45
52
|
class HTTPClient:
|
|
46
53
|
"""An HTTP client.
|
|
@@ -344,6 +351,17 @@ class HTTPClient:
|
|
|
344
351
|
body=response.text,
|
|
345
352
|
content=response.content,
|
|
346
353
|
)
|
|
354
|
+
if retry_request := self._retry_request(response, request):
|
|
355
|
+
return retry_request
|
|
356
|
+
else:
|
|
357
|
+
# Permanent failure
|
|
358
|
+
return FailedResponse2(
|
|
359
|
+
status_code=response.status_code,
|
|
360
|
+
body=response.text,
|
|
361
|
+
error=ErrorDetails2.from_response(response),
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
def _retry_request(self, response: httpx.Response, request: _T_Request_Message) -> _T_Request_Message | None:
|
|
347
365
|
retry_after = self._get_retry_after_in_header(response)
|
|
348
366
|
if retry_after is not None and response.status_code == 429 and request.status_attempt < self._max_retries:
|
|
349
367
|
if self._console is not None:
|
|
@@ -359,13 +377,7 @@ class HTTPClient:
|
|
|
359
377
|
request.status_attempt += 1
|
|
360
378
|
time.sleep(self._backoff_time(request.total_attempts))
|
|
361
379
|
return request
|
|
362
|
-
|
|
363
|
-
# Permanent failure
|
|
364
|
-
return FailedResponse2(
|
|
365
|
-
status_code=response.status_code,
|
|
366
|
-
body=response.text,
|
|
367
|
-
error=ErrorDetails2.from_response(response),
|
|
368
|
-
)
|
|
380
|
+
return None
|
|
369
381
|
|
|
370
382
|
def _handle_error_single(self, e: Exception, request: RequestMessage2) -> RequestMessage2 | HTTPResult2:
|
|
371
383
|
if isinstance(e, httpx.ReadTimeout | httpx.TimeoutException):
|
|
@@ -388,3 +400,136 @@ class HTTPClient:
|
|
|
388
400
|
error_msg = f"RequestException after {request.total_attempts - 1} attempts ({error_type} error): {e!s}"
|
|
389
401
|
|
|
390
402
|
return FailedRequest2(error=error_msg)
|
|
403
|
+
|
|
404
|
+
def request_items(self, message: ItemsRequest2) -> Sequence[ItemsRequest2 | ItemsResultMessage2]:
|
|
405
|
+
"""Send an HTTP request with multiple items and return the response.
|
|
406
|
+
|
|
407
|
+
Args:
|
|
408
|
+
message (ItemsRequest2): The request message to send.
|
|
409
|
+
Returns:
|
|
410
|
+
Sequence[ItemsRequest2 | ItemsResultMessage2]: The response message(s). This can also
|
|
411
|
+
include ItemsRequest2(s) to be retried or split.
|
|
412
|
+
"""
|
|
413
|
+
if message.tracker and message.tracker.limit_reached():
|
|
414
|
+
return [
|
|
415
|
+
ItemsFailedRequest2(
|
|
416
|
+
ids=[item.as_id() for item in message.items],
|
|
417
|
+
error_message=f"Aborting further splitting of requests after {message.tracker.failed_split_count} failed attempts.",
|
|
418
|
+
)
|
|
419
|
+
]
|
|
420
|
+
try:
|
|
421
|
+
response = self._make_request2(message)
|
|
422
|
+
results = self._handle_items_response(response, message)
|
|
423
|
+
except Exception as e:
|
|
424
|
+
results = self._handle_items_error(e, message)
|
|
425
|
+
return results
|
|
426
|
+
|
|
427
|
+
def request_items_retries(self, message: ItemsRequest2) -> Sequence[ItemsResultMessage2]:
|
|
428
|
+
"""Send an HTTP request with multiple items and handle retries.
|
|
429
|
+
|
|
430
|
+
This method will keep retrying the request until it either succeeds or
|
|
431
|
+
exhausts the maximum number of retries.
|
|
432
|
+
|
|
433
|
+
Note this method will use the current thread to process all request, thus
|
|
434
|
+
it is blocking.
|
|
435
|
+
|
|
436
|
+
Args:
|
|
437
|
+
message (ItemsRequest2): The request message to send.
|
|
438
|
+
Returns:
|
|
439
|
+
Sequence[ItemsResultMessage2]: The final response message, which can be either successful response or failed request.
|
|
440
|
+
"""
|
|
441
|
+
if message.total_attempts > 0:
|
|
442
|
+
raise RuntimeError(f"ItemsRequest2 has already been attempted {message.total_attempts} times.")
|
|
443
|
+
pending_requests: deque[ItemsRequest2] = deque()
|
|
444
|
+
pending_requests.append(message)
|
|
445
|
+
final_responses: list[ItemsResultMessage2] = []
|
|
446
|
+
while pending_requests:
|
|
447
|
+
current_request = pending_requests.popleft()
|
|
448
|
+
results = self.request_items(current_request)
|
|
449
|
+
|
|
450
|
+
for result in results:
|
|
451
|
+
if isinstance(result, ItemsRequest2):
|
|
452
|
+
pending_requests.append(result)
|
|
453
|
+
elif isinstance(result, ItemsResultMessage2):
|
|
454
|
+
final_responses.append(result)
|
|
455
|
+
else:
|
|
456
|
+
raise TypeError(f"Unexpected result type: {type(result)}")
|
|
457
|
+
|
|
458
|
+
return final_responses
|
|
459
|
+
|
|
460
|
+
def _handle_items_response(
|
|
461
|
+
self, response: httpx.Response, request: ItemsRequest2
|
|
462
|
+
) -> Sequence[ItemsRequest2 | ItemsResultMessage2]:
|
|
463
|
+
if 200 <= response.status_code < 300:
|
|
464
|
+
return [
|
|
465
|
+
ItemsSuccessResponse2(
|
|
466
|
+
ids=[item.as_id() for item in request.items],
|
|
467
|
+
status_code=response.status_code,
|
|
468
|
+
body=response.text,
|
|
469
|
+
content=response.content,
|
|
470
|
+
)
|
|
471
|
+
]
|
|
472
|
+
elif len(request.items) > 1 and response.status_code in self._split_items_status_codes:
|
|
473
|
+
# 4XX: Status there is at least one item that is invalid, split the batch to get all valid items processed
|
|
474
|
+
# 5xx: Server error, split to reduce the number of items in each request, and count as a status attempt
|
|
475
|
+
status_attempts = request.status_attempt
|
|
476
|
+
if 500 <= response.status_code < 600:
|
|
477
|
+
status_attempts += 1
|
|
478
|
+
splits = request.split(status_attempts=status_attempts)
|
|
479
|
+
if splits[0].tracker and splits[0].tracker.limit_reached():
|
|
480
|
+
return [
|
|
481
|
+
ItemsFailedResponse2(
|
|
482
|
+
ids=[item.as_id() for item in request.items],
|
|
483
|
+
status_code=response.status_code,
|
|
484
|
+
body=response.text,
|
|
485
|
+
error=ErrorDetails2.from_response(response),
|
|
486
|
+
)
|
|
487
|
+
]
|
|
488
|
+
return splits
|
|
489
|
+
|
|
490
|
+
if retry_request := self._retry_request(response, request):
|
|
491
|
+
return [retry_request]
|
|
492
|
+
else:
|
|
493
|
+
# Permanent failure
|
|
494
|
+
return [
|
|
495
|
+
ItemsFailedResponse2(
|
|
496
|
+
ids=[item.as_id() for item in request.items],
|
|
497
|
+
status_code=response.status_code,
|
|
498
|
+
body=response.text,
|
|
499
|
+
error=ErrorDetails2.from_response(response),
|
|
500
|
+
)
|
|
501
|
+
]
|
|
502
|
+
|
|
503
|
+
def _handle_items_error(
|
|
504
|
+
self, e: Exception, request: ItemsRequest2
|
|
505
|
+
) -> Sequence[ItemsRequest2 | ItemsResultMessage2]:
|
|
506
|
+
if isinstance(e, httpx.ReadTimeout | httpx.TimeoutException):
|
|
507
|
+
error_type = "read"
|
|
508
|
+
request.read_attempt += 1
|
|
509
|
+
attempts = request.read_attempt
|
|
510
|
+
elif isinstance(e, ConnectionError | httpx.ConnectError | httpx.ConnectTimeout):
|
|
511
|
+
error_type = "connect"
|
|
512
|
+
request.connect_attempt += 1
|
|
513
|
+
attempts = request.connect_attempt
|
|
514
|
+
else:
|
|
515
|
+
error_msg = f"Unexpected exception: {e!s}"
|
|
516
|
+
return [
|
|
517
|
+
ItemsFailedRequest2(
|
|
518
|
+
ids=[item.as_id() for item in request.items],
|
|
519
|
+
error_message=error_msg,
|
|
520
|
+
)
|
|
521
|
+
]
|
|
522
|
+
|
|
523
|
+
if attempts <= self._max_retries:
|
|
524
|
+
time.sleep(self._backoff_time(request.total_attempts))
|
|
525
|
+
return [request]
|
|
526
|
+
else:
|
|
527
|
+
# We have already incremented the attempt count, so we subtract 1 here
|
|
528
|
+
error_msg = f"RequestException after {request.total_attempts - 1} attempts ({error_type} error): {e!s}"
|
|
529
|
+
|
|
530
|
+
return [
|
|
531
|
+
ItemsFailedRequest2(
|
|
532
|
+
ids=[item.as_id() for item in request.items],
|
|
533
|
+
error_message=error_msg,
|
|
534
|
+
)
|
|
535
|
+
]
|
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
import gzip
|
|
2
|
+
import sys
|
|
2
3
|
from abc import ABC, abstractmethod
|
|
4
|
+
from collections.abc import Hashable
|
|
3
5
|
from typing import Any, Literal
|
|
4
6
|
|
|
5
7
|
import httpx
|
|
6
8
|
from cognite.client import global_config
|
|
7
|
-
from pydantic import BaseModel, JsonValue, TypeAdapter, model_validator
|
|
9
|
+
from pydantic import BaseModel, ConfigDict, Field, JsonValue, TypeAdapter, model_validator
|
|
10
|
+
from pydantic.alias_generators import to_camel
|
|
8
11
|
|
|
12
|
+
from cognite_toolkit._cdf_tk.utils.http_client._tracker import ItemsRequestTracker
|
|
9
13
|
from cognite_toolkit._cdf_tk.utils.useful_types import PrimitiveType
|
|
10
14
|
|
|
15
|
+
if sys.version_info >= (3, 11):
|
|
16
|
+
from typing import Self
|
|
17
|
+
else:
|
|
18
|
+
from typing_extensions import Self
|
|
19
|
+
|
|
11
20
|
|
|
12
21
|
class HTTPResult2(BaseModel): ...
|
|
13
22
|
|
|
@@ -95,3 +104,84 @@ class RequestMessage2(BaseRequestMessage):
|
|
|
95
104
|
|
|
96
105
|
|
|
97
106
|
_BODY_SERIALIZER = TypeAdapter(dict[str, JsonValue])
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class ItemsResultMessage2(BaseModel):
|
|
110
|
+
ids: list[Hashable]
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class ItemsFailedRequest2(ItemsResultMessage2):
|
|
114
|
+
error_message: str
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class ItemsSuccessResponse2(ItemsResultMessage2):
|
|
118
|
+
status_code: int
|
|
119
|
+
body: str
|
|
120
|
+
content: bytes
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class ItemsFailedResponse2(ItemsResultMessage2):
|
|
124
|
+
status_code: int
|
|
125
|
+
error: ErrorDetails2
|
|
126
|
+
body: str
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class BaseModelObject(BaseModel):
|
|
130
|
+
"""Base class for all object. This includes resources and nested objects."""
|
|
131
|
+
|
|
132
|
+
# We allow extra fields to support forward compatibility.
|
|
133
|
+
model_config = ConfigDict(alias_generator=to_camel, extra="allow")
|
|
134
|
+
|
|
135
|
+
def dump(self, camel_case: bool = True) -> dict[str, Any]:
|
|
136
|
+
"""Dump the resource to a dictionary.
|
|
137
|
+
|
|
138
|
+
This is the default serialization method for request resources.
|
|
139
|
+
"""
|
|
140
|
+
return self.model_dump(mode="json", by_alias=camel_case, exclude_unset=True)
|
|
141
|
+
|
|
142
|
+
@classmethod
|
|
143
|
+
def _load(cls, resource: dict[str, Any]) -> "Self":
|
|
144
|
+
"""Load method to match CogniteResource signature."""
|
|
145
|
+
return cls.model_validate(resource)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class RequestResource(BaseModelObject, ABC):
|
|
149
|
+
@abstractmethod
|
|
150
|
+
def as_id(self) -> Hashable: ...
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _set_default_tracker(data: dict[str, Any]) -> ItemsRequestTracker:
|
|
154
|
+
if "tracker" not in data or data["tracker"] is None:
|
|
155
|
+
return ItemsRequestTracker(data.get("max_failures_before_abort", 50))
|
|
156
|
+
return data["tracker"]
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class ItemsRequest2(BaseRequestMessage):
|
|
160
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
161
|
+
items: list[RequestResource]
|
|
162
|
+
extra_body_fields: dict[str, JsonValue] | None = None
|
|
163
|
+
max_failures_before_abort: int = 50
|
|
164
|
+
tracker: ItemsRequestTracker = Field(init=False, default_factory=_set_default_tracker, exclude=True)
|
|
165
|
+
|
|
166
|
+
@property
|
|
167
|
+
def content(self) -> str | bytes | None:
|
|
168
|
+
body: dict[str, JsonValue] = {"items": [item.dump() for item in self.items]}
|
|
169
|
+
if self.extra_body_fields:
|
|
170
|
+
body.update(self.extra_body_fields)
|
|
171
|
+
res = _BODY_SERIALIZER.dump_json(body)
|
|
172
|
+
if not global_config.disable_gzip and isinstance(res, str):
|
|
173
|
+
return gzip.compress(res.encode("utf-8"))
|
|
174
|
+
return res
|
|
175
|
+
|
|
176
|
+
def split(self, status_attempts: int) -> list["ItemsRequest2"]:
|
|
177
|
+
"""Split the request into multiple requests with a single item each."""
|
|
178
|
+
mid = len(self.items) // 2
|
|
179
|
+
if mid == 0:
|
|
180
|
+
return [self]
|
|
181
|
+
self.tracker.register_failure()
|
|
182
|
+
messages: list[ItemsRequest2] = []
|
|
183
|
+
for part in (self.items[:mid], self.items[mid:]):
|
|
184
|
+
new_request = self.model_copy(update={"items": part, "status_attempt": status_attempts})
|
|
185
|
+
new_request.tracker = self.tracker
|
|
186
|
+
messages.append(new_request)
|
|
187
|
+
return messages
|
|
@@ -8,18 +8,29 @@ from cognite.client.utils._text import to_snake_case
|
|
|
8
8
|
from pydantic import BaseModel, TypeAdapter, ValidationError
|
|
9
9
|
from pydantic_core import ErrorDetails
|
|
10
10
|
|
|
11
|
-
from cognite_toolkit._cdf_tk.
|
|
11
|
+
from cognite_toolkit._cdf_tk.cdf_toml import CDFToml
|
|
12
|
+
from cognite_toolkit._cdf_tk.constants import DEV_ONLY_MODULES
|
|
13
|
+
from cognite_toolkit._cdf_tk.data_classes import BuildConfigYAML, BuildVariables, ModuleDirectories
|
|
14
|
+
from cognite_toolkit._cdf_tk.exceptions import (
|
|
15
|
+
ToolkitDuplicatedModuleError,
|
|
16
|
+
ToolkitEnvError,
|
|
17
|
+
ToolkitMissingModuleError,
|
|
18
|
+
)
|
|
19
|
+
from cognite_toolkit._cdf_tk.hints import ModuleDefinition
|
|
12
20
|
from cognite_toolkit._cdf_tk.resource_classes import BaseModelResource
|
|
13
21
|
from cognite_toolkit._cdf_tk.tk_warnings import (
|
|
14
22
|
DataSetMissingWarning,
|
|
23
|
+
MediumSeverityWarning,
|
|
15
24
|
TemplateVariableWarning,
|
|
16
25
|
WarningList,
|
|
17
26
|
)
|
|
18
27
|
from cognite_toolkit._cdf_tk.tk_warnings.fileread import ResourceFormatWarning
|
|
28
|
+
from cognite_toolkit._cdf_tk.utils import humanize_collection
|
|
19
29
|
|
|
20
30
|
__all__ = [
|
|
21
31
|
"humanize_validation_error",
|
|
22
32
|
"validate_data_set_is_set",
|
|
33
|
+
"validate_module_selection",
|
|
23
34
|
"validate_modules_variables",
|
|
24
35
|
]
|
|
25
36
|
|
|
@@ -210,3 +221,70 @@ def as_json_path(loc: tuple[str | int, ...]) -> str:
|
|
|
210
221
|
|
|
211
222
|
suffix = ".".join([str(x) if isinstance(x, str) else f"[{x + 1}]" for x in loc]).replace(".[", "[")
|
|
212
223
|
return f"{prefix}{suffix}"
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def validate_module_selection(
|
|
227
|
+
modules: ModuleDirectories,
|
|
228
|
+
config: BuildConfigYAML,
|
|
229
|
+
packages: dict[str, list[str]],
|
|
230
|
+
selected_modules: set[str | Path],
|
|
231
|
+
organization_dir: Path,
|
|
232
|
+
) -> WarningList:
|
|
233
|
+
"""Validates module selection and returns warnings for non-critical issues.
|
|
234
|
+
|
|
235
|
+
Critical errors (duplicate modules, missing modules, no modules selected) are still raised
|
|
236
|
+
as exceptions as they prevent the build from proceeding.
|
|
237
|
+
"""
|
|
238
|
+
warnings: WarningList = WarningList()
|
|
239
|
+
|
|
240
|
+
# Validations: Ambiguous selection.
|
|
241
|
+
selected_names = {s for s in config.environment.selected if isinstance(s, str)}
|
|
242
|
+
if duplicate_modules := {
|
|
243
|
+
module_name: paths
|
|
244
|
+
for module_name, paths in modules.as_path_by_name().items()
|
|
245
|
+
if len(paths) > 1 and module_name in selected_names
|
|
246
|
+
}:
|
|
247
|
+
# If the user has selected a module by name, and there are multiple modules with that name, raise an error.
|
|
248
|
+
# Note, if the user uses a path to select a module, this error will not be raised.
|
|
249
|
+
raise ToolkitDuplicatedModuleError(
|
|
250
|
+
f"Ambiguous module selected in config.{config.environment.name}.yaml:", duplicate_modules
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
# Package Referenced Modules Exists
|
|
254
|
+
for package, package_modules in packages.items():
|
|
255
|
+
if package not in selected_names:
|
|
256
|
+
# We do not check packages that are not selected.
|
|
257
|
+
# Typically, the user will delete the modules that are irrelevant for them;
|
|
258
|
+
# thus we only check the selected packages.
|
|
259
|
+
continue
|
|
260
|
+
if missing_packages := set(package_modules) - modules.available_names:
|
|
261
|
+
raise ToolkitMissingModuleError(
|
|
262
|
+
f"Package {package} defined in {CDFToml.file_name!s} is referring "
|
|
263
|
+
f"the following missing modules {missing_packages}."
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
# Selected modules does not exists
|
|
267
|
+
if missing_modules := set(selected_modules) - modules.available:
|
|
268
|
+
hint = ModuleDefinition.long(missing_modules, organization_dir)
|
|
269
|
+
raise ToolkitMissingModuleError(
|
|
270
|
+
f"The following selected modules are missing, please check path: {missing_modules}.\n{hint}"
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
# Nothing is Selected
|
|
274
|
+
if not modules.selected:
|
|
275
|
+
raise ToolkitEnvError(
|
|
276
|
+
f"No selected modules specified in {config.filepath!s}, have you configured "
|
|
277
|
+
f"the environment ({config.environment.name})?"
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
# Dev modules warning (non-critical)
|
|
281
|
+
dev_modules = modules.available_names & DEV_ONLY_MODULES
|
|
282
|
+
if dev_modules and config.environment.validation_type != "dev":
|
|
283
|
+
warnings.append(
|
|
284
|
+
MediumSeverityWarning(
|
|
285
|
+
"The following modules should [bold]only[/bold] be used a in CDF Projects designated as dev (development): "
|
|
286
|
+
f"{humanize_collection(dev_modules)!r}",
|
|
287
|
+
)
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
return warnings
|
cognite_toolkit/_version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.7.
|
|
1
|
+
__version__ = "0.7.31"
|
|
@@ -3,7 +3,7 @@ cognite_toolkit/_cdf.py,sha256=sefGD2JQuOTBZhEqSj_ECbNZ7nTRN4AwGwX1pSUhoow,5636
|
|
|
3
3
|
cognite_toolkit/_cdf_tk/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
4
|
cognite_toolkit/_cdf_tk/apps/__init__.py,sha256=KKmhbpvPKTwqQS2g_XqAC2yvtPsvdl8wV5TgJA3zqhs,702
|
|
5
5
|
cognite_toolkit/_cdf_tk/apps/_auth_app.py,sha256=ER7uYb3ViwsHMXiQEZpyhwU6TIjKaB9aEy32VI4MPpg,3397
|
|
6
|
-
cognite_toolkit/_cdf_tk/apps/_core_app.py,sha256=
|
|
6
|
+
cognite_toolkit/_cdf_tk/apps/_core_app.py,sha256=BiY7GWInq0INa7uCiGQyt4cBs1Eguyr5BBCW14JHo3I,14018
|
|
7
7
|
cognite_toolkit/_cdf_tk/apps/_data_app.py,sha256=LeplXlxXtyIymRPgbatQrRFodU4VZBFxI0bqDutLSbg,806
|
|
8
8
|
cognite_toolkit/_cdf_tk/apps/_dev_app.py,sha256=FaY67PFdKwdiMKgJbTcjHT1X2Xfbog2PKL6T-kcawyc,2818
|
|
9
9
|
cognite_toolkit/_cdf_tk/apps/_download_app.py,sha256=2nPn9P_9br9poynSpKKSZF7WYTYT--BfxlxXkSEeH-8,41156
|
|
@@ -93,7 +93,7 @@ cognite_toolkit/_cdf_tk/client/testing.py,sha256=mXqEXPMZcbETrXBn6D-SiAcjD7xAkuu
|
|
|
93
93
|
cognite_toolkit/_cdf_tk/client/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
94
94
|
cognite_toolkit/_cdf_tk/client/utils/_concurrency.py,sha256=3GtQbKDaosyKHEt-KzxKK9Yie4TvZPdoou2vUk6dUa8,2298
|
|
95
95
|
cognite_toolkit/_cdf_tk/client/utils/_http_client.py,sha256=oXNKrIaizG4WiSAhL_kSCHAuL4aaaEhCU4pOJGxh6Xs,483
|
|
96
|
-
cognite_toolkit/_cdf_tk/commands/__init__.py,sha256=
|
|
96
|
+
cognite_toolkit/_cdf_tk/commands/__init__.py,sha256=g-k-P7jWUVzDM0iIBlq9kXdAs_dlGji3NxYEEmTv0L8,1412
|
|
97
97
|
cognite_toolkit/_cdf_tk/commands/_base.py,sha256=1gl8Y-yqfedRMfdbwM3iPTIUIZriX1UvC1deLsJSJwM,2667
|
|
98
98
|
cognite_toolkit/_cdf_tk/commands/_changes.py,sha256=sU0KaTtPVSJgAZcaZ1Tkcajj36pmhd13kh7V8QbIED8,22987
|
|
99
99
|
cognite_toolkit/_cdf_tk/commands/_cli_commands.py,sha256=TK6U_rm6VZT_V941kTyHMoulWgJzbDC8YIIQDPJ5x3w,1011
|
|
@@ -113,12 +113,16 @@ cognite_toolkit/_cdf_tk/commands/_migrate/selectors.py,sha256=N1H_-rBpPUD6pbrlco
|
|
|
113
113
|
cognite_toolkit/_cdf_tk/commands/_profile.py,sha256=_4iX3AHAI6eLmRVUlWXCSvVHx1BZW2yDr_i2i9ECg6U,43120
|
|
114
114
|
cognite_toolkit/_cdf_tk/commands/_purge.py,sha256=HqN2T0OrHG53BfQrolYLi0kFa-_KuJjbZldFtQdMdnY,32914
|
|
115
115
|
cognite_toolkit/_cdf_tk/commands/_questionary_style.py,sha256=h-w7fZKkGls3TrzIGBKjsZSGoXJJIYchgD1StfA40r8,806
|
|
116
|
-
cognite_toolkit/_cdf_tk/commands/_upload.py,sha256=
|
|
116
|
+
cognite_toolkit/_cdf_tk/commands/_upload.py,sha256=SBqevUZjNW_-hGXSI8YzwgQ7AgJH15M0S3avm7Wmqpw,13040
|
|
117
117
|
cognite_toolkit/_cdf_tk/commands/_utils.py,sha256=UxMJW5QYKts4om5n6x2Tq2ihvfO9gWjhQKeqZNFTlKg,402
|
|
118
118
|
cognite_toolkit/_cdf_tk/commands/_virtual_env.py,sha256=GFAid4hplixmj9_HkcXqU5yCLj-fTXm4cloGD6U2swY,2180
|
|
119
119
|
cognite_toolkit/_cdf_tk/commands/about.py,sha256=pEXNdCeJYONOalH8x-7QRsKLgj-9gdIqN16pPxA3bhg,9395
|
|
120
120
|
cognite_toolkit/_cdf_tk/commands/auth.py,sha256=TX_8YuVCjMVIQjEdfBE66bSDrojJhCnxf_jcT3-9wM8,32550
|
|
121
|
-
cognite_toolkit/_cdf_tk/commands/build_cmd.py,sha256=
|
|
121
|
+
cognite_toolkit/_cdf_tk/commands/build_cmd.py,sha256=c_4S9cZZYxXs2gXZVAEFlPRLZQK_hpCzEMNN1Zoua_o,28284
|
|
122
|
+
cognite_toolkit/_cdf_tk/commands/build_v2/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
123
|
+
cognite_toolkit/_cdf_tk/commands/build_v2/build_cmd.py,sha256=U69tPVVWGxMD-FwwTf60pE2gL8MgD0J5wcrVWfmHiME,10146
|
|
124
|
+
cognite_toolkit/_cdf_tk/commands/build_v2/build_input.py,sha256=wHTtUl0OrtNadBZm-i9Ju3zjV7Y6Asth4XuLFZ48qww,3197
|
|
125
|
+
cognite_toolkit/_cdf_tk/commands/build_v2/build_issues.py,sha256=xx11FtNbVfgO4mHgyPbe4m1veYZW_1NDgs8J2PKFa58,739
|
|
122
126
|
cognite_toolkit/_cdf_tk/commands/clean.py,sha256=JNPIjNSiOJaP-cUhFzT8H3s30luA9lI39yH18QxHaYM,16603
|
|
123
127
|
cognite_toolkit/_cdf_tk/commands/collect.py,sha256=zBMKhhvjOpuASMnwP0eeHRI02tANcvFEZgv0CQO1ECc,627
|
|
124
128
|
cognite_toolkit/_cdf_tk/commands/deploy.py,sha256=DpA6q0f17qCpsrYUywavvaRjJ-qaIm78ukw8kIfCNbg,23262
|
|
@@ -155,7 +159,7 @@ cognite_toolkit/_cdf_tk/cruds/_resource_cruds/robotics.py,sha256=IeDks5yJFta8Tp4
|
|
|
155
159
|
cognite_toolkit/_cdf_tk/cruds/_resource_cruds/streams.py,sha256=1GAIn6G3ZC11ZAgRW-HKaJltMhIqSKH6_ttdEMGgFN0,3054
|
|
156
160
|
cognite_toolkit/_cdf_tk/cruds/_resource_cruds/three_d_model.py,sha256=5jIRF2JqosVHyAi2Ek6H38K2FWcNqrbPOR6MTSIEAQI,7320
|
|
157
161
|
cognite_toolkit/_cdf_tk/cruds/_resource_cruds/timeseries.py,sha256=rMkA78K9CZqUu7YBDiLgnaZgPcIsWdT-J3nB4xwCJWw,23032
|
|
158
|
-
cognite_toolkit/_cdf_tk/cruds/_resource_cruds/transformation.py,sha256=
|
|
162
|
+
cognite_toolkit/_cdf_tk/cruds/_resource_cruds/transformation.py,sha256=oDgscEy6ybc3k1ZDg8S0vky1YnO895fyKV0hcgQM3Nk,35136
|
|
159
163
|
cognite_toolkit/_cdf_tk/cruds/_resource_cruds/workflow.py,sha256=dVkf7OpZHV2H7V3zRU4BzUqDxgAudiWBJPvGeydATCg,26035
|
|
160
164
|
cognite_toolkit/_cdf_tk/cruds/_worker.py,sha256=QeZjziBCePilyI4WfTZvLfSHGT3qJGCMhugsoO1SVtU,9285
|
|
161
165
|
cognite_toolkit/_cdf_tk/data_classes/__init__.py,sha256=4zL-zR3lgQTCWfcy28LK0HEcukQOndPEFXVqfYfdKHU,1720
|
|
@@ -173,7 +177,7 @@ cognite_toolkit/_cdf_tk/data_classes/_packages.py,sha256=RPAi54GCBwMZFAA4XgIa1rG
|
|
|
173
177
|
cognite_toolkit/_cdf_tk/data_classes/_tracking_info.py,sha256=LoUX6GC9I3VXOqOpKna8wFDbCdiC_SR4qBX53vabK0Y,2014
|
|
174
178
|
cognite_toolkit/_cdf_tk/data_classes/_yaml_comments.py,sha256=zfuDu9aAsb1ExeZBAJIqVaoqIZ050tO_oh3dApzlDwY,4937
|
|
175
179
|
cognite_toolkit/_cdf_tk/exceptions.py,sha256=xG0jMwi5A20nvPvyo6sCyz_cyKycynPyIzpYiGR4gcU,6064
|
|
176
|
-
cognite_toolkit/_cdf_tk/feature_flags.py,sha256=
|
|
180
|
+
cognite_toolkit/_cdf_tk/feature_flags.py,sha256=X2-penlGM4RlFcX3UXTxXeb85eDrV0NiRpUrfvD7aQA,2218
|
|
177
181
|
cognite_toolkit/_cdf_tk/hints.py,sha256=UI1ymi2T5wCcYOpEbKbVaDnlyFReFy8TDtMVt-5E1h8,6493
|
|
178
182
|
cognite_toolkit/_cdf_tk/plugins.py,sha256=0V14rceAWLZQF8iWdyL5QmK7xB796YaDEtb9RIj5AOc,836
|
|
179
183
|
cognite_toolkit/_cdf_tk/protocols.py,sha256=Lc8XnBfmDZN6dwmSopmK7cFE9a9jZ2zdUryEeCXn27I,3052
|
|
@@ -247,7 +251,7 @@ cognite_toolkit/_cdf_tk/storageio/_datapoints.py,sha256=xE1YgoP98-mJjIeF5536KwCh
|
|
|
247
251
|
cognite_toolkit/_cdf_tk/storageio/_file_content.py,sha256=4b1Lr8ZROLZg7MjT7IiCsRhyVAl1KCWtr0fcDtyzk1o,19062
|
|
248
252
|
cognite_toolkit/_cdf_tk/storageio/_instances.py,sha256=t9fNpHnT6kCk8LDoPj3qZXmHpyDbPF5BZ6pI8ziTyFw,10810
|
|
249
253
|
cognite_toolkit/_cdf_tk/storageio/_raw.py,sha256=pgZN5MbqDwMZl9Ow1KouDJUO2Ngga8_b6hwv7H31SVQ,5161
|
|
250
|
-
cognite_toolkit/_cdf_tk/storageio/selectors/__init__.py,sha256=
|
|
254
|
+
cognite_toolkit/_cdf_tk/storageio/selectors/__init__.py,sha256=zPLiF5iCzvKF1fyAOAnTUS2YqHCDzNHaPg3hobvwDuw,3916
|
|
251
255
|
cognite_toolkit/_cdf_tk/storageio/selectors/_asset_centric.py,sha256=7Iv_ccVX6Vzt3ZLFZ0Er3hN92iEsFTm9wgF-yermOWE,1467
|
|
252
256
|
cognite_toolkit/_cdf_tk/storageio/selectors/_base.py,sha256=hjFkbmNGsK3QIW-jnJV_8YNmvVROERxzG82qIZhU7SM,3065
|
|
253
257
|
cognite_toolkit/_cdf_tk/storageio/selectors/_canvas.py,sha256=E9S-wr-JUqRosI_2cSCfR0tF8MdIFTrMxDItuWRcuO4,597
|
|
@@ -281,9 +285,9 @@ cognite_toolkit/_cdf_tk/utils/fileio/_writers.py,sha256=mc23m0kJgl57FUDvwLmS7yR3
|
|
|
281
285
|
cognite_toolkit/_cdf_tk/utils/graphql_parser.py,sha256=2i2wDjg_Uw3hJ-pHtPK8hczIuCj5atrK8HZbgWJB-Pk,11532
|
|
282
286
|
cognite_toolkit/_cdf_tk/utils/hashing.py,sha256=3NyNfljyYNTqAyAFBd6XlyWaj43jRzENxIuPdOY6nqo,2116
|
|
283
287
|
cognite_toolkit/_cdf_tk/utils/http_client/__init__.py,sha256=G8b7Bg4yIet5R4Igh3dS2SntWzE6I0iTGBeNlNsSxkQ,857
|
|
284
|
-
cognite_toolkit/_cdf_tk/utils/http_client/_client.py,sha256=
|
|
288
|
+
cognite_toolkit/_cdf_tk/utils/http_client/_client.py,sha256=WQFqzjWobjUYEcO1YV9uOMaoRrfi4APuDFrZakD86BQ,22496
|
|
285
289
|
cognite_toolkit/_cdf_tk/utils/http_client/_data_classes.py,sha256=8KEDyRRaOLhwN2eA2vaBAzZ__JDUicUDyir6x_PE5lk,14817
|
|
286
|
-
cognite_toolkit/_cdf_tk/utils/http_client/_data_classes2.py,sha256=
|
|
290
|
+
cognite_toolkit/_cdf_tk/utils/http_client/_data_classes2.py,sha256=mg0BfP7Te5TSgljPFWI0atLtIGa9OPb50jEAXpkzVXE,6078
|
|
287
291
|
cognite_toolkit/_cdf_tk/utils/http_client/_exception.py,sha256=fC9oW6BN0HbUe2AkYABMP7Kj0-9dNYXVFBY5RQztq2c,126
|
|
288
292
|
cognite_toolkit/_cdf_tk/utils/http_client/_tracker.py,sha256=EBBnd-JZ7nc_jYNFJokCHN2UZ9sx0McFLZvlceUYYic,1215
|
|
289
293
|
cognite_toolkit/_cdf_tk/utils/interactive_select.py,sha256=dP_ZFHvzQRPQxRt6EzURY3Z3Ld_otJtCz-nGqUNtt1k,35725
|
|
@@ -297,20 +301,20 @@ cognite_toolkit/_cdf_tk/utils/text.py,sha256=1-LQMo633_hEhNhishQo7Buj-7np5Pe4qKk
|
|
|
297
301
|
cognite_toolkit/_cdf_tk/utils/thread_safe_dict.py,sha256=NbRHcZvWpF9xHP5OkOMGFpxrPNbi0Q3Eea6PUNbGlt4,3426
|
|
298
302
|
cognite_toolkit/_cdf_tk/utils/useful_types.py,sha256=oK88W6G_aK3hebORSQKZjWrq7jG-pO2lkLWSWYMlngM,1872
|
|
299
303
|
cognite_toolkit/_cdf_tk/utils/validate_access.py,sha256=1puswcpgEDNCwdk91dhLqCBSu_aaUAd3Hsw21d-YVFs,21955
|
|
300
|
-
cognite_toolkit/_cdf_tk/validation.py,sha256=
|
|
304
|
+
cognite_toolkit/_cdf_tk/validation.py,sha256=ixxYJA96EYZwFeo3vHjGfuHsFbbPgAJ3RQ-Sh0-PMok,11790
|
|
301
305
|
cognite_toolkit/_repo_files/.env.tmpl,sha256=UmgKZVvIp-OzD8oOcYuwb_6c7vSJsqkLhuFaiVgK7RI,972
|
|
302
306
|
cognite_toolkit/_repo_files/.gitignore,sha256=ip9kf9tcC5OguF4YF4JFEApnKYw0nG0vPi6urlpTZ3k,5274
|
|
303
307
|
cognite_toolkit/_repo_files/AzureDevOps/.devops/README.md,sha256=OLA0D7yCX2tACpzvkA0IfkgQ4_swSd-OlJ1tYcTBpsA,240
|
|
304
308
|
cognite_toolkit/_repo_files/AzureDevOps/.devops/deploy-pipeline.yml,sha256=brULcs8joAeBC_w_aoWjDDUHs3JheLMIR9ajPUK96nc,693
|
|
305
309
|
cognite_toolkit/_repo_files/AzureDevOps/.devops/dry-run-pipeline.yml,sha256=OBFDhFWK1mlT4Dc6mDUE2Es834l8sAlYG50-5RxRtHk,723
|
|
306
|
-
cognite_toolkit/_repo_files/GitHub/.github/workflows/deploy.yaml,sha256=
|
|
307
|
-
cognite_toolkit/_repo_files/GitHub/.github/workflows/dry-run.yaml,sha256=
|
|
308
|
-
cognite_toolkit/_resources/cdf.toml,sha256=
|
|
309
|
-
cognite_toolkit/_version.py,sha256=
|
|
310
|
+
cognite_toolkit/_repo_files/GitHub/.github/workflows/deploy.yaml,sha256=Ft9lViXmay2AajHAwOiFbP1BYBIlVHvxu56qFQlN9Zk,667
|
|
311
|
+
cognite_toolkit/_repo_files/GitHub/.github/workflows/dry-run.yaml,sha256=_gR9FBSVup2h_P2iTlKRvnxV5kdRMZFvHLo-fK6o_Zc,2430
|
|
312
|
+
cognite_toolkit/_resources/cdf.toml,sha256=3YM8T2ivUqrfocBzLTYRWCWyeYXyXfM7wRSY1Zxa7k8,475
|
|
313
|
+
cognite_toolkit/_version.py,sha256=CtvyqFRtdJwNvYSDVmaT9l1R18bsaIwflR8r696jttk,23
|
|
310
314
|
cognite_toolkit/config.dev.yaml,sha256=M33FiIKdS3XKif-9vXniQ444GTZ-bLXV8aFH86u9iUQ,332
|
|
311
315
|
cognite_toolkit/demo/__init__.py,sha256=-m1JoUiwRhNCL18eJ6t7fZOL7RPfowhCuqhYFtLgrss,72
|
|
312
316
|
cognite_toolkit/demo/_base.py,sha256=6xKBUQpXZXGQ3fJ5f7nj7oT0s2n7OTAGIa17ZlKHZ5U,8052
|
|
313
|
-
cognite_toolkit-0.7.
|
|
314
|
-
cognite_toolkit-0.7.
|
|
315
|
-
cognite_toolkit-0.7.
|
|
316
|
-
cognite_toolkit-0.7.
|
|
317
|
+
cognite_toolkit-0.7.31.dist-info/WHEEL,sha256=93kfTGt3a0Dykt_T-gsjtyS5_p8F_d6CE1NwmBOirzo,79
|
|
318
|
+
cognite_toolkit-0.7.31.dist-info/entry_points.txt,sha256=EtZ17K2mUjh-AY0QNU1CPIB_aDSSOdmtNI_4Fj967mA,84
|
|
319
|
+
cognite_toolkit-0.7.31.dist-info/METADATA,sha256=lOFzBCcUEoVSZjcxZh685Mb3FTEhTlrZTN6XXlrlJ4A,4507
|
|
320
|
+
cognite_toolkit-0.7.31.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|