datasecops-cli 0.4.2__tar.gz → 0.4.4__tar.gz

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 (54) hide show
  1. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/CHANGELOG.md +16 -0
  2. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/PKG-INFO +1 -1
  3. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/pyproject.toml +1 -1
  4. datasecops_cli-0.4.4/src/datasecops_cli/__init__.py +1 -0
  5. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/src/datasecops_cli/config.py +7 -0
  6. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/src/datasecops_cli/main.py +14 -5
  7. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/src/datasecops_cli/menus/configuration.py +40 -2
  8. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/src/datasecops_cli/menus/development.py +62 -1
  9. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/src/datasecops_cli/models/project_config.py +9 -0
  10. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/src/datasecops_cli/services/dbt_runner.py +6 -4
  11. datasecops_cli-0.4.2/src/datasecops_cli/__init__.py +0 -1
  12. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/.github/workflows/auto-tag.yml +0 -0
  13. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/.github/workflows/publish-cli.yml +0 -0
  14. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/.gitignore +0 -0
  15. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/DEVELOPMENT.md +0 -0
  16. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/LICENSE +0 -0
  17. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/README.md +0 -0
  18. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/docs/getting-started.md +0 -0
  19. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/docs/legacy.md +0 -0
  20. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/docs/legacy_plan_of_action.md +0 -0
  21. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/docs/mcp-server.md +0 -0
  22. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/mcp-servers.json +0 -0
  23. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/setup.ps1 +0 -0
  24. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/setup.sh +0 -0
  25. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/src/datasecops_cli/menus/__init__.py +0 -0
  26. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/src/datasecops_cli/menus/downloads.py +0 -0
  27. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/src/datasecops_cli/menus/git_operations.py +0 -0
  28. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/src/datasecops_cli/models/__init__.py +0 -0
  29. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/src/datasecops_cli/models/git_helpers.py +0 -0
  30. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/src/datasecops_cli/services/__init__.py +0 -0
  31. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/src/datasecops_cli/services/bootstrap_service.py +0 -0
  32. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/src/datasecops_cli/services/dbt_project_generator.py +0 -0
  33. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/src/datasecops_cli/services/directory_scaffolder.py +0 -0
  34. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/src/datasecops_cli/services/download_service.py +0 -0
  35. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/src/datasecops_cli/services/git_service.py +0 -0
  36. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/src/datasecops_cli/services/linting_service.py +0 -0
  37. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/src/datasecops_cli/services/skill_service.py +0 -0
  38. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/src/datasecops_cli/services/snowflake_service.py +0 -0
  39. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/src/datasecops_cli/services/upstream_service.py +0 -0
  40. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/src/datasecops_cli/utilities/__init__.py +0 -0
  41. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/src/datasecops_cli/utilities/display.py +0 -0
  42. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/src/datasecops_cli/utilities/file_utils.py +0 -0
  43. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/src/datasecops_cli/utilities/yaml_utils.py +0 -0
  44. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/src/datasecops_mcp/__init__.py +0 -0
  45. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/src/datasecops_mcp/__main__.py +0 -0
  46. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/src/datasecops_mcp/connection.py +0 -0
  47. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/src/datasecops_mcp/server.py +0 -0
  48. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/tests/__init__.py +0 -0
  49. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/tests/test_config.py +0 -0
  50. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/tests/test_file_utils.py +0 -0
  51. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/tests/test_main.py +0 -0
  52. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/tests/test_models.py +0 -0
  53. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/tests/test_version.py +0 -0
  54. {datasecops_cli-0.4.2 → datasecops_cli-0.4.4}/tests/test_yaml_utils.py +0 -0
@@ -2,6 +2,22 @@
2
2
 
3
3
  All notable changes to the DataSecOps CLI are documented in this file.
4
4
 
