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,1157 @@
1
+ """Metrics collection system for Truthound.
2
+
3
+ This module provides a flexible metrics collection framework with support
4
+ for multiple backend exporters (Prometheus, StatsD, OpenTelemetry).
5
+
6
+ Metric Types:
7
+ - Counter: Monotonically increasing value (e.g., request count)
8
+ - Gauge: Point-in-time value (e.g., active connections)
9
+ - Histogram: Distribution of values with buckets
10
+ - Summary: Distribution with quantiles
11
+
12
+ Design Principles:
13
+ 1. Label-based: Dimensional metrics with key-value labels
14
+ 2. Backend agnostic: Same API for all exporters
15
+ 3. Lazy registration: Metrics created on first use
16
+ 4. Thread-safe: All operations are thread-safe
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import threading
22
+ import time
23
+ from abc import ABC, abstractmethod
24
+ from contextlib import contextmanager
25
+ from dataclasses import dataclass, field
26
+ from datetime import datetime
27
+ from enum import Enum
28
+ from typing import Any, Callable, Iterator, TypeVar
29
+
30
+
31
+ # =============================================================================
32
+ # Metric Types
33
+ # =============================================================================
34
+
35
+
36
+ class MetricType(Enum):
37
+ """Types of metrics."""
38
+
39
+ COUNTER = "counter"
40
+ GAUGE = "gauge"
41
+ HISTOGRAM = "histogram"
42
+ SUMMARY = "summary"
43
+
44
+
45
+ @dataclass(frozen=True)
46
+ class MetricKey:
47
+ """Unique identifier for a metric with labels."""
48
+
49
+ name: str
50
+ labels: tuple[tuple[str, str], ...]
51
+
52
+ @classmethod
53
+ def create(cls, name: str, labels: dict[str, str] | None = None) -> "MetricKey":
54
+ """Create metric key from name and labels."""
55
+ label_tuple = tuple(sorted((labels or {}).items()))
56
+ return cls(name=name, labels=label_tuple)
57
+
58
+
59
+ @dataclass
60
+ class MetricMetadata:
61
+ """Metadata about a metric."""
62
+
63
+ name: str
64
+ type: MetricType
65
+ description: str
66
+ unit: str = ""
67
+ label_names: tuple[str, ...] = ()
68
+
69
+
70
+ # =============================================================================
71
+ # Metric Base Class
72
+ # =============================================================================
73
+
74
+
75
+ class Metric(ABC):
76
+ """Abstract base class for metrics.
77
+
78
+ All metrics support labels (dimensions) for multi-dimensional analysis.
79
+ """
80
+
81
+ def __init__(
82
+ self,
83
+ name: str,
84
+ description: str = "",
85
+ *,
86
+ unit: str = "",
87
+ label_names: tuple[str, ...] | list[str] = (),
88
+ ) -> None:
89
+ """Initialize metric.
90
+
91
+ Args:
92
+ name: Metric name (should be lowercase with underscores).
93
+ description: Human-readable description.
94
+ unit: Unit of measurement.
95
+ label_names: Names of labels for this metric.
96
+ """
97
+ self._name = name
98
+ self._description = description
99
+ self._unit = unit
100
+ self._label_names = tuple(label_names)
101
+ self._lock = threading.Lock()
102
+
103
+ @property
104
+ def name(self) -> str:
105
+ """Get metric name."""
106
+ return self._name
107
+
108
+ @property
109
+ def description(self) -> str:
110
+ """Get metric description."""
111
+ return self._description
112
+
113
+ @property
114
+ @abstractmethod
115
+ def type(self) -> MetricType:
116
+ """Get metric type."""
117
+ pass
118
+
119
+ @property
120
+ def metadata(self) -> MetricMetadata:
121
+ """Get metric metadata."""
122
+ return MetricMetadata(
123
+ name=self._name,
124
+ type=self.type,
125
+ description=self._description,
126
+ unit=self._unit,
127
+ label_names=self._label_names,
128
+ )
129
+
130
+ def _validate_labels(self, labels: dict[str, str]) -> None:
131
+ """Validate label names match expected."""
132
+ if set(labels.keys()) != set(self._label_names):
133
+ expected = set(self._label_names)
134
+ actual = set(labels.keys())
135
+ raise ValueError(
136
+ f"Label mismatch for '{self._name}': "
137
+ f"expected {expected}, got {actual}"
138
+ )
139
+
140
+ @abstractmethod
141
+ def collect(self) -> list[tuple[dict[str, str], float]]:
142
+ """Collect all metric values with labels.
143
+
144
+ Returns:
145
+ List of (labels, value) tuples.
146
+ """
147
+ pass
148
+
149
+
150
+ # =============================================================================
151
+ # Counter
152
+ # =============================================================================
153
+
154
+
155
+ class Counter(Metric):
156
+ """Monotonically increasing counter.
157
+
158
+ Counters only go up (and reset to zero on restart).
159
+ Use for: request counts, errors, completed tasks.
160
+
161
+ Example:
162
+ >>> requests = Counter("http_requests_total", "Total HTTP requests")
163
+ >>> requests.inc()
164
+ >>> requests.add(5)
165
+ >>> requests.labels(method="GET", status="200").inc()
166
+ """
167
+
168
+ def __init__(
169
+ self,
170
+ name: str,
171
+ description: str = "",
172
+ *,
173
+ labels: tuple[str, ...] | list[str] = (),
174
+ **kwargs: Any,
175
+ ) -> None:
176
+ """Initialize counter.
177
+
178
+ Args:
179
+ name: Counter name.
180
+ description: Human-readable description.
181
+ labels: Label names for this counter.
182
+ **kwargs: Additional arguments for Metric.
183
+ """
184
+ super().__init__(name, description, label_names=labels, **kwargs)
185
+ self._values: dict[MetricKey, float] = {}
186
+
187
+ @property
188
+ def type(self) -> MetricType:
189
+ return MetricType.COUNTER
190
+
191
+ def inc(self, value: float = 1.0, **labels: str) -> None:
192
+ """Increment counter.
193
+
194
+ Args:
195
+ value: Amount to increment (must be positive).
196
+ **labels: Label values.
197
+ """
198
+ if value < 0:
199
+ raise ValueError("Counter can only be incremented")
200
+ self.add(value, **labels)
201
+
202
+ def add(self, value: float, **labels: str) -> None:
203
+ """Add to counter.
204
+
205
+ Args:
206
+ value: Amount to add (must be positive).
207
+ **labels: Label values.
208
+ """
209
+ if value < 0:
210
+ raise ValueError("Counter can only increase")
211
+
212
+ if self._label_names:
213
+ self._validate_labels(labels)
214
+
215
+ key = MetricKey.create(self._name, labels)
216
+
217
+ with self._lock:
218
+ self._values[key] = self._values.get(key, 0.0) + value
219
+
220
+ def labels(self, **labels: str) -> "LabeledCounter":
221
+ """Get counter with specific labels.
222
+
223
+ Args:
224
+ **labels: Label values.
225
+
226
+ Returns:
227
+ LabeledCounter for the specific label set.
228
+ """
229
+ return LabeledCounter(self, labels)
230
+
231
+ def get(self, **labels: str) -> float:
232
+ """Get current counter value.
233
+
234
+ Args:
235
+ **labels: Label values.
236
+
237
+ Returns:
238
+ Current value.
239
+ """
240
+ key = MetricKey.create(self._name, labels)
241
+ with self._lock:
242
+ return self._values.get(key, 0.0)
243
+
244
+ def collect(self) -> list[tuple[dict[str, str], float]]:
245
+ """Collect all counter values."""
246
+ with self._lock:
247
+ return [
248
+ (dict(key.labels), value)
249
+ for key, value in self._values.items()
250
+ ]
251
+
252
+
253
+ class LabeledCounter:
254
+ """Counter with pre-set labels."""
255
+
256
+ def __init__(self, counter: Counter, labels: dict[str, str]) -> None:
257
+ self._counter = counter
258
+ self._labels = labels
259
+
260
+ def inc(self, value: float = 1.0) -> None:
261
+ """Increment counter."""
262
+ self._counter.inc(value, **self._labels)
263
+
264
+ def add(self, value: float) -> None:
265
+ """Add to counter."""
266
+ self._counter.add(value, **self._labels)
267
+
268
+
269
+ # =============================================================================
270
+ # Gauge
271
+ # =============================================================================
272
+
273
+
274
+ class Gauge(Metric):
275
+ """Point-in-time value that can go up or down.
276
+
277
+ Use for: temperature, queue size, memory usage.
278
+
279
+ Example:
280
+ >>> temperature = Gauge("temperature_celsius", "Current temperature")
281
+ >>> temperature.set(23.5)
282
+ >>> temperature.inc()
283
+ >>> temperature.dec(0.5)
284
+ """
285
+
286
+ def __init__(
287
+ self,
288
+ name: str,
289
+ description: str = "",
290
+ *,
291
+ labels: tuple[str, ...] | list[str] = (),
292
+ **kwargs: Any,
293
+ ) -> None:
294
+ """Initialize gauge."""
295
+ super().__init__(name, description, label_names=labels, **kwargs)
296
+ self._values: dict[MetricKey, float] = {}
297
+
298
+ @property
299
+ def type(self) -> MetricType:
300
+ return MetricType.GAUGE
301
+
302
+ def set(self, value: float, **labels: str) -> None:
303
+ """Set gauge value.
304
+
305
+ Args:
306
+ value: New value.
307
+ **labels: Label values.
308
+ """
309
+ if self._label_names:
310
+ self._validate_labels(labels)
311
+
312
+ key = MetricKey.create(self._name, labels)
313
+
314
+ with self._lock:
315
+ self._values[key] = value
316
+
317
+ def inc(self, value: float = 1.0, **labels: str) -> None:
318
+ """Increment gauge.
319
+
320
+ Args:
321
+ value: Amount to increment.
322
+ **labels: Label values.
323
+ """
324
+ key = MetricKey.create(self._name, labels if self._label_names else {})
325
+
326
+ with self._lock:
327
+ self._values[key] = self._values.get(key, 0.0) + value
328
+
329
+ def dec(self, value: float = 1.0, **labels: str) -> None:
330
+ """Decrement gauge.
331
+
332
+ Args:
333
+ value: Amount to decrement.
334
+ **labels: Label values.
335
+ """
336
+ self.inc(-value, **labels)
337
+
338
+ def get(self, **labels: str) -> float:
339
+ """Get current gauge value."""
340
+ key = MetricKey.create(self._name, labels)
341
+ with self._lock:
342
+ return self._values.get(key, 0.0)
343
+
344
+ def labels(self, **labels: str) -> "LabeledGauge":
345
+ """Get gauge with specific labels."""
346
+ return LabeledGauge(self, labels)
347
+
348
+ @contextmanager
349
+ def track_inprogress(self, **labels: str) -> Iterator[None]:
350
+ """Track in-progress operations.
351
+
352
+ Increments on entry, decrements on exit.
353
+ """
354
+ self.inc(**labels)
355
+ try:
356
+ yield
357
+ finally:
358
+ self.dec(**labels)
359
+
360
+ def set_to_current_time(self, **labels: str) -> None:
361
+ """Set gauge to current Unix timestamp."""
362
+ self.set(time.time(), **labels)
363
+
364
+ def collect(self) -> list[tuple[dict[str, str], float]]:
365
+ """Collect all gauge values."""
366
+ with self._lock:
367
+ return [
368
+ (dict(key.labels), value)
369
+ for key, value in self._values.items()
370
+ ]
371
+
372
+
373
+ class LabeledGauge:
374
+ """Gauge with pre-set labels."""
375
+
376
+ def __init__(self, gauge: Gauge, labels: dict[str, str]) -> None:
377
+ self._gauge = gauge
378
+ self._labels = labels
379
+
380
+ def set(self, value: float) -> None:
381
+ """Set gauge value."""
382
+ self._gauge.set(value, **self._labels)
383
+
384
+ def inc(self, value: float = 1.0) -> None:
385
+ """Increment gauge."""
386
+ self._gauge.inc(value, **self._labels)
387
+
388
+ def dec(self, value: float = 1.0) -> None:
389
+ """Decrement gauge."""
390
+ self._gauge.dec(value, **self._labels)
391
+
392
+
393
+ # =============================================================================
394
+ # Histogram
395
+ # =============================================================================
396
+
397
+
398
+ class Histogram(Metric):
399
+ """Distribution of values with configurable buckets.
400
+
401
+ Use for: request latency, response sizes.
402
+
403
+ Example:
404
+ >>> latency = Histogram(
405
+ ... "request_duration_seconds",
406
+ ... "Request latency",
407
+ ... buckets=[0.01, 0.05, 0.1, 0.5, 1.0, 5.0],
408
+ ... )
409
+ >>> latency.observe(0.42)
410
+ >>> with latency.time():
411
+ ... process_request()
412
+ """
413
+
414
+ DEFAULT_BUCKETS = (
415
+ 0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5,
416
+ 0.75, 1.0, 2.5, 5.0, 7.5, 10.0, float("inf"),
417
+ )
418
+
419
+ def __init__(
420
+ self,
421
+ name: str,
422
+ description: str = "",
423
+ *,
424
+ buckets: tuple[float, ...] | list[float] | None = None,
425
+ labels: tuple[str, ...] | list[str] = (),
426
+ **kwargs: Any,
427
+ ) -> None:
428
+ """Initialize histogram.
429
+
430
+ Args:
431
+ name: Histogram name.
432
+ description: Human-readable description.
433
+ buckets: Upper bounds for buckets.
434
+ labels: Label names.
435
+ **kwargs: Additional arguments.
436
+ """
437
+ super().__init__(name, description, label_names=labels, **kwargs)
438
+ self._buckets = tuple(sorted(buckets or self.DEFAULT_BUCKETS))
439
+
440
+ # Ensure +Inf is included
441
+ if self._buckets[-1] != float("inf"):
442
+ self._buckets = (*self._buckets, float("inf"))
443
+
444
+ # Storage: key -> (bucket_counts, sum, count)
445
+ self._data: dict[MetricKey, tuple[list[int], float, int]] = {}
446
+
447
+ @property
448
+ def type(self) -> MetricType:
449
+ return MetricType.HISTOGRAM
450
+
451
+ @property
452
+ def buckets(self) -> tuple[float, ...]:
453
+ """Get bucket boundaries."""
454
+ return self._buckets
455
+
456
+ def _get_data(self, key: MetricKey) -> tuple[list[int], float, int]:
457
+ """Get or create data for a key."""
458
+ if key not in self._data:
459
+ self._data[key] = ([0] * len(self._buckets), 0.0, 0)
460
+ return self._data[key]
461
+
462
+ def observe(self, value: float, **labels: str) -> None:
463
+ """Observe a value.
464
+
465
+ Args:
466
+ value: Observed value.
467
+ **labels: Label values.
468
+ """
469
+ if self._label_names:
470
+ self._validate_labels(labels)
471
+
472
+ key = MetricKey.create(self._name, labels)
473
+
474
+ with self._lock:
475
+ bucket_counts, total_sum, count = self._get_data(key)
476
+
477
+ # Find the bucket this value belongs to (first bucket where value <= bound)
478
+ # Only increment that one bucket - cumulative is computed in collect()
479
+ for i, bound in enumerate(self._buckets):
480
+ if value <= bound:
481
+ bucket_counts[i] += 1
482
+ break
483
+
484
+ # Update sum and count
485
+ self._data[key] = (bucket_counts, total_sum + value, count + 1)
486
+
487
+ @contextmanager
488
+ def time(self, **labels: str) -> Iterator[None]:
489
+ """Context manager to measure duration.
490
+
491
+ Args:
492
+ **labels: Label values.
493
+ """
494
+ start = time.perf_counter()
495
+ try:
496
+ yield
497
+ finally:
498
+ duration = time.perf_counter() - start
499
+ self.observe(duration, **labels)
500
+
501
+ def labels(self, **labels: str) -> "LabeledHistogram":
502
+ """Get histogram with specific labels."""
503
+ return LabeledHistogram(self, labels)
504
+
505
+ def collect(self) -> list[tuple[dict[str, str], dict[str, Any]]]:
506
+ """Collect histogram data.
507
+
508
+ Returns list of (labels, data) where data contains:
509
+ - buckets: dict mapping bound to cumulative count
510
+ - sum: total of all observed values
511
+ - count: number of observations
512
+ """
513
+ with self._lock:
514
+ results = []
515
+ for key, (bucket_counts, total_sum, count) in self._data.items():
516
+ # Calculate cumulative counts
517
+ cumulative = []
518
+ running = 0
519
+ for c in bucket_counts:
520
+ running += c
521
+ cumulative.append(running)
522
+
523
+ data = {
524
+ "buckets": {
525
+ str(bound): cum
526
+ for bound, cum in zip(self._buckets, cumulative)
527
+ },
528
+ "sum": total_sum,
529
+ "count": count,
530
+ }
531
+ results.append((dict(key.labels), data))
532
+ return results
533
+
534
+
535
+ class LabeledHistogram:
536
+ """Histogram with pre-set labels."""
537
+
538
+ def __init__(self, histogram: Histogram, labels: dict[str, str]) -> None:
539
+ self._histogram = histogram
540
+ self._labels = labels
541
+
542
+ def observe(self, value: float) -> None:
543
+ """Observe a value."""
544
+ self._histogram.observe(value, **self._labels)
545
+
546
+ @contextmanager
547
+ def time(self) -> Iterator[None]:
548
+ """Time a block of code."""
549
+ with self._histogram.time(**self._labels):
550
+ yield
551
+
552
+
553
+ # =============================================================================
554
+ # Summary
555
+ # =============================================================================
556
+
557
+
558
+ class Summary(Metric):
559
+ """Summary with streaming quantiles.
560
+
561
+ Similar to Histogram but calculates quantiles on the client side.
562
+ Use when you need specific percentiles (p50, p90, p99).
563
+
564
+ Note: This is a simplified implementation that keeps recent samples.
565
+ For production use, consider a proper streaming quantile algorithm.
566
+ """
567
+
568
+ def __init__(
569
+ self,
570
+ name: str,
571
+ description: str = "",
572
+ *,
573
+ quantiles: tuple[float, ...] = (0.5, 0.9, 0.99),
574
+ max_samples: int = 1000,
575
+ labels: tuple[str, ...] | list[str] = (),
576
+ **kwargs: Any,
577
+ ) -> None:
578
+ """Initialize summary.
579
+
580
+ Args:
581
+ name: Summary name.
582
+ description: Human-readable description.
583
+ quantiles: Quantiles to calculate (0.0-1.0).
584
+ max_samples: Maximum samples to keep.
585
+ labels: Label names.
586
+ **kwargs: Additional arguments.
587
+ """
588
+ super().__init__(name, description, label_names=labels, **kwargs)
589
+ self._quantiles = quantiles
590
+ self._max_samples = max_samples
591
+ self._data: dict[MetricKey, tuple[list[float], float, int]] = {}
592
+
593
+ @property
594
+ def type(self) -> MetricType:
595
+ return MetricType.SUMMARY
596
+
597
+ def observe(self, value: float, **labels: str) -> None:
598
+ """Observe a value.
599
+
600
+ Args:
601
+ value: Observed value.
602
+ **labels: Label values.
603
+ """
604
+ if self._label_names:
605
+ self._validate_labels(labels)
606
+
607
+ key = MetricKey.create(self._name, labels)
608
+
609
+ with self._lock:
610
+ if key not in self._data:
611
+ self._data[key] = ([], 0.0, 0)
612
+
613
+ samples, total_sum, count = self._data[key]
614
+
615
+ # Add sample (circular buffer behavior)
616
+ if len(samples) >= self._max_samples:
617
+ samples.pop(0)
618
+ samples.append(value)
619
+
620
+ self._data[key] = (samples, total_sum + value, count + 1)
621
+
622
+ @contextmanager
623
+ def time(self, **labels: str) -> Iterator[None]:
624
+ """Time a block of code."""
625
+ start = time.perf_counter()
626
+ try:
627
+ yield
628
+ finally:
629
+ self.observe(time.perf_counter() - start, **labels)
630
+
631
+ def labels(self, **labels: str) -> "LabeledSummary":
632
+ """Get summary with specific labels."""
633
+ return LabeledSummary(self, labels)
634
+
635
+ def collect(self) -> list[tuple[dict[str, str], dict[str, Any]]]:
636
+ """Collect summary data with quantiles."""
637
+ with self._lock:
638
+ results = []
639
+ for key, (samples, total_sum, count) in self._data.items():
640
+ quantile_values = {}
641
+ if samples:
642
+ sorted_samples = sorted(samples)
643
+ n = len(sorted_samples)
644
+ for q in self._quantiles:
645
+ idx = int(q * (n - 1))
646
+ quantile_values[f"p{int(q*100)}"] = sorted_samples[idx]
647
+
648
+ data = {
649
+ "quantiles": quantile_values,
650
+ "sum": total_sum,
651
+ "count": count,
652
+ }
653
+ results.append((dict(key.labels), data))
654
+ return results
655
+
656
+
657
+ class LabeledSummary:
658
+ """Summary with pre-set labels."""
659
+
660
+ def __init__(self, summary: Summary, labels: dict[str, str]) -> None:
661
+ self._summary = summary
662
+ self._labels = labels
663
+
664
+ def observe(self, value: float) -> None:
665
+ """Observe a value."""
666
+ self._summary.observe(value, **self._labels)
667
+
668
+
669
+ # =============================================================================
670
+ # Metrics Registry
671
+ # =============================================================================
672
+
673
+
674
+ class MetricsRegistry:
675
+ """Central registry for all metrics.
676
+
677
+ Ensures unique metric names and provides collection.
678
+ """
679
+
680
+ def __init__(self) -> None:
681
+ """Initialize registry."""
682
+ self._metrics: dict[str, Metric] = {}
683
+ self._lock = threading.Lock()
684
+
685
+ def register(self, metric: Metric) -> Metric:
686
+ """Register a metric.
687
+
688
+ Args:
689
+ metric: Metric to register.
690
+
691
+ Returns:
692
+ The registered metric.
693
+
694
+ Raises:
695
+ ValueError: If metric name already registered with different type.
696
+ """
697
+ with self._lock:
698
+ if metric.name in self._metrics:
699
+ existing = self._metrics[metric.name]
700
+ if existing.type != metric.type:
701
+ raise ValueError(
702
+ f"Metric '{metric.name}' already registered "
703
+ f"as {existing.type.value}"
704
+ )
705
+ return existing
706
+ self._metrics[metric.name] = metric
707
+ return metric
708
+
709
+ def get(self, name: str) -> Metric | None:
710
+ """Get a registered metric by name."""
711
+ return self._metrics.get(name)
712
+
713
+ def unregister(self, name: str) -> bool:
714
+ """Unregister a metric.
715
+
716
+ Args:
717
+ name: Metric name.
718
+
719
+ Returns:
720
+ True if unregistered, False if not found.
721
+ """
722
+ with self._lock:
723
+ if name in self._metrics:
724
+ del self._metrics[name]
725
+ return True
726
+ return False
727
+
728
+ def collect_all(self) -> dict[str, Any]:
729
+ """Collect all metrics.
730
+
731
+ Returns:
732
+ Dictionary mapping metric names to their collected data.
733
+ """
734
+ with self._lock:
735
+ result = {}
736
+ for name, metric in self._metrics.items():
737
+ result[name] = {
738
+ "type": metric.type.value,
739
+ "description": metric.description,
740
+ "data": metric.collect(),
741
+ }
742
+ return result
743
+
744
+ def clear(self) -> None:
745
+ """Clear all registered metrics."""
746
+ with self._lock:
747
+ self._metrics.clear()
748
+
749
+
750
+ # =============================================================================
751
+ # Metrics Collector
752
+ # =============================================================================
753
+
754
+
755
+ class MetricsCollector:
756
+ """High-level interface for creating and managing metrics.
757
+
758
+ MetricsCollector provides a fluent API for creating metrics and
759
+ manages their registration in the registry.
760
+
761
+ Example:
762
+ >>> collector = MetricsCollector()
763
+ >>> requests = collector.counter(
764
+ ... "http_requests_total",
765
+ ... "Total HTTP requests",
766
+ ... labels=["method", "status"],
767
+ ... )
768
+ >>> latency = collector.histogram(
769
+ ... "request_duration_seconds",
770
+ ... "Request latency",
771
+ ... )
772
+ """
773
+
774
+ def __init__(self, registry: MetricsRegistry | None = None) -> None:
775
+ """Initialize collector.
776
+
777
+ Args:
778
+ registry: Metrics registry (creates new if None).
779
+ """
780
+ self._registry = registry or MetricsRegistry()
781
+
782
+ @property
783
+ def registry(self) -> MetricsRegistry:
784
+ """Get the metrics registry."""
785
+ return self._registry
786
+
787
+ def counter(
788
+ self,
789
+ name: str,
790
+ description: str = "",
791
+ *,
792
+ labels: list[str] | tuple[str, ...] = (),
793
+ **kwargs: Any,
794
+ ) -> Counter:
795
+ """Create or get a counter.
796
+
797
+ Args:
798
+ name: Counter name.
799
+ description: Human-readable description.
800
+ labels: Label names.
801
+ **kwargs: Additional arguments.
802
+
803
+ Returns:
804
+ Counter instance.
805
+ """
806
+ counter = Counter(name, description, labels=labels, **kwargs)
807
+ return self._registry.register(counter) # type: ignore
808
+
809
+ def gauge(
810
+ self,
811
+ name: str,
812
+ description: str = "",
813
+ *,
814
+ labels: list[str] | tuple[str, ...] = (),
815
+ **kwargs: Any,
816
+ ) -> Gauge:
817
+ """Create or get a gauge.
818
+
819
+ Args:
820
+ name: Gauge name.
821
+ description: Human-readable description.
822
+ labels: Label names.
823
+ **kwargs: Additional arguments.
824
+
825
+ Returns:
826
+ Gauge instance.
827
+ """
828
+ gauge = Gauge(name, description, labels=labels, **kwargs)
829
+ return self._registry.register(gauge) # type: ignore
830
+
831
+ def histogram(
832
+ self,
833
+ name: str,
834
+ description: str = "",
835
+ *,
836
+ buckets: list[float] | tuple[float, ...] | None = None,
837
+ labels: list[str] | tuple[str, ...] = (),
838
+ **kwargs: Any,
839
+ ) -> Histogram:
840
+ """Create or get a histogram.
841
+
842
+ Args:
843
+ name: Histogram name.
844
+ description: Human-readable description.
845
+ buckets: Bucket boundaries.
846
+ labels: Label names.
847
+ **kwargs: Additional arguments.
848
+
849
+ Returns:
850
+ Histogram instance.
851
+ """
852
+ histogram = Histogram(
853
+ name, description, buckets=buckets, labels=labels, **kwargs
854
+ )
855
+ return self._registry.register(histogram) # type: ignore
856
+
857
+ def summary(
858
+ self,
859
+ name: str,
860
+ description: str = "",
861
+ *,
862
+ quantiles: tuple[float, ...] = (0.5, 0.9, 0.99),
863
+ labels: list[str] | tuple[str, ...] = (),
864
+ **kwargs: Any,
865
+ ) -> Summary:
866
+ """Create or get a summary.
867
+
868
+ Args:
869
+ name: Summary name.
870
+ description: Human-readable description.
871
+ quantiles: Quantiles to calculate.
872
+ labels: Label names.
873
+ **kwargs: Additional arguments.
874
+
875
+ Returns:
876
+ Summary instance.
877
+ """
878
+ summary = Summary(
879
+ name, description, quantiles=quantiles, labels=labels, **kwargs
880
+ )
881
+ return self._registry.register(summary) # type: ignore
882
+
883
+
884
+ # =============================================================================
885
+ # Metrics Exporters
886
+ # =============================================================================
887
+
888
+
889
+ class MetricsExporter(ABC):
890
+ """Abstract base class for metrics exporters."""
891
+
892
+ @abstractmethod
893
+ def export(self, registry: MetricsRegistry) -> str:
894
+ """Export metrics from registry.
895
+
896
+ Args:
897
+ registry: Metrics registry to export.
898
+
899
+ Returns:
900
+ Exported metrics as string.
901
+ """
902
+ pass
903
+
904
+ @abstractmethod
905
+ def push(self, registry: MetricsRegistry, endpoint: str) -> bool:
906
+ """Push metrics to remote endpoint.
907
+
908
+ Args:
909
+ registry: Metrics registry to push.
910
+ endpoint: Remote endpoint URL.
911
+
912
+ Returns:
913
+ True if successful.
914
+ """
915
+ pass
916
+
917
+
918
+ class PrometheusExporter(MetricsExporter):
919
+ """Export metrics in Prometheus text format.
920
+
921
+ Generates output compatible with Prometheus text exposition format.
922
+
923
+ Example output:
924
+ # HELP http_requests_total Total HTTP requests
925
+ # TYPE http_requests_total counter
926
+ http_requests_total{method="GET",status="200"} 1234
927
+ """
928
+
929
+ def export(self, registry: MetricsRegistry) -> str:
930
+ """Export metrics in Prometheus format."""
931
+ lines = []
932
+ data = registry.collect_all()
933
+
934
+ for name, info in data.items():
935
+ metric_type = info["type"]
936
+ description = info["description"]
937
+ metric_data = info["data"]
938
+
939
+ # HELP and TYPE lines
940
+ lines.append(f"# HELP {name} {description}")
941
+ lines.append(f"# TYPE {name} {metric_type}")
942
+
943
+ # Metric lines
944
+ for labels, value in metric_data:
945
+ label_str = self._format_labels(labels)
946
+
947
+ if metric_type == "histogram":
948
+ # Histogram has special format
949
+ for bound, count in value["buckets"].items():
950
+ bound_label = f'{label_str},le="{bound}"' if label_str else f'le="{bound}"'
951
+ lines.append(f"{name}_bucket{{{bound_label}}} {count}")
952
+ sum_labels = f"{{{label_str}}}" if label_str else ""
953
+ lines.append(f"{name}_sum{sum_labels} {value['sum']}")
954
+ lines.append(f"{name}_count{sum_labels} {value['count']}")
955
+
956
+ elif metric_type == "summary":
957
+ for quantile_name, quantile_value in value.get("quantiles", {}).items():
958
+ q = quantile_name[1:] # Remove 'p' prefix
959
+ q_label = f'{label_str},quantile="{int(q)/100}"' if label_str else f'quantile="{int(q)/100}"'
960
+ lines.append(f"{name}{{{q_label}}} {quantile_value}")
961
+ sum_labels = f"{{{label_str}}}" if label_str else ""
962
+ lines.append(f"{name}_sum{sum_labels} {value['sum']}")
963
+ lines.append(f"{name}_count{sum_labels} {value['count']}")
964
+
965
+ else:
966
+ # Counter and Gauge
967
+ if label_str:
968
+ lines.append(f"{name}{{{label_str}}} {value}")
969
+ else:
970
+ lines.append(f"{name} {value}")
971
+
972
+ lines.append("") # Empty line between metrics
973
+
974
+ return "\n".join(lines)
975
+
976
+ def _format_labels(self, labels: dict[str, str]) -> str:
977
+ """Format labels for Prometheus."""
978
+ if not labels:
979
+ return ""
980
+ parts = [f'{k}="{v}"' for k, v in sorted(labels.items())]
981
+ return ",".join(parts)
982
+
983
+ def push(self, registry: MetricsRegistry, endpoint: str) -> bool:
984
+ """Push metrics to Prometheus Pushgateway."""
985
+ import urllib.request
986
+ import urllib.error
987
+
988
+ data = self.export(registry).encode("utf-8")
989
+
990
+ try:
991
+ request = urllib.request.Request(
992
+ endpoint,
993
+ data=data,
994
+ method="POST",
995
+ headers={"Content-Type": "text/plain"},
996
+ )
997
+ with urllib.request.urlopen(request, timeout=30):
998
+ return True
999
+ except urllib.error.URLError:
1000
+ return False
1001
+
1002
+
1003
+ class StatsDExporter(MetricsExporter):
1004
+ """Export metrics in StatsD format.
1005
+
1006
+ Sends metrics via UDP to a StatsD server.
1007
+ """
1008
+
1009
+ def __init__(
1010
+ self,
1011
+ host: str = "localhost",
1012
+ port: int = 8125,
1013
+ prefix: str = "",
1014
+ ) -> None:
1015
+ """Initialize StatsD exporter.
1016
+
1017
+ Args:
1018
+ host: StatsD server host.
1019
+ port: StatsD server port.
1020
+ prefix: Metric name prefix.
1021
+ """
1022
+ self._host = host
1023
+ self._port = port
1024
+ self._prefix = prefix
1025
+
1026
+ def export(self, registry: MetricsRegistry) -> str:
1027
+ """Export metrics in StatsD format."""
1028
+ lines = []
1029
+ data = registry.collect_all()
1030
+
1031
+ for name, info in data.items():
1032
+ metric_type = info["type"]
1033
+ metric_data = info["data"]
1034
+
1035
+ full_name = f"{self._prefix}{name}" if self._prefix else name
1036
+
1037
+ for labels, value in metric_data:
1038
+ label_suffix = self._format_labels(labels)
1039
+ metric_name = f"{full_name}{label_suffix}"
1040
+
1041
+ if metric_type == "counter":
1042
+ lines.append(f"{metric_name}:{value}|c")
1043
+ elif metric_type == "gauge":
1044
+ lines.append(f"{metric_name}:{value}|g")
1045
+ elif metric_type == "histogram":
1046
+ # Send as timing
1047
+ lines.append(f"{metric_name}:{value['sum']/max(value['count'], 1)*1000}|ms")
1048
+ elif metric_type == "summary":
1049
+ lines.append(f"{metric_name}:{value['sum']/max(value['count'], 1)*1000}|ms")
1050
+
1051
+ return "\n".join(lines)
1052
+
1053
+ def _format_labels(self, labels: dict[str, str]) -> str:
1054
+ """Format labels as suffix."""
1055
+ if not labels:
1056
+ return ""
1057
+ parts = [f".{k}_{v}" for k, v in sorted(labels.items())]
1058
+ return "".join(parts)
1059
+
1060
+ def push(self, registry: MetricsRegistry, endpoint: str | None = None) -> bool:
1061
+ """Send metrics to StatsD server via UDP."""
1062
+ import socket
1063
+
1064
+ data = self.export(registry)
1065
+
1066
+ try:
1067
+ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
1068
+ sock.sendto(data.encode("utf-8"), (self._host, self._port))
1069
+ sock.close()
1070
+ return True
1071
+ except socket.error:
1072
+ return False
1073
+
1074
+
1075
+ class InMemoryExporter(MetricsExporter):
1076
+ """In-memory exporter for testing.
1077
+
1078
+ Stores exported metrics for inspection.
1079
+ """
1080
+
1081
+ def __init__(self) -> None:
1082
+ """Initialize in-memory exporter."""
1083
+ self._exports: list[dict[str, Any]] = []
1084
+
1085
+ @property
1086
+ def exports(self) -> list[dict[str, Any]]:
1087
+ """Get all exports."""
1088
+ return self._exports
1089
+
1090
+ def export(self, registry: MetricsRegistry) -> str:
1091
+ """Export metrics to memory."""
1092
+ import json
1093
+ data = registry.collect_all()
1094
+ self._exports.append(data)
1095
+ return json.dumps(data, default=str)
1096
+
1097
+ def push(self, registry: MetricsRegistry, endpoint: str) -> bool:
1098
+ """Push to memory (always succeeds)."""
1099
+ self.export(registry)
1100
+ return True
1101
+
1102
+ def clear(self) -> None:
1103
+ """Clear stored exports."""
1104
+ self._exports.clear()
1105
+
1106
+
1107
+ # =============================================================================
1108
+ # Global Metrics
1109
+ # =============================================================================
1110
+
1111
+ _global_collector: MetricsCollector | None = None
1112
+ _lock = threading.Lock()
1113
+
1114
+
1115
+ def get_metrics() -> MetricsCollector:
1116
+ """Get the global metrics collector.
1117
+
1118
+ Returns:
1119
+ Global MetricsCollector instance.
1120
+ """
1121
+ global _global_collector
1122
+
1123
+ with _lock:
1124
+ if _global_collector is None:
1125
+ _global_collector = MetricsCollector()
1126
+ return _global_collector
1127
+
1128
+
1129
+ def set_metrics(collector: MetricsCollector) -> None:
1130
+ """Set the global metrics collector.
1131
+
1132
+ Args:
1133
+ collector: MetricsCollector to use globally.
1134
+ """
1135
+ global _global_collector
1136
+
1137
+ with _lock:
1138
+ _global_collector = collector
1139
+
1140
+
1141
+ def configure_metrics(
1142
+ *,
1143
+ exporter: MetricsExporter | None = None,
1144
+ registry: MetricsRegistry | None = None,
1145
+ ) -> MetricsCollector:
1146
+ """Configure global metrics.
1147
+
1148
+ Args:
1149
+ exporter: Exporter to use.
1150
+ registry: Registry to use.
1151
+
1152
+ Returns:
1153
+ Configured MetricsCollector.
1154
+ """
1155
+ collector = MetricsCollector(registry=registry)
1156
+ set_metrics(collector)
1157
+ return collector