datasecops-cli 0.2.1__tar.gz → 0.2.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.2.1 → datasecops_cli-0.2.3}/CHANGELOG.md +25 -0
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/PKG-INFO +1 -1
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/pyproject.toml +1 -1
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/setup.ps1 +48 -6
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/setup.sh +45 -6
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/src/datasecops_cli/menus/development.py +9 -8
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/src/datasecops_cli/services/download_service.py +137 -23
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/src/datasecops_cli/services/linting_service.py +9 -0
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/.github/workflows/publish-cli.yml +0 -0
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/.gitignore +0 -0
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/DEVELOPMENT.md +0 -0
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/LICENSE +0 -0
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/README.md +0 -0
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/docs/getting-started.md +0 -0
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/docs/legacy.md +0 -0
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/docs/legacy_plan_of_action.md +0 -0
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/docs/mcp-server.md +0 -0
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/mcp-servers.json +0 -0
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/src/datasecops_cli/__init__.py +0 -0
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/src/datasecops_cli/config.py +0 -0
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/src/datasecops_cli/main.py +0 -0
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/src/datasecops_cli/menus/__init__.py +0 -0
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/src/datasecops_cli/menus/downloads.py +0 -0
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/src/datasecops_cli/menus/git_operations.py +0 -0
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/src/datasecops_cli/models/__init__.py +0 -0
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/src/datasecops_cli/models/git_helpers.py +0 -0
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/src/datasecops_cli/models/project_config.py +0 -0
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/src/datasecops_cli/services/__init__.py +0 -0
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/src/datasecops_cli/services/bootstrap_service.py +0 -0
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/src/datasecops_cli/services/dbt_runner.py +0 -0
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/src/datasecops_cli/services/git_service.py +0 -0
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/src/datasecops_cli/services/skill_service.py +0 -0
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/src/datasecops_cli/services/snowflake_service.py +0 -0
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/src/datasecops_cli/utilities/__init__.py +0 -0
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/src/datasecops_cli/utilities/display.py +0 -0
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/src/datasecops_cli/utilities/file_utils.py +0 -0
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/src/datasecops_cli/utilities/yaml_utils.py +0 -0
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/src/datasecops_mcp/__init__.py +0 -0
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/src/datasecops_mcp/__main__.py +0 -0
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/src/datasecops_mcp/connection.py +0 -0
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/src/datasecops_mcp/server.py +0 -0
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/tests/__init__.py +0 -0
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/tests/test_config.py +0 -0
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/tests/test_file_utils.py +0 -0
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/tests/test_models.py +0 -0
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/tests/test_version.py +0 -0
- {datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/tests/test_yaml_utils.py +0 -0
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the DataSecOps CLI are documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.2.3] - 2026-05-11
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- **Stripped invalid `dbt_skip_compilation_error` option** from the generated `[sqlfluff:templater:dbt]` section — this native app field is not a valid sqlfluff config key and caused parse errors
|
|
10
|
+
- **`profiles_dir = ${DBT_PROFILES_DIR}` no longer leaks** into generated config — the raw value from the native app is now always replaced with the resolved local path or removed entirely
|
|
11
|
+
- **Empty string values** (e.g. `warnings = ""`) now emit `None` instead of blank, matching sqlfluff's expected INI format
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **Auto-install `sqlfluff-templater-dbt`** — the lint menu now checks for missing sqlfluff packages before linting and installs them automatically
|
|
16
|
+
|
|
17
|
+
## [0.2.2] - 2026-05-10
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- **Full SQLFluff config generation** — `download_sqlfluff_config()` now generates all sections from the native app config: core, templater, indentation, layout types, global rules, and all rule bundles (aliasing, ambiguous, capitalisation, convention, jinja, layout, references, structure)
|
|
22
|
+
- Previously only `core` and `indentation` sections were emitted, missing ~90% of the config
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- **Setup script connection selector** — `setup.ps1` and `setup.sh` now parse `~/.snowflake/connections.toml`, display a numbered list of connections with the default highlighted, and let the user select by number instead of typing the name manually
|
|
27
|
+
- Rule code to section name mapping (`RULE_SECTION_MAP`) for all sqlfluff rule bundles (AL01-AL09, AM01-AM08, CP01-CP05, CV01-CV12, JJ01, LT01-LT15, RF01-RF06, ST01-ST11)
|
|
28
|
+
- Value formatting helper (`_format_value`) for correct INI output of bools, lists, nulls, and empty strings
|
|
29
|
+
|
|
5
30
|
## [0.2.1] - 2026-05-10
|
|
6
31
|
|
|
7
32
|
### Fixed
|
|
@@ -48,18 +48,60 @@ Write-Host "--- Snowflake Connection ---" -ForegroundColor Green
|
|
|
48
48
|
Write-Host ""
|
|
49
49
|
|
|
50
50
|
$connectionsFile = Join-Path $env:USERPROFILE ".snowflake\connections.toml"
|
|
51
|
+
$connectionNames = @()
|
|
52
|
+
$defaultConnection = $null
|
|
53
|
+
|
|
51
54
|
if (Test-Path $connectionsFile) {
|
|
55
|
+
# Parse default connection name
|
|
56
|
+
$defaultLine = Get-Content $connectionsFile | Select-String '^default_connection_name\s*=' | Select-Object -First 1
|
|
57
|
+
if ($defaultLine) {
|
|
58
|
+
$defaultConnection = ($defaultLine -replace '.*=\s*', '').Trim().Trim('"').Trim("'")
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# Parse connection section names
|
|
62
|
+
$connectionNames = @(Get-Content $connectionsFile | Select-String '^\[([^\]]+)\]' | ForEach-Object { $_.Matches[0].Groups[1].Value })
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if ($connectionNames.Count -gt 0) {
|
|
52
66
|
Write-Host "Available connections:" -ForegroundColor Cyan
|
|
53
|
-
|
|
67
|
+
for ($i = 0; $i -lt $connectionNames.Count; $i++) {
|
|
68
|
+
$marker = ""
|
|
69
|
+
if ($connectionNames[$i] -eq $defaultConnection) { $marker = " (default)" }
|
|
70
|
+
Write-Host " [$($i + 1)] $($connectionNames[$i])$marker"
|
|
71
|
+
}
|
|
54
72
|
Write-Host ""
|
|
55
|
-
}
|
|
56
73
|
|
|
57
|
-
$
|
|
58
|
-
if (
|
|
59
|
-
|
|
60
|
-
|
|
74
|
+
$defaultIndex = -1
|
|
75
|
+
if ($defaultConnection) {
|
|
76
|
+
$defaultIndex = [array]::IndexOf($connectionNames, $defaultConnection)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if ($defaultIndex -ge 0) {
|
|
80
|
+
$prompt = "Select connection [1-$($connectionNames.Count)] (default: $($defaultIndex + 1))"
|
|
81
|
+
} else {
|
|
82
|
+
$prompt = "Select connection [1-$($connectionNames.Count)]"
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
$selection = Read-Host $prompt
|
|
86
|
+
if ([string]::IsNullOrWhiteSpace($selection) -and $defaultIndex -ge 0) {
|
|
87
|
+
$connectionName = $defaultConnection
|
|
88
|
+
} elseif ($selection -match '^\d+$' -and [int]$selection -ge 1 -and [int]$selection -le $connectionNames.Count) {
|
|
89
|
+
$connectionName = $connectionNames[[int]$selection - 1]
|
|
90
|
+
} else {
|
|
91
|
+
Write-Host "ERROR: Invalid selection" -ForegroundColor Red
|
|
92
|
+
exit 1
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
Write-Host "No connections found in $connectionsFile" -ForegroundColor Yellow
|
|
96
|
+
$connectionName = Read-Host "Enter connection name"
|
|
97
|
+
if ([string]::IsNullOrWhiteSpace($connectionName)) {
|
|
98
|
+
Write-Host "ERROR: Connection name is required" -ForegroundColor Red
|
|
99
|
+
exit 1
|
|
100
|
+
}
|
|
61
101
|
}
|
|
62
102
|
|
|
103
|
+
Write-Host "[OK] Using connection: $connectionName" -ForegroundColor Green
|
|
104
|
+
|
|
63
105
|
# --- Step 3: Enter native app database name ---
|
|
64
106
|
|
|
65
107
|
Write-Host ""
|
|
@@ -46,18 +46,57 @@ echo "--- Snowflake Connection ---"
|
|
|
46
46
|
echo ""
|
|
47
47
|
|
|
48
48
|
CONNECTIONS_FILE="$HOME/.snowflake/connections.toml"
|
|
49
|
+
CONNECTION_NAMES=()
|
|
50
|
+
DEFAULT_CONNECTION=""
|
|
51
|
+
|
|
49
52
|
if [ -f "$CONNECTIONS_FILE" ]; then
|
|
53
|
+
# Parse default connection name
|
|
54
|
+
DEFAULT_CONNECTION=$(grep '^default_connection_name' "$CONNECTIONS_FILE" | sed 's/.*=\s*//;s/[" ]//g' | head -1)
|
|
55
|
+
|
|
56
|
+
# Parse connection section names
|
|
57
|
+
while IFS= read -r name; do
|
|
58
|
+
CONNECTION_NAMES+=("$name")
|
|
59
|
+
done < <(grep '^\[' "$CONNECTIONS_FILE" | sed 's/\[//;s/\]//')
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
if [ ${#CONNECTION_NAMES[@]} -gt 0 ]; then
|
|
50
63
|
echo "Available connections:"
|
|
51
|
-
|
|
64
|
+
DEFAULT_INDEX=-1
|
|
65
|
+
for i in "${!CONNECTION_NAMES[@]}"; do
|
|
66
|
+
marker=""
|
|
67
|
+
if [ "${CONNECTION_NAMES[$i]}" = "$DEFAULT_CONNECTION" ]; then
|
|
68
|
+
marker=" (default)"
|
|
69
|
+
DEFAULT_INDEX=$i
|
|
70
|
+
fi
|
|
71
|
+
echo " [$((i + 1))] ${CONNECTION_NAMES[$i]}${marker}"
|
|
72
|
+
done
|
|
52
73
|
echo ""
|
|
53
|
-
fi
|
|
54
74
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
75
|
+
if [ $DEFAULT_INDEX -ge 0 ]; then
|
|
76
|
+
read -rp "Select connection [1-${#CONNECTION_NAMES[@]}] (default: $((DEFAULT_INDEX + 1))): " SELECTION
|
|
77
|
+
else
|
|
78
|
+
read -rp "Select connection [1-${#CONNECTION_NAMES[@]}]: " SELECTION
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
if [ -z "$SELECTION" ] && [ $DEFAULT_INDEX -ge 0 ]; then
|
|
82
|
+
CONNECTION_NAME="$DEFAULT_CONNECTION"
|
|
83
|
+
elif [[ "$SELECTION" =~ ^[0-9]+$ ]] && [ "$SELECTION" -ge 1 ] && [ "$SELECTION" -le ${#CONNECTION_NAMES[@]} ]; then
|
|
84
|
+
CONNECTION_NAME="${CONNECTION_NAMES[$((SELECTION - 1))]}"
|
|
85
|
+
else
|
|
86
|
+
echo "ERROR: Invalid selection"
|
|
87
|
+
exit 1
|
|
88
|
+
fi
|
|
89
|
+
else
|
|
90
|
+
echo "No connections found in $CONNECTIONS_FILE"
|
|
91
|
+
read -rp "Enter connection name: " CONNECTION_NAME
|
|
92
|
+
if [ -z "$CONNECTION_NAME" ]; then
|
|
93
|
+
echo "ERROR: Connection name is required"
|
|
94
|
+
exit 1
|
|
95
|
+
fi
|
|
59
96
|
fi
|
|
60
97
|
|
|
98
|
+
echo "[OK] Using connection: $CONNECTION_NAME"
|
|
99
|
+
|
|
61
100
|
# --- Step 3: Enter native app database name ---
|
|
62
101
|
|
|
63
102
|
echo ""
|
|
@@ -141,15 +141,16 @@ class DevelopmentMenu:
|
|
|
141
141
|
complete_action()
|
|
142
142
|
|
|
143
143
|
def _ensure_sqlfluff_config(self) -> bool:
|
|
144
|
-
"""Download .sqlfluff from the framework if it doesn't exist locally."""
|
|
144
|
+
"""Download .sqlfluff from the framework if it doesn't exist locally, and ensure templater is installed."""
|
|
145
145
|
config_path = self.linting.project_dir / ".sqlfluff"
|
|
146
|
-
if config_path.exists():
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
146
|
+
if not config_path.exists():
|
|
147
|
+
if not self.downloads:
|
|
148
|
+
error_line("No .sqlfluff config found and download service not available")
|
|
149
|
+
return False
|
|
150
|
+
info_line("No .sqlfluff config found — downloading from framework...")
|
|
151
|
+
if not self.downloads.download_sqlfluff_config(profiles_dir=self.profiles_dir):
|
|
152
|
+
return False
|
|
153
|
+
return self.linting.ensure_templater_installed()
|
|
153
154
|
|
|
154
155
|
def _lint_menu(self) -> None:
|
|
155
156
|
clear()
|
{datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/src/datasecops_cli/services/download_service.py
RENAMED
|
@@ -12,10 +12,88 @@ from datasecops_cli.utilities.file_utils import write_file, ensure_dir
|
|
|
12
12
|
|
|
13
13
|
class DownloadService:
|
|
14
14
|
"""Downloads configurations from the native app."""
|
|
15
|
+
|
|
16
|
+
# Maps rule codes to their sqlfluff INI section names.
|
|
17
|
+
RULE_SECTION_MAP: dict[str, str] = {
|
|
18
|
+
# Aliasing
|
|
19
|
+
"AL01": "aliasing.table", "AL02": "aliasing.column", "AL03": "aliasing.expression",
|
|
20
|
+
"AL04": "aliasing.unique.table", "AL05": "aliasing.unused", "AL06": "aliasing.length",
|
|
21
|
+
"AL07": "aliasing.forbid", "AL08": "aliasing.unique.column", "AL09": "aliasing.self_alias",
|
|
22
|
+
# Ambiguous
|
|
23
|
+
"AM01": "ambiguous.distinct", "AM02": "ambiguous.union", "AM03": "ambiguous.order_by",
|
|
24
|
+
"AM04": "ambiguous.column_count", "AM05": "ambiguous.join",
|
|
25
|
+
"AM06": "ambiguous.column_references", "AM07": "ambiguous.set_columns",
|
|
26
|
+
"AM08": "ambiguous.union_type",
|
|
27
|
+
# Capitalisation
|
|
28
|
+
"CP01": "capitalisation.keywords", "CP02": "capitalisation.identifiers",
|
|
29
|
+
"CP03": "capitalisation.functions", "CP04": "capitalisation.literals",
|
|
30
|
+
"CP05": "capitalisation.types",
|
|
31
|
+
# Convention
|
|
32
|
+
"CV01": "convention.not_equal", "CV02": "convention.coalesce",
|
|
33
|
+
"CV03": "convention.select_trailing_comma", "CV04": "convention.count_rows",
|
|
34
|
+
"CV05": "convention.is_null", "CV06": "convention.terminator",
|
|
35
|
+
"CV07": "convention.statement_brackets", "CV08": "convention.left_join",
|
|
36
|
+
"CV09": "convention.blocked_words", "CV10": "convention.quoted_literals",
|
|
37
|
+
"CV11": "convention.casting_style", "CV12": "convention.semicolon",
|
|
38
|
+
# Jinja
|
|
39
|
+
"JJ01": "jinja.padding",
|
|
40
|
+
# Layout
|
|
41
|
+
"LT01": "layout.spacing", "LT02": "layout.indent", "LT03": "layout.operators",
|
|
42
|
+
"LT04": "layout.commas", "LT05": "layout.long_lines", "LT06": "layout.functions",
|
|
43
|
+
"LT07": "layout.cte_bracket", "LT08": "layout.cte_newline",
|
|
44
|
+
"LT09": "layout.select_targets", "LT10": "layout.select_modifiers",
|
|
45
|
+
"LT11": "layout.set_operators", "LT12": "layout.end-of-file",
|
|
46
|
+
"LT13": "layout.start_of_file", "LT14": "layout.one_line", "LT15": "layout.newlines",
|
|
47
|
+
# References
|
|
48
|
+
"RF01": "references.from", "RF02": "references.qualification",
|
|
49
|
+
"RF03": "references.consistent", "RF04": "references.keywords",
|
|
50
|
+
"RF05": "references.special_chars", "RF06": "references.quoting",
|
|
51
|
+
# Structure
|
|
52
|
+
"ST01": "structure.else_null", "ST02": "structure.simple_case",
|
|
53
|
+
"ST03": "structure.unused_cte", "ST04": "structure.nested_case",
|
|
54
|
+
"ST05": "structure.subquery", "ST06": "structure.column_order",
|
|
55
|
+
"ST07": "structure.using", "ST08": "structure.distinct",
|
|
56
|
+
"ST09": "structure.join_condition_order", "ST10": "structure.cte_definition_order",
|
|
57
|
+
"ST11": "structure.test_query",
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# Core sub-keys that map to specific INI sections.
|
|
61
|
+
CORE_SECTION_MAP: dict[str, str] = {
|
|
62
|
+
"sqlfluff": "sqlfluff",
|
|
63
|
+
"dbt": "sqlfluff:templater:dbt",
|
|
64
|
+
"jinja": "sqlfluff:templater:jinja",
|
|
65
|
+
"templater": "sqlfluff:templater",
|
|
66
|
+
}
|
|
15
67
|
|
|
16
68
|
def __init__(self, snowflake_service: SnowflakeService, project_dir: Path):
|
|
17
69
|
self.sf = snowflake_service
|
|
18
70
|
self.project_dir = project_dir
|
|
71
|
+
|
|
72
|
+
# Options to strip from specific core sections (not valid sqlfluff config keys).
|
|
73
|
+
CORE_STRIP_OPTIONS: dict[str, set[str]] = {
|
|
74
|
+
"dbt": {"dbt_skip_compilation_error"},
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@staticmethod
|
|
78
|
+
def _format_value(val) -> str:
|
|
79
|
+
"""Format a JSON value for sqlfluff INI output."""
|
|
80
|
+
if isinstance(val, bool):
|
|
81
|
+
return str(val)
|
|
82
|
+
if isinstance(val, list):
|
|
83
|
+
if not val:
|
|
84
|
+
return "None"
|
|
85
|
+
return ", ".join(str(v) for v in val)
|
|
86
|
+
if val is None or val == "":
|
|
87
|
+
return "None"
|
|
88
|
+
return str(val)
|
|
89
|
+
|
|
90
|
+
@staticmethod
|
|
91
|
+
def _emit_section(lines: list[str], header: str, options: dict) -> None:
|
|
92
|
+
"""Append an INI section with its options to lines."""
|
|
93
|
+
lines.append(f"[{header}]")
|
|
94
|
+
for key, val in options.items():
|
|
95
|
+
lines.append(f"{key} = {DownloadService._format_value(val)}")
|
|
96
|
+
lines.append("")
|
|
19
97
|
|
|
20
98
|
def download_sqlfluff_config(self, profiles_dir: str = None) -> bool:
|
|
21
99
|
info_line("Downloading SQLFluff configuration...")
|
|
@@ -24,31 +102,67 @@ class DownloadService:
|
|
|
24
102
|
error_line("No SQLFluff configuration found in native app")
|
|
25
103
|
return False
|
|
26
104
|
|
|
27
|
-
lines
|
|
28
|
-
|
|
105
|
+
lines: list[str] = []
|
|
106
|
+
|
|
107
|
+
# --- Core sections ([sqlfluff], [sqlfluff:templater:dbt], etc.) ---
|
|
29
108
|
core = raw.get("core", {})
|
|
30
|
-
for
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
109
|
+
for key, section_header in self.CORE_SECTION_MAP.items():
|
|
110
|
+
entry = core.get(key)
|
|
111
|
+
if not isinstance(entry, dict) or not entry.get("enabled", True):
|
|
112
|
+
continue
|
|
113
|
+
opts = dict(entry.get("options", {}))
|
|
114
|
+
# Strip options that are not valid sqlfluff config keys
|
|
115
|
+
for strip_key in self.CORE_STRIP_OPTIONS.get(key, set()):
|
|
116
|
+
opts.pop(strip_key, None)
|
|
117
|
+
if key == "dbt":
|
|
118
|
+
# Override profiles_dir / project_dir with local values
|
|
119
|
+
opts["project_dir"] = "."
|
|
120
|
+
if profiles_dir:
|
|
121
|
+
opts["profiles_dir"] = profiles_dir
|
|
122
|
+
else:
|
|
123
|
+
opts.pop("profiles_dir", None)
|
|
124
|
+
if opts:
|
|
125
|
+
self._emit_section(lines, section_header, opts)
|
|
126
|
+
|
|
127
|
+
# --- Indentation ---
|
|
44
128
|
indentation = raw.get("indentation", {})
|
|
45
|
-
for
|
|
46
|
-
if isinstance(
|
|
47
|
-
opts =
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
129
|
+
for _code, entry in indentation.items():
|
|
130
|
+
if isinstance(entry, dict) and entry.get("enabled", True):
|
|
131
|
+
opts = entry.get("options", {})
|
|
132
|
+
if opts:
|
|
133
|
+
self._emit_section(lines, "sqlfluff:indentation", opts)
|
|
134
|
+
|
|
135
|
+
# --- Layout sections ([sqlfluff:layout:type:<code>]) ---
|
|
136
|
+
layout = raw.get("layout", {})
|
|
137
|
+
for code, entry in layout.items():
|
|
138
|
+
if not isinstance(entry, dict) or not entry.get("enabled", True):
|
|
139
|
+
continue
|
|
140
|
+
opts = entry.get("options", {})
|
|
141
|
+
if opts:
|
|
142
|
+
self._emit_section(lines, f"sqlfluff:layout:type:{code}", opts)
|
|
143
|
+
|
|
144
|
+
# --- Global rules ([sqlfluff:rules]) ---
|
|
145
|
+
rules = raw.get("rules", {})
|
|
146
|
+
for _code, entry in rules.items():
|
|
147
|
+
if isinstance(entry, dict) and entry.get("enabled", True):
|
|
148
|
+
opts = entry.get("options", {})
|
|
149
|
+
if opts:
|
|
150
|
+
self._emit_section(lines, "sqlfluff:rules", opts)
|
|
151
|
+
|
|
152
|
+
# --- Rule bundle sections (rules_aliasing, rules_capitalisation, etc.) ---
|
|
153
|
+
for section_key, entries in raw.items():
|
|
154
|
+
if not section_key.startswith("rules_") or not isinstance(entries, dict):
|
|
155
|
+
continue
|
|
156
|
+
for code, entry in entries.items():
|
|
157
|
+
if not isinstance(entry, dict) or not entry.get("enabled", True):
|
|
158
|
+
continue
|
|
159
|
+
section_name = self.RULE_SECTION_MAP.get(code)
|
|
160
|
+
if not section_name:
|
|
161
|
+
continue
|
|
162
|
+
opts = entry.get("options", {})
|
|
163
|
+
self._emit_section(lines, f"sqlfluff:rules:{section_name}", opts)
|
|
164
|
+
|
|
165
|
+
content = "\n".join(lines)
|
|
52
166
|
dest = self.project_dir / ".sqlfluff"
|
|
53
167
|
write_file(dest, content)
|
|
54
168
|
success_line(f"SQLFluff config written to {dest}")
|
{datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/src/datasecops_cli/services/linting_service.py
RENAMED
|
@@ -43,6 +43,15 @@ class LintingService:
|
|
|
43
43
|
error_line(f"pip install failed with exit code {result.returncode}")
|
|
44
44
|
return False
|
|
45
45
|
|
|
46
|
+
def ensure_templater_installed(self) -> bool:
|
|
47
|
+
"""Check if sqlfluff-templater-dbt is installed; install it if missing."""
|
|
48
|
+
versions = self.get_installed_versions()
|
|
49
|
+
missing = [pkg for pkg in ["sqlfluff", "sqlfluff-templater-dbt"] if not versions.get(pkg)]
|
|
50
|
+
if not missing:
|
|
51
|
+
return True
|
|
52
|
+
warning_line(f"Missing packages: {', '.join(missing)}")
|
|
53
|
+
return self.install_requirements(missing)
|
|
54
|
+
|
|
46
55
|
def lint_file(self, file_path: str = None, fix: bool = False) -> subprocess.CompletedProcess:
|
|
47
56
|
action = "fix" if fix else "lint"
|
|
48
57
|
cmd = ["sqlfluff", action]
|
|
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.2.1 → datasecops_cli-0.2.3}/src/datasecops_cli/services/bootstrap_service.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{datasecops_cli-0.2.1 → datasecops_cli-0.2.3}/src/datasecops_cli/services/snowflake_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
|