5
+ ## [0.4.4] - 2026-05-18
6
+
7
+ ### Added
8
+
9
+ - **dbt engine toggle** — new `dbt_engine` field in `.datasecops.yml` to switch between `dbtf` (dbt Fusion) and `dbt` (dbt Core). Defaults to `dbtf`. Toggleable via Configure menu `[6] dbt engine`. All dbt commands use the configured engine binary.
10
+
11
+ ## [0.4.3] - 2026-05-17
12
+
13
+ ### Added
14
+
15
+ - **dbt autofix** — new `[14] autofix` option in the development menu integrating [dbt-autofix](https://github.com/dbt-labs/dbt-autofix) to prepare projects for dbt Fusion / dbt 2.0:
16
+ - Fix deprecated YAML configs, SQL files, and `dbt_project.yml` settings
17
+ - Upgrade packages for Fusion compatibility (with optional `dbt deps` afterwards)
18
+ - Dry run mode to preview changes without writing
19
+ - Auto-installs `dbt-autofix` via `uv tool install` if not found on PATH
20
+
5
21
  ## [0.4.2] - 2026-05-17
6
22
 
7
23
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datasecops-cli
3
- Version: 0.4.2
3
+ Version: 0.4.4
4
4
  Summary: DataSecOps Framework CLI for Snowflake Native App
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "datasecops-cli"
7
- version = "0.4.2"
7
+ version = "0.4.4"
8
8
  description = "DataSecOps Framework CLI for Snowflake Native App"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -0,0 +1 @@
1
+ __version__ = "0.4.4"
@@ -36,7 +36,14 @@ class Config:
36
36
  app_database=raw.get("app_database", ""),
37
37
  profile_name=raw.get("profile_name", ""),
38
38
  cortex_connection=raw.get("cortex_connection", ""),
39
+ dbt_engine=raw.get("dbt_engine", "dbtf"),
39
40
  )
41
+
42
+ # Validate dbt_engine
43
+ if self.datasecops.dbt_engine not in self.datasecops.VALID_ENGINES:
44
+ from datasecops_cli.utilities.display import warning_line
45
+ warning_line(f"Invalid dbt_engine '{self.datasecops.dbt_engine}' in .datasecops.yml — falling back to dbtf")
46
+ self.datasecops.dbt_engine = "dbtf"
40
47
 
41
48
  if not self.datasecops.connection_name or not self.datasecops.app_database:
42
49
  error_line(".datasecops.yml is missing connection_name or app_database.")
@@ -473,11 +473,17 @@ def _run_interactive(config: Config):
473
473
  sf_service = _connect_and_load(config)
474
474
 
475
475
  try:
476
- # Check for dbt Fusion (dbtf) on PATH
477
- if not shutil.which("dbtf"):
476
+ # Check for configured dbt engine on PATH
477
+ dbt_engine = config.datasecops.dbt_engine or "dbtf"
478
+ if not shutil.which(dbt_engine):
478
479
  from datasecops_cli.utilities.display import warning_line
479
- warning_line("dbt Fusion (dbtf) not found on PATH — dbt commands will not work.")
480
- warning_line("Install dbt Fusion: https://docs.getdbt.com/docs/core/installation")
480
+ warning_line(f"{dbt_engine} not found on PATH — dbt commands will not work.")
481
+ if dbt_engine == "dbtf":
482
+ warning_line("Install dbt Fusion: https://docs.getdbt.com/docs/core/installation")
483
+ else:
484
+ warning_line("Install dbt Core: uv pip install dbt-core dbt-snowflake")
485
+ else:
486
+ info_line(f"dbt engine: {config.datasecops.get_engine_label()}")
481
487
 
482
488
  # Check upstream project versions
483
489
  from datasecops_cli.services.upstream_service import check_upstream_versions
@@ -492,7 +498,8 @@ def _run_interactive(config: Config):
492
498
  dbt_runner = DbtRunner(
493
499
  project_dir=config.dbt_project_dir,
494
500
  profiles_dir=config.get_dbt_profiles_dir(),
495
- target=config.project_settings.get_default_target().target_name if config.get_default_target() else "dev"
501
+ target=config.project_settings.get_default_target().target_name if config.get_default_target() else "dev",
502
+ engine=config.datasecops.dbt_engine or "dbtf"
496
503
  )
497
504
 
498
505
  try:
