datasecops-cli 0.2.6__tar.gz → 0.2.7__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 (47) hide show
  1. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/CHANGELOG.md +10 -0
  2. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/PKG-INFO +1 -1
  3. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/pyproject.toml +1 -1
  4. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/src/datasecops_cli/menus/development.py +64 -23
  5. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/src/datasecops_cli/services/bootstrap_service.py +10 -6
  6. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/src/datasecops_cli/services/dbt_runner.py +3 -0
  7. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/src/datasecops_cli/services/download_service.py +28 -10
  8. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/src/datasecops_cli/services/linting_service.py +13 -7
  9. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/.github/workflows/publish-cli.yml +0 -0
  10. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/.gitignore +0 -0
  11. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/DEVELOPMENT.md +0 -0
  12. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/LICENSE +0 -0
  13. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/README.md +0 -0
  14. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/docs/getting-started.md +0 -0
  15. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/docs/legacy.md +0 -0
  16. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/docs/legacy_plan_of_action.md +0 -0
  17. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/docs/mcp-server.md +0 -0
  18. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/mcp-servers.json +0 -0
  19. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/setup.ps1 +0 -0
  20. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/setup.sh +0 -0
  21. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/src/datasecops_cli/__init__.py +0 -0
  22. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/src/datasecops_cli/config.py +0 -0
  23. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/src/datasecops_cli/main.py +0 -0
  24. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/src/datasecops_cli/menus/__init__.py +0 -0
  25. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/src/datasecops_cli/menus/downloads.py +0 -0
  26. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/src/datasecops_cli/menus/git_operations.py +0 -0
  27. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/src/datasecops_cli/models/__init__.py +0 -0
  28. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/src/datasecops_cli/models/git_helpers.py +0 -0
  29. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/src/datasecops_cli/models/project_config.py +0 -0
  30. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/src/datasecops_cli/services/__init__.py +0 -0
  31. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/src/datasecops_cli/services/git_service.py +0 -0
  32. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/src/datasecops_cli/services/skill_service.py +0 -0
  33. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/src/datasecops_cli/services/snowflake_service.py +0 -0
  34. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/src/datasecops_cli/utilities/__init__.py +0 -0
  35. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/src/datasecops_cli/utilities/display.py +0 -0
  36. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/src/datasecops_cli/utilities/file_utils.py +0 -0
  37. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/src/datasecops_cli/utilities/yaml_utils.py +0 -0
  38. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/src/datasecops_mcp/__init__.py +0 -0
  39. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/src/datasecops_mcp/__main__.py +0 -0
  40. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/src/datasecops_mcp/connection.py +0 -0
  41. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/src/datasecops_mcp/server.py +0 -0
  42. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/tests/__init__.py +0 -0
  43. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/tests/test_config.py +0 -0
  44. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/tests/test_file_utils.py +0 -0
  45. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/tests/test_models.py +0 -0
  46. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/tests/test_version.py +0 -0
  47. {datasecops_cli-0.2.6 → datasecops_cli-0.2.7}/tests/test_yaml_utils.py +0 -0
@@ -2,6 +2,16 @@
2
2
 
3
3
  All notable changes to the DataSecOps CLI are documented in this file.
4
4
 
5
+ ## [0.2.7] - 2026-05-12
6
+
7
+ ### Changed
8
+
9
+ - **Separate SQLFluff and dbt installs** — `get_sqlfluff_requirements()` now returns only `sqlfluff` and `sqlfluff-templater-dbt`; new `get_dbt_requirements()` returns only `dbt-core` and `dbt-snowflake`. The lint menu install option and the new development menu install option each handle their own packages independently
10
+ - **Reorganised development menu** — added `[7] parse` for `dbt parse`, added `[14] install dbt` for explicit dbt-core/dbt-snowflake installation, removed standalone retry (available in run and test submenus)
11
+ - **Expanded test submenu** — added unit tests (`test_type:unit`), data tests (`test_type:data`), and failed test retry options alongside all tests and specific selector
12
+ - **Clearer input prompts** — run, test, and lint specific-file prompts now include examples of valid input (e.g. `my_model+`, `tag:nightly`, `models/staging/stg_orders.sql`)
13
+ - **Bootstrap shows separate install hints** — step 7 now lists SQLFluff and dbt requirements separately with individual `uv pip install` commands
14
+
5
15
  ## [0.2.6] - 2026-05-11
