datasecops-cli 0.2.8__tar.gz → 0.2.9__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 (48) hide show
  1. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/CHANGELOG.md +6 -0
  2. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/PKG-INFO +10 -3
  3. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/README.md +9 -2
  4. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/pyproject.toml +1 -1
  5. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/src/datasecops_cli/main.py +22 -3
  6. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/tests/test_main.py +103 -8
  7. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/.github/workflows/publish-cli.yml +0 -0
  8. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/.gitignore +0 -0
  9. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/DEVELOPMENT.md +0 -0
  10. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/LICENSE +0 -0
  11. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/docs/getting-started.md +0 -0
  12. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/docs/legacy.md +0 -0
  13. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/docs/legacy_plan_of_action.md +0 -0
  14. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/docs/mcp-server.md +0 -0
  15. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/mcp-servers.json +0 -0
  16. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/setup.ps1 +0 -0
  17. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/setup.sh +0 -0
  18. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/src/datasecops_cli/__init__.py +0 -0
  19. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/src/datasecops_cli/config.py +0 -0
  20. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/src/datasecops_cli/menus/__init__.py +0 -0
  21. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/src/datasecops_cli/menus/development.py +0 -0
  22. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/src/datasecops_cli/menus/downloads.py +0 -0
  23. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/src/datasecops_cli/menus/git_operations.py +0 -0
  24. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/src/datasecops_cli/models/__init__.py +0 -0
  25. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/src/datasecops_cli/models/git_helpers.py +0 -0
  26. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/src/datasecops_cli/models/project_config.py +0 -0
  27. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/src/datasecops_cli/services/__init__.py +0 -0
  28. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/src/datasecops_cli/services/bootstrap_service.py +0 -0
  29. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/src/datasecops_cli/services/dbt_runner.py +0 -0
  30. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/src/datasecops_cli/services/download_service.py +0 -0
  31. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/src/datasecops_cli/services/git_service.py +0 -0
  32. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/src/datasecops_cli/services/linting_service.py +0 -0
  33. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/src/datasecops_cli/services/skill_service.py +0 -0
  34. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/src/datasecops_cli/services/snowflake_service.py +0 -0
  35. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/src/datasecops_cli/utilities/__init__.py +0 -0
  36. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/src/datasecops_cli/utilities/display.py +0 -0
  37. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/src/datasecops_cli/utilities/file_utils.py +0 -0
  38. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/src/datasecops_cli/utilities/yaml_utils.py +0 -0
  39. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/src/datasecops_mcp/__init__.py +0 -0
  40. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/src/datasecops_mcp/__main__.py +0 -0
  41. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/src/datasecops_mcp/connection.py +0 -0
  42. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/src/datasecops_mcp/server.py +0 -0
  43. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/tests/__init__.py +0 -0
  44. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/tests/test_config.py +0 -0
  45. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/tests/test_file_utils.py +0 -0
  46. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/tests/test_models.py +0 -0
  47. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/tests/test_version.py +0 -0
  48. {datasecops_cli-0.2.8 → datasecops_cli-0.2.9}/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.2.9] - 2026-05-14
6
+
7
+ ### Added
8
+
9
+ - **`install-sqlfluff` and `install-dbt` download items** — `datasecops download install-sqlfluff` and `datasecops download install-dbt` fetch framework-pinned package versions from the native app and install them via `uv pip install`. `datasecops download all` now includes both installs.
10
+
5
11
  ## [0.2.8] - 2026-05-14
6
12
 
7
13
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datasecops-cli
3
- Version: 0.2.8
3
+ Version: 0.2.9
4
4
  Summary: DataSecOps Framework CLI for Snowflake Native App
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -106,11 +106,18 @@ datasecops download sqlfluff
106
106
  datasecops download sqlfluff packages
107
107
  datasecops download pipelines macros
108
108
 
109
- # Download everything
109
+ # Install framework-pinned package versions
110
+ datasecops download install-sqlfluff
111
+ datasecops download install-dbt
112
+
113
+ # Download config and install packages together
114
+ datasecops download sqlfluff install-sqlfluff
115
+
116
+ # Download and install everything
110
117
  datasecops download all
