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,1365 @@
1
+ """Enterprise-grade connection pool management for database stores.
2
+
3
+ This module provides a highly extensible and maintainable connection pool
4
+ abstraction layer with:
5
+ - Multiple pooling strategies (QueuePool, NullPool, StaticPool, AsyncAdaptedQueuePool)
6
+ - Connection health monitoring and automatic recovery
7
+ - Circuit breaker pattern for fault tolerance
8
+ - Comprehensive metrics and observability
9
+ - Database-specific optimizations
10
+ - Retry logic with exponential backoff
11
+
12
+ Install with: pip install truthound[database]
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import logging
18
+ import threading
19
+ import time
20
+ import weakref
21
+ from abc import ABC, abstractmethod
22
+ from collections import deque
23
+ from contextlib import contextmanager
24
+ from dataclasses import dataclass, field
25
+ from datetime import datetime, timedelta, timezone
26
+ from enum import Enum, auto
27
+ from typing import (
28
+ TYPE_CHECKING,
29
+ Any,
30
+ Callable,
31
+ Generic,
32
+ Iterator,
33
+ Protocol,
34
+ TypeVar,
35
+ runtime_checkable,
36
+ )
37
+
38
+ logger = logging.getLogger(__name__)
39
+
40
+ # Lazy import SQLAlchemy
41
+ try:
42
+ from sqlalchemy import create_engine, event, text
43
+ from sqlalchemy.engine import Engine
44
+ from sqlalchemy.exc import (
45
+ DBAPIError,
46
+ DisconnectionError,
47
+ InterfaceError,
48
+ OperationalError,
49
+ SQLAlchemyError,
50
+ )
51
+ from sqlalchemy.orm import Session, sessionmaker
52
+ from sqlalchemy.pool import (
53
+ NullPool,
54
+ QueuePool,
55
+ SingletonThreadPool,
56
+ StaticPool,
57
+ )
58
+
59
+ HAS_SQLALCHEMY = True
60
+ except ImportError:
61
+ HAS_SQLALCHEMY = False
62
+ SQLAlchemyError = Exception # type: ignore
63
+ DBAPIError = Exception # type: ignore
64
+ OperationalError = Exception # type: ignore
65
+ DisconnectionError = Exception # type: ignore
66
+ InterfaceError = Exception # type: ignore
67
+
68
+ if TYPE_CHECKING:
69
+ from sqlalchemy.engine import Engine
70
+ from sqlalchemy.orm import Session
71
+ from sqlalchemy.pool import Pool
72
+
73
+
74
+ # =============================================================================
75
+ # Enums and Constants
76
+ # =============================================================================
77
+
78
+
79
+ class PoolStrategy(Enum):
80
+ """Connection pool strategy types."""
81
+
82
+ QUEUE_POOL = auto() # Standard pool with overflow
83
+ NULL_POOL = auto() # No pooling, new connection each time
84
+ STATIC_POOL = auto() # Single connection for all requests
85
+ SINGLETON_THREAD = auto() # One connection per thread
86
+ ASYNC_QUEUE = auto() # Async-compatible queue pool
87
+
88
+
89
+ class DatabaseDialect(Enum):
90
+ """Supported database dialects with specific optimizations."""
91
+
92
+ POSTGRESQL = "postgresql"
93
+ MYSQL = "mysql"
94
+ SQLITE = "sqlite"
95
+ MSSQL = "mssql"
96
+ ORACLE = "oracle"
97
+ UNKNOWN = "unknown"
98
+
99
+ @classmethod
100
+ def from_url(cls, url: str) -> "DatabaseDialect":
101
+ """Detect dialect from connection URL."""
102
+ url_lower = url.lower()
103
+ if url_lower.startswith("postgresql") or url_lower.startswith("postgres"):
104
+ return cls.POSTGRESQL
105
+ elif url_lower.startswith("mysql"):
106
+ return cls.MYSQL
107
+ elif url_lower.startswith("sqlite"):
108
+ return cls.SQLITE
109
+ elif url_lower.startswith("mssql") or "pyodbc" in url_lower:
110
+ return cls.MSSQL
111
+ elif url_lower.startswith("oracle"):
112
+ return cls.ORACLE
113
+ return cls.UNKNOWN
114
+
115
+
116
+ class ConnectionState(Enum):
117
+ """State of a pooled connection."""
118
+
119
+ IDLE = auto()
120
+ IN_USE = auto()
121
+ STALE = auto()
122
+ BROKEN = auto()
123
+ RECYCLING = auto()
124
+
125
+
126
+ class CircuitState(Enum):
127
+ """Circuit breaker states."""
128
+
129
+ CLOSED = auto() # Normal operation
130
+ OPEN = auto() # Failing, reject requests
131
+ HALF_OPEN = auto() # Testing if recovered
132
+
133
+
134
+ # =============================================================================
135
+ # Configuration Dataclasses
136
+ # =============================================================================
137
+
138
+
139
+ @dataclass
140
+ class PoolConfig:
141
+ """Connection pool configuration.
142
+
143
+ Attributes:
144
+ strategy: Pooling strategy to use.
145
+ pool_size: Number of connections to maintain in pool.
146
+ max_overflow: Maximum overflow connections beyond pool_size.
147
+ pool_timeout: Seconds to wait for available connection.
148
+ pool_recycle: Seconds before a connection is recycled (-1 = never).
149
+ pool_pre_ping: Whether to test connections before use.
150
+ echo_pool: Log pool checkouts/checkins for debugging.
151
+ reset_on_return: How to reset connections when returned.
152
+ """
153
+
154
+ strategy: PoolStrategy = PoolStrategy.QUEUE_POOL
155
+ pool_size: int = 5
156
+ max_overflow: int = 10
157
+ pool_timeout: float = 30.0
158
+ pool_recycle: int = 3600 # 1 hour
159
+ pool_pre_ping: bool = True
160
+ echo_pool: bool = False
161
+ reset_on_return: str = "rollback" # "rollback", "commit", or None
162
+
163
+
164
+ def _get_default_retryable_errors() -> tuple[type[Exception], ...]:
165
+ """Get default retryable error types based on available imports."""
166
+ if HAS_SQLALCHEMY:
167
+ return (OperationalError, DisconnectionError, InterfaceError)
168
+ # When SQLAlchemy is not available, use a placeholder that won't match anything
169
+ # Users should provide their own retryable_errors in this case
170
+ return ()
171
+
172
+
173
+ @dataclass
174
+ class RetryConfig:
175
+ """Retry behavior configuration.
176
+
177
+ Attributes:
178
+ max_retries: Maximum number of retry attempts.
179
+ base_delay: Base delay between retries in seconds.
180
+ max_delay: Maximum delay between retries in seconds.
181
+ exponential_base: Base for exponential backoff calculation.
182
+ jitter: Whether to add random jitter to delays.
183
+ retryable_errors: Error types that should trigger retry.
184
+ """
185
+
186
+ max_retries: int = 3
187
+ base_delay: float = 0.1
188
+ max_delay: float = 30.0
189
+ exponential_base: float = 2.0
190
+ jitter: bool = True
191
+ retryable_errors: tuple[type[Exception], ...] = field(
192
+ default_factory=_get_default_retryable_errors
193
+ )
194
+
195
+
196
+ @dataclass
197
+ class CircuitBreakerConfig:
198
+ """Circuit breaker configuration.
199
+
200
+ Attributes:
201
+ failure_threshold: Failures before opening circuit.
202
+ success_threshold: Successes before closing circuit.
203
+ timeout: Seconds before attempting recovery.
204
+ half_open_max_calls: Max calls in half-open state.
205
+ """
206
+
207
+ failure_threshold: int = 5
208
+ success_threshold: int = 3
209
+ timeout: float = 60.0
210
+ half_open_max_calls: int = 3
211
+
212
+
213
+ @dataclass
214
+ class HealthCheckConfig:
215
+ """Health check configuration.
216
+
217
+ Attributes:
218
+ enabled: Whether health checks are enabled.
219
+ interval: Seconds between health checks.
220
+ timeout: Seconds before health check times out.
221
+ query: SQL query to use for health check.
222
+ """
223
+
224
+ enabled: bool = True
225
+ interval: float = 30.0
226
+ timeout: float = 5.0
227
+ query: str = "SELECT 1"
228
+
229
+
230
+ @dataclass
231
+ class ConnectionPoolConfig:
232
+ """Complete connection pool manager configuration.
233
+
234
+ Combines all sub-configurations into a single config object.
235
+ """
236
+
237
+ connection_url: str = "sqlite:///:memory:"
238
+ pool: PoolConfig = field(default_factory=PoolConfig)
239
+ retry: RetryConfig = field(default_factory=RetryConfig)
240
+ circuit_breaker: CircuitBreakerConfig = field(default_factory=CircuitBreakerConfig)
241
+ health_check: HealthCheckConfig = field(default_factory=HealthCheckConfig)
242
+ echo: bool = False
243
+ connect_args: dict[str, Any] = field(default_factory=dict)
244
+ engine_options: dict[str, Any] = field(default_factory=dict)
245
+
246
+ @property
247
+ def dialect(self) -> DatabaseDialect:
248
+ """Get detected database dialect."""
249
+ return DatabaseDialect.from_url(self.connection_url)
250
+
251
+ def with_dialect_defaults(self) -> "ConnectionPoolConfig":
252
+ """Apply dialect-specific default settings."""
253
+ dialect = self.dialect
254
+ config = ConnectionPoolConfig(
255
+ connection_url=self.connection_url,
256
+ pool=PoolConfig(
257
+ strategy=self.pool.strategy,
258
+ pool_size=self.pool.pool_size,
259
+ max_overflow=self.pool.max_overflow,
260
+ pool_timeout=self.pool.pool_timeout,
261
+ pool_recycle=self.pool.pool_recycle,
262
+ pool_pre_ping=self.pool.pool_pre_ping,
263
+ echo_pool=self.pool.echo_pool,
264
+ reset_on_return=self.pool.reset_on_return,
265
+ ),
266
+ retry=self.retry,
267
+ circuit_breaker=self.circuit_breaker,
268
+ health_check=self.health_check,
269
+ echo=self.echo,
270
+ connect_args=dict(self.connect_args),
271
+ engine_options=dict(self.engine_options),
272
+ )
273
+
274
+ # Apply dialect-specific defaults
275
+ if dialect == DatabaseDialect.SQLITE:
276
+ config.connect_args.setdefault("check_same_thread", False)
277
+ # SQLite doesn't support connection pooling well
278
+ if config.pool.strategy == PoolStrategy.QUEUE_POOL:
279
+ config.pool.pool_size = 1
280
+ config.pool.max_overflow = 0
281
+
282
+ elif dialect == DatabaseDialect.POSTGRESQL:
283
+ # PostgreSQL-specific optimizations
284
+ config.engine_options.setdefault("pool_use_lifo", True)
285
+ config.pool.pool_recycle = min(config.pool.pool_recycle, 1800)
286
+
287
+ elif dialect == DatabaseDialect.MYSQL:
288
+ # MySQL typically needs more aggressive recycling
289
+ config.pool.pool_recycle = min(config.pool.pool_recycle, 3600)
290
+ config.connect_args.setdefault("connect_timeout", 10)
291
+
292
+ return config
293
+
294
+
295
+ # =============================================================================
296
+ # Protocols
297
+ # =============================================================================
298
+
299
+
300
+ @runtime_checkable
301
+ class PoolMetricsProtocol(Protocol):
302
+ """Protocol for pool metrics providers."""
303
+
304
+ @property
305
+ def total_connections(self) -> int:
306
+ """Total connections in pool."""
307
+ ...
308
+
309
+ @property
310
+ def active_connections(self) -> int:
311
+ """Currently checked out connections."""
312
+ ...
313
+
314
+ @property
315
+ def idle_connections(self) -> int:
316
+ """Available connections in pool."""
317
+ ...
318
+
319
+ @property
320
+ def overflow_connections(self) -> int:
321
+ """Connections beyond pool_size."""
322
+ ...
323
+
324
+
325
+ @runtime_checkable
326
+ class ConnectionPoolProtocol(Protocol):
327
+ """Protocol for connection pool implementations."""
328
+
329
+ def get_connection(self) -> Any:
330
+ """Get a connection from the pool."""
331
+ ...
332
+
333
+ def return_connection(self, connection: Any) -> None:
334
+ """Return a connection to the pool."""
335
+ ...
336
+
337
+ def dispose(self) -> None:
338
+ """Dispose of all connections."""
339
+ ...
340
+
341
+ @property
342
+ def metrics(self) -> PoolMetricsProtocol:
343
+ """Get pool metrics."""
344
+ ...
345
+
346
+
347
+ # =============================================================================
348
+ # Metrics
349
+ # =============================================================================
350
+
351
+
352
+ @dataclass
353
+ class PoolMetrics:
354
+ """Connection pool metrics for monitoring and observability.
355
+
356
+ Thread-safe metrics collection with atomic operations.
357
+ """
358
+
359
+ _lock: threading.Lock = field(default_factory=threading.Lock, repr=False)
360
+
361
+ # Connection counts
362
+ total_created: int = 0
363
+ total_closed: int = 0
364
+ current_size: int = 0
365
+ current_checked_out: int = 0
366
+ current_overflow: int = 0
367
+ peak_connections: int = 0
368
+
369
+ # Operation counts
370
+ checkouts: int = 0
371
+ checkins: int = 0
372
+ checkout_failures: int = 0
373
+ recycles: int = 0
374
+ invalidations: int = 0
375
+
376
+ # Timing metrics
377
+ total_checkout_time_ms: float = 0.0
378
+ max_checkout_time_ms: float = 0.0
379
+ total_connection_time_ms: float = 0.0
380
+
381
+ # Health check metrics
382
+ health_checks_passed: int = 0
383
+ health_checks_failed: int = 0
384
+ last_health_check: datetime | None = None
385
+
386
+ # Circuit breaker metrics
387
+ circuit_opens: int = 0
388
+ circuit_closes: int = 0
389
+ requests_rejected: int = 0
390
+
391
+ @property
392
+ def total_connections(self) -> int:
393
+ """Total connections currently managed."""
394
+ return self.current_size + self.current_overflow
395
+
396
+ @property
397
+ def active_connections(self) -> int:
398
+ """Currently checked out connections."""
399
+ return self.current_checked_out
400
+
401
+ @property
402
+ def idle_connections(self) -> int:
403
+ """Available connections in pool."""
404
+ return self.current_size - self.current_checked_out
405
+
406
+ @property
407
+ def overflow_connections(self) -> int:
408
+ """Connections beyond pool_size."""
409
+ return self.current_overflow
410
+
411
+ @property
412
+ def avg_checkout_time_ms(self) -> float:
413
+ """Average checkout time in milliseconds."""
414
+ if self.checkouts == 0:
415
+ return 0.0
416
+ return self.total_checkout_time_ms / self.checkouts
417
+
418
+ def record_checkout(self, duration_ms: float) -> None:
419
+ """Record a successful checkout."""
420
+ with self._lock:
421
+ self.checkouts += 1
422
+ self.current_checked_out += 1
423
+ self.total_checkout_time_ms += duration_ms
424
+ self.max_checkout_time_ms = max(self.max_checkout_time_ms, duration_ms)
425
+ if self.current_checked_out > self.peak_connections:
426
+ self.peak_connections = self.current_checked_out
427
+
428
+ def record_checkin(self) -> None:
429
+ """Record a connection checkin."""
430
+ with self._lock:
431
+ self.checkins += 1
432
+ self.current_checked_out = max(0, self.current_checked_out - 1)
433
+
434
+ def record_creation(self) -> None:
435
+ """Record a new connection creation."""
436
+ with self._lock:
437
+ self.total_created += 1
438
+ self.current_size += 1
439
+
440
+ def record_close(self) -> None:
441
+ """Record a connection close."""
442
+ with self._lock:
443
+ self.total_closed += 1
444
+ self.current_size = max(0, self.current_size - 1)
445
+
446
+ def record_health_check(self, passed: bool) -> None:
447
+ """Record health check result."""
448
+ with self._lock:
449
+ self.last_health_check = datetime.now(timezone.utc)
450
+ if passed:
451
+ self.health_checks_passed += 1
452
+ else:
453
+ self.health_checks_failed += 1
454
+
455
+ def to_dict(self) -> dict[str, Any]:
456
+ """Convert metrics to dictionary for serialization."""
457
+ return {
458
+ "connections": {
459
+ "total_created": self.total_created,
460
+ "total_closed": self.total_closed,
461
+ "current_size": self.current_size,
462
+ "current_checked_out": self.current_checked_out,
463
+ "current_overflow": self.current_overflow,
464
+ "peak": self.peak_connections,
465
+ "idle": self.idle_connections,
466
+ },
467
+ "operations": {
468
+ "checkouts": self.checkouts,
469
+ "checkins": self.checkins,
470
+ "checkout_failures": self.checkout_failures,
471
+ "recycles": self.recycles,
472
+ "invalidations": self.invalidations,
473
+ },
474
+ "timing": {
475
+ "avg_checkout_ms": round(self.avg_checkout_time_ms, 2),
476
+ "max_checkout_ms": round(self.max_checkout_time_ms, 2),
477
+ "total_connection_time_ms": round(self.total_connection_time_ms, 2),
478
+ },
479
+ "health": {
480
+ "checks_passed": self.health_checks_passed,
481
+ "checks_failed": self.health_checks_failed,
482
+ "last_check": (
483
+ self.last_health_check.isoformat() if self.last_health_check else None
484
+ ),
485
+ },
486
+ "circuit_breaker": {
487
+ "opens": self.circuit_opens,
488
+ "closes": self.circuit_closes,
489
+ "requests_rejected": self.requests_rejected,
490
+ },
491
+ }
492
+
493
+ def to_prometheus(self) -> str:
494
+ """Export metrics in Prometheus format."""
495
+ lines = [
496
+ "# HELP truthound_pool_connections_total Total connections created",
497
+ f"truthound_pool_connections_total {self.total_created}",
498
+ "# HELP truthound_pool_connections_current Current pool size",
499
+ f"truthound_pool_connections_current {self.current_size}",
500
+ "# HELP truthound_pool_connections_active Active connections",
501
+ f"truthound_pool_connections_active {self.current_checked_out}",
502
+ "# HELP truthound_pool_connections_idle Idle connections",
503
+ f"truthound_pool_connections_idle {self.idle_connections}",
504
+ "# HELP truthound_pool_checkouts_total Total checkouts",
505
+ f"truthound_pool_checkouts_total {self.checkouts}",
506
+ "# HELP truthound_pool_checkout_time_avg_ms Average checkout time",
507
+ f"truthound_pool_checkout_time_avg_ms {self.avg_checkout_time_ms:.2f}",
508
+ "# HELP truthound_pool_health_checks_passed Health checks passed",
509
+ f"truthound_pool_health_checks_passed {self.health_checks_passed}",
510
+ "# HELP truthound_pool_health_checks_failed Health checks failed",
511
+ f"truthound_pool_health_checks_failed {self.health_checks_failed}",
512
+ ]
513
+ return "\n".join(lines)
514
+
515
+
516
+ # =============================================================================
517
+ # Circuit Breaker
518
+ # =============================================================================
519
+
520
+
521
+ class CircuitBreaker:
522
+ """Circuit breaker implementation for connection pool fault tolerance.
523
+
524
+ Prevents cascading failures by temporarily rejecting requests when
525
+ the database is experiencing issues.
526
+ """
527
+
528
+ def __init__(self, config: CircuitBreakerConfig) -> None:
529
+ """Initialize circuit breaker.
530
+
531
+ Args:
532
+ config: Circuit breaker configuration.
533
+ """
534
+ self._config = config
535
+ self._state = CircuitState.CLOSED
536
+ self._failure_count = 0
537
+ self._success_count = 0
538
+ self._last_failure_time: datetime | None = None
539
+ self._half_open_calls = 0
540
+ self._lock = threading.Lock()
541
+
542
+ @property
543
+ def state(self) -> CircuitState:
544
+ """Get current circuit state."""
545
+ return self._state
546
+
547
+ @property
548
+ def is_closed(self) -> bool:
549
+ """Check if circuit is closed (normal operation)."""
550
+ return self._state == CircuitState.CLOSED
551
+
552
+ @property
553
+ def is_open(self) -> bool:
554
+ """Check if circuit is open (rejecting requests)."""
555
+ return self._state == CircuitState.OPEN
556
+
557
+ def can_execute(self) -> bool:
558
+ """Check if a request can be executed."""
559
+ with self._lock:
560
+ if self._state == CircuitState.CLOSED:
561
+ return True
562
+
563
+ if self._state == CircuitState.OPEN:
564
+ # Check if timeout has passed
565
+ if self._last_failure_time:
566
+ elapsed = (datetime.now(timezone.utc) - self._last_failure_time).total_seconds()
567
+ if elapsed >= self._config.timeout:
568
+ self._transition_to(CircuitState.HALF_OPEN)
569
+ # First call in half-open, increment counter
570
+ self._half_open_calls += 1
571
+ return True
572
+ return False
573
+
574
+ if self._state == CircuitState.HALF_OPEN:
575
+ # Allow limited calls in half-open state
576
+ if self._half_open_calls < self._config.half_open_max_calls:
577
+ self._half_open_calls += 1
578
+ return True
579
+ return False
580
+
581
+ return False
582
+
583
+ def record_success(self) -> None:
584
+ """Record a successful operation."""
585
+ with self._lock:
586
+ if self._state == CircuitState.HALF_OPEN:
587
+ self._success_count += 1
588
+ if self._success_count >= self._config.success_threshold:
589
+ self._transition_to(CircuitState.CLOSED)
590
+ elif self._state == CircuitState.CLOSED:
591
+ # Reset failure count on success
592
+ self._failure_count = 0
593
+
594
+ def record_failure(self) -> None:
595
+ """Record a failed operation."""
596
+ with self._lock:
597
+ self._failure_count += 1
598
+ self._last_failure_time = datetime.now(timezone.utc)
599
+
600
+ if self._state == CircuitState.HALF_OPEN:
601
+ # Any failure in half-open immediately opens
602
+ self._transition_to(CircuitState.OPEN)
603
+ elif self._state == CircuitState.CLOSED:
604
+ if self._failure_count >= self._config.failure_threshold:
605
+ self._transition_to(CircuitState.OPEN)
606
+
607
+ def _transition_to(self, new_state: CircuitState) -> None:
608
+ """Transition to a new state."""
609
+ old_state = self._state
610
+ self._state = new_state
611
+
612
+ if new_state == CircuitState.CLOSED:
613
+ self._failure_count = 0
614
+ self._success_count = 0
615
+ self._half_open_calls = 0
616
+ logger.info(f"Circuit breaker closed (was {old_state.name})")
617
+
618
+ elif new_state == CircuitState.OPEN:
619
+ self._success_count = 0
620
+ self._half_open_calls = 0
621
+ logger.warning(
622
+ f"Circuit breaker opened after {self._failure_count} failures"
623
+ )
624
+
625
+ elif new_state == CircuitState.HALF_OPEN:
626
+ self._success_count = 0
627
+ self._half_open_calls = 0
628
+ logger.info("Circuit breaker half-open, testing recovery")
629
+
630
+ def reset(self) -> None:
631
+ """Reset circuit breaker to closed state."""
632
+ with self._lock:
633
+ self._transition_to(CircuitState.CLOSED)
634
+
635
+
636
+ # =============================================================================
637
+ # Retry Handler
638
+ # =============================================================================
639
+
640
+
641
+ class RetryHandler:
642
+ """Retry handler with exponential backoff and jitter.
643
+
644
+ Provides configurable retry behavior for transient database errors.
645
+ """
646
+
647
+ def __init__(self, config: RetryConfig) -> None:
648
+ """Initialize retry handler.
649
+
650
+ Args:
651
+ config: Retry configuration.
652
+ """
653
+ self._config = config
654
+ import random
655
+
656
+ self._random = random
657
+
658
+ def should_retry(self, error: Exception, attempt: int) -> bool:
659
+ """Check if operation should be retried.
660
+
661
+ Args:
662
+ error: The exception that occurred.
663
+ attempt: Current attempt number (1-based).
664
+
665
+ Returns:
666
+ True if should retry, False otherwise.
667
+ """
668
+ if attempt >= self._config.max_retries:
669
+ return False
670
+ return isinstance(error, self._config.retryable_errors)
671
+
672
+ def get_delay(self, attempt: int) -> float:
673
+ """Calculate delay before next retry.
674
+
675
+ Args:
676
+ attempt: Current attempt number (1-based).
677
+
678
+ Returns:
679
+ Delay in seconds.
680
+ """
681
+ delay = self._config.base_delay * (self._config.exponential_base ** (attempt - 1))
682
+ delay = min(delay, self._config.max_delay)
683
+
684
+ if self._config.jitter:
685
+ # Add ±25% jitter
686
+ jitter = delay * 0.25 * (2 * self._random.random() - 1)
687
+ delay += jitter
688
+
689
+ return max(0, delay)
690
+
691
+ def execute_with_retry(
692
+ self,
693
+ operation: Callable[[], Any],
694
+ on_retry: Callable[[Exception, int], None] | None = None,
695
+ ) -> Any:
696
+ """Execute operation with retry logic.
697
+
698
+ Args:
699
+ operation: Callable to execute.
700
+ on_retry: Optional callback called before each retry.
701
+
702
+ Returns:
703
+ Result of the operation.
704
+
705
+ Raises:
706
+ Exception: The last exception if all retries exhausted.
707
+ """
708
+ last_error: Exception | None = None
709
+
710
+ for attempt in range(1, self._config.max_retries + 2):
711
+ try:
712
+ return operation()
713
+ except Exception as e:
714
+ last_error = e
715
+
716
+ if not self.should_retry(e, attempt):
717
+ raise
718
+
719
+ delay = self.get_delay(attempt)
720
+
721
+ if on_retry:
722
+ on_retry(e, attempt)
723
+
724
+ logger.warning(
725
+ f"Retry attempt {attempt}/{self._config.max_retries} "
726
+ f"after {delay:.2f}s: {e}"
727
+ )
728
+
729
+ time.sleep(delay)
730
+
731
+ # Should not reach here, but for type safety
732
+ if last_error:
733
+ raise last_error
734
+ raise RuntimeError("Retry logic error")
735
+
736
+
737
+ # =============================================================================
738
+ # Connection Pool Manager
739
+ # =============================================================================
740
+
741
+
742
+ class ConnectionPoolManager:
743
+ """Enterprise-grade connection pool manager.
744
+
745
+ Provides:
746
+ - Multiple pooling strategies
747
+ - Health monitoring
748
+ - Circuit breaker protection
749
+ - Retry logic
750
+ - Comprehensive metrics
751
+ - Database-specific optimizations
752
+
753
+ Example:
754
+ >>> config = ConnectionPoolConfig(
755
+ ... connection_url="postgresql://user:pass@localhost/db",
756
+ ... pool=PoolConfig(pool_size=10, max_overflow=20),
757
+ ... )
758
+ >>> manager = ConnectionPoolManager(config)
759
+ >>> with manager.session() as session:
760
+ ... session.execute(text("SELECT 1"))
761
+ """
762
+
763
+ def __init__(
764
+ self,
765
+ config: ConnectionPoolConfig | None = None,
766
+ *,
767
+ connection_url: str | None = None,
768
+ pool_size: int = 5,
769
+ max_overflow: int = 10,
770
+ **kwargs: Any,
771
+ ) -> None:
772
+ """Initialize connection pool manager.
773
+
774
+ Args:
775
+ config: Complete configuration object.
776
+ connection_url: Database connection URL (if not using config).
777
+ pool_size: Pool size (if not using config).
778
+ max_overflow: Max overflow (if not using config).
779
+ **kwargs: Additional options passed to config.
780
+ """
781
+ if not HAS_SQLALCHEMY:
782
+ raise ImportError(
783
+ "SQLAlchemy is required for ConnectionPoolManager. "
784
+ "Install with: pip install truthound[database]"
785
+ )
786
+
787
+ # Build config from parameters if not provided
788
+ if config is None:
789
+ url = connection_url or kwargs.pop("url", "sqlite:///:memory:")
790
+ pool_config = PoolConfig(
791
+ pool_size=pool_size,
792
+ max_overflow=max_overflow,
793
+ pool_timeout=kwargs.pop("pool_timeout", 30.0),
794
+ pool_recycle=kwargs.pop("pool_recycle", 3600),
795
+ pool_pre_ping=kwargs.pop("pool_pre_ping", True),
796
+ )
797
+ config = ConnectionPoolConfig(
798
+ connection_url=url,
799
+ pool=pool_config,
800
+ echo=kwargs.pop("echo", False),
801
+ connect_args=kwargs.pop("connect_args", {}),
802
+ engine_options=kwargs,
803
+ )
804
+
805
+ # Apply dialect-specific defaults
806
+ self._config = config.with_dialect_defaults()
807
+
808
+ # Initialize components
809
+ self._engine: Engine | None = None
810
+ self._session_factory: sessionmaker | None = None
811
+ self._metrics = PoolMetrics()
812
+ self._circuit_breaker = CircuitBreaker(self._config.circuit_breaker)
813
+ self._retry_handler = RetryHandler(self._config.retry)
814
+
815
+ # Health check management
816
+ self._health_check_thread: threading.Thread | None = None
817
+ self._health_check_stop = threading.Event()
818
+ self._is_healthy = True
819
+
820
+ # Lifecycle management
821
+ self._lock = threading.RLock()
822
+ self._initialized = False
823
+ self._disposed = False
824
+
825
+ # Tracked sessions for cleanup
826
+ self._active_sessions: weakref.WeakSet[Session] = weakref.WeakSet()
827
+
828
+ @property
829
+ def config(self) -> ConnectionPoolConfig:
830
+ """Get pool configuration."""
831
+ return self._config
832
+
833
+ @property
834
+ def metrics(self) -> PoolMetrics:
835
+ """Get pool metrics."""
836
+ return self._metrics
837
+
838
+ @property
839
+ def is_healthy(self) -> bool:
840
+ """Check if pool is healthy."""
841
+ return self._is_healthy and self._circuit_breaker.is_closed
842
+
843
+ @property
844
+ def circuit_state(self) -> CircuitState:
845
+ """Get circuit breaker state."""
846
+ return self._circuit_breaker.state
847
+
848
+ def initialize(self) -> None:
849
+ """Initialize the connection pool.
850
+
851
+ Creates the engine, session factory, and starts health checks.
852
+ """
853
+ with self._lock:
854
+ if self._initialized:
855
+ return
856
+
857
+ if self._disposed:
858
+ raise RuntimeError("Cannot initialize disposed pool manager")
859
+
860
+ self._create_engine()
861
+ self._test_connection()
862
+ self._setup_event_listeners()
863
+ self._start_health_checks()
864
+ self._initialized = True
865
+
866
+ logger.info(
867
+ f"ConnectionPoolManager initialized: "
868
+ f"dialect={self._config.dialect.value}, "
869
+ f"pool_size={self._config.pool.pool_size}, "
870
+ f"max_overflow={self._config.pool.max_overflow}"
871
+ )
872
+
873
+ def _create_engine(self) -> None:
874
+ """Create SQLAlchemy engine with configured pool."""
875
+ pool_config = self._config.pool
876
+ strategy = pool_config.strategy
877
+
878
+ # Build pool class based on strategy
879
+ pool_class: type | None = None
880
+ pool_kwargs: dict[str, Any] = {}
881
+
882
+ if strategy == PoolStrategy.QUEUE_POOL:
883
+ pool_class = QueuePool
884
+ pool_kwargs = {
885
+ "pool_size": pool_config.pool_size,
886
+ "max_overflow": pool_config.max_overflow,
887
+ "timeout": pool_config.pool_timeout,
888
+ "pool_recycle": pool_config.pool_recycle,
889
+ "pool_pre_ping": pool_config.pool_pre_ping,
890
+ }
891
+ elif strategy == PoolStrategy.NULL_POOL:
892
+ pool_class = NullPool
893
+ elif strategy == PoolStrategy.STATIC_POOL:
894
+ pool_class = StaticPool
895
+ elif strategy == PoolStrategy.SINGLETON_THREAD:
896
+ pool_class = SingletonThreadPool
897
+ pool_kwargs = {"pool_size": pool_config.pool_size}
898
+
899
+ # Build engine options
900
+ engine_options = {
901
+ "echo": self._config.echo,
902
+ "echo_pool": pool_config.echo_pool,
903
+ "connect_args": self._config.connect_args,
904
+ **self._config.engine_options,
905
+ }
906
+
907
+ if pool_class:
908
+ engine_options["poolclass"] = pool_class
909
+ engine_options.update(pool_kwargs)
910
+
911
+ self._engine = create_engine(self._config.connection_url, **engine_options)
912
+ self._session_factory = sessionmaker(bind=self._engine)
913
+
914
+ def _test_connection(self) -> None:
915
+ """Test database connection."""
916
+ if self._engine is None:
917
+ raise RuntimeError("Engine not created")
918
+
919
+ try:
920
+ with self._engine.connect() as conn:
921
+ conn.execute(text(self._config.health_check.query))
922
+ conn.commit()
923
+ except SQLAlchemyError as e:
924
+ logger.error(f"Connection test failed: {e}")
925
+ raise
926
+
927
+ def _setup_event_listeners(self) -> None:
928
+ """Set up SQLAlchemy event listeners for metrics."""
929
+ if self._engine is None:
930
+ return
931
+
932
+ @event.listens_for(self._engine, "checkout")
933
+ def on_checkout(
934
+ dbapi_conn: Any, connection_record: Any, connection_proxy: Any
935
+ ) -> None:
936
+ self._metrics.record_checkout(0) # Duration tracked elsewhere
937
+
938
+ @event.listens_for(self._engine, "checkin")
939
+ def on_checkin(dbapi_conn: Any, connection_record: Any) -> None:
940
+ self._metrics.record_checkin()
941
+
942
+ @event.listens_for(self._engine, "connect")
943
+ def on_connect(dbapi_conn: Any, connection_record: Any) -> None:
944
+ self._metrics.record_creation()
945
+
946
+ @event.listens_for(self._engine, "close")
947
+ def on_close(dbapi_conn: Any, connection_record: Any) -> None:
948
+ self._metrics.record_close()
949
+
950
+ @event.listens_for(self._engine, "invalidate")
951
+ def on_invalidate(
952
+ dbapi_conn: Any, connection_record: Any, exception: Any
953
+ ) -> None:
954
+ self._metrics._lock.acquire()
955
+ try:
956
+ self._metrics.invalidations += 1
957
+ finally:
958
+ self._metrics._lock.release()
959
+
960
+ def _start_health_checks(self) -> None:
961
+ """Start background health check thread."""
962
+ if not self._config.health_check.enabled:
963
+ return
964
+
965
+ self._health_check_stop.clear()
966
+ self._health_check_thread = threading.Thread(
967
+ target=self._health_check_loop,
968
+ name="truthound-pool-health-check",
969
+ daemon=True,
970
+ )
971
+ self._health_check_thread.start()
972
+
973
+ def _health_check_loop(self) -> None:
974
+ """Background health check loop."""
975
+ while not self._health_check_stop.is_set():
976
+ try:
977
+ self._perform_health_check()
978
+ except Exception as e:
979
+ logger.error(f"Health check error: {e}")
980
+
981
+ self._health_check_stop.wait(self._config.health_check.interval)
982
+
983
+ def _perform_health_check(self) -> None:
984
+ """Perform a single health check."""
985
+ if self._engine is None:
986
+ self._is_healthy = False
987
+ self._metrics.record_health_check(False)
988
+ return
989
+
990
+ try:
991
+ with self._engine.connect() as conn:
992
+ conn.execute(text(self._config.health_check.query))
993
+ conn.commit()
994
+ self._is_healthy = True
995
+ self._metrics.record_health_check(True)
996
+ except SQLAlchemyError:
997
+ self._is_healthy = False
998
+ self._metrics.record_health_check(False)
999
+
1000
+ def get_engine(self) -> "Engine":
1001
+ """Get the SQLAlchemy engine.
1002
+
1003
+ Returns:
1004
+ The configured engine.
1005
+
1006
+ Raises:
1007
+ RuntimeError: If not initialized.
1008
+ """
1009
+ if not self._initialized:
1010
+ self.initialize()
1011
+ if self._engine is None:
1012
+ raise RuntimeError("Engine not available")
1013
+ return self._engine
1014
+
1015
+ def get_session(self) -> "Session":
1016
+ """Get a new database session.
1017
+
1018
+ Returns:
1019
+ A new session instance.
1020
+
1021
+ Raises:
1022
+ RuntimeError: If circuit is open.
1023
+ """
1024
+ if not self._initialized:
1025
+ self.initialize()
1026
+
1027
+ if not self._circuit_breaker.can_execute():
1028
+ self._metrics._lock.acquire()
1029
+ try:
1030
+ self._metrics.requests_rejected += 1
1031
+ finally:
1032
+ self._metrics._lock.release()
1033
+ raise RuntimeError(
1034
+ "Circuit breaker is open, database connections temporarily unavailable"
1035
+ )
1036
+
1037
+ if self._session_factory is None:
1038
+ raise RuntimeError("Session factory not available")
1039
+
1040
+ session = self._session_factory()
1041
+ self._active_sessions.add(session)
1042
+ return session
1043
+
1044
+ @contextmanager
1045
+ def session(self) -> Iterator["Session"]:
1046
+ """Context manager for database sessions.
1047
+
1048
+ Provides automatic commit/rollback and connection return.
1049
+
1050
+ Yields:
1051
+ Database session.
1052
+
1053
+ Example:
1054
+ >>> with pool_manager.session() as session:
1055
+ ... result = session.execute(text("SELECT 1"))
1056
+ """
1057
+ session = None
1058
+ start_time = time.time()
1059
+ success = False
1060
+
1061
+ try:
1062
+ session = self.get_session()
1063
+ yield session
1064
+ session.commit()
1065
+ success = True
1066
+ self._circuit_breaker.record_success()
1067
+
1068
+ except SQLAlchemyError as e:
1069
+ if session:
1070
+ session.rollback()
1071
+ self._circuit_breaker.record_failure()
1072
+ raise
1073
+
1074
+ finally:
1075
+ duration_ms = (time.time() - start_time) * 1000
1076
+ if session:
1077
+ try:
1078
+ session.close()
1079
+ except Exception:
1080
+ pass
1081
+
1082
+ # Record checkout timing
1083
+ if success:
1084
+ with self._metrics._lock:
1085
+ self._metrics.total_checkout_time_ms += duration_ms
1086
+ self._metrics.max_checkout_time_ms = max(
1087
+ self._metrics.max_checkout_time_ms, duration_ms
1088
+ )
1089
+
1090
+ def execute_with_retry(
1091
+ self,
1092
+ operation: Callable[["Session"], Any],
1093
+ ) -> Any:
1094
+ """Execute operation with automatic retry.
1095
+
1096
+ Args:
1097
+ operation: Callable that takes a session and returns a result.
1098
+
1099
+ Returns:
1100
+ Result of the operation.
1101
+ """
1102
+
1103
+ def wrapped_operation() -> Any:
1104
+ with self.session() as session:
1105
+ return operation(session)
1106
+
1107
+ return self._retry_handler.execute_with_retry(wrapped_operation)
1108
+
1109
+ def recycle_connections(self) -> int:
1110
+ """Manually recycle all pool connections.
1111
+
1112
+ Returns:
1113
+ Number of connections recycled.
1114
+ """
1115
+ if self._engine is None:
1116
+ return 0
1117
+
1118
+ pool = self._engine.pool
1119
+ if pool is None:
1120
+ return 0
1121
+
1122
+ # Dispose and recreate
1123
+ old_size = self._metrics.current_size
1124
+ self._engine.dispose()
1125
+ self._metrics._lock.acquire()
1126
+ try:
1127
+ self._metrics.recycles += old_size
1128
+ self._metrics.current_size = 0
1129
+ finally:
1130
+ self._metrics._lock.release()
1131
+
1132
+ logger.info(f"Recycled {old_size} connections")
1133
+ return old_size
1134
+
1135
+ def get_pool_status(self) -> dict[str, Any]:
1136
+ """Get comprehensive pool status.
1137
+
1138
+ Returns:
1139
+ Dictionary with pool status information.
1140
+ """
1141
+ return {
1142
+ "initialized": self._initialized,
1143
+ "disposed": self._disposed,
1144
+ "healthy": self.is_healthy,
1145
+ "circuit_state": self._circuit_breaker.state.name,
1146
+ "dialect": self._config.dialect.value,
1147
+ "config": {
1148
+ "connection_url": self._mask_password(self._config.connection_url),
1149
+ "pool_size": self._config.pool.pool_size,
1150
+ "max_overflow": self._config.pool.max_overflow,
1151
+ "pool_timeout": self._config.pool.pool_timeout,
1152
+ "pool_recycle": self._config.pool.pool_recycle,
1153
+ },
1154
+ "metrics": self._metrics.to_dict(),
1155
+ }
1156
+
1157
+ def _mask_password(self, url: str) -> str:
1158
+ """Mask password in connection URL."""
1159
+ import re
1160
+
1161
+ return re.sub(r"://([^:]+):([^@]+)@", r"://\1:***@", url)
1162
+
1163
+ def dispose(self) -> None:
1164
+ """Dispose of the connection pool.
1165
+
1166
+ Closes all connections and stops health checks.
1167
+ """
1168
+ with self._lock:
1169
+ if self._disposed:
1170
+ return
1171
+
1172
+ # Stop health checks
1173
+ if self._health_check_thread:
1174
+ self._health_check_stop.set()
1175
+ self._health_check_thread.join(timeout=5.0)
1176
+ self._health_check_thread = None
1177
+
1178
+ # Close active sessions
1179
+ for session in list(self._active_sessions):
1180
+ try:
1181
+ session.close()
1182
+ except Exception:
1183
+ pass
1184
+
1185
+ # Dispose engine
1186
+ if self._engine:
1187
+ self._engine.dispose()
1188
+ self._engine = None
1189
+ self._session_factory = None
1190
+
1191
+ self._disposed = True
1192
+ self._initialized = False
1193
+
1194
+ logger.info("ConnectionPoolManager disposed")
1195
+
1196
+ def __enter__(self) -> "ConnectionPoolManager":
1197
+ """Context manager entry."""
1198
+ self.initialize()
1199
+ return self
1200
+
1201
+ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
1202
+ """Context manager exit."""
1203
+ self.dispose()
1204
+
1205
+ def __del__(self) -> None:
1206
+ """Destructor - ensure cleanup."""
1207
+ try:
1208
+ self.dispose()
1209
+ except Exception:
1210
+ pass
1211
+
1212
+
1213
+ # =============================================================================
1214
+ # Factory Functions
1215
+ # =============================================================================
1216
+
1217
+
1218
+ def create_pool_manager(
1219
+ connection_url: str,
1220
+ *,
1221
+ pool_size: int = 5,
1222
+ max_overflow: int = 10,
1223
+ strategy: PoolStrategy = PoolStrategy.QUEUE_POOL,
1224
+ enable_circuit_breaker: bool = True,
1225
+ enable_health_checks: bool = True,
1226
+ **kwargs: Any,
1227
+ ) -> ConnectionPoolManager:
1228
+ """Create a connection pool manager with sensible defaults.
1229
+
1230
+ Args:
1231
+ connection_url: Database connection URL.
1232
+ pool_size: Number of connections to maintain.
1233
+ max_overflow: Maximum overflow connections.
1234
+ strategy: Pooling strategy to use.
1235
+ enable_circuit_breaker: Whether to enable circuit breaker.
1236
+ enable_health_checks: Whether to enable health checks.
1237
+ **kwargs: Additional configuration options.
1238
+
1239
+ Returns:
1240
+ Configured ConnectionPoolManager instance.
1241
+
1242
+ Example:
1243
+ >>> manager = create_pool_manager(
1244
+ ... "postgresql://user:pass@localhost/db",
1245
+ ... pool_size=10,
1246
+ ... max_overflow=20,
1247
+ ... )
1248
+ """
1249
+ pool_config = PoolConfig(
1250
+ strategy=strategy,
1251
+ pool_size=pool_size,
1252
+ max_overflow=max_overflow,
1253
+ pool_timeout=kwargs.pop("pool_timeout", 30.0),
1254
+ pool_recycle=kwargs.pop("pool_recycle", 3600),
1255
+ pool_pre_ping=kwargs.pop("pool_pre_ping", True),
1256
+ )
1257
+
1258
+ circuit_config = CircuitBreakerConfig()
1259
+ if not enable_circuit_breaker:
1260
+ circuit_config.failure_threshold = 999999 # Effectively disabled
1261
+
1262
+ health_config = HealthCheckConfig(enabled=enable_health_checks)
1263
+
1264
+ config = ConnectionPoolConfig(
1265
+ connection_url=connection_url,
1266
+ pool=pool_config,
1267
+ circuit_breaker=circuit_config,
1268
+ health_check=health_config,
1269
+ echo=kwargs.pop("echo", False),
1270
+ connect_args=kwargs.pop("connect_args", {}),
1271
+ engine_options=kwargs,
1272
+ )
1273
+
1274
+ return ConnectionPoolManager(config)
1275
+
1276
+
1277
+ def create_pool_for_dialect(
1278
+ dialect: DatabaseDialect | str,
1279
+ host: str = "localhost",
1280
+ port: int | None = None,
1281
+ database: str = "truthound",
1282
+ username: str = "",
1283
+ password: str = "",
1284
+ **kwargs: Any,
1285
+ ) -> ConnectionPoolManager:
1286
+ """Create a pool manager for a specific database dialect.
1287
+
1288
+ Args:
1289
+ dialect: Database dialect (or string name).
1290
+ host: Database host.
1291
+ port: Database port (uses dialect default if None).
1292
+ database: Database name.
1293
+ username: Database username.
1294
+ password: Database password.
1295
+ **kwargs: Additional pool configuration.
1296
+
1297
+ Returns:
1298
+ Configured ConnectionPoolManager instance.
1299
+
1300
+ Example:
1301
+ >>> manager = create_pool_for_dialect(
1302
+ ... DatabaseDialect.POSTGRESQL,
1303
+ ... host="localhost",
1304
+ ... database="mydb",
1305
+ ... username="user",
1306
+ ... password="pass",
1307
+ ... )
1308
+ """
1309
+ if isinstance(dialect, str):
1310
+ dialect = DatabaseDialect(dialect)
1311
+
1312
+ # Build connection URL based on dialect
1313
+ default_ports = {
1314
+ DatabaseDialect.POSTGRESQL: 5432,
1315
+ DatabaseDialect.MYSQL: 3306,
1316
+ DatabaseDialect.MSSQL: 1433,
1317
+ DatabaseDialect.ORACLE: 1521,
1318
+ }
1319
+
1320
+ port = port or default_ports.get(dialect)
1321
+
1322
+ if dialect == DatabaseDialect.SQLITE:
1323
+ url = f"sqlite:///{database}"
1324
+ elif dialect == DatabaseDialect.POSTGRESQL:
1325
+ url = f"postgresql://{username}:{password}@{host}:{port}/{database}"
1326
+ elif dialect == DatabaseDialect.MYSQL:
1327
+ url = f"mysql+pymysql://{username}:{password}@{host}:{port}/{database}"
1328
+ elif dialect == DatabaseDialect.MSSQL:
1329
+ url = f"mssql+pyodbc://{username}:{password}@{host}:{port}/{database}?driver=ODBC+Driver+17+for+SQL+Server"
1330
+ elif dialect == DatabaseDialect.ORACLE:
1331
+ url = f"oracle+cx_oracle://{username}:{password}@{host}:{port}/{database}"
1332
+ else:
1333
+ raise ValueError(f"Unsupported dialect: {dialect}")
1334
+
1335
+ return create_pool_manager(url, **kwargs)
1336
+
1337
+
1338
+ # =============================================================================
1339
+ # Exports
1340
+ # =============================================================================
1341
+
1342
+ __all__ = [
1343
+ # Enums
1344
+ "PoolStrategy",
1345
+ "DatabaseDialect",
1346
+ "ConnectionState",
1347
+ "CircuitState",
1348
+ # Configuration
1349
+ "PoolConfig",
1350
+ "RetryConfig",
1351
+ "CircuitBreakerConfig",
1352
+ "HealthCheckConfig",
1353
+ "ConnectionPoolConfig",
1354
+ # Core classes
1355
+ "PoolMetrics",
1356
+ "CircuitBreaker",
1357
+ "RetryHandler",
1358
+ "ConnectionPoolManager",
1359
+ # Factory functions
1360
+ "create_pool_manager",
1361
+ "create_pool_for_dialect",
1362
+ # Protocols
1363
+ "PoolMetricsProtocol",
1364
+ "ConnectionPoolProtocol",
1365
+ ]