truthound 1.0.8__py3-none-any.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 (877) hide show
  1. truthound/__init__.py +162 -0
  2. truthound/adapters.py +100 -0
  3. truthound/api.py +365 -0
  4. truthound/audit/__init__.py +248 -0
  5. truthound/audit/core.py +967 -0
  6. truthound/audit/filters.py +620 -0
  7. truthound/audit/formatters.py +707 -0
  8. truthound/audit/logger.py +902 -0
  9. truthound/audit/middleware.py +571 -0
  10. truthound/audit/storage.py +1083 -0
  11. truthound/benchmark/__init__.py +123 -0
  12. truthound/benchmark/base.py +757 -0
  13. truthound/benchmark/comparison.py +635 -0
  14. truthound/benchmark/generators.py +706 -0
  15. truthound/benchmark/reporters.py +718 -0
  16. truthound/benchmark/runner.py +635 -0
  17. truthound/benchmark/scenarios.py +712 -0
  18. truthound/cache.py +252 -0
  19. truthound/checkpoint/__init__.py +136 -0
  20. truthound/checkpoint/actions/__init__.py +164 -0
  21. truthound/checkpoint/actions/base.py +324 -0
  22. truthound/checkpoint/actions/custom.py +234 -0
  23. truthound/checkpoint/actions/discord_notify.py +290 -0
  24. truthound/checkpoint/actions/email_notify.py +405 -0
  25. truthound/checkpoint/actions/github_action.py +406 -0
  26. truthound/checkpoint/actions/opsgenie.py +1499 -0
  27. truthound/checkpoint/actions/pagerduty.py +226 -0
  28. truthound/checkpoint/actions/slack_notify.py +233 -0
  29. truthound/checkpoint/actions/store_result.py +249 -0
  30. truthound/checkpoint/actions/teams_notify.py +1570 -0
  31. truthound/checkpoint/actions/telegram_notify.py +419 -0
  32. truthound/checkpoint/actions/update_docs.py +552 -0
  33. truthound/checkpoint/actions/webhook.py +293 -0
  34. truthound/checkpoint/analytics/__init__.py +147 -0
  35. truthound/checkpoint/analytics/aggregations/__init__.py +23 -0
  36. truthound/checkpoint/analytics/aggregations/rollup.py +481 -0
  37. truthound/checkpoint/analytics/aggregations/time_bucket.py +306 -0
  38. truthound/checkpoint/analytics/analyzers/__init__.py +17 -0
  39. truthound/checkpoint/analytics/analyzers/anomaly.py +386 -0
  40. truthound/checkpoint/analytics/analyzers/base.py +270 -0
  41. truthound/checkpoint/analytics/analyzers/forecast.py +421 -0
  42. truthound/checkpoint/analytics/analyzers/trend.py +314 -0
  43. truthound/checkpoint/analytics/models.py +292 -0
  44. truthound/checkpoint/analytics/protocols.py +549 -0
  45. truthound/checkpoint/analytics/service.py +718 -0
  46. truthound/checkpoint/analytics/stores/__init__.py +16 -0
  47. truthound/checkpoint/analytics/stores/base.py +306 -0
  48. truthound/checkpoint/analytics/stores/memory_store.py +353 -0
  49. truthound/checkpoint/analytics/stores/sqlite_store.py +557 -0
  50. truthound/checkpoint/analytics/stores/timescale_store.py +501 -0
  51. truthound/checkpoint/async_actions.py +794 -0
  52. truthound/checkpoint/async_base.py +708 -0
  53. truthound/checkpoint/async_checkpoint.py +617 -0
  54. truthound/checkpoint/async_runner.py +639 -0
  55. truthound/checkpoint/checkpoint.py +527 -0
  56. truthound/checkpoint/ci/__init__.py +61 -0
  57. truthound/checkpoint/ci/detector.py +355 -0
  58. truthound/checkpoint/ci/reporter.py +436 -0
  59. truthound/checkpoint/ci/templates.py +454 -0
  60. truthound/checkpoint/circuitbreaker/__init__.py +133 -0
  61. truthound/checkpoint/circuitbreaker/breaker.py +542 -0
  62. truthound/checkpoint/circuitbreaker/core.py +252 -0
  63. truthound/checkpoint/circuitbreaker/detection.py +459 -0
  64. truthound/checkpoint/circuitbreaker/middleware.py +389 -0
  65. truthound/checkpoint/circuitbreaker/registry.py +357 -0
  66. truthound/checkpoint/distributed/__init__.py +139 -0
  67. truthound/checkpoint/distributed/backends/__init__.py +35 -0
  68. truthound/checkpoint/distributed/backends/celery_backend.py +503 -0
  69. truthound/checkpoint/distributed/backends/kubernetes_backend.py +696 -0
  70. truthound/checkpoint/distributed/backends/local_backend.py +397 -0
  71. truthound/checkpoint/distributed/backends/ray_backend.py +625 -0
  72. truthound/checkpoint/distributed/base.py +774 -0
  73. truthound/checkpoint/distributed/orchestrator.py +765 -0
  74. truthound/checkpoint/distributed/protocols.py +842 -0
  75. truthound/checkpoint/distributed/registry.py +449 -0
  76. truthound/checkpoint/idempotency/__init__.py +120 -0
  77. truthound/checkpoint/idempotency/core.py +295 -0
  78. truthound/checkpoint/idempotency/fingerprint.py +454 -0
  79. truthound/checkpoint/idempotency/locking.py +604 -0
  80. truthound/checkpoint/idempotency/service.py +592 -0
  81. truthound/checkpoint/idempotency/stores.py +653 -0
  82. truthound/checkpoint/monitoring/__init__.py +134 -0
  83. truthound/checkpoint/monitoring/aggregators/__init__.py +15 -0
  84. truthound/checkpoint/monitoring/aggregators/base.py +372 -0
  85. truthound/checkpoint/monitoring/aggregators/realtime.py +300 -0
  86. truthound/checkpoint/monitoring/aggregators/window.py +493 -0
  87. truthound/checkpoint/monitoring/collectors/__init__.py +17 -0
  88. truthound/checkpoint/monitoring/collectors/base.py +257 -0
  89. truthound/checkpoint/monitoring/collectors/memory_collector.py +617 -0
  90. truthound/checkpoint/monitoring/collectors/prometheus_collector.py +451 -0
  91. truthound/checkpoint/monitoring/collectors/redis_collector.py +518 -0
  92. truthound/checkpoint/monitoring/events.py +410 -0
  93. truthound/checkpoint/monitoring/protocols.py +636 -0
  94. truthound/checkpoint/monitoring/service.py +578 -0
  95. truthound/checkpoint/monitoring/views/__init__.py +17 -0
  96. truthound/checkpoint/monitoring/views/base.py +172 -0
  97. truthound/checkpoint/monitoring/views/queue_view.py +220 -0
  98. truthound/checkpoint/monitoring/views/task_view.py +240 -0
  99. truthound/checkpoint/monitoring/views/worker_view.py +263 -0
  100. truthound/checkpoint/registry.py +337 -0
  101. truthound/checkpoint/runner.py +356 -0
  102. truthound/checkpoint/transaction/__init__.py +133 -0
  103. truthound/checkpoint/transaction/base.py +389 -0
  104. truthound/checkpoint/transaction/compensatable.py +537 -0
  105. truthound/checkpoint/transaction/coordinator.py +576 -0
  106. truthound/checkpoint/transaction/executor.py +622 -0
  107. truthound/checkpoint/transaction/idempotency.py +534 -0
  108. truthound/checkpoint/transaction/saga/__init__.py +143 -0
  109. truthound/checkpoint/transaction/saga/builder.py +584 -0
  110. truthound/checkpoint/transaction/saga/definition.py +515 -0
  111. truthound/checkpoint/transaction/saga/event_store.py +542 -0
  112. truthound/checkpoint/transaction/saga/patterns.py +833 -0
  113. truthound/checkpoint/transaction/saga/runner.py +718 -0
  114. truthound/checkpoint/transaction/saga/state_machine.py +793 -0
  115. truthound/checkpoint/transaction/saga/strategies.py +780 -0
  116. truthound/checkpoint/transaction/saga/testing.py +886 -0
  117. truthound/checkpoint/triggers/__init__.py +58 -0
  118. truthound/checkpoint/triggers/base.py +237 -0
  119. truthound/checkpoint/triggers/event.py +385 -0
  120. truthound/checkpoint/triggers/schedule.py +355 -0
  121. truthound/cli.py +2358 -0
  122. truthound/cli_modules/__init__.py +124 -0
  123. truthound/cli_modules/advanced/__init__.py +45 -0
  124. truthound/cli_modules/advanced/benchmark.py +343 -0
  125. truthound/cli_modules/advanced/docs.py +225 -0
  126. truthound/cli_modules/advanced/lineage.py +209 -0
  127. truthound/cli_modules/advanced/ml.py +320 -0
  128. truthound/cli_modules/advanced/realtime.py +196 -0
  129. truthound/cli_modules/checkpoint/__init__.py +46 -0
  130. truthound/cli_modules/checkpoint/init.py +114 -0
  131. truthound/cli_modules/checkpoint/list.py +71 -0
  132. truthound/cli_modules/checkpoint/run.py +159 -0
  133. truthound/cli_modules/checkpoint/validate.py +67 -0
  134. truthound/cli_modules/common/__init__.py +71 -0
  135. truthound/cli_modules/common/errors.py +414 -0
  136. truthound/cli_modules/common/options.py +419 -0
  137. truthound/cli_modules/common/output.py +507 -0
  138. truthound/cli_modules/common/protocol.py +552 -0
  139. truthound/cli_modules/core/__init__.py +48 -0
  140. truthound/cli_modules/core/check.py +123 -0
  141. truthound/cli_modules/core/compare.py +104 -0
  142. truthound/cli_modules/core/learn.py +57 -0
  143. truthound/cli_modules/core/mask.py +77 -0
  144. truthound/cli_modules/core/profile.py +65 -0
  145. truthound/cli_modules/core/scan.py +61 -0
  146. truthound/cli_modules/profiler/__init__.py +51 -0
  147. truthound/cli_modules/profiler/auto_profile.py +175 -0
  148. truthound/cli_modules/profiler/metadata.py +107 -0
  149. truthound/cli_modules/profiler/suite.py +283 -0
  150. truthound/cli_modules/registry.py +431 -0
  151. truthound/cli_modules/scaffolding/__init__.py +89 -0
  152. truthound/cli_modules/scaffolding/base.py +631 -0
  153. truthound/cli_modules/scaffolding/commands.py +545 -0
  154. truthound/cli_modules/scaffolding/plugins.py +1072 -0
  155. truthound/cli_modules/scaffolding/reporters.py +594 -0
  156. truthound/cli_modules/scaffolding/validators.py +1127 -0
  157. truthound/common/__init__.py +18 -0
  158. truthound/common/resilience/__init__.py +130 -0
  159. truthound/common/resilience/bulkhead.py +266 -0
  160. truthound/common/resilience/circuit_breaker.py +516 -0
  161. truthound/common/resilience/composite.py +332 -0
  162. truthound/common/resilience/config.py +292 -0
  163. truthound/common/resilience/protocols.py +217 -0
  164. truthound/common/resilience/rate_limiter.py +404 -0
  165. truthound/common/resilience/retry.py +341 -0
  166. truthound/datadocs/__init__.py +260 -0
  167. truthound/datadocs/base.py +571 -0
  168. truthound/datadocs/builder.py +761 -0
  169. truthound/datadocs/charts.py +764 -0
  170. truthound/datadocs/dashboard/__init__.py +63 -0
  171. truthound/datadocs/dashboard/app.py +576 -0
  172. truthound/datadocs/dashboard/components.py +584 -0
  173. truthound/datadocs/dashboard/state.py +240 -0
  174. truthound/datadocs/engine/__init__.py +46 -0
  175. truthound/datadocs/engine/context.py +376 -0
  176. truthound/datadocs/engine/pipeline.py +618 -0
  177. truthound/datadocs/engine/registry.py +469 -0
  178. truthound/datadocs/exporters/__init__.py +49 -0
  179. truthound/datadocs/exporters/base.py +198 -0
  180. truthound/datadocs/exporters/html.py +178 -0
  181. truthound/datadocs/exporters/json_exporter.py +253 -0
  182. truthound/datadocs/exporters/markdown.py +284 -0
  183. truthound/datadocs/exporters/pdf.py +392 -0
  184. truthound/datadocs/i18n/__init__.py +86 -0
  185. truthound/datadocs/i18n/catalog.py +960 -0
  186. truthound/datadocs/i18n/formatting.py +505 -0
  187. truthound/datadocs/i18n/loader.py +256 -0
  188. truthound/datadocs/i18n/plurals.py +378 -0
  189. truthound/datadocs/renderers/__init__.py +42 -0
  190. truthound/datadocs/renderers/base.py +401 -0
  191. truthound/datadocs/renderers/custom.py +342 -0
  192. truthound/datadocs/renderers/jinja.py +697 -0
  193. truthound/datadocs/sections.py +736 -0
  194. truthound/datadocs/styles.py +931 -0
  195. truthound/datadocs/themes/__init__.py +101 -0
  196. truthound/datadocs/themes/base.py +336 -0
  197. truthound/datadocs/themes/default.py +417 -0
  198. truthound/datadocs/themes/enterprise.py +419 -0
  199. truthound/datadocs/themes/loader.py +336 -0
  200. truthound/datadocs/themes.py +301 -0
  201. truthound/datadocs/transformers/__init__.py +57 -0
  202. truthound/datadocs/transformers/base.py +268 -0
  203. truthound/datadocs/transformers/enrichers.py +544 -0
  204. truthound/datadocs/transformers/filters.py +447 -0
  205. truthound/datadocs/transformers/i18n.py +468 -0
  206. truthound/datadocs/versioning/__init__.py +62 -0
  207. truthound/datadocs/versioning/diff.py +639 -0
  208. truthound/datadocs/versioning/storage.py +497 -0
  209. truthound/datadocs/versioning/version.py +358 -0
  210. truthound/datasources/__init__.py +223 -0
  211. truthound/datasources/_async_protocols.py +222 -0
  212. truthound/datasources/_protocols.py +159 -0
  213. truthound/datasources/adapters.py +428 -0
  214. truthound/datasources/async_base.py +599 -0
  215. truthound/datasources/async_factory.py +511 -0
  216. truthound/datasources/base.py +516 -0
  217. truthound/datasources/factory.py +433 -0
  218. truthound/datasources/nosql/__init__.py +47 -0
  219. truthound/datasources/nosql/base.py +487 -0
  220. truthound/datasources/nosql/elasticsearch.py +801 -0
  221. truthound/datasources/nosql/mongodb.py +636 -0
  222. truthound/datasources/pandas_optimized.py +582 -0
  223. truthound/datasources/pandas_source.py +216 -0
  224. truthound/datasources/polars_source.py +395 -0
  225. truthound/datasources/spark_source.py +479 -0
  226. truthound/datasources/sql/__init__.py +154 -0
  227. truthound/datasources/sql/base.py +710 -0
  228. truthound/datasources/sql/bigquery.py +410 -0
  229. truthound/datasources/sql/cloud_base.py +199 -0
  230. truthound/datasources/sql/databricks.py +471 -0
  231. truthound/datasources/sql/mysql.py +316 -0
  232. truthound/datasources/sql/oracle.py +427 -0
  233. truthound/datasources/sql/postgresql.py +321 -0
  234. truthound/datasources/sql/redshift.py +479 -0
  235. truthound/datasources/sql/snowflake.py +439 -0
  236. truthound/datasources/sql/sqlite.py +286 -0
  237. truthound/datasources/sql/sqlserver.py +437 -0
  238. truthound/datasources/streaming/__init__.py +47 -0
  239. truthound/datasources/streaming/base.py +350 -0
  240. truthound/datasources/streaming/kafka.py +670 -0
  241. truthound/decorators.py +98 -0
  242. truthound/docs/__init__.py +69 -0
  243. truthound/docs/extractor.py +971 -0
  244. truthound/docs/generator.py +601 -0
  245. truthound/docs/parser.py +1037 -0
  246. truthound/docs/renderer.py +999 -0
  247. truthound/drift/__init__.py +22 -0
  248. truthound/drift/compare.py +189 -0
  249. truthound/drift/detectors.py +464 -0
  250. truthound/drift/report.py +160 -0
  251. truthound/execution/__init__.py +65 -0
  252. truthound/execution/_protocols.py +324 -0
  253. truthound/execution/base.py +576 -0
  254. truthound/execution/distributed/__init__.py +179 -0
  255. truthound/execution/distributed/aggregations.py +731 -0
  256. truthound/execution/distributed/arrow_bridge.py +817 -0
  257. truthound/execution/distributed/base.py +550 -0
  258. truthound/execution/distributed/dask_engine.py +976 -0
  259. truthound/execution/distributed/mixins.py +766 -0
  260. truthound/execution/distributed/protocols.py +756 -0
  261. truthound/execution/distributed/ray_engine.py +1127 -0
  262. truthound/execution/distributed/registry.py +446 -0
  263. truthound/execution/distributed/spark_engine.py +1011 -0
  264. truthound/execution/distributed/validator_adapter.py +682 -0
  265. truthound/execution/pandas_engine.py +401 -0
  266. truthound/execution/polars_engine.py +497 -0
  267. truthound/execution/pushdown/__init__.py +230 -0
  268. truthound/execution/pushdown/ast.py +1550 -0
  269. truthound/execution/pushdown/builder.py +1550 -0
  270. truthound/execution/pushdown/dialects.py +1072 -0
  271. truthound/execution/pushdown/executor.py +829 -0
  272. truthound/execution/pushdown/optimizer.py +1041 -0
  273. truthound/execution/sql_engine.py +518 -0
  274. truthound/infrastructure/__init__.py +189 -0
  275. truthound/infrastructure/audit.py +1515 -0
  276. truthound/infrastructure/config.py +1133 -0
  277. truthound/infrastructure/encryption.py +1132 -0
  278. truthound/infrastructure/logging.py +1503 -0
  279. truthound/infrastructure/metrics.py +1220 -0
  280. truthound/lineage/__init__.py +89 -0
  281. truthound/lineage/base.py +746 -0
  282. truthound/lineage/impact_analysis.py +474 -0
  283. truthound/lineage/integrations/__init__.py +22 -0
  284. truthound/lineage/integrations/openlineage.py +548 -0
  285. truthound/lineage/tracker.py +512 -0
  286. truthound/lineage/visualization/__init__.py +33 -0
  287. truthound/lineage/visualization/protocols.py +145 -0
  288. truthound/lineage/visualization/renderers/__init__.py +20 -0
  289. truthound/lineage/visualization/renderers/cytoscape.py +329 -0
  290. truthound/lineage/visualization/renderers/d3.py +331 -0
  291. truthound/lineage/visualization/renderers/graphviz.py +276 -0
  292. truthound/lineage/visualization/renderers/mermaid.py +308 -0
  293. truthound/maskers.py +113 -0
  294. truthound/ml/__init__.py +124 -0
  295. truthound/ml/anomaly_models/__init__.py +31 -0
  296. truthound/ml/anomaly_models/ensemble.py +362 -0
  297. truthound/ml/anomaly_models/isolation_forest.py +444 -0
  298. truthound/ml/anomaly_models/statistical.py +392 -0
  299. truthound/ml/base.py +1178 -0
  300. truthound/ml/drift_detection/__init__.py +26 -0
  301. truthound/ml/drift_detection/concept.py +381 -0
  302. truthound/ml/drift_detection/distribution.py +361 -0
  303. truthound/ml/drift_detection/feature.py +442 -0
  304. truthound/ml/drift_detection/multivariate.py +495 -0
  305. truthound/ml/monitoring/__init__.py +88 -0
  306. truthound/ml/monitoring/alerting/__init__.py +33 -0
  307. truthound/ml/monitoring/alerting/handlers.py +427 -0
  308. truthound/ml/monitoring/alerting/rules.py +508 -0
  309. truthound/ml/monitoring/collectors/__init__.py +19 -0
  310. truthound/ml/monitoring/collectors/composite.py +105 -0
  311. truthound/ml/monitoring/collectors/drift.py +324 -0
  312. truthound/ml/monitoring/collectors/performance.py +179 -0
  313. truthound/ml/monitoring/collectors/quality.py +369 -0
  314. truthound/ml/monitoring/monitor.py +536 -0
  315. truthound/ml/monitoring/protocols.py +451 -0
  316. truthound/ml/monitoring/stores/__init__.py +15 -0
  317. truthound/ml/monitoring/stores/memory.py +201 -0
  318. truthound/ml/monitoring/stores/prometheus.py +296 -0
  319. truthound/ml/rule_learning/__init__.py +25 -0
  320. truthound/ml/rule_learning/constraint_miner.py +443 -0
  321. truthound/ml/rule_learning/pattern_learner.py +499 -0
  322. truthound/ml/rule_learning/profile_learner.py +462 -0
  323. truthound/multitenancy/__init__.py +326 -0
  324. truthound/multitenancy/core.py +852 -0
  325. truthound/multitenancy/integration.py +597 -0
  326. truthound/multitenancy/isolation.py +630 -0
  327. truthound/multitenancy/manager.py +770 -0
  328. truthound/multitenancy/middleware.py +765 -0
  329. truthound/multitenancy/quota.py +537 -0
  330. truthound/multitenancy/resolvers.py +603 -0
  331. truthound/multitenancy/storage.py +703 -0
  332. truthound/observability/__init__.py +307 -0
  333. truthound/observability/context.py +531 -0
  334. truthound/observability/instrumentation.py +611 -0
  335. truthound/observability/logging.py +887 -0
  336. truthound/observability/metrics.py +1157 -0
  337. truthound/observability/tracing/__init__.py +178 -0
  338. truthound/observability/tracing/baggage.py +310 -0
  339. truthound/observability/tracing/config.py +426 -0
  340. truthound/observability/tracing/exporter.py +787 -0
  341. truthound/observability/tracing/integration.py +1018 -0
  342. truthound/observability/tracing/otel/__init__.py +146 -0
  343. truthound/observability/tracing/otel/adapter.py +982 -0
  344. truthound/observability/tracing/otel/bridge.py +1177 -0
  345. truthound/observability/tracing/otel/compat.py +681 -0
  346. truthound/observability/tracing/otel/config.py +691 -0
  347. truthound/observability/tracing/otel/detection.py +327 -0
  348. truthound/observability/tracing/otel/protocols.py +426 -0
  349. truthound/observability/tracing/processor.py +561 -0
  350. truthound/observability/tracing/propagator.py +757 -0
  351. truthound/observability/tracing/provider.py +569 -0
  352. truthound/observability/tracing/resource.py +515 -0
  353. truthound/observability/tracing/sampler.py +487 -0
  354. truthound/observability/tracing/span.py +676 -0
  355. truthound/plugins/__init__.py +198 -0
  356. truthound/plugins/base.py +599 -0
  357. truthound/plugins/cli.py +680 -0
  358. truthound/plugins/dependencies/__init__.py +42 -0
  359. truthound/plugins/dependencies/graph.py +422 -0
  360. truthound/plugins/dependencies/resolver.py +417 -0
  361. truthound/plugins/discovery.py +379 -0
  362. truthound/plugins/docs/__init__.py +46 -0
  363. truthound/plugins/docs/extractor.py +444 -0
  364. truthound/plugins/docs/renderer.py +499 -0
  365. truthound/plugins/enterprise_manager.py +877 -0
  366. truthound/plugins/examples/__init__.py +19 -0
  367. truthound/plugins/examples/custom_validators.py +317 -0
  368. truthound/plugins/examples/slack_notifier.py +312 -0
  369. truthound/plugins/examples/xml_reporter.py +254 -0
  370. truthound/plugins/hooks.py +558 -0
  371. truthound/plugins/lifecycle/__init__.py +43 -0
  372. truthound/plugins/lifecycle/hot_reload.py +402 -0
  373. truthound/plugins/lifecycle/manager.py +371 -0
  374. truthound/plugins/manager.py +736 -0
  375. truthound/plugins/registry.py +338 -0
  376. truthound/plugins/security/__init__.py +93 -0
  377. truthound/plugins/security/exceptions.py +332 -0
  378. truthound/plugins/security/policies.py +348 -0
  379. truthound/plugins/security/protocols.py +643 -0
  380. truthound/plugins/security/sandbox/__init__.py +45 -0
  381. truthound/plugins/security/sandbox/context.py +158 -0
  382. truthound/plugins/security/sandbox/engines/__init__.py +19 -0
  383. truthound/plugins/security/sandbox/engines/container.py +379 -0
  384. truthound/plugins/security/sandbox/engines/noop.py +144 -0
  385. truthound/plugins/security/sandbox/engines/process.py +336 -0
  386. truthound/plugins/security/sandbox/factory.py +211 -0
  387. truthound/plugins/security/signing/__init__.py +57 -0
  388. truthound/plugins/security/signing/service.py +330 -0
  389. truthound/plugins/security/signing/trust_store.py +368 -0
  390. truthound/plugins/security/signing/verifier.py +459 -0
  391. truthound/plugins/versioning/__init__.py +41 -0
  392. truthound/plugins/versioning/constraints.py +297 -0
  393. truthound/plugins/versioning/resolver.py +329 -0
  394. truthound/profiler/__init__.py +1729 -0
  395. truthound/profiler/_lazy.py +452 -0
  396. truthound/profiler/ab_testing/__init__.py +80 -0
  397. truthound/profiler/ab_testing/analysis.py +449 -0
  398. truthound/profiler/ab_testing/base.py +257 -0
  399. truthound/profiler/ab_testing/experiment.py +395 -0
  400. truthound/profiler/ab_testing/tracking.py +368 -0
  401. truthound/profiler/auto_threshold.py +1170 -0
  402. truthound/profiler/base.py +579 -0
  403. truthound/profiler/cache_patterns.py +911 -0
  404. truthound/profiler/caching.py +1303 -0
  405. truthound/profiler/column_profiler.py +712 -0
  406. truthound/profiler/comparison.py +1007 -0
  407. truthound/profiler/custom_patterns.py +1170 -0
  408. truthound/profiler/dashboard/__init__.py +50 -0
  409. truthound/profiler/dashboard/app.py +476 -0
  410. truthound/profiler/dashboard/components.py +457 -0
  411. truthound/profiler/dashboard/config.py +72 -0
  412. truthound/profiler/distributed/__init__.py +83 -0
  413. truthound/profiler/distributed/base.py +281 -0
  414. truthound/profiler/distributed/dask_backend.py +498 -0
  415. truthound/profiler/distributed/local_backend.py +293 -0
  416. truthound/profiler/distributed/profiler.py +304 -0
  417. truthound/profiler/distributed/ray_backend.py +374 -0
  418. truthound/profiler/distributed/spark_backend.py +375 -0
  419. truthound/profiler/distributed.py +1366 -0
  420. truthound/profiler/enterprise_sampling.py +1065 -0
  421. truthound/profiler/errors.py +488 -0
  422. truthound/profiler/evolution/__init__.py +91 -0
  423. truthound/profiler/evolution/alerts.py +426 -0
  424. truthound/profiler/evolution/changes.py +206 -0
  425. truthound/profiler/evolution/compatibility.py +365 -0
  426. truthound/profiler/evolution/detector.py +372 -0
  427. truthound/profiler/evolution/protocols.py +121 -0
  428. truthound/profiler/generators/__init__.py +48 -0
  429. truthound/profiler/generators/base.py +384 -0
  430. truthound/profiler/generators/ml_rules.py +375 -0
  431. truthound/profiler/generators/pattern_rules.py +384 -0
  432. truthound/profiler/generators/schema_rules.py +267 -0
  433. truthound/profiler/generators/stats_rules.py +324 -0
  434. truthound/profiler/generators/suite_generator.py +857 -0
  435. truthound/profiler/i18n.py +1542 -0
  436. truthound/profiler/incremental.py +554 -0
  437. truthound/profiler/incremental_validation.py +1710 -0
  438. truthound/profiler/integration/__init__.py +73 -0
  439. truthound/profiler/integration/adapters.py +345 -0
  440. truthound/profiler/integration/context.py +371 -0
  441. truthound/profiler/integration/executor.py +527 -0
  442. truthound/profiler/integration/naming.py +75 -0
  443. truthound/profiler/integration/protocols.py +243 -0
  444. truthound/profiler/memory.py +1185 -0
  445. truthound/profiler/migration/__init__.py +60 -0
  446. truthound/profiler/migration/base.py +345 -0
  447. truthound/profiler/migration/manager.py +444 -0
  448. truthound/profiler/migration/v1_0_to_v1_1.py +484 -0
  449. truthound/profiler/ml/__init__.py +73 -0
  450. truthound/profiler/ml/base.py +244 -0
  451. truthound/profiler/ml/classifier.py +507 -0
  452. truthound/profiler/ml/feature_extraction.py +604 -0
  453. truthound/profiler/ml/pretrained.py +448 -0
  454. truthound/profiler/ml_inference.py +1276 -0
  455. truthound/profiler/native_patterns.py +815 -0
  456. truthound/profiler/observability.py +1184 -0
  457. truthound/profiler/process_timeout.py +1566 -0
  458. truthound/profiler/progress.py +568 -0
  459. truthound/profiler/progress_callbacks.py +1734 -0
  460. truthound/profiler/quality.py +1345 -0
  461. truthound/profiler/resilience.py +1180 -0
  462. truthound/profiler/sampled_matcher.py +794 -0
  463. truthound/profiler/sampling.py +1288 -0
  464. truthound/profiler/scheduling/__init__.py +82 -0
  465. truthound/profiler/scheduling/protocols.py +214 -0
  466. truthound/profiler/scheduling/scheduler.py +474 -0
  467. truthound/profiler/scheduling/storage.py +457 -0
  468. truthound/profiler/scheduling/triggers.py +449 -0
  469. truthound/profiler/schema.py +603 -0
  470. truthound/profiler/streaming.py +685 -0
  471. truthound/profiler/streaming_patterns.py +1354 -0
  472. truthound/profiler/suite_cli.py +625 -0
  473. truthound/profiler/suite_config.py +789 -0
  474. truthound/profiler/suite_export.py +1268 -0
  475. truthound/profiler/table_profiler.py +547 -0
  476. truthound/profiler/timeout.py +565 -0
  477. truthound/profiler/validation.py +1532 -0
  478. truthound/profiler/visualization/__init__.py +118 -0
  479. truthound/profiler/visualization/base.py +346 -0
  480. truthound/profiler/visualization/generator.py +1259 -0
  481. truthound/profiler/visualization/plotly_renderer.py +811 -0
  482. truthound/profiler/visualization/renderers.py +669 -0
  483. truthound/profiler/visualization/sections.py +540 -0
  484. truthound/profiler/visualization.py +2122 -0
  485. truthound/profiler/yaml_validation.py +1151 -0
  486. truthound/py.typed +0 -0
  487. truthound/ratelimit/__init__.py +248 -0
  488. truthound/ratelimit/algorithms.py +1108 -0
  489. truthound/ratelimit/core.py +573 -0
  490. truthound/ratelimit/integration.py +532 -0
  491. truthound/ratelimit/limiter.py +663 -0
  492. truthound/ratelimit/middleware.py +700 -0
  493. truthound/ratelimit/policy.py +792 -0
  494. truthound/ratelimit/storage.py +763 -0
  495. truthound/rbac/__init__.py +340 -0
  496. truthound/rbac/core.py +976 -0
  497. truthound/rbac/integration.py +760 -0
  498. truthound/rbac/manager.py +1052 -0
  499. truthound/rbac/middleware.py +842 -0
  500. truthound/rbac/policy.py +954 -0
  501. truthound/rbac/storage.py +878 -0
  502. truthound/realtime/__init__.py +141 -0
  503. truthound/realtime/adapters/__init__.py +43 -0
  504. truthound/realtime/adapters/base.py +533 -0
  505. truthound/realtime/adapters/kafka.py +487 -0
  506. truthound/realtime/adapters/kinesis.py +479 -0
  507. truthound/realtime/adapters/mock.py +243 -0
  508. truthound/realtime/base.py +553 -0
  509. truthound/realtime/factory.py +382 -0
  510. truthound/realtime/incremental.py +660 -0
  511. truthound/realtime/processing/__init__.py +67 -0
  512. truthound/realtime/processing/exactly_once.py +575 -0
  513. truthound/realtime/processing/state.py +547 -0
  514. truthound/realtime/processing/windows.py +647 -0
  515. truthound/realtime/protocols.py +569 -0
  516. truthound/realtime/streaming.py +605 -0
  517. truthound/realtime/testing/__init__.py +32 -0
  518. truthound/realtime/testing/containers.py +615 -0
  519. truthound/realtime/testing/fixtures.py +484 -0
  520. truthound/report.py +280 -0
  521. truthound/reporters/__init__.py +46 -0
  522. truthound/reporters/_protocols.py +30 -0
  523. truthound/reporters/base.py +324 -0
  524. truthound/reporters/ci/__init__.py +66 -0
  525. truthound/reporters/ci/azure.py +436 -0
  526. truthound/reporters/ci/base.py +509 -0
  527. truthound/reporters/ci/bitbucket.py +567 -0
  528. truthound/reporters/ci/circleci.py +547 -0
  529. truthound/reporters/ci/detection.py +364 -0
  530. truthound/reporters/ci/factory.py +182 -0
  531. truthound/reporters/ci/github.py +388 -0
  532. truthound/reporters/ci/gitlab.py +471 -0
  533. truthound/reporters/ci/jenkins.py +525 -0
  534. truthound/reporters/console_reporter.py +299 -0
  535. truthound/reporters/factory.py +211 -0
  536. truthound/reporters/html_reporter.py +524 -0
  537. truthound/reporters/json_reporter.py +256 -0
  538. truthound/reporters/markdown_reporter.py +280 -0
  539. truthound/reporters/sdk/__init__.py +174 -0
  540. truthound/reporters/sdk/builder.py +558 -0
  541. truthound/reporters/sdk/mixins.py +1150 -0
  542. truthound/reporters/sdk/schema.py +1493 -0
  543. truthound/reporters/sdk/templates.py +666 -0
  544. truthound/reporters/sdk/testing.py +968 -0
  545. truthound/scanners.py +170 -0
  546. truthound/scheduling/__init__.py +122 -0
  547. truthound/scheduling/cron.py +1136 -0
  548. truthound/scheduling/presets.py +212 -0
  549. truthound/schema.py +275 -0
  550. truthound/secrets/__init__.py +173 -0
  551. truthound/secrets/base.py +618 -0
  552. truthound/secrets/cloud.py +682 -0
  553. truthound/secrets/integration.py +507 -0
  554. truthound/secrets/manager.py +633 -0
  555. truthound/secrets/oidc/__init__.py +172 -0
  556. truthound/secrets/oidc/base.py +902 -0
  557. truthound/secrets/oidc/credential_provider.py +623 -0
  558. truthound/secrets/oidc/exchangers.py +1001 -0
  559. truthound/secrets/oidc/github/__init__.py +110 -0
  560. truthound/secrets/oidc/github/claims.py +718 -0
  561. truthound/secrets/oidc/github/enhanced_provider.py +693 -0
  562. truthound/secrets/oidc/github/trust_policy.py +742 -0
  563. truthound/secrets/oidc/github/verification.py +723 -0
  564. truthound/secrets/oidc/github/workflow.py +691 -0
  565. truthound/secrets/oidc/providers.py +825 -0
  566. truthound/secrets/providers.py +506 -0
  567. truthound/secrets/resolver.py +495 -0
  568. truthound/stores/__init__.py +177 -0
  569. truthound/stores/backends/__init__.py +18 -0
  570. truthound/stores/backends/_protocols.py +340 -0
  571. truthound/stores/backends/azure_blob.py +530 -0
  572. truthound/stores/backends/concurrent_filesystem.py +915 -0
  573. truthound/stores/backends/connection_pool.py +1365 -0
  574. truthound/stores/backends/database.py +743 -0
  575. truthound/stores/backends/filesystem.py +538 -0
  576. truthound/stores/backends/gcs.py +399 -0
  577. truthound/stores/backends/memory.py +354 -0
  578. truthound/stores/backends/s3.py +434 -0
  579. truthound/stores/backpressure/__init__.py +84 -0
  580. truthound/stores/backpressure/base.py +375 -0
  581. truthound/stores/backpressure/circuit_breaker.py +434 -0
  582. truthound/stores/backpressure/monitor.py +376 -0
  583. truthound/stores/backpressure/strategies.py +677 -0
  584. truthound/stores/base.py +551 -0
  585. truthound/stores/batching/__init__.py +65 -0
  586. truthound/stores/batching/base.py +305 -0
  587. truthound/stores/batching/buffer.py +370 -0
  588. truthound/stores/batching/store.py +248 -0
  589. truthound/stores/batching/writer.py +521 -0
  590. truthound/stores/caching/__init__.py +60 -0
  591. truthound/stores/caching/backends.py +684 -0
  592. truthound/stores/caching/base.py +356 -0
  593. truthound/stores/caching/store.py +305 -0
  594. truthound/stores/compression/__init__.py +193 -0
  595. truthound/stores/compression/adaptive.py +694 -0
  596. truthound/stores/compression/base.py +514 -0
  597. truthound/stores/compression/pipeline.py +868 -0
  598. truthound/stores/compression/providers.py +672 -0
  599. truthound/stores/compression/streaming.py +832 -0
  600. truthound/stores/concurrency/__init__.py +81 -0
  601. truthound/stores/concurrency/atomic.py +556 -0
  602. truthound/stores/concurrency/index.py +775 -0
  603. truthound/stores/concurrency/locks.py +576 -0
  604. truthound/stores/concurrency/manager.py +482 -0
  605. truthound/stores/encryption/__init__.py +297 -0
  606. truthound/stores/encryption/base.py +952 -0
  607. truthound/stores/encryption/keys.py +1191 -0
  608. truthound/stores/encryption/pipeline.py +903 -0
  609. truthound/stores/encryption/providers.py +953 -0
  610. truthound/stores/encryption/streaming.py +950 -0
  611. truthound/stores/expectations.py +227 -0
  612. truthound/stores/factory.py +246 -0
  613. truthound/stores/migration/__init__.py +75 -0
  614. truthound/stores/migration/base.py +480 -0
  615. truthound/stores/migration/manager.py +347 -0
  616. truthound/stores/migration/registry.py +382 -0
  617. truthound/stores/migration/store.py +559 -0
  618. truthound/stores/observability/__init__.py +106 -0
  619. truthound/stores/observability/audit.py +718 -0
  620. truthound/stores/observability/config.py +270 -0
  621. truthound/stores/observability/factory.py +208 -0
  622. truthound/stores/observability/metrics.py +636 -0
  623. truthound/stores/observability/protocols.py +410 -0
  624. truthound/stores/observability/store.py +570 -0
  625. truthound/stores/observability/tracing.py +784 -0
  626. truthound/stores/replication/__init__.py +76 -0
  627. truthound/stores/replication/base.py +260 -0
  628. truthound/stores/replication/monitor.py +269 -0
  629. truthound/stores/replication/store.py +439 -0
  630. truthound/stores/replication/syncer.py +391 -0
  631. truthound/stores/results.py +359 -0
  632. truthound/stores/retention/__init__.py +77 -0
  633. truthound/stores/retention/base.py +378 -0
  634. truthound/stores/retention/policies.py +621 -0
  635. truthound/stores/retention/scheduler.py +279 -0
  636. truthound/stores/retention/store.py +526 -0
  637. truthound/stores/streaming/__init__.py +138 -0
  638. truthound/stores/streaming/base.py +801 -0
  639. truthound/stores/streaming/database.py +984 -0
  640. truthound/stores/streaming/filesystem.py +719 -0
  641. truthound/stores/streaming/reader.py +629 -0
  642. truthound/stores/streaming/s3.py +843 -0
  643. truthound/stores/streaming/writer.py +790 -0
  644. truthound/stores/tiering/__init__.py +108 -0
  645. truthound/stores/tiering/base.py +462 -0
  646. truthound/stores/tiering/manager.py +249 -0
  647. truthound/stores/tiering/policies.py +692 -0
  648. truthound/stores/tiering/store.py +526 -0
  649. truthound/stores/versioning/__init__.py +56 -0
  650. truthound/stores/versioning/base.py +376 -0
  651. truthound/stores/versioning/store.py +660 -0
  652. truthound/stores/versioning/strategies.py +353 -0
  653. truthound/types.py +56 -0
  654. truthound/validators/__init__.py +774 -0
  655. truthound/validators/aggregate/__init__.py +27 -0
  656. truthound/validators/aggregate/central.py +116 -0
  657. truthound/validators/aggregate/extremes.py +116 -0
  658. truthound/validators/aggregate/spread.py +118 -0
  659. truthound/validators/aggregate/sum.py +64 -0
  660. truthound/validators/aggregate/type.py +78 -0
  661. truthound/validators/anomaly/__init__.py +93 -0
  662. truthound/validators/anomaly/base.py +431 -0
  663. truthound/validators/anomaly/ml_based.py +1190 -0
  664. truthound/validators/anomaly/multivariate.py +647 -0
  665. truthound/validators/anomaly/statistical.py +599 -0
  666. truthound/validators/base.py +1089 -0
  667. truthound/validators/business_rule/__init__.py +46 -0
  668. truthound/validators/business_rule/base.py +147 -0
  669. truthound/validators/business_rule/checksum.py +509 -0
  670. truthound/validators/business_rule/financial.py +526 -0
  671. truthound/validators/cache.py +733 -0
  672. truthound/validators/completeness/__init__.py +39 -0
  673. truthound/validators/completeness/conditional.py +73 -0
  674. truthound/validators/completeness/default.py +98 -0
  675. truthound/validators/completeness/empty.py +103 -0
  676. truthound/validators/completeness/nan.py +337 -0
  677. truthound/validators/completeness/null.py +152 -0
  678. truthound/validators/cross_table/__init__.py +17 -0
  679. truthound/validators/cross_table/aggregate.py +333 -0
  680. truthound/validators/cross_table/row_count.py +122 -0
  681. truthound/validators/datetime/__init__.py +29 -0
  682. truthound/validators/datetime/format.py +78 -0
  683. truthound/validators/datetime/freshness.py +269 -0
  684. truthound/validators/datetime/order.py +73 -0
  685. truthound/validators/datetime/parseable.py +185 -0
  686. truthound/validators/datetime/range.py +202 -0
  687. truthound/validators/datetime/timezone.py +69 -0
  688. truthound/validators/distribution/__init__.py +49 -0
  689. truthound/validators/distribution/distribution.py +128 -0
  690. truthound/validators/distribution/monotonic.py +119 -0
  691. truthound/validators/distribution/outlier.py +178 -0
  692. truthound/validators/distribution/quantile.py +80 -0
  693. truthound/validators/distribution/range.py +254 -0
  694. truthound/validators/distribution/set.py +125 -0
  695. truthound/validators/distribution/statistical.py +459 -0
  696. truthound/validators/drift/__init__.py +79 -0
  697. truthound/validators/drift/base.py +427 -0
  698. truthound/validators/drift/multi_feature.py +401 -0
  699. truthound/validators/drift/numeric.py +395 -0
  700. truthound/validators/drift/psi.py +446 -0
  701. truthound/validators/drift/statistical.py +510 -0
  702. truthound/validators/enterprise.py +1658 -0
  703. truthound/validators/geospatial/__init__.py +80 -0
  704. truthound/validators/geospatial/base.py +97 -0
  705. truthound/validators/geospatial/boundary.py +238 -0
  706. truthound/validators/geospatial/coordinate.py +351 -0
  707. truthound/validators/geospatial/distance.py +399 -0
  708. truthound/validators/geospatial/polygon.py +665 -0
  709. truthound/validators/i18n/__init__.py +308 -0
  710. truthound/validators/i18n/bidi.py +571 -0
  711. truthound/validators/i18n/catalogs.py +570 -0
  712. truthound/validators/i18n/dialects.py +763 -0
  713. truthound/validators/i18n/extended_catalogs.py +549 -0
  714. truthound/validators/i18n/formatting.py +1434 -0
  715. truthound/validators/i18n/loader.py +1020 -0
  716. truthound/validators/i18n/messages.py +521 -0
  717. truthound/validators/i18n/plural.py +683 -0
  718. truthound/validators/i18n/protocols.py +855 -0
  719. truthound/validators/i18n/tms.py +1162 -0
  720. truthound/validators/localization/__init__.py +53 -0
  721. truthound/validators/localization/base.py +122 -0
  722. truthound/validators/localization/chinese.py +362 -0
  723. truthound/validators/localization/japanese.py +275 -0
  724. truthound/validators/localization/korean.py +524 -0
  725. truthound/validators/memory/__init__.py +94 -0
  726. truthound/validators/memory/approximate_knn.py +506 -0
  727. truthound/validators/memory/base.py +547 -0
  728. truthound/validators/memory/sgd_online.py +719 -0
  729. truthound/validators/memory/streaming_ecdf.py +753 -0
  730. truthound/validators/ml_feature/__init__.py +54 -0
  731. truthound/validators/ml_feature/base.py +249 -0
  732. truthound/validators/ml_feature/correlation.py +299 -0
  733. truthound/validators/ml_feature/leakage.py +344 -0
  734. truthound/validators/ml_feature/null_impact.py +270 -0
  735. truthound/validators/ml_feature/scale.py +264 -0
  736. truthound/validators/multi_column/__init__.py +89 -0
  737. truthound/validators/multi_column/arithmetic.py +284 -0
  738. truthound/validators/multi_column/base.py +231 -0
  739. truthound/validators/multi_column/comparison.py +273 -0
  740. truthound/validators/multi_column/consistency.py +312 -0
  741. truthound/validators/multi_column/statistical.py +299 -0
  742. truthound/validators/optimization/__init__.py +164 -0
  743. truthound/validators/optimization/aggregation.py +563 -0
  744. truthound/validators/optimization/covariance.py +556 -0
  745. truthound/validators/optimization/geo.py +626 -0
  746. truthound/validators/optimization/graph.py +587 -0
  747. truthound/validators/optimization/orchestrator.py +970 -0
  748. truthound/validators/optimization/profiling.py +1312 -0
  749. truthound/validators/privacy/__init__.py +223 -0
  750. truthound/validators/privacy/base.py +635 -0
  751. truthound/validators/privacy/ccpa.py +670 -0
  752. truthound/validators/privacy/gdpr.py +728 -0
  753. truthound/validators/privacy/global_patterns.py +604 -0
  754. truthound/validators/privacy/plugins.py +867 -0
  755. truthound/validators/profiling/__init__.py +52 -0
  756. truthound/validators/profiling/base.py +175 -0
  757. truthound/validators/profiling/cardinality.py +312 -0
  758. truthound/validators/profiling/entropy.py +391 -0
  759. truthound/validators/profiling/frequency.py +455 -0
  760. truthound/validators/pushdown_support.py +660 -0
  761. truthound/validators/query/__init__.py +91 -0
  762. truthound/validators/query/aggregate.py +346 -0
  763. truthound/validators/query/base.py +246 -0
  764. truthound/validators/query/column.py +249 -0
  765. truthound/validators/query/expression.py +274 -0
  766. truthound/validators/query/result.py +323 -0
  767. truthound/validators/query/row_count.py +264 -0
  768. truthound/validators/referential/__init__.py +80 -0
  769. truthound/validators/referential/base.py +395 -0
  770. truthound/validators/referential/cascade.py +391 -0
  771. truthound/validators/referential/circular.py +563 -0
  772. truthound/validators/referential/foreign_key.py +624 -0
  773. truthound/validators/referential/orphan.py +485 -0
  774. truthound/validators/registry.py +112 -0
  775. truthound/validators/schema/__init__.py +41 -0
  776. truthound/validators/schema/column_count.py +142 -0
  777. truthound/validators/schema/column_exists.py +80 -0
  778. truthound/validators/schema/column_order.py +82 -0
  779. truthound/validators/schema/column_pair.py +85 -0
  780. truthound/validators/schema/column_pair_set.py +195 -0
  781. truthound/validators/schema/column_type.py +94 -0
  782. truthound/validators/schema/multi_column.py +53 -0
  783. truthound/validators/schema/multi_column_aggregate.py +175 -0
  784. truthound/validators/schema/referential.py +274 -0
  785. truthound/validators/schema/table_schema.py +91 -0
  786. truthound/validators/schema_validator.py +219 -0
  787. truthound/validators/sdk/__init__.py +250 -0
  788. truthound/validators/sdk/builder.py +680 -0
  789. truthound/validators/sdk/decorators.py +474 -0
  790. truthound/validators/sdk/enterprise/__init__.py +211 -0
  791. truthound/validators/sdk/enterprise/docs.py +725 -0
  792. truthound/validators/sdk/enterprise/fuzzing.py +659 -0
  793. truthound/validators/sdk/enterprise/licensing.py +709 -0
  794. truthound/validators/sdk/enterprise/manager.py +543 -0
  795. truthound/validators/sdk/enterprise/resources.py +628 -0
  796. truthound/validators/sdk/enterprise/sandbox.py +766 -0
  797. truthound/validators/sdk/enterprise/signing.py +603 -0
  798. truthound/validators/sdk/enterprise/templates.py +865 -0
  799. truthound/validators/sdk/enterprise/versioning.py +659 -0
  800. truthound/validators/sdk/templates.py +757 -0
  801. truthound/validators/sdk/testing.py +807 -0
  802. truthound/validators/security/__init__.py +181 -0
  803. truthound/validators/security/redos/__init__.py +182 -0
  804. truthound/validators/security/redos/core.py +861 -0
  805. truthound/validators/security/redos/cpu_monitor.py +593 -0
  806. truthound/validators/security/redos/cve_database.py +791 -0
  807. truthound/validators/security/redos/ml/__init__.py +155 -0
  808. truthound/validators/security/redos/ml/base.py +785 -0
  809. truthound/validators/security/redos/ml/datasets.py +618 -0
  810. truthound/validators/security/redos/ml/features.py +359 -0
  811. truthound/validators/security/redos/ml/models.py +1000 -0
  812. truthound/validators/security/redos/ml/predictor.py +507 -0
  813. truthound/validators/security/redos/ml/storage.py +632 -0
  814. truthound/validators/security/redos/ml/training.py +571 -0
  815. truthound/validators/security/redos/ml_analyzer.py +937 -0
  816. truthound/validators/security/redos/optimizer.py +674 -0
  817. truthound/validators/security/redos/profiler.py +682 -0
  818. truthound/validators/security/redos/re2_engine.py +709 -0
  819. truthound/validators/security/redos.py +886 -0
  820. truthound/validators/security/sql_security.py +1247 -0
  821. truthound/validators/streaming/__init__.py +126 -0
  822. truthound/validators/streaming/base.py +292 -0
  823. truthound/validators/streaming/completeness.py +210 -0
  824. truthound/validators/streaming/mixin.py +575 -0
  825. truthound/validators/streaming/range.py +308 -0
  826. truthound/validators/streaming/sources.py +846 -0
  827. truthound/validators/string/__init__.py +57 -0
  828. truthound/validators/string/casing.py +158 -0
  829. truthound/validators/string/charset.py +96 -0
  830. truthound/validators/string/format.py +501 -0
  831. truthound/validators/string/json.py +77 -0
  832. truthound/validators/string/json_schema.py +184 -0
  833. truthound/validators/string/length.py +104 -0
  834. truthound/validators/string/like_pattern.py +237 -0
  835. truthound/validators/string/regex.py +202 -0
  836. truthound/validators/string/regex_extended.py +435 -0
  837. truthound/validators/table/__init__.py +88 -0
  838. truthound/validators/table/base.py +78 -0
  839. truthound/validators/table/column_count.py +198 -0
  840. truthound/validators/table/freshness.py +362 -0
  841. truthound/validators/table/row_count.py +251 -0
  842. truthound/validators/table/schema.py +333 -0
  843. truthound/validators/table/size.py +285 -0
  844. truthound/validators/timeout/__init__.py +102 -0
  845. truthound/validators/timeout/advanced/__init__.py +247 -0
  846. truthound/validators/timeout/advanced/circuit_breaker.py +675 -0
  847. truthound/validators/timeout/advanced/prediction.py +773 -0
  848. truthound/validators/timeout/advanced/priority.py +618 -0
  849. truthound/validators/timeout/advanced/redis_backend.py +770 -0
  850. truthound/validators/timeout/advanced/retry.py +721 -0
  851. truthound/validators/timeout/advanced/sampling.py +788 -0
  852. truthound/validators/timeout/advanced/sla.py +661 -0
  853. truthound/validators/timeout/advanced/telemetry.py +804 -0
  854. truthound/validators/timeout/cascade.py +477 -0
  855. truthound/validators/timeout/deadline.py +657 -0
  856. truthound/validators/timeout/degradation.py +525 -0
  857. truthound/validators/timeout/distributed.py +597 -0
  858. truthound/validators/timeseries/__init__.py +89 -0
  859. truthound/validators/timeseries/base.py +326 -0
  860. truthound/validators/timeseries/completeness.py +617 -0
  861. truthound/validators/timeseries/gap.py +485 -0
  862. truthound/validators/timeseries/monotonic.py +310 -0
  863. truthound/validators/timeseries/seasonality.py +422 -0
  864. truthound/validators/timeseries/trend.py +510 -0
  865. truthound/validators/uniqueness/__init__.py +59 -0
  866. truthound/validators/uniqueness/approximate.py +475 -0
  867. truthound/validators/uniqueness/distinct_values.py +253 -0
  868. truthound/validators/uniqueness/duplicate.py +118 -0
  869. truthound/validators/uniqueness/primary_key.py +140 -0
  870. truthound/validators/uniqueness/unique.py +191 -0
  871. truthound/validators/uniqueness/within_record.py +599 -0
  872. truthound/validators/utils.py +756 -0
  873. truthound-1.0.8.dist-info/METADATA +474 -0
  874. truthound-1.0.8.dist-info/RECORD +877 -0
  875. truthound-1.0.8.dist-info/WHEEL +4 -0
  876. truthound-1.0.8.dist-info/entry_points.txt +2 -0
  877. truthound-1.0.8.dist-info/licenses/LICENSE +190 -0