111
118
  ```
112
119
 
113
- Available items: `sqlfluff`, `pipelines`, `packages`, `macros`, `all`
120
+ Available items: `sqlfluff`, `pipelines`, `packages`, `macros`, `install-sqlfluff`, `install-dbt`, `all`
114
121
 
115
122
  The pipeline platform (GitHub / Azure DevOps) is auto-detected from the native app's source control configuration.
116
123
 
@@ -86,11 +86,18 @@ datasecops download sqlfluff
86
86
  datasecops download sqlfluff packages
87
87
  datasecops download pipelines macros
88
88
 
89
- # Download everything
89
+ # Install framework-pinned package versions
90
+ datasecops download install-sqlfluff
91
+ datasecops download install-dbt
92
+
93
+ # Download config and install packages together
94
+ datasecops download sqlfluff install-sqlfluff
95
+
96
+ # Download and install everything
90
97
  datasecops download all
91
98
  ```
92
99
 
93
- Available items: `sqlfluff`, `pipelines`, `packages`, `macros`, `all`
100
+ Available items: `sqlfluff`, `pipelines`, `packages`, `macros`, `install-sqlfluff`, `install-dbt`, `all`
94
101
 
95
102
  The pipeline platform (GitHub / Azure DevOps) is auto-detected from the native app's source control configuration.
96
103
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "datasecops-cli"
7
- version = "0.2.8"
7
+ version = "0.2.9"
8
8
  description = "DataSecOps Framework CLI for Snowflake Native App"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -20,7 +20,10 @@ from datasecops_cli.utilities.display import (
20
20
  )
21
21
 
22
22
 
23
- DOWNLOAD_ITEMS = ["sqlfluff", "pipelines", "packages", "macros", "all"]
23
+ DOWNLOAD_ITEMS = [
24
+ "sqlfluff", "pipelines", "packages", "macros",
25
+ "install-sqlfluff", "install-dbt", "all",
26
+ ]
24
27
 
25
28
 
26
29
  def _build_parser() -> argparse.ArgumentParser:
@@ -37,7 +40,7 @@ def _build_parser() -> argparse.ArgumentParser:
37
40
  "items",
38
41
  nargs="+",
39
42
  choices=DOWNLOAD_ITEMS,
40
- help="Item(s) to download: sqlfluff, pipelines, packages, macros, or all",
43
+ help="Item(s) to download/install: sqlfluff, pipelines, packages, macros, install-sqlfluff, install-dbt, or all",
41
44
  )
42
45
 
43
46
  return parser
@@ -129,10 +132,12 @@ def _run_download(config: Config, items: list[str]):
129
132
 
130
133
  try:
131
134
  download_service = DownloadService(sf_service, config.project_dir)
135
+ linting_service = LintingService(config.dbt_project_dir)
132
136
  profiles_dir = str(config.get_dbt_profiles_dir())
133
137
 
134
138
  if "all" in items:
135
- items = ["sqlfluff", "pipelines", "packages", "macros"]
139
+ items = ["sqlfluff", "pipelines", "packages", "macros",
140
+ "install-sqlfluff", "install-dbt"]
136
141
 
137
142
  failed = False
138
143
  for item in items:
@@ -153,6 +158,20 @@ def _run_download(config: Config, items: list[str]):
153
158
  elif item == "macros":
154
159
  if not download_service.download_macros(config.profile_name, config.dbt_project_dir):
155
160
  failed = True
161
+ elif item == "install-sqlfluff":
162
+ packages = download_service.get_sqlfluff_requirements()
163
+ if packages:
164
+ if not linting_service.install_requirements(packages):
165
+ failed = True
166
+ else:
167
+ failed = True
168
+ elif item == "install-dbt":
169
+ packages = download_service.get_dbt_requirements()
170
+ if packages:
171
+ if not linting_service.install_requirements(packages):
172
+ failed = True
173
+ else:
174
+ failed = True
156
175
 
157
176
  sys.exit(1 if failed else 0)
158
177
  finally:
@@ -73,7 +73,8 @@ class TestRunDownload:
73
73
  mock_sf = MagicMock()
74
74
  mock_connect.return_value = mock_sf
75
75
 
76
- with patch("datasecops_cli.main.DownloadService") as MockDS:
76
+ with patch("datasecops_cli.main.DownloadService") as MockDS, \
77
+ patch("datasecops_cli.main.LintingService"):
77
78
  mock_ds = MockDS.return_value
78
79
  mock_ds.download_sqlfluff_config.return_value = True
79
80
 
@@ -92,7 +93,8 @@ class TestRunDownload:
92
93
  mock_sf = MagicMock()
93
94
  mock_connect.return_value = mock_sf
94
95
 
95
- with patch("datasecops_cli.main.DownloadService") as MockDS:
96
+ with patch("datasecops_cli.main.DownloadService") as MockDS, \
97
+ patch("datasecops_cli.main.LintingService"):
96
98
  mock_ds = MockDS.return_value
