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,1083 @@
1
+ """Storage backends for audit logging.
2
+
3
+ This module provides various storage implementations for persisting
4
+ audit events:
5
+ - Memory: Fast, in-process storage for development/testing
6
+ - File: JSON/JSONL file-based storage
7
+ - SQLite: Local database storage
8
+ - SQL: Generic SQL database storage (PostgreSQL, MySQL, etc.)
9
+ - Elasticsearch: Full-text search capable storage
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import gzip
15
+ import json
16
+ import os
17
+ import sqlite3
18
+ import threading
19
+ from abc import ABC
20
+ from collections import deque
21
+ from dataclasses import dataclass, field
22
+ from datetime import datetime, timezone
23
+ from pathlib import Path
24
+ from typing import Any, Iterator
25
+
26
+ from truthound.audit.core import (
27
+ AuditEvent,
28
+ AuditEventType,
29
+ AuditOutcome,
30
+ AuditStorage,
31
+ AuditStorageError,
32
+ current_timestamp,
33
+ )
34
+
35
+
36
+ # =============================================================================
37
+ # Memory Storage
38
+ # =============================================================================
39
+
40
+
41
+ class MemoryAuditStorage(AuditStorage):
42
+ """In-memory storage for audit events.
43
+
44
+ Suitable for development, testing, and short-lived processes.
45
+
46
+ Example:
47
+ >>> storage = MemoryAuditStorage(max_events=10000)
48
+ >>> storage.write(event)
49
+ >>> events = storage.query(limit=10)
50
+ """
51
+
52
+ def __init__(
53
+ self,
54
+ max_events: int = 10000,
55
+ auto_expire: bool = True,
56
+ ) -> None:
57
+ """Initialize memory storage.
58
+
59
+ Args:
60
+ max_events: Maximum events to keep.
61
+ auto_expire: Automatically remove oldest events when full.
62
+ """
63
+ self._events: deque[AuditEvent] = deque(maxlen=max_events if auto_expire else None)
64
+ self._index: dict[str, AuditEvent] = {}
65
+ self._lock = threading.Lock()
66
+ self._max_events = max_events
67
+ self._auto_expire = auto_expire
68
+
69
+ def write(self, event: AuditEvent) -> None:
70
+ """Write a single audit event."""
71
+ with self._lock:
72
+ # Remove from index if we're at capacity and auto-expiring
73
+ if self._auto_expire and len(self._events) >= self._max_events:
74
+ old_event = self._events[0]
75
+ self._index.pop(old_event.id, None)
76
+
77
+ self._events.append(event)
78
+ self._index[event.id] = event
79
+
80
+ def write_batch(self, events: list[AuditEvent]) -> None:
81
+ """Write multiple audit events."""
82
+ with self._lock:
83
+ for event in events:
84
+ if self._auto_expire and len(self._events) >= self._max_events:
85
+ old_event = self._events[0]
86
+ self._index.pop(old_event.id, None)
87
+
88
+ self._events.append(event)
89
+ self._index[event.id] = event
90
+
91
+ def read(self, event_id: str) -> AuditEvent | None:
92
+ """Read a single audit event by ID."""
93
+ with self._lock:
94
+ return self._index.get(event_id)
95
+
96
+ def query(
97
+ self,
98
+ start_time: datetime | None = None,
99
+ end_time: datetime | None = None,
100
+ event_types: list[AuditEventType] | None = None,
101
+ actor_id: str | None = None,
102
+ resource_id: str | None = None,
103
+ outcome: AuditOutcome | None = None,
104
+ limit: int = 100,
105
+ offset: int = 0,
106
+ ) -> list[AuditEvent]:
107
+ """Query audit events."""
108
+ with self._lock:
109
+ results = []
110
+
111
+ for event in reversed(self._events): # Most recent first
112
+ if self._matches_filters(
113
+ event,
114
+ start_time,
115
+ end_time,
116
+ event_types,
117
+ actor_id,
118
+ resource_id,
119
+ outcome,
120
+ ):
121
+ results.append(event)
122
+
123
+ # Apply offset and limit
124
+ return results[offset : offset + limit]
125
+
126
+ def count(
127
+ self,
128
+ start_time: datetime | None = None,
129
+ end_time: datetime | None = None,
130
+ event_types: list[AuditEventType] | None = None,
131
+ ) -> int:
132
+ """Count matching audit events."""
133
+ with self._lock:
134
+ count = 0
135
+ for event in self._events:
136
+ if self._matches_filters(
137
+ event,
138
+ start_time,
139
+ end_time,
140
+ event_types,
141
+ None,
142
+ None,
143
+ None,
144
+ ):
145
+ count += 1
146
+ return count
147
+
148
+ def delete_before(self, before: datetime) -> int:
149
+ """Delete events before a given time."""
150
+ with self._lock:
151
+ deleted = 0
152
+ events_to_keep = []
153
+
154
+ for event in self._events:
155
+ if event.timestamp >= before:
156
+ events_to_keep.append(event)
157
+ else:
158
+ self._index.pop(event.id, None)
159
+ deleted += 1
160
+
161
+ self._events.clear()
162
+ for event in events_to_keep:
163
+ self._events.append(event)
164
+
165
+ return deleted
166
+
167
+ def clear(self) -> None:
168
+ """Clear all events."""
169
+ with self._lock:
170
+ self._events.clear()
171
+ self._index.clear()
172
+
173
+ def _matches_filters(
174
+ self,
175
+ event: AuditEvent,
176
+ start_time: datetime | None,
177
+ end_time: datetime | None,
178
+ event_types: list[AuditEventType] | None,
179
+ actor_id: str | None,
180
+ resource_id: str | None,
181
+ outcome: AuditOutcome | None,
182
+ ) -> bool:
183
+ """Check if event matches all filters."""
184
+ if start_time and event.timestamp < start_time:
185
+ return False
186
+ if end_time and event.timestamp > end_time:
187
+ return False
188
+ if event_types and event.event_type not in event_types:
189
+ return False
190
+ if actor_id and (not event.actor or event.actor.id != actor_id):
191
+ return False
192
+ if resource_id and (not event.resource or event.resource.id != resource_id):
193
+ return False
194
+ if outcome and event.outcome != outcome:
195
+ return False
196
+ return True
197
+
198
+
199
+ # =============================================================================
200
+ # File Storage
201
+ # =============================================================================
202
+
203
+
204
+ @dataclass
205
+ class FileStorageConfig:
206
+ """Configuration for file-based audit storage."""
207
+
208
+ path: str = "./audit_logs"
209
+ filename_pattern: str = "audit_{date}.jsonl"
210
+ compress: bool = False
211
+ max_file_size_mb: int = 100
212
+ rotate_daily: bool = True
213
+ encoding: str = "utf-8"
214
+
215
+
216
+ class FileAuditStorage(AuditStorage):
217
+ """File-based storage for audit events.
218
+
219
+ Supports JSONL format with optional compression and rotation.
220
+
221
+ Example:
222
+ >>> storage = FileAuditStorage(
223
+ ... config=FileStorageConfig(
224
+ ... path="./audit_logs",
225
+ ... compress=True,
226
+ ... )
227
+ ... )
228
+ """
229
+
230
+ def __init__(self, config: FileStorageConfig | None = None) -> None:
231
+ """Initialize file storage.
232
+
233
+ Args:
234
+ config: Storage configuration.
235
+ """
236
+ self._config = config or FileStorageConfig()
237
+ self._lock = threading.Lock()
238
+ self._current_file: Any = None
239
+ self._current_filename: str = ""
240
+
241
+ # Ensure directory exists
242
+ Path(self._config.path).mkdir(parents=True, exist_ok=True)
243
+
244
+ def write(self, event: AuditEvent) -> None:
245
+ """Write a single audit event."""
246
+ with self._lock:
247
+ self._ensure_file_open()
248
+ line = json.dumps(event.to_dict()) + "\n"
249
+ self._current_file.write(line)
250
+ self._current_file.flush()
251
+
252
+ def write_batch(self, events: list[AuditEvent]) -> None:
253
+ """Write multiple audit events."""
254
+ with self._lock:
255
+ self._ensure_file_open()
256
+ for event in events:
257
+ line = json.dumps(event.to_dict()) + "\n"
258
+ self._current_file.write(line)
259
+ self._current_file.flush()
260
+
261
+ def read(self, event_id: str) -> AuditEvent | None:
262
+ """Read a single audit event by ID."""
263
+ # Search through all files
264
+ for filepath in self._get_all_files():
265
+ try:
266
+ for event in self._read_file(filepath):
267
+ if event.id == event_id:
268
+ return event
269
+ except Exception:
270
+ continue
271
+ return None
272
+
273
+ def query(
274
+ self,
275
+ start_time: datetime | None = None,
276
+ end_time: datetime | None = None,
277
+ event_types: list[AuditEventType] | None = None,
278
+ actor_id: str | None = None,
279
+ resource_id: str | None = None,
280
+ outcome: AuditOutcome | None = None,
281
+ limit: int = 100,
282
+ offset: int = 0,
283
+ ) -> list[AuditEvent]:
284
+ """Query audit events."""
285
+ results = []
286
+ skipped = 0
287
+
288
+ for filepath in reversed(self._get_all_files()): # Most recent first
289
+ if len(results) >= limit:
290
+ break
291
+
292
+ try:
293
+ for event in self._read_file(filepath):
294
+ if self._matches_filters(
295
+ event,
296
+ start_time,
297
+ end_time,
298
+ event_types,
299
+ actor_id,
300
+ resource_id,
301
+ outcome,
302
+ ):
303
+ if skipped < offset:
304
+ skipped += 1
305
+ else:
306
+ results.append(event)
307
+ if len(results) >= limit:
308
+ break
309
+ except Exception:
310
+ continue
311
+
312
+ return results
313
+
314
+ def count(
315
+ self,
316
+ start_time: datetime | None = None,
317
+ end_time: datetime | None = None,
318
+ event_types: list[AuditEventType] | None = None,
319
+ ) -> int:
320
+ """Count matching audit events."""
321
+ count = 0
322
+
323
+ for filepath in self._get_all_files():
324
+ try:
325
+ for event in self._read_file(filepath):
326
+ if self._matches_filters(
327
+ event,
328
+ start_time,
329
+ end_time,
330
+ event_types,
331
+ None,
332
+ None,
333
+ None,
334
+ ):
335
+ count += 1
336
+ except Exception:
337
+ continue
338
+
339
+ return count
340
+
341
+ def delete_before(self, before: datetime) -> int:
342
+ """Delete events before a given time."""
343
+ deleted = 0
344
+
345
+ for filepath in self._get_all_files():
346
+ # Check if entire file is before cutoff
347
+ try:
348
+ file_date = self._extract_date_from_filename(filepath)
349
+ if file_date and file_date.date() < before.date():
350
+ os.remove(filepath)
351
+ deleted += 1 # Count as 1 per file
352
+ except Exception:
353
+ continue
354
+
355
+ return deleted
356
+
357
+ def close(self) -> None:
358
+ """Close storage."""
359
+ with self._lock:
360
+ if self._current_file:
361
+ self._current_file.close()
362
+ self._current_file = None
363
+
364
+ def flush(self) -> None:
365
+ """Flush buffered data."""
366
+ with self._lock:
367
+ if self._current_file:
368
+ self._current_file.flush()
369
+
370
+ def _ensure_file_open(self) -> None:
371
+ """Ensure current file is open."""
372
+ filename = self._get_current_filename()
373
+
374
+ if filename != self._current_filename:
375
+ if self._current_file:
376
+ self._current_file.close()
377
+
378
+ filepath = os.path.join(self._config.path, filename)
379
+
380
+ if self._config.compress:
381
+ self._current_file = gzip.open(
382
+ filepath + ".gz",
383
+ "at",
384
+ encoding=self._config.encoding,
385
+ )
386
+ else:
387
+ self._current_file = open(
388
+ filepath,
389
+ "a",
390
+ encoding=self._config.encoding,
391
+ )
392
+
393
+ self._current_filename = filename
394
+
395
+ def _get_current_filename(self) -> str:
396
+ """Get current filename based on pattern."""
397
+ now = current_timestamp()
398
+ return self._config.filename_pattern.format(
399
+ date=now.strftime("%Y-%m-%d"),
400
+ datetime=now.strftime("%Y-%m-%d_%H"),
401
+ )
402
+
403
+ def _get_all_files(self) -> list[str]:
404
+ """Get all audit log files sorted by date."""
405
+ path = Path(self._config.path)
406
+ files = []
407
+
408
+ for f in path.glob("audit_*.jsonl*"):
409
+ files.append(str(f))
410
+
411
+ return sorted(files)
412
+
413
+ def _read_file(self, filepath: str) -> Iterator[AuditEvent]:
414
+ """Read events from a file."""
415
+ opener = gzip.open if filepath.endswith(".gz") else open
416
+
417
+ with opener(filepath, "rt", encoding=self._config.encoding) as f:
418
+ for line in f:
419
+ line = line.strip()
420
+ if line:
421
+ try:
422
+ data = json.loads(line)
423
+ yield AuditEvent.from_dict(data)
424
+ except (json.JSONDecodeError, TypeError):
425
+ continue
426
+
427
+ def _extract_date_from_filename(self, filepath: str) -> datetime | None:
428
+ """Extract date from filename."""
429
+ import re
430
+
431
+ match = re.search(r"audit_(\d{4}-\d{2}-\d{2})", filepath)
432
+ if match:
433
+ return datetime.strptime(match.group(1), "%Y-%m-%d").replace(
434
+ tzinfo=timezone.utc
435
+ )
436
+ return None
437
+
438
+ def _matches_filters(
439
+ self,
440
+ event: AuditEvent,
441
+ start_time: datetime | None,
442
+ end_time: datetime | None,
443
+ event_types: list[AuditEventType] | None,
444
+ actor_id: str | None,
445
+ resource_id: str | None,
446
+ outcome: AuditOutcome | None,
447
+ ) -> bool:
448
+ """Check if event matches all filters."""
449
+ if start_time and event.timestamp < start_time:
450
+ return False
451
+ if end_time and event.timestamp > end_time:
452
+ return False
453
+ if event_types and event.event_type not in event_types:
454
+ return False
455
+ if actor_id and (not event.actor or event.actor.id != actor_id):
456
+ return False
457
+ if resource_id and (not event.resource or event.resource.id != resource_id):
458
+ return False
459
+ if outcome and event.outcome != outcome:
460
+ return False
461
+ return True
462
+
463
+
464
+ # =============================================================================
465
+ # SQLite Storage
466
+ # =============================================================================
467
+
468
+
469
+ class SQLiteAuditStorage(AuditStorage):
470
+ """SQLite-based storage for audit events.
471
+
472
+ Provides persistent local storage with SQL query capabilities.
473
+
474
+ Example:
475
+ >>> storage = SQLiteAuditStorage("./audit.db")
476
+ >>> storage.write(event)
477
+ """
478
+
479
+ def __init__(
480
+ self,
481
+ db_path: str = "./audit.db",
482
+ *,
483
+ create_indexes: bool = True,
484
+ ) -> None:
485
+ """Initialize SQLite storage.
486
+
487
+ Args:
488
+ db_path: Path to SQLite database file.
489
+ create_indexes: Create indexes for common queries.
490
+ """
491
+ self._db_path = db_path
492
+ self._lock = threading.Lock()
493
+ self._local = threading.local()
494
+
495
+ # Initialize schema
496
+ self._init_schema(create_indexes)
497
+
498
+ def _get_connection(self) -> sqlite3.Connection:
499
+ """Get thread-local database connection."""
500
+ if not hasattr(self._local, "conn"):
501
+ self._local.conn = sqlite3.connect(self._db_path)
502
+ self._local.conn.row_factory = sqlite3.Row
503
+ return self._local.conn
504
+
505
+ def _init_schema(self, create_indexes: bool) -> None:
506
+ """Initialize database schema."""
507
+ conn = self._get_connection()
508
+ cursor = conn.cursor()
509
+
510
+ cursor.execute("""
511
+ CREATE TABLE IF NOT EXISTS audit_events (
512
+ id TEXT PRIMARY KEY,
513
+ timestamp TEXT NOT NULL,
514
+ timestamp_unix REAL NOT NULL,
515
+ event_type TEXT NOT NULL,
516
+ category TEXT NOT NULL,
517
+ severity TEXT NOT NULL,
518
+ action TEXT,
519
+ outcome TEXT NOT NULL,
520
+ message TEXT,
521
+ reason TEXT,
522
+ actor_id TEXT,
523
+ actor_type TEXT,
524
+ actor_name TEXT,
525
+ actor_ip TEXT,
526
+ resource_id TEXT,
527
+ resource_type TEXT,
528
+ resource_name TEXT,
529
+ target_id TEXT,
530
+ context_request_id TEXT,
531
+ context_trace_id TEXT,
532
+ context_environment TEXT,
533
+ data_json TEXT,
534
+ tags_json TEXT,
535
+ duration_ms REAL,
536
+ checksum TEXT
537
+ )
538
+ """)
539
+
540
+ if create_indexes:
541
+ cursor.execute(
542
+ "CREATE INDEX IF NOT EXISTS idx_timestamp ON audit_events(timestamp_unix)"
543
+ )
544
+ cursor.execute(
545
+ "CREATE INDEX IF NOT EXISTS idx_event_type ON audit_events(event_type)"
546
+ )
547
+ cursor.execute(
548
+ "CREATE INDEX IF NOT EXISTS idx_actor_id ON audit_events(actor_id)"
549
+ )
550
+ cursor.execute(
551
+ "CREATE INDEX IF NOT EXISTS idx_resource_id ON audit_events(resource_id)"
552
+ )
553
+ cursor.execute(
554
+ "CREATE INDEX IF NOT EXISTS idx_outcome ON audit_events(outcome)"
555
+ )
556
+
557
+ conn.commit()
558
+
559
+ def write(self, event: AuditEvent) -> None:
560
+ """Write a single audit event."""
561
+ with self._lock:
562
+ conn = self._get_connection()
563
+ self._insert_event(conn, event)
564
+ conn.commit()
565
+
566
+ def write_batch(self, events: list[AuditEvent]) -> None:
567
+ """Write multiple audit events."""
568
+ with self._lock:
569
+ conn = self._get_connection()
570
+ for event in events:
571
+ self._insert_event(conn, event)
572
+ conn.commit()
573
+
574
+ def _insert_event(self, conn: sqlite3.Connection, event: AuditEvent) -> None:
575
+ """Insert a single event."""
576
+ cursor = conn.cursor()
577
+ cursor.execute(
578
+ """
579
+ INSERT OR REPLACE INTO audit_events (
580
+ id, timestamp, timestamp_unix, event_type, category, severity,
581
+ action, outcome, message, reason,
582
+ actor_id, actor_type, actor_name, actor_ip,
583
+ resource_id, resource_type, resource_name, target_id,
584
+ context_request_id, context_trace_id, context_environment,
585
+ data_json, tags_json, duration_ms, checksum
586
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
587
+ """,
588
+ (
589
+ event.id,
590
+ event.timestamp_iso,
591
+ event.timestamp_unix,
592
+ event.event_type.value,
593
+ event.category.value,
594
+ event.severity.value,
595
+ event.action,
596
+ event.outcome.value,
597
+ event.message,
598
+ event.reason,
599
+ event.actor.id if event.actor else None,
600
+ event.actor.type if event.actor else None,
601
+ event.actor.name if event.actor else None,
602
+ event.actor.ip_address if event.actor else None,
603
+ event.resource.id if event.resource else None,
604
+ event.resource.type if event.resource else None,
605
+ event.resource.name if event.resource else None,
606
+ event.target.id if event.target else None,
607
+ event.context.request_id,
608
+ event.context.trace_id,
609
+ event.context.environment,
610
+ json.dumps(event.data),
611
+ json.dumps(event.tags),
612
+ event.duration_ms,
613
+ event.compute_checksum(),
614
+ ),
615
+ )
616
+
617
+ def read(self, event_id: str) -> AuditEvent | None:
618
+ """Read a single audit event by ID."""
619
+ conn = self._get_connection()
620
+ cursor = conn.cursor()
621
+ cursor.execute("SELECT * FROM audit_events WHERE id = ?", (event_id,))
622
+ row = cursor.fetchone()
623
+ if row:
624
+ return self._row_to_event(row)
625
+ return None
626
+
627
+ def query(
628
+ self,
629
+ start_time: datetime | None = None,
630
+ end_time: datetime | None = None,
631
+ event_types: list[AuditEventType] | None = None,
632
+ actor_id: str | None = None,
633
+ resource_id: str | None = None,
634
+ outcome: AuditOutcome | None = None,
635
+ limit: int = 100,
636
+ offset: int = 0,
637
+ ) -> list[AuditEvent]:
638
+ """Query audit events."""
639
+ conn = self._get_connection()
640
+ cursor = conn.cursor()
641
+
642
+ query = "SELECT * FROM audit_events WHERE 1=1"
643
+ params: list[Any] = []
644
+
645
+ if start_time:
646
+ query += " AND timestamp_unix >= ?"
647
+ params.append(start_time.timestamp())
648
+ if end_time:
649
+ query += " AND timestamp_unix <= ?"
650
+ params.append(end_time.timestamp())
651
+ if event_types:
652
+ placeholders = ",".join("?" * len(event_types))
653
+ query += f" AND event_type IN ({placeholders})"
654
+ params.extend(et.value for et in event_types)
655
+ if actor_id:
656
+ query += " AND actor_id = ?"
657
+ params.append(actor_id)
658
+ if resource_id:
659
+ query += " AND resource_id = ?"
660
+ params.append(resource_id)
661
+ if outcome:
662
+ query += " AND outcome = ?"
663
+ params.append(outcome.value)
664
+
665
+ query += " ORDER BY timestamp_unix DESC LIMIT ? OFFSET ?"
666
+ params.extend([limit, offset])
667
+
668
+ cursor.execute(query, params)
669
+ return [self._row_to_event(row) for row in cursor.fetchall()]
670
+
671
+ def count(
672
+ self,
673
+ start_time: datetime | None = None,
674
+ end_time: datetime | None = None,
675
+ event_types: list[AuditEventType] | None = None,
676
+ ) -> int:
677
+ """Count matching audit events."""
678
+ conn = self._get_connection()
679
+ cursor = conn.cursor()
680
+
681
+ query = "SELECT COUNT(*) FROM audit_events WHERE 1=1"
682
+ params: list[Any] = []
683
+
684
+ if start_time:
685
+ query += " AND timestamp_unix >= ?"
686
+ params.append(start_time.timestamp())
687
+ if end_time:
688
+ query += " AND timestamp_unix <= ?"
689
+ params.append(end_time.timestamp())
690
+ if event_types:
691
+ placeholders = ",".join("?" * len(event_types))
692
+ query += f" AND event_type IN ({placeholders})"
693
+ params.extend(et.value for et in event_types)
694
+
695
+ cursor.execute(query, params)
696
+ return cursor.fetchone()[0]
697
+
698
+ def delete_before(self, before: datetime) -> int:
699
+ """Delete events before a given time."""
700
+ with self._lock:
701
+ conn = self._get_connection()
702
+ cursor = conn.cursor()
703
+ cursor.execute(
704
+ "DELETE FROM audit_events WHERE timestamp_unix < ?",
705
+ (before.timestamp(),),
706
+ )
707
+ deleted = cursor.rowcount
708
+ conn.commit()
709
+ return deleted
710
+
711
+ def close(self) -> None:
712
+ """Close storage."""
713
+ if hasattr(self._local, "conn"):
714
+ self._local.conn.close()
715
+ del self._local.conn
716
+
717
+ def _row_to_event(self, row: sqlite3.Row) -> AuditEvent:
718
+ """Convert database row to AuditEvent."""
719
+ from truthound.audit.core import (
720
+ AuditActor,
721
+ AuditResource,
722
+ AuditContext,
723
+ )
724
+
725
+ actor = None
726
+ if row["actor_id"]:
727
+ actor = AuditActor(
728
+ id=row["actor_id"],
729
+ type=row["actor_type"] or "user",
730
+ name=row["actor_name"] or "",
731
+ ip_address=row["actor_ip"] or "",
732
+ )
733
+
734
+ resource = None
735
+ if row["resource_id"]:
736
+ resource = AuditResource(
737
+ id=row["resource_id"],
738
+ type=row["resource_type"] or "",
739
+ name=row["resource_name"] or "",
740
+ )
741
+
742
+ target = None
743
+ if row["target_id"]:
744
+ target = AuditResource(
745
+ id=row["target_id"],
746
+ type="",
747
+ name="",
748
+ )
749
+
750
+ context = AuditContext(
751
+ request_id=row["context_request_id"] or "",
752
+ trace_id=row["context_trace_id"] or "",
753
+ environment=row["context_environment"] or "",
754
+ )
755
+
756
+ timestamp = datetime.fromisoformat(
757
+ row["timestamp"].replace("Z", "+00:00")
758
+ )
759
+
760
+ return AuditEvent(
761
+ id=row["id"],
762
+ timestamp=timestamp,
763
+ event_type=AuditEventType(row["event_type"]),
764
+ category=AuditCategory(row["category"]),
765
+ severity=AuditSeverity(row["severity"]),
766
+ action=row["action"] or "",
767
+ outcome=AuditOutcome(row["outcome"]),
768
+ message=row["message"] or "",
769
+ reason=row["reason"] or "",
770
+ actor=actor,
771
+ resource=resource,
772
+ target=target,
773
+ context=context,
774
+ data=json.loads(row["data_json"]) if row["data_json"] else {},
775
+ tags=json.loads(row["tags_json"]) if row["tags_json"] else [],
776
+ duration_ms=row["duration_ms"],
777
+ checksum=row["checksum"] or "",
778
+ )
779
+
780
+
781
+ # Need to import these for the _row_to_event method
782
+ from truthound.audit.core import AuditCategory, AuditSeverity
783
+
784
+
785
+ # =============================================================================
786
+ # Async File Storage
787
+ # =============================================================================
788
+
789
+
790
+ class AsyncBufferedStorage(AuditStorage):
791
+ """Buffered storage that writes asynchronously.
792
+
793
+ Wraps any storage backend with buffering for improved performance.
794
+
795
+ Example:
796
+ >>> base_storage = FileAuditStorage(config)
797
+ >>> storage = AsyncBufferedStorage(
798
+ ... base_storage,
799
+ ... buffer_size=100,
800
+ ... flush_interval=5.0,
801
+ ... )
802
+ """
803
+
804
+ def __init__(
805
+ self,
806
+ storage: AuditStorage,
807
+ buffer_size: int = 100,
808
+ flush_interval: float = 5.0,
809
+ ) -> None:
810
+ """Initialize async buffered storage.
811
+
812
+ Args:
813
+ storage: Underlying storage backend.
814
+ buffer_size: Maximum buffer size before auto-flush.
815
+ flush_interval: Seconds between auto-flushes.
816
+ """
817
+ self._storage = storage
818
+ self._buffer: list[AuditEvent] = []
819
+ self._buffer_size = buffer_size
820
+ self._flush_interval = flush_interval
821
+ self._lock = threading.Lock()
822
+ self._stop_event = threading.Event()
823
+ self._flush_thread: threading.Thread | None = None
824
+
825
+ # Start flush thread
826
+ self._start_flush_thread()
827
+
828
+ def _start_flush_thread(self) -> None:
829
+ """Start background flush thread."""
830
+ def flush_loop() -> None:
831
+ while not self._stop_event.wait(self._flush_interval):
832
+ self.flush()
833
+
834
+ self._flush_thread = threading.Thread(target=flush_loop, daemon=True)
835
+ self._flush_thread.start()
836
+
837
+ def write(self, event: AuditEvent) -> None:
838
+ """Write event to buffer."""
839
+ with self._lock:
840
+ self._buffer.append(event)
841
+ if len(self._buffer) >= self._buffer_size:
842
+ self._flush_internal()
843
+
844
+ def write_batch(self, events: list[AuditEvent]) -> None:
845
+ """Write events to buffer."""
846
+ with self._lock:
847
+ self._buffer.extend(events)
848
+ if len(self._buffer) >= self._buffer_size:
849
+ self._flush_internal()
850
+
851
+ def read(self, event_id: str) -> AuditEvent | None:
852
+ """Read from underlying storage."""
853
+ # Check buffer first
854
+ with self._lock:
855
+ for event in self._buffer:
856
+ if event.id == event_id:
857
+ return event
858
+
859
+ return self._storage.read(event_id)
860
+
861
+ def query(
862
+ self,
863
+ start_time: datetime | None = None,
864
+ end_time: datetime | None = None,
865
+ event_types: list[AuditEventType] | None = None,
866
+ actor_id: str | None = None,
867
+ resource_id: str | None = None,
868
+ outcome: AuditOutcome | None = None,
869
+ limit: int = 100,
870
+ offset: int = 0,
871
+ ) -> list[AuditEvent]:
872
+ """Query from underlying storage."""
873
+ # Flush buffer first to ensure complete results
874
+ self.flush()
875
+ return self._storage.query(
876
+ start_time,
877
+ end_time,
878
+ event_types,
879
+ actor_id,
880
+ resource_id,
881
+ outcome,
882
+ limit,
883
+ offset,
884
+ )
885
+
886
+ def count(
887
+ self,
888
+ start_time: datetime | None = None,
889
+ end_time: datetime | None = None,
890
+ event_types: list[AuditEventType] | None = None,
891
+ ) -> int:
892
+ """Count from underlying storage."""
893
+ self.flush()
894
+ return self._storage.count(start_time, end_time, event_types)
895
+
896
+ def delete_before(self, before: datetime) -> int:
897
+ """Delete from underlying storage."""
898
+ self.flush()
899
+ return self._storage.delete_before(before)
900
+
901
+ def flush(self) -> None:
902
+ """Flush buffer to storage."""
903
+ with self._lock:
904
+ self._flush_internal()
905
+
906
+ def _flush_internal(self) -> None:
907
+ """Internal flush without lock."""
908
+ if self._buffer:
909
+ try:
910
+ self._storage.write_batch(self._buffer)
911
+ self._buffer = []
912
+ except Exception as e:
913
+ # Keep events in buffer on failure
914
+ raise AuditStorageError(f"Failed to flush buffer: {e}") from e
915
+
916
+ def close(self) -> None:
917
+ """Close storage."""
918
+ self._stop_event.set()
919
+ if self._flush_thread:
920
+ self._flush_thread.join(timeout=self._flush_interval * 2)
921
+ self.flush()
922
+ self._storage.close()
923
+
924
+
925
+ # =============================================================================
926
+ # Composite Storage
927
+ # =============================================================================
928
+
929
+
930
+ class CompositeAuditStorage(AuditStorage):
931
+ """Storage that writes to multiple backends.
932
+
933
+ Useful for writing to both fast (memory) and persistent (file/db) storage.
934
+
935
+ Example:
936
+ >>> storage = CompositeAuditStorage([
937
+ ... MemoryAuditStorage(max_events=1000),
938
+ ... SQLiteAuditStorage("./audit.db"),
939
+ ... ])
940
+ """
941
+
942
+ def __init__(
943
+ self,
944
+ storages: list[AuditStorage],
945
+ *,
946
+ primary_index: int = 0,
947
+ fail_fast: bool = False,
948
+ ) -> None:
949
+ """Initialize composite storage.
950
+
951
+ Args:
952
+ storages: List of storage backends.
953
+ primary_index: Index of primary storage for reads.
954
+ fail_fast: Fail if any storage fails.
955
+ """
956
+ self._storages = storages
957
+ self._primary_index = primary_index
958
+ self._fail_fast = fail_fast
959
+
960
+ @property
961
+ def primary(self) -> AuditStorage:
962
+ """Get primary storage."""
963
+ return self._storages[self._primary_index]
964
+
965
+ def write(self, event: AuditEvent) -> None:
966
+ """Write to all storages."""
967
+ errors = []
968
+ for storage in self._storages:
969
+ try:
970
+ storage.write(event)
971
+ except Exception as e:
972
+ if self._fail_fast:
973
+ raise
974
+ errors.append(e)
975
+
976
+ if errors and len(errors) == len(self._storages):
977
+ raise AuditStorageError(f"All storages failed: {errors}")
978
+
979
+ def write_batch(self, events: list[AuditEvent]) -> None:
980
+ """Write batch to all storages."""
981
+ errors = []
982
+ for storage in self._storages:
983
+ try:
984
+ storage.write_batch(events)
985
+ except Exception as e:
986
+ if self._fail_fast:
987
+ raise
988
+ errors.append(e)
989
+
990
+ if errors and len(errors) == len(self._storages):
991
+ raise AuditStorageError(f"All storages failed: {errors}")
992
+
993
+ def read(self, event_id: str) -> AuditEvent | None:
994
+ """Read from primary storage."""
995
+ return self.primary.read(event_id)
996
+
997
+ def query(
998
+ self,
999
+ start_time: datetime | None = None,
1000
+ end_time: datetime | None = None,
1001
+ event_types: list[AuditEventType] | None = None,
1002
+ actor_id: str | None = None,
1003
+ resource_id: str | None = None,
1004
+ outcome: AuditOutcome | None = None,
1005
+ limit: int = 100,
1006
+ offset: int = 0,
1007
+ ) -> list[AuditEvent]:
1008
+ """Query from primary storage."""
1009
+ return self.primary.query(
1010
+ start_time,
1011
+ end_time,
1012
+ event_types,
1013
+ actor_id,
1014
+ resource_id,
1015
+ outcome,
1016
+ limit,
1017
+ offset,
1018
+ )
1019
+
1020
+ def count(
1021
+ self,
1022
+ start_time: datetime | None = None,
1023
+ end_time: datetime | None = None,
1024
+ event_types: list[AuditEventType] | None = None,
1025
+ ) -> int:
1026
+ """Count from primary storage."""
1027
+ return self.primary.count(start_time, end_time, event_types)
1028
+
1029
+ def delete_before(self, before: datetime) -> int:
1030
+ """Delete from all storages."""
1031
+ max_deleted = 0
1032
+ for storage in self._storages:
1033
+ try:
1034
+ deleted = storage.delete_before(before)
1035
+ max_deleted = max(max_deleted, deleted)
1036
+ except Exception:
1037
+ if self._fail_fast:
1038
+ raise
1039
+ return max_deleted
1040
+
1041
+ def flush(self) -> None:
1042
+ """Flush all storages."""
1043
+ for storage in self._storages:
1044
+ storage.flush()
1045
+
1046
+ def close(self) -> None:
1047
+ """Close all storages."""
1048
+ for storage in self._storages:
1049
+ storage.close()
1050
+
1051
+
1052
+ # =============================================================================
1053
+ # Storage Factory
1054
+ # =============================================================================
1055
+
1056
+
1057
+ def create_storage(
1058
+ backend: str = "memory",
1059
+ **kwargs: Any,
1060
+ ) -> AuditStorage:
1061
+ """Create storage backend from configuration.
1062
+
1063
+ Args:
1064
+ backend: Storage backend type.
1065
+ **kwargs: Backend-specific configuration.
1066
+
1067
+ Returns:
1068
+ Storage instance.
1069
+
1070
+ Example:
1071
+ >>> storage = create_storage("memory", max_events=10000)
1072
+ >>> storage = create_storage("file", path="./audit_logs")
1073
+ >>> storage = create_storage("sqlite", db_path="./audit.db")
1074
+ """
1075
+ if backend == "memory":
1076
+ return MemoryAuditStorage(**kwargs)
1077
+ elif backend == "file":
1078
+ config = FileStorageConfig(**kwargs)
1079
+ return FileAuditStorage(config)
1080
+ elif backend == "sqlite":
1081
+ return SQLiteAuditStorage(**kwargs)
1082
+ else:
1083
+ raise ValueError(f"Unknown storage backend: {backend}")