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.
Files changed (57) hide show
  1. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/CHANGELOG.md +7 -0
  2. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/PKG-INFO +1 -1
  3. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/pyproject.toml +1 -1
  4. datasecops_cli-0.6.2/src/datasecops_cli/__init__.py +1 -0
  5. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/services/dbt_runner.py +37 -80
  6. datasecops_cli-0.6.1/src/datasecops_cli/__init__.py +0 -1
  7. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/.github/workflows/auto-tag.yml +0 -0
  8. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/.github/workflows/publish-cli.yml +0 -0
  9. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/.github/workflows/test.yml +0 -0
  10. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/.gitignore +0 -0
  11. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/DEVELOPMENT.md +0 -0
  12. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/LICENSE +0 -0
  13. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/README.md +0 -0
  14. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/docs/getting-started.md +0 -0
  15. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/docs/git-operations.md +0 -0
  16. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/docs/legacy.md +0 -0
  17. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/docs/legacy_plan_of_action.md +0 -0
  18. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/docs/mcp-server.md +0 -0
  19. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/mcp-servers.json +0 -0
  20. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/setup.ps1 +0 -0
  21. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/setup.sh +0 -0
  22. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/config.py +0 -0
  23. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/main.py +0 -0
  24. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/menus/__init__.py +0 -0
  25. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/menus/configuration.py +0 -0
  26. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/menus/development.py +0 -0
  27. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/menus/downloads.py +0 -0
  28. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/menus/git_operations.py +0 -0
  29. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/models/__init__.py +0 -0
  30. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/models/git_helpers.py +0 -0
  31. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/models/project_config.py +0 -0
  32. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/services/__init__.py +0 -0
  33. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/services/bootstrap_service.py +0 -0
  34. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/services/dbt_project_generator.py +0 -0
  35. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/services/directory_scaffolder.py +0 -0
  36. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/services/download_service.py +0 -0
  37. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/services/git_service.py +0 -0
  38. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/services/linting_service.py +0 -0
  39. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/services/skill_service.py +0 -0
  40. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/services/snowflake_service.py +0 -0
  41. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/services/upstream_service.py +0 -0
  42. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/utilities/__init__.py +0 -0
  43. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/utilities/display.py +0 -0
  44. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/utilities/file_utils.py +0 -0
  45. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/utilities/mcp.py +0 -0
  46. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_cli/utilities/yaml_utils.py +0 -0
  47. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_mcp/__init__.py +0 -0
  48. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_mcp/__main__.py +0 -0
  49. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_mcp/connection.py +0 -0
  50. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/src/datasecops_mcp/server.py +0 -0
  51. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/tests/__init__.py +0 -0
  52. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/tests/test_config.py +0 -0
  53. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/tests/test_file_utils.py +0 -0
  54. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/tests/test_main.py +0 -0
  55. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/tests/test_models.py +0 -0
  56. {datasecops_cli-0.6.1 → datasecops_cli-0.6.2}/tests/test_version.py +0 -0
  57. {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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: datasecops-cli
3
- Version: 0.6.1
3
+ Version: 0.6.2
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.6.1"
7
+ version = "0.6.2"
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.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, warning_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
- return self._run_command("compile", ["--write-catalog", f"--target={self.target}"])
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
- if self.engine == "dbtf":
132
- target_dir = self.project_dir / "target"
133
- index_file = target_dir / "index.html"
134
- if not index_file.exists():
135
- info_line("Downloading dbt docs index.html...")
136
- url = "https://raw.githubusercontent.com/dbt-labs/dbt-core/main/core/dbt/task/docs/index.html"
137
- try:
138
- urllib.request.urlretrieve(url, str(index_file))
139
- success_line("Downloaded index.html to target/")
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