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.
Files changed (119) hide show
  1. confiture/__init__.py +48 -0
  2. confiture/_core.cp311-win_amd64.pyd +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 +1656 -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 +132 -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 +793 -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 +0 -0
  95. confiture/models/lint.py +193 -0
  96. confiture/models/migration.py +180 -0
  97. confiture/models/schema.py +203 -0
  98. confiture/scenarios/__init__.py +36 -0
  99. confiture/scenarios/compliance.py +586 -0
  100. confiture/scenarios/ecommerce.py +199 -0
  101. confiture/scenarios/financial.py +253 -0
  102. confiture/scenarios/healthcare.py +315 -0
  103. confiture/scenarios/multi_tenant.py +340 -0
  104. confiture/scenarios/saas.py +295 -0
  105. confiture/testing/FRAMEWORK_API.md +722 -0
  106. confiture/testing/__init__.py +38 -0
  107. confiture/testing/fixtures/__init__.py +11 -0
  108. confiture/testing/fixtures/data_validator.py +229 -0
  109. confiture/testing/fixtures/migration_runner.py +167 -0
  110. confiture/testing/fixtures/schema_snapshotter.py +352 -0
  111. confiture/testing/frameworks/__init__.py +10 -0
  112. confiture/testing/frameworks/mutation.py +587 -0
  113. confiture/testing/frameworks/performance.py +479 -0
  114. confiture/testing/utils/__init__.py +0 -0
  115. fraiseql_confiture-0.3.4.dist-info/METADATA +438 -0
  116. fraiseql_confiture-0.3.4.dist-info/RECORD +119 -0
  117. fraiseql_confiture-0.3.4.dist-info/WHEEL +4 -0
  118. fraiseql_confiture-0.3.4.dist-info/entry_points.txt +2 -0
  119. fraiseql_confiture-0.3.4.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,41 @@
