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,1180 @@
1
+ """Resilience patterns for cache backends.
2
+
3
+ This module provides fault-tolerant caching with:
4
+ - Circuit breaker pattern for failure detection
5
+ - Automatic fallback to alternative backends
6
+ - Retry logic with exponential backoff
7
+ - Health monitoring and auto-recovery
8
+ - Comprehensive logging and observability
9
+
10
+ Key classes:
11
+ - CircuitBreaker: Prevents cascade failures
12
+ - RetryPolicy: Configurable retry with backoff
13
+ - ResilientCacheBackend: Wrapper with resilience patterns
14
+ - FallbackChain: Multi-backend fallback chain
15
+ - HealthMonitor: Continuous backend health checking
16
+
17
+ Example:
18
+ from truthound.profiler.resilience import (
19
+ ResilientCacheBackend,
20
+ FallbackChain,
21
+ CircuitBreakerConfig,
22
+ )
23
+
24
+ # Create resilient Redis with memory fallback
25
+ cache = ResilientCacheBackend(
26
+ primary=RedisCacheBackend(host="redis.example.com"),
27
+ fallback=MemoryCacheBackend(),
28
+ circuit_breaker=CircuitBreakerConfig(
29
+ failure_threshold=5,
30
+ recovery_timeout=30,
31
+ ),
32
+ )
33
+ """
34
+
35
+ from __future__ import annotations
36
+
37
+ import logging
38
+ import threading
39
+ import time
40
+ from abc import ABC, abstractmethod
41
+ from dataclasses import dataclass, field
42
+ from datetime import datetime, timedelta
43
+ from enum import Enum
44
+ from functools import wraps
45
+ from typing import Any, Callable, Generic, TypeVar
46
+
47
+ from truthound.profiler.caching import (
48
+ CacheBackend,
49
+ CacheEntry,
50
+ MemoryCacheBackend,
51
+ FileCacheBackend,
52
+ )
53
+
54
+
55
+ # Set up logging
56
+ logger = logging.getLogger("truthound.cache.resilience")
57
+
58
+
59
+ # =============================================================================
60
+ # Types
61
+ # =============================================================================
62
+
63
+ T = TypeVar("T")
64
+
65
+
66
+ class CircuitState(str, Enum):
67
+ """Circuit breaker states."""
68
+
69
+ CLOSED = "closed" # Normal operation
70
+ OPEN = "open" # Failing, reject requests
71
+ HALF_OPEN = "half_open" # Testing recovery
72
+
73
+
74
+ class BackendHealth(str, Enum):
75
+ """Backend health status."""
76
+
77
+ HEALTHY = "healthy"
78
+ DEGRADED = "degraded"
79
+ UNHEALTHY = "unhealthy"
80
+ UNKNOWN = "unknown"
81
+
82
+
83
+ class FailureType(str, Enum):
84
+ """Types of failures for classification."""
85
+
86
+ CONNECTION = "connection"
87
+ TIMEOUT = "timeout"
88
+ SERIALIZATION = "serialization"
89
+ UNKNOWN = "unknown"
90
+
91
+
92
+ # =============================================================================
93
+ # Configuration
94
+ # =============================================================================
95
+
96
+
97
+ @dataclass
98
+ class CircuitBreakerConfig:
99
+ """Configuration for circuit breaker.
100
+
101
+ Attributes:
102
+ failure_threshold: Number of failures before opening circuit
103
+ success_threshold: Number of successes to close circuit
104
+ recovery_timeout: Seconds to wait before trying half-open
105
+ failure_window: Window in seconds to count failures
106
+ excluded_exceptions: Exceptions that don't count as failures
107
+ """
108
+
109
+ failure_threshold: int = 5
110
+ success_threshold: int = 2
111
+ recovery_timeout: float = 30.0
112
+ failure_window: float = 60.0
113
+ excluded_exceptions: tuple[type[Exception], ...] = ()
114
+
115
+ @classmethod
116
+ def aggressive(cls) -> "CircuitBreakerConfig":
117
+ """Aggressive config - opens quickly, recovers slowly."""
118
+ return cls(
119
+ failure_threshold=3,
120
+ success_threshold=3,
121
+ recovery_timeout=60.0,
122
+ )
123
+
124
+ @classmethod
125
+ def lenient(cls) -> "CircuitBreakerConfig":
126
+ """Lenient config - tolerates more failures."""
127
+ return cls(
128
+ failure_threshold=10,
129
+ success_threshold=1,
130
+ recovery_timeout=15.0,
131
+ )
132
+
133
+ @classmethod
134
+ def disabled(cls) -> "CircuitBreakerConfig":
135
+ """Effectively disabled circuit breaker."""
136
+ return cls(
137
+ failure_threshold=1000000,
138
+ recovery_timeout=0.1,
139
+ )
140
+
141
+
142
+ @dataclass
143
+ class RetryConfig:
144
+ """Configuration for retry logic.
145
+
146
+ Attributes:
147
+ max_attempts: Maximum retry attempts (1 = no retry)
148
+ base_delay: Base delay in seconds
149
+ max_delay: Maximum delay cap
150
+ exponential_base: Multiplier for exponential backoff
151
+ jitter: Add random jitter to delays
152
+ retryable_exceptions: Exceptions that trigger retry
153
+ """
154
+
155
+ max_attempts: int = 3
156
+ base_delay: float = 0.1
157
+ max_delay: float = 10.0
158
+ exponential_base: float = 2.0
159
+ jitter: bool = True
160
+ retryable_exceptions: tuple[type[Exception], ...] = (
161
+ ConnectionError,
162
+ TimeoutError,
163
+ OSError,
164
+ )
165
+
166
+ def calculate_delay(self, attempt: int) -> float:
167
+ """Calculate delay for given attempt number."""
168
+ delay = self.base_delay * (self.exponential_base ** attempt)
169
+ delay = min(delay, self.max_delay)
170
+
171
+ if self.jitter:
172
+ import random
173
+ delay *= (0.5 + random.random())
174
+
175
+ return delay
176
+
177
+ @classmethod
178
+ def no_retry(cls) -> "RetryConfig":
179
+ """No retry configuration."""
180
+ return cls(max_attempts=1)
181
+
182
+ @classmethod
183
+ def quick(cls) -> "RetryConfig":
184
+ """Quick retry for transient failures."""
185
+ return cls(
186
+ max_attempts=3,
187
+ base_delay=0.05,
188
+ max_delay=1.0,
189
+ )
190
+
191
+ @classmethod
192
+ def persistent(cls) -> "RetryConfig":
193
+ """Persistent retry for important operations."""
194
+ return cls(
195
+ max_attempts=5,
196
+ base_delay=0.5,
197
+ max_delay=30.0,
198
+ )
199
+
200
+
201
+ @dataclass
202
+ class HealthCheckConfig:
203
+ """Configuration for health monitoring.
204
+
205
+ Attributes:
206
+ check_interval: Seconds between health checks
207
+ timeout: Timeout for health check operations
208
+ healthy_threshold: Consecutive successes to mark healthy
209
+ unhealthy_threshold: Consecutive failures to mark unhealthy
210
+ """
211
+
212
+ check_interval: float = 30.0
213
+ timeout: float = 5.0
214
+ healthy_threshold: int = 2
215
+ unhealthy_threshold: int = 3
216
+ enabled: bool = True
217
+
218
+
219
+ # =============================================================================
220
+ # Circuit Breaker
221
+ # =============================================================================
222
+
223
+
224
+ @dataclass
225
+ class FailureRecord:
226
+ """Record of a failure event."""
227
+
228
+ timestamp: datetime
229
+ exception_type: str
230
+ failure_type: FailureType
231
+ message: str
232
+
233
+
234
+ class CircuitBreaker:
235
+ """Circuit breaker pattern implementation.
236
+
237
+ Prevents cascade failures by tracking failure rates and
238
+ temporarily disabling operations when failure threshold is reached.
239
+
240
+ Example:
241
+ breaker = CircuitBreaker(CircuitBreakerConfig(failure_threshold=5))
242
+
243
+ @breaker.protect
244
+ def risky_operation():
245
+ return redis_client.get("key")
246
+ """
247
+
248
+ def __init__(self, config: CircuitBreakerConfig | None = None):
249
+ self.config = config or CircuitBreakerConfig()
250
+ self._state = CircuitState.CLOSED
251
+ self._failures: list[FailureRecord] = []
252
+ self._successes_in_half_open = 0
253
+ self._last_failure_time: datetime | None = None
254
+ self._opened_at: datetime | None = None
255
+ self._lock = threading.RLock()
256
+
257
+ # Statistics
258
+ self._total_calls = 0
259
+ self._total_failures = 0
260
+ self._total_rejections = 0
261
+ self._state_changes: list[tuple[datetime, CircuitState, CircuitState]] = []
262
+
263
+ @property
264
+ def state(self) -> CircuitState:
265
+ """Get current circuit state."""
266
+ with self._lock:
267
+ self._check_recovery()
268
+ return self._state
269
+
270
+ @property
271
+ def is_open(self) -> bool:
272
+ """Check if circuit is open (rejecting requests)."""
273
+ return self.state == CircuitState.OPEN
274
+
275
+ @property
276
+ def is_closed(self) -> bool:
277
+ """Check if circuit is closed (normal operation)."""
278
+ return self.state == CircuitState.CLOSED
279
+
280
+ def _check_recovery(self) -> None:
281
+ """Check if circuit should transition to half-open."""
282
+ if self._state != CircuitState.OPEN:
283
+ return
284
+
285
+ if self._opened_at is None:
286
+ return
287
+
288
+ elapsed = (datetime.now() - self._opened_at).total_seconds()
289
+ if elapsed >= self.config.recovery_timeout:
290
+ self._transition_to(CircuitState.HALF_OPEN)
291
+
292
+ def _transition_to(self, new_state: CircuitState) -> None:
293
+ """Transition to a new state."""
294
+ old_state = self._state
295
+ self._state = new_state
296
+ self._state_changes.append((datetime.now(), old_state, new_state))
297
+
298
+ logger.info(
299
+ f"Circuit breaker state change: {old_state.value} -> {new_state.value}"
300
+ )
301
+
302
+ if new_state == CircuitState.OPEN:
303
+ self._opened_at = datetime.now()
304
+ elif new_state == CircuitState.CLOSED:
305
+ self._failures.clear()
306
+ self._successes_in_half_open = 0
307
+
308
+ def _count_recent_failures(self) -> int:
309
+ """Count failures within the failure window."""
310
+ cutoff = datetime.now() - timedelta(seconds=self.config.failure_window)
311
+ return sum(1 for f in self._failures if f.timestamp > cutoff)
312
+
313
+ def _classify_failure(self, exc: Exception) -> FailureType:
314
+ """Classify the type of failure."""
315
+ if isinstance(exc, (ConnectionError, ConnectionRefusedError)):
316
+ return FailureType.CONNECTION
317
+ elif isinstance(exc, TimeoutError):
318
+ return FailureType.TIMEOUT
319
+ elif isinstance(exc, (TypeError, ValueError)):
320
+ return FailureType.SERIALIZATION
321
+ else:
322
+ return FailureType.UNKNOWN
323
+
324
+ def record_success(self) -> None:
325
+ """Record a successful operation."""
326
+ with self._lock:
327
+ self._total_calls += 1
328
+
329
+ if self._state == CircuitState.HALF_OPEN:
330
+ self._successes_in_half_open += 1
331
+ if self._successes_in_half_open >= self.config.success_threshold:
332
+ self._transition_to(CircuitState.CLOSED)
333
+
334
+ def record_failure(self, exc: Exception) -> None:
335
+ """Record a failed operation."""
336
+ with self._lock:
337
+ self._total_calls += 1
338
+ self._total_failures += 1
339
+
340
+ # Check if this exception should be excluded
341
+ if isinstance(exc, self.config.excluded_exceptions):
342
+ return
343
+
344
+ failure = FailureRecord(
345
+ timestamp=datetime.now(),
346
+ exception_type=type(exc).__name__,
347
+ failure_type=self._classify_failure(exc),
348
+ message=str(exc),
349
+ )
350
+ self._failures.append(failure)
351
+ self._last_failure_time = datetime.now()
352
+
353
+ # Check state transitions
354
+ if self._state == CircuitState.HALF_OPEN:
355
+ self._transition_to(CircuitState.OPEN)
356
+ elif self._state == CircuitState.CLOSED:
357
+ if self._count_recent_failures() >= self.config.failure_threshold:
358
+ self._transition_to(CircuitState.OPEN)
359
+
360
+ def can_execute(self) -> bool:
361
+ """Check if operation can be executed."""
362
+ with self._lock:
363
+ self._check_recovery()
364
+
365
+ if self._state == CircuitState.OPEN:
366
+ self._total_rejections += 1
367
+ return False
368
+
369
+ return True
370
+
371
+ def protect(self, func: Callable[..., T]) -> Callable[..., T]:
372
+ """Decorator to protect a function with circuit breaker.
373
+
374
+ Args:
375
+ func: Function to protect
376
+
377
+ Returns:
378
+ Wrapped function
379
+
380
+ Raises:
381
+ CircuitOpenError: If circuit is open
382
+ """
383
+ @wraps(func)
384
+ def wrapper(*args: Any, **kwargs: Any) -> T:
385
+ if not self.can_execute():
386
+ raise CircuitOpenError(
387
+ f"Circuit breaker is open. "
388
+ f"Recovery in {self.time_until_recovery:.1f}s"
389
+ )
390
+
391
+ try:
392
+ result = func(*args, **kwargs)
393
+ self.record_success()
394
+ return result
395
+ except Exception as e:
396
+ self.record_failure(e)
397
+ raise
398
+
399
+ return wrapper
400
+
401
+ @property
402
+ def time_until_recovery(self) -> float:
403
+ """Time in seconds until circuit might recover."""
404
+ if self._state != CircuitState.OPEN or self._opened_at is None:
405
+ return 0.0
406
+
407
+ elapsed = (datetime.now() - self._opened_at).total_seconds()
408
+ remaining = self.config.recovery_timeout - elapsed
409
+ return max(0.0, remaining)
410
+
411
+ def reset(self) -> None:
412
+ """Manually reset the circuit breaker."""
413
+ with self._lock:
414
+ self._transition_to(CircuitState.CLOSED)
415
+ self._failures.clear()
416
+ self._successes_in_half_open = 0
417
+
418
+ def get_stats(self) -> dict[str, Any]:
419
+ """Get circuit breaker statistics."""
420
+ with self._lock:
421
+ return {
422
+ "state": self._state.value,
423
+ "total_calls": self._total_calls,
424
+ "total_failures": self._total_failures,
425
+ "total_rejections": self._total_rejections,
426
+ "recent_failures": self._count_recent_failures(),
427
+ "failure_threshold": self.config.failure_threshold,
428
+ "time_until_recovery": self.time_until_recovery,
429
+ "last_failure": self._last_failure_time.isoformat() if self._last_failure_time else None,
430
+ }
431
+
432
+
433
+ class CircuitOpenError(Exception):
434
+ """Raised when circuit breaker is open."""
435
+
436
+ pass
437
+
438
+
439
+ # =============================================================================
440
+ # Retry Logic
441
+ # =============================================================================
442
+
443
+
444
+ class RetryPolicy:
445
+ """Retry policy with exponential backoff.
446
+
447
+ Example:
448
+ policy = RetryPolicy(RetryConfig(max_attempts=3))
449
+
450
+ @policy.retry
451
+ def flaky_operation():
452
+ return http_call()
453
+ """
454
+
455
+ def __init__(self, config: RetryConfig | None = None):
456
+ self.config = config or RetryConfig()
457
+ self._total_attempts = 0
458
+ self._total_retries = 0
459
+ self._lock = threading.Lock()
460
+
461
+ def should_retry(self, exc: Exception, attempt: int) -> bool:
462
+ """Determine if operation should be retried."""
463
+ if attempt >= self.config.max_attempts:
464
+ return False
465
+
466
+ return isinstance(exc, self.config.retryable_exceptions)
467
+
468
+ def execute_with_retry(
469
+ self,
470
+ func: Callable[..., T],
471
+ *args: Any,
472
+ **kwargs: Any,
473
+ ) -> T:
474
+ """Execute function with retry logic.
475
+
476
+ Args:
477
+ func: Function to execute
478
+ *args: Positional arguments
479
+ **kwargs: Keyword arguments
480
+
481
+ Returns:
482
+ Function result
483
+
484
+ Raises:
485
+ Last exception if all retries fail
486
+ """
487
+ last_exception: Exception | None = None
488
+
489
+ for attempt in range(self.config.max_attempts):
490
+ with self._lock:
491
+ self._total_attempts += 1
492
+ if attempt > 0:
493
+ self._total_retries += 1
494
+
495
+ try:
496
+ return func(*args, **kwargs)
497
+
498
+ except Exception as e:
499
+ last_exception = e
500
+
501
+ if not self.should_retry(e, attempt + 1):
502
+ logger.debug(
503
+ f"Not retrying {func.__name__}: {type(e).__name__} "
504
+ f"is not retryable or max attempts reached"
505
+ )
506
+ raise
507
+
508
+ delay = self.config.calculate_delay(attempt)
509
+ logger.warning(
510
+ f"Retry {attempt + 1}/{self.config.max_attempts} for "
511
+ f"{func.__name__} after {delay:.2f}s: {e}"
512
+ )
513
+ time.sleep(delay)
514
+
515
+ if last_exception:
516
+ raise last_exception
517
+
518
+ raise RuntimeError("Unexpected state in retry logic")
519
+
520
+ def retry(self, func: Callable[..., T]) -> Callable[..., T]:
521
+ """Decorator to add retry logic to a function."""
522
+ @wraps(func)
523
+ def wrapper(*args: Any, **kwargs: Any) -> T:
524
+ return self.execute_with_retry(func, *args, **kwargs)
525
+
526
+ return wrapper
527
+
528
+ def get_stats(self) -> dict[str, Any]:
529
+ """Get retry statistics."""
530
+ with self._lock:
531
+ return {
532
+ "total_attempts": self._total_attempts,
533
+ "total_retries": self._total_retries,
534
+ "retry_rate": (
535
+ self._total_retries / self._total_attempts
536
+ if self._total_attempts > 0 else 0.0
537
+ ),
538
+ }
539
+
540
+
541
+ # =============================================================================
542
+ # Health Monitor
543
+ # =============================================================================
544
+
545
+
546
+ class HealthMonitor:
547
+ """Monitors backend health with periodic checks.
548
+
549
+ Example:
550
+ monitor = HealthMonitor(backend, HealthCheckConfig())
551
+ monitor.start()
552
+
553
+ if monitor.is_healthy:
554
+ backend.get(key)
555
+
556
+ monitor.stop()
557
+ """
558
+
559
+ def __init__(
560
+ self,
561
+ backend: CacheBackend,
562
+ config: HealthCheckConfig | None = None,
563
+ name: str = "cache",
564
+ ):
565
+ self.backend = backend
566
+ self.config = config or HealthCheckConfig()
567
+ self.name = name
568
+
569
+ self._health = BackendHealth.UNKNOWN
570
+ self._consecutive_successes = 0
571
+ self._consecutive_failures = 0
572
+ self._last_check: datetime | None = None
573
+ self._last_check_duration: float = 0.0
574
+ self._running = False
575
+ self._thread: threading.Thread | None = None
576
+ self._lock = threading.RLock()
577
+
578
+ # Statistics
579
+ self._total_checks = 0
580
+ self._total_failures = 0
581
+
582
+ @property
583
+ def health(self) -> BackendHealth:
584
+ """Get current health status."""
585
+ with self._lock:
586
+ return self._health
587
+
588
+ @property
589
+ def is_healthy(self) -> bool:
590
+ """Check if backend is healthy."""
591
+ return self.health == BackendHealth.HEALTHY
592
+
593
+ def check_health(self) -> bool:
594
+ """Perform a health check.
595
+
596
+ Returns:
597
+ True if healthy
598
+ """
599
+ start = time.perf_counter()
600
+
601
+ try:
602
+ # Try a simple operation
603
+ test_key = f"__health_check_{self.name}__"
604
+ self.backend.exists(test_key)
605
+
606
+ with self._lock:
607
+ self._total_checks += 1
608
+ self._consecutive_successes += 1
609
+ self._consecutive_failures = 0
610
+ self._last_check = datetime.now()
611
+ self._last_check_duration = time.perf_counter() - start
612
+
613
+ if self._consecutive_successes >= self.config.healthy_threshold:
614
+ self._health = BackendHealth.HEALTHY
615
+ elif self._health == BackendHealth.UNHEALTHY:
616
+ self._health = BackendHealth.DEGRADED
617
+
618
+ logger.debug(
619
+ f"Health check passed for {self.name}: "
620
+ f"{self._last_check_duration*1000:.1f}ms"
621
+ )
622
+ return True
623
+
624
+ except Exception as e:
625
+ with self._lock:
626
+ self._total_checks += 1
627
+ self._total_failures += 1
628
+ self._consecutive_failures += 1
629
+ self._consecutive_successes = 0
630
+ self._last_check = datetime.now()
631
+ self._last_check_duration = time.perf_counter() - start
632
+
633
+ if self._consecutive_failures >= self.config.unhealthy_threshold:
634
+ self._health = BackendHealth.UNHEALTHY
635
+ else:
636
+ self._health = BackendHealth.DEGRADED
637
+
638
+ logger.warning(f"Health check failed for {self.name}: {e}")
639
+ return False
640
+
641
+ def _monitor_loop(self) -> None:
642
+ """Background monitoring loop."""
643
+ while self._running:
644
+ try:
645
+ self.check_health()
646
+ except Exception as e:
647
+ logger.error(f"Health monitor error: {e}")
648
+
649
+ time.sleep(self.config.check_interval)
650
+
651
+ def start(self) -> None:
652
+ """Start background health monitoring."""
653
+ if not self.config.enabled:
654
+ return
655
+
656
+ if self._running:
657
+ return
658
+
659
+ self._running = True
660
+ self._thread = threading.Thread(
661
+ target=self._monitor_loop,
662
+ daemon=True,
663
+ name=f"health-monitor-{self.name}",
664
+ )
665
+ self._thread.start()
666
+ logger.info(f"Started health monitor for {self.name}")
667
+
668
+ def stop(self) -> None:
669
+ """Stop background health monitoring."""
670
+ self._running = False
671
+ if self._thread:
672
+ self._thread.join(timeout=5.0)
673
+ self._thread = None
674
+ logger.info(f"Stopped health monitor for {self.name}")
675
+
676
+ def get_stats(self) -> dict[str, Any]:
677
+ """Get health monitor statistics."""
678
+ with self._lock:
679
+ return {
680
+ "name": self.name,
681
+ "health": self._health.value,
682
+ "is_healthy": self.is_healthy,
683
+ "consecutive_successes": self._consecutive_successes,
684
+ "consecutive_failures": self._consecutive_failures,
685
+ "total_checks": self._total_checks,
686
+ "total_failures": self._total_failures,
687
+ "last_check": self._last_check.isoformat() if self._last_check else None,
688
+ "last_check_duration_ms": self._last_check_duration * 1000,
689
+ "running": self._running,
690
+ }
691
+
692
+
693
+ # =============================================================================
694
+ # Resilient Cache Backend
695
+ # =============================================================================
696
+
697
+
698
+ @dataclass
699
+ class ResilienceConfig:
700
+ """Configuration for resilient cache backend."""
701
+
702
+ circuit_breaker: CircuitBreakerConfig = field(
703
+ default_factory=CircuitBreakerConfig
704
+ )
705
+ retry: RetryConfig = field(default_factory=RetryConfig)
706
+ health_check: HealthCheckConfig = field(default_factory=HealthCheckConfig)
707
+ fallback_on_error: bool = True
708
+ log_failures: bool = True
709
+
710
+ @classmethod
711
+ def default(cls) -> "ResilienceConfig":
712
+ """Default resilience configuration."""
713
+ return cls()
714
+
715
+ @classmethod
716
+ def high_availability(cls) -> "ResilienceConfig":
717
+ """High availability configuration."""
718
+ return cls(
719
+ circuit_breaker=CircuitBreakerConfig.lenient(),
720
+ retry=RetryConfig.persistent(),
721
+ health_check=HealthCheckConfig(
722
+ check_interval=10.0,
723
+ healthy_threshold=1,
724
+ ),
725
+ )
726
+
727
+ @classmethod
728
+ def low_latency(cls) -> "ResilienceConfig":
729
+ """Low latency configuration - fail fast."""
730
+ return cls(
731
+ circuit_breaker=CircuitBreakerConfig.aggressive(),
732
+ retry=RetryConfig.no_retry(),
733
+ health_check=HealthCheckConfig(check_interval=60.0),
734
+ )
735
+
736
+
737
+ class ResilientCacheBackend(CacheBackend):
738
+ """Cache backend wrapper with resilience patterns.
739
+
740
+ Wraps a primary backend with circuit breaker, retry logic,
741
+ and optional fallback to a secondary backend.
742
+
743
+ Example:
744
+ primary = RedisCacheBackend(host="redis.example.com")
745
+ fallback = MemoryCacheBackend()
746
+
747
+ cache = ResilientCacheBackend(
748
+ primary=primary,
749
+ fallback=fallback,
750
+ config=ResilienceConfig.high_availability(),
751
+ )
752
+
753
+ # Automatically falls back to memory on Redis failure
754
+ entry = cache.get("my-key")
755
+ """
756
+
757
+ def __init__(
758
+ self,
759
+ primary: CacheBackend,
760
+ fallback: CacheBackend | None = None,
761
+ config: ResilienceConfig | None = None,
762
+ name: str = "resilient-cache",
763
+ ):
764
+ self.primary = primary
765
+ self.fallback = fallback or MemoryCacheBackend()
766
+ self.config = config or ResilienceConfig.default()
767
+ self.name = name
768
+
769
+ # Initialize components
770
+ self._circuit_breaker = CircuitBreaker(self.config.circuit_breaker)
771
+ self._retry_policy = RetryPolicy(self.config.retry)
772
+ self._health_monitor = HealthMonitor(
773
+ primary,
774
+ self.config.health_check,
775
+ name=f"{name}-primary",
776
+ )
777
+
778
+ # Statistics
779
+ self._primary_calls = 0
780
+ self._fallback_calls = 0
781
+ self._lock = threading.Lock()
782
+
783
+ # Start health monitoring
784
+ self._health_monitor.start()
785
+
786
+ def _execute_with_resilience(
787
+ self,
788
+ primary_fn: Callable[[], T],
789
+ fallback_fn: Callable[[], T] | None = None,
790
+ operation_name: str = "operation",
791
+ ) -> T:
792
+ """Execute an operation with full resilience patterns.
793
+
794
+ Args:
795
+ primary_fn: Primary operation
796
+ fallback_fn: Fallback operation
797
+ operation_name: Name for logging
798
+
799
+ Returns:
800
+ Operation result
801
+ """
802
+ # Check circuit breaker
803
+ if not self._circuit_breaker.can_execute():
804
+ if fallback_fn and self.config.fallback_on_error:
805
+ logger.debug(f"Circuit open, using fallback for {operation_name}")
806
+ with self._lock:
807
+ self._fallback_calls += 1
808
+ return fallback_fn()
809
+ raise CircuitOpenError(f"Circuit is open for {operation_name}")
810
+
811
+ # Try primary with retry
812
+ try:
813
+ result = self._retry_policy.execute_with_retry(primary_fn)
814
+ self._circuit_breaker.record_success()
815
+ with self._lock:
816
+ self._primary_calls += 1
817
+ return result
818
+
819
+ except Exception as e:
820
+ self._circuit_breaker.record_failure(e)
821
+
822
+ if self.config.log_failures:
823
+ logger.warning(
824
+ f"Primary cache failed for {operation_name}: {e}"
825
+ )
826
+
827
+ # Try fallback
828
+ if fallback_fn and self.config.fallback_on_error:
829
+ logger.info(f"Using fallback for {operation_name}")
830
+ with self._lock:
831
+ self._fallback_calls += 1
832
+ return fallback_fn()
833
+
834
+ raise
835
+
836
+ def get(self, key: str) -> CacheEntry | None:
837
+ """Get from cache with resilience."""
838
+ return self._execute_with_resilience(
839
+ primary_fn=lambda: self.primary.get(key),
840
+ fallback_fn=lambda: self.fallback.get(key),
841
+ operation_name=f"get:{key[:20]}",
842
+ )
843
+
844
+ def set(
845
+ self,
846
+ key: str,
847
+ entry: CacheEntry,
848
+ ttl: timedelta | None = None,
849
+ ) -> None:
850
+ """Set in cache with resilience."""
851
+ def primary_set() -> None:
852
+ self.primary.set(key, entry, ttl)
853
+
854
+ def fallback_set() -> None:
855
+ self.fallback.set(key, entry, ttl)
856
+
857
+ self._execute_with_resilience(
858
+ primary_fn=primary_set,
859
+ fallback_fn=fallback_set,
860
+ operation_name=f"set:{key[:20]}",
861
+ )
862
+
863
+ def delete(self, key: str) -> bool:
864
+ """Delete from cache with resilience."""
865
+ try:
866
+ result = self._execute_with_resilience(
867
+ primary_fn=lambda: self.primary.delete(key),
868
+ fallback_fn=lambda: self.fallback.delete(key),
869
+ operation_name=f"delete:{key[:20]}",
870
+ )
871
+ return result
872
+ except Exception:
873
+ return False
874
+
875
+ def clear(self) -> int:
876
+ """Clear cache."""
877
+ count = 0
878
+ try:
879
+ count += self.primary.clear()
880
+ except Exception as e:
881
+ logger.warning(f"Failed to clear primary cache: {e}")
882
+
883
+ try:
884
+ count += self.fallback.clear()
885
+ except Exception as e:
886
+ logger.warning(f"Failed to clear fallback cache: {e}")
887
+
888
+ return count
889
+
890
+ def exists(self, key: str) -> bool:
891
+ """Check if key exists."""
892
+ return self._execute_with_resilience(
893
+ primary_fn=lambda: self.primary.exists(key),
894
+ fallback_fn=lambda: self.fallback.exists(key),
895
+ operation_name=f"exists:{key[:20]}",
896
+ )
897
+
898
+ def get_stats(self) -> dict[str, Any]:
899
+ """Get comprehensive statistics."""
900
+ with self._lock:
901
+ total_calls = self._primary_calls + self._fallback_calls
902
+ return {
903
+ "type": "resilient",
904
+ "name": self.name,
905
+ "primary_calls": self._primary_calls,
906
+ "fallback_calls": self._fallback_calls,
907
+ "fallback_rate": (
908
+ self._fallback_calls / total_calls
909
+ if total_calls > 0 else 0.0
910
+ ),
911
+ "circuit_breaker": self._circuit_breaker.get_stats(),
912
+ "retry": self._retry_policy.get_stats(),
913
+ "health": self._health_monitor.get_stats(),
914
+ "primary": self.primary.get_stats(),
915
+ "fallback": self.fallback.get_stats(),
916
+ }
917
+
918
+ def is_primary_healthy(self) -> bool:
919
+ """Check if primary backend is healthy."""
920
+ return self._health_monitor.is_healthy
921
+
922
+ def force_primary_check(self) -> bool:
923
+ """Force an immediate health check on primary."""
924
+ return self._health_monitor.check_health()
925
+
926
+ def reset_circuit(self) -> None:
927
+ """Manually reset the circuit breaker."""
928
+ self._circuit_breaker.reset()
929
+ logger.info(f"Circuit breaker reset for {self.name}")
930
+
931
+ def shutdown(self) -> None:
932
+ """Shutdown the resilient backend."""
933
+ self._health_monitor.stop()
934
+
935
+
936
+ # =============================================================================
937
+ # Fallback Chain
938
+ # =============================================================================
939
+
940
+
941
+ class FallbackChain(CacheBackend):
942
+ """Chain of cache backends with automatic fallback.
943
+
944
+ Tries backends in order until one succeeds.
945
+
946
+ Example:
947
+ chain = FallbackChain([
948
+ RedisCacheBackend(host="primary-redis"),
949
+ RedisCacheBackend(host="secondary-redis"),
950
+ FileCacheBackend(cache_dir=".cache"),
951
+ MemoryCacheBackend(),
952
+ ])
953
+
954
+ # Will try each backend in order until success
955
+ entry = chain.get("key")
956
+ """
957
+
958
+ def __init__(
959
+ self,
960
+ backends: list[CacheBackend],
961
+ retry_config: RetryConfig | None = None,
962
+ ):
963
+ if not backends:
964
+ raise ValueError("At least one backend is required")
965
+
966
+ self.backends = backends
967
+ self._retry_policy = RetryPolicy(retry_config or RetryConfig.quick())
968
+ self._lock = threading.Lock()
969
+
970
+ # Track which backends are working
971
+ self._backend_health: dict[int, bool] = {
972
+ i: True for i in range(len(backends))
973
+ }
974
+ self._calls_per_backend: dict[int, int] = {
975
+ i: 0 for i in range(len(backends))
976
+ }
977
+
978
+ def _try_backends(
979
+ self,
980
+ operation: Callable[[CacheBackend], T],
981
+ operation_name: str = "operation",
982
+ ) -> T:
983
+ """Try operation on backends in order."""
984
+ last_exception: Exception | None = None
985
+
986
+ for i, backend in enumerate(self.backends):
987
+ # Skip unhealthy backends (but try last one regardless)
988
+ if not self._backend_health[i] and i < len(self.backends) - 1:
989
+ continue
990
+
991
+ try:
992
+ result = operation(backend)
993
+
994
+ with self._lock:
995
+ self._calls_per_backend[i] += 1
996
+ self._backend_health[i] = True
997
+
998
+ if i > 0:
999
+ logger.debug(
1000
+ f"Fallback to backend {i} succeeded for {operation_name}"
1001
+ )
1002
+
1003
+ return result
1004
+
1005
+ except Exception as e:
1006
+ last_exception = e
1007
+ with self._lock:
1008
+ self._backend_health[i] = False
1009
+
1010
+ logger.warning(
1011
+ f"Backend {i} failed for {operation_name}: {e}"
1012
+ )
1013
+
1014
+ if last_exception:
1015
+ raise last_exception
1016
+
1017
+ raise RuntimeError("No backends available")
1018
+
1019
+ def get(self, key: str) -> CacheEntry | None:
1020
+ try:
1021
+ return self._try_backends(
1022
+ lambda b: b.get(key),
1023
+ f"get:{key[:20]}",
1024
+ )
1025
+ except Exception:
1026
+ return None
1027
+
1028
+ def set(
1029
+ self,
1030
+ key: str,
1031
+ entry: CacheEntry,
1032
+ ttl: timedelta | None = None,
1033
+ ) -> None:
1034
+ self._try_backends(
1035
+ lambda b: b.set(key, entry, ttl),
1036
+ f"set:{key[:20]}",
1037
+ )
1038
+
1039
+ def delete(self, key: str) -> bool:
1040
+ try:
1041
+ return self._try_backends(
1042
+ lambda b: b.delete(key),
1043
+ f"delete:{key[:20]}",
1044
+ )
1045
+ except Exception:
1046
+ return False
1047
+
1048
+ def clear(self) -> int:
1049
+ total = 0
1050
+ for backend in self.backends:
1051
+ try:
1052
+ total += backend.clear()
1053
+ except Exception as e:
1054
+ logger.warning(f"Failed to clear backend: {e}")
1055
+ return total
1056
+
1057
+ def exists(self, key: str) -> bool:
1058
+ try:
1059
+ return self._try_backends(
1060
+ lambda b: b.exists(key),
1061
+ f"exists:{key[:20]}",
1062
+ )
1063
+ except Exception:
1064
+ return False
1065
+
1066
+ def get_stats(self) -> dict[str, Any]:
1067
+ with self._lock:
1068
+ return {
1069
+ "type": "fallback_chain",
1070
+ "backend_count": len(self.backends),
1071
+ "backend_health": dict(self._backend_health),
1072
+ "calls_per_backend": dict(self._calls_per_backend),
1073
+ "backends": [b.get_stats() for b in self.backends],
1074
+ }
1075
+
1076
+
1077
+ # =============================================================================
1078
+ # Factory Functions
1079
+ # =============================================================================
1080
+
1081
+
1082
+ def create_resilient_redis_backend(
1083
+ host: str = "localhost",
1084
+ port: int = 6379,
1085
+ db: int = 0,
1086
+ password: str | None = None,
1087
+ fallback_to_memory: bool = True,
1088
+ fallback_to_file: bool = False,
1089
+ file_cache_dir: str = ".truthound_cache",
1090
+ config: ResilienceConfig | None = None,
1091
+ **redis_kwargs: Any,
1092
+ ) -> CacheBackend:
1093
+ """Create a resilient Redis backend with fallback.
1094
+
1095
+ This is the recommended way to create a Redis-backed cache
1096
+ with automatic fallback.
1097
+
1098
+ Args:
1099
+ host: Redis host
1100
+ port: Redis port
1101
+ db: Redis database
1102
+ password: Redis password
1103
+ fallback_to_memory: Use memory as fallback
1104
+ fallback_to_file: Use file as fallback
1105
+ file_cache_dir: Directory for file cache
1106
+ config: Resilience configuration
1107
+ **redis_kwargs: Additional Redis options
1108
+
1109
+ Returns:
1110
+ Configured resilient cache backend
1111
+ """
1112
+ try:
1113
+ from truthound.profiler.caching import RedisCacheBackend
1114
+ primary = RedisCacheBackend(
1115
+ host=host,
1116
+ port=port,
1117
+ db=db,
1118
+ password=password,
1119
+ **redis_kwargs,
1120
+ )
1121
+ except ImportError:
1122
+ logger.warning(
1123
+ "Redis package not installed. Using memory cache."
1124
+ )
1125
+ return MemoryCacheBackend()
1126
+
1127
+ # Create fallback chain
1128
+ fallbacks: list[CacheBackend] = []
1129
+ if fallback_to_file:
1130
+ fallbacks.append(FileCacheBackend(cache_dir=file_cache_dir))
1131
+ if fallback_to_memory:
1132
+ fallbacks.append(MemoryCacheBackend())
1133
+
1134
+ if not fallbacks:
1135
+ fallbacks.append(MemoryCacheBackend())
1136
+
1137
+ # Use first fallback as the direct fallback
1138
+ fallback = fallbacks[0] if len(fallbacks) == 1 else FallbackChain(fallbacks)
1139
+
1140
+ return ResilientCacheBackend(
1141
+ primary=primary,
1142
+ fallback=fallback,
1143
+ config=config or ResilienceConfig.default(),
1144
+ name="resilient-redis",
1145
+ )
1146
+
1147
+
1148
+ def create_high_availability_cache(
1149
+ primary_host: str,
1150
+ secondary_host: str | None = None,
1151
+ port: int = 6379,
1152
+ **kwargs: Any,
1153
+ ) -> CacheBackend:
1154
+ """Create a high-availability cache with multiple Redis instances.
1155
+
1156
+ Args:
1157
+ primary_host: Primary Redis host
1158
+ secondary_host: Secondary Redis host (optional)
1159
+ port: Redis port
1160
+ **kwargs: Additional options
1161
+
1162
+ Returns:
1163
+ High-availability cache backend
1164
+ """
1165
+ backends: list[CacheBackend] = []
1166
+
1167
+ try:
1168
+ from truthound.profiler.caching import RedisCacheBackend
1169
+
1170
+ backends.append(RedisCacheBackend(host=primary_host, port=port))
1171
+
1172
+ if secondary_host:
1173
+ backends.append(RedisCacheBackend(host=secondary_host, port=port))
1174
+ except ImportError:
1175
+ logger.warning("Redis package not installed.")
1176
+
1177
+ backends.append(FileCacheBackend())
1178
+ backends.append(MemoryCacheBackend())
1179
+
1180
+ return FallbackChain(backends)