@@ -685,6 +692,8 @@ def _main_menu(config: Config, dbt_runner: DbtRunner, git_service: GitService,
685
692
  project_settings=config.project_settings,
686
693
  profile=config.profile,
687
694
  source_control=config.source_control,
695
+ datasecops_config=config.datasecops,
696
+ project_dir=config.project_dir,
688
697
  )
689
698
  config_menu.show()
690
699
 
@@ -5,7 +5,7 @@ import shutil
5
5
  import subprocess
6
6
  from pathlib import Path
7
7
 
8
- from datasecops_cli.models.project_config import ProjectProfile, ProjectSettings, SourceControl
8
+ from datasecops_cli.models.project_config import DatasecopsConfig, ProjectProfile, ProjectSettings, SourceControl
9
9
  from datasecops_cli.services.download_service import DownloadService
10
10
  from datasecops_cli.services.linting_service import LintingService
11
11
  from datasecops_cli.utilities.display import (
@@ -14,13 +14,15 @@ from datasecops_cli.utilities.display import (
14
14
  info_line, success_line, warning_line, error_line,
15
15
  get_input_string, select_from_list, select_multiple_from_list
16
16
  )
17
+ from datasecops_cli.utilities.yaml_utils import write_datasecops_config
17
18
 
18
19
 
19
20
  class ConfigurationMenu:
20
21
  def __init__(self, download_service: DownloadService, linting_service: LintingService,
21
22
  profile_name: str, dbt_project_dir: Path,
22
23
  project_settings: ProjectSettings = None, profile: ProjectProfile = None,
23
- source_control: SourceControl = None):
24
+ source_control: SourceControl = None,
25
+ datasecops_config: DatasecopsConfig = None, project_dir: Path = None):
24
26
  self.downloads = download_service
25
27
  self.linting = linting_service
26
28
  self.profile_name = profile_name
@@ -28,6 +30,8 @@ class ConfigurationMenu:
28
30
  self.project_settings = project_settings
29
31
  self.profile = profile
30
32
  self.source_control = source_control
33
+ self.datasecops_config = datasecops_config or DatasecopsConfig()
34
+ self.project_dir = project_dir or Path.cwd()
31
35
 
32
36
  def show(self) -> None:
33
37
  self._menu()
@@ -45,17 +49,21 @@ class ConfigurationMenu:
45
49
  complete_action()
46
50
  elif option == 5:
47
51
  self._cortex_upgrade()
52
+ elif option == 6:
53
+ self._toggle_dbt_engine()
48
54
  self._menu()
49
55
  option = get_input_number("Choose an option: ")
50
56
 
51
57
  def _menu(self) -> None:
52
58
  clear()
59
+ engine_label = self.datasecops_config.get_engine_label()
53
60
  section_header("Configuration", self.profile_name)
54
61
  menu_option(1, "install dbt - Install dbt-core & dbt-snowflake from framework versions")
55
62
  menu_option(2, "install lint - Install SQLFluff from framework versions")
56
63
  menu_option(3, "mcp servers - Configure MCP servers for AI tools")
57
64
  menu_option(4, "new project - Initialize a new dbt project with framework profiles")
58
65
  menu_option(5, "cortex update - Update Cortex Code to the latest version")
66
+ menu_option(6, f"dbt engine - Switch dbt engine (current: {engine_label})")
59
67
  menu_option(0, "back - Return to main menu")
60
68
 
61
69
  def _install_dbt_requirements(self) -> None:
@@ -250,3 +258,33 @@ class ConfigurationMenu:
250
258
  except FileNotFoundError:
251
259
  error_line("Cortex Code CLI not found.")
252
260
  complete_action()
