fraiseql-confiture 0.3.4__cp311-cp311-win_amd64.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.cp311-win_amd64.pyd +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 +1656 -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 +132 -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 +793 -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 +0 -0
- confiture/models/lint.py +193 -0
- confiture/models/migration.py +180 -0
- confiture/models/schema.py +203 -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 +38 -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/utils/__init__.py +0 -0
- fraiseql_confiture-0.3.4.dist-info/METADATA +438 -0
- fraiseql_confiture-0.3.4.dist-info/RECORD +119 -0
- fraiseql_confiture-0.3.4.dist-info/WHEEL +4 -0
- fraiseql_confiture-0.3.4.dist-info/entry_points.txt +2 -0
- fraiseql_confiture-0.3.4.dist-info/licenses/LICENSE +21 -0
|
File without changes
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
"""Environment configuration management
|
|
2
|
+
|
|
3
|
+
Handles loading and validation of environment-specific configuration from YAML files.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import yaml
|
|
10
|
+
from pydantic import BaseModel, Field, field_validator
|
|
11
|
+
|
|
12
|
+
from confiture.exceptions import ConfigurationError
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BuildConfig(BaseModel):
|
|
16
|
+
"""Build configuration options."""
|
|
17
|
+
|
|
18
|
+
sort_mode: str = "alphabetical" # Options: alphabetical, hex
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class LockingConfig(BaseModel):
|
|
22
|
+
"""Distributed locking configuration.
|
|
23
|
+
|
|
24
|
+
Controls how Confiture acquires locks to prevent concurrent migrations
|
|
25
|
+
in multi-pod Kubernetes deployments.
|
|
26
|
+
|
|
27
|
+
Attributes:
|
|
28
|
+
enabled: Whether locking is enabled (default: True)
|
|
29
|
+
timeout_ms: Lock acquisition timeout in milliseconds (default: 30000)
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
enabled: bool = True
|
|
33
|
+
timeout_ms: int = 30000 # 30 seconds default
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class MigrationConfig(BaseModel):
|
|
37
|
+
"""Migration configuration options.
|
|
38
|
+
|
|
39
|
+
Attributes:
|
|
40
|
+
strict_mode: Whether to fail on warnings/notices (default: False)
|
|
41
|
+
locking: Distributed locking configuration
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
strict_mode: bool = False # Whether to fail on warnings/notices
|
|
45
|
+
locking: LockingConfig = Field(default_factory=LockingConfig)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class DirectoryConfig(BaseModel):
|
|
49
|
+
"""Directory configuration with pattern matching."""
|
|
50
|
+
|
|
51
|
+
path: str
|
|
52
|
+
recursive: bool = True
|
|
53
|
+
include: list[str] = Field(default_factory=lambda: ["**/*.sql"])
|
|
54
|
+
exclude: list[str] = Field(default_factory=list)
|
|
55
|
+
auto_discover: bool = True
|
|
56
|
+
order: int = 0
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class DatabaseConfig(BaseModel):
|
|
60
|
+
"""Database connection configuration.
|
|
61
|
+
|
|
62
|
+
Can be initialized from a connection URL or individual parameters.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
host: str = "localhost"
|
|
66
|
+
port: int = 5432
|
|
67
|
+
database: str = "postgres"
|
|
68
|
+
user: str = "postgres"
|
|
69
|
+
password: str = ""
|
|
70
|
+
|
|
71
|
+
@classmethod
|
|
72
|
+
def from_url(cls, url: str) -> "DatabaseConfig":
|
|
73
|
+
"""Parse database configuration from PostgreSQL URL.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
url: PostgreSQL connection URL (postgresql://user:pass@host:port/dbname)
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
DatabaseConfig instance
|
|
80
|
+
|
|
81
|
+
Example:
|
|
82
|
+
>>> config = DatabaseConfig.from_url("postgresql://user:pass@localhost:5432/mydb")
|
|
83
|
+
>>> config.host
|
|
84
|
+
'localhost'
|
|
85
|
+
"""
|
|
86
|
+
import re
|
|
87
|
+
|
|
88
|
+
# Parse URL: postgresql://user:pass@host:port/dbname
|
|
89
|
+
pattern = r"(?:postgresql|postgres)://(?:([^:]+):([^@]+)@)?([^:/]+)(?::(\d+))?/(.+)"
|
|
90
|
+
match = re.match(pattern, url)
|
|
91
|
+
|
|
92
|
+
if not match:
|
|
93
|
+
raise ValueError(f"Invalid PostgreSQL URL: {url}")
|
|
94
|
+
|
|
95
|
+
user, password, host, port, database = match.groups()
|
|
96
|
+
|
|
97
|
+
return cls(
|
|
98
|
+
host=host or "localhost",
|
|
99
|
+
port=int(port) if port else 5432,
|
|
100
|
+
database=database,
|
|
101
|
+
user=user or "postgres",
|
|
102
|
+
password=password or "",
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
def to_dict(self) -> dict[str, Any]:
|
|
106
|
+
"""Convert to dictionary for use with create_connection."""
|
|
107
|
+
return {
|
|
108
|
+
"database": {
|
|
109
|
+
"host": self.host,
|
|
110
|
+
"port": self.port,
|
|
111
|
+
"database": self.database,
|
|
112
|
+
"user": self.user,
|
|
113
|
+
"password": self.password,
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class Environment(BaseModel):
|
|
119
|
+
"""Environment configuration
|
|
120
|
+
|
|
121
|
+
Loaded from db/environments/{env_name}.yaml files.
|
|
122
|
+
|
|
123
|
+
Attributes:
|
|
124
|
+
name: Environment name (e.g., "local", "production")
|
|
125
|
+
database_url: PostgreSQL connection URL
|
|
126
|
+
include_dirs: Directories to include when building schema (supports both string and dict formats)
|
|
127
|
+
exclude_dirs: Directories to exclude from schema build
|
|
128
|
+
migration_table: Table name for tracking migrations
|
|
129
|
+
auto_backup: Whether to automatically backup before migrations
|
|
130
|
+
require_confirmation: Whether to require user confirmation for risky operations
|
|
131
|
+
build: Build configuration options
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
name: str
|
|
135
|
+
database_url: str
|
|
136
|
+
include_dirs: list[str | DirectoryConfig]
|
|
137
|
+
exclude_dirs: list[str] = Field(default_factory=list)
|
|
138
|
+
migration_table: str = "confiture_migrations"
|
|
139
|
+
auto_backup: bool = True
|
|
140
|
+
require_confirmation: bool = True
|
|
141
|
+
build: BuildConfig = Field(default_factory=BuildConfig)
|
|
142
|
+
migration: MigrationConfig = Field(default_factory=MigrationConfig)
|
|
143
|
+
|
|
144
|
+
@property
|
|
145
|
+
def database(self) -> DatabaseConfig:
|
|
146
|
+
"""Get database configuration from database_url.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
DatabaseConfig instance
|
|
150
|
+
"""
|
|
151
|
+
return DatabaseConfig.from_url(self.database_url)
|
|
152
|
+
|
|
153
|
+
@field_validator("database_url")
|
|
154
|
+
@classmethod
|
|
155
|
+
def validate_database_url(cls, v: str) -> str:
|
|
156
|
+
"""Validate PostgreSQL connection URL format"""
|
|
157
|
+
if not v.startswith(("postgresql://", "postgres://")):
|
|
158
|
+
raise ValueError(
|
|
159
|
+
f"Invalid database_url: must start with postgresql:// or postgres://, got: {v}"
|
|
160
|
+
)
|
|
161
|
+
return v
|
|
162
|
+
|
|
163
|
+
@classmethod
|
|
164
|
+
def load(cls, env_name: str, project_dir: Path | None = None) -> "Environment":
|
|
165
|
+
"""Load environment configuration from YAML file
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
env_name: Environment name (e.g., "local", "production")
|
|
169
|
+
project_dir: Project root directory. If None, uses current directory.
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
Environment configuration object
|
|
173
|
+
|
|
174
|
+
Raises:
|
|
175
|
+
ConfigurationError: If config file not found, invalid, or missing required fields
|
|
176
|
+
|
|
177
|
+
Example:
|
|
178
|
+
>>> env = Environment.load("local")
|
|
179
|
+
>>> print(env.database_url)
|
|
180
|
+
postgresql://localhost/myapp_local
|
|
181
|
+
"""
|
|
182
|
+
if project_dir is None:
|
|
183
|
+
project_dir = Path.cwd()
|
|
184
|
+
|
|
185
|
+
# Find config file
|
|
186
|
+
config_path = project_dir / "db" / "environments" / f"{env_name}.yaml"
|
|
187
|
+
|
|
188
|
+
if not config_path.exists():
|
|
189
|
+
raise ConfigurationError(
|
|
190
|
+
f"Environment config not found: {config_path}\n"
|
|
191
|
+
f"Expected: db/environments/{env_name}.yaml"
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
# Load YAML
|
|
195
|
+
try:
|
|
196
|
+
with open(config_path) as f:
|
|
197
|
+
data = yaml.safe_load(f)
|
|
198
|
+
except yaml.YAMLError as e:
|
|
199
|
+
raise ConfigurationError(f"Invalid YAML in {config_path}: {e}") from e
|
|
200
|
+
|
|
201
|
+
if not isinstance(data, dict):
|
|
202
|
+
raise ConfigurationError(
|
|
203
|
+
f"Invalid config format in {config_path}: expected dictionary, got {type(data)}"
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
# Validate required fields
|
|
207
|
+
if "database_url" not in data:
|
|
208
|
+
raise ConfigurationError(f"Missing required field 'database_url' in {config_path}")
|
|
209
|
+
|
|
210
|
+
if "include_dirs" not in data:
|
|
211
|
+
raise ConfigurationError(f"Missing required field 'include_dirs' in {config_path}")
|
|
212
|
+
|
|
213
|
+
# Resolve include_dirs paths to absolute
|
|
214
|
+
resolved_include_dirs: list[str | dict[str, Any]] = []
|
|
215
|
+
for include_item in data["include_dirs"]:
|
|
216
|
+
if isinstance(include_item, str):
|
|
217
|
+
# Simple string format - resolve to absolute path
|
|
218
|
+
abs_path = (project_dir / include_item).resolve()
|
|
219
|
+
if not abs_path.exists():
|
|
220
|
+
raise ConfigurationError(
|
|
221
|
+
f"Include directory does not exist: {abs_path}\nSpecified in {config_path}"
|
|
222
|
+
)
|
|
223
|
+
resolved_include_dirs.append(str(abs_path))
|
|
224
|
+
elif isinstance(include_item, dict):
|
|
225
|
+
# Dict format - resolve the path field and keep as dict
|
|
226
|
+
path_str = include_item.get("path")
|
|
227
|
+
if not path_str:
|
|
228
|
+
raise ConfigurationError(
|
|
229
|
+
f"Missing 'path' field in include_dirs item: {include_item}\nIn {config_path}"
|
|
230
|
+
)
|
|
231
|
+
abs_path = (project_dir / path_str).resolve()
|
|
232
|
+
auto_discover = include_item.get("auto_discover", True)
|
|
233
|
+
if not abs_path.exists() and not auto_discover:
|
|
234
|
+
raise ConfigurationError(
|
|
235
|
+
f"Include directory does not exist: {abs_path}\nSpecified in {config_path}"
|
|
236
|
+
)
|
|
237
|
+
# Keep the dict format but with resolved path
|
|
238
|
+
resolved_item = include_item.copy()
|
|
239
|
+
resolved_item["path"] = str(abs_path)
|
|
240
|
+
resolved_include_dirs.append(resolved_item)
|
|
241
|
+
else:
|
|
242
|
+
raise ConfigurationError(
|
|
243
|
+
f"Invalid include_dirs item type: {type(include_item)}. Expected str or dict.\nIn {config_path}"
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
data["include_dirs"] = resolved_include_dirs
|
|
247
|
+
|
|
248
|
+
# Resolve exclude_dirs if present
|
|
249
|
+
if "exclude_dirs" in data:
|
|
250
|
+
exclude_dirs = []
|
|
251
|
+
for dir_path in data["exclude_dirs"]:
|
|
252
|
+
abs_path = (project_dir / dir_path).resolve()
|
|
253
|
+
exclude_dirs.append(str(abs_path))
|
|
254
|
+
data["exclude_dirs"] = exclude_dirs
|
|
255
|
+
|
|
256
|
+
# Set environment name
|
|
257
|
+
data["name"] = env_name
|
|
258
|
+
|
|
259
|
+
# Create Environment instance
|
|
260
|
+
try:
|
|
261
|
+
return cls(**data)
|
|
262
|
+
except Exception as e:
|
|
263
|
+
raise ConfigurationError(f"Invalid configuration in {config_path}: {e}") from e
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Core migration execution and schema building components."""
|
|
2
|
+
|
|
3
|
+
from confiture.core.dry_run import (
|
|
4
|
+
DryRunError,
|
|
5
|
+
DryRunExecutor,
|
|
6
|
+
DryRunResult,
|
|
7
|
+
)
|
|
8
|
+
from confiture.core.hooks import (
|
|
9
|
+
CircuitBreaker,
|
|
10
|
+
CircuitBreakerState,
|
|
11
|
+
ExecutionDAG,
|
|
12
|
+
Hook,
|
|
13
|
+
HookContext,
|
|
14
|
+
HookErrorStrategy,
|
|
15
|
+
HookExecutionEvent,
|
|
16
|
+
HookExecutionResult,
|
|
17
|
+
HookExecutionStatus,
|
|
18
|
+
HookExecutionStrategy,
|
|
19
|
+
HookExecutionTracer,
|
|
20
|
+
HookPhase,
|
|
21
|
+
HookRegistry,
|
|
22
|
+
HookResult,
|
|
23
|
+
PerformanceTrace,
|
|
24
|
+
RetryConfig,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
# Dry-run mode
|
|
29
|
+
"DryRunError",
|
|
30
|
+
"DryRunExecutor",
|
|
31
|
+
"DryRunResult",
|
|
32
|
+
# Hook system - Base
|
|
33
|
+
"Hook",
|
|
34
|
+
"HookContext",
|
|
35
|
+
"HookResult",
|
|
36
|
+
"HookPhase",
|
|
37
|
+
"HookRegistry",
|
|
38
|
+
# Hook system - Execution strategies
|
|
39
|
+
"HookExecutionStrategy",
|
|
40
|
+
"HookErrorStrategy",
|
|
41
|
+
"RetryConfig",
|
|
42
|
+
# Hook system - Observability
|
|
43
|
+
"HookExecutionStatus",
|
|
44
|
+
"HookExecutionEvent",
|
|
45
|
+
"HookExecutionResult",
|
|
46
|
+
"CircuitBreaker",
|
|
47
|
+
"CircuitBreakerState",
|
|
48
|
+
"HookExecutionTracer",
|
|
49
|
+
"ExecutionDAG",
|
|
50
|
+
"PerformanceTrace",
|
|
51
|
+
]
|
|
File without changes
|