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,902 @@
1
+ """Base classes and protocols for OIDC authentication.
2
+
3
+ This module defines the core abstractions for OIDC-based authentication,
4
+ providing a pluggable architecture for different identity providers and
5
+ cloud platforms.
6
+
7
+ Design Principles:
8
+ 1. Protocol-based: Duck typing for flexibility
9
+ 2. Token immutability: OIDCToken is immutable after creation
10
+ 3. Secure by default: Tokens are redacted in repr/str
11
+ 4. Extensible: Easy to add new providers and exchangers
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import base64
17
+ import hashlib
18
+ import json
19
+ import logging
20
+ from abc import ABC, abstractmethod
21
+ from dataclasses import dataclass, field
22
+ from datetime import datetime, timedelta
23
+ from enum import Enum
24
+ from typing import (
25
+ TYPE_CHECKING,
26
+ Any,
27
+ Callable,
28
+ Generic,
29
+ Protocol,
30
+ TypeVar,
31
+ runtime_checkable,
32
+ )
33
+
34
+ if TYPE_CHECKING:
35
+ pass
36
+
37
+
38
+ logger = logging.getLogger(__name__)
39
+
40
+
41
+ # =============================================================================
42
+ # Exceptions
43
+ # =============================================================================
44
+
45
+
46
+ class OIDCError(Exception):
47
+ """Base exception for OIDC-related errors."""
48
+
49
+ pass
50
+
51
+
52
+ class OIDCTokenError(OIDCError):
53
+ """Raised when there's an error with the OIDC token."""
54
+
55
+ def __init__(self, message: str, provider: str | None = None) -> None:
56
+ self.provider = provider
57
+ msg = message
58
+ if provider:
59
+ msg = f"[{provider}] {message}"
60
+ super().__init__(msg)
61
+
62
+
63
+ class OIDCExchangeError(OIDCError):
64
+ """Raised when token exchange fails."""
65
+
66
+ def __init__(
67
+ self,
68
+ message: str,
69
+ cloud_provider: str,
70
+ status_code: int | None = None,
71
+ response: str | None = None,
72
+ ) -> None:
73
+ self.cloud_provider = cloud_provider
74
+ self.status_code = status_code
75
+ self.response = response
76
+ msg = f"[{cloud_provider}] Token exchange failed: {message}"
77
+ if status_code:
78
+ msg += f" (status: {status_code})"
79
+ super().__init__(msg)
80
+
81
+
82
+ class OIDCConfigurationError(OIDCError):
83
+ """Raised when OIDC configuration is invalid."""
84
+
85
+ def __init__(self, message: str, field: str | None = None) -> None:
86
+ self.field = field
87
+ msg = f"OIDC configuration error: {message}"
88
+ if field:
89
+ msg += f" (field: {field})"
90
+ super().__init__(msg)
91
+
92
+
93
+ class OIDCProviderNotAvailableError(OIDCError):
94
+ """Raised when OIDC provider is not available in the current environment."""
95
+
96
+ def __init__(self, provider: str, reason: str) -> None:
97
+ self.provider = provider
98
+ self.reason = reason
99
+ super().__init__(
100
+ f"OIDC provider '{provider}' is not available: {reason}"
101
+ )
102
+
103
+
104
+ # =============================================================================
105
+ # Cloud Provider Enum
106
+ # =============================================================================
107
+
108
+
109
+ class CloudProvider(str, Enum):
110
+ """Supported cloud providers for token exchange."""
111
+
112
+ AWS = "aws"
113
+ GCP = "gcp"
114
+ AZURE = "azure"
115
+ VAULT = "vault"
116
+
117
+ def __str__(self) -> str:
118
+ return self.value
119
+
120
+
121
+ class CIProvider(str, Enum):
122
+ """Supported CI/CD providers with OIDC support."""
123
+
124
+ GITHUB_ACTIONS = "github_actions"
125
+ GITLAB_CI = "gitlab_ci"
126
+ CIRCLECI = "circleci"
127
+ BITBUCKET = "bitbucket"
128
+ JENKINS = "jenkins"
129
+ UNKNOWN = "unknown"
130
+
131
+ def __str__(self) -> str:
132
+ return self.value
133
+
134
+
135
+ # =============================================================================
136
+ # OIDC Token and Claims
137
+ # =============================================================================
138
+
139
+
140
+ @dataclass(frozen=True)
141
+ class OIDCClaims:
142
+ """Parsed OIDC token claims.
143
+
144
+ Common claims across different CI providers with optional
145
+ provider-specific extensions.
146
+
147
+ Attributes:
148
+ issuer: Token issuer (iss claim).
149
+ subject: Subject identifier (sub claim).
150
+ audience: Token audience (aud claim).
151
+ expiration: Token expiration time (exp claim).
152
+ issued_at: Token issue time (iat claim).
153
+ repository: Repository identifier (if available).
154
+ ref: Git ref (branch/tag) if available.
155
+ sha: Git commit SHA if available.
156
+ actor: User/actor who triggered the workflow.
157
+ workflow: Workflow name if available.
158
+ job: Job name if available.
159
+ run_id: Run/pipeline ID if available.
160
+ environment: Deployment environment if available.
161
+ extra: Additional provider-specific claims.
162
+ """
163
+
164
+ issuer: str
165
+ subject: str
166
+ audience: str | list[str]
167
+ expiration: datetime
168
+ issued_at: datetime
169
+ repository: str | None = None
170
+ ref: str | None = None
171
+ sha: str | None = None
172
+ actor: str | None = None
173
+ workflow: str | None = None
174
+ job: str | None = None
175
+ run_id: str | None = None
176
+ environment: str | None = None
177
+ extra: dict[str, Any] = field(default_factory=dict)
178
+
179
+ @classmethod
180
+ def from_jwt_payload(cls, payload: dict[str, Any]) -> "OIDCClaims":
181
+ """Parse claims from JWT payload.
182
+
183
+ Args:
184
+ payload: Decoded JWT payload.
185
+
186
+ Returns:
187
+ OIDCClaims instance.
188
+ """
189
+ # Required claims
190
+ issuer = payload.get("iss", "")
191
+ subject = payload.get("sub", "")
192
+ audience = payload.get("aud", "")
193
+
194
+ # Time claims
195
+ exp = payload.get("exp", 0)
196
+ iat = payload.get("iat", 0)
197
+ expiration = datetime.fromtimestamp(exp) if exp else datetime.now()
198
+ issued_at = datetime.fromtimestamp(iat) if iat else datetime.now()
199
+
200
+ # Common optional claims (varies by provider)
201
+ repository = payload.get("repository") or payload.get("project_path")
202
+ ref = payload.get("ref") or payload.get("ref_path")
203
+ sha = payload.get("sha") or payload.get("commit_sha")
204
+ actor = payload.get("actor") or payload.get("user_login")
205
+ workflow = payload.get("workflow") or payload.get("pipeline_source")
206
+ job = payload.get("job") or payload.get("job_id")
207
+ run_id = payload.get("run_id") or payload.get("pipeline_id")
208
+ environment = payload.get("environment") or payload.get("environment_scope")
209
+
210
+ # Collect extra claims
211
+ known_claims = {
212
+ "iss", "sub", "aud", "exp", "iat", "nbf", "jti",
213
+ "repository", "project_path", "ref", "ref_path",
214
+ "sha", "commit_sha", "actor", "user_login",
215
+ "workflow", "pipeline_source", "job", "job_id",
216
+ "run_id", "pipeline_id", "environment", "environment_scope",
217
+ }
218
+ extra = {k: v for k, v in payload.items() if k not in known_claims}
219
+
220
+ return cls(
221
+ issuer=issuer,
222
+ subject=subject,
223
+ audience=audience,
224
+ expiration=expiration,
225
+ issued_at=issued_at,
226
+ repository=repository,
227
+ ref=ref,
228
+ sha=sha,
229
+ actor=actor,
230
+ workflow=workflow,
231
+ job=job,
232
+ run_id=run_id,
233
+ environment=environment,
234
+ extra=extra,
235
+ )
236
+
237
+ @property
238
+ def is_expired(self) -> bool:
239
+ """Check if the token is expired."""
240
+ return datetime.now() >= self.expiration
241
+
242
+ @property
243
+ def time_until_expiry(self) -> timedelta:
244
+ """Get time remaining until expiration."""
245
+ return self.expiration - datetime.now()
246
+
247
+ def get_audience_list(self) -> list[str]:
248
+ """Get audience as a list."""
249
+ if isinstance(self.audience, list):
250
+ return self.audience
251
+ return [self.audience] if self.audience else []
252
+
253
+
254
+ class OIDCToken:
255
+ """Container for OIDC JWT token.
256
+
257
+ Provides secure handling of the raw JWT token with:
258
+ - Lazy claim parsing
259
+ - Token not exposed in repr/str
260
+ - Expiration checking
261
+ - Hash for change detection
262
+
263
+ Example:
264
+ >>> token = OIDCToken(jwt_string, provider="github")
265
+ >>> print(token) # "OIDCToken(provider=github, expires_in=...)"
266
+ >>> token.get_token() # Returns raw JWT
267
+ >>> token.claims.subject # Parsed claims
268
+ """
269
+
270
+ __slots__ = (
271
+ "_token",
272
+ "_hash",
273
+ "_provider",
274
+ "_claims",
275
+ "_created_at",
276
+ )
277
+
278
+ def __init__(
279
+ self,
280
+ token: str,
281
+ *,
282
+ provider: str = "unknown",
283
+ ) -> None:
284
+ """Initialize OIDC token.
285
+
286
+ Args:
287
+ token: Raw JWT token string.
288
+ provider: Identity provider name.
289
+ """
290
+ self._token = token
291
+ self._hash = hashlib.sha256(token.encode()).hexdigest()[:16]
292
+ self._provider = provider
293
+ self._claims: OIDCClaims | None = None
294
+ self._created_at = datetime.now()
295
+
296
+ def get_token(self) -> str:
297
+ """Get the raw JWT token.
298
+
299
+ This is the only way to access the underlying token.
300
+ """
301
+ return self._token
302
+
303
+ @property
304
+ def provider(self) -> str:
305
+ """Get the identity provider name."""
306
+ return self._provider
307
+
308
+ @property
309
+ def claims(self) -> OIDCClaims:
310
+ """Get parsed token claims (lazy parsing)."""
311
+ if self._claims is None:
312
+ self._claims = self._parse_claims()
313
+ return self._claims
314
+
315
+ @property
316
+ def is_expired(self) -> bool:
317
+ """Check if the token is expired."""
318
+ return self.claims.is_expired
319
+
320
+ @property
321
+ def hash(self) -> str:
322
+ """Get hash for change detection."""
323
+ return self._hash
324
+
325
+ def _parse_claims(self) -> OIDCClaims:
326
+ """Parse JWT claims without verification.
327
+
328
+ Note: This only decodes the payload, it does NOT verify the signature.
329
+ Signature verification is done by the token exchanger (e.g., AWS STS).
330
+ """
331
+ try:
332
+ parts = self._token.split(".")
333
+ if len(parts) != 3:
334
+ raise OIDCTokenError(
335
+ "Invalid JWT format", provider=self._provider
336
+ )
337
+
338
+ # Decode payload (add padding if needed)
339
+ payload_b64 = parts[1]
340
+ padding = 4 - len(payload_b64) % 4
341
+ if padding != 4:
342
+ payload_b64 += "=" * padding
343
+
344
+ payload_json = base64.urlsafe_b64decode(payload_b64)
345
+ payload = json.loads(payload_json)
346
+
347
+ return OIDCClaims.from_jwt_payload(payload)
348
+
349
+ except json.JSONDecodeError as e:
350
+ raise OIDCTokenError(
351
+ f"Invalid JWT payload: {e}", provider=self._provider
352
+ ) from e
353
+ except Exception as e:
354
+ if isinstance(e, OIDCTokenError):
355
+ raise
356
+ raise OIDCTokenError(
357
+ f"Failed to parse JWT: {e}", provider=self._provider
358
+ ) from e
359
+
360
+ def __repr__(self) -> str:
361
+ """Safe representation that doesn't expose the token."""
362
+ try:
363
+ expires_in = self.claims.time_until_expiry
364
+ expires_str = f"{expires_in.total_seconds():.0f}s"
365
+ except Exception:
366
+ expires_str = "unknown"
367
+
368
+ return (
369
+ f"OIDCToken(provider={self._provider!r}, "
370
+ f"expires_in={expires_str})"
371
+ )
372
+
373
+ def __str__(self) -> str:
374
+ """String representation shows masked token."""
375
+ return f"OIDCToken(***)"
376
+
377
+ def __bool__(self) -> bool:
378
+ """Check if token is valid and not expired."""
379
+ return bool(self._token) and not self.is_expired
380
+
381
+
382
+ # =============================================================================
383
+ # Cloud Credentials
384
+ # =============================================================================
385
+
386
+
387
+ @dataclass
388
+ class CloudCredentials:
389
+ """Base class for cloud provider credentials.
390
+
391
+ Attributes:
392
+ provider: Cloud provider name.
393
+ expires_at: When credentials expire.
394
+ metadata: Additional metadata.
395
+ """
396
+
397
+ provider: CloudProvider = field(default=CloudProvider.AWS)
398
+ expires_at: datetime | None = None
399
+ metadata: dict[str, Any] = field(default_factory=dict)
400
+
401
+ @property
402
+ def is_expired(self) -> bool:
403
+ """Check if credentials are expired."""
404
+ if self.expires_at is None:
405
+ return False
406
+ return datetime.now() >= self.expires_at
407
+
408
+ @property
409
+ def time_until_expiry(self) -> timedelta | None:
410
+ """Get time remaining until expiration."""
411
+ if self.expires_at is None:
412
+ return None
413
+ return self.expires_at - datetime.now()
414
+
415
+
416
+ @dataclass
417
+ class AWSCredentials(CloudCredentials):
418
+ """AWS STS credentials from OIDC token exchange.
419
+
420
+ Attributes:
421
+ access_key_id: AWS access key ID.
422
+ secret_access_key: AWS secret access key.
423
+ session_token: AWS session token.
424
+ assumed_role_arn: ARN of the assumed role.
425
+ """
426
+
427
+ access_key_id: str = ""
428
+ secret_access_key: str = ""
429
+ session_token: str = ""
430
+ assumed_role_arn: str = ""
431
+
432
+ def __post_init__(self) -> None:
433
+ self.provider = CloudProvider.AWS
434
+
435
+ def to_boto3_session_credentials(self) -> dict[str, str]:
436
+ """Convert to boto3 session credentials format."""
437
+ return {
438
+ "aws_access_key_id": self.access_key_id,
439
+ "aws_secret_access_key": self.secret_access_key,
440
+ "aws_session_token": self.session_token,
441
+ }
442
+
443
+ def to_environment_variables(self) -> dict[str, str]:
444
+ """Convert to environment variable format."""
445
+ return {
446
+ "AWS_ACCESS_KEY_ID": self.access_key_id,
447
+ "AWS_SECRET_ACCESS_KEY": self.secret_access_key,
448
+ "AWS_SESSION_TOKEN": self.session_token,
449
+ }
450
+
451
+ def __repr__(self) -> str:
452
+ """Safe representation."""
453
+ return (
454
+ f"AWSCredentials(access_key_id={self.access_key_id[:8]}..., "
455
+ f"role={self.assumed_role_arn})"
456
+ )
457
+
458
+
459
+ @dataclass
460
+ class GCPCredentials(CloudCredentials):
461
+ """GCP Workload Identity credentials from OIDC token exchange.
462
+
463
+ Attributes:
464
+ access_token: OAuth2 access token.
465
+ token_type: Token type (usually "Bearer").
466
+ service_account: Service account email.
467
+ project_id: GCP project ID.
468
+ """
469
+
470
+ access_token: str = ""
471
+ token_type: str = "Bearer"
472
+ service_account: str = ""
473
+ project_id: str = ""
474
+
475
+ def __post_init__(self) -> None:
476
+ self.provider = CloudProvider.GCP
477
+
478
+ def to_google_credentials(self) -> dict[str, Any]:
479
+ """Convert to google-auth credentials format."""
480
+ return {
481
+ "token": self.access_token,
482
+ "expiry": self.expires_at,
483
+ }
484
+
485
+ def to_environment_variables(self) -> dict[str, str]:
486
+ """Convert to environment variable format."""
487
+ return {
488
+ "GOOGLE_OAUTH_ACCESS_TOKEN": self.access_token,
489
+ }
490
+
491
+ def get_authorization_header(self) -> dict[str, str]:
492
+ """Get HTTP authorization header."""
493
+ return {"Authorization": f"{self.token_type} {self.access_token}"}
494
+
495
+ def __repr__(self) -> str:
496
+ """Safe representation."""
497
+ return (
498
+ f"GCPCredentials(service_account={self.service_account}, "
499
+ f"project={self.project_id})"
500
+ )
501
+
502
+
503
+ @dataclass
504
+ class AzureCredentials(CloudCredentials):
505
+ """Azure federated credentials from OIDC token exchange.
506
+
507
+ Attributes:
508
+ access_token: OAuth2 access token.
509
+ token_type: Token type (usually "Bearer").
510
+ tenant_id: Azure tenant ID.
511
+ client_id: Azure client/app ID.
512
+ subscription_id: Azure subscription ID.
513
+ """
514
+
515
+ access_token: str = ""
516
+ token_type: str = "Bearer"
517
+ tenant_id: str = ""
518
+ client_id: str = ""
519
+ subscription_id: str = ""
520
+
521
+ def __post_init__(self) -> None:
522
+ self.provider = CloudProvider.AZURE
523
+
524
+ def to_environment_variables(self) -> dict[str, str]:
525
+ """Convert to environment variable format."""
526
+ return {
527
+ "AZURE_ACCESS_TOKEN": self.access_token,
528
+ "AZURE_TENANT_ID": self.tenant_id,
529
+ "AZURE_CLIENT_ID": self.client_id,
530
+ "AZURE_SUBSCRIPTION_ID": self.subscription_id,
531
+ }
532
+
533
+ def get_authorization_header(self) -> dict[str, str]:
534
+ """Get HTTP authorization header."""
535
+ return {"Authorization": f"{self.token_type} {self.access_token}"}
536
+
537
+ def __repr__(self) -> str:
538
+ """Safe representation."""
539
+ return (
540
+ f"AzureCredentials(tenant={self.tenant_id}, "
541
+ f"client={self.client_id})"
542
+ )
543
+
544
+
545
+ # =============================================================================
546
+ # Protocols
547
+ # =============================================================================
548
+
549
+
550
+ @runtime_checkable
551
+ class OIDCProvider(Protocol):
552
+ """Protocol for OIDC identity providers.
553
+
554
+ Implementations must provide get_token() to retrieve the OIDC JWT token
555
+ from the CI environment.
556
+
557
+ Example:
558
+ >>> class MyOIDCProvider:
559
+ ... @property
560
+ ... def name(self) -> str:
561
+ ... return "my-ci"
562
+ ...
563
+ ... def get_token(self, audience: str) -> OIDCToken:
564
+ ... jwt = fetch_from_my_ci_environment(audience)
565
+ ... return OIDCToken(jwt, provider=self.name)
566
+ ...
567
+ ... def is_available(self) -> bool:
568
+ ... return "MY_CI_TOKEN_URL" in os.environ
569
+ """
570
+
571
+ @property
572
+ def name(self) -> str:
573
+ """Provider name for identification."""
574
+ ...
575
+
576
+ def get_token(self, audience: str | None = None) -> OIDCToken:
577
+ """Retrieve OIDC token for the given audience.
578
+
579
+ Args:
580
+ audience: Token audience (required by some providers).
581
+
582
+ Returns:
583
+ OIDCToken containing the JWT.
584
+
585
+ Raises:
586
+ OIDCTokenError: If token retrieval fails.
587
+ OIDCProviderNotAvailableError: If provider is not available.
588
+ """
589
+ ...
590
+
591
+ def is_available(self) -> bool:
592
+ """Check if this provider is available in the current environment."""
593
+ ...
594
+
595
+
596
+ @runtime_checkable
597
+ class TokenExchanger(Protocol):
598
+ """Protocol for cloud token exchangers.
599
+
600
+ Implementations exchange an OIDC token for cloud provider credentials.
601
+
602
+ Example:
603
+ >>> class MyExchanger:
604
+ ... @property
605
+ ... def cloud_provider(self) -> CloudProvider:
606
+ ... return CloudProvider.AWS
607
+ ...
608
+ ... def exchange(self, token: OIDCToken) -> CloudCredentials:
609
+ ... creds = call_sts_api(token.get_token())
610
+ ... return AWSCredentials(...)
611
+ """
612
+
613
+ @property
614
+ def cloud_provider(self) -> CloudProvider:
615
+ """Cloud provider this exchanger targets."""
616
+ ...
617
+
618
+ def exchange(self, token: OIDCToken) -> CloudCredentials:
619
+ """Exchange OIDC token for cloud credentials.
620
+
621
+ Args:
622
+ token: OIDC token from CI provider.
623
+
624
+ Returns:
625
+ Cloud provider credentials.
626
+
627
+ Raises:
628
+ OIDCExchangeError: If exchange fails.
629
+ """
630
+ ...
631
+
632
+
633
+ # =============================================================================
634
+ # Base Implementations
635
+ # =============================================================================
636
+
637
+
638
+ class BaseOIDCProvider(ABC):
639
+ """Abstract base class for OIDC providers.
640
+
641
+ Provides common functionality for OIDC identity providers:
642
+ - Token caching with configurable TTL
643
+ - Retry logic for token requests
644
+ - Logging and metrics
645
+
646
+ Subclasses must implement:
647
+ - _fetch_token(): Retrieve token from the CI environment
648
+ - name property: Provider identifier
649
+ - is_available(): Check if provider is usable
650
+ """
651
+
652
+ def __init__(
653
+ self,
654
+ *,
655
+ cache_ttl_seconds: int = 300,
656
+ enable_cache: bool = True,
657
+ retry_attempts: int = 3,
658
+ retry_delay_seconds: float = 1.0,
659
+ ) -> None:
660
+ """Initialize the provider.
661
+
662
+ Args:
663
+ cache_ttl_seconds: How long to cache tokens.
664
+ enable_cache: Whether to enable caching.
665
+ retry_attempts: Number of retry attempts for token fetch.
666
+ retry_delay_seconds: Delay between retries.
667
+ """
668
+ self._cache: dict[str, OIDCToken] = {}
669
+ self._cache_ttl = timedelta(seconds=cache_ttl_seconds)
670
+ self._enable_cache = enable_cache
671
+ self._retry_attempts = retry_attempts
672
+ self._retry_delay = retry_delay_seconds
673
+
674
+ @property
675
+ @abstractmethod
676
+ def name(self) -> str:
677
+ """Provider name for identification."""
678
+ pass
679
+
680
+ @abstractmethod
681
+ def _fetch_token(self, audience: str | None = None) -> str:
682
+ """Fetch token from the CI environment.
683
+
684
+ Args:
685
+ audience: Token audience.
686
+
687
+ Returns:
688
+ Raw JWT token string.
689
+
690
+ Raises:
691
+ OIDCTokenError: If token fetch fails.
692
+ """
693
+ pass
694
+
695
+ @abstractmethod
696
+ def is_available(self) -> bool:
697
+ """Check if this provider is available in the current environment."""
698
+ pass
699
+
700
+ def get_token(self, audience: str | None = None) -> OIDCToken:
701
+ """Get OIDC token with caching.
702
+
703
+ Args:
704
+ audience: Token audience.
705
+
706
+ Returns:
707
+ OIDCToken containing the JWT.
708
+ """
709
+ if not self.is_available():
710
+ raise OIDCProviderNotAvailableError(
711
+ self.name,
712
+ "Required environment variables not found",
713
+ )
714
+
715
+ cache_key = audience or "_default_"
716
+
717
+ # Check cache
718
+ if self._enable_cache and cache_key in self._cache:
719
+ cached = self._cache[cache_key]
720
+ # Use token if not expired and has >30s remaining
721
+ if not cached.is_expired:
722
+ remaining = cached.claims.time_until_expiry
723
+ if remaining.total_seconds() > 30:
724
+ logger.debug(
725
+ f"Using cached OIDC token for {self.name} "
726
+ f"(expires in {remaining.total_seconds():.0f}s)"
727
+ )
728
+ return cached
729
+
730
+ # Fetch new token with retry
731
+ import time
732
+ last_error: Exception | None = None
733
+
734
+ for attempt in range(self._retry_attempts):
735
+ try:
736
+ jwt = self._fetch_token(audience)
737
+ token = OIDCToken(jwt, provider=self.name)
738
+
739
+ # Cache the token
740
+ if self._enable_cache:
741
+ self._cache[cache_key] = token
742
+
743
+ logger.debug(
744
+ f"Fetched new OIDC token from {self.name} "
745
+ f"(expires in {token.claims.time_until_expiry.total_seconds():.0f}s)"
746
+ )
747
+ return token
748
+
749
+ except Exception as e:
750
+ last_error = e
751
+ if attempt < self._retry_attempts - 1:
752
+ time.sleep(self._retry_delay * (2 ** attempt))
753
+ logger.warning(
754
+ f"Retry {attempt + 1}/{self._retry_attempts} "
755
+ f"for OIDC token from {self.name}: {e}"
756
+ )
757
+
758
+ if last_error:
759
+ if isinstance(last_error, OIDCError):
760
+ raise last_error
761
+ raise OIDCTokenError(str(last_error), provider=self.name) from last_error
762
+ raise OIDCTokenError("Token fetch failed", provider=self.name)
763
+
764
+ def clear_cache(self, audience: str | None = None) -> None:
765
+ """Clear cached tokens.
766
+
767
+ Args:
768
+ audience: Specific audience to clear, or None to clear all.
769
+ """
770
+ if audience is None:
771
+ self._cache.clear()
772
+ else:
773
+ self._cache.pop(audience, None)
774
+
775
+ def __repr__(self) -> str:
776
+ return f"{self.__class__.__name__}(name={self.name!r})"
777
+
778
+
779
+ class BaseTokenExchanger(ABC):
780
+ """Abstract base class for token exchangers.
781
+
782
+ Provides common functionality for cloud token exchange:
783
+ - Credential caching with configurable TTL
784
+ - Retry logic for exchange requests
785
+ - Logging and metrics
786
+
787
+ Subclasses must implement:
788
+ - _exchange(): Perform the actual token exchange
789
+ - cloud_provider property: Target cloud provider
790
+ """
791
+
792
+ def __init__(
793
+ self,
794
+ *,
795
+ cache_ttl_seconds: int = 3600,
796
+ enable_cache: bool = True,
797
+ retry_attempts: int = 3,
798
+ retry_delay_seconds: float = 1.0,
799
+ ) -> None:
800
+ """Initialize the exchanger.
801
+
802
+ Args:
803
+ cache_ttl_seconds: How long to cache credentials.
804
+ enable_cache: Whether to enable caching.
805
+ retry_attempts: Number of retry attempts.
806
+ retry_delay_seconds: Delay between retries.
807
+ """
808
+ self._cache: dict[str, CloudCredentials] = {}
809
+ self._cache_ttl = timedelta(seconds=cache_ttl_seconds)
810
+ self._enable_cache = enable_cache
811
+ self._retry_attempts = retry_attempts
812
+ self._retry_delay = retry_delay_seconds
813
+
814
+ @property
815
+ @abstractmethod
816
+ def cloud_provider(self) -> CloudProvider:
817
+ """Cloud provider this exchanger targets."""
818
+ pass
819
+
820
+ @abstractmethod
821
+ def _exchange(self, token: OIDCToken) -> CloudCredentials:
822
+ """Perform the actual token exchange.
823
+
824
+ Args:
825
+ token: OIDC token to exchange.
826
+
827
+ Returns:
828
+ Cloud provider credentials.
829
+
830
+ Raises:
831
+ OIDCExchangeError: If exchange fails.
832
+ """
833
+ pass
834
+
835
+ def exchange(self, token: OIDCToken) -> CloudCredentials:
836
+ """Exchange OIDC token for cloud credentials with caching.
837
+
838
+ Args:
839
+ token: OIDC token from CI provider.
840
+
841
+ Returns:
842
+ Cloud provider credentials.
843
+ """
844
+ cache_key = token.hash
845
+
846
+ # Check cache
847
+ if self._enable_cache and cache_key in self._cache:
848
+ cached = self._cache[cache_key]
849
+ # Use credentials if not expired and has >60s remaining
850
+ if not cached.is_expired:
851
+ remaining = cached.time_until_expiry
852
+ if remaining and remaining.total_seconds() > 60:
853
+ logger.debug(
854
+ f"Using cached {self.cloud_provider} credentials "
855
+ f"(expires in {remaining.total_seconds():.0f}s)"
856
+ )
857
+ return cached
858
+
859
+ # Exchange token with retry
860
+ import time
861
+ last_error: Exception | None = None
862
+
863
+ for attempt in range(self._retry_attempts):
864
+ try:
865
+ credentials = self._exchange(token)
866
+
867
+ # Cache the credentials
868
+ if self._enable_cache:
869
+ self._cache[cache_key] = credentials
870
+
871
+ logger.debug(
872
+ f"Exchanged OIDC token for {self.cloud_provider} credentials"
873
+ )
874
+ return credentials
875
+
876
+ except Exception as e:
877
+ last_error = e
878
+ if attempt < self._retry_attempts - 1:
879
+ time.sleep(self._retry_delay * (2 ** attempt))
880
+ logger.warning(
881
+ f"Retry {attempt + 1}/{self._retry_attempts} "
882
+ f"for {self.cloud_provider} exchange: {e}"
883
+ )
884
+
885
+ if last_error:
886
+ if isinstance(last_error, OIDCError):
887
+ raise last_error
888
+ raise OIDCExchangeError(
889
+ str(last_error),
890
+ cloud_provider=str(self.cloud_provider),
891
+ ) from last_error
892
+ raise OIDCExchangeError(
893
+ "Exchange failed",
894
+ cloud_provider=str(self.cloud_provider),
895
+ )
896
+
897
+ def clear_cache(self) -> None:
898
+ """Clear cached credentials."""
899
+ self._cache.clear()
900
+
901
+ def __repr__(self) -> str:
902
+ return f"{self.__class__.__name__}(provider={self.cloud_provider!r})"