6
16
 
7
17
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datasecops-cli
3
- Version: 0.2.6
3
+ Version: 0.2.7
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.2.6"
7
+ version = "0.2.7"
8
8
  description = "DataSecOps Framework CLI for Snowflake Native App"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -48,33 +48,35 @@ class DevelopmentMenu:
48
48
  self.dbt.deps()
49
49
  complete_action()
50
50
  elif option == 7:
51
+ display_action_header("dbt parse")
52
+ self.dbt.parse()
53
+ complete_action()
54
+ elif option == 8:
51
55
  display_action_header("dbt compile")
52
56
  self.dbt.compile()
53
57
  complete_action()
54
- elif option == 8:
58
+ elif option == 9:
55
59
  display_action_header("dbt snapshot")
56
60
  self.dbt.snapshot()
57
61
  complete_action()
58
- elif option == 9:
62
+ elif option == 10:
59
63
  display_action_header("dbt source freshness")
60
64
  self.dbt.source_freshness()
61
65
  complete_action()
62
- elif option == 10:
66
+ elif option == 11:
63
67
  display_action_header("dbt clean")
64
68
  self.dbt.clean()
65
69
  complete_action()
66
- elif option == 11:
70
+ elif option == 12:
67
71
  display_action_header("dbt debug")
68
72
  self.dbt.debug()
69
73
  complete_action()
70
- elif option == 12:
74
+ elif option == 13:
71
75
  display_action_header("dbt list")
72
76
  self.dbt.list_models()
73
77
  complete_action()
74
- elif option == 13:
75
- display_action_header("dbt retry")
76
- self.dbt.retry()
77
- complete_action()
78
+ elif option == 14:
79
+ self._install_dbt_requirements()
78
80
  self._menu()
79
81
  option = get_input_number("Choose an option: ")
80
82
 
@@ -87,13 +89,14 @@ class DevelopmentMenu:
87
89
  menu_option(4, "docs - Generate & serve dbt docs")
88
90
  menu_option(5, "seed - Load seed data")
89
91
  menu_option(6, "deps - Install dbt packages")
90
- menu_option(7, "compile - Compile dbt models")
91
- menu_option(8, "snapshot - Run dbt snapshots")
92
- menu_option(9, "freshness - Check source freshness")
93
- menu_option(10, "clean - Clean dbt target")
94
- menu_option(11, "debug - Debug dbt connection")
95
- menu_option(12, "list - List dbt resources")
96
- menu_option(13, "retry - Retry failed dbt run")
92
+ menu_option(7, "parse - Parse dbt project")
93
+ menu_option(8, "compile - Compile dbt models")
94
+ menu_option(9, "snapshot - Run dbt snapshots")
95
+ menu_option(10, "freshness - Check source freshness")
96
+ menu_option(11, "clean - Clean dbt target")
97
+ menu_option(12, "debug - Debug dbt connection")
98
+ menu_option(13, "list - List dbt resources")
99
+ menu_option(14, "install dbt - Install dbt-core & dbt-snowflake from framework")
97
100
  menu_option(0, "back - Return to main menu")
98
101
 
99
102
  def _run_menu(self) -> None:
@@ -113,7 +116,7 @@ class DevelopmentMenu:
113
116
  full_refresh = get_input_true_false("Full refresh?", "n")
114
117
  self.dbt.run(modified_only=True, full_refresh=full_refresh)
115
118
  elif option == 3:
116
- select = get_input_string("Enter model selector: ")
119
+ select = get_input_string("Enter model selector (e.g. my_model, my_model+, tag:nightly, path:models/...): ")
117
120
  if select != "0":
118
121
  self.dbt.run(select=select)
119
122
  elif option == 4:
@@ -129,13 +132,22 @@ class DevelopmentMenu:
129
132
  clear()
130
133
  display_action_header("dbt Test Options")
131
134
  menu_option(1, "all tests - Run all tests")
132
- menu_option(2, "specific - Run specific test(s)")
135
+ menu_option(2, "unit tests - Run unit tests only")
136
+ menu_option(3, "data tests - Run data tests only")
137
+ menu_option(4, "failed - Retry failed tests")
138
+ menu_option(5, "specific - Run specific test(s)")
133
139
  menu_option(0, "back - Return to development menu")
134
140
  option = get_input_number("Choose an option: ")
135
141
  if option == 1:
136
142
  self.dbt.test()
137
143
  elif option == 2:
138
- select = get_input_string("Enter test selector: ")
144
+ self.dbt.test(select="test_type:unit")
145
+ elif option == 3:
146
+ self.dbt.test(select="test_type:data")
147
+ elif option == 4:
148
+ self.dbt.retry()
149
+ elif option == 5:
150
+ select = get_input_string("Enter test selector (e.g. test_name, model_name, tag:nightly): ")
139
151
  if select != "0":
140
152
  self.dbt.test(select=select)
141
153
  complete_action()
@@ -179,7 +191,7 @@ class DevelopmentMenu:
179
191
  elif option == 4:
180
192
  self.linting.lint_file(fix=True)
181
193
  elif option == 5:
182
- path = get_input_string("Enter file path: ")
194
+ path = get_input_string("Enter SQL file path (e.g. models/staging/stg_orders.sql): ")
183
195
  if path != "0":
184
196
  self.linting.lint_file(file_path=path, fix=False)
185
197
  elif option == 6:
@@ -192,11 +204,11 @@ class DevelopmentMenu:
192
204
  error_line("Download service not available")
193
205
  return
194
206
 
195
- # Show current versions
207
+ # Show current sqlfluff versions
196
208
  installed = self.linting.get_installed_versions()
197
209
  info_line("Currently installed:")
198
- for pkg, ver in installed.items():
199
- info_line(f" {pkg}: {ver or 'not installed'}")
210
+ for pkg in ("sqlfluff", "sqlfluff-templater-dbt"):
211
+ info_line(f" {pkg}: {installed.get(pkg) or 'not installed'}")
200
212
 
201
213
  # Fetch required versions from framework
202
214
  packages = self.downloads.get_sqlfluff_requirements()
@@ -210,3 +222,32 @@ class DevelopmentMenu:
210
222
 
211
223
  info_line("")
212
224
  self.linting.install_requirements(packages)
225
+
226
+ def _install_dbt_requirements(self) -> None:
227
+ """Install dbt-core and dbt-snowflake at versions defined by the framework."""
228
+ display_action_header("Install dbt Requirements")
229
+ if not self.downloads:
230
+ error_line("Download service not available")
231
+ complete_action()
232
+ return
233
+
234
+ # Show current dbt versions
235
+ installed = self.linting.get_installed_versions()
236
+ info_line("Currently installed:")
237
+ for pkg in ("dbt-core", "dbt-snowflake"):
238
+ info_line(f" {pkg}: {installed.get(pkg) or 'not installed'}")
239
+
240
+ # Fetch required dbt versions from framework
241
+ packages = self.downloads.get_dbt_requirements()
242
+ if not packages:
243
+ complete_action()
244
+ return
245
+
246
+ info_line("")
247
+ info_line("Framework requires:")
248
+ for pkg in packages:
249
+ info_line(f" {pkg}")
250
+
251
+ info_line("")
252
+ self.linting.install_requirements(packages)
253
+ complete_action()
@@ -109,14 +109,18 @@ class BootstrapService:
109
109
  info_line(" (dbt deps failed - run manually after fixing packages.yml)")