261
+
262
+ def _toggle_dbt_engine(self) -> None:
263
+ """Toggle between dbt Fusion and dbt Core."""
264
+ display_action_header("Switch dbt Engine")
265
+ current = self.datasecops_config.dbt_engine or "dbtf"
266
+ info_line(f"Current engine: {self.datasecops_config.get_engine_label()} ({current})")
267
+ info_line("")
268
+ menu_option(1, "dbt Fusion - dbtf (recommended)")
269
+ menu_option(2, "dbt Core - dbt")
270
+ menu_option(0, "cancel")
271
+ option = get_input_number("Choose an option: ")
272
+
273
+ if option == 1:
274
+ new_engine = "dbtf"
275
+ elif option == 2:
276
+ new_engine = "dbt"
277
+ else:
278
+ return
279
+
280
+ if new_engine == current:
281
+ info_line("No change.")
282
+ complete_action()
283
+ return
284
+
285
+ self.datasecops_config.dbt_engine = new_engine
286
+ config_data = self.datasecops_config.model_dump(exclude={"VALID_ENGINES"})
287
+ write_datasecops_config(self.project_dir, config_data)
288
+ success_line(f"dbt engine switched to {self.datasecops_config.get_engine_label()} ({new_engine})")
289
+ warning_line("Restart the CLI for the change to take effect.")
290
+ complete_action()
@@ -1,4 +1,6 @@
1
1
  from pathlib import Path
2
+ import shutil
3
+ import subprocess
2
4
 
3
5
  from datasecops_cli.services.dbt_runner import DbtRunner
4
6
  from datasecops_cli.services.linting_service import LintingService
@@ -7,7 +9,7 @@ from datasecops_cli.services.git_service import GitService
7
9
  from datasecops_cli.utilities.display import (
8
10
  clear, section_header, display_action_header, menu_option,
9
11
  get_input_number, get_input_string, get_input_true_false, complete_action,
10
- info_line, error_line
12
+ info_line, error_line, success_line, warning_line
11
13
  )
12
14
 
13
15
 
@@ -75,6 +77,8 @@ class DevelopmentMenu:
75
77
  display_action_header("dbt list")
76
78
  self.dbt.list_models()
77
79
  complete_action()
80
+ elif option == 14:
81
+ self._autofix_menu()
78
82
  self._menu()
79
83
  option = get_input_number("Choose an option: ")
80
84
 
@@ -94,6 +98,7 @@ class DevelopmentMenu:
94
98
  menu_option(11, "clean - Clean dbt target")
95
99
  menu_option(12, "debug - Debug dbt connection")
96
100
  menu_option(13, "list - List dbt resources")
101
+ menu_option(14, "autofix - dbt autofix (deprecations, packages, jobs)")
97
102
  menu_option(0, "back - Return to main menu")
98
103
 
99
104
  def _run_menu(self) -> None:
@@ -191,3 +196,59 @@ class DevelopmentMenu:
191
196
  if path != "0":
192
197
  self.linting.lint_file(file_path=path, fix=False)
193
198
  complete_action()
199
+
200
+ def _autofix_menu(self) -> None:
201
+ clear()
202
+ display_action_header("dbt Autofix")
203
+ menu_option(1, "deprecations - Fix deprecated configs for dbt Fusion / dbt 2.0")
204
+ menu_option(2, "packages - Upgrade packages for Fusion compatibility")
205
+ menu_option(3, "dry run - Preview deprecation fixes without writing")
206
+ menu_option(0, "back - Return to development menu")
207
+ option = get_input_number("Choose an option: ")
208
+
209
+ if option == 0:
210
+ return
211
+
212
+ # Ensure dbt-autofix is available
213
+ if not shutil.which("dbt-autofix"):
214
+ if not shutil.which("uv"):
215
+ error_line("uv not found — install uv first: https://astral.sh/uv")
216
+ complete_action()
217
+ return
218
+ info_line("dbt-autofix not found. Installing via uv...")
219
+ try:
220
+ result = subprocess.run(["uv", "tool", "install", "dbt-autofix"],
221
+ capture_output=True, text=True)
222
+ if result.returncode != 0:
223
+ error_line(f"Failed to install dbt-autofix: {result.stderr.strip()}")
224
+ error_line("Install manually: uv tool install dbt-autofix")
225
+ complete_action()
226
+ return
227
+ success_line("dbt-autofix installed")
228
+ except FileNotFoundError:
229
+ error_line("uv appears in PATH but is not runnable — reinstall uv")
230
+ complete_action()
231
+ return
232
+
233
+ project_path = str(self.dbt.project_dir)
234
+
235
+ if option == 1:
236
+ info_line("Running dbt-autofix deprecations...")
237
+ result = subprocess.run(["dbt-autofix", "deprecations", "--path", project_path])
238
+ if result.returncode != 0:
239
+ error_line(f"dbt-autofix deprecations failed (exit code {result.returncode})")
240
+ elif option == 2:
241
+ info_line("Running dbt-autofix packages...")
242
+ result = subprocess.run(["dbt-autofix", "packages", "--path", project_path])
243
+ if result.returncode != 0:
244
+ error_line("dbt-autofix packages failed — skipping dbt deps")
245
+ else:
246
+ if get_input_true_false("Run dbt deps now?"):
247
+ self.dbt.deps()
248
+ elif option == 3:
249
+ info_line("Running dbt-autofix deprecations (dry run)...")
250
+ result = subprocess.run(["dbt-autofix", "deprecations", "--path", project_path, "--dry-run"])
251
+ if result.returncode != 0:
252
+ error_line(f"dbt-autofix dry run failed (exit code {result.returncode})")
253
+
254
+ complete_action()
@@ -7,6 +7,15 @@ class DatasecopsConfig(BaseModel):
7
7
  app_database: str = ""
