datasecops-cli 0.4.3__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.
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/CHANGELOG.md +6 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/PKG-INFO +1 -1
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/pyproject.toml +1 -1
- datasecops_cli-0.4.4/src/datasecops_cli/__init__.py +1 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/src/datasecops_cli/config.py +7 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/src/datasecops_cli/main.py +14 -5
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/src/datasecops_cli/menus/configuration.py +40 -2
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/src/datasecops_cli/models/project_config.py +9 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/src/datasecops_cli/services/dbt_runner.py +6 -4
- datasecops_cli-0.4.3/src/datasecops_cli/__init__.py +0 -1
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/.github/workflows/auto-tag.yml +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/.github/workflows/publish-cli.yml +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/.gitignore +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/DEVELOPMENT.md +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/LICENSE +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/README.md +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/docs/getting-started.md +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/docs/legacy.md +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/docs/legacy_plan_of_action.md +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/docs/mcp-server.md +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/mcp-servers.json +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/setup.ps1 +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/setup.sh +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/src/datasecops_cli/menus/__init__.py +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/src/datasecops_cli/menus/development.py +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/src/datasecops_cli/menus/downloads.py +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/src/datasecops_cli/menus/git_operations.py +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/src/datasecops_cli/models/__init__.py +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/src/datasecops_cli/models/git_helpers.py +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/src/datasecops_cli/services/__init__.py +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/src/datasecops_cli/services/bootstrap_service.py +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/src/datasecops_cli/services/dbt_project_generator.py +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/src/datasecops_cli/services/directory_scaffolder.py +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/src/datasecops_cli/services/download_service.py +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/src/datasecops_cli/services/git_service.py +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/src/datasecops_cli/services/linting_service.py +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/src/datasecops_cli/services/skill_service.py +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/src/datasecops_cli/services/snowflake_service.py +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/src/datasecops_cli/services/upstream_service.py +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/src/datasecops_cli/utilities/__init__.py +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/src/datasecops_cli/utilities/display.py +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/src/datasecops_cli/utilities/file_utils.py +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/src/datasecops_cli/utilities/yaml_utils.py +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/src/datasecops_mcp/__init__.py +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/src/datasecops_mcp/__main__.py +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/src/datasecops_mcp/connection.py +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/src/datasecops_mcp/server.py +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/tests/__init__.py +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/tests/test_config.py +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/tests/test_file_utils.py +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/tests/test_main.py +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/tests/test_models.py +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/tests/test_version.py +0 -0
- {datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/tests/test_yaml_utils.py +0 -0
|
@@ -2,6 +2,12 @@
|
|
|
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
|
+
|
|
5
11
|
## [0.4.3] - 2026-05-17
|
|
6
12
|
|
|
7
13
|
### Added
|
|
@@ -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
|
|
477
|
-
|
|
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("
|
|
480
|
-
|
|
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()
|
|
@@ -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 = [
|
|
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 = [
|
|
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.3"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/src/datasecops_cli/services/bootstrap_service.py
RENAMED
|
File without changes
|
{datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/src/datasecops_cli/services/dbt_project_generator.py
RENAMED
|
File without changes
|
{datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/src/datasecops_cli/services/directory_scaffolder.py
RENAMED
|
File without changes
|
{datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/src/datasecops_cli/services/download_service.py
RENAMED
|
File without changes
|
|
File without changes
|
{datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/src/datasecops_cli/services/linting_service.py
RENAMED
|
File without changes
|
|
File without changes
|
{datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/src/datasecops_cli/services/snowflake_service.py
RENAMED
|
File without changes
|
{datasecops_cli-0.4.3 → datasecops_cli-0.4.4}/src/datasecops_cli/services/upstream_service.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|