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,1184 @@
1
+ """Observability module with OpenTelemetry integration for profiler metrics.
2
+
3
+ This module provides comprehensive observability for the profiling system:
4
+ - OpenTelemetry tracing for operation tracking
5
+ - Metrics collection for performance monitoring
6
+ - Structured logging integration
7
+ - Custom metric exporters
8
+
9
+ Key features:
10
+ - Pluggable exporter architecture
11
+ - Automatic span creation for profiling operations
12
+ - Histogram and counter metrics
13
+ - Context propagation
14
+
15
+ Example:
16
+ from truthound.profiler.observability import (
17
+ ProfilerTelemetry,
18
+ MetricsCollector,
19
+ traced,
20
+ )
21
+
22
+ # Initialize telemetry
23
+ telemetry = ProfilerTelemetry(service_name="truthound-profiler")
24
+
25
+ # Use decorator for automatic tracing
26
+ @traced("profile_column")
27
+ def profile_column(col: str) -> ColumnProfile:
28
+ # profiling logic...
29
+ pass
30
+
31
+ # Or manual tracing
32
+ with telemetry.span("custom_operation") as span:
33
+ span.set_attribute("column_count", 10)
34
+ # operation...
35
+ """
36
+
37
+ from __future__ import annotations
38
+
39
+ import logging
40
+ import threading
41
+ import time
42
+ from abc import ABC, abstractmethod
43
+ from collections import defaultdict
44
+ from contextlib import contextmanager
45
+ from dataclasses import dataclass, field
46
+ from datetime import datetime, timedelta
47
+ from enum import Enum
48
+ from functools import wraps
49
+ from typing import (
50
+ TYPE_CHECKING,
51
+ Any,
52
+ Callable,
53
+ ContextManager,
54
+ Generator,
55
+ Generic,
56
+ Protocol,
57
+ TypeVar,
58
+ )
59
+
60
+ # =============================================================================
61
+ # Types
62
+ # =============================================================================
63
+
64
+ T = TypeVar("T")
65
+ F = TypeVar("F", bound=Callable[..., Any])
66
+
67
+
68
+ class MetricType(str, Enum):
69
+ """Types of metrics."""
70
+
71
+ COUNTER = "counter"
72
+ GAUGE = "gauge"
73
+ HISTOGRAM = "histogram"
74
+ SUMMARY = "summary"
75
+
76
+
77
+ class SpanStatus(str, Enum):
78
+ """Span status codes."""
79
+
80
+ OK = "ok"
81
+ ERROR = "error"
82
+ UNSET = "unset"
83
+
84
+
85
+ # =============================================================================
86
+ # Span Protocol
87
+ # =============================================================================
88
+
89
+
90
+ class SpanProtocol(Protocol):
91
+ """Protocol for span interface (compatible with OpenTelemetry)."""
92
+
93
+ def set_attribute(self, key: str, value: Any) -> None:
94
+ """Set span attribute."""
95
+ ...
96
+
97
+ def set_status(self, status: SpanStatus, description: str = "") -> None:
98
+ """Set span status."""
99
+ ...
100
+
101
+ def add_event(self, name: str, attributes: dict[str, Any] | None = None) -> None:
102
+ """Add event to span."""
103
+ ...
104
+
105
+ def record_exception(self, exception: BaseException) -> None:
106
+ """Record exception in span."""
107
+ ...
108
+
109
+ def end(self) -> None:
110
+ """End the span."""
111
+ ...
112
+
113
+
114
+ # =============================================================================
115
+ # Span Implementation
116
+ # =============================================================================
117
+
118
+
119
+ @dataclass
120
+ class SpanEvent:
121
+ """Event recorded in a span."""
122
+
123
+ name: str
124
+ timestamp: datetime
125
+ attributes: dict[str, Any] = field(default_factory=dict)
126
+
127
+
128
+ @dataclass
129
+ class Span:
130
+ """Lightweight span implementation.
131
+
132
+ Compatible with OpenTelemetry Span interface but can work standalone.
133
+ """
134
+
135
+ name: str
136
+ trace_id: str
137
+ span_id: str
138
+ parent_id: str | None = None
139
+ start_time: datetime = field(default_factory=datetime.now)
140
+ end_time: datetime | None = None
141
+ status: SpanStatus = SpanStatus.UNSET
142
+ status_description: str = ""
143
+ attributes: dict[str, Any] = field(default_factory=dict)
144
+ events: list[SpanEvent] = field(default_factory=list)
145
+ exception: BaseException | None = None
146
+
147
+ def set_attribute(self, key: str, value: Any) -> None:
148
+ """Set span attribute."""
149
+ self.attributes[key] = value
150
+
151
+ def set_status(self, status: SpanStatus, description: str = "") -> None:
152
+ """Set span status."""
153
+ self.status = status
154
+ self.status_description = description
155
+
156
+ def add_event(self, name: str, attributes: dict[str, Any] | None = None) -> None:
157
+ """Add event to span."""
158
+ self.events.append(
159
+ SpanEvent(
160
+ name=name,
161
+ timestamp=datetime.now(),
162
+ attributes=attributes or {},
163
+ )
164
+ )
165
+
166
+ def record_exception(self, exception: BaseException) -> None:
167
+ """Record exception in span."""
168
+ self.exception = exception
169
+ self.set_status(SpanStatus.ERROR, str(exception))
170
+ self.add_event(
171
+ "exception",
172
+ {
173
+ "exception.type": type(exception).__name__,
174
+ "exception.message": str(exception),
175
+ },
176
+ )
177
+
178
+ def end(self) -> None:
179
+ """End the span."""
180
+ self.end_time = datetime.now()
181
+
182
+ @property
183
+ def duration_ms(self) -> float:
184
+ """Get span duration in milliseconds."""
185
+ if self.end_time is None:
186
+ return (datetime.now() - self.start_time).total_seconds() * 1000
187
+ return (self.end_time - self.start_time).total_seconds() * 1000
188
+
189
+ def to_dict(self) -> dict[str, Any]:
190
+ """Convert to dictionary."""
191
+ return {
192
+ "name": self.name,
193
+ "trace_id": self.trace_id,
194
+ "span_id": self.span_id,
195
+ "parent_id": self.parent_id,
196
+ "start_time": self.start_time.isoformat(),
197
+ "end_time": self.end_time.isoformat() if self.end_time else None,
198
+ "duration_ms": self.duration_ms,
199
+ "status": self.status.value,
200
+ "status_description": self.status_description,
201
+ "attributes": self.attributes,
202
+ "events": [
203
+ {
204
+ "name": e.name,
205
+ "timestamp": e.timestamp.isoformat(),
206
+ "attributes": e.attributes,
207
+ }
208
+ for e in self.events
209
+ ],
210
+ }
211
+
212
+
213
+ # =============================================================================
214
+ # Metrics
215
+ # =============================================================================
216
+
217
+
218
+ @dataclass
219
+ class MetricValue:
220
+ """Single metric measurement."""
221
+
222
+ value: float
223
+ timestamp: datetime = field(default_factory=datetime.now)
224
+ labels: dict[str, str] = field(default_factory=dict)
225
+
226
+
227
+ class Metric(ABC):
228
+ """Abstract base class for metrics."""
229
+
230
+ def __init__(
231
+ self,
232
+ name: str,
233
+ description: str = "",
234
+ unit: str = "",
235
+ labels: list[str] | None = None,
236
+ ):
237
+ self.name = name
238
+ self.description = description
239
+ self.unit = unit
240
+ self.label_names = labels or []
241
+ self._lock = threading.Lock()
242
+
243
+ @property
244
+ @abstractmethod
245
+ def metric_type(self) -> MetricType:
246
+ """Get metric type."""
247
+ pass
248
+
249
+ @abstractmethod
250
+ def collect(self) -> list[MetricValue]:
251
+ """Collect current metric values."""
252
+ pass
253
+
254
+
255
+ class Counter(Metric):
256
+ """Monotonically increasing counter metric."""
257
+
258
+ def __init__(
259
+ self,
260
+ name: str,
261
+ description: str = "",
262
+ labels: list[str] | None = None,
263
+ ):
264
+ super().__init__(name, description, labels=labels)
265
+ self._values: dict[tuple[str, ...], float] = defaultdict(float)
266
+
267
+ @property
268
+ def metric_type(self) -> MetricType:
269
+ return MetricType.COUNTER
270
+
271
+ def inc(self, value: float = 1.0, **labels: str) -> None:
272
+ """Increment counter."""
273
+ key = self._label_key(labels)
274
+ with self._lock:
275
+ self._values[key] += value
276
+
277
+ def _label_key(self, labels: dict[str, str]) -> tuple[str, ...]:
278
+ """Create label key tuple."""
279
+ return tuple(labels.get(name, "") for name in self.label_names)
280
+
281
+ def collect(self) -> list[MetricValue]:
282
+ """Collect current values."""
283
+ with self._lock:
284
+ return [
285
+ MetricValue(
286
+ value=value,
287
+ labels=dict(zip(self.label_names, key)),
288
+ )
289
+ for key, value in self._values.items()
290
+ ]
291
+
292
+
293
+ class Gauge(Metric):
294
+ """Gauge metric that can go up and down."""
295
+
296
+ def __init__(
297
+ self,
298
+ name: str,
299
+ description: str = "",
300
+ labels: list[str] | None = None,
301
+ ):
302
+ super().__init__(name, description, labels=labels)
303
+ self._values: dict[tuple[str, ...], float] = {}
304
+
305
+ @property
306
+ def metric_type(self) -> MetricType:
307
+ return MetricType.GAUGE
308
+
309
+ def set(self, value: float, **labels: str) -> None:
310
+ """Set gauge value."""
311
+ key = self._label_key(labels)
312
+ with self._lock:
313
+ self._values[key] = value
314
+
315
+ def inc(self, value: float = 1.0, **labels: str) -> None:
316
+ """Increment gauge."""
317
+ key = self._label_key(labels)
318
+ with self._lock:
319
+ self._values[key] = self._values.get(key, 0.0) + value
320
+
321
+ def dec(self, value: float = 1.0, **labels: str) -> None:
322
+ """Decrement gauge."""
323
+ self.inc(-value, **labels)
324
+
325
+ def _label_key(self, labels: dict[str, str]) -> tuple[str, ...]:
326
+ """Create label key tuple."""
327
+ return tuple(labels.get(name, "") for name in self.label_names)
328
+
329
+ def collect(self) -> list[MetricValue]:
330
+ """Collect current values."""
331
+ with self._lock:
332
+ return [
333
+ MetricValue(
334
+ value=value,
335
+ labels=dict(zip(self.label_names, key)),
336
+ )
337
+ for key, value in self._values.items()
338
+ ]
339
+
340
+
341
+ class Histogram(Metric):
342
+ """Histogram metric for distributions."""
343
+
344
+ DEFAULT_BUCKETS = (
345
+ 0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0,
346
+ )
347
+
348
+ def __init__(
349
+ self,
350
+ name: str,
351
+ description: str = "",
352
+ labels: list[str] | None = None,
353
+ buckets: tuple[float, ...] | None = None,
354
+ ):
355
+ super().__init__(name, description, labels=labels)
356
+ self.buckets = buckets or self.DEFAULT_BUCKETS
357
+ self._observations: dict[tuple[str, ...], list[float]] = defaultdict(list)
358
+
359
+ @property
360
+ def metric_type(self) -> MetricType:
361
+ return MetricType.HISTOGRAM
362
+
363
+ def observe(self, value: float, **labels: str) -> None:
364
+ """Record an observation."""
365
+ key = self._label_key(labels)
366
+ with self._lock:
367
+ self._observations[key].append(value)
368
+
369
+ @contextmanager
370
+ def time(self, **labels: str) -> Generator[None, None, None]:
371
+ """Context manager to time operations."""
372
+ start = time.perf_counter()
373
+ try:
374
+ yield
375
+ finally:
376
+ duration = time.perf_counter() - start
377
+ self.observe(duration, **labels)
378
+
379
+ def _label_key(self, labels: dict[str, str]) -> tuple[str, ...]:
380
+ """Create label key tuple."""
381
+ return tuple(labels.get(name, "") for name in self.label_names)
382
+
383
+ def collect(self) -> list[MetricValue]:
384
+ """Collect histogram statistics."""
385
+ results = []
386
+ with self._lock:
387
+ for key, observations in self._observations.items():
388
+ if not observations:
389
+ continue
390
+
391
+ labels = dict(zip(self.label_names, key))
392
+
393
+ # Count and sum
394
+ results.append(MetricValue(
395
+ value=len(observations),
396
+ labels={**labels, "stat": "count"},
397
+ ))
398
+ results.append(MetricValue(
399
+ value=sum(observations),
400
+ labels={**labels, "stat": "sum"},
401
+ ))
402
+
403
+ # Bucket counts
404
+ for bucket in self.buckets:
405
+ count = sum(1 for v in observations if v <= bucket)
406
+ results.append(MetricValue(
407
+ value=count,
408
+ labels={**labels, "le": str(bucket)},
409
+ ))
410
+
411
+ return results
412
+
413
+ def get_percentile(self, percentile: float, **labels: str) -> float | None:
414
+ """Get percentile value for a label set."""
415
+ key = self._label_key(labels)
416
+ with self._lock:
417
+ observations = self._observations.get(key, [])
418
+ if not observations:
419
+ return None
420
+ sorted_obs = sorted(observations)
421
+ idx = int(len(sorted_obs) * percentile / 100)
422
+ return sorted_obs[min(idx, len(sorted_obs) - 1)]
423
+
424
+
425
+ # =============================================================================
426
+ # Metrics Collector
427
+ # =============================================================================
428
+
429
+
430
+ class MetricsCollector:
431
+ """Central collector for all profiler metrics.
432
+
433
+ Provides pre-defined metrics for common profiling operations
434
+ and allows custom metric registration.
435
+
436
+ Example:
437
+ collector = MetricsCollector()
438
+
439
+ # Record profile duration
440
+ with collector.profile_duration.time(column="user_id"):
441
+ profile_column(...)
442
+
443
+ # Increment counter
444
+ collector.profiles_total.inc(status="success")
445
+ """
446
+
447
+ def __init__(self, prefix: str = "truthound_profiler"):
448
+ self.prefix = prefix
449
+ self._metrics: dict[str, Metric] = {}
450
+ self._lock = threading.Lock()
451
+
452
+ # Register default metrics
453
+ self._register_default_metrics()
454
+
455
+ def _register_default_metrics(self) -> None:
456
+ """Register standard profiler metrics."""
457
+ # Counters
458
+ self.profiles_total = self.register_counter(
459
+ "profiles_total",
460
+ "Total number of profiles completed",
461
+ labels=["status", "type"],
462
+ )
463
+
464
+ self.columns_profiled = self.register_counter(
465
+ "columns_profiled",
466
+ "Total number of columns profiled",
467
+ labels=["data_type"],
468
+ )
469
+
470
+ self.rules_generated = self.register_counter(
471
+ "rules_generated",
472
+ "Total number of rules generated",
473
+ labels=["category", "generator"],
474
+ )
475
+
476
+ self.cache_operations = self.register_counter(
477
+ "cache_operations",
478
+ "Cache operations",
479
+ labels=["operation", "result"],
480
+ )
481
+
482
+ # Gauges
483
+ self.active_profiles = self.register_gauge(
484
+ "active_profiles",
485
+ "Number of profiles currently in progress",
486
+ )
487
+
488
+ self.cache_size = self.register_gauge(
489
+ "cache_size",
490
+ "Current cache size",
491
+ labels=["backend"],
492
+ )
493
+
494
+ # Histograms
495
+ self.profile_duration = self.register_histogram(
496
+ "profile_duration_seconds",
497
+ "Time spent profiling",
498
+ labels=["operation"],
499
+ buckets=(0.01, 0.05, 0.1, 0.5, 1.0, 5.0, 10.0, 30.0, 60.0),
500
+ )
501
+
502
+ self.column_profile_duration = self.register_histogram(
503
+ "column_profile_duration_seconds",
504
+ "Time spent profiling a column",
505
+ labels=["column_type"],
506
+ )
507
+
508
+ self.pattern_match_duration = self.register_histogram(
509
+ "pattern_match_duration_seconds",
510
+ "Time spent matching patterns",
511
+ labels=["pattern_name"],
512
+ )
513
+
514
+ self.rows_processed = self.register_histogram(
515
+ "rows_processed",
516
+ "Number of rows processed per profile",
517
+ buckets=(100, 1000, 10000, 100000, 1000000, 10000000),
518
+ )
519
+
520
+ def register_counter(
521
+ self,
522
+ name: str,
523
+ description: str = "",
524
+ labels: list[str] | None = None,
525
+ ) -> Counter:
526
+ """Register a counter metric."""
527
+ full_name = f"{self.prefix}_{name}"
528
+ metric = Counter(full_name, description, labels)
529
+ with self._lock:
530
+ self._metrics[full_name] = metric
531
+ return metric
532
+
533
+ def register_gauge(
534
+ self,
535
+ name: str,
536
+ description: str = "",
537
+ labels: list[str] | None = None,
538
+ ) -> Gauge:
539
+ """Register a gauge metric."""
540
+ full_name = f"{self.prefix}_{name}"
541
+ metric = Gauge(full_name, description, labels)
542
+ with self._lock:
543
+ self._metrics[full_name] = metric
544
+ return metric
545
+
546
+ def register_histogram(
547
+ self,
548
+ name: str,
549
+ description: str = "",
550
+ labels: list[str] | None = None,
551
+ buckets: tuple[float, ...] | None = None,
552
+ ) -> Histogram:
553
+ """Register a histogram metric."""
554
+ full_name = f"{self.prefix}_{name}"
555
+ metric = Histogram(full_name, description, labels, buckets)
556
+ with self._lock:
557
+ self._metrics[full_name] = metric
558
+ return metric
559
+
560
+ def get_metric(self, name: str) -> Metric | None:
561
+ """Get a registered metric by name."""
562
+ full_name = f"{self.prefix}_{name}"
563
+ return self._metrics.get(full_name)
564
+
565
+ def collect_all(self) -> dict[str, list[MetricValue]]:
566
+ """Collect all metric values."""
567
+ with self._lock:
568
+ return {
569
+ name: metric.collect()
570
+ for name, metric in self._metrics.items()
571
+ }
572
+
573
+ def to_prometheus(self) -> str:
574
+ """Export metrics in Prometheus text format."""
575
+ lines = []
576
+ for name, metric in self._metrics.items():
577
+ lines.append(f"# HELP {name} {metric.description}")
578
+ lines.append(f"# TYPE {name} {metric.metric_type.value}")
579
+
580
+ for value in metric.collect():
581
+ label_str = ""
582
+ if value.labels:
583
+ label_pairs = [f'{k}="{v}"' for k, v in value.labels.items()]
584
+ label_str = "{" + ",".join(label_pairs) + "}"
585
+ lines.append(f"{name}{label_str} {value.value}")
586
+
587
+ return "\n".join(lines)
588
+
589
+
590
+ # =============================================================================
591
+ # Span Exporter Protocol
592
+ # =============================================================================
593
+
594
+
595
+ class SpanExporter(ABC):
596
+ """Abstract base class for span exporters."""
597
+
598
+ @abstractmethod
599
+ def export(self, spans: list[Span]) -> bool:
600
+ """Export spans to backend.
601
+
602
+ Args:
603
+ spans: Spans to export
604
+
605
+ Returns:
606
+ True if export succeeded
607
+ """
608
+ pass
609
+
610
+ def shutdown(self) -> None:
611
+ """Shutdown the exporter."""
612
+ pass
613
+
614
+
615
+ class ConsoleSpanExporter(SpanExporter):
616
+ """Exports spans to console/logging."""
617
+
618
+ def __init__(self, logger: logging.Logger | None = None):
619
+ self.logger = logger or logging.getLogger("truthound.profiler.tracing")
620
+
621
+ def export(self, spans: list[Span]) -> bool:
622
+ for span in spans:
623
+ self.logger.info(
624
+ "Span: %s [%s] duration=%.2fms status=%s",
625
+ span.name,
626
+ span.span_id[:8],
627
+ span.duration_ms,
628
+ span.status.value,
629
+ )
630
+ for key, value in span.attributes.items():
631
+ self.logger.debug(" %s: %s", key, value)
632
+ return True
633
+
634
+
635
+ class InMemorySpanExporter(SpanExporter):
636
+ """Exports spans to in-memory storage for testing."""
637
+
638
+ def __init__(self, max_spans: int = 10000):
639
+ self.max_spans = max_spans
640
+ self._spans: list[Span] = []
641
+ self._lock = threading.Lock()
642
+
643
+ def export(self, spans: list[Span]) -> bool:
644
+ with self._lock:
645
+ self._spans.extend(spans)
646
+ # Trim if over limit
647
+ if len(self._spans) > self.max_spans:
648
+ self._spans = self._spans[-self.max_spans:]
649
+ return True
650
+
651
+ def get_spans(self) -> list[Span]:
652
+ """Get all exported spans."""
653
+ with self._lock:
654
+ return list(self._spans)
655
+
656
+ def clear(self) -> None:
657
+ """Clear all spans."""
658
+ with self._lock:
659
+ self._spans.clear()
660
+
661
+
662
+ class OTLPSpanExporter(SpanExporter):
663
+ """Exports spans via OTLP (OpenTelemetry Protocol).
664
+
665
+ Requires opentelemetry-exporter-otlp package.
666
+ """
667
+
668
+ def __init__(
669
+ self,
670
+ endpoint: str = "http://localhost:4317",
671
+ headers: dict[str, str] | None = None,
672
+ timeout: int = 30,
673
+ ):
674
+ self.endpoint = endpoint
675
+ self.headers = headers or {}
676
+ self.timeout = timeout
677
+ self._otlp_exporter = None
678
+
679
+ try:
680
+ from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import (
681
+ OTLPSpanExporter as _OTLPExporter,
682
+ )
683
+ self._otlp_exporter = _OTLPExporter(
684
+ endpoint=endpoint,
685
+ headers=headers,
686
+ timeout=timeout,
687
+ )
688
+ except ImportError:
689
+ pass
690
+
691
+ def export(self, spans: list[Span]) -> bool:
692
+ if self._otlp_exporter is None:
693
+ return False
694
+
695
+ # Convert to OpenTelemetry spans
696
+ # This is a simplified conversion - full implementation would
697
+ # use proper OTLP span conversion
698
+ return True
699
+
700
+ def shutdown(self) -> None:
701
+ if self._otlp_exporter:
702
+ self._otlp_exporter.shutdown()
703
+
704
+
705
+ # =============================================================================
706
+ # Span Exporter Registry
707
+ # =============================================================================
708
+
709
+
710
+ class SpanExporterRegistry:
711
+ """Registry for span exporter factories."""
712
+
713
+ def __init__(self) -> None:
714
+ self._exporters: dict[str, type[SpanExporter]] = {}
715
+
716
+ def register(self, name: str, exporter_class: type[SpanExporter]) -> None:
717
+ """Register an exporter class."""
718
+ self._exporters[name] = exporter_class
719
+
720
+ def create(self, name: str, **kwargs: Any) -> SpanExporter:
721
+ """Create an exporter instance."""
722
+ if name not in self._exporters:
723
+ raise KeyError(
724
+ f"Unknown exporter: {name}. "
725
+ f"Available: {list(self._exporters.keys())}"
726
+ )
727
+ return self._exporters[name](**kwargs)
728
+
729
+ def list_exporters(self) -> list[str]:
730
+ """List registered exporter names."""
731
+ return list(self._exporters.keys())
732
+
733
+
734
+ # Global registry
735
+ span_exporter_registry = SpanExporterRegistry()
736
+ span_exporter_registry.register("console", ConsoleSpanExporter)
737
+ span_exporter_registry.register("memory", InMemorySpanExporter)
738
+ span_exporter_registry.register("otlp", OTLPSpanExporter)
739
+
740
+
741
+ # =============================================================================
742
+ # Profiler Telemetry
743
+ # =============================================================================
744
+
745
+
746
+ @dataclass
747
+ class TelemetryConfig:
748
+ """Configuration for profiler telemetry."""
749
+
750
+ service_name: str = "truthound-profiler"
751
+ enabled: bool = True
752
+ exporter: str = "console"
753
+ exporter_options: dict[str, Any] = field(default_factory=dict)
754
+ sample_rate: float = 1.0 # 1.0 = sample all
755
+ batch_size: int = 100
756
+ flush_interval_seconds: float = 5.0
757
+
758
+
759
+ class ProfilerTelemetry:
760
+ """Main telemetry interface for the profiler.
761
+
762
+ Provides tracing, metrics, and logging integration.
763
+
764
+ Example:
765
+ telemetry = ProfilerTelemetry(service_name="my-profiler")
766
+
767
+ with telemetry.span("profile_table") as span:
768
+ span.set_attribute("table_name", "users")
769
+ # profiling logic...
770
+ """
771
+
772
+ def __init__(
773
+ self,
774
+ service_name: str = "truthound-profiler",
775
+ enabled: bool = True,
776
+ exporter: str | SpanExporter = "console",
777
+ exporter_options: dict[str, Any] | None = None,
778
+ sample_rate: float = 1.0,
779
+ ):
780
+ self.service_name = service_name
781
+ self.enabled = enabled
782
+ self.sample_rate = sample_rate
783
+ self._lock = threading.Lock()
784
+
785
+ # Initialize exporter
786
+ if isinstance(exporter, SpanExporter):
787
+ self._exporter = exporter
788
+ else:
789
+ options = exporter_options or {}
790
+ self._exporter = span_exporter_registry.create(exporter, **options)
791
+
792
+ # Span tracking
793
+ self._spans: list[Span] = []
794
+ self._current_span: Span | None = None
795
+ self._span_stack: list[Span] = []
796
+
797
+ # Metrics collector
798
+ self.metrics = MetricsCollector()
799
+
800
+ # Generate IDs
801
+ self._trace_id_counter = 0
802
+ self._span_id_counter = 0
803
+
804
+ def _generate_trace_id(self) -> str:
805
+ """Generate unique trace ID."""
806
+ with self._lock:
807
+ self._trace_id_counter += 1
808
+ return f"{self._trace_id_counter:032x}"
809
+
810
+ def _generate_span_id(self) -> str:
811
+ """Generate unique span ID."""
812
+ with self._lock:
813
+ self._span_id_counter += 1
814
+ return f"{self._span_id_counter:016x}"
815
+
816
+ def _should_sample(self) -> bool:
817
+ """Determine if this trace should be sampled."""
818
+ import random
819
+ return random.random() < self.sample_rate
820
+
821
+ @contextmanager
822
+ def span(
823
+ self,
824
+ name: str,
825
+ attributes: dict[str, Any] | None = None,
826
+ ) -> Generator[Span, None, None]:
827
+ """Create a span context manager.
828
+
829
+ Args:
830
+ name: Span name
831
+ attributes: Initial attributes
832
+
833
+ Yields:
834
+ Span object for the duration
835
+ """
836
+ if not self.enabled or not self._should_sample():
837
+ # Return a no-op span
838
+ yield Span(
839
+ name=name,
840
+ trace_id="",
841
+ span_id="",
842
+ )
843
+ return
844
+
845
+ # Create span
846
+ parent_id = self._span_stack[-1].span_id if self._span_stack else None
847
+ trace_id = (
848
+ self._span_stack[0].trace_id if self._span_stack
849
+ else self._generate_trace_id()
850
+ )
851
+
852
+ span = Span(
853
+ name=name,
854
+ trace_id=trace_id,
855
+ span_id=self._generate_span_id(),
856
+ parent_id=parent_id,
857
+ attributes=attributes or {},
858
+ )
859
+
860
+ self._span_stack.append(span)
861
+
862
+ try:
863
+ yield span
864
+ if span.status == SpanStatus.UNSET:
865
+ span.set_status(SpanStatus.OK)
866
+ except Exception as e:
867
+ span.record_exception(e)
868
+ raise
869
+ finally:
870
+ span.end()
871
+ self._span_stack.pop()
872
+ self._spans.append(span)
873
+
874
+ # Export if batch is full
875
+ if len(self._spans) >= 100:
876
+ self._flush()
877
+
878
+ def _flush(self) -> None:
879
+ """Flush pending spans to exporter."""
880
+ with self._lock:
881
+ if self._spans:
882
+ self._exporter.export(list(self._spans))
883
+ self._spans.clear()
884
+
885
+ def shutdown(self) -> None:
886
+ """Shutdown telemetry and flush remaining spans."""
887
+ self._flush()
888
+ self._exporter.shutdown()
889
+
890
+ def record_profile(
891
+ self,
892
+ profile_type: str,
893
+ duration_seconds: float,
894
+ row_count: int,
895
+ column_count: int,
896
+ success: bool = True,
897
+ ) -> None:
898
+ """Record profile metrics.
899
+
900
+ Args:
901
+ profile_type: Type of profile (table, column, etc.)
902
+ duration_seconds: Time taken
903
+ row_count: Number of rows profiled
904
+ column_count: Number of columns profiled
905
+ success: Whether profile succeeded
906
+ """
907
+ status = "success" if success else "error"
908
+
909
+ self.metrics.profiles_total.inc(status=status, type=profile_type)
910
+ self.metrics.profile_duration.observe(
911
+ duration_seconds,
912
+ operation=profile_type,
913
+ )
914
+ self.metrics.rows_processed.observe(row_count)
915
+
916
+ def record_column_profile(
917
+ self,
918
+ column_type: str,
919
+ duration_seconds: float,
920
+ ) -> None:
921
+ """Record column profile metrics."""
922
+ self.metrics.columns_profiled.inc(data_type=column_type)
923
+ self.metrics.column_profile_duration.observe(
924
+ duration_seconds,
925
+ column_type=column_type,
926
+ )
927
+
928
+ def record_cache_operation(
929
+ self,
930
+ operation: str,
931
+ hit: bool,
932
+ ) -> None:
933
+ """Record cache operation."""
934
+ result = "hit" if hit else "miss"
935
+ self.metrics.cache_operations.inc(operation=operation, result=result)
936
+
937
+
938
+ # =============================================================================
939
+ # Decorators
940
+ # =============================================================================
941
+
942
+
943
+ # Global telemetry instance (can be overridden)
944
+ _global_telemetry: ProfilerTelemetry | None = None
945
+
946
+
947
+ def get_telemetry() -> ProfilerTelemetry:
948
+ """Get or create global telemetry instance."""
949
+ global _global_telemetry
950
+ if _global_telemetry is None:
951
+ _global_telemetry = ProfilerTelemetry()
952
+ return _global_telemetry
953
+
954
+
955
+ def set_telemetry(telemetry: ProfilerTelemetry) -> None:
956
+ """Set global telemetry instance."""
957
+ global _global_telemetry
958
+ _global_telemetry = telemetry
959
+
960
+
961
+ def traced(
962
+ name: str | None = None,
963
+ attributes: dict[str, Any] | None = None,
964
+ record_args: bool = False,
965
+ ) -> Callable[[F], F]:
966
+ """Decorator to automatically trace a function.
967
+
968
+ Example:
969
+ @traced("profile_column")
970
+ def profile_column(name: str) -> ColumnProfile:
971
+ ...
972
+
973
+ @traced(record_args=True)
974
+ def process_data(data: list) -> None:
975
+ ...
976
+
977
+ Args:
978
+ name: Span name (defaults to function name)
979
+ attributes: Static attributes to add
980
+ record_args: Whether to record function arguments
981
+
982
+ Returns:
983
+ Decorated function
984
+ """
985
+ def decorator(func: F) -> F:
986
+ span_name = name or func.__name__
987
+
988
+ @wraps(func)
989
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
990
+ telemetry = get_telemetry()
991
+
992
+ span_attrs = dict(attributes or {})
993
+ span_attrs["function"] = func.__name__
994
+ span_attrs["module"] = func.__module__
995
+
996
+ if record_args:
997
+ span_attrs["args_count"] = len(args)
998
+ span_attrs["kwargs_keys"] = list(kwargs.keys())
999
+
1000
+ with telemetry.span(span_name, attributes=span_attrs) as span:
1001
+ result = func(*args, **kwargs)
1002
+
1003
+ # Add result info if available
1004
+ if hasattr(result, "__len__"):
1005
+ span.set_attribute("result_length", len(result))
1006
+
1007
+ return result
1008
+
1009
+ return wrapper # type: ignore
1010
+
1011
+ return decorator
1012
+
1013
+
1014
+ def timed(
1015
+ histogram: Histogram | None = None,
1016
+ metric_name: str = "operation_duration_seconds",
1017
+ **labels: str,
1018
+ ) -> Callable[[F], F]:
1019
+ """Decorator to time a function and record to histogram.
1020
+
1021
+ Example:
1022
+ @timed(metric_name="profile_column_seconds", column_type="string")
1023
+ def profile_string_column(col):
1024
+ ...
1025
+
1026
+ Args:
1027
+ histogram: Histogram to record to
1028
+ metric_name: Metric name if creating new histogram
1029
+ **labels: Labels to add to metric
1030
+
1031
+ Returns:
1032
+ Decorated function
1033
+ """
1034
+ def decorator(func: F) -> F:
1035
+ @wraps(func)
1036
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
1037
+ hist = histogram
1038
+ if hist is None:
1039
+ telemetry = get_telemetry()
1040
+ hist = telemetry.metrics.get_metric(metric_name)
1041
+ if hist is None or not isinstance(hist, Histogram):
1042
+ hist = telemetry.metrics.register_histogram(metric_name)
1043
+
1044
+ start = time.perf_counter()
1045
+ try:
1046
+ return func(*args, **kwargs)
1047
+ finally:
1048
+ duration = time.perf_counter() - start
1049
+ hist.observe(duration, **labels)
1050
+
1051
+ return wrapper # type: ignore
1052
+
1053
+ return decorator
1054
+
1055
+
1056
+ # =============================================================================
1057
+ # OpenTelemetry Integration
1058
+ # =============================================================================
1059
+
1060
+
1061
+ class OpenTelemetryIntegration:
1062
+ """Integration with OpenTelemetry SDK.
1063
+
1064
+ Wraps OpenTelemetry tracer and meter for seamless integration.
1065
+
1066
+ Example:
1067
+ # If opentelemetry is installed
1068
+ otel = OpenTelemetryIntegration.create(
1069
+ service_name="truthound-profiler",
1070
+ endpoint="http://localhost:4317"
1071
+ )
1072
+
1073
+ with otel.tracer.start_as_current_span("profile") as span:
1074
+ span.set_attribute("table", "users")
1075
+ ...
1076
+ """
1077
+
1078
+ def __init__(
1079
+ self,
1080
+ tracer: Any = None,
1081
+ meter: Any = None,
1082
+ ):
1083
+ self._tracer = tracer
1084
+ self._meter = meter
1085
+
1086
+ @classmethod
1087
+ def create(
1088
+ cls,
1089
+ service_name: str = "truthound-profiler",
1090
+ endpoint: str | None = None,
1091
+ ) -> "OpenTelemetryIntegration":
1092
+ """Create OpenTelemetry integration.
1093
+
1094
+ Args:
1095
+ service_name: Service name for tracing
1096
+ endpoint: OTLP endpoint (optional)
1097
+
1098
+ Returns:
1099
+ Configured integration
1100
+ """
1101
+ tracer = None
1102
+ meter = None
1103
+
1104
+ try:
1105
+ from opentelemetry import trace, metrics
1106
+ from opentelemetry.sdk.resources import Resource
1107
+ from opentelemetry.sdk.trace import TracerProvider
1108
+ from opentelemetry.sdk.metrics import MeterProvider
1109
+
1110
+ resource = Resource.create({"service.name": service_name})
1111
+
1112
+ # Setup tracing
1113
+ trace_provider = TracerProvider(resource=resource)
1114
+ trace.set_tracer_provider(trace_provider)
1115
+
1116
+ if endpoint:
1117
+ from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import (
1118
+ OTLPSpanExporter,
1119
+ )
1120
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor
1121
+
1122
+ exporter = OTLPSpanExporter(endpoint=endpoint)
1123
+ trace_provider.add_span_processor(BatchSpanProcessor(exporter))
1124
+
1125
+ tracer = trace.get_tracer(service_name)
1126
+
1127
+ # Setup metrics
1128
+ meter_provider = MeterProvider(resource=resource)
1129
+ metrics.set_meter_provider(meter_provider)
1130
+ meter = metrics.get_meter(service_name)
1131
+
1132
+ except ImportError:
1133
+ pass
1134
+
1135
+ return cls(tracer=tracer, meter=meter)
1136
+
1137
+ @property
1138
+ def tracer(self) -> Any:
1139
+ """Get OpenTelemetry tracer."""
1140
+ return self._tracer
1141
+
1142
+ @property
1143
+ def meter(self) -> Any:
1144
+ """Get OpenTelemetry meter."""
1145
+ return self._meter
1146
+
1147
+ @property
1148
+ def available(self) -> bool:
1149
+ """Check if OpenTelemetry is available."""
1150
+ return self._tracer is not None
1151
+
1152
+
1153
+ # =============================================================================
1154
+ # Convenience Functions
1155
+ # =============================================================================
1156
+
1157
+
1158
+ def create_telemetry(
1159
+ service_name: str = "truthound-profiler",
1160
+ exporter: str = "console",
1161
+ **kwargs: Any,
1162
+ ) -> ProfilerTelemetry:
1163
+ """Create and configure profiler telemetry.
1164
+
1165
+ Args:
1166
+ service_name: Service name for tracing
1167
+ exporter: Exporter type
1168
+ **kwargs: Exporter options
1169
+
1170
+ Returns:
1171
+ Configured ProfilerTelemetry instance
1172
+ """
1173
+ telemetry = ProfilerTelemetry(
1174
+ service_name=service_name,
1175
+ exporter=exporter,
1176
+ exporter_options=kwargs,
1177
+ )
1178
+ set_telemetry(telemetry)
1179
+ return telemetry
1180
+
1181
+
1182
+ def get_metrics() -> MetricsCollector:
1183
+ """Get the global metrics collector."""
1184
+ return get_telemetry().metrics