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,319 @@
1
+ """Strategy factory for creating strategies from profile configurations.
2
+
3
+ Provides:
4
+ - Profile-based strategy creation
5
+ - Column-to-strategy mapping
6
+ - Strategy suggestion engine
7
+ - Factory caching and optimization
8
+ - Configuration validation
9
+
10
+ Enables declarative strategy configuration via profiles.
11
+ """
12
+
13
+ from dataclasses import dataclass, field
14
+ from typing import Any
15
+
16
+ from confiture.core.anonymization.registry import StrategyRegistry
17
+ from confiture.core.anonymization.strategy import AnonymizationStrategy
18
+
19
+
20
+ @dataclass
21
+ class StrategyProfile:
22
+ """Configuration profile mapping columns to strategies.
23
+
24
+ Attributes:
25
+ name: Profile name (e.g., "ecommerce", "healthcare")
26
+ seed: Global seed for all strategies (can be overridden per column)
27
+ columns: Dict mapping column name to strategy name/config
28
+ defaults: Default strategy for unmapped columns
29
+
30
+ Example:
31
+ >>> profile = StrategyProfile(
32
+ ... name="ecommerce",
33
+ ... seed=12345,
34
+ ... columns={
35
+ ... "customer_name": "name:firstname_lastname",
36
+ ... "email": "email_mask",
37
+ ... "phone": "phone_mask"
38
+ ... }
39
+ ... )
40
+ """
41
+
42
+ name: str
43
+ seed: int = 0
44
+ columns: dict[str, str] = field(default_factory=dict)
45
+ defaults: str = "preserve"
46
+
47
+
48
+ class StrategyFactory:
49
+ """Factory for creating strategies from profiles.
50
+
51
+ Creates strategy instances based on profile configuration,
52
+ with caching and validation.
53
+
54
+ Features:
55
+ - Profile-based strategy creation
56
+ - Column mapping
57
+ - Strategy caching
58
+ - Configuration validation
59
+ - Suggestion engine
60
+
61
+ Example:
62
+ >>> profile = StrategyProfile(
63
+ ... name="ecommerce",
64
+ ... columns={"name": "name", "email": "email"}
65
+ ... )
66
+ >>> factory = StrategyFactory(profile)
67
+ >>> strategy = factory.get_strategy("name")
68
+ >>> anonymized = strategy.anonymize("John Doe")
69
+ """
70
+
71
+ def __init__(self, profile: StrategyProfile):
72
+ """Initialize factory with profile.
73
+
74
+ Args:
75
+ profile: Strategy profile configuration
76
+ """
77
+ self.profile = profile
78
+ self._cache = {}
79
+ self._validate_profile()
80
+
81
+ def get_strategy(self, column_name: str) -> AnonymizationStrategy:
82
+ """Get strategy for column.
83
+
84
+ Args:
85
+ column_name: Name of column
86
+
87
+ Returns:
88
+ AnonymizationStrategy instance
89
+
90
+ Raises:
91
+ ValueError: If strategy not found
92
+ """
93
+ # Check cache first
94
+ if column_name in self._cache:
95
+ return self._cache[column_name]
96
+
97
+ # Get strategy name from profile
98
+ strategy_name = self.profile.columns.get(column_name, self.profile.defaults)
99
+
100
+ # Create strategy
101
+ try:
102
+ strategy = StrategyRegistry.get(strategy_name, {"seed": self.profile.seed})
103
+ self._cache[column_name] = strategy
104
+ return strategy
105
+ except ValueError as e:
106
+ raise ValueError(f"Failed to create strategy for column '{column_name}': {e}") from e
107
+
108
+ def get_strategies(self, column_names: list[str]) -> dict[str, AnonymizationStrategy]:
109
+ """Get strategies for multiple columns.
110
+
111
+ Args:
112
+ column_names: List of column names
113
+
114
+ Returns:
115
+ Dict mapping column name to strategy
116
+ """
117
+ return {col: self.get_strategy(col) for col in column_names}
118
+
119
+ def anonymize(self, data: dict[str, Any]) -> dict[str, Any]:
120
+ """Anonymize entire data dictionary using profile.
121
+
122
+ Args:
123
+ data: Dict mapping column names to values
124
+
125
+ Returns:
126
+ Dict with anonymized values
127
+
128
+ Example:
129
+ >>> data = {"name": "John", "email": "john@example.com"}
130
+ >>> anonymized = factory.anonymize(data)
131
+ """
132
+ result = {}
133
+
134
+ for column_name, value in data.items():
135
+ strategy = self.get_strategy(column_name)
136
+ result[column_name] = strategy.anonymize(value)
137
+
138
+ return result
139
+
140
+ def _validate_profile(self) -> None:
141
+ """Validate profile configuration.
142
+
143
+ Raises:
144
+ ValueError: If profile is invalid
145
+ """
146
+ if not self.profile.name:
147
+ raise ValueError("Profile must have a name")
148
+
149
+ # Validate strategy names exist
150
+ all_strategies = list(self.profile.columns.values()) + [self.profile.defaults]
151
+
152
+ for strategy_name in all_strategies:
153
+ # Extract base strategy name (remove config suffix if present)
154
+ # "name:firstname_lastname" -> "name"
155
+ base_name = strategy_name.split(":")[0] if strategy_name else ""
156
+
157
+ if not StrategyRegistry.is_registered(base_name):
158
+ raise ValueError(f"Unknown strategy: {strategy_name}")
159
+
160
+ def list_column_strategies(self) -> dict[str, str]:
161
+ """List all column-to-strategy mappings.
162
+
163
+ Returns:
164
+ Dict mapping column names to strategy names
165
+ """
166
+ return dict(self.profile.columns)
167
+
168
+ def clear_cache(self) -> None:
169
+ """Clear strategy cache (useful for testing)."""
170
+ self._cache.clear()
171
+
172
+
173
+ class StrategySuggester:
174
+ """Suggests appropriate strategies based on column characteristics.
175
+
176
+ Analyzes column names, data types, and sample values to suggest
177
+ anonymization strategies.
178
+
179
+ Features:
180
+ - Pattern-based column analysis
181
+ - Data type detection
182
+ - Multi-strategy suggestion
183
+ - Confidence scoring
184
+ """
185
+
186
+ # Patterns for column name detection
187
+ NAME_PATTERNS = [
188
+ "name",
189
+ "fullname",
190
+ "full_name",
191
+ "firstname",
192
+ "first_name",
193
+ "lastname",
194
+ "last_name",
195
+ "personname",
196
+ "person_name",
197
+ ]
198
+ EMAIL_PATTERNS = ["email", "email_address", "e_mail", "mail"]
199
+ PHONE_PATTERNS = ["phone", "telephone", "mobile", "cellphone", "cell_phone"]
200
+ ADDRESS_PATTERNS = ["address", "street", "city", "state", "zip", "postal"]
201
+ DATE_PATTERNS = ["date", "born", "birthday", "birthdate", "dob", "created"]
202
+ CC_PATTERNS = ["credit", "card", "cc", "cardnumber", "card_number"]
203
+ IP_PATTERNS = ["ip", "ipaddress", "ip_address", "server"]
204
+
205
+ def suggest(self, column_name: str, sample_value: str | None = None) -> list[tuple]:
206
+ """Suggest strategies for column.
207
+
208
+ Args:
209
+ column_name: Name of column
210
+ sample_value: Optional sample value for analysis
211
+
212
+ Returns:
213
+ List of (strategy_name, confidence) tuples, sorted by confidence
214
+ """
215
+ suggestions = []
216
+
217
+ # Analyze column name
218
+ col_lower = column_name.lower()
219
+
220
+ if any(p in col_lower for p in self.NAME_PATTERNS):
221
+ suggestions.append(("name:firstname_lastname", 0.95))
222
+
223
+ if any(p in col_lower for p in self.EMAIL_PATTERNS):
224
+ suggestions.append(("email", 0.95))
225
+
226
+ if any(p in col_lower for p in self.PHONE_PATTERNS):
227
+ suggestions.append(("phone", 0.90))
228
+
229
+ if any(p in col_lower for p in self.ADDRESS_PATTERNS):
230
+ suggestions.append(("address", 0.85))
231
+
232
+ if any(p in col_lower for p in self.DATE_PATTERNS):
233
+ suggestions.append(("date", 0.85))
234
+
235
+ if any(p in col_lower for p in self.CC_PATTERNS):
236
+ suggestions.append(("credit_card", 0.90))
237
+
238
+ if any(p in col_lower for p in self.IP_PATTERNS):
239
+ suggestions.append(("ip_address", 0.85))
240
+
241
+ # Analyze sample value if provided
242
+ if sample_value:
243
+ value_suggestions = self._analyze_value(sample_value)
244
+ suggestions.extend(value_suggestions)
245
+
246
+ # Remove duplicates, keep highest confidence
247
+ seen = {}
248
+ for strategy, confidence in suggestions:
249
+ if strategy not in seen or confidence > seen[strategy]:
250
+ seen[strategy] = confidence
251
+
252
+ # Sort by confidence descending
253
+ return sorted(
254
+ seen.items(),
255
+ key=lambda x: x[1],
256
+ reverse=True,
257
+ )
258
+
259
+ def _analyze_value(self, value: str) -> list[tuple]:
260
+ """Analyze sample value for pattern matching.
261
+
262
+ Args:
263
+ value: Sample value
264
+
265
+ Returns:
266
+ List of (strategy_name, confidence) tuples
267
+ """
268
+ suggestions = []
269
+
270
+ # Check for email pattern
271
+ if "@" in value and "." in value:
272
+ suggestions.append(("email", 0.85))
273
+
274
+ # Check for phone pattern (simple)
275
+ if (
276
+ any(c.isdigit() for c in value)
277
+ and len(value) >= 10
278
+ and ("-" in value or "(" in value or ")" in value)
279
+ ):
280
+ suggestions.append(("phone", 0.75))
281
+
282
+ # Check for IP pattern
283
+ if value.count(".") == 3:
284
+ parts = value.split(".")
285
+ if all(p.isdigit() and 0 <= int(p) <= 255 for p in parts):
286
+ suggestions.append(("ip_address", 0.90))
287
+
288
+ # Check for credit card pattern
289
+ if all(c.isdigit() or c in " -" for c in value):
290
+ cleaned = value.replace(" ", "").replace("-", "")
291
+ if 13 <= len(cleaned) <= 19:
292
+ suggestions.append(("credit_card", 0.70))
293
+
294
+ return suggestions
295
+
296
+ def create_profile(self, name: str, columns: list[str], seed: int = 0) -> StrategyProfile:
297
+ """Create profile based on column suggestions.
298
+
299
+ Args:
300
+ name: Profile name
301
+ columns: List of column names
302
+ seed: Global seed
303
+
304
+ Returns:
305
+ StrategyProfile with suggested strategies
306
+ """
307
+ column_map = {}
308
+
309
+ for col in columns:
310
+ suggestions = self.suggest(col)
311
+ if suggestions:
312
+ # Use strategy with highest confidence
313
+ strategy_name = suggestions[0][0]
314
+ column_map[col] = strategy_name
315
+ else:
316
+ # Default to preserve
317
+ column_map[col] = "preserve"
318
+
319
+ return StrategyProfile(name=name, seed=seed, columns=column_map, defaults="preserve")