datasecops-cli 0.5.2__tar.gz → 0.5.3__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.5.2 → datasecops_cli-0.5.3}/CHANGELOG.md +8 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/PKG-INFO +1 -1
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/pyproject.toml +1 -1
- datasecops_cli-0.5.3/src/datasecops_cli/__init__.py +1 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_mcp/server.py +62 -12
- datasecops_cli-0.5.2/src/datasecops_cli/__init__.py +0 -1
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/.github/workflows/auto-tag.yml +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/.github/workflows/publish-cli.yml +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/.github/workflows/test.yml +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/.gitignore +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/DEVELOPMENT.md +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/LICENSE +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/README.md +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/docs/getting-started.md +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/docs/git-operations.md +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/docs/legacy.md +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/docs/legacy_plan_of_action.md +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/docs/mcp-server.md +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/mcp-servers.json +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/setup.ps1 +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/setup.sh +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/config.py +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/main.py +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/menus/__init__.py +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/menus/configuration.py +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/menus/development.py +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/menus/downloads.py +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/menus/git_operations.py +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/models/__init__.py +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/models/git_helpers.py +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/models/project_config.py +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/services/__init__.py +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/services/bootstrap_service.py +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/services/dbt_project_generator.py +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/services/dbt_runner.py +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/services/directory_scaffolder.py +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/services/download_service.py +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/services/git_service.py +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/services/linting_service.py +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/services/skill_service.py +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/services/snowflake_service.py +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/services/upstream_service.py +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/utilities/__init__.py +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/utilities/display.py +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/utilities/file_utils.py +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/utilities/mcp.py +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/utilities/yaml_utils.py +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_mcp/__init__.py +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_mcp/__main__.py +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_mcp/connection.py +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/tests/__init__.py +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/tests/test_config.py +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/tests/test_file_utils.py +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/tests/test_main.py +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/tests/test_models.py +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/tests/test_version.py +0 -0
- {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/tests/test_yaml_utils.py +0 -0
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the DataSecOps CLI are documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.5.3] - 2026-06-05
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- **MCP linting tools report progress** — `lint_sql`, `fix_sql`, and `lint_project` now use MCP `Context` to send progress notifications and info messages during execution, so callers can see what stage the tool is at (project discovery, running SQLFluff, processing results).
|
|
10
|
+
- **MCP linting tools no longer block the event loop** — subprocess calls to SQLFluff are wrapped in `asyncio.to_thread` so the MCP server remains responsive while linting runs.
|
|
11
|
+
- **`_ensure_dbt_packages` skips `dbt deps` when packages exist** — `dbt deps` is only triggered when `dbt_packages/` is actually empty, but the `dbt-snowflake` Python package check always runs to ensure the dbt templater works.
|
|
12
|
+
|
|
5
13
|
## [0.5.2] - 2026-06-05
|
|
6
14
|
|
|
7
15
|
### Added
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.5.3"
|
|
@@ -8,7 +8,7 @@ dbt packages, project profiles, and deployment settings in real-time.
|
|
|
8
8
|
import json
|
|
9
9
|
import logging
|
|
10
10
|
|
|
11
|
-
from mcp.server.fastmcp import FastMCP
|
|
11
|
+
from mcp.server.fastmcp import Context, FastMCP
|
|
12
12
|
|
|
13
13
|
from datasecops_mcp.connection import get_snowflake_service
|
|
14
14
|
from datasecops_cli.models.project_config import DEFAULT_BRANCH_FORMAT
|
|
@@ -429,7 +429,7 @@ def get_deployment_workflow() -> str:
|
|
|
429
429
|
|
|
430
430
|
|
|
431
431
|
@mcp.tool()
|
|
432
|
-
def lint_sql(file_path: str) -> str:
|
|
432
|
+
async def lint_sql(file_path: str, ctx: Context) -> str:
|
|
433
433
|
"""Lint a SQL file using SQLFluff with the project's configured rules.
|
|
434
434
|
|
|
435
435
|
Args:
|
|
@@ -438,11 +438,15 @@ def lint_sql(file_path: str) -> str:
|
|
|
438
438
|
Returns the linting results including rule violations, line numbers,
|
|
439
439
|
and descriptions. Uses the project's .sqlfluff config from the framework.
|
|
440
440
|
"""
|
|
441
|
+
import asyncio
|
|
441
442
|
import subprocess
|
|
442
443
|
from pathlib import Path
|
|
443
444
|
|
|
445
|
+
await ctx.report_progress(0, 3)
|
|
446
|
+
await ctx.info(f"Finding dbt project for: {file_path}")
|
|
444
447
|
project_dir = _find_project_dir()
|
|
445
448
|
_ensure_dbt_packages(project_dir)
|
|
449
|
+
|
|
446
450
|
target = Path(file_path)
|
|
447
451
|
if not target.is_absolute():
|
|
448
452
|
target = project_dir / target
|
|
@@ -450,12 +454,20 @@ def lint_sql(file_path: str) -> str:
|
|
|
450
454
|
if not target.exists():
|
|
451
455
|
return json.dumps({"error": f"File not found: {target}"})
|
|
452
456
|
|
|
457
|
+
await ctx.report_progress(1, 3)
|
|
458
|
+
await ctx.info(f"Running SQLFluff lint on {target.name}...")
|
|
459
|
+
|
|
453
460
|
cmd = ["sqlfluff", "lint", str(target), "--format", "json"]
|
|
454
461
|
config_path = project_dir / ".sqlfluff"
|
|
455
462
|
if config_path.exists():
|
|
456
463
|
cmd += ["--config", str(config_path)]
|
|
457
464
|
|
|
458
|
-
result =
|
|
465
|
+
result = await asyncio.to_thread(
|
|
466
|
+
subprocess.run, cmd, capture_output=True, text=True, cwd=str(project_dir)
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
await ctx.report_progress(2, 3)
|
|
470
|
+
await ctx.info("Processing lint results...")
|
|
459
471
|
|
|
460
472
|
# SQLFluff returns exit code 1 when violations are found (not an error)
|
|
461
473
|
if result.returncode > 1:
|
|
@@ -464,14 +476,17 @@ def lint_sql(file_path: str) -> str:
|
|
|
464
476
|
if result.stdout.strip():
|
|
465
477
|
try:
|
|
466
478
|
violations = json.loads(result.stdout)
|
|
479
|
+
await ctx.report_progress(3, 3)
|
|
467
480
|
return json.dumps(violations, indent=2)
|
|
468
481
|
except json.JSONDecodeError:
|
|
482
|
+
await ctx.report_progress(3, 3)
|
|
469
483
|
return result.stdout
|
|
484
|
+
await ctx.report_progress(3, 3)
|
|
470
485
|
return json.dumps({"status": "clean", "message": "No linting issues found"})
|
|
471
486
|
|
|
472
487
|
|
|
473
488
|
@mcp.tool()
|
|
474
|
-
def fix_sql(file_path: str) -> str:
|
|
489
|
+
async def fix_sql(file_path: str, ctx: Context) -> str:
|
|
475
490
|
"""Auto-fix linting issues in a SQL file using SQLFluff.
|
|
476
491
|
|
|
477
492
|
Args:
|
|
@@ -480,11 +495,15 @@ def fix_sql(file_path: str) -> str:
|
|
|
480
495
|
Applies automatic fixes using the project's .sqlfluff config.
|
|
481
496
|
Returns the fix results and what was changed.
|
|
482
497
|
"""
|
|
498
|
+
import asyncio
|
|
483
499
|
import subprocess
|
|
484
500
|
from pathlib import Path
|
|
485
501
|
|
|
502
|
+
await ctx.report_progress(0, 3)
|
|
503
|
+
await ctx.info(f"Finding dbt project for: {file_path}")
|
|
486
504
|
project_dir = _find_project_dir()
|
|
487
505
|
_ensure_dbt_packages(project_dir)
|
|
506
|
+
|
|
488
507
|
target = Path(file_path)
|
|
489
508
|
if not target.is_absolute():
|
|
490
509
|
target = project_dir / target
|
|
@@ -492,12 +511,20 @@ def fix_sql(file_path: str) -> str:
|
|
|
492
511
|
if not target.exists():
|
|
493
512
|
return json.dumps({"error": f"File not found: {target}"})
|
|
494
513
|
|
|
514
|
+
await ctx.report_progress(1, 3)
|
|
515
|
+
await ctx.info(f"Running SQLFluff fix on {target.name}...")
|
|
516
|
+
|
|
495
517
|
cmd = ["sqlfluff", "fix", str(target), "--force", "--format", "json"]
|
|
496
518
|
config_path = project_dir / ".sqlfluff"
|
|
497
519
|
if config_path.exists():
|
|
498
520
|
cmd += ["--config", str(config_path)]
|
|
499
521
|
|
|
500
|
-
result =
|
|
522
|
+
result = await asyncio.to_thread(
|
|
523
|
+
subprocess.run, cmd, capture_output=True, text=True, cwd=str(project_dir)
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
await ctx.report_progress(2, 3)
|
|
527
|
+
await ctx.info("Processing fix results...")
|
|
501
528
|
|
|
502
529
|
if result.returncode > 1:
|
|
503
530
|
return json.dumps({"error": f"SQLFluff fix failed: {result.stderr.strip()}"})
|
|
@@ -505,14 +532,17 @@ def fix_sql(file_path: str) -> str:
|
|
|
505
532
|
if result.stdout.strip():
|
|
506
533
|
try:
|
|
507
534
|
fix_results = json.loads(result.stdout)
|
|
535
|
+
await ctx.report_progress(3, 3)
|
|
508
536
|
return json.dumps(fix_results, indent=2)
|
|
509
537
|
except json.JSONDecodeError:
|
|
538
|
+
await ctx.report_progress(3, 3)
|
|
510
539
|
return result.stdout
|
|
540
|
+
await ctx.report_progress(3, 3)
|
|
511
541
|
return json.dumps({"status": "fixed", "message": f"Fixes applied to {target.name}"})
|
|
512
542
|
|
|
513
543
|
|
|
514
544
|
@mcp.tool()
|
|
515
|
-
def lint_project(fix: bool
|
|
545
|
+
async def lint_project(fix: bool, ctx: Context) -> str:
|
|
516
546
|
"""Lint all SQL models in the dbt project.
|
|
517
547
|
|
|
518
548
|
Args:
|
|
@@ -521,16 +551,23 @@ def lint_project(fix: bool = False) -> str:
|
|
|
521
551
|
Lints the models/ directory using the project's .sqlfluff config.
|
|
522
552
|
Returns a summary of violations or fixes applied.
|
|
523
553
|
"""
|
|
554
|
+
import asyncio
|
|
524
555
|
import subprocess
|
|
525
556
|
from pathlib import Path
|
|
526
557
|
|
|
558
|
+
action = "fix" if fix else "lint"
|
|
559
|
+
|
|
560
|
+
await ctx.report_progress(0, 3)
|
|
561
|
+
await ctx.info("Finding dbt project...")
|
|
527
562
|
project_dir = _find_project_dir()
|
|
528
563
|
_ensure_dbt_packages(project_dir)
|
|
529
564
|
models_dir = project_dir / "models"
|
|
530
565
|
if not models_dir.exists():
|
|
531
566
|
return json.dumps({"error": f"No models directory found at {models_dir}"})
|
|
532
567
|
|
|
533
|
-
|
|
568
|
+
await ctx.report_progress(1, 3)
|
|
569
|
+
await ctx.info(f"Running SQLFluff {action} on models/...")
|
|
570
|
+
|
|
534
571
|
cmd = ["sqlfluff", action, str(models_dir), "--format", "json"]
|
|
535
572
|
if fix:
|
|
536
573
|
cmd.append("--force")
|
|
@@ -539,7 +576,12 @@ def lint_project(fix: bool = False) -> str:
|
|
|
539
576
|
if config_path.exists():
|
|
540
577
|
cmd += ["--config", str(config_path)]
|
|
541
578
|
|
|
542
|
-
result =
|
|
579
|
+
result = await asyncio.to_thread(
|
|
580
|
+
subprocess.run, cmd, capture_output=True, text=True, cwd=str(project_dir)
|
|
581
|
+
)
|
|
582
|
+
|
|
583
|
+
await ctx.report_progress(2, 3)
|
|
584
|
+
await ctx.info("Processing results...")
|
|
543
585
|
|
|
544
586
|
if result.returncode > 1:
|
|
545
587
|
return json.dumps({"error": f"SQLFluff {action} failed: {result.stderr.strip()}"})
|
|
@@ -560,10 +602,14 @@ def lint_project(fix: bool = False) -> str:
|
|
|
560
602
|
"truncated": True,
|
|
561
603
|
"message": f"Showing first 5 of {len(output)} files",
|
|
562
604
|
}
|
|
605
|
+
await ctx.report_progress(3, 3)
|
|
563
606
|
return json.dumps(summary, indent=2)
|
|
607
|
+
await ctx.report_progress(3, 3)
|
|
564
608
|
return json.dumps(output, indent=2)
|
|
565
609
|
except json.JSONDecodeError:
|
|
610
|
+
await ctx.report_progress(3, 3)
|
|
566
611
|
return result.stdout
|
|
612
|
+
await ctx.report_progress(3, 3)
|
|
567
613
|
return json.dumps({"status": "clean", "action": action, "message": "No issues found"})
|
|
568
614
|
|
|
569
615
|
|
|
@@ -580,15 +626,18 @@ def _find_project_dir() -> "Path":
|
|
|
580
626
|
|
|
581
627
|
|
|
582
628
|
def _ensure_dbt_packages(project_dir: "Path") -> None:
|
|
583
|
-
"""Ensure dbt-snowflake and dbt packages are available for sqlfluff dbt templater.
|
|
629
|
+
"""Ensure dbt-snowflake and dbt packages are available for sqlfluff dbt templater.
|
|
630
|
+
|
|
631
|
+
Skips expensive dbt deps if packages are already present, but always
|
|
632
|
+
ensures the dbt-snowflake Python package is importable.
|
|
633
|
+
"""
|
|
584
634
|
import subprocess
|
|
585
635
|
from pathlib import Path
|
|
586
636
|
|
|
587
|
-
#
|
|
637
|
+
# Always ensure dbt-snowflake Python package is available (required by sqlfluff-templater-dbt)
|
|
588
638
|
try:
|
|
589
639
|
import dbt.adapters.snowflake # noqa: F401
|
|
590
640
|
except ImportError:
|
|
591
|
-
# Attempt to install dbt-snowflake
|
|
592
641
|
subprocess.run(
|
|
593
642
|
["uv", "pip", "install", "dbt-snowflake"],
|
|
594
643
|
capture_output=True, text=True,
|
|
@@ -609,8 +658,9 @@ def _ensure_dbt_packages(project_dir: "Path") -> None:
|
|
|
609
658
|
if not packages_yml.exists():
|
|
610
659
|
return # No packages to install
|
|
611
660
|
|
|
612
|
-
#
|
|
661
|
+
# Packages are missing — run dbt deps to install them
|
|
613
662
|
import shutil
|
|
663
|
+
|
|
614
664
|
for cmd in [["dbtf", "deps"], ["dbt", "deps"]]:
|
|
615
665
|
if shutil.which(cmd[0]):
|
|
616
666
|
subprocess.run(cmd, capture_output=True, text=True, cwd=str(project_dir))
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.5.2"
|
|
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.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/services/bootstrap_service.py
RENAMED
|
File without changes
|
{datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/services/dbt_project_generator.py
RENAMED
|
File without changes
|
|
File without changes
|
{datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/services/directory_scaffolder.py
RENAMED
|
File without changes
|
{datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/services/download_service.py
RENAMED
|
File without changes
|
|
File without changes
|
{datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/services/linting_service.py
RENAMED
|
File without changes
|
|
File without changes
|
{datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/services/snowflake_service.py
RENAMED
|
File without changes
|
{datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/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
|