97
99
  mock_ds.download_pipelines.return_value = True
98
100
 
@@ -108,7 +110,8 @@ class TestRunDownload:
108
110
  mock_sf = MagicMock()
109
111
  mock_connect.return_value = mock_sf
110
112
 
111
- with patch("datasecops_cli.main.DownloadService") as MockDS:
113
+ with patch("datasecops_cli.main.DownloadService") as MockDS, \
114
+ patch("datasecops_cli.main.LintingService"):
112
115
  mock_ds = MockDS.return_value
113
116
  mock_ds.download_dbt_packages.return_value = True
114
117
 
@@ -124,7 +127,8 @@ class TestRunDownload:
124
127
  mock_sf = MagicMock()
125
128
  mock_connect.return_value = mock_sf
126
129
 
127
- with patch("datasecops_cli.main.DownloadService") as MockDS:
130
+ with patch("datasecops_cli.main.DownloadService") as MockDS, \
131
+ patch("datasecops_cli.main.LintingService"):
128
132
  mock_ds = MockDS.return_value
129
133
  mock_ds.download_macros.return_value = True
130
134
 
@@ -140,12 +144,17 @@ class TestRunDownload:
140
144
  mock_sf = MagicMock()
141
145
  mock_connect.return_value = mock_sf
142
146
 
143
- with patch("datasecops_cli.main.DownloadService") as MockDS:
147
+ with patch("datasecops_cli.main.DownloadService") as MockDS, \
148
+ patch("datasecops_cli.main.LintingService") as MockLS:
144
149
  mock_ds = MockDS.return_value
150
+ mock_ls = MockLS.return_value
145
151
  mock_ds.download_sqlfluff_config.return_value = True
146
152
  mock_ds.download_pipelines.return_value = True
147
153
  mock_ds.download_dbt_packages.return_value = True
148
154
  mock_ds.download_macros.return_value = True
155
+ mock_ds.get_sqlfluff_requirements.return_value = ["sqlfluff==3.4.0"]
156
+ mock_ds.get_dbt_requirements.return_value = ["dbt-core==1.9.0"]
157
+ mock_ls.install_requirements.return_value = True
149
158
 
150
159
  with pytest.raises(SystemExit) as exc:
151
160
  _run_download(config, ["all"])
@@ -155,6 +164,9 @@ class TestRunDownload:
155
164
  mock_ds.download_pipelines.assert_called_once()
156
165
  mock_ds.download_dbt_packages.assert_called_once()
157
166
  mock_ds.download_macros.assert_called_once()
167
+ mock_ds.get_sqlfluff_requirements.assert_called_once()
168
+ mock_ds.get_dbt_requirements.assert_called_once()
169
+ assert mock_ls.install_requirements.call_count == 2
158
170
 
159
171
  @patch("datasecops_cli.main._connect_and_load")
160
172
  def test_download_failure_exits_1(self, mock_connect, tmp_path):
@@ -162,7 +174,8 @@ class TestRunDownload:
162
174
  mock_sf = MagicMock()
163
175
  mock_connect.return_value = mock_sf
164
176
 
165
- with patch("datasecops_cli.main.DownloadService") as MockDS:
177
+ with patch("datasecops_cli.main.DownloadService") as MockDS, \
178
+ patch("datasecops_cli.main.LintingService"):
166
179
  mock_ds = MockDS.return_value
167
180
  mock_ds.download_sqlfluff_config.return_value = False # failure
168
181
 
@@ -178,7 +191,8 @@ class TestRunDownload:
178
191
  mock_sf = MagicMock()
179
192
  mock_connect.return_value = mock_sf
180
193
 
181
- with patch("datasecops_cli.main.DownloadService") as MockDS:
194
+ with patch("datasecops_cli.main.DownloadService") as MockDS, \
195
+ patch("datasecops_cli.main.LintingService"):
182
196
  mock_ds = MockDS.return_value
183
197
  mock_ds.download_sqlfluff_config.return_value = True
184
198
  mock_ds.download_dbt_packages.return_value = False
@@ -196,7 +210,8 @@ class TestRunDownload:
196
210
  mock_sf = MagicMock()
197
211
  mock_connect.return_value = mock_sf
198
212
 
199
- with patch("datasecops_cli.main.DownloadService") as MockDS:
213
+ with patch("datasecops_cli.main.DownloadService") as MockDS, \
214
+ patch("datasecops_cli.main.LintingService"):
200
215
  mock_ds = MockDS.return_value
201
216
  mock_ds.download_pipelines.return_value = True
