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.
Files changed (124) hide show
  1. confiture/__init__.py +48 -0
  2. confiture/_core.cpython-311-darwin.so +0 -0
  3. confiture/cli/__init__.py +0 -0
  4. confiture/cli/dry_run.py +116 -0
  5. confiture/cli/lint_formatter.py +193 -0
  6. confiture/cli/main.py +1893 -0
  7. confiture/config/__init__.py +0 -0
  8. confiture/config/environment.py +263 -0
  9. confiture/core/__init__.py +51 -0
  10. confiture/core/anonymization/__init__.py +0 -0
  11. confiture/core/anonymization/audit.py +485 -0
  12. confiture/core/anonymization/benchmarking.py +372 -0
  13. confiture/core/anonymization/breach_notification.py +652 -0
  14. confiture/core/anonymization/compliance.py +617 -0
  15. confiture/core/anonymization/composer.py +298 -0
  16. confiture/core/anonymization/data_subject_rights.py +669 -0
  17. confiture/core/anonymization/factory.py +319 -0
  18. confiture/core/anonymization/governance.py +737 -0
  19. confiture/core/anonymization/performance.py +1092 -0
  20. confiture/core/anonymization/profile.py +284 -0
  21. confiture/core/anonymization/registry.py +195 -0
  22. confiture/core/anonymization/security/kms_manager.py +547 -0
  23. confiture/core/anonymization/security/lineage.py +888 -0
  24. confiture/core/anonymization/security/token_store.py +686 -0
  25. confiture/core/anonymization/strategies/__init__.py +41 -0
  26. confiture/core/anonymization/strategies/address.py +359 -0
  27. confiture/core/anonymization/strategies/credit_card.py +374 -0
  28. confiture/core/anonymization/strategies/custom.py +161 -0
  29. confiture/core/anonymization/strategies/date.py +218 -0
  30. confiture/core/anonymization/strategies/differential_privacy.py +398 -0
  31. confiture/core/anonymization/strategies/email.py +141 -0
  32. confiture/core/anonymization/strategies/format_preserving_encryption.py +310 -0
  33. confiture/core/anonymization/strategies/hash.py +150 -0
  34. confiture/core/anonymization/strategies/ip_address.py +235 -0
  35. confiture/core/anonymization/strategies/masking_retention.py +252 -0
  36. confiture/core/anonymization/strategies/name.py +298 -0
  37. confiture/core/anonymization/strategies/phone.py +119 -0
  38. confiture/core/anonymization/strategies/preserve.py +85 -0
  39. confiture/core/anonymization/strategies/redact.py +101 -0
  40. confiture/core/anonymization/strategies/salted_hashing.py +322 -0
  41. confiture/core/anonymization/strategies/text_redaction.py +183 -0
  42. confiture/core/anonymization/strategies/tokenization.py +334 -0
  43. confiture/core/anonymization/strategy.py +241 -0
  44. confiture/core/anonymization/syncer_audit.py +357 -0
  45. confiture/core/blue_green.py +683 -0
  46. confiture/core/builder.py +500 -0
  47. confiture/core/checksum.py +358 -0
  48. confiture/core/connection.py +184 -0
  49. confiture/core/differ.py +522 -0
  50. confiture/core/drift.py +564 -0
  51. confiture/core/dry_run.py +182 -0
  52. confiture/core/health.py +313 -0
  53. confiture/core/hooks/__init__.py +87 -0
  54. confiture/core/hooks/base.py +232 -0
  55. confiture/core/hooks/context.py +146 -0
  56. confiture/core/hooks/execution_strategies.py +57 -0
  57. confiture/core/hooks/observability.py +220 -0
  58. confiture/core/hooks/phases.py +53 -0
  59. confiture/core/hooks/registry.py +295 -0
  60. confiture/core/large_tables.py +775 -0
  61. confiture/core/linting/__init__.py +70 -0
  62. confiture/core/linting/composer.py +192 -0
  63. confiture/core/linting/libraries/__init__.py +17 -0
  64. confiture/core/linting/libraries/gdpr.py +168 -0
  65. confiture/core/linting/libraries/general.py +184 -0
  66. confiture/core/linting/libraries/hipaa.py +144 -0
  67. confiture/core/linting/libraries/pci_dss.py +104 -0
  68. confiture/core/linting/libraries/sox.py +120 -0
  69. confiture/core/linting/schema_linter.py +491 -0
  70. confiture/core/linting/versioning.py +151 -0
  71. confiture/core/locking.py +389 -0
  72. confiture/core/migration_generator.py +298 -0
  73. confiture/core/migrator.py +882 -0
  74. confiture/core/observability/__init__.py +44 -0
  75. confiture/core/observability/audit.py +323 -0
  76. confiture/core/observability/logging.py +187 -0
  77. confiture/core/observability/metrics.py +174 -0
  78. confiture/core/observability/tracing.py +192 -0
  79. confiture/core/pg_version.py +418 -0
  80. confiture/core/pool.py +406 -0
  81. confiture/core/risk/__init__.py +39 -0
  82. confiture/core/risk/predictor.py +188 -0
  83. confiture/core/risk/scoring.py +248 -0
  84. confiture/core/rollback_generator.py +388 -0
  85. confiture/core/schema_analyzer.py +769 -0
  86. confiture/core/schema_to_schema.py +590 -0
  87. confiture/core/security/__init__.py +32 -0
  88. confiture/core/security/logging.py +201 -0
  89. confiture/core/security/validation.py +416 -0
  90. confiture/core/signals.py +371 -0
  91. confiture/core/syncer.py +540 -0
  92. confiture/exceptions.py +192 -0
  93. confiture/integrations/__init__.py +0 -0
  94. confiture/models/__init__.py +24 -0
  95. confiture/models/lint.py +193 -0
  96. confiture/models/migration.py +265 -0
  97. confiture/models/schema.py +203 -0
  98. confiture/models/sql_file_migration.py +225 -0
  99. confiture/scenarios/__init__.py +36 -0
  100. confiture/scenarios/compliance.py +586 -0
  101. confiture/scenarios/ecommerce.py +199 -0
  102. confiture/scenarios/financial.py +253 -0
  103. confiture/scenarios/healthcare.py +315 -0
  104. confiture/scenarios/multi_tenant.py +340 -0
  105. confiture/scenarios/saas.py +295 -0
  106. confiture/testing/FRAMEWORK_API.md +722 -0
  107. confiture/testing/__init__.py +100 -0
  108. confiture/testing/fixtures/__init__.py +11 -0
  109. confiture/testing/fixtures/data_validator.py +229 -0
  110. confiture/testing/fixtures/migration_runner.py +167 -0
  111. confiture/testing/fixtures/schema_snapshotter.py +352 -0
  112. confiture/testing/frameworks/__init__.py +10 -0
  113. confiture/testing/frameworks/mutation.py +587 -0
  114. confiture/testing/frameworks/performance.py +479 -0
  115. confiture/testing/loader.py +225 -0
  116. confiture/testing/pytest/__init__.py +38 -0
  117. confiture/testing/pytest_plugin.py +190 -0
  118. confiture/testing/sandbox.py +304 -0
  119. confiture/testing/utils/__init__.py +0 -0
  120. fraiseql_confiture-0.3.7.dist-info/METADATA +438 -0
  121. fraiseql_confiture-0.3.7.dist-info/RECORD +124 -0
  122. fraiseql_confiture-0.3.7.dist-info/WHEEL +4 -0
  123. fraiseql_confiture-0.3.7.dist-info/entry_points.txt +4 -0
  124. fraiseql_confiture-0.3.7.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,371 @@