110
110
  steps_passed += 1
111
111
 
112
- # Step 7: Download SQLFluff requirements
112
+ # Step 7: Check SQLFluff & dbt version requirements
113
113
  info_line("")
114
114
  step_num = 7 if run_deps else 6
115
- info_line(f"[{step_num}] Checking SQLFluff version requirements...")
116
- requirements = self.download_service.get_sqlfluff_requirements()
117
- if requirements:
118
- info_line(f" Required: {', '.join(requirements)}")
119
- info_line(" (install with: uv pip install " + " ".join(requirements) + ")")
115
+ info_line(f"[{step_num}] Checking SQLFluff & dbt version requirements...")
116
+ sqlfluff_requirements = self.download_service.get_sqlfluff_requirements()
117
+ if sqlfluff_requirements:
118
+ info_line(f" SQLFluff: {', '.join(sqlfluff_requirements)}")
119
+ info_line(" (install with: uv pip install " + " ".join(sqlfluff_requirements) + ")")
120
+ dbt_requirements = self.download_service.get_dbt_requirements()
121
+ if dbt_requirements:
122
+ info_line(f" dbt: {', '.join(dbt_requirements)}")
123
+ info_line(" (install with: uv pip install " + " ".join(dbt_requirements) + ")")
120
124
  steps_passed += 1
121
125
 
122
126
  # Step 8: Install Cortex Code skills
@@ -117,6 +117,9 @@ class DbtRunner:
117
117
  self._copy_manifest()
118
118
  return result
119
119
 
120
+ def parse(self) -> subprocess.CompletedProcess:
121
+ return self._run_command("parse")
122
+
120
123
  def run_operation(self, macro: str, args_str: str = None) -> subprocess.CompletedProcess:
121
124
  cmd_args = [f"--target={self.target}"]
122
125
  if args_str:
@@ -168,17 +168,16 @@ class DownloadService:
168
168
  success_line(f"SQLFluff config written to {dest}")
169
169
  return True
170
170
 
171
- def get_sqlfluff_requirements(self) -> list[str]:
172
- """Fetch active sqlfluff and dbt package versions from the framework."""
173
- info_line("Fetching SQLFluff requirements from framework...")
171
+ def _fetch_framework_versions(self) -> dict[str, str]:
172
+ """Fetch all active package versions from the framework's DBT_VERSIONS config.
173
+
174
+ Returns a dict mapping pip package name to pinned spec, e.g. {"dbt-core": "dbt-core==1.9.0", ...}.
175
+ """
174
176
  raw = self.sf.get_framework_config("DBT_VERSIONS")
175
177
  if not raw:
176
178
  error_line("No DBT_VERSIONS configuration found in native app")
177
- return []
178
-
179
- packages = []
179
+ return {}
180
180
 
181
- # Map of config key -> pip package name
182
181
  version_keys = {
183
182
  "sqlfluff_versions": "sqlfluff",
184
183
  "sqlfluff_templater_versions": "sqlfluff-templater-dbt",
@@ -186,16 +185,35 @@ class DownloadService:
186
185
  "dbt_snowflake_versions": "dbt-snowflake",
187
186
  }
188
187
 
188
+ result: dict[str, str] = {}
189
189
  for config_key, pkg_name in version_keys.items():
190
190
  for entry in raw.get(config_key, []):
191
191
  if entry.get("active"):
192
- packages.append(f"{pkg_name}=={entry['version']}")
192
+ result[pkg_name] = f"{pkg_name}=={entry['version']}"
193
193
  break
194
194
 
195
- if not packages:
195
+ if not result:
196
196
  error_line("No active versions found in framework configuration")
197
197
 