@@ -0,0 +1,1191 @@
1
+ """Key management module for encryption.
2
+
3
+ This module provides comprehensive key management including:
4
+ - Key derivation functions (KDFs) for password-based encryption
5
+ - Key rotation and versioning
6
+ - In-memory and file-based key storage
7
+ - Envelope encryption for secure key wrapping
8
+ - Integration with external key management systems (AWS KMS, HashiCorp Vault)
9
+
10
+ Security Best Practices:
11
+ - Never hardcode keys in source code
12
+ - Use strong KDFs for password-derived keys
13
+ - Rotate keys regularly
14
+ - Use envelope encryption for key wrapping
15
+ - Clear key material from memory when done
16
+
17
+ Example:
18
+ >>> from truthound.stores.encryption.keys import (
19
+ ... KeyManager,
20
+ ... derive_key,
21
+ ... KeyDerivation,
22
+ ... )
23
+ >>>
24
+ >>> # Derive key from password
25
+ >>> key = derive_key("my_password", kdf=KeyDerivation.ARGON2ID)
26
+ >>>
27
+ >>> # Use key manager for key lifecycle
28
+ >>> manager = KeyManager()
29
+ >>> key_obj = manager.create_key(algorithm=EncryptionAlgorithm.AES_256_GCM)
30
+ >>> manager.rotate_key(key_obj.key_id)
31
+ """
32
+
33
+ from __future__ import annotations
34
+
35
+ import base64
36
+ import hashlib
37
+ import hmac
38
+ import json
39
+ import os
40
+ import time
41
+ from abc import ABC, abstractmethod
42
+ from dataclasses import dataclass, field
43
+ from datetime import datetime, timedelta, timezone
44
+ from pathlib import Path
45
+ from threading import RLock
46
+ from typing import Any, Callable, Iterator
47
+
48
+ from truthound.stores.encryption.base import (
49
+ EncryptionAlgorithm,
50
+ EncryptionKey,
51
+ KeyDerivation,
52
+ KeyDerivationConfig,
53
+ KeyDerivationError,
54
+ KeyError_,
55
+ KeyExpiredError,
56
+ KeyType,
57
+ generate_key,
58
+ generate_key_id,
59
+ generate_salt,
60
+ )
61
+
62
+
63
+ # =============================================================================
64
+ # Key Derivation Functions
65
+ # =============================================================================
66
+
67
+
68
+ class BaseKeyDeriver(ABC):
69
+ """Base class for key derivation implementations."""
70
+
71
+ def __init__(self, kdf: KeyDerivation) -> None:
72
+ self._kdf = kdf
73
+
74
+ @property
75
+ def kdf(self) -> KeyDerivation:
76
+ """Get the KDF type."""
77
+ return self._kdf
78
+
79
+ @abstractmethod
80
+ def derive(
81
+ self,
82
+ password: str | bytes,
83
+ salt: bytes,
84
+ key_size: int,
85
+ **kwargs: Any,
86
+ ) -> bytes:
87
+ """Derive a key from password.
88
+
89
+ Args:
90
+ password: Password or passphrase.
91
+ salt: Cryptographic salt.
92
+ key_size: Desired key size in bytes.
93
+ **kwargs: KDF-specific parameters.
94
+
95
+ Returns:
96
+ Derived key bytes.
97
+ """
98
+ ...
99
+
100
+
101
+ class Argon2KeyDeriver(BaseKeyDeriver):
102
+ """Argon2 key derivation (recommended for new applications).
103
+
104
+ Argon2 is the winner of the Password Hashing Competition and
105
+ provides excellent resistance against GPU and ASIC attacks.
106
+
107
+ Variants:
108
+ - Argon2id: Hybrid mode (recommended)
109
+ - Argon2i: Data-independent (side-channel resistant)
110
+ - Argon2d: Data-dependent (higher resistance to GPU attacks)
111
+ """
112
+
113
+ def __init__(
114
+ self,
115
+ variant: KeyDerivation = KeyDerivation.ARGON2ID,
116
+ time_cost: int = 3,
117
+ memory_cost: int = 65536,
118
+ parallelism: int = 4,
119
+ ) -> None:
120
+ """Initialize Argon2 key deriver.
121
+
122
+ Args:
123
+ variant: Argon2 variant (id, i, or d).
124
+ time_cost: Number of iterations.
125
+ memory_cost: Memory usage in KiB.
126
+ parallelism: Degree of parallelism.
127
+ """
128
+ super().__init__(variant)
129
+ self.time_cost = time_cost
130
+ self.memory_cost = memory_cost
131
+ self.parallelism = parallelism
132
+
133
+ def derive(
134
+ self,
135
+ password: str | bytes,
136
+ salt: bytes,
137
+ key_size: int,
138
+ **kwargs: Any,
139
+ ) -> bytes:
140
+ """Derive key using Argon2."""
141
+ try:
142
+ from argon2.low_level import Type, hash_secret_raw
143
+ except ImportError as e:
144
+ raise KeyDerivationError(
145
+ "Argon2 requires 'argon2-cffi' package: pip install argon2-cffi",
146
+ self._kdf.value,
147
+ ) from e
148
+
149
+ if isinstance(password, str):
150
+ password = password.encode("utf-8")
151
+
152
+ # Map variant to type
153
+ type_map = {
154
+ KeyDerivation.ARGON2ID: Type.ID,
155
+ KeyDerivation.ARGON2I: Type.I,
156
+ KeyDerivation.ARGON2D: Type.D,
157
+ }
158
+ argon_type = type_map.get(self._kdf, Type.ID)
159
+
160
+ time_cost = kwargs.get("time_cost", self.time_cost)
161
+ memory_cost = kwargs.get("memory_cost", self.memory_cost)
162
+ parallelism = kwargs.get("parallelism", self.parallelism)
163
+
164
+ return hash_secret_raw(
165
+ secret=password,
166
+ salt=salt,
167
+ time_cost=time_cost,
168
+ memory_cost=memory_cost,
169
+ parallelism=parallelism,
170
+ hash_len=key_size,
171
+ type=argon_type,
172
+ )
173
+
174
+
175
+ class PBKDF2KeyDeriver(BaseKeyDeriver):
176
+ """PBKDF2 key derivation (widely compatible).
177
+
178
+ PBKDF2 is a widely supported KDF that is suitable for most
179
+ applications requiring password-based encryption.
180
+ """
181
+
182
+ def __init__(
183
+ self,
184
+ hash_name: str = "sha256",
185
+ iterations: int = 600_000,
186
+ ) -> None:
187
+ """Initialize PBKDF2 key deriver.
188
+
189
+ Args:
190
+ hash_name: Hash function (sha256, sha512).
191
+ iterations: Number of iterations (higher = slower + more secure).
192
+ """
193
+ kdf = (
194
+ KeyDerivation.PBKDF2_SHA512
195
+ if hash_name == "sha512"
196
+ else KeyDerivation.PBKDF2_SHA256
197
+ )
198
+ super().__init__(kdf)
199
+ self.hash_name = hash_name
200
+ self.iterations = iterations
201
+
202
+ def derive(
203
+ self,
204
+ password: str | bytes,
205
+ salt: bytes,
206
+ key_size: int,
207
+ **kwargs: Any,
208
+ ) -> bytes:
209
+ """Derive key using PBKDF2."""
210
+ if isinstance(password, str):
211
+ password = password.encode("utf-8")
212
+
213
+ iterations = kwargs.get("iterations", self.iterations)
214
+
215
+ return hashlib.pbkdf2_hmac(
216
+ self.hash_name,
217
+ password,
218
+ salt,
219
+ iterations,
220
+ dklen=key_size,
221
+ )
222
+
223
+
224
+ class ScryptKeyDeriver(BaseKeyDeriver):
225
+ """scrypt key derivation (memory-hard).
226
+
227
+ scrypt is designed to be memory-hard, making it expensive to
228
+ parallelize on GPUs or ASICs.
229
+ """
230
+
231
+ def __init__(
232
+ self,
233
+ n: int = 2**14,
234
+ r: int = 8,
235
+ p: int = 1,
236
+ ) -> None:
237
+ """Initialize scrypt key deriver.
238
+
239
+ Args:
240
+ n: CPU/memory cost parameter (must be power of 2).
241
+ r: Block size parameter.
242
+ p: Parallelization parameter.
243
+ """
244
+ super().__init__(KeyDerivation.SCRYPT)
245
+ self.n = n
246
+ self.r = r
247
+ self.p = p
248
+
249
+ def derive(
250
+ self,
251
+ password: str | bytes,
252
+ salt: bytes,
253
+ key_size: int,
254
+ **kwargs: Any,
255
+ ) -> bytes:
256
+ """Derive key using scrypt."""
257
+ if isinstance(password, str):
258
+ password = password.encode("utf-8")
259
+
260
+ n = kwargs.get("n", self.n)
261
+ r = kwargs.get("r", self.r)
262
+ p = kwargs.get("p", self.p)
263
+
264
+ return hashlib.scrypt(
265
+ password,
266
+ salt=salt,
267
+ n=n,
268
+ r=r,
269
+ p=p,
270
+ dklen=key_size,
271
+ )
272
+
273
+
274
+ class HKDFKeyDeriver(BaseKeyDeriver):
275
+ """HKDF key derivation (for key expansion).
276
+
277
+ HKDF is used for key expansion and derivation from existing
278
+ key material (not for password-based derivation).
279
+ """
280
+
281
+ def __init__(self, hash_name: str = "sha256") -> None:
282
+ """Initialize HKDF key deriver.
283
+
284
+ Args:
285
+ hash_name: Hash function (sha256, sha512).
286
+ """
287
+ kdf = (
288
+ KeyDerivation.HKDF_SHA512
289
+ if hash_name == "sha512"
290
+ else KeyDerivation.HKDF_SHA256
291
+ )
292
+ super().__init__(kdf)
293
+ self.hash_name = hash_name
294
+
295
+ def derive(
296
+ self,
297
+ password: str | bytes,
298
+ salt: bytes,
299
+ key_size: int,
300
+ **kwargs: Any,
301
+ ) -> bytes:
302
+ """Derive key using HKDF."""
303
+ try:
304
+ from cryptography.hazmat.primitives import hashes
305
+ from cryptography.hazmat.primitives.kdf.hkdf import HKDF
306
+ except ImportError as e:
307
+ raise KeyDerivationError(
308
+ "HKDF requires 'cryptography' package",
309
+ self._kdf.value,
310
+ ) from e
311
+
312
+ if isinstance(password, str):
313
+ password = password.encode("utf-8")
314
+
315
+ hash_algo = hashes.SHA512() if self.hash_name == "sha512" else hashes.SHA256()
316
+ info = kwargs.get("info", b"truthound-encryption")
317
+
318
+ hkdf = HKDF(
319
+ algorithm=hash_algo,
320
+ length=key_size,
321
+ salt=salt if salt else None,
322
+ info=info,
323
+ )
324
+ return hkdf.derive(password)
325
+
326
+
327
+ # =============================================================================
328
+ # Key Deriver Registry and Factory
329
+ # =============================================================================
330
+
331
+ _KEY_DERIVER_REGISTRY: dict[KeyDerivation, type[BaseKeyDeriver]] = {
332
+ KeyDerivation.ARGON2ID: Argon2KeyDeriver,
333
+ KeyDerivation.ARGON2I: Argon2KeyDeriver,
334
+ KeyDerivation.ARGON2D: Argon2KeyDeriver,
335
+ KeyDerivation.PBKDF2_SHA256: PBKDF2KeyDeriver,
336
+ KeyDerivation.PBKDF2_SHA512: PBKDF2KeyDeriver,
337
+ KeyDerivation.SCRYPT: ScryptKeyDeriver,
338
+ KeyDerivation.HKDF_SHA256: HKDFKeyDeriver,
339
+ KeyDerivation.HKDF_SHA512: HKDFKeyDeriver,
340
+ }
341
+
342
+
343
+ def get_key_deriver(kdf: KeyDerivation, **kwargs: Any) -> BaseKeyDeriver:
344
+ """Get a key deriver instance.
345
+
346
+ Args:
347
+ kdf: Key derivation function.
348
+ **kwargs: KDF-specific parameters.
349
+
350
+ Returns:
351
+ Key deriver instance.
352
+ """
353
+ deriver_class = _KEY_DERIVER_REGISTRY.get(kdf)
354
+ if deriver_class is None:
355
+ raise KeyDerivationError(f"Unsupported KDF: {kdf.value}")
356
+
357
+ # Handle variants
358
+ if kdf in (KeyDerivation.ARGON2ID, KeyDerivation.ARGON2I, KeyDerivation.ARGON2D):
359
+ return Argon2KeyDeriver(variant=kdf, **kwargs)
360
+ elif kdf == KeyDerivation.PBKDF2_SHA512:
361
+ return PBKDF2KeyDeriver(hash_name="sha512", **kwargs)
362
+ elif kdf == KeyDerivation.HKDF_SHA512:
363
+ return HKDFKeyDeriver(hash_name="sha512")
364
+
365
+ return deriver_class(**kwargs)
366
+
367
+
368
+ def derive_key(
369
+ password: str | bytes,
370
+ salt: bytes | None = None,
371
+ key_size: int = 32,
372
+ kdf: KeyDerivation = KeyDerivation.ARGON2ID,
373
+ config: KeyDerivationConfig | None = None,
374
+ ) -> tuple[bytes, bytes]:
375
+ """Derive an encryption key from a password.
376
+
377
+ Args:
378
+ password: Password or passphrase.
379
+ salt: Salt (generated if not provided).
380
+ key_size: Desired key size in bytes.
381
+ kdf: Key derivation function.
382
+ config: Detailed configuration.
383
+
384
+ Returns:
385
+ Tuple of (derived_key, salt).
386
+
387
+ Example:
388
+ >>> key, salt = derive_key("my_password")
389
+ >>> # Store salt alongside encrypted data
390
+ """
391
+ if salt is None:
392
+ salt_size = config.salt_size if config else 16
393
+ salt = generate_salt(salt_size)
394
+
395
+ kwargs: dict[str, Any] = {}
396
+ if config:
397
+ if kdf in (KeyDerivation.ARGON2ID, KeyDerivation.ARGON2I, KeyDerivation.ARGON2D):
398
+ kwargs = {
399
+ "time_cost": config.time_cost,
400
+ "memory_cost": config.memory_cost,
401
+ "parallelism": config.parallelism,
402
+ }
403
+ elif kdf in (KeyDerivation.PBKDF2_SHA256, KeyDerivation.PBKDF2_SHA512):
404
+ kwargs = {"iterations": config.get_iterations()}
405
+ elif kdf == KeyDerivation.SCRYPT:
406
+ kwargs = {"n": config.n, "r": config.r, "p": config.p}
407
+
408
+ deriver = get_key_deriver(kdf, **kwargs)
409
+ key = deriver.derive(password, salt, key_size)
410
+ return key, salt
411
+
412
+
413
+ # =============================================================================
414
+ # Key Storage Backends
415
+ # =============================================================================
416
+
417
+
418
+ class BaseKeyStore(ABC):
419
+ """Base class for key storage backends."""
420
+
421
+ @abstractmethod
422
+ def get(self, key_id: str) -> EncryptionKey | None:
423
+ """Retrieve a key by ID."""
424
+ ...
425
+
426
+ @abstractmethod
427
+ def put(self, key: EncryptionKey) -> None:
428
+ """Store a key."""
429
+ ...
430
+
431
+ @abstractmethod
432
+ def delete(self, key_id: str) -> bool:
433
+ """Delete a key by ID."""
434
+ ...
435
+
436
+ @abstractmethod
437
+ def list_keys(self) -> list[str]:
438
+ """List all key IDs."""
439
+ ...
440
+
441
+ def exists(self, key_id: str) -> bool:
442
+ """Check if a key exists."""
443
+ return self.get(key_id) is not None
444
+
445
+
446
+ class InMemoryKeyStore(BaseKeyStore):
447
+ """Thread-safe in-memory key storage.
448
+
449
+ WARNING: Keys are lost when the process exits. Use only for
450
+ testing or ephemeral keys.
451
+ """
452
+
453
+ def __init__(self) -> None:
454
+ self._keys: dict[str, EncryptionKey] = {}
455
+ self._lock = RLock()
456
+
457
+ def get(self, key_id: str) -> EncryptionKey | None:
458
+ with self._lock:
459
+ return self._keys.get(key_id)
460
+
461
+ def put(self, key: EncryptionKey) -> None:
462
+ with self._lock:
463
+ self._keys[key.key_id] = key
464
+
465
+ def delete(self, key_id: str) -> bool:
466
+ with self._lock:
467
+ if key_id in self._keys:
468
+ # Clear key material before removing
469
+ self._keys[key_id].clear()
470
+ del self._keys[key_id]
471
+ return True
472
+ return False
473
+
474
+ def list_keys(self) -> list[str]:
475
+ with self._lock:
476
+ return list(self._keys.keys())
477
+
478
+ def clear(self) -> None:
479
+ """Clear all keys from memory."""
480
+ with self._lock:
481
+ for key in self._keys.values():
482
+ key.clear()
483
+ self._keys.clear()
484
+
485
+
486
+ class FileKeyStore(BaseKeyStore):
487
+ """File-based key storage with encryption.
488
+
489
+ Keys are stored encrypted using a master key. The master key
490
+ should be provided externally (e.g., from environment variable).
491
+
492
+ WARNING: This is a basic implementation. For production use,
493
+ consider using a proper secrets manager.
494
+ """
495
+
496
+ def __init__(
497
+ self,
498
+ path: str | Path,
499
+ master_key: bytes | None = None,
500
+ master_password: str | None = None,
501
+ ) -> None:
502
+ """Initialize file key store.
503
+
504
+ Args:
505
+ path: Directory to store keys.
506
+ master_key: Master key for encrypting stored keys.
507
+ master_password: Password to derive master key (alternative to master_key).
508
+ """
509
+ self._path = Path(path)
510
+ self._path.mkdir(parents=True, exist_ok=True)
511
+ self._lock = RLock()
512
+
513
+ # Derive or use provided master key
514
+ if master_key:
515
+ self._master_key = master_key
516
+ elif master_password:
517
+ # Use fixed salt for master key (stored in .master_salt file)
518
+ salt_file = self._path / ".master_salt"
519
+ if salt_file.exists():
520
+ salt = salt_file.read_bytes()
521
+ else:
522
+ salt = generate_salt(16)
523
+ salt_file.write_bytes(salt)
524
+ self._master_key, _ = derive_key(master_password, salt=salt)
525
+ else:
526
+ # No encryption - store keys in plaintext (NOT RECOMMENDED)
527
+ self._master_key = None
528
+
529
+ def _get_key_path(self, key_id: str) -> Path:
530
+ """Get file path for a key."""
531
+ # Sanitize key_id for filesystem
532
+ safe_id = base64.urlsafe_b64encode(key_id.encode()).decode()
533
+ return self._path / f"{safe_id}.key"
534
+
535
+ def _encrypt_key_data(self, data: bytes) -> bytes:
536
+ """Encrypt key data with master key."""
537
+ if self._master_key is None:
538
+ return data
539
+
540
+ from truthound.stores.encryption.providers import AesGcmEncryptor
541
+
542
+ encryptor = AesGcmEncryptor(key_size=32)
543
+ return encryptor.encrypt(data, self._master_key)
544
+
545
+ def _decrypt_key_data(self, data: bytes) -> bytes:
546
+ """Decrypt key data with master key."""
547
+ if self._master_key is None:
548
+ return data
549
+
550
+ from truthound.stores.encryption.providers import AesGcmEncryptor
551
+
552
+ encryptor = AesGcmEncryptor(key_size=32)
553
+ return encryptor.decrypt(data, self._master_key)
554
+
555
+ def get(self, key_id: str) -> EncryptionKey | None:
556
+ with self._lock:
557
+ key_path = self._get_key_path(key_id)
558
+ if not key_path.exists():
559
+ return None
560
+
561
+ try:
562
+ encrypted_data = key_path.read_bytes()
563
+ data = self._decrypt_key_data(encrypted_data)
564
+ key_dict = json.loads(data.decode())
565
+
566
+ return EncryptionKey(
567
+ key_id=key_dict["key_id"],
568
+ key_material=base64.b64decode(key_dict["key_material"]),
569
+ algorithm=EncryptionAlgorithm(key_dict["algorithm"]),
570
+ key_type=KeyType(key_dict["key_type"]),
571
+ created_at=datetime.fromisoformat(key_dict["created_at"]),
572
+ expires_at=(
573
+ datetime.fromisoformat(key_dict["expires_at"])
574
+ if key_dict.get("expires_at")
575
+ else None
576
+ ),
577
+ version=key_dict["version"],
578
+ metadata=key_dict.get("metadata", {}),
579
+ )
580
+ except Exception:
581
+ return None
582
+
583
+ def put(self, key: EncryptionKey) -> None:
584
+ with self._lock:
585
+ key_dict = {
586
+ "key_id": key.key_id,
587
+ "key_material": base64.b64encode(key.key_material).decode(),
588
+ "algorithm": key.algorithm.value,
589
+ "key_type": key.key_type.value,
590
+ "created_at": key.created_at.isoformat(),
591
+ "expires_at": key.expires_at.isoformat() if key.expires_at else None,
592
+ "version": key.version,
593
+ "metadata": key.metadata,
594
+ }
595
+
596
+ data = json.dumps(key_dict).encode()
597
+ encrypted_data = self._encrypt_key_data(data)
598
+
599
+ key_path = self._get_key_path(key.key_id)
600
+ key_path.write_bytes(encrypted_data)
601
+
602
+ def delete(self, key_id: str) -> bool:
603
+ with self._lock:
604
+ key_path = self._get_key_path(key_id)
605
+ if key_path.exists():
606
+ # Overwrite with zeros before deletion
607
+ size = key_path.stat().st_size
608
+ key_path.write_bytes(b"\x00" * size)
609
+ key_path.unlink()
610
+ return True
611
+ return False
612
+
613
+ def list_keys(self) -> list[str]:
614
+ with self._lock:
615
+ keys = []
616
+ for key_path in self._path.glob("*.key"):
617
+ try:
618
+ safe_id = key_path.stem
619
+ key_id = base64.urlsafe_b64decode(safe_id).decode()
620
+ keys.append(key_id)
621
+ except Exception:
622
+ continue
623
+ return keys
624
+
625
+
626
+ class EnvironmentKeyStore(BaseKeyStore):
627
+ """Key storage using environment variables.
628
+
629
+ Keys are stored base64-encoded in environment variables with
630
+ a configurable prefix.
631
+
632
+ Example:
633
+ TRUTHOUND_KEY_mykey=base64_encoded_key
634
+ """
635
+
636
+ def __init__(self, prefix: str = "TRUTHOUND_KEY_") -> None:
637
+ """Initialize environment key store.
638
+
639
+ Args:
640
+ prefix: Prefix for environment variable names.
641
+ """
642
+ self._prefix = prefix
643
+
644
+ def get(self, key_id: str) -> EncryptionKey | None:
645
+ env_var = f"{self._prefix}{key_id}"
646
+ value = os.environ.get(env_var)
647
+ if not value:
648
+ return None
649
+
650
+ try:
651
+ key_material = base64.b64decode(value)
652
+ # Infer algorithm from key size
653
+ if len(key_material) == 16:
654
+ algorithm = EncryptionAlgorithm.AES_128_GCM
655
+ elif len(key_material) == 32:
656
+ algorithm = EncryptionAlgorithm.AES_256_GCM
657
+ elif len(key_material) == 44: # Fernet key (base64)
658
+ algorithm = EncryptionAlgorithm.FERNET
659
+ key_material = value.encode() # Fernet uses base64 string
660
+ else:
661
+ algorithm = EncryptionAlgorithm.AES_256_GCM
662
+
663
+ return EncryptionKey(
664
+ key_id=key_id,
665
+ key_material=key_material,
666
+ algorithm=algorithm,
667
+ )
668
+ except Exception:
669
+ return None
670
+
671
+ def put(self, key: EncryptionKey) -> None:
672
+ env_var = f"{self._prefix}{key.key_id}"
673
+ if key.algorithm == EncryptionAlgorithm.FERNET:
674
+ value = key.key_material.decode()
675
+ else:
676
+ value = base64.b64encode(key.key_material).decode()
677
+ os.environ[env_var] = value
678
+
679
+ def delete(self, key_id: str) -> bool:
680
+ env_var = f"{self._prefix}{key_id}"
681
+ if env_var in os.environ:
682
+ del os.environ[env_var]
683
+ return True
684
+ return False
685
+
686
+ def list_keys(self) -> list[str]:
687
+ keys = []
688
+ for var in os.environ:
689
+ if var.startswith(self._prefix):
690
+ key_id = var[len(self._prefix) :]
691
+ keys.append(key_id)
692
+ return keys
693
+
694
+
695
+ # =============================================================================
696
+ # Key Manager
697
+ # =============================================================================
698
+
699
+
700
+ @dataclass
701
+ class KeyManagerConfig:
702
+ """Configuration for key manager.
703
+
704
+ Attributes:
705
+ default_algorithm: Default encryption algorithm.
706
+ default_key_type: Default key type.
707
+ default_ttl: Default key TTL (None = no expiration).
708
+ auto_rotate_before_expiry: Auto-rotate keys before expiry.
709
+ rotation_overlap: Time to keep old key active after rotation.
710
+ """
711
+
712
+ default_algorithm: EncryptionAlgorithm = EncryptionAlgorithm.AES_256_GCM
713
+ default_key_type: KeyType = KeyType.DATA_ENCRYPTION_KEY
714
+ default_ttl: timedelta | None = None
715
+ auto_rotate_before_expiry: timedelta = timedelta(days=7)
716
+ rotation_overlap: timedelta = timedelta(hours=24)
717
+
718
+
719
+ class KeyManager:
720
+ """Comprehensive key management system.
721
+
722
+ Features:
723
+ - Key creation with automatic ID generation
724
+ - Key rotation with version tracking
725
+ - Multiple storage backend support
726
+ - Key expiration management
727
+ - Audit logging hooks
728
+
729
+ Example:
730
+ >>> manager = KeyManager()
731
+ >>> key = manager.create_key()
732
+ >>> encrypted = encrypt_data(data, key.key_material)
733
+ >>> manager.rotate_key(key.key_id)
734
+ """
735
+
736
+ def __init__(
737
+ self,
738
+ store: BaseKeyStore | None = None,
739
+ config: KeyManagerConfig | None = None,
740
+ audit_hook: Callable[[str, dict[str, Any]], None] | None = None,
741
+ ) -> None:
742
+ """Initialize key manager.
743
+
744
+ Args:
745
+ store: Key storage backend (defaults to in-memory).
746
+ config: Manager configuration.
747
+ audit_hook: Callback for audit events.
748
+ """
749
+ self._store = store or InMemoryKeyStore()
750
+ self._config = config or KeyManagerConfig()
751
+ self._audit_hook = audit_hook
752
+ self._lock = RLock()
753
+
754
+ def _audit(self, event: str, details: dict[str, Any]) -> None:
755
+ """Log audit event."""
756
+ if self._audit_hook:
757
+ self._audit_hook(event, {
758
+ "timestamp": datetime.now(timezone.utc).isoformat(),
759
+ **details,
760
+ })
761
+
762
+ def create_key(
763
+ self,
764
+ key_id: str | None = None,
765
+ algorithm: EncryptionAlgorithm | None = None,
766
+ key_type: KeyType | None = None,
767
+ ttl: timedelta | None = None,
768
+ metadata: dict[str, Any] | None = None,
769
+ ) -> EncryptionKey:
770
+ """Create a new encryption key.
771
+
772
+ Args:
773
+ key_id: Custom key ID (auto-generated if not provided).
774
+ algorithm: Encryption algorithm.
775
+ key_type: Type of key.
776
+ ttl: Time to live.
777
+ metadata: Additional metadata.
778
+
779
+ Returns:
780
+ New encryption key.
781
+ """
782
+ with self._lock:
783
+ key_id = key_id or generate_key_id()
784
+ algorithm = algorithm or self._config.default_algorithm
785
+ key_type = key_type or self._config.default_key_type
786
+
787
+ # Calculate expiration
788
+ expires_at = None
789
+ effective_ttl = ttl or self._config.default_ttl
790
+ if effective_ttl:
791
+ expires_at = datetime.now(timezone.utc) + effective_ttl
792
+
793
+ key = EncryptionKey(
794
+ key_id=key_id,
795
+ key_material=generate_key(algorithm),
796
+ algorithm=algorithm,
797
+ key_type=key_type,
798
+ expires_at=expires_at,
799
+ metadata=metadata or {},
800
+ )
801
+
802
+ self._store.put(key)
803
+ self._audit("key_created", {
804
+ "key_id": key_id,
805
+ "algorithm": algorithm.value,
806
+ "key_type": key_type.value,
807
+ })
808
+
809
+ return key
810
+
811
+ def get_key(self, key_id: str, validate: bool = True) -> EncryptionKey:
812
+ """Retrieve a key by ID.
813
+
814
+ Args:
815
+ key_id: Key identifier.
816
+ validate: Whether to validate key is not expired.
817
+
818
+ Returns:
819
+ Encryption key.
820
+
821
+ Raises:
822
+ KeyError_: If key not found.
823
+ KeyExpiredError: If key has expired.
824
+ """
825
+ key = self._store.get(key_id)
826
+ if key is None:
827
+ raise KeyError_(f"Key not found: {key_id}")
828
+
829
+ if validate:
830
+ key.validate()
831
+
832
+ self._audit("key_accessed", {"key_id": key_id})
833
+ return key
834
+
835
+ def rotate_key(
836
+ self,
837
+ key_id: str,
838
+ archive_old: bool = True,
839
+ ) -> EncryptionKey:
840
+ """Rotate a key (create new version).
841
+
842
+ Args:
843
+ key_id: Key to rotate.
844
+ archive_old: Whether to keep old key version.
845
+
846
+ Returns:
847
+ New key version.
848
+ """
849
+ with self._lock:
850
+ old_key = self.get_key(key_id, validate=False)
851
+
852
+ # Create new key with incremented version
853
+ new_key = EncryptionKey(
854
+ key_id=key_id,
855
+ key_material=generate_key(old_key.algorithm),
856
+ algorithm=old_key.algorithm,
857
+ key_type=old_key.key_type,
858
+ version=old_key.version + 1,
859
+ metadata={
860
+ **old_key.metadata,
861
+ "previous_version": old_key.version,
862
+ "rotated_at": datetime.now(timezone.utc).isoformat(),
863
+ },
864
+ )
865
+
866
+ if self._config.default_ttl:
867
+ new_key.expires_at = datetime.now(timezone.utc) + self._config.default_ttl
868
+
869
+ # Archive old key if requested
870
+ if archive_old:
871
+ archive_id = f"{key_id}_v{old_key.version}"
872
+ archived_key = EncryptionKey(
873
+ key_id=archive_id,
874
+ key_material=old_key.key_material,
875
+ algorithm=old_key.algorithm,
876
+ key_type=old_key.key_type,
877
+ created_at=old_key.created_at,
878
+ expires_at=datetime.now(timezone.utc) + self._config.rotation_overlap,
879
+ version=old_key.version,
880
+ metadata={**old_key.metadata, "archived": True},
881
+ )
882
+ self._store.put(archived_key)
883
+
884
+ # Store new key
885
+ self._store.put(new_key)
886
+ self._audit("key_rotated", {
887
+ "key_id": key_id,
888
+ "old_version": old_key.version,
889
+ "new_version": new_key.version,
890
+ })
891
+
892
+ return new_key
893
+
894
+ def delete_key(self, key_id: str) -> bool:
895
+ """Delete a key.
896
+
897
+ Args:
898
+ key_id: Key to delete.
899
+
900
+ Returns:
901
+ True if deleted.
902
+ """
903
+ with self._lock:
904
+ result = self._store.delete(key_id)
905
+ if result:
906
+ self._audit("key_deleted", {"key_id": key_id})
907
+ return result
908
+
909
+ def list_keys(
910
+ self,
911
+ include_expired: bool = False,
912
+ key_type: KeyType | None = None,
913
+ ) -> list[EncryptionKey]:
914
+ """List all keys.
915
+
916
+ Args:
917
+ include_expired: Include expired keys.
918
+ key_type: Filter by key type.
919
+
920
+ Returns:
921
+ List of keys.
922
+ """
923
+ keys = []
924
+ for key_id in self._store.list_keys():
925
+ key = self._store.get(key_id)
926
+ if key is None:
927
+ continue
928
+ if not include_expired and key.is_expired:
929
+ continue
930
+ if key_type and key.key_type != key_type:
931
+ continue
932
+ keys.append(key)
933
+ return keys
934
+
935
+ def get_or_create_key(
936
+ self,
937
+ key_id: str,
938
+ **create_kwargs: Any,
939
+ ) -> EncryptionKey:
940
+ """Get existing key or create new one.
941
+
942
+ Args:
943
+ key_id: Key identifier.
944
+ **create_kwargs: Arguments for create_key if creating.
945
+
946
+ Returns:
947
+ Encryption key.
948
+ """
949
+ try:
950
+ return self.get_key(key_id)
951
+ except KeyError_:
952
+ return self.create_key(key_id=key_id, **create_kwargs)
953
+
954
+ def cleanup_expired(self) -> int:
955
+ """Delete all expired keys.
956
+
957
+ Returns:
958
+ Number of keys deleted.
959
+ """
960
+ deleted = 0
961
+ for key_id in self._store.list_keys():
962
+ key = self._store.get(key_id)
963
+ if key and key.is_expired:
964
+ self.delete_key(key_id)
965
+ deleted += 1
966
+ return deleted
967
+
968
+
969
+ # =============================================================================
970
+ # Envelope Encryption
971
+ # =============================================================================
972
+
973
+
974
+ @dataclass
975
+ class EnvelopeEncryptedData:
976
+ """Data encrypted using envelope encryption.
977
+
978
+ Attributes:
979
+ encrypted_key: DEK encrypted with KEK.
980
+ encrypted_data: Data encrypted with DEK.
981
+ kek_id: ID of key encryption key used.
982
+ algorithm: Algorithm for data encryption.
983
+ nonce: Nonce used for data encryption.
984
+ tag: Authentication tag.
985
+ """
986
+
987
+ encrypted_key: bytes
988
+ encrypted_data: bytes
989
+ kek_id: str
990
+ algorithm: EncryptionAlgorithm
991
+ nonce: bytes
992
+ tag: bytes
993
+
994
+ def to_bytes(self) -> bytes:
995
+ """Serialize to bytes."""
996
+ header = json.dumps({
997
+ "kek_id": self.kek_id,
998
+ "algorithm": self.algorithm.value,
999
+ "key_len": len(self.encrypted_key),
1000
+ "nonce_len": len(self.nonce),
1001
+ "tag_len": len(self.tag),
1002
+ }).encode()
1003
+
1004
+ header_len = len(header).to_bytes(4, "big")
1005
+ return (
1006
+ header_len
1007
+ + header
1008
+ + self.encrypted_key
1009
+ + self.nonce
1010
+ + self.encrypted_data
1011
+ + self.tag
1012
+ )
1013
+
1014
+ @classmethod
1015
+ def from_bytes(cls, data: bytes) -> "EnvelopeEncryptedData":
1016
+ """Deserialize from bytes."""
1017
+ header_len = int.from_bytes(data[:4], "big")
1018
+ header = json.loads(data[4 : 4 + header_len].decode())
1019
+
1020
+ offset = 4 + header_len
1021
+ key_len = header["key_len"]
1022
+ nonce_len = header["nonce_len"]
1023
+ tag_len = header["tag_len"]
1024
+
1025
+ encrypted_key = data[offset : offset + key_len]
1026
+ offset += key_len
1027
+
1028
+ nonce = data[offset : offset + nonce_len]
1029
+ offset += nonce_len
1030
+
1031
+ encrypted_data = data[offset : -tag_len]
1032
+ tag = data[-tag_len:]
1033
+
1034
+ return cls(
1035
+ encrypted_key=encrypted_key,
1036
+ encrypted_data=encrypted_data,
1037
+ kek_id=header["kek_id"],
1038
+ algorithm=EncryptionAlgorithm(header["algorithm"]),
1039
+ nonce=nonce,
1040
+ tag=tag,
1041
+ )
1042
+
1043
+
1044
+ class EnvelopeEncryption:
1045
+ """Envelope encryption for secure key management.
1046
+
1047
+ Envelope encryption uses two levels of keys:
1048
+ - Key Encryption Key (KEK): Used to encrypt data keys
1049
+ - Data Encryption Key (DEK): Used to encrypt actual data
1050
+
1051
+ This pattern allows:
1052
+ - Easy key rotation (just re-encrypt DEK)
1053
+ - Integration with external KMS
1054
+ - Fine-grained access control
1055
+
1056
+ Example:
1057
+ >>> envelope = EnvelopeEncryption(key_manager)
1058
+ >>> encrypted = envelope.encrypt(data, kek_id="master_key")
1059
+ >>> decrypted = envelope.decrypt(encrypted)
1060
+ """
1061
+
1062
+ def __init__(
1063
+ self,
1064
+ key_manager: KeyManager,
1065
+ data_algorithm: EncryptionAlgorithm = EncryptionAlgorithm.AES_256_GCM,
1066
+ ) -> None:
1067
+ """Initialize envelope encryption.
1068
+
1069
+ Args:
1070
+ key_manager: Key manager for KEK storage.
1071
+ data_algorithm: Algorithm for data encryption.
1072
+ """
1073
+ self._key_manager = key_manager
1074
+ self._data_algorithm = data_algorithm
1075
+
1076
+ def encrypt(
1077
+ self,
1078
+ plaintext: bytes,
1079
+ kek_id: str,
1080
+ aad: bytes | None = None,
1081
+ ) -> EnvelopeEncryptedData:
1082
+ """Encrypt data using envelope encryption.
1083
+
1084
+ Args:
1085
+ plaintext: Data to encrypt.
1086
+ kek_id: ID of key encryption key.
1087
+ aad: Additional authenticated data.
1088
+
1089
+ Returns:
1090
+ Envelope encrypted data.
1091
+ """
1092
+ from truthound.stores.encryption.providers import get_encryptor
1093
+
1094
+ # Get KEK
1095
+ kek = self._key_manager.get_key(kek_id)
1096
+
1097
+ # Generate ephemeral DEK
1098
+ dek = generate_key(self._data_algorithm)
1099
+
1100
+ # Encrypt DEK with KEK
1101
+ kek_encryptor = get_encryptor(kek.algorithm)
1102
+ encrypted_dek = kek_encryptor.encrypt(dek, kek.key_material)
1103
+
1104
+ # Encrypt data with DEK
1105
+ dek_encryptor = get_encryptor(self._data_algorithm)
1106
+ result = dek_encryptor.encrypt_with_metrics(plaintext, dek, aad=aad)
1107
+
1108
+ # Clear DEK from memory
1109
+ dek = b"\x00" * len(dek)
1110
+
1111
+ return EnvelopeEncryptedData(
1112
+ encrypted_key=encrypted_dek,
1113
+ encrypted_data=result.ciphertext,
1114
+ kek_id=kek_id,
1115
+ algorithm=self._data_algorithm,
1116
+ nonce=result.nonce,
1117
+ tag=result.tag,
1118
+ )
1119
+
1120
+ def decrypt(
1121
+ self,
1122
+ envelope: EnvelopeEncryptedData,
1123
+ aad: bytes | None = None,
1124
+ ) -> bytes:
1125
+ """Decrypt envelope encrypted data.
1126
+
1127
+ Args:
1128
+ envelope: Envelope encrypted data.
1129
+ aad: Additional authenticated data.
1130
+
1131
+ Returns:
1132
+ Decrypted plaintext.
1133
+ """
1134
+ from truthound.stores.encryption.providers import get_encryptor
1135
+
1136
+ # Get KEK
1137
+ kek = self._key_manager.get_key(envelope.kek_id)
1138
+
1139
+ # Decrypt DEK
1140
+ kek_encryptor = get_encryptor(kek.algorithm)
1141
+ dek = kek_encryptor.decrypt(envelope.encrypted_key, kek.key_material)
1142
+
1143
+ # Decrypt data
1144
+ dek_encryptor = get_encryptor(envelope.algorithm)
1145
+ ciphertext = envelope.nonce + envelope.encrypted_data + envelope.tag
1146
+ plaintext = dek_encryptor.decrypt(ciphertext, dek, aad=aad)
1147
+
1148
+ # Clear DEK from memory
1149
+ dek = b"\x00" * len(dek)
1150
+
1151
+ return plaintext
1152
+
1153
+ def reencrypt_key(
1154
+ self,
1155
+ envelope: EnvelopeEncryptedData,
1156
+ new_kek_id: str,
1157
+ ) -> EnvelopeEncryptedData:
1158
+ """Re-encrypt DEK with a new KEK (for key rotation).
1159
+
1160
+ Args:
1161
+ envelope: Original envelope data.
1162
+ new_kek_id: ID of new key encryption key.
1163
+
1164
+ Returns:
1165
+ Envelope with re-encrypted DEK.
1166
+ """
1167
+ from truthound.stores.encryption.providers import get_encryptor
1168
+
1169
+ # Get old and new KEKs
1170
+ old_kek = self._key_manager.get_key(envelope.kek_id, validate=False)
1171
+ new_kek = self._key_manager.get_key(new_kek_id)
1172
+
1173
+ # Decrypt DEK with old KEK
1174
+ old_encryptor = get_encryptor(old_kek.algorithm)
1175
+ dek = old_encryptor.decrypt(envelope.encrypted_key, old_kek.key_material)
1176
+
1177
+ # Encrypt DEK with new KEK
1178
+ new_encryptor = get_encryptor(new_kek.algorithm)
1179
+ new_encrypted_dek = new_encryptor.encrypt(dek, new_kek.key_material)
1180
+
1181
+ # Clear DEK from memory
1182
+ dek = b"\x00" * len(dek)
1183
+
1184
+ return EnvelopeEncryptedData(
1185
+ encrypted_key=new_encrypted_dek,
1186
+ encrypted_data=envelope.encrypted_data,
1187
+ kek_id=new_kek_id,
1188
+ algorithm=envelope.algorithm,
1189
+ nonce=envelope.nonce,
1190
+ tag=envelope.tag,
1191
+ )