8
8
  profile_name: str = ""
9
9
  cortex_connection: str = ""
10
+ dbt_engine: str = "dbtf" # "dbtf" for dbt Fusion, "dbt" for dbt Core
11
+
12
+ VALID_ENGINES: dict = {"dbtf": "dbt Fusion", "dbt": "dbt Core"}
13
+
14
+ model_config = {"arbitrary_types_allowed": True}
15
+
16
+ def get_engine_label(self) -> str:
17
+ """Return human-readable label for the current dbt engine."""
18
+ return self.VALID_ENGINES.get(self.dbt_engine, f"unknown ({self.dbt_engine})")
10
19
 
11
20
  class DbtTarget(BaseModel):
12
21
  target_name: str = ""
@@ -7,12 +7,14 @@ from datasecops_cli.utilities.display import info_line, error_line, success_line
7
7
 
8
8
 
9
9
  class DbtRunner:
10
- """Runs dbt commands via subprocess (dbt Fusion)."""
10
+ """Runs dbt commands via subprocess (dbt Fusion or dbt Core)."""
11
11
 
12
- def __init__(self, project_dir: Path, profiles_dir: Path, target: str = "dev"):
12
+ def __init__(self, project_dir: Path, profiles_dir: Path, target: str = "dev",
13
+ engine: str = "dbtf"):
13
14
  self.project_dir = project_dir
14
15
  self.profiles_dir = profiles_dir
15
16
  self.target = target
17
+ self.engine = engine # "dbtf" or "dbt"
16
18
 
17
19
  def _default_args(self) -> list[str]:
18
20
  return [
@@ -21,7 +23,7 @@ class DbtRunner:
21
23
  ]
22
24
 
23
25
  def _run_command(self, command: str, extra_args: list[str] = None) -> subprocess.CompletedProcess:
24
- cmd = ["dbtf", command] + (extra_args or []) + self._default_args()
26
+ cmd = [self.engine, command] + (extra_args or []) + self._default_args()
25
27
  info_line(f"Running: {' '.join(cmd)}")
26
28
  result = subprocess.run(cmd, capture_output=False)
27
29
  if result.returncode != 0:
@@ -94,7 +96,7 @@ class DbtRunner:
94
96
  return self._run_command("docs", ["generate", f"--target={self.target}"])
95
97
 
96
98
  def docs_serve(self) -> subprocess.Popen:
97
- cmd = ["dbtf", "docs", "serve"] + self._default_args()
99
+ cmd = [self.engine, "docs", "serve"] + self._default_args()
98
100
  info_line(f"Running: {' '.join(cmd)}")
99
101
  return subprocess.Popen(cmd)
100
102
 
@@ -1 +0,0 @@
1
- __version__ = "0.4.2"
File without changes
File without changes
File without changes
File without changes