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.
Files changed (57) hide show
  1. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/CHANGELOG.md +8 -0
  2. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/PKG-INFO +1 -1
  3. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/pyproject.toml +1 -1
  4. datasecops_cli-0.5.3/src/datasecops_cli/__init__.py +1 -0
  5. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_mcp/server.py +62 -12
  6. datasecops_cli-0.5.2/src/datasecops_cli/__init__.py +0 -1
  7. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/.github/workflows/auto-tag.yml +0 -0
  8. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/.github/workflows/publish-cli.yml +0 -0
  9. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/.github/workflows/test.yml +0 -0
  10. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/.gitignore +0 -0
  11. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/DEVELOPMENT.md +0 -0
  12. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/LICENSE +0 -0
  13. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/README.md +0 -0
  14. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/docs/getting-started.md +0 -0
  15. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/docs/git-operations.md +0 -0
  16. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/docs/legacy.md +0 -0
  17. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/docs/legacy_plan_of_action.md +0 -0
  18. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/docs/mcp-server.md +0 -0
  19. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/mcp-servers.json +0 -0
  20. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/setup.ps1 +0 -0
  21. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/setup.sh +0 -0
  22. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/config.py +0 -0
  23. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/main.py +0 -0
  24. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/menus/__init__.py +0 -0
  25. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/menus/configuration.py +0 -0
  26. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/menus/development.py +0 -0
  27. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/menus/downloads.py +0 -0
  28. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/menus/git_operations.py +0 -0
  29. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/models/__init__.py +0 -0
  30. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/models/git_helpers.py +0 -0
  31. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/models/project_config.py +0 -0
  32. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/services/__init__.py +0 -0
  33. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/services/bootstrap_service.py +0 -0
  34. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/services/dbt_project_generator.py +0 -0
  35. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/services/dbt_runner.py +0 -0
  36. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/services/directory_scaffolder.py +0 -0
  37. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/services/download_service.py +0 -0
  38. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/services/git_service.py +0 -0
  39. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/services/linting_service.py +0 -0
  40. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/services/skill_service.py +0 -0
  41. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/services/snowflake_service.py +0 -0
  42. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/services/upstream_service.py +0 -0
  43. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/utilities/__init__.py +0 -0
  44. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/utilities/display.py +0 -0
  45. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/utilities/file_utils.py +0 -0
  46. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/utilities/mcp.py +0 -0
  47. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_cli/utilities/yaml_utils.py +0 -0
  48. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_mcp/__init__.py +0 -0
  49. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_mcp/__main__.py +0 -0
  50. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/src/datasecops_mcp/connection.py +0 -0
  51. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/tests/__init__.py +0 -0
  52. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/tests/test_config.py +0 -0
  53. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/tests/test_file_utils.py +0 -0
  54. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/tests/test_main.py +0 -0
  55. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/tests/test_models.py +0 -0
  56. {datasecops_cli-0.5.2 → datasecops_cli-0.5.3}/tests/test_version.py +0 -0
  57. {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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datasecops-cli
3
- Version: 0.5.2
3
+ Version: 0.5.3
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.5.2"
7
+ version = "0.5.3"
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.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 = subprocess.run(cmd, capture_output=True, text=True, cwd=str(project_dir))
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 = subprocess.run(cmd, capture_output=True, text=True, cwd=str(project_dir))
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 = False) -> str:
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
- action = "fix" if fix else "lint"
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 = subprocess.run(cmd, capture_output=True, text=True, cwd=str(project_dir))
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
- # Check that dbt-snowflake Python package is installed (required by sqlfluff-templater-dbt)
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
- # Run dbt deps to install packages
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