198
- return packages
198
+ return result
199
+
200
+ def get_sqlfluff_requirements(self) -> list[str]:
201
+ """Fetch active sqlfluff and sqlfluff-templater-dbt versions from the framework."""
202
+ info_line("Fetching SQLFluff requirements from framework...")
203
+ versions = self._fetch_framework_versions()
204
+ sqlfluff_packages = [versions[k] for k in ("sqlfluff", "sqlfluff-templater-dbt") if k in versions]
205
+ if not sqlfluff_packages:
206
+ error_line("No active SQLFluff versions found in framework configuration")
207
+ return sqlfluff_packages
208
+
209
+ def get_dbt_requirements(self) -> list[str]:
210
+ """Fetch active dbt-core and dbt-snowflake versions from the framework."""
211
+ info_line("Fetching dbt requirements from framework...")
212
+ versions = self._fetch_framework_versions()
213
+ dbt_packages = [versions[k] for k in ("dbt-core", "dbt-snowflake") if k in versions]
214
+ if not dbt_packages:
215
+ error_line("No active dbt versions found in framework configuration")
216
+ return dbt_packages
199
217
 
200
218
  def download_pipelines(self, platform: str = "github") -> bool:
201
219
  info_line(f"Downloading {platform} pipeline configurations...")
@@ -10,10 +10,16 @@ class LintingService:
10
10
  def __init__(self, project_dir: Path):
11
11
  self.project_dir = project_dir
12
12
 
13
- def get_installed_versions(self) -> dict[str, str]:
14
- """Get currently installed versions of sqlfluff packages."""
13
+ def get_installed_versions(self, packages: list[str] = None) -> dict[str, str]:
14
+ """Get currently installed versions of the specified packages.
15
+
16
+ Args:
17
+ packages: Package names to check. Defaults to sqlfluff, sqlfluff-templater-dbt, dbt-core, dbt-snowflake.
18
+ """
19
+ if packages is None:
20
+ packages = ["sqlfluff", "sqlfluff-templater-dbt", "dbt-core", "dbt-snowflake"]
15
21
  versions = {}
16
- for package in ["sqlfluff", "sqlfluff-templater-dbt", "dbt-core", "dbt-snowflake"]:
22
+ for package in packages:
17
23
  result = subprocess.run(
18
24
  ["uv", "pip", "show", package],
19
25
  capture_output=True, text=True
@@ -28,7 +34,7 @@ class LintingService:
28
34
  return versions
29
35
 
30
36
  def install_requirements(self, packages: list[str]) -> bool:
31
- """Install sqlfluff packages at specified versions."""
37
+ """Install packages at specified versions via uv pip."""
32
38
  if not packages:
33
39
  warning_line("No packages to install")
34
40
  return False
@@ -37,7 +43,7 @@ class LintingService:
37
43
  info_line(f"Installing: {', '.join(packages)}")
38
44
  result = subprocess.run(cmd, capture_output=False)
39
45
  if result.returncode == 0:
40
- success_line("SQLFluff requirements installed successfully")
46
+ success_line("Packages installed successfully")
41
47
  return True
42
48
  else:
43
49
  error_line(f"uv pip install failed with exit code {result.returncode}")
@@ -75,8 +81,8 @@ class LintingService:
75
81
  return True
76
82
  return self.install_requirements(to_install)
77
83
 
78
- # No pinned versions — just ensure packages are present
79
- missing = [pkg for pkg in ["sqlfluff", "sqlfluff-templater-dbt", "dbt-core", "dbt-snowflake"] if not installed.get(pkg)]
84
+ # No pinned versions — just ensure sqlfluff packages are present
85
+ missing = [pkg for pkg in ["sqlfluff", "sqlfluff-templater-dbt"] if not installed.get(pkg)]
80
86
  if not missing:
81
87
  return True
82
88
  warning_line(f"Missing packages: {', '.join(missing)}")
File without changes
File without changes
File without changes
File without changes