fraiseql-confiture 0.3.7__cp311-cp311-macosx_11_0_arm64.whl
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.
- confiture/__init__.py +48 -0
- confiture/_core.cpython-311-darwin.so +0 -0
- confiture/cli/__init__.py +0 -0
- confiture/cli/dry_run.py +116 -0
- confiture/cli/lint_formatter.py +193 -0
- confiture/cli/main.py +1893 -0
- confiture/config/__init__.py +0 -0
- confiture/config/environment.py +263 -0
- confiture/core/__init__.py +51 -0
- confiture/core/anonymization/__init__.py +0 -0
- confiture/core/anonymization/audit.py +485 -0
- confiture/core/anonymization/benchmarking.py +372 -0
- confiture/core/anonymization/breach_notification.py +652 -0
- confiture/core/anonymization/compliance.py +617 -0
- confiture/core/anonymization/composer.py +298 -0
- confiture/core/anonymization/data_subject_rights.py +669 -0
- confiture/core/anonymization/factory.py +319 -0
- confiture/core/anonymization/governance.py +737 -0
- confiture/core/anonymization/performance.py +1092 -0
- confiture/core/anonymization/profile.py +284 -0
- confiture/core/anonymization/registry.py +195 -0
- confiture/core/anonymization/security/kms_manager.py +547 -0
- confiture/core/anonymization/security/lineage.py +888 -0
- confiture/core/anonymization/security/token_store.py +686 -0
- confiture/core/anonymization/strategies/__init__.py +41 -0
- confiture/core/anonymization/strategies/address.py +359 -0
- confiture/core/anonymization/strategies/credit_card.py +374 -0
- confiture/core/anonymization/strategies/custom.py +161 -0
- confiture/core/anonymization/strategies/date.py +218 -0
- confiture/core/anonymization/strategies/differential_privacy.py +398 -0
- confiture/core/anonymization/strategies/email.py +141 -0
- confiture/core/anonymization/strategies/format_preserving_encryption.py +310 -0
- confiture/core/anonymization/strategies/hash.py +150 -0
- confiture/core/anonymization/strategies/ip_address.py +235 -0
- confiture/core/anonymization/strategies/masking_retention.py +252 -0
- confiture/core/anonymization/strategies/name.py +298 -0
- confiture/core/anonymization/strategies/phone.py +119 -0
- confiture/core/anonymization/strategies/preserve.py +85 -0
- confiture/core/anonymization/strategies/redact.py +101 -0
- confiture/core/anonymization/strategies/salted_hashing.py +322 -0
- confiture/core/anonymization/strategies/text_redaction.py +183 -0
- confiture/core/anonymization/strategies/tokenization.py +334 -0
- confiture/core/anonymization/strategy.py +241 -0
- confiture/core/anonymization/syncer_audit.py +357 -0
- confiture/core/blue_green.py +683 -0
- confiture/core/builder.py +500 -0
- confiture/core/checksum.py +358 -0
- confiture/core/connection.py +184 -0
- confiture/core/differ.py +522 -0
- confiture/core/drift.py +564 -0
- confiture/core/dry_run.py +182 -0
- confiture/core/health.py +313 -0
- confiture/core/hooks/__init__.py +87 -0
- confiture/core/hooks/base.py +232 -0
- confiture/core/hooks/context.py +146 -0
- confiture/core/hooks/execution_strategies.py +57 -0
- confiture/core/hooks/observability.py +220 -0
- confiture/core/hooks/phases.py +53 -0
- confiture/core/hooks/registry.py +295 -0
- confiture/core/large_tables.py +775 -0
- confiture/core/linting/__init__.py +70 -0
- confiture/core/linting/composer.py +192 -0
- confiture/core/linting/libraries/__init__.py +17 -0
- confiture/core/linting/libraries/gdpr.py +168 -0
- confiture/core/linting/libraries/general.py +184 -0
- confiture/core/linting/libraries/hipaa.py +144 -0
- confiture/core/linting/libraries/pci_dss.py +104 -0
- confiture/core/linting/libraries/sox.py +120 -0
- confiture/core/linting/schema_linter.py +491 -0
- confiture/core/linting/versioning.py +151 -0
- confiture/core/locking.py +389 -0
- confiture/core/migration_generator.py +298 -0
- confiture/core/migrator.py +882 -0
- confiture/core/observability/__init__.py +44 -0
- confiture/core/observability/audit.py +323 -0
- confiture/core/observability/logging.py +187 -0
- confiture/core/observability/metrics.py +174 -0
- confiture/core/observability/tracing.py +192 -0
- confiture/core/pg_version.py +418 -0
- confiture/core/pool.py +406 -0
- confiture/core/risk/__init__.py +39 -0
- confiture/core/risk/predictor.py +188 -0
- confiture/core/risk/scoring.py +248 -0
- confiture/core/rollback_generator.py +388 -0
- confiture/core/schema_analyzer.py +769 -0
- confiture/core/schema_to_schema.py +590 -0
- confiture/core/security/__init__.py +32 -0
- confiture/core/security/logging.py +201 -0
- confiture/core/security/validation.py +416 -0
- confiture/core/signals.py +371 -0
- confiture/core/syncer.py +540 -0
- confiture/exceptions.py +192 -0
- confiture/integrations/__init__.py +0 -0
- confiture/models/__init__.py +24 -0
- confiture/models/lint.py +193 -0
- confiture/models/migration.py +265 -0
- confiture/models/schema.py +203 -0
- confiture/models/sql_file_migration.py +225 -0
- confiture/scenarios/__init__.py +36 -0
- confiture/scenarios/compliance.py +586 -0
- confiture/scenarios/ecommerce.py +199 -0
- confiture/scenarios/financial.py +253 -0
- confiture/scenarios/healthcare.py +315 -0
- confiture/scenarios/multi_tenant.py +340 -0
- confiture/scenarios/saas.py +295 -0
- confiture/testing/FRAMEWORK_API.md +722 -0
- confiture/testing/__init__.py +100 -0
- confiture/testing/fixtures/__init__.py +11 -0
- confiture/testing/fixtures/data_validator.py +229 -0
- confiture/testing/fixtures/migration_runner.py +167 -0
- confiture/testing/fixtures/schema_snapshotter.py +352 -0
- confiture/testing/frameworks/__init__.py +10 -0
- confiture/testing/frameworks/mutation.py +587 -0
- confiture/testing/frameworks/performance.py +479 -0
- confiture/testing/loader.py +225 -0
- confiture/testing/pytest/__init__.py +38 -0
- confiture/testing/pytest_plugin.py +190 -0
- confiture/testing/sandbox.py +304 -0
- confiture/testing/utils/__init__.py +0 -0
- fraiseql_confiture-0.3.7.dist-info/METADATA +438 -0
- fraiseql_confiture-0.3.7.dist-info/RECORD +124 -0
- fraiseql_confiture-0.3.7.dist-info/WHEEL +4 -0
- fraiseql_confiture-0.3.7.dist-info/entry_points.txt +4 -0
- fraiseql_confiture-0.3.7.dist-info/licenses/LICENSE +21 -0
confiture/__init__.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Confiture: PostgreSQL migrations, sweetly done 🍓
|
|
2
|
+
|
|
3
|
+
Confiture is a modern PostgreSQL migration tool with a build-from-scratch
|
|
4
|
+
philosophy and 4 migration strategies.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
>>> from confiture import __version__
|
|
8
|
+
>>> print(__version__)
|
|
9
|
+
0.3.7
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from confiture.core.linting import SchemaLinter
|
|
15
|
+
|
|
16
|
+
__version__ = "0.3.7"
|
|
17
|
+
__author__ = "Lionel Hamayon"
|
|
18
|
+
__email__ = "lionel.hamayon@evolution-digitale.fr"
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"__version__",
|
|
22
|
+
"__author__",
|
|
23
|
+
"__email__",
|
|
24
|
+
"SchemaLinter",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
# Lazy imports to avoid errors during development
|
|
28
|
+
# These will be enabled as components are implemented:
|
|
29
|
+
# - SchemaBuilder (Milestone 1.3+)
|
|
30
|
+
# - Migrator (Milestone 1.7+)
|
|
31
|
+
# - Environment (Milestone 1.2+)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def __getattr__(name: str) -> Any:
|
|
35
|
+
"""Lazy import for not-yet-implemented components"""
|
|
36
|
+
if name == "SchemaBuilder":
|
|
37
|
+
from confiture.core.builder import SchemaBuilder
|
|
38
|
+
|
|
39
|
+
return SchemaBuilder
|
|
40
|
+
elif name == "Migrator":
|
|
41
|
+
from confiture.core.migrator import Migrator
|
|
42
|
+
|
|
43
|
+
return Migrator
|
|
44
|
+
elif name == "Environment":
|
|
45
|
+
from confiture.config.environment import Environment
|
|
46
|
+
|
|
47
|
+
return Environment
|
|
48
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
Binary file
|
|
File without changes
|
confiture/cli/dry_run.py
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Dry-run mode helpers for CLI integration.
|
|
2
|
+
|
|
3
|
+
This module provides helper functions for dry-run analysis integration with the CLI.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import logging
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def save_text_report(report_text: str, filepath: Path) -> None:
|
|
18
|
+
"""Save text report to file.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
report_text: Formatted text report
|
|
22
|
+
filepath: Path to save report to
|
|
23
|
+
|
|
24
|
+
Raises:
|
|
25
|
+
IOError: If file write fails
|
|
26
|
+
"""
|
|
27
|
+
filepath.parent.mkdir(parents=True, exist_ok=True)
|
|
28
|
+
filepath.write_text(report_text)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def save_json_report(report_data: dict, filepath: Path) -> None:
|
|
32
|
+
"""Save JSON report to file.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
report_data: Report dictionary to save
|
|
36
|
+
filepath: Path to save report to
|
|
37
|
+
|
|
38
|
+
Raises:
|
|
39
|
+
IOError: If file write fails
|
|
40
|
+
"""
|
|
41
|
+
filepath.parent.mkdir(parents=True, exist_ok=True)
|
|
42
|
+
with filepath.open("w") as f:
|
|
43
|
+
json.dump(report_data, f, indent=2)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def print_json_report(report_data: dict) -> None:
|
|
47
|
+
"""Print JSON report to console.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
report_data: Report dictionary to print
|
|
51
|
+
"""
|
|
52
|
+
console.print_json(data=report_data)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def show_report_summary(report: Any) -> None:
|
|
56
|
+
"""Show a brief summary of the report status.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
report: Report object with has_unsafe_statements, unsafe_count,
|
|
60
|
+
total_estimated_time_ms, and total_estimated_disk_mb attributes
|
|
61
|
+
"""
|
|
62
|
+
if not report.has_unsafe_statements:
|
|
63
|
+
console.print("[green]✓ SAFE[/green]", end=" ")
|
|
64
|
+
else:
|
|
65
|
+
unsafe_msg = f"[red]❌ UNSAFE ({report.unsafe_count} statements)[/red]"
|
|
66
|
+
console.print(unsafe_msg, end=" ")
|
|
67
|
+
|
|
68
|
+
time_str = report.total_estimated_time_ms
|
|
69
|
+
disk_str = report.total_estimated_disk_mb
|
|
70
|
+
console.print(f"| Time: {time_str}ms | Disk: {disk_str:.1f}MB")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def ask_dry_run_execute_confirmation() -> bool:
|
|
74
|
+
"""Ask user to confirm real execution after dry-run-execute test.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
True if user confirms, False otherwise
|
|
78
|
+
"""
|
|
79
|
+
import typer
|
|
80
|
+
|
|
81
|
+
return typer.confirm("\n🔄 Proceed with real execution?", default=False)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def extract_sql_statements_from_migration(migration_class) -> list[str]:
|
|
85
|
+
"""Extract SQL statements from a migration's up() method.
|
|
86
|
+
|
|
87
|
+
This is a helper that attempts to extract SQL statements from migration
|
|
88
|
+
code by inspecting the migration object. This is limited and approximate
|
|
89
|
+
since migrations use self.execute() calls.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
migration_class: Migration class (not instance)
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
List of SQL statement strings (may be approximate/incomplete)
|
|
96
|
+
"""
|
|
97
|
+
# This is a placeholder - in Day 2 implementation, we would:
|
|
98
|
+
# 1. Create a migration instance with a mock connection
|
|
99
|
+
# 2. Track calls to self.execute()
|
|
100
|
+
# 3. Extract the SQL statements
|
|
101
|
+
# For now, return empty list - actual implementation in Day 2
|
|
102
|
+
return []
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def display_dry_run_header(mode: str) -> None:
|
|
106
|
+
"""Display header for dry-run analysis.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
mode: Either "analysis" for --dry-run or "testing" for --dry-run-execute
|
|
110
|
+
"""
|
|
111
|
+
if mode == "testing":
|
|
112
|
+
msg = "[cyan]🧪 Executing migrations in SAVEPOINT (guaranteed rollback)...[/cyan]"
|
|
113
|
+
console.print(msg + "\n")
|
|
114
|
+
else:
|
|
115
|
+
msg = "[cyan]🔍 Analyzing migrations without execution...[/cyan]"
|
|
116
|
+
console.print(msg + "\n")
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""Output formatting for linting results.
|
|
2
|
+
|
|
3
|
+
This module provides functions to format LintReport results in various
|
|
4
|
+
output formats (table, JSON, CSV) for the lint CLI command.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Literal
|
|
10
|
+
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
from rich.table import Table
|
|
13
|
+
|
|
14
|
+
from confiture.models.lint import LintReport, LintSeverity
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def format_lint_report(
|
|
18
|
+
report: LintReport,
|
|
19
|
+
format_type: Literal["table", "json", "csv"] = "table",
|
|
20
|
+
console: Console | None = None,
|
|
21
|
+
) -> str:
|
|
22
|
+
"""Format a LintReport in the specified format.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
report: LintReport to format
|
|
26
|
+
format_type: Output format (table, json, or csv)
|
|
27
|
+
console: Rich Console instance for table rendering
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Formatted report as string
|
|
31
|
+
"""
|
|
32
|
+
if format_type == "json":
|
|
33
|
+
return format_json(report)
|
|
34
|
+
elif format_type == "csv":
|
|
35
|
+
return format_csv(report)
|
|
36
|
+
else: # table
|
|
37
|
+
if console is None:
|
|
38
|
+
console = Console()
|
|
39
|
+
format_table(report, console)
|
|
40
|
+
return ""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _severity_string(severity: LintSeverity) -> str:
|
|
44
|
+
"""Format severity level with color.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
severity: Severity level to format
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
Colored severity string for Rich output
|
|
51
|
+
"""
|
|
52
|
+
if severity == LintSeverity.ERROR:
|
|
53
|
+
return "[red]ERROR[/red]"
|
|
54
|
+
elif severity == LintSeverity.WARNING:
|
|
55
|
+
return "[yellow]WARNING[/yellow]"
|
|
56
|
+
return "[blue]INFO[/blue]"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def format_table(report: LintReport, console: Console) -> None:
|
|
60
|
+
"""Display LintReport as a rich table.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
report: LintReport to display
|
|
64
|
+
console: Rich Console instance for rendering
|
|
65
|
+
"""
|
|
66
|
+
# Summary section
|
|
67
|
+
console.print(f"\n[bold]Schema Linting Results[/bold] - {report.schema_name}")
|
|
68
|
+
console.print(f"Tables: {report.tables_checked} checked")
|
|
69
|
+
console.print(f"Columns: {report.columns_checked} checked")
|
|
70
|
+
console.print(f"Time: {report.execution_time_ms}ms\n")
|
|
71
|
+
|
|
72
|
+
if not report.violations:
|
|
73
|
+
console.print("[green]✅ No violations found![/green]\n")
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
# Violations table
|
|
77
|
+
table = Table(title="Violations")
|
|
78
|
+
table.add_column("Severity", style="bold")
|
|
79
|
+
table.add_column("Rule", style="cyan")
|
|
80
|
+
table.add_column("Location", style="yellow")
|
|
81
|
+
table.add_column("Message", style="white")
|
|
82
|
+
|
|
83
|
+
for violation in sorted(
|
|
84
|
+
report.violations,
|
|
85
|
+
key=lambda v: (
|
|
86
|
+
v.severity == LintSeverity.ERROR,
|
|
87
|
+
v.severity == LintSeverity.WARNING,
|
|
88
|
+
),
|
|
89
|
+
reverse=True,
|
|
90
|
+
):
|
|
91
|
+
table.add_row(
|
|
92
|
+
_severity_string(violation.severity),
|
|
93
|
+
violation.rule_name,
|
|
94
|
+
violation.location,
|
|
95
|
+
violation.message,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
console.print(table)
|
|
99
|
+
|
|
100
|
+
# Summary counts
|
|
101
|
+
console.print("\n[bold]Summary:[/bold]")
|
|
102
|
+
console.print(f" {report.errors_count} errors")
|
|
103
|
+
console.print(f" {report.warnings_count} warnings")
|
|
104
|
+
console.print(f" {report.info_count} info")
|
|
105
|
+
|
|
106
|
+
# Suggested fixes (if any)
|
|
107
|
+
fixes = [v for v in report.violations if v.suggested_fix]
|
|
108
|
+
if fixes:
|
|
109
|
+
console.print("\n[bold]Suggested Fixes:[/bold]")
|
|
110
|
+
for violation in fixes:
|
|
111
|
+
console.print(f" {violation.location}: {violation.suggested_fix}")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def format_json(report: LintReport) -> str:
|
|
115
|
+
"""Format LintReport as JSON.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
report: LintReport to format
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
JSON string representation
|
|
122
|
+
"""
|
|
123
|
+
data = {
|
|
124
|
+
"schema_name": report.schema_name,
|
|
125
|
+
"tables_checked": report.tables_checked,
|
|
126
|
+
"columns_checked": report.columns_checked,
|
|
127
|
+
"execution_time_ms": report.execution_time_ms,
|
|
128
|
+
"violations": {
|
|
129
|
+
"total": len(report.violations),
|
|
130
|
+
"errors": report.errors_count,
|
|
131
|
+
"warnings": report.warnings_count,
|
|
132
|
+
"info": report.info_count,
|
|
133
|
+
"items": [
|
|
134
|
+
{
|
|
135
|
+
"rule": v.rule_name,
|
|
136
|
+
"severity": v.severity.value,
|
|
137
|
+
"location": v.location,
|
|
138
|
+
"message": v.message,
|
|
139
|
+
"suggested_fix": v.suggested_fix,
|
|
140
|
+
}
|
|
141
|
+
for v in report.violations
|
|
142
|
+
],
|
|
143
|
+
},
|
|
144
|
+
}
|
|
145
|
+
return json.dumps(data, indent=2)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def format_csv(report: LintReport) -> str:
|
|
149
|
+
"""Format LintReport as CSV.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
report: LintReport to format
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
CSV string representation
|
|
156
|
+
"""
|
|
157
|
+
lines = [
|
|
158
|
+
"rule_name,severity,location,message,suggested_fix",
|
|
159
|
+
]
|
|
160
|
+
|
|
161
|
+
for violation in report.violations:
|
|
162
|
+
# Escape quotes in fields
|
|
163
|
+
rule = violation.rule_name.replace('"', '""')
|
|
164
|
+
severity = violation.severity.value
|
|
165
|
+
location = violation.location.replace('"', '""')
|
|
166
|
+
message = violation.message.replace('"', '""')
|
|
167
|
+
fix = (violation.suggested_fix or "").replace('"', '""')
|
|
168
|
+
|
|
169
|
+
# Quote fields that contain commas
|
|
170
|
+
rule = f'"{rule}"' if "," in rule else rule
|
|
171
|
+
location = f'"{location}"' if "," in location else location
|
|
172
|
+
message = f'"{message}"' if "," in message else message
|
|
173
|
+
fix = f'"{fix}"' if "," in fix else fix
|
|
174
|
+
|
|
175
|
+
lines.append(f"{rule},{severity},{location},{message},{fix}")
|
|
176
|
+
|
|
177
|
+
return "\n".join(lines)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def save_report(
|
|
181
|
+
report: LintReport,
|
|
182
|
+
output_path: Path,
|
|
183
|
+
format_type: Literal["json", "csv"] = "json",
|
|
184
|
+
) -> None:
|
|
185
|
+
"""Save LintReport to a file.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
report: LintReport to save
|
|
189
|
+
output_path: Path to save to
|
|
190
|
+
format_type: Output format (json or csv)
|
|
191
|
+
"""
|
|
192
|
+
content = format_json(report) if format_type == "json" else format_csv(report)
|
|
193
|
+
output_path.write_text(content)
|