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,1001 @@
1
+ """Cloud Token Exchanger Implementations.
2
+
3
+ This module provides token exchange implementations for various cloud providers:
4
+ - AWS STS (AssumeRoleWithWebIdentity)
5
+ - Google Cloud Workload Identity
6
+ - Azure Federated Credentials
7
+ - HashiCorp Vault JWT Auth
8
+
9
+ Each exchanger converts an OIDC token to cloud-specific credentials.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import json
15
+ import logging
16
+ import os
17
+ import urllib.error
18
+ import urllib.parse
19
+ import urllib.request
20
+ from dataclasses import dataclass, field
21
+ from datetime import datetime, timedelta
22
+ from typing import TYPE_CHECKING, Any
23
+ from xml.etree import ElementTree
24
+
25
+ from truthound.secrets.oidc.base import (
26
+ AWSCredentials,
27
+ AzureCredentials,
28
+ BaseTokenExchanger,
29
+ CloudCredentials,
30
+ CloudProvider,
31
+ GCPCredentials,
32
+ OIDCExchangeError,
33
+ OIDCToken,
34
+ )
35
+
36
+ if TYPE_CHECKING:
37
+ pass
38
+
39
+
40
+ logger = logging.getLogger(__name__)
41
+
42
+
43
+ # =============================================================================
44
+ # AWS Token Exchanger
45
+ # =============================================================================
46
+
47
+
48
+ @dataclass
49
+ class AWSTokenExchangerConfig:
50
+ """Configuration for AWS token exchange.
51
+
52
+ Attributes:
53
+ role_arn: ARN of the IAM role to assume.
54
+ session_name: Session name for the assumed role.
55
+ session_duration_seconds: How long credentials should be valid.
56
+ region: AWS region for STS endpoint.
57
+ sts_endpoint: Custom STS endpoint (optional).
58
+ request_timeout: HTTP request timeout.
59
+ """
60
+
61
+ role_arn: str = ""
62
+ session_name: str = "truthound-oidc"
63
+ session_duration_seconds: int = 3600
64
+ region: str = "us-east-1"
65
+ sts_endpoint: str | None = None
66
+ request_timeout: float = 30.0
67
+
68
+
69
+ class AWSTokenExchanger(BaseTokenExchanger):
70
+ """AWS STS token exchanger using AssumeRoleWithWebIdentity.
71
+
72
+ Exchanges an OIDC token for temporary AWS credentials by calling
73
+ the AWS STS AssumeRoleWithWebIdentity API.
74
+
75
+ Requirements:
76
+ - IAM role must have a trust policy allowing the OIDC provider
77
+ - Role ARN must be configured
78
+
79
+ Example:
80
+ >>> exchanger = AWSTokenExchanger(
81
+ ... role_arn="arn:aws:iam::123456789012:role/my-role",
82
+ ... session_name="my-session",
83
+ ... )
84
+ >>> credentials = exchanger.exchange(oidc_token)
85
+ >>> print(credentials.access_key_id)
86
+
87
+ Trust Policy Example:
88
+ ```json
89
+ {
90
+ "Version": "2012-10-17",
91
+ "Statement": [{
92
+ "Effect": "Allow",
93
+ "Principal": {
94
+ "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
95
+ },
96
+ "Action": "sts:AssumeRoleWithWebIdentity",
97
+ "Condition": {
98
+ "StringEquals": {
99
+ "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
100
+ },
101
+ "StringLike": {
102
+ "token.actions.githubusercontent.com:sub": "repo:owner/repo:*"
103
+ }
104
+ }
105
+ }]
106
+ }
107
+ ```
108
+ """
109
+
110
+ # Default STS endpoint
111
+ STS_ENDPOINT_TEMPLATE = "https://sts.{region}.amazonaws.com/"
112
+
113
+ def __init__(
114
+ self,
115
+ role_arn: str | None = None,
116
+ *,
117
+ session_name: str = "truthound-oidc",
118
+ session_duration_seconds: int = 3600,
119
+ region: str | None = None,
120
+ config: AWSTokenExchangerConfig | None = None,
121
+ **kwargs: Any,
122
+ ) -> None:
123
+ """Initialize AWS token exchanger.
124
+
125
+ Args:
126
+ role_arn: IAM role ARN (or use AWS_ROLE_ARN env var).
127
+ session_name: Session name.
128
+ session_duration_seconds: Credential validity duration.
129
+ region: AWS region (or use AWS_REGION env var).
130
+ config: Full configuration object.
131
+ **kwargs: Additional base class arguments.
132
+ """
133
+ self._config = config or AWSTokenExchangerConfig()
134
+
135
+ # Override with explicit parameters
136
+ if role_arn:
137
+ self._config.role_arn = role_arn
138
+ elif not self._config.role_arn:
139
+ self._config.role_arn = os.environ.get("AWS_ROLE_ARN", "")
140
+
141
+ self._config.session_name = session_name
142
+ self._config.session_duration_seconds = session_duration_seconds
143
+
144
+ if region:
145
+ self._config.region = region
146
+ elif not self._config.region or self._config.region == "us-east-1":
147
+ self._config.region = os.environ.get("AWS_REGION", "us-east-1")
148
+
149
+ super().__init__(**kwargs)
150
+
151
+ @property
152
+ def cloud_provider(self) -> CloudProvider:
153
+ return CloudProvider.AWS
154
+
155
+ def _get_sts_endpoint(self) -> str:
156
+ """Get the STS endpoint URL."""
157
+ if self._config.sts_endpoint:
158
+ return self._config.sts_endpoint
159
+ return self.STS_ENDPOINT_TEMPLATE.format(region=self._config.region)
160
+
161
+ def _exchange(self, token: OIDCToken) -> AWSCredentials:
162
+ """Exchange OIDC token for AWS credentials.
163
+
164
+ Args:
165
+ token: OIDC token to exchange.
166
+
167
+ Returns:
168
+ AWS credentials.
169
+
170
+ Raises:
171
+ OIDCExchangeError: If exchange fails.
172
+ """
173
+ if not self._config.role_arn:
174
+ raise OIDCExchangeError(
175
+ "role_arn is required",
176
+ cloud_provider="aws",
177
+ )
178
+
179
+ # Build STS request parameters
180
+ params = {
181
+ "Action": "AssumeRoleWithWebIdentity",
182
+ "Version": "2011-06-15",
183
+ "RoleArn": self._config.role_arn,
184
+ "RoleSessionName": self._config.session_name,
185
+ "WebIdentityToken": token.get_token(),
186
+ "DurationSeconds": str(self._config.session_duration_seconds),
187
+ }
188
+
189
+ endpoint = self._get_sts_endpoint()
190
+ url = f"{endpoint}?{urllib.parse.urlencode(params)}"
191
+
192
+ request = urllib.request.Request(
193
+ url,
194
+ method="POST",
195
+ headers={"Accept": "application/xml"},
196
+ )
197
+
198
+ try:
199
+ with urllib.request.urlopen(
200
+ request, timeout=self._config.request_timeout
201
+ ) as response:
202
+ return self._parse_sts_response(response.read())
203
+
204
+ except urllib.error.HTTPError as e:
205
+ error_body = ""
206
+ try:
207
+ error_body = e.read().decode()
208
+ except Exception:
209
+ pass
210
+ raise OIDCExchangeError(
211
+ f"STS API error: {error_body or e.reason}",
212
+ cloud_provider="aws",
213
+ status_code=e.code,
214
+ response=error_body,
215
+ ) from e
216
+ except urllib.error.URLError as e:
217
+ raise OIDCExchangeError(
218
+ f"Network error: {e.reason}",
219
+ cloud_provider="aws",
220
+ ) from e
221
+
222
+ def _parse_sts_response(self, response_xml: bytes) -> AWSCredentials:
223
+ """Parse STS AssumeRoleWithWebIdentity response.
224
+
225
+ Args:
226
+ response_xml: XML response from STS.
227
+
228
+ Returns:
229
+ AWS credentials.
230
+ """
231
+ try:
232
+ # Parse XML response
233
+ root = ElementTree.fromstring(response_xml)
234
+
235
+ # Find credentials in response (namespace handling)
236
+ ns = {"sts": "https://sts.amazonaws.com/doc/2011-06-15/"}
237
+
238
+ # Try with namespace first, then without
239
+ creds = root.find(".//sts:Credentials", ns)
240
+ if creds is None:
241
+ creds = root.find(".//Credentials")
242
+
243
+ if creds is None:
244
+ raise OIDCExchangeError(
245
+ "Credentials not found in STS response",
246
+ cloud_provider="aws",
247
+ )
248
+
249
+ # Extract credential values
250
+ def get_text(elem: ElementTree.Element | None, tag: str) -> str:
251
+ child = elem.find(f"sts:{tag}", ns) if elem else None
252
+ if child is None and elem is not None:
253
+ child = elem.find(tag)
254
+ return child.text if child is not None and child.text else ""
255
+
256
+ access_key_id = get_text(creds, "AccessKeyId")
257
+ secret_access_key = get_text(creds, "SecretAccessKey")
258
+ session_token = get_text(creds, "SessionToken")
259
+ expiration_str = get_text(creds, "Expiration")
260
+
261
+ # Parse expiration time
262
+ expires_at = None
263
+ if expiration_str:
264
+ try:
265
+ # Handle ISO format with Z suffix
266
+ if expiration_str.endswith("Z"):
267
+ expiration_str = expiration_str[:-1] + "+00:00"
268
+ expires_at = datetime.fromisoformat(expiration_str)
269
+ except ValueError:
270
+ pass
271
+
272
+ # Get assumed role ARN
273
+ assumed_role = root.find(".//sts:AssumedRoleUser", ns)
274
+ if assumed_role is None:
275
+ assumed_role = root.find(".//AssumedRoleUser")
276
+
277
+ assumed_role_arn = ""
278
+ if assumed_role is not None:
279
+ arn_elem = assumed_role.find("sts:Arn", ns)
280
+ if arn_elem is None:
281
+ arn_elem = assumed_role.find("Arn")
282
+ if arn_elem is not None and arn_elem.text:
283
+ assumed_role_arn = arn_elem.text
284
+
285
+ return AWSCredentials(
286
+ access_key_id=access_key_id,
287
+ secret_access_key=secret_access_key,
288
+ session_token=session_token,
289
+ assumed_role_arn=assumed_role_arn,
290
+ expires_at=expires_at,
291
+ )
292
+
293
+ except ElementTree.ParseError as e:
294
+ raise OIDCExchangeError(
295
+ f"Invalid STS response: {e}",
296
+ cloud_provider="aws",
297
+ ) from e
298
+
299
+
300
+ # =============================================================================
301
+ # GCP Token Exchanger
302
+ # =============================================================================
303
+
304
+
305
+ @dataclass
306
+ class GCPTokenExchangerConfig:
307
+ """Configuration for GCP token exchange.
308
+
309
+ Attributes:
310
+ project_number: GCP project number.
311
+ pool_id: Workload Identity Pool ID.
312
+ provider_id: Workload Identity Provider ID.
313
+ service_account_email: Service account to impersonate.
314
+ token_lifetime_seconds: Access token lifetime.
315
+ request_timeout: HTTP request timeout.
316
+ """
317
+
318
+ project_number: str = ""
319
+ pool_id: str = ""
320
+ provider_id: str = ""
321
+ service_account_email: str = ""
322
+ token_lifetime_seconds: int = 3600
323
+ request_timeout: float = 30.0
324
+
325
+
326
+ class GCPTokenExchanger(BaseTokenExchanger):
327
+ """GCP Workload Identity token exchanger.
328
+
329
+ Exchanges an OIDC token for GCP access token through:
330
+ 1. STS Token Exchange - exchange OIDC for federated token
331
+ 2. Service Account Impersonation - get access token for SA
332
+
333
+ Requirements:
334
+ - Workload Identity Pool configured
335
+ - OIDC provider registered in the pool
336
+ - Service account with appropriate permissions
337
+
338
+ Example:
339
+ >>> exchanger = GCPTokenExchanger(
340
+ ... project_number="123456789",
341
+ ... pool_id="my-pool",
342
+ ... provider_id="github",
343
+ ... service_account_email="sa@project.iam.gserviceaccount.com",
344
+ ... )
345
+ >>> credentials = exchanger.exchange(oidc_token)
346
+ >>> print(credentials.access_token)
347
+
348
+ Configuration in GCP:
349
+ 1. Create Workload Identity Pool
350
+ 2. Add OIDC provider (e.g., GitHub Actions)
351
+ 3. Grant service account impersonation permission
352
+ """
353
+
354
+ # GCP endpoints
355
+ STS_EXCHANGE_URL = "https://sts.googleapis.com/v1/token"
356
+ SA_IMPERSONATE_URL = (
357
+ "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/"
358
+ "{service_account}:generateAccessToken"
359
+ )
360
+
361
+ def __init__(
362
+ self,
363
+ project_number: str | None = None,
364
+ pool_id: str | None = None,
365
+ provider_id: str | None = None,
366
+ service_account_email: str | None = None,
367
+ *,
368
+ config: GCPTokenExchangerConfig | None = None,
369
+ **kwargs: Any,
370
+ ) -> None:
371
+ """Initialize GCP token exchanger.
372
+
373
+ Args:
374
+ project_number: GCP project number.
375
+ pool_id: Workload Identity Pool ID.
376
+ provider_id: Workload Identity Provider ID.
377
+ service_account_email: Service account to impersonate.
378
+ config: Full configuration object.
379
+ **kwargs: Additional base class arguments.
380
+ """
381
+ self._config = config or GCPTokenExchangerConfig()
382
+
383
+ # Override with explicit parameters
384
+ if project_number:
385
+ self._config.project_number = project_number
386
+ if pool_id:
387
+ self._config.pool_id = pool_id
388
+ if provider_id:
389
+ self._config.provider_id = provider_id
390
+ if service_account_email:
391
+ self._config.service_account_email = service_account_email
392
+
393
+ super().__init__(**kwargs)
394
+
395
+ @property
396
+ def cloud_provider(self) -> CloudProvider:
397
+ return CloudProvider.GCP
398
+
399
+ def _get_audience(self) -> str:
400
+ """Get the Workload Identity audience URL."""
401
+ return (
402
+ f"//iam.googleapis.com/projects/{self._config.project_number}/"
403
+ f"locations/global/workloadIdentityPools/{self._config.pool_id}/"
404
+ f"providers/{self._config.provider_id}"
405
+ )
406
+
407
+ def _exchange(self, token: OIDCToken) -> GCPCredentials:
408
+ """Exchange OIDC token for GCP credentials.
409
+
410
+ Args:
411
+ token: OIDC token to exchange.
412
+
413
+ Returns:
414
+ GCP credentials.
415
+
416
+ Raises:
417
+ OIDCExchangeError: If exchange fails.
418
+ """
419
+ if not all([
420
+ self._config.project_number,
421
+ self._config.pool_id,
422
+ self._config.provider_id,
423
+ self._config.service_account_email,
424
+ ]):
425
+ raise OIDCExchangeError(
426
+ "project_number, pool_id, provider_id, and "
427
+ "service_account_email are required",
428
+ cloud_provider="gcp",
429
+ )
430
+
431
+ # Step 1: Exchange OIDC token for federated token
432
+ federated_token = self._exchange_for_federated_token(token)
433
+
434
+ # Step 2: Impersonate service account
435
+ return self._impersonate_service_account(federated_token)
436
+
437
+ def _exchange_for_federated_token(self, token: OIDCToken) -> str:
438
+ """Exchange OIDC token for GCP federated token.
439
+
440
+ Args:
441
+ token: OIDC token.
442
+
443
+ Returns:
444
+ Federated access token.
445
+ """
446
+ audience = self._get_audience()
447
+
448
+ request_body = {
449
+ "grantType": "urn:ietf:params:oauth:grant-type:token-exchange",
450
+ "audience": audience,
451
+ "scope": "https://www.googleapis.com/auth/cloud-platform",
452
+ "requestedTokenType": "urn:ietf:params:oauth:token-type:access_token",
453
+ "subjectTokenType": "urn:ietf:params:oauth:token-type:jwt",
454
+ "subjectToken": token.get_token(),
455
+ }
456
+
457
+ request = urllib.request.Request(
458
+ self.STS_EXCHANGE_URL,
459
+ data=json.dumps(request_body).encode(),
460
+ headers={
461
+ "Content-Type": "application/json",
462
+ },
463
+ method="POST",
464
+ )
465
+
466
+ try:
467
+ with urllib.request.urlopen(
468
+ request, timeout=self._config.request_timeout
469
+ ) as response:
470
+ data = json.loads(response.read())
471
+ return data["access_token"]
472
+
473
+ except urllib.error.HTTPError as e:
474
+ error_body = ""
475
+ try:
476
+ error_body = e.read().decode()
477
+ except Exception:
478
+ pass
479
+ raise OIDCExchangeError(
480
+ f"GCP STS exchange failed: {error_body or e.reason}",
481
+ cloud_provider="gcp",
482
+ status_code=e.code,
483
+ response=error_body,
484
+ ) from e
485
+ except KeyError:
486
+ raise OIDCExchangeError(
487
+ "access_token not found in STS response",
488
+ cloud_provider="gcp",
489
+ )
490
+
491
+ def _impersonate_service_account(self, federated_token: str) -> GCPCredentials:
492
+ """Impersonate service account to get access token.
493
+
494
+ Args:
495
+ federated_token: Federated token from STS exchange.
496
+
497
+ Returns:
498
+ GCP credentials with access token.
499
+ """
500
+ url = self.SA_IMPERSONATE_URL.format(
501
+ service_account=urllib.parse.quote(
502
+ self._config.service_account_email, safe=""
503
+ )
504
+ )
505
+
506
+ request_body = {
507
+ "scope": ["https://www.googleapis.com/auth/cloud-platform"],
508
+ "lifetime": f"{self._config.token_lifetime_seconds}s",
509
+ }
510
+
511
+ request = urllib.request.Request(
512
+ url,
513
+ data=json.dumps(request_body).encode(),
514
+ headers={
515
+ "Authorization": f"Bearer {federated_token}",
516
+ "Content-Type": "application/json",
517
+ },
518
+ method="POST",
519
+ )
520
+
521
+ try:
522
+ with urllib.request.urlopen(
523
+ request, timeout=self._config.request_timeout
524
+ ) as response:
525
+ data = json.loads(response.read())
526
+
527
+ # Parse expiration time
528
+ expires_at = None
529
+ expire_time = data.get("expireTime")
530
+ if expire_time:
531
+ try:
532
+ if expire_time.endswith("Z"):
533
+ expire_time = expire_time[:-1] + "+00:00"
534
+ expires_at = datetime.fromisoformat(expire_time)
535
+ except ValueError:
536
+ pass
537
+
538
+ # Extract project ID from service account email
539
+ parts = self._config.service_account_email.split("@")
540
+ project_id = ""
541
+ if len(parts) == 2:
542
+ project_parts = parts[1].split(".")
543
+ if project_parts:
544
+ project_id = project_parts[0]
545
+
546
+ return GCPCredentials(
547
+ access_token=data["accessToken"],
548
+ service_account=self._config.service_account_email,
549
+ project_id=project_id,
550
+ expires_at=expires_at,
551
+ )
552
+
553
+ except urllib.error.HTTPError as e:
554
+ error_body = ""
555
+ try:
556
+ error_body = e.read().decode()
557
+ except Exception:
558
+ pass
559
+ raise OIDCExchangeError(
560
+ f"Service account impersonation failed: {error_body or e.reason}",
561
+ cloud_provider="gcp",
562
+ status_code=e.code,
563
+ response=error_body,
564
+ ) from e
565
+
566
+
567
+ # =============================================================================
568
+ # Azure Token Exchanger
569
+ # =============================================================================
570
+
571
+
572
+ @dataclass
573
+ class AzureTokenExchangerConfig:
574
+ """Configuration for Azure token exchange.
575
+
576
+ Attributes:
577
+ tenant_id: Azure tenant ID.
578
+ client_id: Azure client/app ID.
579
+ subscription_id: Azure subscription ID.
580
+ scope: Token scope (default: Azure management).
581
+ request_timeout: HTTP request timeout.
582
+ """
583
+
584
+ tenant_id: str = ""
585
+ client_id: str = ""
586
+ subscription_id: str = ""
587
+ scope: str = "https://management.azure.com/.default"
588
+ request_timeout: float = 30.0
589
+
590
+
591
+ class AzureTokenExchanger(BaseTokenExchanger):
592
+ """Azure federated credentials token exchanger.
593
+
594
+ Exchanges an OIDC token for Azure access token using the
595
+ client credentials flow with federated identity.
596
+
597
+ Requirements:
598
+ - App registration with federated credential configured
599
+ - OIDC issuer and subject claim mapping
600
+
601
+ Example:
602
+ >>> exchanger = AzureTokenExchanger(
603
+ ... tenant_id="12345678-...",
604
+ ... client_id="87654321-...",
605
+ ... )
606
+ >>> credentials = exchanger.exchange(oidc_token)
607
+ >>> print(credentials.access_token)
608
+
609
+ Configuration in Azure:
610
+ 1. Create App Registration
611
+ 2. Add Federated Credential with OIDC issuer
612
+ 3. Configure subject claim matching
613
+ """
614
+
615
+ # Azure OAuth2 endpoint template
616
+ TOKEN_URL_TEMPLATE = (
617
+ "https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token"
618
+ )
619
+
620
+ def __init__(
621
+ self,
622
+ tenant_id: str | None = None,
623
+ client_id: str | None = None,
624
+ *,
625
+ subscription_id: str | None = None,
626
+ scope: str | None = None,
627
+ config: AzureTokenExchangerConfig | None = None,
628
+ **kwargs: Any,
629
+ ) -> None:
630
+ """Initialize Azure token exchanger.
631
+
632
+ Args:
633
+ tenant_id: Azure tenant ID (or AZURE_TENANT_ID env var).
634
+ client_id: Azure client ID (or AZURE_CLIENT_ID env var).
635
+ subscription_id: Azure subscription ID.
636
+ scope: Token scope.
637
+ config: Full configuration object.
638
+ **kwargs: Additional base class arguments.
639
+ """
640
+ self._config = config or AzureTokenExchangerConfig()
641
+
642
+ # Override with explicit parameters or env vars
643
+ if tenant_id:
644
+ self._config.tenant_id = tenant_id
645
+ elif not self._config.tenant_id:
646
+ self._config.tenant_id = os.environ.get("AZURE_TENANT_ID", "")
647
+
648
+ if client_id:
649
+ self._config.client_id = client_id
650
+ elif not self._config.client_id:
651
+ self._config.client_id = os.environ.get("AZURE_CLIENT_ID", "")
652
+
653
+ if subscription_id:
654
+ self._config.subscription_id = subscription_id
655
+ elif not self._config.subscription_id:
656
+ self._config.subscription_id = os.environ.get("AZURE_SUBSCRIPTION_ID", "")
657
+
658
+ if scope:
659
+ self._config.scope = scope
660
+
661
+ super().__init__(**kwargs)
662
+
663
+ @property
664
+ def cloud_provider(self) -> CloudProvider:
665
+ return CloudProvider.AZURE
666
+
667
+ def _exchange(self, token: OIDCToken) -> AzureCredentials:
668
+ """Exchange OIDC token for Azure credentials.
669
+
670
+ Args:
671
+ token: OIDC token to exchange.
672
+
673
+ Returns:
674
+ Azure credentials.
675
+
676
+ Raises:
677
+ OIDCExchangeError: If exchange fails.
678
+ """
679
+ if not self._config.tenant_id or not self._config.client_id:
680
+ raise OIDCExchangeError(
681
+ "tenant_id and client_id are required",
682
+ cloud_provider="azure",
683
+ )
684
+
685
+ url = self.TOKEN_URL_TEMPLATE.format(tenant_id=self._config.tenant_id)
686
+
687
+ # Build form data for token request
688
+ form_data = {
689
+ "client_id": self._config.client_id,
690
+ "client_assertion_type": (
691
+ "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
692
+ ),
693
+ "client_assertion": token.get_token(),
694
+ "grant_type": "client_credentials",
695
+ "scope": self._config.scope,
696
+ }
697
+
698
+ request = urllib.request.Request(
699
+ url,
700
+ data=urllib.parse.urlencode(form_data).encode(),
701
+ headers={
702
+ "Content-Type": "application/x-www-form-urlencoded",
703
+ },
704
+ method="POST",
705
+ )
706
+
707
+ try:
708
+ with urllib.request.urlopen(
709
+ request, timeout=self._config.request_timeout
710
+ ) as response:
711
+ data = json.loads(response.read())
712
+
713
+ # Calculate expiration time
714
+ expires_at = None
715
+ expires_in = data.get("expires_in")
716
+ if expires_in:
717
+ expires_at = datetime.now() + timedelta(seconds=int(expires_in))
718
+
719
+ return AzureCredentials(
720
+ access_token=data["access_token"],
721
+ token_type=data.get("token_type", "Bearer"),
722
+ tenant_id=self._config.tenant_id,
723
+ client_id=self._config.client_id,
724
+ subscription_id=self._config.subscription_id,
725
+ expires_at=expires_at,
726
+ )
727
+
728
+ except urllib.error.HTTPError as e:
729
+ error_body = ""
730
+ try:
731
+ error_body = e.read().decode()
732
+ except Exception:
733
+ pass
734
+ raise OIDCExchangeError(
735
+ f"Azure token exchange failed: {error_body or e.reason}",
736
+ cloud_provider="azure",
737
+ status_code=e.code,
738
+ response=error_body,
739
+ ) from e
740
+ except KeyError:
741
+ raise OIDCExchangeError(
742
+ "access_token not found in Azure response",
743
+ cloud_provider="azure",
744
+ )
745
+
746
+
747
+ # =============================================================================
748
+ # Vault Token Exchanger
749
+ # =============================================================================
750
+
751
+
752
+ @dataclass
753
+ class VaultTokenExchangerConfig:
754
+ """Configuration for HashiCorp Vault token exchange.
755
+
756
+ Attributes:
757
+ vault_url: Vault server URL.
758
+ jwt_auth_path: Path to JWT auth backend.
759
+ role: Vault role to use.
760
+ namespace: Vault namespace (Enterprise only).
761
+ request_timeout: HTTP request timeout.
762
+ """
763
+
764
+ vault_url: str = ""
765
+ jwt_auth_path: str = "jwt"
766
+ role: str = ""
767
+ namespace: str = ""
768
+ request_timeout: float = 30.0
769
+
770
+
771
+ @dataclass
772
+ class VaultCredentials(CloudCredentials):
773
+ """HashiCorp Vault credentials.
774
+
775
+ Attributes:
776
+ client_token: Vault client token.
777
+ accessor: Token accessor.
778
+ policies: List of applied policies.
779
+ renewable: Whether token is renewable.
780
+ lease_duration: Token lease duration in seconds.
781
+ """
782
+
783
+ client_token: str = ""
784
+ accessor: str = ""
785
+ policies: list[str] = field(default_factory=list)
786
+ renewable: bool = False
787
+ lease_duration: int = 0
788
+
789
+ def __post_init__(self) -> None:
790
+ self.provider = CloudProvider.VAULT
791
+
792
+ def get_authorization_header(self) -> dict[str, str]:
793
+ """Get HTTP authorization header for Vault."""
794
+ return {"X-Vault-Token": self.client_token}
795
+
796
+ def __repr__(self) -> str:
797
+ """Safe representation."""
798
+ return (
799
+ f"VaultCredentials(accessor={self.accessor}, "
800
+ f"policies={self.policies})"
801
+ )
802
+
803
+
804
+ class VaultTokenExchanger(BaseTokenExchanger):
805
+ """HashiCorp Vault JWT auth token exchanger.
806
+
807
+ Exchanges an OIDC token for Vault client token using the
808
+ JWT authentication method.
809
+
810
+ Requirements:
811
+ - JWT auth backend enabled and configured
812
+ - Role configured with appropriate policies
813
+
814
+ Example:
815
+ >>> exchanger = VaultTokenExchanger(
816
+ ... vault_url="https://vault.example.com",
817
+ ... role="my-github-role",
818
+ ... )
819
+ >>> credentials = exchanger.exchange(oidc_token)
820
+ >>> print(credentials.client_token)
821
+
822
+ Vault Configuration:
823
+ ```bash
824
+ vault auth enable jwt
825
+
826
+ vault write auth/jwt/config \\
827
+ oidc_discovery_url="https://token.actions.githubusercontent.com" \\
828
+ bound_issuer="https://token.actions.githubusercontent.com"
829
+
830
+ vault write auth/jwt/role/my-github-role \\
831
+ role_type="jwt" \\
832
+ bound_audiences="https://vault.example.com" \\
833
+ bound_claims_type="glob" \\
834
+ bound_claims='{"repository":"owner/repo"}' \\
835
+ user_claim="repository" \\
836
+ policies="my-policy" \\
837
+ ttl="1h"
838
+ ```
839
+ """
840
+
841
+ def __init__(
842
+ self,
843
+ vault_url: str | None = None,
844
+ role: str | None = None,
845
+ *,
846
+ jwt_auth_path: str = "jwt",
847
+ namespace: str | None = None,
848
+ config: VaultTokenExchangerConfig | None = None,
849
+ **kwargs: Any,
850
+ ) -> None:
851
+ """Initialize Vault token exchanger.
852
+
853
+ Args:
854
+ vault_url: Vault server URL (or VAULT_ADDR env var).
855
+ role: Vault role name.
856
+ jwt_auth_path: Path to JWT auth backend.
857
+ namespace: Vault namespace.
858
+ config: Full configuration object.
859
+ **kwargs: Additional base class arguments.
860
+ """
861
+ self._config = config or VaultTokenExchangerConfig()
862
+
863
+ # Override with explicit parameters or env vars
864
+ if vault_url:
865
+ self._config.vault_url = vault_url
866
+ elif not self._config.vault_url:
867
+ self._config.vault_url = os.environ.get("VAULT_ADDR", "")
868
+
869
+ if role:
870
+ self._config.role = role
871
+
872
+ self._config.jwt_auth_path = jwt_auth_path
873
+
874
+ if namespace:
875
+ self._config.namespace = namespace
876
+ elif not self._config.namespace:
877
+ self._config.namespace = os.environ.get("VAULT_NAMESPACE", "")
878
+
879
+ super().__init__(**kwargs)
880
+
881
+ @property
882
+ def cloud_provider(self) -> CloudProvider:
883
+ return CloudProvider.VAULT
884
+
885
+ def _exchange(self, token: OIDCToken) -> VaultCredentials:
886
+ """Exchange OIDC token for Vault credentials.
887
+
888
+ Args:
889
+ token: OIDC token to exchange.
890
+
891
+ Returns:
892
+ Vault credentials.
893
+
894
+ Raises:
895
+ OIDCExchangeError: If exchange fails.
896
+ """
897
+ if not self._config.vault_url or not self._config.role:
898
+ raise OIDCExchangeError(
899
+ "vault_url and role are required",
900
+ cloud_provider="vault",
901
+ )
902
+
903
+ url = (
904
+ f"{self._config.vault_url.rstrip('/')}/v1/auth/"
905
+ f"{self._config.jwt_auth_path}/login"
906
+ )
907
+
908
+ request_body = {
909
+ "jwt": token.get_token(),
910
+ "role": self._config.role,
911
+ }
912
+
913
+ headers = {"Content-Type": "application/json"}
914
+ if self._config.namespace:
915
+ headers["X-Vault-Namespace"] = self._config.namespace
916
+
917
+ request = urllib.request.Request(
918
+ url,
919
+ data=json.dumps(request_body).encode(),
920
+ headers=headers,
921
+ method="POST",
922
+ )
923
+
924
+ try:
925
+ with urllib.request.urlopen(
926
+ request, timeout=self._config.request_timeout
927
+ ) as response:
928
+ data = json.loads(response.read())
929
+ auth = data.get("auth", {})
930
+
931
+ # Calculate expiration time
932
+ expires_at = None
933
+ lease_duration = auth.get("lease_duration", 0)
934
+ if lease_duration:
935
+ expires_at = datetime.now() + timedelta(seconds=lease_duration)
936
+
937
+ return VaultCredentials(
938
+ client_token=auth.get("client_token", ""),
939
+ accessor=auth.get("accessor", ""),
940
+ policies=auth.get("policies", []),
941
+ renewable=auth.get("renewable", False),
942
+ lease_duration=lease_duration,
943
+ expires_at=expires_at,
944
+ )
945
+
946
+ except urllib.error.HTTPError as e:
947
+ error_body = ""
948
+ try:
949
+ error_body = e.read().decode()
950
+ except Exception:
951
+ pass
952
+ raise OIDCExchangeError(
953
+ f"Vault JWT login failed: {error_body or e.reason}",
954
+ cloud_provider="vault",
955
+ status_code=e.code,
956
+ response=error_body,
957
+ ) from e
958
+
959
+
960
+ # =============================================================================
961
+ # Factory Function
962
+ # =============================================================================
963
+
964
+
965
+ def create_token_exchanger(
966
+ cloud_provider: CloudProvider | str,
967
+ **kwargs: Any,
968
+ ) -> BaseTokenExchanger:
969
+ """Create a token exchanger for the specified cloud provider.
970
+
971
+ Args:
972
+ cloud_provider: Target cloud provider.
973
+ **kwargs: Provider-specific configuration.
974
+
975
+ Returns:
976
+ Token exchanger instance.
977
+
978
+ Raises:
979
+ ValueError: If cloud provider is not supported.
980
+
981
+ Example:
982
+ >>> exchanger = create_token_exchanger(
983
+ ... "aws",
984
+ ... role_arn="arn:aws:iam::123456789012:role/my-role",
985
+ ... )
986
+ """
987
+ if isinstance(cloud_provider, str):
988
+ cloud_provider = CloudProvider(cloud_provider.lower())
989
+
990
+ exchangers = {
991
+ CloudProvider.AWS: AWSTokenExchanger,
992
+ CloudProvider.GCP: GCPTokenExchanger,
993
+ CloudProvider.AZURE: AzureTokenExchanger,
994
+ CloudProvider.VAULT: VaultTokenExchanger,
995
+ }
996
+
997
+ exchanger_cls = exchangers.get(cloud_provider)
998
+ if exchanger_cls is None:
999
+ raise ValueError(f"Unsupported cloud provider: {cloud_provider}")
1000
+
1001
+ return exchanger_cls(**kwargs)