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.
@@ -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 = BuildCommand(print_warning=print_warning)
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,
@@ -24,6 +24,7 @@ __all__ = [
24
24
  "AboutCommand",
25
25
  "AuthCommand",
26
26
  "BuildCommand",
27
+ "BuildCommandV2",
27
28
  "CleanCommand",
28
29
  "CollectCommand",
29
30
  "DeployCommand",
@@ -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, SelectorAdapter
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
- selector_dict = read_yaml_file(manifest_file, expected_output="dict")
145
- try:
146
- selector = SelectorAdapter.validate_python(selector_dict)
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
- self._validate_modules(modules, config, packages, user_selected_modules, organization_dir)
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 for" in e.message and len(chunk) > 1:
431
- MediumSeverityWarning(
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
- else:
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.data_classes import BuildVariables
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
@@ -12,7 +12,7 @@ jobs:
12
12
  environment: dev
13
13
  name: Deploy
14
14
  container:
15
- image: cognite/toolkit:0.7.29
15
+ image: cognite/toolkit:0.7.31
16
16
  env:
17
17
  CDF_CLUSTER: ${{ vars.CDF_CLUSTER }}
18
18
  CDF_PROJECT: ${{ vars.CDF_PROJECT }}
@@ -10,7 +10,7 @@ jobs:
10
10
  environment: dev
11
11
  name: Deploy Dry Run
12
12
  container:
13
- image: cognite/toolkit:0.7.29
13
+ image: cognite/toolkit:0.7.31
14
14
  env:
15
15
  CDF_CLUSTER: ${{ vars.CDF_CLUSTER }}
16
16
  CDF_PROJECT: ${{ vars.CDF_PROJECT }}
@@ -4,7 +4,7 @@ default_env = "<DEFAULT_ENV_PLACEHOLDER>"
4
4
  [modules]
5
5
  # This is the version of the modules. It should not be changed manually.
6
6
  # It will be updated by the 'cdf modules upgrade' command.
7
- version = "0.7.29"
7
+ version = "0.7.31"
8
8
 
9
9
 
10
10
  [plugins]
@@ -1 +1 @@
1
- __version__ = "0.7.29"
1
+ __version__ = "0.7.31"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cognite_toolkit
3
- Version: 0.7.29
3
+ Version: 0.7.31
4
4
  Summary: Official Cognite Data Fusion tool for project templates and configuration deployment
5
5
  Author: Cognite AS
6
6
  Author-email: Cognite AS <support@cognite.com>
@@ -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=YK0MOK7Tv3cDSe5_6o9GtM5n_6sE7I0Wm-Se4eJnyNM,13744
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=wXRbOwMyhGjMj83_bXTOUXtCh70YS4-eiYzzCMN_YOs,1390
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=jFt4NG2zEcUu8WUCgRQEoYDIs5C70VTVvTKkZ0F5j1A,13534
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=6m-lK0vccje1gaQ_fd68UvA4CbhuBszDapnHwu4VU_U,30897
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=ndhoGIC6tBjNmwI0wDfg8v_6dJfCnHUnTienYOtKwC8,33876
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=ESxrtl2hbe0Fr6e1535MAjSIM326Zc6W7d2PtfjAWkk,2071
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=VUK1A76zsu4a25A3oaPUrQEEuRcCpUBK6o8UMMKw7qg,2458
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=OrrGq3GjusxPPzhFoW8iyiphpdbWOWAoaYOeOy9kqjQ,16212
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=mUrmTqTm60WP4JpMmNO07-NfgWQM8Wby7OLCo02I71U,3031
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=KFdPgnNIbVM0yjFF0cqmpBB8MI8e-U-YbBYrP4IiClE,8441
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=FQFw5rkuhjbBY-cBoP38pdrcUZF6qkK2Y9YKbab8LzU,667
307
- cognite_toolkit/_repo_files/GitHub/.github/workflows/dry-run.yaml,sha256=e87zJqrsOVfo7QqSnIZY5U32szqyvq5Bjq-e_16xHiQ,2430
308
- cognite_toolkit/_resources/cdf.toml,sha256=tJP7KRNhWsrkXisd58pe5ZlzUBOAfe4zR5TTAP63tzg,475
309
- cognite_toolkit/_version.py,sha256=XdC0R1utqmwH52az-uz3mk0vLZyV2OyWiin3soRIguo,23
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.29.dist-info/WHEEL,sha256=93kfTGt3a0Dykt_T-gsjtyS5_p8F_d6CE1NwmBOirzo,79
314
- cognite_toolkit-0.7.29.dist-info/entry_points.txt,sha256=EtZ17K2mUjh-AY0QNU1CPIB_aDSSOdmtNI_4Fj967mA,84
315
- cognite_toolkit-0.7.29.dist-info/METADATA,sha256=71UXU4S9UVcZVxhDGazAQOCbTSYyQzD6Kj2FkxNJHYs,4507
316
- cognite_toolkit-0.7.29.dist-info/RECORD,,
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,,