202
217
 
@@ -205,3 +220,83 @@ class TestRunDownload:
205
220
 
206
221
  assert exc.value.code == 0
207
222
  mock_ds.download_pipelines.assert_called_once_with(platform="azuredevops")
223
+
224
+ @patch("datasecops_cli.main._connect_and_load")
225
+ def test_install_sqlfluff(self, mock_connect, tmp_path):
226
+ """install-sqlfluff fetches pinned versions and installs them."""
227
+ config = self._make_config(tmp_path)
228
+ mock_connect.return_value = MagicMock()
229
+
230
+ with patch("datasecops_cli.main.DownloadService") as MockDS, \
231
+ patch("datasecops_cli.main.LintingService") as MockLS:
232
+ mock_ds = MockDS.return_value
233
+ mock_ls = MockLS.return_value
234
+ mock_ds.get_sqlfluff_requirements.return_value = ["sqlfluff==3.4.0", "sqlfluff-templater-dbt==3.4.0"]
235
+ mock_ls.install_requirements.return_value = True
236
+
237
+ with pytest.raises(SystemExit) as exc:
238
+ _run_download(config, ["install-sqlfluff"])
239
+
240
+ assert exc.value.code == 0
241
+ mock_ds.get_sqlfluff_requirements.assert_called_once()
242
+ mock_ls.install_requirements.assert_called_once_with(
243
+ ["sqlfluff==3.4.0", "sqlfluff-templater-dbt==3.4.0"]
244
+ )
245
+
246
+ @patch("datasecops_cli.main._connect_and_load")
247
+ def test_install_dbt(self, mock_connect, tmp_path):
248
+ """install-dbt fetches pinned versions and installs them."""
249
+ config = self._make_config(tmp_path)
250
+ mock_connect.return_value = MagicMock()
251
+
252
+ with patch("datasecops_cli.main.DownloadService") as MockDS, \
253
+ patch("datasecops_cli.main.LintingService") as MockLS:
254
+ mock_ds = MockDS.return_value
255
+ mock_ls = MockLS.return_value
256
+ mock_ds.get_dbt_requirements.return_value = ["dbt-core==1.9.0", "dbt-snowflake==1.9.0"]
257
+ mock_ls.install_requirements.return_value = True
258
+
259
+ with pytest.raises(SystemExit) as exc:
260
+ _run_download(config, ["install-dbt"])
261
+
262
+ assert exc.value.code == 0
263
+ mock_ds.get_dbt_requirements.assert_called_once()
264
+ mock_ls.install_requirements.assert_called_once_with(
265
+ ["dbt-core==1.9.0", "dbt-snowflake==1.9.0"]
266
+ )
267
+
268
+ @patch("datasecops_cli.main._connect_and_load")
269
+ def test_install_sqlfluff_no_versions_exits_1(self, mock_connect, tmp_path):
270
+ """install-sqlfluff fails if no versions returned from framework."""
271
+ config = self._make_config(tmp_path)
272
+ mock_connect.return_value = MagicMock()
273
+
274
+ with patch("datasecops_cli.main.DownloadService") as MockDS, \
275
+ patch("datasecops_cli.main.LintingService") as MockLS:
276
+ mock_ds = MockDS.return_value
277
+ mock_ls = MockLS.return_value
278
+ mock_ds.get_sqlfluff_requirements.return_value = []
279
+
280
+ with pytest.raises(SystemExit) as exc:
281
+ _run_download(config, ["install-sqlfluff"])
282
+
283
+ assert exc.value.code == 1
284
+ mock_ls.install_requirements.assert_not_called()
285
+
286
+ @patch("datasecops_cli.main._connect_and_load")
287
+ def test_install_dbt_pip_failure_exits_1(self, mock_connect, tmp_path):
288
+ """install-dbt exits 1 if uv pip install fails."""
289
+ config = self._make_config(tmp_path)
290
+ mock_connect.return_value = MagicMock()
291
+
292
+ with patch("datasecops_cli.main.DownloadService") as MockDS, \
293
+ patch("datasecops_cli.main.LintingService") as MockLS:
294
+ mock_ds = MockDS.return_value
295
+ mock_ls = MockLS.return_value
296
+ mock_ds.get_dbt_requirements.return_value = ["dbt-core==1.9.0"]
297
+ mock_ls.install_requirements.return_value = False # pip failure
298
+
299
+ with pytest.raises(SystemExit) as exc:
300
+ _run_download(config, ["install-dbt"])
301
+
302
+ assert exc.value.code == 1
File without changes
File without changes
File without changes