cognite-toolkit 0.6.97__py3-none-any.whl → 0.7.30__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. cognite_toolkit/_cdf.py +16 -17
  2. cognite_toolkit/_cdf_tk/apps/__init__.py +2 -0
  3. cognite_toolkit/_cdf_tk/apps/_core_app.py +13 -5
  4. cognite_toolkit/_cdf_tk/apps/_data_app.py +1 -1
  5. cognite_toolkit/_cdf_tk/apps/_dev_app.py +86 -0
  6. cognite_toolkit/_cdf_tk/apps/_download_app.py +692 -24
  7. cognite_toolkit/_cdf_tk/apps/_dump_app.py +43 -101
  8. cognite_toolkit/_cdf_tk/apps/_landing_app.py +18 -4
  9. cognite_toolkit/_cdf_tk/apps/_migrate_app.py +249 -9
  10. cognite_toolkit/_cdf_tk/apps/_modules_app.py +0 -3
  11. cognite_toolkit/_cdf_tk/apps/_purge.py +15 -43
  12. cognite_toolkit/_cdf_tk/apps/_run.py +11 -0
  13. cognite_toolkit/_cdf_tk/apps/_upload_app.py +45 -6
  14. cognite_toolkit/_cdf_tk/builders/__init__.py +2 -2
  15. cognite_toolkit/_cdf_tk/builders/_base.py +28 -42
  16. cognite_toolkit/_cdf_tk/cdf_toml.py +20 -1
  17. cognite_toolkit/_cdf_tk/client/_toolkit_client.py +23 -3
  18. cognite_toolkit/_cdf_tk/client/api/extended_functions.py +6 -9
  19. cognite_toolkit/_cdf_tk/client/api/infield.py +93 -1
  20. cognite_toolkit/_cdf_tk/client/api/migration.py +175 -1
  21. cognite_toolkit/_cdf_tk/client/api/streams.py +84 -0
  22. cognite_toolkit/_cdf_tk/client/api/three_d.py +50 -0
  23. cognite_toolkit/_cdf_tk/client/data_classes/base.py +25 -1
  24. cognite_toolkit/_cdf_tk/client/data_classes/canvas.py +46 -3
  25. cognite_toolkit/_cdf_tk/client/data_classes/charts.py +3 -3
  26. cognite_toolkit/_cdf_tk/client/data_classes/charts_data.py +95 -213
  27. cognite_toolkit/_cdf_tk/client/data_classes/infield.py +32 -18
  28. cognite_toolkit/_cdf_tk/client/data_classes/migration.py +10 -2
  29. cognite_toolkit/_cdf_tk/client/data_classes/streams.py +90 -0
  30. cognite_toolkit/_cdf_tk/client/data_classes/three_d.py +47 -0
  31. cognite_toolkit/_cdf_tk/client/testing.py +18 -2
  32. cognite_toolkit/_cdf_tk/commands/__init__.py +6 -6
  33. cognite_toolkit/_cdf_tk/commands/_changes.py +3 -42
  34. cognite_toolkit/_cdf_tk/commands/_download.py +21 -11
  35. cognite_toolkit/_cdf_tk/commands/_migrate/__init__.py +0 -2
  36. cognite_toolkit/_cdf_tk/commands/_migrate/command.py +22 -20
  37. cognite_toolkit/_cdf_tk/commands/_migrate/conversion.py +133 -91
  38. cognite_toolkit/_cdf_tk/commands/_migrate/data_classes.py +73 -22
  39. cognite_toolkit/_cdf_tk/commands/_migrate/data_mapper.py +311 -43
  40. cognite_toolkit/_cdf_tk/commands/_migrate/default_mappings.py +5 -5
  41. cognite_toolkit/_cdf_tk/commands/_migrate/issues.py +33 -0
  42. cognite_toolkit/_cdf_tk/commands/_migrate/migration_io.py +157 -8
  43. cognite_toolkit/_cdf_tk/commands/_migrate/selectors.py +9 -4
  44. cognite_toolkit/_cdf_tk/commands/_purge.py +27 -28
  45. cognite_toolkit/_cdf_tk/commands/_questionary_style.py +16 -0
  46. cognite_toolkit/_cdf_tk/commands/_upload.py +109 -86
  47. cognite_toolkit/_cdf_tk/commands/about.py +221 -0
  48. cognite_toolkit/_cdf_tk/commands/auth.py +19 -12
  49. cognite_toolkit/_cdf_tk/commands/build_cmd.py +15 -61
  50. cognite_toolkit/_cdf_tk/commands/clean.py +63 -16
  51. cognite_toolkit/_cdf_tk/commands/deploy.py +20 -17
  52. cognite_toolkit/_cdf_tk/commands/dump_resource.py +6 -4
  53. cognite_toolkit/_cdf_tk/commands/init.py +225 -3
  54. cognite_toolkit/_cdf_tk/commands/modules.py +20 -44
  55. cognite_toolkit/_cdf_tk/commands/pull.py +6 -19
  56. cognite_toolkit/_cdf_tk/commands/resources.py +179 -0
  57. cognite_toolkit/_cdf_tk/constants.py +20 -1
  58. cognite_toolkit/_cdf_tk/cruds/__init__.py +19 -5
  59. cognite_toolkit/_cdf_tk/cruds/_base_cruds.py +14 -70
  60. cognite_toolkit/_cdf_tk/cruds/_data_cruds.py +8 -17
  61. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/__init__.py +4 -1
  62. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/agent.py +11 -9
  63. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/auth.py +4 -14
  64. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/classic.py +44 -43
  65. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/configuration.py +4 -11
  66. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/data_organization.py +4 -13
  67. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/datamodel.py +205 -66
  68. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/extraction_pipeline.py +5 -17
  69. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/fieldops.py +116 -27
  70. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/file.py +6 -27
  71. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/function.py +9 -28
  72. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/hosted_extractors.py +12 -30
  73. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/industrial_tool.py +3 -7
  74. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/location.py +3 -15
  75. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/migration.py +4 -12
  76. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/raw.py +4 -10
  77. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/relationship.py +3 -8
  78. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/robotics.py +15 -44
  79. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/streams.py +94 -0
  80. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/three_d_model.py +3 -7
  81. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/timeseries.py +5 -15
  82. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/transformation.py +39 -31
  83. cognite_toolkit/_cdf_tk/cruds/_resource_cruds/workflow.py +20 -40
  84. cognite_toolkit/_cdf_tk/cruds/_worker.py +24 -36
  85. cognite_toolkit/_cdf_tk/feature_flags.py +16 -36
  86. cognite_toolkit/_cdf_tk/plugins.py +2 -1
  87. cognite_toolkit/_cdf_tk/resource_classes/__init__.py +4 -0
  88. cognite_toolkit/_cdf_tk/resource_classes/capabilities.py +12 -0
  89. cognite_toolkit/_cdf_tk/resource_classes/functions.py +3 -1
  90. cognite_toolkit/_cdf_tk/resource_classes/infield_cdm_location_config.py +109 -0
  91. cognite_toolkit/_cdf_tk/resource_classes/migration.py +8 -17
  92. cognite_toolkit/_cdf_tk/resource_classes/streams.py +29 -0
  93. cognite_toolkit/_cdf_tk/storageio/__init__.py +9 -21
  94. cognite_toolkit/_cdf_tk/storageio/_annotations.py +19 -16
  95. cognite_toolkit/_cdf_tk/storageio/_applications.py +338 -26
  96. cognite_toolkit/_cdf_tk/storageio/_asset_centric.py +67 -104
  97. cognite_toolkit/_cdf_tk/storageio/_base.py +61 -29
  98. cognite_toolkit/_cdf_tk/storageio/_datapoints.py +276 -20
  99. cognite_toolkit/_cdf_tk/storageio/_file_content.py +436 -0
  100. cognite_toolkit/_cdf_tk/storageio/_instances.py +34 -2
  101. cognite_toolkit/_cdf_tk/storageio/_raw.py +26 -0
  102. cognite_toolkit/_cdf_tk/storageio/selectors/__init__.py +62 -4
  103. cognite_toolkit/_cdf_tk/storageio/selectors/_base.py +14 -2
  104. cognite_toolkit/_cdf_tk/storageio/selectors/_canvas.py +14 -0
  105. cognite_toolkit/_cdf_tk/storageio/selectors/_charts.py +14 -0
  106. cognite_toolkit/_cdf_tk/storageio/selectors/_datapoints.py +23 -3
  107. cognite_toolkit/_cdf_tk/storageio/selectors/_file_content.py +164 -0
  108. cognite_toolkit/_cdf_tk/tk_warnings/other.py +4 -0
  109. cognite_toolkit/_cdf_tk/tracker.py +2 -2
  110. cognite_toolkit/_cdf_tk/utils/dtype_conversion.py +9 -3
  111. cognite_toolkit/_cdf_tk/utils/fileio/__init__.py +2 -0
  112. cognite_toolkit/_cdf_tk/utils/fileio/_base.py +5 -1
  113. cognite_toolkit/_cdf_tk/utils/fileio/_readers.py +112 -20
  114. cognite_toolkit/_cdf_tk/utils/fileio/_writers.py +15 -15
  115. cognite_toolkit/_cdf_tk/utils/http_client/_client.py +284 -18
  116. cognite_toolkit/_cdf_tk/utils/http_client/_data_classes.py +50 -4
  117. cognite_toolkit/_cdf_tk/utils/http_client/_data_classes2.py +187 -0
  118. cognite_toolkit/_cdf_tk/utils/interactive_select.py +9 -14
  119. cognite_toolkit/_cdf_tk/utils/sql_parser.py +2 -3
  120. cognite_toolkit/_cdf_tk/utils/useful_types.py +6 -2
  121. cognite_toolkit/_cdf_tk/validation.py +79 -1
  122. cognite_toolkit/_repo_files/GitHub/.github/workflows/deploy.yaml +1 -1
  123. cognite_toolkit/_repo_files/GitHub/.github/workflows/dry-run.yaml +1 -1
  124. cognite_toolkit/_resources/cdf.toml +5 -4
  125. cognite_toolkit/_version.py +1 -1
  126. cognite_toolkit/config.dev.yaml +13 -0
  127. {cognite_toolkit-0.6.97.dist-info → cognite_toolkit-0.7.30.dist-info}/METADATA +24 -24
  128. {cognite_toolkit-0.6.97.dist-info → cognite_toolkit-0.7.30.dist-info}/RECORD +153 -143
  129. cognite_toolkit-0.7.30.dist-info/WHEEL +4 -0
  130. {cognite_toolkit-0.6.97.dist-info → cognite_toolkit-0.7.30.dist-info}/entry_points.txt +1 -0
  131. cognite_toolkit/_cdf_tk/commands/_migrate/canvas.py +0 -201
  132. cognite_toolkit/_cdf_tk/commands/dump_data.py +0 -489
  133. cognite_toolkit/_cdf_tk/commands/featureflag.py +0 -27
  134. cognite_toolkit/_cdf_tk/utils/table_writers.py +0 -434
  135. cognite_toolkit-0.6.97.dist-info/WHEEL +0 -4
  136. cognite_toolkit-0.6.97.dist-info/licenses/LICENSE +0 -18