1
+ """Signal handling for graceful shutdown.
2
+
3
+ Provides utilities for graceful shutdown in containerized environments,
4
+ particularly for Kubernetes which sends SIGTERM before killing pods.
5
+ """
6
+
7
+ import logging
8
+ import signal
9
+ import threading
10
+ from collections.abc import Callable
11
+ from dataclasses import dataclass, field
12
+ from enum import Enum
13
+ from typing import Any
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class ShutdownState(Enum):
19
+ """State of the shutdown handler."""
20
+
21
+ RUNNING = "running"
22
+ SHUTTING_DOWN = "shutting_down"
23
+ STOPPED = "stopped"
24
+
25
+
26
+ @dataclass
27
+ class ShutdownContext:
28
+ """Context passed to cleanup handlers."""
29
+
30
+ signal_received: str
31
+ current_operation: str | None = None
32
+ migration_in_progress: str | None = None
33
+ metadata: dict[str, Any] = field(default_factory=dict)
34
+
35
+
36
+ class GracefulShutdown:
37
+ """Handle graceful shutdown signals.
38
+
39
+ Registers signal handlers for SIGTERM and SIGINT to enable
40
+ graceful shutdown in containerized environments.
41
+
42
+ Example:
43
+ >>> shutdown = GracefulShutdown()
44
+ >>> shutdown.register()
45
+ >>> shutdown.add_cleanup(lambda ctx: print("Cleaning up..."))
46
+ >>> while not shutdown.should_stop:
47
+ ... # Do work
48
+ ... pass
49
+
50
+ Note:
51
+ In Kubernetes, pods receive SIGTERM before being killed.
52
+ This handler allows migrations to complete the current
53
+ operation before exiting.
54
+ """
55
+
56
+ def __init__(self, timeout: float = 30.0):
57
+ """Initialize graceful shutdown handler.
58
+
59
+ Args:
60
+ timeout: Maximum seconds to wait for cleanup handlers
61
+ """
62
+ self._should_stop = False
63
+ self._state = ShutdownState.RUNNING
64
+ self._cleanup_handlers: list[Callable[[ShutdownContext], None]] = []
65
+ self._current_operation: str | None = None
66
+ self._migration_in_progress: str | None = None
67
+ self._timeout = timeout
68
+ self._lock = threading.Lock()
69
+ self._registered = False
70
+ self._original_handlers: dict[int, Any] = {}
71
+
72
+ @property
73
+ def should_stop(self) -> bool:
74
+ """Check if shutdown has been requested."""
75
+ return self._should_stop
76
+
77
+ @property
78
+ def state(self) -> ShutdownState:
79
+ """Get current shutdown state."""
80
+ return self._state
81
+
82
+ @property
83
+ def is_running(self) -> bool:
84
+ """Check if still in running state."""
85
+ return self._state == ShutdownState.RUNNING
86
+
87
+ def register(self) -> "GracefulShutdown":
88
+ """Register signal handlers.
89
+
90
+ Returns:
91
+ Self for chaining
92
+ """
93
+ if self._registered:
94
+ logger.warning("Graceful shutdown handlers already registered")
95
+ return self
96
+
97
+ # Store original handlers
98
+ self._original_handlers[signal.SIGTERM] = signal.signal(signal.SIGTERM, self._handle_signal)
99
+ self._original_handlers[signal.SIGINT] = signal.signal(signal.SIGINT, self._handle_signal)
100
+
101
+ self._registered = True
102
+ logger.info("Graceful shutdown handlers registered")
103
+ return self
104
+
105
+ def unregister(self) -> None:
106
+ """Restore original signal handlers."""
107
+ if not self._registered:
108
+ return
109
+
110
+ for sig, handler in self._original_handlers.items():
111
+ signal.signal(sig, handler)
112
+
113
+ self._original_handlers.clear()
114
+ self._registered = False
115
+ logger.info("Graceful shutdown handlers unregistered")
116
+
117
+ def add_cleanup(self, handler: Callable[[ShutdownContext], None], priority: int = 0) -> None:
118
+ """Add cleanup handler to run on shutdown.
119
+
120
+ Args:
121
+ handler: Callable that receives ShutdownContext
122
+ priority: Higher priority handlers run first (default: 0)
123
+
124
+ Example:
125
+ >>> def release_lock(ctx):
126
+ ... print(f"Releasing lock during {ctx.signal_received}")
127
+ >>> shutdown.add_cleanup(release_lock, priority=10)
128
+ """
129
+ with self._lock:
130
+ # Insert maintaining priority order (highest first)
131
+ self._cleanup_handlers.append((priority, handler))
132
+ self._cleanup_handlers.sort(key=lambda x: -x[0])
133
+
134
+ def remove_cleanup(self, handler: Callable[[ShutdownContext], None]) -> bool:
135
+ """Remove a cleanup handler.
136
+
137
+ Args:
138
+ handler: The handler to remove
139
+
140
+ Returns:
141
+ True if handler was found and removed
142
+ """
143
+ with self._lock:
144
+ for i, (_, h) in enumerate(self._cleanup_handlers):
145
+ if h == handler:
146
+ self._cleanup_handlers.pop(i)
147
+ return True
148
+ return False
149
+
150
+ def set_current_operation(self, operation: str | None) -> None:
151
+ """Set the current operation being performed.
152
+
153
+ Args:
154
+ operation: Description of current operation, or None if idle
155
+ """
156
+ self._current_operation = operation
157
+
158
+ def set_migration_in_progress(self, migration: str | None) -> None:
159
+ """Set the current migration being executed.
160
+
161
+ Args:
162
+ migration: Migration name/version, or None if not migrating
163
+ """
164
+ self._migration_in_progress = migration
165
+
166
+ def _handle_signal(self, signum: int, frame: Any) -> None: # noqa: ARG002
167
+ """Handle shutdown signal.
168
+
169
+ Args:
170
+ signum: Signal number
171
+ frame: Current stack frame
172
+ """
173
+ sig_name = signal.Signals(signum).name
174
+ logger.warning(f"Received {sig_name}, initiating graceful shutdown...")
175
+
176
+ with self._lock:
177
+ if self._state != ShutdownState.RUNNING:
178
+ logger.warning(f"Already in {self._state.value} state, ignoring signal")
179
+ return
180
+
181
+ self._should_stop = True
182
+ self._state = ShutdownState.SHUTTING_DOWN
183
+
184
+ # Create context for cleanup handlers
185
+ context = ShutdownContext(
186
+ signal_received=sig_name,
187
+ current_operation=self._current_operation,
188
+ migration_in_progress=self._migration_in_progress,
189
+ )
190
+
191
+ # Run cleanup handlers
192
+ self._run_cleanup_handlers(context)
193
+
194
+ self._state = ShutdownState.STOPPED
195
+ logger.info("Graceful shutdown complete")
196
+
197
+ def _run_cleanup_handlers(self, context: ShutdownContext) -> None:
198
+ """Run all registered cleanup handlers.
199
+
200
+ Args:
201
+ context: Shutdown context to pass to handlers
202
+ """
203
+ with self._lock:
204
+ handlers = list(self._cleanup_handlers)
205
+
206
+ for priority, handler in handlers:
207
+ try:
208
+ logger.debug(f"Running cleanup handler (priority={priority})")
209
+ handler(context)
210
+ except Exception as e:
211
+ logger.error(f"Cleanup handler failed: {e}")
212
+
213
+ def request_shutdown(self) -> None:
214
+ """Programmatically request shutdown.
215
+
216
+ Useful for testing or triggering shutdown from code.
217
+ """
218
+ self._handle_signal(signal.SIGTERM, None)
219
+
220
+ def __enter__(self) -> "GracefulShutdown":
221
+ """Context manager entry."""
222
+ return self.register()
223
+
224
+ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
225
+ """Context manager exit."""
226
+ self.unregister()
227
+
228
+
229
+ # Global instance for convenience
230
+ _shutdown: GracefulShutdown | None = None
231
+
232
+
233
+ def get_shutdown_handler() -> GracefulShutdown:
234
+ """Get or create the global shutdown handler.
235
+
236
+ Returns:
237
+ Global GracefulShutdown instance
238
+ """
239
+ global _shutdown
240
+ if _shutdown is None:
241
+ _shutdown = GracefulShutdown()
242
+ return _shutdown
243
+
244
+
245
+ def register_shutdown() -> GracefulShutdown:
246
+ """Register and return global shutdown handler.
247
+
248
+ Returns:
249
+ Registered GracefulShutdown instance
250
+
251
+ Example:
252
+ >>> shutdown = register_shutdown()
253
+ >>> shutdown.add_cleanup(my_cleanup_func)
254
+ """
255
+ return get_shutdown_handler().register()
256
+
257
+
258
+ def add_cleanup(handler: Callable[[ShutdownContext], None], priority: int = 0) -> None:
259
+ """Add cleanup handler to global shutdown handler.
260
+
261
+ Args:
262
+ handler: Cleanup function receiving ShutdownContext
263
+ priority: Higher priority handlers run first
264
+ """
265
+ get_shutdown_handler().add_cleanup(handler, priority)
266
+
267
+
268
+ def should_stop() -> bool:
269
+ """Check if shutdown has been requested.
270
+
271
+ Returns:
272
+ True if shutdown is in progress
273
+ """
274
+ return get_shutdown_handler().should_stop
275
+
276
+
277
+ def set_current_operation(operation: str | None) -> None:
278
+ """Set current operation on global handler.
279
+
280
+ Args:
281
+ operation: Description of current operation
282
+ """
283
+ get_shutdown_handler().set_current_operation(operation)
284
+
285
+
286
+ def set_migration_in_progress(migration: str | None) -> None:
287
+ """Set current migration on global handler.
288
+
289
+ Args:
290
+ migration: Migration name/version
291
+ """
292
+ get_shutdown_handler().set_migration_in_progress(migration)
293
+
294
+
295
+ class MigrationShutdownGuard:
296
+ """Context manager for safe migration execution.
297
+
298
+ Tracks migration state and ensures clean shutdown if
299
+ interrupted mid-migration.
300
+
301
+ Example:
302
+ >>> with MigrationShutdownGuard("001_create_users", lock, connection):
303
+ ... migration.up()
304
+ """
305
+
306
+ def __init__(
307
+ self,
308
+ migration_name: str,
309
+ lock: Any | None = None,
310
+ connection: Any | None = None,
311
+ ):
312
+ """Initialize migration guard.
313
+
314
+ Args:
315
+ migration_name: Name of the migration being run
316
+ lock: Optional lock to release on shutdown
317
+ connection: Optional connection to rollback on shutdown
318
+ """
319
+ self.migration_name = migration_name
320
+ self.lock = lock
321
+ self.connection = connection
322
+ self._shutdown = get_shutdown_handler()
323
+ self._cleanup_registered = False
324
+
325
+ def __enter__(self) -> "MigrationShutdownGuard":
326
+ """Enter migration context."""
327
+ self._shutdown.set_migration_in_progress(self.migration_name)
328
+
329
+ # Register cleanup handler
330
+ def cleanup(ctx: ShutdownContext) -> None: # noqa: ARG001
331
+ logger.warning(f"Shutdown during migration {self.migration_name}, cleaning up...")
332
+
333
+ # Release lock if held
334
+ if self.lock is not None:
335
+ try:
336
+ if hasattr(self.lock, "release"):
337
+ self.lock.release()
338
+ elif hasattr(self.lock, "_release_lock"):
339
+ self.lock._release_lock()
340
+ logger.info("Lock released during shutdown")
341
+ except Exception as e:
342
+ logger.error(f"Failed to release lock: {e}")
343
+
344
+ # Rollback connection if in transaction
345
+ if self.connection is not None:
346
+ try:
347
+ self.connection.rollback()
348
+ logger.info("Transaction rolled back during shutdown")
349
+ except Exception as e:
350
+ logger.error(f"Failed to rollback transaction: {e}")
351
+
352
+ self._shutdown.add_cleanup(cleanup, priority=100)
353
+ self._cleanup_registered = True
354
+
355
+ return self
356
+
357
+ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
358
+ """Exit migration context."""
359
+ self._shutdown.set_migration_in_progress(None)
360
+
361
+ # Note: We don't remove the cleanup handler here because
362
+ # if shutdown is in progress, it may still need to run.
363
+ # Handlers are cleared when the process exits anyway.
364
+
365
+ def check_shutdown(self) -> bool:
366
+ """Check if shutdown was requested.
367
+
368
+ Returns:
369
+ True if should stop
370
+ """
371
+ return self._shutdown.should_stop