datasecops-cli 0.6.1__tar.gz → 0.6.2__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.6.1 → datasecops_cli-0.6.2}/CHANGELOG.md +7 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/PKG-INFO +1 -1
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/pyproject.toml +1 -1
- datasecops_cli-0.6.2/src/datasecops_cli/__init__.py +1 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/services/dbt_runner.py +37 -80
- datasecops_cli-0.6.1/src/datasecops_cli/__init__.py +0 -1
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/.github/workflows/auto-tag.yml +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/.github/workflows/publish-cli.yml +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/.github/workflows/test.yml +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/.gitignore +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/DEVELOPMENT.md +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/LICENSE +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/README.md +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/docs/getting-started.md +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/docs/git-operations.md +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/docs/legacy.md +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/docs/legacy_plan_of_action.md +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/docs/mcp-server.md +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/mcp-servers.json +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/setup.ps1 +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/setup.sh +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/config.py +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/main.py +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/menus/__init__.py +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/menus/configuration.py +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/menus/development.py +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/menus/downloads.py +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/menus/git_operations.py +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/models/__init__.py +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/models/git_helpers.py +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/models/project_config.py +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/services/__init__.py +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/services/bootstrap_service.py +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/services/dbt_project_generator.py +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/services/directory_scaffolder.py +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/services/download_service.py +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/services/git_service.py +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/services/linting_service.py +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/services/skill_service.py +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/services/snowflake_service.py +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/services/upstream_service.py +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/utilities/__init__.py +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/utilities/display.py +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/utilities/file_utils.py +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/utilities/mcp.py +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/utilities/yaml_utils.py +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_mcp/__init__.py +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_mcp/__main__.py +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_mcp/connection.py +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_mcp/server.py +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/tests/__init__.py +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/tests/test_config.py +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/tests/test_file_utils.py +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/tests/test_main.py +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/tests/test_models.py +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/tests/test_version.py +0 -0
- {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/tests/test_yaml_utils.py +0 -0
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the DataSecOps CLI are documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.6.2] - 2026-09-07
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- **Native `dbt docs generate`/`docs serve`** — `docs_serve()` now delegates directly to the engine's `docs serve` command (native in dbt Fusion and dbt Core 2.0+), which builds the site if missing/stale, serves it, and opens the browser itself. Removed the manual `index.html` download and platform-specific terminal/`http.server` spawning workaround, along with the now-unused `docs_generate()` engine branch.
|
|
10
|
+
- **Column-level lineage in generated docs (Fusion)** — `docs_generate()` now runs `dbt compile --write-index --static-analysis strict` before `dbt docs generate --no-compile` on the Fusion engine, so the exported site includes column-level lineage instead of hiding those features.
|
|
11
|
+
|
|
5
12
|
## [0.6.1] - 2026-08-20
|
|
6
13
|
|
|
7
14
|
### Fixed
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.6.2"
|
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
import subprocess
|
|
2
|
-
import shlex
|
|
3
2
|
import shutil
|
|
4
|
-
import sys
|
|
5
|
-
import urllib.request
|
|
6
|
-
import webbrowser
|
|
7
3
|
from pathlib import Path
|
|
8
4
|
from typing import Optional
|
|
9
5
|
|
|
10
6
|
import yaml
|
|
11
7
|
|
|
12
|
-
from datasecops_cli.utilities.display import info_line, error_line, success_line
|
|
8
|
+
from datasecops_cli.utilities.display import info_line, error_line, success_line
|
|
13
9
|
|
|
14
10
|
|
|
15
11
|
class DbtRunner:
|
|
16
12
|
"""Runs dbt commands via subprocess (dbt Fusion or dbt Core)."""
|
|
17
|
-
|
|
13
|
+
|
|
18
14
|
def __init__(self, project_dir: Path, profiles_dir: Path, target: str = "dev",
|
|
19
15
|
engine: str = "dbtf"):
|
|
20
16
|
self.project_dir = project_dir
|
|
@@ -35,13 +31,13 @@ class DbtRunner:
|
|
|
35
31
|
else:
|
|
36
32
|
self._project_name = ""
|
|
37
33
|
return self._project_name or None
|
|
38
|
-
|
|
34
|
+
|
|
39
35
|
def _default_args(self) -> list[str]:
|
|
40
36
|
return [
|
|
41
37
|
f"--project-dir={self.project_dir}",
|
|
42
38
|
f"--profiles-dir={self.profiles_dir}",
|
|
43
39
|
]
|
|
44
|
-
|
|
40
|
+
|
|
45
41
|
def _run_command(self, command: str, extra_args: list[str] = None) -> subprocess.CompletedProcess:
|
|
46
42
|
cmd = [self.engine, command] + (extra_args or []) + self._default_args()
|
|
47
43
|
info_line(f"Running: {' '.join(cmd)}")
|
|
@@ -51,7 +47,7 @@ class DbtRunner:
|
|
|
51
47
|
else:
|
|
52
48
|
success_line(f"dbt {command} completed successfully")
|
|
53
49
|
return result
|
|
54
|
-
|
|
50
|
+
|
|
55
51
|
def run(self, select: str = None, exclude: str = None, full_refresh: bool = False,
|
|
56
52
|
modified_only: bool = False) -> subprocess.CompletedProcess:
|
|
57
53
|
args = [f"--target={self.target}"]
|
|
@@ -68,7 +64,7 @@ class DbtRunner:
|
|
|
68
64
|
result = self._run_command("run", args)
|
|
69
65
|
self._copy_manifest()
|
|
70
66
|
return result
|
|
71
|
-
|
|
67
|
+
|
|
72
68
|
def build(self, select: str = None, exclude: str = None) -> subprocess.CompletedProcess:
|
|
73
69
|
args = [f"--target={self.target}"]
|
|
74
70
|
if select:
|
|
@@ -80,7 +76,7 @@ class DbtRunner:
|
|
|
80
76
|
result = self._run_command("build", args)
|
|
81
77
|
self._copy_manifest()
|
|
82
78
|
return result
|
|
83
|
-
|
|
79
|
+
|
|
84
80
|
def test(self, select: str = None) -> subprocess.CompletedProcess:
|
|
85
81
|
args = [f"--target={self.target}"]
|
|
86
82
|
if select:
|
|
@@ -88,7 +84,7 @@ class DbtRunner:
|
|
|
88
84
|
elif self.project_name:
|
|
89
85
|
args += ["--select", self.project_name]
|
|
90
86
|
return self._run_command("test", args)
|
|
91
|
-
|
|
87
|
+
|
|
92
88
|
def compile(self, select: str = None) -> subprocess.CompletedProcess:
|
|
93
89
|
args = [f"--target={self.target}"]
|
|
94
90
|
if select:
|
|
@@ -96,10 +92,10 @@ class DbtRunner:
|
|
|
96
92
|
elif self.project_name:
|
|
97
93
|
args += ["--select", self.project_name]
|
|
98
94
|
return self._run_command("compile", args)
|
|
99
|
-
|
|
95
|
+
|
|
100
96
|
def deps(self) -> subprocess.CompletedProcess:
|
|
101
97
|
return self._run_command("deps")
|
|
102
|
-
|
|
98
|
+
|
|
103
99
|
def seed(self, select: str = None, full_refresh: bool = False) -> subprocess.CompletedProcess:
|
|
104
100
|
args = [f"--target={self.target}"]
|
|
105
101
|
if select:
|
|
@@ -109,87 +105,48 @@ class DbtRunner:
|
|
|
109
105
|
if full_refresh:
|
|
110
106
|
args.append("--full-refresh")
|
|
111
107
|
return self._run_command("seed", args)
|
|
112
|
-
|
|
108
|
+
|
|
113
109
|
def snapshot(self, select: str = None) -> subprocess.CompletedProcess:
|
|
114
110
|
args = [f"--target={self.target}"]
|
|
115
111
|
if select:
|
|
116
112
|
args += ["--select", select]
|
|
117
113
|
return self._run_command("snapshot", args)
|
|
118
|
-
|
|
114
|
+
|
|
119
115
|
def source_freshness(self, select: str = None) -> subprocess.CompletedProcess:
|
|
120
116
|
args = [f"--target={self.target}"]
|
|
121
117
|
if select:
|
|
122
118
|
args += ["--select", select]
|
|
123
119
|
return self._run_command("source", ["freshness"] + args)
|
|
124
|
-
|
|
120
|
+
|
|
125
121
|
def docs_generate(self) -> subprocess.CompletedProcess:
|
|
126
122
|
if self.engine == "dbtf":
|
|
127
|
-
|
|
123
|
+
# Build the index with strict static analysis first for column-level
|
|
124
|
+
# lineage, then export the site without recompiling.
|
|
125
|
+
compile_result = self._run_command(
|
|
126
|
+
"compile", ["--write-index", "--static-analysis", "strict", f"--target={self.target}"]
|
|
127
|
+
)
|
|
128
|
+
if compile_result.returncode != 0:
|
|
129
|
+
return compile_result
|
|
130
|
+
return self._run_command("docs", ["generate", "--no-compile", f"--target={self.target}"])
|
|
131
|
+
|
|
128
132
|
return self._run_command("docs", ["generate", f"--target={self.target}"])
|
|
129
|
-
|
|
133
|
+
|
|
130
134
|
def docs_serve(self, port: int = 8080) -> None:
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
except Exception as e:
|
|
141
|
-
error_line(f"Failed to download index.html: {e}")
|
|
142
|
-
warning_line("Download manually from: https://github.com/dbt-labs/dbt-core/blob/main/core/dbt/task/docs/index.html")
|
|
143
|
-
return
|
|
144
|
-
# Launch HTTP server in a new terminal window
|
|
145
|
-
cmd = [sys.executable, "-m", "http.server", str(port)]
|
|
146
|
-
try:
|
|
147
|
-
if sys.platform == "win32":
|
|
148
|
-
subprocess.Popen( # noqa: S603 - all args are internal/hardcoded
|
|
149
|
-
["cmd", "/c", "start", f"dbt docs - localhost:{port}"] + cmd,
|
|
150
|
-
cwd=str(target_dir),
|
|
151
|
-
)
|
|
152
|
-
elif sys.platform == "darwin":
|
|
153
|
-
subprocess.Popen( # noqa: S603 - all args are internal/hardcoded
|
|
154
|
-
["open", "-a", "Terminal", "--args"] + cmd,
|
|
155
|
-
cwd=str(target_dir),
|
|
156
|
-
)
|
|
157
|
-
else:
|
|
158
|
-
# Linux — use shlex.quote and bash -lc for safe argument passing
|
|
159
|
-
cmd_str = " ".join(shlex.quote(arg) for arg in cmd)
|
|
160
|
-
for term in ("x-terminal-emulator", "gnome-terminal", "xterm"):
|
|
161
|
-
if not shutil.which(term):
|
|
162
|
-
continue
|
|
163
|
-
if term == "gnome-terminal":
|
|
164
|
-
term_cmd = [term, "--", "bash", "-lc", cmd_str]
|
|
165
|
-
else:
|
|
166
|
-
term_cmd = [term, "-e", "bash", "-lc", cmd_str]
|
|
167
|
-
subprocess.Popen(term_cmd, cwd=str(target_dir)) # noqa: S603
|
|
168
|
-
break
|
|
169
|
-
else:
|
|
170
|
-
# Fallback: run in background
|
|
171
|
-
subprocess.Popen(cmd, cwd=str(target_dir)) # noqa: S603
|
|
172
|
-
except (OSError, subprocess.SubprocessError) as e:
|
|
173
|
-
error_line(f"Failed to start docs server: {e}")
|
|
174
|
-
warning_line("Try manually: python -m http.server 8080 (from the target/ directory)")
|
|
175
|
-
return
|
|
176
|
-
info_line(f"Docs server started at http://localhost:{port}")
|
|
177
|
-
webbrowser.open(f"http://localhost:{port}")
|
|
178
|
-
success_line("Opened docs in browser")
|
|
179
|
-
else:
|
|
180
|
-
cmd = [self.engine, "docs", "serve"] + self._default_args()
|
|
181
|
-
info_line(f"Running: {' '.join(cmd)}")
|
|
182
|
-
try:
|
|
183
|
-
subprocess.Popen(cmd) # noqa: S603 - cmd is internal (engine + hardcoded args)
|
|
184
|
-
except (OSError, subprocess.SubprocessError) as e:
|
|
185
|
-
error_line(f"Failed to start docs server: {e}")
|
|
186
|
-
|
|
135
|
+
# Native in dbt Fusion / dbt Core 2.0+: builds the site if missing/stale,
|
|
136
|
+
# serves it, and opens the browser itself — no manual download or server needed.
|
|
137
|
+
cmd = [self.engine, "docs", "serve", f"--port={port}", f"--target={self.target}"] + self._default_args()
|
|
138
|
+
info_line(f"Running: {' '.join(cmd)}")
|
|
139
|
+
try:
|
|
140
|
+
subprocess.Popen(cmd) # noqa: S603 - cmd is internal (engine + hardcoded args)
|
|
141
|
+
except (OSError, subprocess.SubprocessError) as e:
|
|
142
|
+
error_line(f"Failed to start docs server: {e}")
|
|
143
|
+
|
|
187
144
|
def clean(self) -> subprocess.CompletedProcess:
|
|
188
145
|
return self._run_command("clean")
|
|
189
|
-
|
|
146
|
+
|
|
190
147
|
def debug(self) -> subprocess.CompletedProcess:
|
|
191
148
|
return self._run_command("debug", [f"--target={self.target}"])
|
|
192
|
-
|
|
149
|
+
|
|
193
150
|
def list_models(self, select: str = None, resource_type: str = None) -> subprocess.CompletedProcess:
|
|
194
151
|
args = [f"--target={self.target}"]
|
|
195
152
|
if select:
|
|
@@ -197,12 +154,12 @@ class DbtRunner:
|
|
|
197
154
|
if resource_type:
|
|
198
155
|
args += ["--resource-type", resource_type]
|
|
199
156
|
return self._run_command("list", args)
|
|
200
|
-
|
|
157
|
+
|
|
201
158
|
def retry(self) -> subprocess.CompletedProcess:
|
|
202
159
|
result = self._run_command("retry", [f"--target={self.target}"])
|
|
203
160
|
self._copy_manifest()
|
|
204
161
|
return result
|
|
205
|
-
|
|
162
|
+
|
|
206
163
|
def parse(self) -> subprocess.CompletedProcess:
|
|
207
164
|
return self._run_command("parse")
|
|
208
165
|
|
|
@@ -211,7 +168,7 @@ class DbtRunner:
|
|
|
211
168
|
if args_str:
|
|
212
169
|
cmd_args += ["--args", args_str]
|
|
213
170
|
return self._run_command("run-operation", [macro] + cmd_args)
|
|
214
|
-
|
|
171
|
+
|
|
215
172
|
def _copy_manifest(self) -> None:
|
|
216
173
|
manifest = self.project_dir / "target" / "manifest.json"
|
|
217
174
|
dest = self.project_dir / "manifest.json"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.6.1"
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/services/bootstrap_service.py
RENAMED
|
File without changes
|
{datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/services/dbt_project_generator.py
RENAMED
|
File without changes
|
{datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/services/directory_scaffolder.py
RENAMED
|
File without changes
|
{datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/services/download_service.py
RENAMED
|
File without changes
|
|
File without changes
|
{datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/services/linting_service.py
RENAMED
|
File without changes
|
|
File without changes
|
{datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/services/snowflake_service.py
RENAMED
|
File without changes
|
{datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/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
|
|
File without changes
|