@@ -3,7 +3,7 @@ import json
3
3
  import zipfile
4
4
  from abc import ABC, abstractmethod
5
5
  from collections import defaultdict
6
- from collections.abc import Hashable, Iterable, Iterator
6
+ from collections.abc import Hashable, Iterable, Iterator, Sequence
7
7
  from functools import cached_property
8
8
  from pathlib import Path
9
9
  from typing import Generic, cast
@@ -22,6 +22,7 @@ from cognite.client.data_classes import (
22
22
  filters,
23
23
  )
24
24
  from cognite.client.data_classes._base import (
25
+ CogniteResource,
25
26
  CogniteResourceList,
26
27
  )
27
28
  from cognite.client.data_classes.agents import (
@@ -113,7 +114,7 @@ class ResourceFinder(Iterable, ABC, Generic[T_ID]):
113
114
  raise NotImplementedError
114
115
 
115
116
  # Can be implemented in subclasses
116
- def update(self, resources: CogniteResourceList) -> None: ...
117
+ def update(self, resources: Sequence[CogniteResource]) -> None: ...
117
118
 
118
119
 
119
120
  class DataModelFinder(ResourceFinder[DataModelId]):
@@ -178,7 +179,7 @@ class DataModelFinder(ResourceFinder[DataModelId]):
178
179
  self.data_model = models_by_version[selected_model]
179
180
  return self.data_model.as_id()
180
181
 
181
- def update(self, resources: CogniteResourceList) -> None:
182
+ def update(self, resources: Sequence[CogniteResource]) -> None:
182
183
  if isinstance(resources, dm.DataModelList):
183
184
  self.view_ids |= {
184
185
  view.as_id() if isinstance(view, dm.View) else view for item in resources for view in item.views
@@ -187,7 +188,7 @@ class DataModelFinder(ResourceFinder[DataModelId]):
187
188
  self.container_ids |= resources.referenced_containers()
188
189
  elif isinstance(resources, dm.SpaceList):
189
190
  return
190
- self.space_ids |= {item.space for item in resources}
191
+ self.space_ids |= {item.space for item in resources if hasattr(item, "space")}
191
192
 
192
193
  def __iter__(self) -> Iterator[tuple[list[Hashable], CogniteResourceList | None, ResourceCRUD, None | str]]:
193
194
  self.identifier = self._selected()
@@ -804,6 +805,7 @@ class DumpResourceCommand(ToolkitCommand):
804
805
  output_dir.mkdir(exist_ok=True)
805
806
 
806
807
  dumped_ids: list[Hashable] = []
808
+ resources: Sequence[CogniteResource] | None = None
807
809
  for identifiers, resources, loader, subfolder in finder:
808
810
  if not identifiers and not resources:
809
811
  # No resources to dump
@@ -1,9 +1,231 @@
1
+ import shutil
2
+ import tempfile
3
+ import traceback
4
+ from collections.abc import Callable
5
+ from dataclasses import dataclass
6
+ from enum import Enum
7
+ from pathlib import Path
8
+
9
+ import questionary
10
+ import typer
11
+ from questionary import Choice
1
12
  from rich import print
2
13
  from rich.panel import Panel
3
14
 
4
- from ._base import ToolkitCommand
15
+ from cognite_toolkit._cdf_tk.cdf_toml import CDFToml
16
+ from cognite_toolkit._cdf_tk.commands._base import ToolkitCommand
17
+ from cognite_toolkit._cdf_tk.commands.auth import AuthCommand
18
+ from cognite_toolkit._cdf_tk.commands.collect import CollectCommand
19
+ from cognite_toolkit._cdf_tk.commands.modules import ModulesCommand
20
+ from cognite_toolkit._cdf_tk.commands.repo import RepoCommand
21
+ from cognite_toolkit._cdf_tk.exceptions import ToolkitError
22
+ from cognite_toolkit._cdf_tk.feature_flags import FeatureFlag
23
+
24
+
25
+ class InitItemStatus(Enum):
26
+ """Status of an init checklist item"""
27
+
28
+ NONE = "none"
29
+ SUCCESSFUL = "successful"
30
+ FAILED = "failed"
31
+
32
+
33
+ @dataclass
34
+ class InitChecklistItem:
35
+ name: str
36
+ description: str
37
+ function: Callable[[], None]
38
+ status: InitItemStatus | None = None
39
+ mandatory: bool = False
40
+
41
+ def get_status_display(self) -> str:
42
+ if self.status == InitItemStatus.SUCCESSFUL:
43
+ return "✓"
44
+ elif self.status == InitItemStatus.FAILED:
45
+ return "✗"
46
+ else:
47
+ return "○"
48
+
49
+ def get_choice_title(self) -> str:
50
+ status_icon = self.get_status_display()
51
+ return f"{status_icon} {self.description} (required)" if self.mandatory else f"{status_icon} {self.description}"
5
52
 
6
53
 
7
54
  class InitCommand(ToolkitCommand):
8
- def execute(self) -> None:
9
- print(Panel("This command is deprecated. Use 'cdf modules init' instead."))
55
+ organization_dir: Path | None
56
+
57
+ def __init__(self, print_warning: bool = True, skip_tracking: bool = False, silent: bool = False) -> None:
58
+ super().__init__(print_warning, skip_tracking, silent)
59
+ self.organization_dir = None
60
+
61
+ def execute(self, dry_run: bool = False) -> None:
62
+ print("\n")
63
+ print(
64
+ Panel(
65
+ "Go through the following steps to set up the Cognite Toolkit. You can re-run steps if you need to change something.",
66
+ title="Cognite Toolkit Setup",
67
+ style="green",
68
+ padding=(1, 2),
69
+ )
70
+ )
71
+
72
+ # Initialize checklist items
73
+ checklist_items = [
74
+ InitChecklistItem(
75
+ name="initToml",
76
+ description="Create toml file",
77
+ function=lambda: self._init_toml(dry_run=dry_run),
78
+ mandatory=True,
79
+ ),
80
+ InitChecklistItem(
81
+ name="initAuth",
82
+ description="Authentication",
83
+ function=lambda: self._init_auth(dry_run=dry_run),
84
+ ),
85
+ InitChecklistItem(
86
+ name="initModules",
87
+ description="Modules",
88
+ function=lambda: self._init_modules(dry_run=dry_run),
89
+ ),
90
+ InitChecklistItem(
91
+ name="initRepo",
92
+ description="Git repository",
93
+ function=lambda: self._init_repo(dry_run=dry_run),
94
+ ),
95
+ InitChecklistItem(
96
+ name="initDataCollection",
97
+ description="Usage statistics",
98
+ function=lambda: self._init_data_collection(dry_run=dry_run),
99
+ ),
100
+ ]
101
+
102
+ if CDFToml.load().is_loaded_from_file:
103
+ checklist_items[0].status = InitItemStatus.SUCCESSFUL
104
+ print("cdf.toml configuration file already exists. Skipping creation.")
105
+
106
+ # Main loop: keep showing checklist until user is done
107
+ while True:
108
+ # Build choices for questionary
109
+ choices = []
110
+ for item in checklist_items:
111
+ choices.append(
112
+ Choice(
113
+ title=item.get_choice_title(),
114
+ value=item.name,
115
+ )
116
+ )
117
+ # Check if all mandatory items are completed
118
+ mandatory_items = [item for item in checklist_items if item.mandatory]
119
+ all_mandatory_complete = all(item.status == InitItemStatus.SUCCESSFUL for item in mandatory_items)
120
+
121
+ choices.append(Choice(title="> Quit", value="__exit__"))
122
+
123
+ # Find the first item with status None to use as default
124
+ default_item = next((item for item in checklist_items if item.status is None), None)
125
+ default_value = default_item.name if default_item else "__exit__"
126
+
127
+ # Show checklist and get user selection
128
+ selected = questionary.select(
129
+ "Select a task:",
130
+ choices=choices,
131
+ default=default_value,
132
+ ).ask()
133
+
134
+ # User cancelled (Ctrl+C or similar)
135
+ if selected is None:
136
+ return
137
+
138
+ if selected == "__exit__":
139
+ if all_mandatory_complete:
140
+ print("Setup complete!")
141
+ break
142
+ else:
143
+ incomplete_mandatory = [
144
+ item.description for item in mandatory_items if item.status != InitItemStatus.SUCCESSFUL
145
+ ]
146
+ print(f"Warning: recommended item not completed: {', '.join(incomplete_mandatory)}")
147
+ break
148
+
149
+ # Find the selected item
150
+ selected_item = next((item for item in checklist_items if item.name == selected), None)
151
+ if selected_item is None:
152
+ continue
153
+
154
+ # If item was already run, ask for confirmation to re-run
155
+ if selected_item.status is not None:
156
+ status_text = (
157
+ "successfully. Re-run it?"
158
+ if selected_item.status == InitItemStatus.SUCCESSFUL
159
+ else "with failure. Retry?"
160
+ )
161
+ confirm = questionary.confirm(
162
+ f"'{selected_item.description}' was already run {status_text}",
163
+ default=False,
164
+ ).ask()
165
+ if not confirm:
166
+ continue
167
+
168
+ try:
169
+ selected_item.function()
170
+ selected_item.status = InitItemStatus.SUCCESSFUL
171
+ print(f"✓ {selected_item.description} completed successfully")
172
+ except typer.Exit:
173
+ # typer.Exit is used for normal exits, treat as success
174
+ selected_item.status = InitItemStatus.SUCCESSFUL
175
+ print(f"✓ {selected_item.description} completed successfully")
176
+ except ToolkitError as e:
177
+ # Catch expected toolkit errors
178
+ selected_item.status = InitItemStatus.FAILED
179
+ print(f"✗ {selected_item.description} failed: {e}")
180
+ except Exception as e:
181
+ # Catch unexpected errors and log full traceback for debugging
182
+ selected_item.status = InitItemStatus.FAILED
183
+ print(f"✗ {selected_item.description} failed: {e}")
184
+ print(f"Unexpected error occurred. Full traceback:\n{traceback.format_exc()}")
185
+
186
+ def _init_toml(self, dry_run: bool = False) -> None:
187
+ if self.organization_dir is None:
188
+ self.organization_dir = ModulesCommand._prompt_organization_dir()
189
+ if dry_run:
190
+ print("Would initialize cdf.toml configuration file")
191
+ return
192
+ CDFToml.write(self.organization_dir, "dev")
193
+ FeatureFlag.flush()
194
+ print(f"cdf.toml configuration file initialized in {self.organization_dir}")
195
+
196
+ def _init_auth(self, dry_run: bool = False) -> None:
197
+ auth_command = AuthCommand()
198
+ auth_command.run(lambda: auth_command.init(no_verify=True, dry_run=dry_run))
199
+
200
+ def _init_modules(self, dry_run: bool = False) -> None:
201
+ with ModulesCommand() as modules_command:
202
+ if self.organization_dir is None:
203
+ self.organization_dir = ModulesCommand._prompt_organization_dir()
204
+ if dry_run:
205
+ organization_dir = Path(tempfile.mkdtemp(prefix="init_modules_", suffix=".tmp", dir=Path.cwd()))
206
+ modules_command.run(lambda: modules_command.init(organization_dir=organization_dir))
207
+ shutil.rmtree(organization_dir)
208
+ else:
209
+ modules_command.run(lambda: modules_command.init(organization_dir=self.organization_dir))
210
+
211
+ def _init_repo(self, dry_run: bool = False) -> None:
212
+ repo_command = RepoCommand()
213
+ repo_command.run(lambda: repo_command.init(cwd=Path.cwd(), host=None, verbose=False))
214
+
215
+ def _init_data_collection(self, dry_run: bool = False) -> None:
216
+ """Opt in to collect usage statistics"""
217
+
218
+ opt_in = questionary.confirm(
219
+ "Do you want to opt in to collect usage statistics? This will help us improve the Toolkit.",
220
+ default=True,
221
+ ).ask()
222
+ if dry_run:
223
+ print("Would opt in to collect data" if opt_in else "Would not opt in to collect data")
224
+ return
225
+
226
+ if opt_in:
227
+ collect_command = CollectCommand()
228
+ collect_command.run(lambda: collect_command.execute("opt-in"))
229
+ else:
230
+ collect_command = CollectCommand()
231
+ collect_command.run(lambda: collect_command.execute("opt-out"))
@@ -34,6 +34,7 @@ from cognite_toolkit._cdf_tk.commands._changes import (
34
34
  UpdateDockerImageVersion,
35
35
  UpdateModuleVersion,
36
36
  )
37
+ from cognite_toolkit._cdf_tk.commands._questionary_style import custom_style_fancy
37
38
  from cognite_toolkit._cdf_tk.constants import (
38
39
  MODULES,
39
40
  RESOURCES_PATH,
@@ -50,7 +51,6 @@ from cognite_toolkit._cdf_tk.data_classes import (
50
51
  Packages,
51
52
  )
52
53
  from cognite_toolkit._cdf_tk.exceptions import ToolkitError, ToolkitRequiredValueError, ToolkitValueError
53
- from cognite_toolkit._cdf_tk.feature_flags import Flags
54
54
  from cognite_toolkit._cdf_tk.hints import verify_module_directory
55
55
  from cognite_toolkit._cdf_tk.tk_warnings import MediumSeverityWarning
56
56
  from cognite_toolkit._cdf_tk.tk_warnings.other import HighSeverityWarning
@@ -64,20 +64,6 @@ if sys.version_info >= (3, 11):
64
64
  from typing import Self
65
65
  else:
66
66
  from typing_extensions import Self
67
- custom_style_fancy = questionary.Style(
68
- [
69
- ("qmark", "fg:#673ab7"), # token in front of the question
70
- ("question", "bold"), # question text
71
- ("answer", "fg:#f44336 bold"), # submitted answer text behind the question
72
- ("pointer", "fg:#673ab7 bold"), # pointer used in select and checkbox prompts
73
- ("highlighted", "fg:#673ab7 bold"), # pointed-at choice in select and checkbox prompts
74
- ("selected", "fg:#673ab7"), # style for a selected item of a checkbox
75
- ("separator", "fg:#cc5454"), # separator in lists
76
- ("instruction", ""), # user instructions for select, rawselect, checkbox
77
- ("text", ""), # plain text
78
- ("disabled", "fg:#858585 italic"), # disabled choices for select and checkbox prompts
79
- ]
80
- )
81
67
 
82
68
  INDENT = " "
83
69
  POINTER = INDENT + "▶"
@@ -285,26 +271,7 @@ class ModulesCommand(ToolkitCommand):
285
271
  )
286
272
  safe_write(Path(organization_dir) / f"config.{environment}.yaml", config_init.dump_yaml_with_comments())
287
273
 
288
- cdf_toml_content = self.create_cdf_toml(organization_dir, environments[0] if environments else "dev")
289
-
290
- destination = Path.cwd() / CDFToml.file_name
291
- if destination.exists():
292
- print(f"{INDENT}[yellow]cdf.toml file already exists. Skipping creation.")
293
- else:
294
- destination.write_text(cdf_toml_content, encoding="utf-8")
295
-
296
- def create_cdf_toml(self, organization_dir: Path, env: EnvType = "dev") -> str:
297
- cdf_toml_content = safe_read(RESOURCES_PATH / CDFToml.file_name)
298
- if organization_dir != Path.cwd():
299
- cdf_toml_content = cdf_toml_content.replace(
300
- "#<PLACEHOLDER>",
301
- f'''
302
- default_organization_dir = "{organization_dir.name}"''',
303
- )
304
- else:
305
- cdf_toml_content = cdf_toml_content.replace("#<PLACEHOLDER>", "")
306
- cdf_toml_content = cdf_toml_content.replace("<DEFAULT_ENV_PLACEHOLDER>", env)
307
- return cdf_toml_content
274
+ CDFToml.write(organization_dir, environments[0] if environments else "dev")
308
275
 
309
276
  def init(
310
277
  self,
@@ -318,14 +285,7 @@ default_organization_dir = "{organization_dir.name}"''',
318
285
  library_checksum: str | None = None,
319
286
  ) -> None:
320
287
  if not organization_dir:
321
- new_line = "\n "
322
- message = (
323
- f"Which directory would you like to create templates in? (default: current directory){new_line}"
324
- f"HINT Use an organization directory if you use the repository for more than Toolkit. "
325
- f"If not, use the current (repository root) directory '.':"
326
- )
327
- organization_dir_raw = questionary.text(message=message, default="").ask()
328
- organization_dir = Path(organization_dir_raw.strip())
288
+ organization_dir = ModulesCommand._prompt_organization_dir()
329
289
 
330
290
  modules_root_dir = organization_dir / MODULES
331
291
 
@@ -453,6 +413,21 @@ default_organization_dir = "{organization_dir.name}"''',
453
413
  if not is_interactive:
454
414
  raise typer.Exit()
455
415
 
416
+ @staticmethod
417
+ def _prompt_organization_dir() -> Path:
418
+ print(
419
+ "\n".join(
420
+ [
421
+ "Enter an optional subdirectory for Toolkit content.",
422
+ " [bold yellow]HINT[/bold yellow] Use an organization directory if you use the repository for more than Toolkit.",
423
+ " [dim]The current directory is the default.[/dim]",
424
+ ]
425
+ )
426
+ )
427
+
428
+ organization_dir_raw = questionary.text(message="", default="").ask()
429
+ return Path(organization_dir_raw.strip())
430
+
456
431
  @staticmethod
457
432
  def _get_download_data(selected: Packages) -> bool:
458
433
  example_data = {
@@ -780,7 +755,8 @@ default_organization_dir = "{organization_dir.name}"''',
780
755
  """
781
756
 
782
757
  cdf_toml = CDFToml.load()
783
- if (Flags.EXTERNAL_LIBRARIES.is_enabled() or user_library) and self._module_source_dir is None:
758
+
759
+ if self._module_source_dir is None:
784
760
  libraries = {"userdefined": user_library} if user_library else cdf_toml.libraries
785
761
 
786
762
  for library_name, library in libraries.items():
@@ -29,13 +29,6 @@ from cognite_toolkit._cdf_tk.cruds import (
29
29
  ResourceCRUD,
30
30
  StreamlitCRUD,
31
31
  )
32
- from cognite_toolkit._cdf_tk.cruds._base_cruds import (
33
- T_ID,
34
- T_ResourceRequest,
35
- T_ResourceRequestList,
36
- T_ResourceResponse,
37
- T_ResourceResponseList,
38
- )
39
32
  from cognite_toolkit._cdf_tk.data_classes import (
40
33
  BuildEnvironment,
41
34
  BuildVariable,
@@ -49,6 +42,7 @@ from cognite_toolkit._cdf_tk.data_classes import (
49
42
  YAMLComments,
50
43
  )
51
44
  from cognite_toolkit._cdf_tk.exceptions import ToolkitError, ToolkitMissingResourceError, ToolkitValueError
45
+ from cognite_toolkit._cdf_tk.protocols import T_ResourceRequest, T_ResourceResponse
52
46
  from cognite_toolkit._cdf_tk.tk_warnings import LowSeverityWarning, MediumSeverityWarning
53
47
  from cognite_toolkit._cdf_tk.utils import (
54
48
  YAMLComment,
@@ -64,6 +58,7 @@ from cognite_toolkit._cdf_tk.utils.modules import (
64
58
  module_directory_from_path,
65
59
  parse_user_selected_modules,
66
60
  )
61
+ from cognite_toolkit._cdf_tk.utils.useful_types import T_ID
67
62
 
68
63
  from ._base import ToolkitCommand
69
64
  from .build_cmd import BuildCommand
@@ -547,9 +542,7 @@ class PullCommand(ToolkitCommand):
547
542
 
548
543
  def _pull_resources(
549
544
  self,
550
- loader: ResourceCRUD[
551
- T_ID, T_ResourceRequest, T_ResourceResponse, T_ResourceRequestList, T_ResourceResponseList
552
- ],
545
+ loader: ResourceCRUD[T_ID, T_ResourceRequest, T_ResourceResponse],
553
546
  resources: BuiltFullResourceList[T_ID],
554
547
  dry_run: bool,
555
548
  environment_variables: dict[str, str | None],
@@ -581,9 +574,7 @@ class PullCommand(ToolkitCommand):
581
574
  local_resource_by_id: dict[T_ID, dict[str, Any]],
582
575
  cdf_resource_by_id: dict[T_ID, T_ResourceResponse],
583
576
  file_results: ResourceDeployResult,
584
- loader: ResourceCRUD[
585
- T_ID, T_ResourceRequest, T_ResourceResponse, T_ResourceRequestList, T_ResourceResponseList
586
- ],
577
+ loader: ResourceCRUD[T_ID, T_ResourceRequest, T_ResourceResponse],
587
578
  ) -> tuple[bool, dict[T_ID, dict[str, Any]]]:
588
579
  to_write: dict[T_ID, dict[str, Any]] = {}
589
580
  has_changes = False
@@ -612,9 +603,7 @@ class PullCommand(ToolkitCommand):
612
603
  @staticmethod
613
604
  def _get_local_resource_dict_by_id(
614
605
  resources: BuiltFullResourceList[T_ID],
615
- loader: ResourceCRUD[
616
- T_ID, T_ResourceRequest, T_ResourceResponse, T_ResourceRequestList, T_ResourceResponseList
617
- ],
606
+ loader: ResourceCRUD[T_ID, T_ResourceRequest, T_ResourceResponse],
618
607
  environment_variables: dict[str, str | None],
619
608
  ) -> dict[T_ID, dict[str, Any]]:
620
609
  unique_destinations = {r.destination for r in resources if r.destination}
@@ -651,9 +640,7 @@ class PullCommand(ToolkitCommand):
651
640
  to_write: dict[T_ID, dict[str, Any]],
652
641
  resources: BuiltFullResourceList[T_ID],
653
642
  environment_variables: dict[str, str | None],
654
- loader: ResourceCRUD[
655
- T_ID, T_ResourceRequest, T_ResourceResponse, T_ResourceRequestList, T_ResourceResponseList
656
- ],
643
+ loader: ResourceCRUD[T_ID, T_ResourceRequest, T_ResourceResponse],
657
644
  source_file: Path,
658
645
  ) -> tuple[str, dict[Path, str]]:
659
646
  # 1. Replace all variables with placeholders
@@ -0,0 +1,179 @@
1
+ import difflib
2
+ from pathlib import Path
3
+ from typing import cast
4
+
5
+ import questionary
6
+ import typer
7
+ from questionary import Choice
8
+ from rich import print
9
+
10
+ from cognite_toolkit._cdf_tk.commands._base import ToolkitCommand
11
+ from cognite_toolkit._cdf_tk.constants import MODULES
12
+ from cognite_toolkit._cdf_tk.cruds import RESOURCE_CRUD_LIST, ResourceCRUD
13
+ from cognite_toolkit._cdf_tk.data_classes import ModuleDirectories
14
+ from cognite_toolkit._cdf_tk.resource_classes import ToolkitResource
15
+ from cognite_toolkit._cdf_tk.utils.collection import humanize_collection
16
+ from cognite_toolkit._cdf_tk.utils.file import yaml_safe_dump
17
+
18
+
19
+ class ResourcesCommand(ToolkitCommand):
20
+ def __init__(self, print_warning: bool = True, skip_tracking: bool = False, silent: bool = False) -> None:
21
+ super().__init__(print_warning, skip_tracking, silent)
22
+
23
+ def _get_or_prompt_module_path(self, module: str | None, organization_dir: Path, verbose: bool) -> Path:
24
+ """
25
+ Check if the module exists in the organization directory and return the module path.
26
+ If module is not provided, ask the user to select or create a new module.
27
+ """
28
+ present_modules = ModuleDirectories.load(organization_dir, None)
29
+
30
+ if module:
31
+ for mod in present_modules:
32
+ if mod.name.casefold() == module.casefold():
33
+ return mod.dir
34
+
35
+ if questionary.confirm(f"{module} module not found. Do you want to create a new one?").ask():
36
+ return organization_dir / MODULES / module
37
+
38
+ if verbose:
39
+ print(f"[red]Aborting as {module} module not found...[/red]")
40
+ else:
41
+ print("[red]Aborting...[/red]")
42
+ raise typer.Exit()
43
+
44
+ choices = [Choice(title=mod.name, value=mod.dir) for mod in present_modules]
45
+ choices.append(Choice(title="<Create new module>", value="NEW"))
46
+
47
+ selected = questionary.select("Select a module:", choices=choices).ask()
48
+
49
+ if selected == "NEW":
50
+ new_module_name = questionary.text("Enter name for new module:").ask()
51
+ if not new_module_name:
52
+ print("[red]No module name provided. Aborting...[/red]")
53
+ raise typer.Exit()
54
+ return organization_dir / MODULES / new_module_name
55
+
56
+ if not selected:
57
+ print("[red]No module selected. Aborting...[/red]")
58
+ raise typer.Exit()
59
+
60
+ return cast(Path, selected)
61
+
62
+ def _resolve_kinds(self, kinds: list[str] | None) -> list[type[ResourceCRUD]]:
63
+ """
64
+ Resolve kinds from list of strings or do an interactive selection.
65
+ """
66
+ all_cruds = {crud.kind.casefold(): crud for crud in RESOURCE_CRUD_LIST}
67
+
68
+ if not kinds:
69
+ sorted_cruds = sorted(RESOURCE_CRUD_LIST, key=lambda x: x.kind)
70
+ choices = [Choice(title=crud.kind, value=crud) for crud in sorted_cruds]
71
+
72
+ selected = questionary.select("Select resource type:", choices=choices).ask()
73
+ if not selected:
74
+ print("[red]No resource type selected. Aborting...[/red]")
75
+ raise typer.Exit()
76
+ return [selected]
77
+
78
+ resolved_cruds = []
79
+ for kind in kinds:
80
+ kind_lower = kind.casefold()
81
+ if kind_lower in all_cruds:
82
+ resolved_cruds.append(all_cruds[kind_lower])
83
+ else:
84
+ matches = difflib.get_close_matches(kind_lower, all_cruds.keys())
85
+ if matches:
86
+ suggestion = all_cruds[matches[0]].kind
87
+ print(f"[red]Unknown resource type '{kind}'. Did you mean '{suggestion}'?[/red]")
88
+ else:
89
+ print(
90
+ f"[red]Unknown resource type '{kind}'. "
91
+ f"Available types: {humanize_collection(sorted([c.kind for c in RESOURCE_CRUD_LIST]))}[/red]"
92
+ )
93
+ raise typer.Exit()
94
+
95
+ return resolved_cruds
96
+
97
+ def _create_resource_yaml_skeleton(self, yaml_cls: type[ToolkitResource]) -> dict[str, str]:
98
+ """
99
+ Build YAML skeleton from a Pydantic model class using JSON schema for better type information.
100
+ """
101
+ yaml_skeleton: dict[str, str] = {}
102
+ for field_name, field in yaml_cls.model_fields.items():
103
+ name = field.alias or field_name
104
+ description = field.description or name
105
+ if field.is_required():
106
+ yaml_skeleton[name] = f"(Required) {description}"
107
+ else:
108
+ yaml_skeleton[name] = description
109
+
110
+ return yaml_skeleton
111
+
112
+ def _get_resource_yaml_content(self, resource_crud: type[ResourceCRUD]) -> str:
113
+ """
114
+ Creates a new resource in the specified module using the resource_crud.yaml_cls.
115
+ """
116
+ yaml_header = (
117
+ f"# API docs: {resource_crud.doc_url()}\n"
118
+ f"# YAML reference: https://docs.cognite.com/cdf/deploy/cdf_toolkit/references/resource_library"
119
+ )
120
+ yaml_skeleton = self._create_resource_yaml_skeleton(resource_crud.yaml_cls)
121
+ yaml_contents = yaml_safe_dump(yaml_skeleton)
122
+ return yaml_header + "\n\n" + yaml_contents
123
+
124
+ def _create_resource_yaml_file(
125
+ self,
126
+ resource_crud: type[ResourceCRUD],
127
+ module_path: Path,
128
+ prefix: str | None = None,
129
+ verbose: bool = False,
130
+ ) -> None:
131
+ """
132
+ Creates a new resource YAML file in the specified module using the resource_crud.yaml_cls.
133
+ """
134
+ resource_dir: Path = module_path / resource_crud.folder_name
135
+ if resource_crud.sub_folder_name:
136
+ resource_dir = resource_dir / resource_crud.sub_folder_name
137
+
138
+ if not resource_dir.exists():
139
+ resource_dir.mkdir(parents=True, exist_ok=True)
140
+
141
+ final_prefix = prefix if prefix is not None else f"my_{resource_crud.kind}"
142
+ file_name = f"{final_prefix}.{resource_crud.kind}.yaml"
143
+ file_path: Path = resource_dir / file_name
144
+
145
+ if file_path.exists() and not questionary.confirm(f"{file_path.name} file already exists. Overwrite?").ask():
146
+ print("[red]Skipping...[/red]")
147
+ return
148
+
149
+ yaml_content = self._get_resource_yaml_content(resource_crud)
150
+ file_path.write_text(yaml_content)
151
+ if verbose:
152
+ print(
153
+ f"[green]{resource_crud.kind} Resource YAML file created successfully at {file_path.as_posix()}[/green]"
154
+ )
155
+ else:
156
+ print(f"[green]Created {file_path.as_posix()}[/green]")
157
+
158
+ def create(
159
+ self,
160
+ organization_dir: Path,
161
+ module_name: str | None = None,
162
+ kind: list[str] | None = None,
163
+ prefix: str | None = None,
164
+ verbose: bool = False,
165
+ ) -> None:
166
+ """
167
+ create resource YAMLs.
168
+
169
+ Args:
170
+ organization_dir: The directory of the organization.
171
+ module_name: The name of the module.
172
+ kind: The kind(s) of resource to create.
173
+ prefix: The prefix for the resource file.
174
+ verbose: Whether to print verbose output.
175
+ """
176
+ module_path = self._get_or_prompt_module_path(module_name, organization_dir, verbose)
177
+ resource_cruds = self._resolve_kinds(kind)
178
+ for crud in resource_cruds:
179
+ self._create_resource_yaml_file(crud, module_path, prefix, verbose)