1
+ """Anonymization strategies module.
2
+
3
+ Provides standard PII anonymization strategies for common data types.
4
+ All strategies are automatically registered with the StrategyRegistry.
5
+ """
6
+
7
+ from confiture.core.anonymization.registry import StrategyRegistry
8
+ from confiture.core.anonymization.strategies.address import AddressStrategy
9
+ from confiture.core.anonymization.strategies.credit_card import CreditCardStrategy
10
+ from confiture.core.anonymization.strategies.custom import (
11
+ CustomLambdaStrategy,
12
+ CustomStrategy,
13
+ )
14
+ from confiture.core.anonymization.strategies.date import DateMaskingStrategy
15
+ from confiture.core.anonymization.strategies.ip_address import IPAddressStrategy
16
+ from confiture.core.anonymization.strategies.name import NameMaskingStrategy
17
+ from confiture.core.anonymization.strategies.preserve import PreserveStrategy
18
+ from confiture.core.anonymization.strategies.text_redaction import TextRedactionStrategy
19
+
20
+ # Register all strategies
21
+ StrategyRegistry.register("name", NameMaskingStrategy)
22
+ StrategyRegistry.register("date", DateMaskingStrategy)
23
+ StrategyRegistry.register("address", AddressStrategy)
24
+ StrategyRegistry.register("credit_card", CreditCardStrategy)
25
+ StrategyRegistry.register("ip_address", IPAddressStrategy)
26
+ StrategyRegistry.register("text_redaction", TextRedactionStrategy)
27
+ StrategyRegistry.register("preserve", PreserveStrategy)
28
+ StrategyRegistry.register("custom", CustomStrategy)
29
+ StrategyRegistry.register("custom_lambda", CustomLambdaStrategy)
30
+
31
+ __all__ = [
32
+ "NameMaskingStrategy",
33
+ "DateMaskingStrategy",
34
+ "AddressStrategy",
35
+ "CreditCardStrategy",
36
+ "IPAddressStrategy",
37
+ "TextRedactionStrategy",
38
+ "PreserveStrategy",
39
+ "CustomStrategy",
40
+ "CustomLambdaStrategy",
41
+ ]
@@ -0,0 +1,359 @@
1
+ """Address masking anonymization strategy.
2
+
3
+ Provides flexible address anonymization with field-level control:
4
+ - Preserve specific fields (city, state, zip, country)
5
+ - Anonymize street address
6
+ - Combine with other strategies for complete address masking
7
+
8
+ Supports multiple address formats and field preservation combinations.
9
+ """
10
+
11
+ import random
12
+ from dataclasses import dataclass, field
13
+
14
+ from confiture.core.anonymization.strategy import AnonymizationStrategy, StrategyConfig
15
+
16
+ # Sample streets for anonymization
17
+ SAMPLE_STREETS = [
18
+ "Main",
19
+ "Oak",
20
+ "Elm",
21
+ "Maple",
22
+ "Pine",
23
+ "Cedar",
24
+ "Birch",
25
+ "Ash",
26
+ "Walnut",
27
+ "Cherry",
28
+ "Spruce",
29
+ "Fir",
30
+ "Hickory",
31
+ "Chestnut",
32
+ ]
33
+
34
+ STREET_TYPES = [
35
+ "Street",
36
+ "Avenue",
37
+ "Boulevard",
38
+ "Drive",
39
+ "Road",
40
+ "Lane",
41
+ "Court",
42
+ "Circle",
43
+ "Trail",
44
+ "Way",
45
+ "Terrace",
46
+ "Place",
47
+ "Square",
48
+ "Parkway",
49
+ ]
50
+
51
+ # Sample cities (for full anonymization)
52
+ SAMPLE_CITIES = [
53
+ "Springfield",
54
+ "Shelbyville",
55
+ "Capital City",
56
+ "Maple Valley",
57
+ "Riverside",
58
+ "Lakewood",
59
+ "Summerville",
60
+ "Hilltown",
61
+ ]
62
+
63
+ # US States
64
+ US_STATES = [
65
+ "AL",
66
+ "AK",
67
+ "AZ",
68
+ "AR",
69
+ "CA",
70
+ "CO",
71
+ "CT",
72
+ "DE",
73
+ "FL",
74
+ "GA",
75
+ "HI",
76
+ "ID",
77
+ "IL",
78
+ "IN",
79
+ "IA",
80
+ "KS",
81
+ "KY",
82
+ "LA",
83
+ "ME",
84
+ "MD",
85
+ "MA",
86
+ "MI",
87
+ "MN",
88
+ "MS",
89
+ "MO",
90
+ "MT",
91
+ "NE",
92
+ "NV",
93
+ "NH",
94
+ "NJ",
95
+ "NM",
96
+ "NY",
97
+ "NC",
98
+ "ND",
99
+ "OH",
100
+ "OK",
101
+ "OR",
102
+ "PA",
103
+ "RI",
104
+ "SC",
105
+ "SD",
106
+ "TN",
107
+ "TX",
108
+ "UT",
109
+ "VT",
110
+ "VA",
111
+ "WA",
112
+ "WV",
113
+ "WI",
114
+ "WY",
115
+ ]
116
+
117
+
118
+ @dataclass
119
+ class AddressConfig(StrategyConfig):
120
+ """Configuration for address masking strategy.
121
+
122
+ Attributes:
123
+ seed: Seed for deterministic randomization
124
+ preserve_fields: List of fields to preserve:
125
+ - "city": Keep city name
126
+ - "state": Keep state/province
127
+ - "country": Keep country
128
+ - "zip": Keep postal code
129
+ redact_street: If True, anonymize street address
130
+ format: Address format:
131
+ - "freetext": Freetext address (need to parse)
132
+ - "structured": Structured object with street, city, state, zip
133
+
134
+ Example:
135
+ >>> config = AddressConfig(seed=12345, preserve_fields=["city", "state"])
136
+ """
137
+
138
+ preserve_fields: list[str] = field(default_factory=lambda: ["city", "country"])
139
+ redact_street: bool = True
140
+ format: str = "freetext" # freetext, structured
141
+
142
+
143
+ class AddressStrategy(AnonymizationStrategy):
144
+ """Anonymization strategy for masking addresses.
145
+
146
+ Provides flexible address anonymization with field-level preservation:
147
+ - Preserve selected fields (city, state, zip, country)
148
+ - Anonymize street address
149
+ - Generate realistic addresses from samples
150
+
151
+ Features:
152
+ - Field-level control
153
+ - Deterministic output (seed-based)
154
+ - Format-preserving (maintains address structure)
155
+ - Handles multiple address formats
156
+
157
+ Example:
158
+ >>> config = AddressConfig(seed=12345, preserve_fields=["city", "state"])
159
+ >>> strategy = AddressStrategy(config)
160
+ >>> strategy.anonymize("123 Main St, Springfield, IL 62701")
161
+ '456 Oak Avenue, Springfield, IL 62701'
162
+ """
163
+
164
+ config_type = AddressConfig
165
+ strategy_name = "address"
166
+
167
+ def anonymize(self, value: str | None) -> str | dict | None:
168
+ """Anonymize an address value.
169
+
170
+ Args:
171
+ value: Address string to anonymize
172
+
173
+ Returns:
174
+ Anonymized address preserving specified fields
175
+
176
+ Example:
177
+ >>> strategy.anonymize("123 Main St, Springfield, IL 62701")
178
+ '456 Oak Avenue, Springfield, IL 62701'
179
+ """
180
+ if value is None:
181
+ return None
182
+
183
+ if isinstance(value, str) and not value.strip():
184
+ return value
185
+
186
+ if self.config.format == "freetext":
187
+ return self._anonymize_freetext(value)
188
+ elif self.config.format == "structured":
189
+ return self._anonymize_structured(value)
190
+ else:
191
+ raise ValueError(f"Unknown format: {self.config.format}")
192
+
193
+ def _anonymize_freetext(self, value: str) -> str:
194
+ """Anonymize freetext address.
195
+
196
+ Attempts to parse and preserve specified fields.
197
+
198
+ Args:
199
+ value: Address string
200
+
201
+ Returns:
202
+ Anonymized address
203
+ """
204
+ # Simple parsing: assume "street, city, state zip" format
205
+ parts = [p.strip() for p in value.split(",")]
206
+
207
+ if len(parts) < 2:
208
+ # Cannot parse - return anonymized version
209
+ return self._anonymize_full_address(value)
210
+
211
+ # Try to extract components
212
+ street = parts[0] if len(parts) > 0 else ""
213
+ city_state_zip = " ".join(parts[1:]) if len(parts) > 1 else ""
214
+
215
+ # Parse city, state, zip
216
+ words = city_state_zip.split()
217
+ city = " ".join(words[:-2]) if len(words) >= 2 else ""
218
+ state = words[-2] if len(words) >= 2 else ""
219
+ zip_code = words[-1] if words else ""
220
+
221
+ # Build anonymized address
222
+ result_parts = []
223
+
224
+ # Street
225
+ if "street" not in self.config.preserve_fields and self.config.redact_street:
226
+ result_parts.append(self._anonymize_street())
227
+ else:
228
+ result_parts.append(street)
229
+
230
+ # City
231
+ if "city" in self.config.preserve_fields:
232
+ result_parts.append(city)
233
+ else:
234
+ result_parts.append(self._anonymize_city())
235
+
236
+ # State
237
+ if "state" in self.config.preserve_fields:
238
+ result_parts.append(state)
239
+ else:
240
+ result_parts.append(self._anonymize_state())
241
+
242
+ # Zip
243
+ if "zip" in self.config.preserve_fields:
244
+ result_parts.append(zip_code)
245
+ else:
246
+ result_parts.append(self._anonymize_zip())
247
+
248
+ return ", ".join(p for p in result_parts if p)
249
+
250
+ def _anonymize_structured(self, value: dict) -> dict:
251
+ """Anonymize structured address dict.
252
+
253
+ Args:
254
+ value: Address dict with keys like street, city, state, zip
255
+
256
+ Returns:
257
+ Anonymized address dict
258
+ """
259
+ result = {}
260
+
261
+ for field_name, field_value in value.items():
262
+ if field_value is None:
263
+ result[field_name] = None
264
+ elif field_name in self.config.preserve_fields:
265
+ result[field_name] = field_value
266
+ elif field_name == "street":
267
+ result[field_name] = self._anonymize_street()
268
+ elif field_name == "city":
269
+ result[field_name] = self._anonymize_city()
270
+ elif field_name == "state":
271
+ result[field_name] = self._anonymize_state()
272
+ elif field_name in ("zip", "postal_code"):
273
+ result[field_name] = self._anonymize_zip()
274
+ else:
275
+ result[field_name] = field_value
276
+
277
+ return result
278
+
279
+ def _anonymize_full_address(self, value: str) -> str:
280
+ """Fully anonymize address (no parsing).
281
+
282
+ Args:
283
+ value: Original address
284
+
285
+ Returns:
286
+ Anonymized address of similar length
287
+ """
288
+ rng = random.Random(f"{self.config.seed}:{value}".encode())
289
+
290
+ street = rng.choice(SAMPLE_STREETS)
291
+ street_type = rng.choice(STREET_TYPES)
292
+ street_num = rng.randint(1, 999)
293
+
294
+ city = rng.choice(SAMPLE_CITIES)
295
+ state = rng.choice(US_STATES)
296
+ zip_code = f"{rng.randint(10000, 99999)}"
297
+
298
+ return f"{street_num} {street} {street_type}, {city}, {state} {zip_code}"
299
+
300
+ def _anonymize_street(self) -> str:
301
+ """Generate anonymized street address.
302
+
303
+ Returns:
304
+ Street address like "123 Oak Avenue"
305
+ """
306
+ rng = random.Random(f"{self.config.seed}:street".encode())
307
+
308
+ street_num = rng.randint(1, 999)
309
+ street = rng.choice(SAMPLE_STREETS)
310
+ street_type = rng.choice(STREET_TYPES)
311
+
312
+ return f"{street_num} {street} {street_type}"
313
+
314
+ def _anonymize_city(self) -> str:
315
+ """Generate anonymized city name.
316
+
317
+ Returns:
318
+ City name
319
+ """
320
+ rng = random.Random(f"{self.config.seed}:city".encode())
321
+ return rng.choice(SAMPLE_CITIES)
322
+
323
+ def _anonymize_state(self) -> str:
324
+ """Generate anonymized state code.
325
+
326
+ Returns:
327
+ State code (e.g., "CA")
328
+ """
329
+ rng = random.Random(f"{self.config.seed}:state".encode())
330
+ return rng.choice(US_STATES)
331
+
332
+ def _anonymize_zip(self) -> str:
333
+ """Generate anonymized zip code.
334
+
335
+ Returns:
336
+ ZIP code (5 digits)
337
+ """
338
+ rng = random.Random(f"{self.config.seed}:zip".encode())
339
+ return f"{rng.randint(10000, 99999)}"
340
+
341
+ def validate(self, value: str | dict) -> bool:
342
+ """Check if strategy can handle this value type.
343
+
344
+ Args:
345
+ value: Sample value to validate
346
+
347
+ Returns:
348
+ True if value is a string, dict, or None
349
+ """
350
+ return isinstance(value, (str, dict)) or value is None
351
+
352
+ def short_name(self) -> str:
353
+ """Return short strategy name for logging.
354
+
355
+ Returns:
356
+ Short name (e.g., "address:preserve_city_state")
357
+ """
358
+ preserved = "_".join(self.config.preserve_fields) if self.config.preserve_fields else "none"
359
+ return f"{self.strategy_name}:{preserved}"