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,1499 @@
1
+ """OpsGenie integration action.
2
+
3
+ This module provides comprehensive OpsGenie integration for checkpoint notifications
4
+ using the OpsGenie Alert API v2.
5
+
6
+ Features:
7
+ - Full OpsGenie Alert API v2 support
8
+ - Multiple alert actions (create, close, acknowledge, add note)
9
+ - Flexible responder configuration (users, teams, schedules, escalations)
10
+ - Auto-priority mapping from validation severity
11
+ - Deduplication for alert grouping
12
+ - Tag and custom property support
13
+ - Region-aware API endpoints (US, EU)
14
+ - Extensible payload builder pattern
15
+
16
+ Example:
17
+ >>> from truthound.checkpoint.actions import OpsGenieAction
18
+ >>>
19
+ >>> action = OpsGenieAction(
20
+ ... api_key="your-api-key",
21
+ ... notify_on="failure",
22
+ ... auto_priority=True,
23
+ ... responders=[
24
+ ... {"type": "team", "name": "data-quality-team"},
25
+ ... {"type": "user", "username": "admin@example.com"},
26
+ ... ],
27
+ ... tags=["data-quality", "production"],
28
+ ... )
29
+ >>> result = action.execute(checkpoint_result)
30
+
31
+ References:
32
+ - OpsGenie Alert API: https://docs.opsgenie.com/docs/alert-api
33
+ - OpsGenie Integrations: https://docs.opsgenie.com/docs/integration-types-to-use
34
+ """
35
+
36
+ from __future__ import annotations
37
+
38
+ import json
39
+ from abc import ABC, abstractmethod
40
+ from dataclasses import dataclass, field
41
+ from datetime import datetime
42
+ from enum import Enum
43
+ from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable
44
+
45
+ from truthound.checkpoint.actions.base import (
46
+ ActionConfig,
47
+ ActionResult,
48
+ ActionStatus,
49
+ BaseAction,
50
+ NotifyCondition,
51
+ )
52
+
53
+ if TYPE_CHECKING:
54
+ from truthound.checkpoint.checkpoint import CheckpointResult
55
+
56
+
57
+ # =============================================================================
58
+ # Enums and Constants
59
+ # =============================================================================
60
+
61
+
62
+ class OpsGenieRegion(str, Enum):
63
+ """OpsGenie API regions."""
64
+
65
+ US = "us"
66
+ EU = "eu"
67
+
68
+ def __str__(self) -> str:
69
+ return self.value
70
+
71
+ @property
72
+ def api_url(self) -> str:
73
+ """Get API URL for this region."""
74
+ if self == OpsGenieRegion.EU:
75
+ return "https://api.eu.opsgenie.com"
76
+ return "https://api.opsgenie.com"
77
+
78
+
79
+ class AlertPriority(str, Enum):
80
+ """OpsGenie alert priority levels.
81
+
82
+ P1 is the highest priority, P5 is the lowest.
83
+ """
84
+
85
+ P1 = "P1" # Critical
86
+ P2 = "P2" # High
87
+ P3 = "P3" # Moderate
88
+ P4 = "P4" # Low
89
+ P5 = "P5" # Informational
90
+
91
+ def __str__(self) -> str:
92
+ return self.value
93
+
94
+ @classmethod
95
+ def from_severity(cls, severity: str) -> "AlertPriority":
96
+ """Map severity string to priority.
97
+
98
+ Args:
99
+ severity: Severity level (critical, high, medium, low, info).
100
+
101
+ Returns:
102
+ Corresponding AlertPriority.
103
+ """
104
+ mapping = {
105
+ "critical": cls.P1,
106
+ "high": cls.P2,
107
+ "medium": cls.P3,
108
+ "moderate": cls.P3,
109
+ "low": cls.P4,
110
+ "info": cls.P5,
111
+ "informational": cls.P5,
112
+ }
113
+ return mapping.get(severity.lower(), cls.P3)
114
+
115
+
116
+ class AlertAction(str, Enum):
117
+ """OpsGenie alert actions."""
118
+
119
+ CREATE = "create"
120
+ CLOSE = "close"
121
+ ACKNOWLEDGE = "acknowledge"
122
+ UNACKNOWLEDGE = "unacknowledge"
123
+ ADD_NOTE = "add_note"
124
+ SNOOZE = "snooze"
125
+ ESCALATE = "escalate"
126
+ ASSIGN = "assign"
127
+ ADD_TAGS = "add_tags"
128
+ REMOVE_TAGS = "remove_tags"
129
+ ADD_DETAILS = "add_details"
130
+ REMOVE_DETAILS = "remove_details"
131
+
132
+ def __str__(self) -> str:
133
+ return self.value
134
+
135
+
136
+ class ResponderType(str, Enum):
137
+ """Types of OpsGenie responders."""
138
+
139
+ USER = "user"
140
+ TEAM = "team"
141
+ ESCALATION = "escalation"
142
+ SCHEDULE = "schedule"
143
+
144
+ def __str__(self) -> str:
145
+ return self.value
146
+
147
+
148
+ # =============================================================================
149
+ # Responder Configuration
150
+ # =============================================================================
151
+
152
+
153
+ @dataclass
154
+ class Responder:
155
+ """OpsGenie responder configuration.
156
+
157
+ Represents a user, team, escalation, or schedule that should be notified.
158
+
159
+ Attributes:
160
+ type: Type of responder (user, team, escalation, schedule).
161
+ id: Responder ID (optional, use either id or name/username).
162
+ name: Team/escalation/schedule name (for non-user types).
163
+ username: User email/username (for user type).
164
+ """
165
+
166
+ type: ResponderType | str
167
+ id: str | None = None
168
+ name: str | None = None
169
+ username: str | None = None
170
+
171
+ def __post_init__(self) -> None:
172
+ """Validate and normalize responder type."""
173
+ if isinstance(self.type, str):
174
+ self.type = ResponderType(self.type.lower())
175
+
176
+ def to_dict(self) -> dict[str, str]:
177
+ """Convert to OpsGenie API format.
178
+
179
+ Returns:
180
+ Dictionary in OpsGenie responder format.
181
+ """
182
+ result: dict[str, str] = {"type": str(self.type)}
183
+
184
+ if self.id:
185
+ result["id"] = self.id
186
+ elif self.type == ResponderType.USER and self.username:
187
+ result["username"] = self.username
188
+ elif self.name:
189
+ result["name"] = self.name
190
+
191
+ return result
192
+
193
+ @classmethod
194
+ def user(cls, username: str) -> "Responder":
195
+ """Create a user responder.
196
+
197
+ Args:
198
+ username: User email/username.
199
+
200
+ Returns:
201
+ Responder instance for a user.
202
+ """
203
+ return cls(type=ResponderType.USER, username=username)
204
+
205
+ @classmethod
206
+ def team(cls, name: str | None = None, id: str | None = None) -> "Responder":
207
+ """Create a team responder.
208
+
209
+ Args:
210
+ name: Team name.
211
+ id: Team ID.
212
+
213
+ Returns:
214
+ Responder instance for a team.
215
+ """
216
+ return cls(type=ResponderType.TEAM, name=name, id=id)
217
+
218
+ @classmethod
219
+ def escalation(cls, name: str | None = None, id: str | None = None) -> "Responder":
220
+ """Create an escalation responder.
221
+
222
+ Args:
223
+ name: Escalation name.
224
+ id: Escalation ID.
225
+
226
+ Returns:
227
+ Responder instance for an escalation.
228
+ """
229
+ return cls(type=ResponderType.ESCALATION, name=name, id=id)
230
+
231
+ @classmethod
232
+ def schedule(cls, name: str | None = None, id: str | None = None) -> "Responder":
233
+ """Create a schedule responder.
234
+
235
+ Args:
236
+ name: Schedule name.
237
+ id: Schedule ID.
238
+
239
+ Returns:
240
+ Responder instance for a schedule.
241
+ """
242
+ return cls(type=ResponderType.SCHEDULE, name=name, id=id)
243
+
244
+
245
+ # =============================================================================
246
+ # Alert Payload Builder (Builder Pattern)
247
+ # =============================================================================
248
+
249
+
250
+ @runtime_checkable
251
+ class AlertPayloadComponent(Protocol):
252
+ """Protocol for alert payload components."""
253
+
254
+ def apply(self, payload: dict[str, Any]) -> None:
255
+ """Apply this component to the payload."""
256
+ ...
257
+
258
+
259
+ class AlertPayloadBuilder:
260
+ """Fluent builder for creating OpsGenie alert payloads.
261
+
262
+ This builder provides a type-safe, fluent API for constructing
263
+ OpsGenie alert payloads with proper validation.
264
+
265
+ Example:
266
+ >>> payload = (
267
+ ... AlertPayloadBuilder()
268
+ ... .set_message("Data quality check failed")
269
+ ... .set_priority(AlertPriority.P2)
270
+ ... .add_responder(Responder.team("data-team"))
271
+ ... .add_tag("production")
272
+ ... .set_details({"checkpoint": "daily_check"})
273
+ ... .build()
274
+ ... )
275
+ """
276
+
277
+ def __init__(self) -> None:
278
+ """Initialize the builder."""
279
+ self._message: str = ""
280
+ self._alias: str | None = None
281
+ self._description: str | None = None
282
+ self._responders: list[Responder] = []
283
+ self._visible_to: list[Responder] = []
284
+ self._actions: list[str] = []
285
+ self._tags: list[str] = []
286
+ self._details: dict[str, str] = {}
287
+ self._entity: str | None = None
288
+ self._source: str | None = None
289
+ self._priority: AlertPriority = AlertPriority.P3
290
+ self._user: str | None = None
291
+ self._note: str | None = None
292
+
293
+ def set_message(self, message: str) -> "AlertPayloadBuilder":
294
+ """Set the alert message (required, max 130 chars).
295
+
296
+ Args:
297
+ message: Alert message.
298
+
299
+ Returns:
300
+ Self for method chaining.
301
+ """
302
+ self._message = message[:130]
303
+ return self
304
+
305
+ def set_alias(self, alias: str) -> "AlertPayloadBuilder":
306
+ """Set the alert alias for deduplication (max 512 chars).
307
+
308
+ Args:
309
+ alias: Unique alias for grouping alerts.
310
+
311
+ Returns:
312
+ Self for method chaining.
313
+ """
314
+ self._alias = alias[:512]
315
+ return self
316
+
317
+ def set_description(self, description: str) -> "AlertPayloadBuilder":
318
+ """Set the alert description (max 15000 chars).
319
+
320
+ Args:
321
+ description: Detailed description.
322
+
323
+ Returns:
324
+ Self for method chaining.
325
+ """
326
+ self._description = description[:15000]
327
+ return self
328
+
329
+ def add_responder(self, responder: Responder) -> "AlertPayloadBuilder":
330
+ """Add a responder to the alert.
331
+
332
+ Args:
333
+ responder: Responder to notify.
334
+
335
+ Returns:
336
+ Self for method chaining.
337
+ """
338
+ self._responders.append(responder)
339
+ return self
340
+
341
+ def add_responders(self, responders: list[Responder]) -> "AlertPayloadBuilder":
342
+ """Add multiple responders.
343
+
344
+ Args:
345
+ responders: List of responders to notify.
346
+
347
+ Returns:
348
+ Self for method chaining.
349
+ """
350
+ self._responders.extend(responders)
351
+ return self
352
+
353
+ def add_visible_to(self, responder: Responder) -> "AlertPayloadBuilder":
354
+ """Add a responder who can see the alert (without being notified).
355
+
356
+ Args:
357
+ responder: Responder who can view the alert.
358
+
359
+ Returns:
360
+ Self for method chaining.
361
+ """
362
+ self._visible_to.append(responder)
363
+ return self
364
+
365
+ def add_action(self, action: str) -> "AlertPayloadBuilder":
366
+ """Add a custom action button.
367
+
368
+ Args:
369
+ action: Action button text.
370
+
371
+ Returns:
372
+ Self for method chaining.
373
+ """
374
+ self._actions.append(action)
375
+ return self
376
+
377
+ def add_tag(self, tag: str) -> "AlertPayloadBuilder":
378
+ """Add a tag to the alert.
379
+
380
+ Args:
381
+ tag: Tag string.
382
+
383
+ Returns:
384
+ Self for method chaining.
385
+ """
386
+ self._tags.append(tag)
387
+ return self
388
+
389
+ def add_tags(self, tags: list[str]) -> "AlertPayloadBuilder":
390
+ """Add multiple tags.
391
+
392
+ Args:
393
+ tags: List of tags.
394
+
395
+ Returns:
396
+ Self for method chaining.
397
+ """
398
+ self._tags.extend(tags)
399
+ return self
400
+
401
+ def set_details(self, details: dict[str, Any]) -> "AlertPayloadBuilder":
402
+ """Set custom details (key-value pairs, max 8000 chars total).
403
+
404
+ Args:
405
+ details: Dictionary of custom details.
406
+
407
+ Returns:
408
+ Self for method chaining.
409
+ """
410
+ self._details = {str(k): str(v) for k, v in details.items()}
411
+ return self
412
+
413
+ def add_detail(self, key: str, value: Any) -> "AlertPayloadBuilder":
414
+ """Add a single custom detail.
415
+
416
+ Args:
417
+ key: Detail key.
418
+ value: Detail value.
419
+
420
+ Returns:
421
+ Self for method chaining.
422
+ """
423
+ self._details[str(key)] = str(value)
424
+ return self
425
+
426
+ def set_entity(self, entity: str) -> "AlertPayloadBuilder":
427
+ """Set the entity associated with the alert (max 512 chars).
428
+
429
+ Args:
430
+ entity: Entity name (e.g., service, host).
431
+
432
+ Returns:
433
+ Self for method chaining.
434
+ """
435
+ self._entity = entity[:512]
436
+ return self
437
+
438
+ def set_source(self, source: str) -> "AlertPayloadBuilder":
439
+ """Set the alert source (max 100 chars).
440
+
441
+ Args:
442
+ source: Source of the alert.
443
+
444
+ Returns:
445
+ Self for method chaining.
446
+ """
447
+ self._source = source[:100]
448
+ return self
449
+
450
+ def set_priority(self, priority: AlertPriority | str) -> "AlertPayloadBuilder":
451
+ """Set the alert priority.
452
+
453
+ Args:
454
+ priority: Priority level (P1-P5).
455
+
456
+ Returns:
457
+ Self for method chaining.
458
+ """
459
+ if isinstance(priority, str):
460
+ priority = AlertPriority(priority.upper())
461
+ self._priority = priority
462
+ return self
463
+
464
+ def set_user(self, user: str) -> "AlertPayloadBuilder":
465
+ """Set the user who triggered the alert.
466
+
467
+ Args:
468
+ user: Username or email.
469
+
470
+ Returns:
471
+ Self for method chaining.
472
+ """
473
+ self._user = user
474
+ return self
475
+
476
+ def set_note(self, note: str) -> "AlertPayloadBuilder":
477
+ """Set a note to include with the alert.
478
+
479
+ Args:
480
+ note: Note text.
481
+
482
+ Returns:
483
+ Self for method chaining.
484
+ """
485
+ self._note = note
486
+ return self
487
+
488
+ def build(self) -> dict[str, Any]:
489
+ """Build the alert payload.
490
+
491
+ Returns:
492
+ Dictionary representing the OpsGenie alert payload.
493
+
494
+ Raises:
495
+ ValueError: If required fields are missing.
496
+ """
497
+ if not self._message:
498
+ raise ValueError("Message is required")
499
+
500
+ payload: dict[str, Any] = {
501
+ "message": self._message,
502
+ "priority": str(self._priority),
503
+ }
504
+
505
+ if self._alias:
506
+ payload["alias"] = self._alias
507
+ if self._description:
508
+ payload["description"] = self._description
509
+ if self._responders:
510
+ payload["responders"] = [r.to_dict() for r in self._responders]
511
+ if self._visible_to:
512
+ payload["visibleTo"] = [r.to_dict() for r in self._visible_to]
513
+ if self._actions:
514
+ payload["actions"] = self._actions
515
+ if self._tags:
516
+ payload["tags"] = self._tags
517
+ if self._details:
518
+ payload["details"] = self._details
519
+ if self._entity:
520
+ payload["entity"] = self._entity
521
+ if self._source:
522
+ payload["source"] = self._source
523
+ if self._user:
524
+ payload["user"] = self._user
525
+ if self._note:
526
+ payload["note"] = self._note
527
+
528
+ return payload
529
+
530
+
531
+ # =============================================================================
532
+ # Message Templates (Strategy Pattern)
533
+ # =============================================================================
534
+
535
+
536
+ class AlertTemplate(ABC):
537
+ """Abstract base class for alert message templates.
538
+
539
+ Templates define how checkpoint results are formatted into OpsGenie alerts.
540
+ """
541
+
542
+ @abstractmethod
543
+ def build_payload(
544
+ self,
545
+ checkpoint_result: "CheckpointResult",
546
+ config: "OpsGenieConfig",
547
+ ) -> dict[str, Any]:
548
+ """Build alert payload from checkpoint result.
549
+
550
+ Args:
551
+ checkpoint_result: The validation result.
552
+ config: OpsGenie configuration.
553
+
554
+ Returns:
555
+ Alert payload dictionary.
556
+ """
557
+ pass
558
+
559
+
560
+ class DefaultAlertTemplate(AlertTemplate):
561
+ """Default alert template with comprehensive details."""
562
+
563
+ def build_payload(
564
+ self,
565
+ checkpoint_result: "CheckpointResult",
566
+ config: "OpsGenieConfig",
567
+ ) -> dict[str, Any]:
568
+ """Build default alert payload."""
569
+ validation = checkpoint_result.validation_result
570
+ stats = validation.statistics if validation else None
571
+ status = checkpoint_result.status.value
572
+
573
+ # Build message
574
+ message = (
575
+ f"[{status.upper()}] {checkpoint_result.checkpoint_name} - "
576
+ f"{checkpoint_result.data_asset}"
577
+ )
578
+
579
+ # Build description
580
+ description_parts = [
581
+ f"**Checkpoint**: {checkpoint_result.checkpoint_name}",
582
+ f"**Data Asset**: {checkpoint_result.data_asset}",
583
+ f"**Status**: {status}",
584
+ f"**Run ID**: {checkpoint_result.run_id}",
585
+ f"**Time**: {checkpoint_result.run_time.isoformat()}",
586
+ ]
587
+
588
+ if stats:
589
+ description_parts.extend([
590
+ "",
591
+ "**Statistics**:",
592
+ f"- Total Issues: {stats.total_issues}",
593
+ f"- Critical: {stats.critical_issues}",
594
+ f"- High: {stats.high_issues}",
595
+ f"- Medium: {stats.medium_issues}",
596
+ f"- Low: {stats.low_issues}",
597
+ f"- Pass Rate: {stats.pass_rate * 100:.1f}%",
598
+ ])
599
+
600
+ description = "\n".join(description_parts)
601
+
602
+ # Determine priority
603
+ priority = self._determine_priority(stats, config)
604
+
605
+ # Build alias for deduplication
606
+ alias = config.alias_template.format(
607
+ checkpoint=checkpoint_result.checkpoint_name,
608
+ data_asset=checkpoint_result.data_asset,
609
+ run_id=checkpoint_result.run_id,
610
+ )
611
+
612
+ # Build payload using builder
613
+ builder = (
614
+ AlertPayloadBuilder()
615
+ .set_message(message)
616
+ .set_alias(alias)
617
+ .set_description(description)
618
+ .set_priority(priority)
619
+ .set_entity(checkpoint_result.data_asset or "truthound")
620
+ .set_source("truthound")
621
+ .add_tags(list(config.tags))
622
+ )
623
+
624
+ # Add responders
625
+ for responder in config.responders:
626
+ if isinstance(responder, Responder):
627
+ builder.add_responder(responder)
628
+ elif isinstance(responder, dict):
629
+ builder.add_responder(Responder(**responder))
630
+
631
+ # Add custom details
632
+ details = {
633
+ "checkpoint": checkpoint_result.checkpoint_name,
634
+ "data_asset": checkpoint_result.data_asset or "",
635
+ "run_id": checkpoint_result.run_id,
636
+ "status": status,
637
+ }
638
+ if stats:
639
+ details.update({
640
+ "total_issues": str(stats.total_issues),
641
+ "critical_issues": str(stats.critical_issues),
642
+ "high_issues": str(stats.high_issues),
643
+ "medium_issues": str(stats.medium_issues),
644
+ "low_issues": str(stats.low_issues),
645
+ "pass_rate": f"{stats.pass_rate * 100:.1f}%",
646
+ })
647
+ details.update(config.custom_details)
648
+ builder.set_details(details)
649
+
650
+ return builder.build()
651
+
652
+ def _determine_priority(
653
+ self,
654
+ stats: Any,
655
+ config: "OpsGenieConfig",
656
+ ) -> AlertPriority:
657
+ """Determine alert priority based on validation statistics."""
658
+ if not config.auto_priority or not stats:
659
+ return config.priority
660
+
661
+ if stats.critical_issues > 0:
662
+ return AlertPriority.P1
663
+ elif stats.high_issues > 0:
664
+ return AlertPriority.P2
665
+ elif stats.medium_issues > 0:
666
+ return AlertPriority.P3
667
+ elif stats.low_issues > 0:
668
+ return AlertPriority.P4
669
+ else:
670
+ return AlertPriority.P5
671
+
672
+
673
+ class MinimalAlertTemplate(AlertTemplate):
674
+ """Minimal alert template with only essential information."""
675
+
676
+ def build_payload(
677
+ self,
678
+ checkpoint_result: "CheckpointResult",
679
+ config: "OpsGenieConfig",
680
+ ) -> dict[str, Any]:
681
+ """Build minimal alert payload."""
682
+ status = checkpoint_result.status.value
683
+ stats = checkpoint_result.validation_result.statistics if checkpoint_result.validation_result else None
684
+
685
+ message = f"[{status.upper()}] {checkpoint_result.checkpoint_name}"
686
+ if stats and stats.total_issues > 0:
687
+ message += f" ({stats.total_issues} issues)"
688
+
689
+ alias = config.alias_template.format(
690
+ checkpoint=checkpoint_result.checkpoint_name,
691
+ data_asset=checkpoint_result.data_asset or "",
692
+ run_id=checkpoint_result.run_id,
693
+ )
694
+
695
+ builder = (
696
+ AlertPayloadBuilder()
697
+ .set_message(message)
698
+ .set_alias(alias)
699
+ .set_priority(config.priority)
700
+ .set_source("truthound")
701
+ .add_tags(list(config.tags))
702
+ )
703
+
704
+ for responder in config.responders:
705
+ if isinstance(responder, Responder):
706
+ builder.add_responder(responder)
707
+ elif isinstance(responder, dict):
708
+ builder.add_responder(Responder(**responder))
709
+
710
+ return builder.build()
711
+
712
+
713
+ class DetailedAlertTemplate(AlertTemplate):
714
+ """Detailed alert template with extended information and recommendations."""
715
+
716
+ def build_payload(
717
+ self,
718
+ checkpoint_result: "CheckpointResult",
719
+ config: "OpsGenieConfig",
720
+ ) -> dict[str, Any]:
721
+ """Build detailed alert payload."""
722
+ validation = checkpoint_result.validation_result
723
+ stats = validation.statistics if validation else None
724
+ status = checkpoint_result.status.value
725
+
726
+ # Build message
727
+ message = f"Data Quality Alert: {checkpoint_result.checkpoint_name}"
728
+
729
+ # Build comprehensive description
730
+ description_parts = [
731
+ "# Data Quality Validation Alert",
732
+ "",
733
+ "## Overview",
734
+ f"- **Checkpoint**: {checkpoint_result.checkpoint_name}",
735
+ f"- **Data Asset**: {checkpoint_result.data_asset}",
736
+ f"- **Status**: {status.upper()}",
737
+ f"- **Run ID**: {checkpoint_result.run_id}",
738
+ f"- **Timestamp**: {checkpoint_result.run_time.isoformat()}",
739
+ ]
740
+
741
+ if stats:
742
+ description_parts.extend([
743
+ "",
744
+ "## Validation Statistics",
745
+ f"| Metric | Value |",
746
+ f"|--------|-------|",
747
+ f"| Total Issues | {stats.total_issues} |",
748
+ f"| Critical | {stats.critical_issues} |",
749
+ f"| High | {stats.high_issues} |",
750
+ f"| Medium | {stats.medium_issues} |",
751
+ f"| Low | {stats.low_issues} |",
752
+ f"| Pass Rate | {stats.pass_rate * 100:.1f}% |",
753
+ ])
754
+
755
+ # Add failed validations if available
756
+ if validation and validation.results:
757
+ failed_results = [r for r in validation.results if not r.passed]
758
+ if failed_results:
759
+ description_parts.extend([
760
+ "",
761
+ "## Failed Validations",
762
+ ])
763
+ for i, result in enumerate(failed_results[:10], 1):
764
+ description_parts.append(
765
+ f"{i}. **{result.validator_name}** on `{result.column}`: {result.message}"
766
+ )
767
+ if len(failed_results) > 10:
768
+ description_parts.append(f"... and {len(failed_results) - 10} more")
769
+
770
+ description_parts.extend([
771
+ "",
772
+ "## Recommended Actions",
773
+ "1. Review the validation results in detail",
774
+ "2. Investigate the root cause of failures",
775
+ "3. Apply necessary data fixes",
776
+ "4. Re-run validation to confirm resolution",
777
+ ])
778
+
779
+ description = "\n".join(description_parts)
780
+
781
+ # Determine priority
782
+ priority = self._determine_priority(stats, config)
783
+
784
+ alias = config.alias_template.format(
785
+ checkpoint=checkpoint_result.checkpoint_name,
786
+ data_asset=checkpoint_result.data_asset or "",
787
+ run_id=checkpoint_result.run_id,
788
+ )
789
+
790
+ builder = (
791
+ AlertPayloadBuilder()
792
+ .set_message(message)
793
+ .set_alias(alias)
794
+ .set_description(description)
795
+ .set_priority(priority)
796
+ .set_entity(checkpoint_result.data_asset or "truthound")
797
+ .set_source("truthound")
798
+ .add_tags(list(config.tags))
799
+ .add_action("View Dashboard")
800
+ .add_action("Acknowledge")
801
+ .add_action("Escalate")
802
+ )
803
+
804
+ for responder in config.responders:
805
+ if isinstance(responder, Responder):
806
+ builder.add_responder(responder)
807
+ elif isinstance(responder, dict):
808
+ builder.add_responder(Responder(**responder))
809
+
810
+ # Extended details
811
+ details = {
812
+ "checkpoint": checkpoint_result.checkpoint_name,
813
+ "data_asset": checkpoint_result.data_asset or "",
814
+ "run_id": checkpoint_result.run_id,
815
+ "status": status,
816
+ "environment": config.custom_details.get("environment", "unknown"),
817
+ }
818
+ if stats:
819
+ details.update({
820
+ "total_issues": str(stats.total_issues),
821
+ "critical_issues": str(stats.critical_issues),
822
+ "pass_rate": f"{stats.pass_rate * 100:.1f}%",
823
+ })
824
+ details.update(config.custom_details)
825
+ builder.set_details(details)
826
+
827
+ return builder.build()
828
+
829
+ def _determine_priority(
830
+ self,
831
+ stats: Any,
832
+ config: "OpsGenieConfig",
833
+ ) -> AlertPriority:
834
+ """Determine alert priority based on validation statistics."""
835
+ if not config.auto_priority or not stats:
836
+ return config.priority
837
+
838
+ if stats.critical_issues > 0:
839
+ return AlertPriority.P1
840
+ elif stats.high_issues > 0:
841
+ return AlertPriority.P2
842
+ elif stats.medium_issues > 0:
843
+ return AlertPriority.P3
844
+ else:
845
+ return AlertPriority.P4
846
+
847
+
848
+ # Template Registry
849
+ _TEMPLATE_REGISTRY: dict[str, type[AlertTemplate]] = {
850
+ "default": DefaultAlertTemplate,
851
+ "minimal": MinimalAlertTemplate,
852
+ "detailed": DetailedAlertTemplate,
853
+ }
854
+
855
+
856
+ def register_template(name: str, template_class: type[AlertTemplate]) -> None:
857
+ """Register a custom alert template.
858
+
859
+ Args:
860
+ name: Template name for lookup.
861
+ template_class: AlertTemplate subclass.
862
+ """
863
+ _TEMPLATE_REGISTRY[name.lower()] = template_class
864
+
865
+
866
+ def get_template(name: str) -> AlertTemplate:
867
+ """Get a template instance by name.
868
+
869
+ Args:
870
+ name: Template name.
871
+
872
+ Returns:
873
+ AlertTemplate instance.
874
+
875
+ Raises:
876
+ ValueError: If template is not found.
877
+ """
878
+ template_class = _TEMPLATE_REGISTRY.get(name.lower())
879
+ if not template_class:
880
+ available = ", ".join(_TEMPLATE_REGISTRY.keys())
881
+ raise ValueError(f"Unknown template '{name}'. Available: {available}")
882
+ return template_class()
883
+
884
+
885
+ # =============================================================================
886
+ # HTTP Client Abstraction
887
+ # =============================================================================
888
+
889
+
890
+ class OpsGenieHTTPClient:
891
+ """HTTP client for OpsGenie API calls.
892
+
893
+ This abstraction allows for easier testing and customization.
894
+ """
895
+
896
+ def __init__(
897
+ self,
898
+ api_key: str,
899
+ region: OpsGenieRegion = OpsGenieRegion.US,
900
+ timeout: int = 30,
901
+ proxy: str | None = None,
902
+ verify_ssl: bool = True,
903
+ ) -> None:
904
+ """Initialize the HTTP client.
905
+
906
+ Args:
907
+ api_key: OpsGenie API key.
908
+ region: API region (US or EU).
909
+ timeout: Request timeout in seconds.
910
+ proxy: Optional proxy URL.
911
+ verify_ssl: Whether to verify SSL certificates.
912
+ """
913
+ self._api_key = api_key
914
+ self._region = region
915
+ self._timeout = timeout
916
+ self._proxy = proxy
917
+ self._verify_ssl = verify_ssl
918
+
919
+ @property
920
+ def base_url(self) -> str:
921
+ """Get the base API URL."""
922
+ return self._region.api_url
923
+
924
+ def _get_headers(self) -> dict[str, str]:
925
+ """Get request headers."""
926
+ return {
927
+ "Authorization": f"GenieKey {self._api_key}",
928
+ "Content-Type": "application/json",
929
+ "User-Agent": "Truthound/1.0",
930
+ }
931
+
932
+ def post(self, endpoint: str, payload: dict[str, Any]) -> dict[str, Any]:
933
+ """Send POST request to OpsGenie API.
934
+
935
+ Args:
936
+ endpoint: API endpoint path.
937
+ payload: Request payload.
938
+
939
+ Returns:
940
+ Response data.
941
+
942
+ Raises:
943
+ OpsGenieAPIError: If the request fails.
944
+ """
945
+ import urllib.request
946
+ import urllib.error
947
+ import ssl
948
+
949
+ url = f"{self.base_url}{endpoint}"
950
+ data = json.dumps(payload).encode("utf-8")
951
+ headers = self._get_headers()
952
+
953
+ request = urllib.request.Request(url, data=data, headers=headers, method="POST")
954
+
955
+ # Configure SSL
956
+ context: ssl.SSLContext | None = None
957
+ if not self._verify_ssl:
958
+ context = ssl.create_default_context()
959
+ context.check_hostname = False
960
+ context.verify_mode = ssl.CERT_NONE
961
+
962
+ # Configure proxy
963
+ if self._proxy:
964
+ proxy_handler = urllib.request.ProxyHandler({"https": self._proxy})
965
+ opener = urllib.request.build_opener(proxy_handler)
966
+ else:
967
+ opener = urllib.request.build_opener()
968
+
969
+ try:
970
+ with opener.open(request, timeout=self._timeout, context=context) as response:
971
+ response_body = response.read().decode("utf-8")
972
+ return json.loads(response_body) if response_body else {}
973
+ except urllib.error.HTTPError as e:
974
+ error_body = e.read().decode("utf-8") if e.fp else ""
975
+ raise OpsGenieAPIError(
976
+ f"HTTP {e.code}: {e.reason}",
977
+ status_code=e.code,
978
+ response_body=error_body,
979
+ ) from e
980
+ except urllib.error.URLError as e:
981
+ raise OpsGenieAPIError(f"URL Error: {e.reason}") from e
982
+
983
+
984
+ class OpsGenieAPIError(Exception):
985
+ """Exception for OpsGenie API errors."""
986
+
987
+ def __init__(
988
+ self,
989
+ message: str,
990
+ status_code: int | None = None,
991
+ response_body: str | None = None,
992
+ ) -> None:
993
+ """Initialize the error.
994
+
995
+ Args:
996
+ message: Error message.
997
+ status_code: HTTP status code.
998
+ response_body: Raw response body.
999
+ """
1000
+ super().__init__(message)
1001
+ self.status_code = status_code
1002
+ self.response_body = response_body
1003
+
1004
+
1005
+ # =============================================================================
1006
+ # Configuration
1007
+ # =============================================================================
1008
+
1009
+
1010
+ @dataclass
1011
+ class OpsGenieConfig(ActionConfig):
1012
+ """Configuration for OpsGenie action.
1013
+
1014
+ Attributes:
1015
+ api_key: OpsGenie API key (required).
1016
+ region: API region (us, eu).
1017
+ priority: Default alert priority (P1-P5).
1018
+ auto_priority: Automatically map validation severity to priority.
1019
+ responders: List of responders to notify.
1020
+ visible_to: List of responders who can view but not be notified.
1021
+ tags: Tags to attach to alerts.
1022
+ alias_template: Template for generating alert aliases (dedup).
1023
+ close_on_success: Close matching alerts on validation success.
1024
+ acknowledge_on_warning: Acknowledge alerts on warning status.
1025
+ template: Alert template to use (default, minimal, detailed).
1026
+ custom_template: Custom AlertTemplate instance.
1027
+ custom_details: Additional details to include in alerts.
1028
+ source: Source identifier for alerts.
1029
+ entity: Entity associated with alerts.
1030
+ actions: Custom action buttons to include.
1031
+ proxy: Optional proxy URL.
1032
+ verify_ssl: Whether to verify SSL certificates.
1033
+ """
1034
+
1035
+ api_key: str = ""
1036
+ region: OpsGenieRegion | str = OpsGenieRegion.US
1037
+ priority: AlertPriority | str = AlertPriority.P3
1038
+ auto_priority: bool = True
1039
+ responders: list[Responder | dict[str, str]] = field(default_factory=list)
1040
+ visible_to: list[Responder | dict[str, str]] = field(default_factory=list)
1041
+ tags: list[str] = field(default_factory=lambda: ["truthound", "data-quality"])
1042
+ alias_template: str = "truthound_{checkpoint}_{data_asset}"
1043
+ close_on_success: bool = True
1044
+ acknowledge_on_warning: bool = False
1045
+ template: str = "default"
1046
+ custom_template: AlertTemplate | None = None
1047
+ custom_details: dict[str, str] = field(default_factory=dict)
1048
+ source: str = "truthound"
1049
+ entity: str | None = None
1050
+ actions: list[str] = field(default_factory=list)
1051
+ proxy: str | None = None
1052
+ verify_ssl: bool = True
1053
+ notify_on: NotifyCondition | str = NotifyCondition.FAILURE_OR_ERROR
1054
+
1055
+ def __post_init__(self) -> None:
1056
+ """Normalize configuration values."""
1057
+ super().__post_init__()
1058
+
1059
+ if isinstance(self.region, str):
1060
+ self.region = OpsGenieRegion(self.region.lower())
1061
+ if isinstance(self.priority, str):
1062
+ self.priority = AlertPriority(self.priority.upper())
1063
+
1064
+
1065
+ # =============================================================================
1066
+ # Main Action Class
1067
+ # =============================================================================
1068
+
1069
+
1070
+ class OpsGenieAction(BaseAction[OpsGenieConfig]):
1071
+ """Action to create and manage OpsGenie alerts.
1072
+
1073
+ Creates, closes, or acknowledges alerts in OpsGenie based on validation
1074
+ results using the Alert API v2.
1075
+
1076
+ Features:
1077
+ - Full OpsGenie Alert API v2 support
1078
+ - Multiple alert actions (create, close, acknowledge)
1079
+ - Flexible responder configuration
1080
+ - Auto-priority mapping from validation severity
1081
+ - Deduplication for alert grouping
1082
+ - Extensible template system
1083
+
1084
+ Example:
1085
+ >>> # Basic usage
1086
+ >>> action = OpsGenieAction(
1087
+ ... api_key="your-api-key",
1088
+ ... notify_on="failure",
1089
+ ... )
1090
+ >>> result = action.execute(checkpoint_result)
1091
+
1092
+ >>> # With team responders
1093
+ >>> action = OpsGenieAction(
1094
+ ... api_key="your-api-key",
1095
+ ... responders=[
1096
+ ... Responder.team("data-quality-team"),
1097
+ ... Responder.user("admin@example.com"),
1098
+ ... ],
1099
+ ... auto_priority=True,
1100
+ ... tags=["production", "critical-pipeline"],
1101
+ ... )
1102
+
1103
+ >>> # Using custom template
1104
+ >>> action = OpsGenieAction(
1105
+ ... api_key="your-api-key",
1106
+ ... template="detailed",
1107
+ ... close_on_success=True,
1108
+ ... )
1109
+ """
1110
+
1111
+ action_type = "opsgenie"
1112
+
1113
+ def __init__(
1114
+ self,
1115
+ config: OpsGenieConfig | None = None,
1116
+ client: OpsGenieHTTPClient | None = None,
1117
+ **kwargs: Any,
1118
+ ) -> None:
1119
+ """Initialize the action.
1120
+
1121
+ Args:
1122
+ config: OpsGenie configuration.
1123
+ client: Optional HTTP client (for testing).
1124
+ **kwargs: Additional configuration options.
1125
+ """
1126
+ super().__init__(config, **kwargs)
1127
+ self._client = client
1128
+
1129
+ @classmethod
1130
+ def _default_config(cls) -> OpsGenieConfig:
1131
+ """Create default configuration."""
1132
+ return OpsGenieConfig()
1133
+
1134
+ def _get_client(self) -> OpsGenieHTTPClient:
1135
+ """Get or create HTTP client."""
1136
+ if self._client:
1137
+ return self._client
1138
+
1139
+ config = self._config
1140
+ return OpsGenieHTTPClient(
1141
+ api_key=config.api_key,
1142
+ region=config.region if isinstance(config.region, OpsGenieRegion) else OpsGenieRegion(config.region),
1143
+ timeout=config.timeout_seconds,
1144
+ proxy=config.proxy,
1145
+ verify_ssl=config.verify_ssl,
1146
+ )
1147
+
1148
+ def _get_template(self) -> AlertTemplate:
1149
+ """Get the configured alert template."""
1150
+ if self._config.custom_template:
1151
+ return self._config.custom_template
1152
+ return get_template(self._config.template)
1153
+
1154
+ def _execute(self, checkpoint_result: "CheckpointResult") -> ActionResult:
1155
+ """Execute the OpsGenie action."""
1156
+ config = self._config
1157
+
1158
+ if not config.api_key:
1159
+ return ActionResult(
1160
+ action_name=self.name,
1161
+ action_type=self.action_type,
1162
+ status=ActionStatus.ERROR,
1163
+ message="No API key configured",
1164
+ error="api_key is required",
1165
+ )
1166
+
1167
+ status = checkpoint_result.status.value
1168
+ client = self._get_client()
1169
+
1170
+ try:
1171
+ # Determine action based on status
1172
+ if status == "success" and config.close_on_success:
1173
+ return self._close_alert(checkpoint_result, client)
1174
+ elif status == "warning" and config.acknowledge_on_warning:
1175
+ return self._acknowledge_alert(checkpoint_result, client)
1176
+ elif status in ("failure", "error"):
1177
+ return self._create_alert(checkpoint_result, client)
1178
+ else:
1179
+ # Create alert for other statuses
1180
+ return self._create_alert(checkpoint_result, client)
1181
+
1182
+ except OpsGenieAPIError as e:
1183
+ return ActionResult(
1184
+ action_name=self.name,
1185
+ action_type=self.action_type,
1186
+ status=ActionStatus.ERROR,
1187
+ message="OpsGenie API error",
1188
+ error=str(e),
1189
+ details={
1190
+ "status_code": e.status_code,
1191
+ "response": e.response_body,
1192
+ },
1193
+ )
1194
+ except Exception as e:
1195
+ return ActionResult(
1196
+ action_name=self.name,
1197
+ action_type=self.action_type,
1198
+ status=ActionStatus.ERROR,
1199
+ message="Failed to send OpsGenie alert",
1200
+ error=str(e),
1201
+ )
1202
+
1203
+ def _create_alert(
1204
+ self,
1205
+ checkpoint_result: "CheckpointResult",
1206
+ client: OpsGenieHTTPClient,
1207
+ ) -> ActionResult:
1208
+ """Create a new alert."""
1209
+ template = self._get_template()
1210
+ payload = template.build_payload(checkpoint_result, self._config)
1211
+
1212
+ response = client.post("/v2/alerts", payload)
1213
+
1214
+ return ActionResult(
1215
+ action_name=self.name,
1216
+ action_type=self.action_type,
1217
+ status=ActionStatus.SUCCESS,
1218
+ message="OpsGenie alert created",
1219
+ details={
1220
+ "action": "create",
1221
+ "alias": payload.get("alias"),
1222
+ "priority": payload.get("priority"),
1223
+ "request_id": response.get("requestId"),
1224
+ "result": response.get("result"),
1225
+ },
1226
+ )
1227
+
1228
+ def _close_alert(
1229
+ self,
1230
+ checkpoint_result: "CheckpointResult",
1231
+ client: OpsGenieHTTPClient,
1232
+ ) -> ActionResult:
1233
+ """Close an existing alert."""
1234
+ alias = self._config.alias_template.format(
1235
+ checkpoint=checkpoint_result.checkpoint_name,
1236
+ data_asset=checkpoint_result.data_asset or "",
1237
+ run_id=checkpoint_result.run_id,
1238
+ )
1239
+
1240
+ payload = {
1241
+ "source": self._config.source,
1242
+ "user": "truthound",
1243
+ "note": f"Validation succeeded at {checkpoint_result.run_time.isoformat()}",
1244
+ }
1245
+
1246
+ # Use alias identifier
1247
+ endpoint = f"/v2/alerts/{alias}/close?identifierType=alias"
1248
+ response = client.post(endpoint, payload)
1249
+
1250
+ return ActionResult(
1251
+ action_name=self.name,
1252
+ action_type=self.action_type,
1253
+ status=ActionStatus.SUCCESS,
1254
+ message="OpsGenie alert closed",
1255
+ details={
1256
+ "action": "close",
1257
+ "alias": alias,
1258
+ "request_id": response.get("requestId"),
1259
+ },
1260
+ )
1261
+
1262
+ def _acknowledge_alert(
1263
+ self,
1264
+ checkpoint_result: "CheckpointResult",
1265
+ client: OpsGenieHTTPClient,
1266
+ ) -> ActionResult:
1267
+ """Acknowledge an existing alert."""
1268
+ alias = self._config.alias_template.format(
1269
+ checkpoint=checkpoint_result.checkpoint_name,
1270
+ data_asset=checkpoint_result.data_asset or "",
1271
+ run_id=checkpoint_result.run_id,
1272
+ )
1273
+
1274
+ payload = {
1275
+ "source": self._config.source,
1276
+ "user": "truthound",
1277
+ "note": f"Validation warning at {checkpoint_result.run_time.isoformat()}",
1278
+ }
1279
+
1280
+ endpoint = f"/v2/alerts/{alias}/acknowledge?identifierType=alias"
1281
+ response = client.post(endpoint, payload)
1282
+
1283
+ return ActionResult(
1284
+ action_name=self.name,
1285
+ action_type=self.action_type,
1286
+ status=ActionStatus.SUCCESS,
1287
+ message="OpsGenie alert acknowledged",
1288
+ details={
1289
+ "action": "acknowledge",
1290
+ "alias": alias,
1291
+ "request_id": response.get("requestId"),
1292
+ },
1293
+ )
1294
+
1295
+ def validate_config(self) -> list[str]:
1296
+ """Validate the configuration."""
1297
+ errors = []
1298
+
1299
+ if not self._config.api_key:
1300
+ errors.append("api_key is required")
1301
+
1302
+ priority = self._config.priority
1303
+ if isinstance(priority, str):
1304
+ try:
1305
+ AlertPriority(priority.upper())
1306
+ except ValueError:
1307
+ errors.append(f"Invalid priority: {priority}. Must be P1-P5.")
1308
+
1309
+ region = self._config.region
1310
+ if isinstance(region, str):
1311
+ try:
1312
+ OpsGenieRegion(region.lower())
1313
+ except ValueError:
1314
+ errors.append(f"Invalid region: {region}. Must be 'us' or 'eu'.")
1315
+
1316
+ # Validate responders
1317
+ for i, responder in enumerate(self._config.responders):
1318
+ if isinstance(responder, dict):
1319
+ if "type" not in responder:
1320
+ errors.append(f"Responder {i}: 'type' is required")
1321
+ elif responder["type"] not in ("user", "team", "escalation", "schedule"):
1322
+ errors.append(f"Responder {i}: Invalid type '{responder['type']}'")
1323
+
1324
+ return errors
1325
+
1326
+
1327
+ # =============================================================================
1328
+ # Convenience Functions
1329
+ # =============================================================================
1330
+
1331
+
1332
+ def create_opsgenie_action(
1333
+ api_key: str,
1334
+ *,
1335
+ notify_on: NotifyCondition | str = NotifyCondition.FAILURE_OR_ERROR,
1336
+ region: OpsGenieRegion | str = OpsGenieRegion.US,
1337
+ priority: AlertPriority | str = AlertPriority.P3,
1338
+ auto_priority: bool = True,
1339
+ template: str = "default",
1340
+ **kwargs: Any,
1341
+ ) -> OpsGenieAction:
1342
+ """Create an OpsGenie action with common settings.
1343
+
1344
+ Args:
1345
+ api_key: OpsGenie API key.
1346
+ notify_on: When to send notifications.
1347
+ region: API region.
1348
+ priority: Default priority.
1349
+ auto_priority: Auto-map severity to priority.
1350
+ template: Alert template to use.
1351
+ **kwargs: Additional configuration options.
1352
+
1353
+ Returns:
1354
+ Configured OpsGenieAction instance.
1355
+ """
1356
+ return OpsGenieAction(
1357
+ api_key=api_key,
1358
+ notify_on=notify_on,
1359
+ region=region,
1360
+ priority=priority,
1361
+ auto_priority=auto_priority,
1362
+ template=template,
1363
+ **kwargs,
1364
+ )
1365
+
1366
+
1367
+ def create_critical_alert(
1368
+ api_key: str,
1369
+ *,
1370
+ responders: list[Responder | dict[str, str]] | None = None,
1371
+ tags: list[str] | None = None,
1372
+ region: OpsGenieRegion | str = OpsGenieRegion.US,
1373
+ **kwargs: Any,
1374
+ ) -> OpsGenieAction:
1375
+ """Create an action for critical alerts only.
1376
+
1377
+ This action only triggers on failures and errors with P1 priority.
1378
+
1379
+ Args:
1380
+ api_key: OpsGenie API key.
1381
+ responders: List of responders to notify.
1382
+ tags: Tags to attach to alerts.
1383
+ region: API region.
1384
+ **kwargs: Additional configuration options.
1385
+
1386
+ Returns:
1387
+ Configured OpsGenieAction for critical alerts.
1388
+ """
1389
+ default_tags = ["truthound", "critical", "data-quality"]
1390
+ return OpsGenieAction(
1391
+ api_key=api_key,
1392
+ notify_on=NotifyCondition.FAILURE_OR_ERROR,
1393
+ region=region,
1394
+ priority=AlertPriority.P1,
1395
+ auto_priority=False,
1396
+ responders=responders or [],
1397
+ tags=tags or default_tags,
1398
+ template="detailed",
1399
+ close_on_success=True,
1400
+ **kwargs,
1401
+ )
1402
+
1403
+
1404
+ def create_team_alert(
1405
+ api_key: str,
1406
+ team_name: str,
1407
+ *,
1408
+ notify_on: NotifyCondition | str = NotifyCondition.FAILURE_OR_ERROR,
1409
+ region: OpsGenieRegion | str = OpsGenieRegion.US,
1410
+ auto_priority: bool = True,
1411
+ **kwargs: Any,
1412
+ ) -> OpsGenieAction:
1413
+ """Create an action that alerts a specific team.
1414
+
1415
+ Args:
1416
+ api_key: OpsGenie API key.
1417
+ team_name: Name of the team to notify.
1418
+ notify_on: When to send notifications.
1419
+ region: API region.
1420
+ auto_priority: Auto-map severity to priority.
1421
+ **kwargs: Additional configuration options.
1422
+
1423
+ Returns:
1424
+ Configured OpsGenieAction for team alerts.
1425
+ """
1426
+ return OpsGenieAction(
1427
+ api_key=api_key,
1428
+ notify_on=notify_on,
1429
+ region=region,
1430
+ auto_priority=auto_priority,
1431
+ responders=[Responder.team(team_name)],
1432
+ **kwargs,
1433
+ )
1434
+
1435
+
1436
+ def create_escalation_alert(
1437
+ api_key: str,
1438
+ escalation_name: str,
1439
+ *,
1440
+ region: OpsGenieRegion | str = OpsGenieRegion.US,
1441
+ **kwargs: Any,
1442
+ ) -> OpsGenieAction:
1443
+ """Create an action that uses an escalation policy.
1444
+
1445
+ Args:
1446
+ api_key: OpsGenie API key.
1447
+ escalation_name: Name of the escalation policy.
1448
+ region: API region.
1449
+ **kwargs: Additional configuration options.
1450
+
1451
+ Returns:
1452
+ Configured OpsGenieAction with escalation.
1453
+ """
1454
+ return OpsGenieAction(
1455
+ api_key=api_key,
1456
+ notify_on=NotifyCondition.FAILURE_OR_ERROR,
1457
+ region=region,
1458
+ priority=AlertPriority.P1,
1459
+ auto_priority=False,
1460
+ responders=[Responder.escalation(escalation_name)],
1461
+ template="detailed",
1462
+ close_on_success=True,
1463
+ **kwargs,
1464
+ )
1465
+
1466
+
1467
+ # =============================================================================
1468
+ # Exports
1469
+ # =============================================================================
1470
+
1471
+ __all__ = [
1472
+ # Enums
1473
+ "OpsGenieRegion",
1474
+ "AlertPriority",
1475
+ "AlertAction",
1476
+ "ResponderType",
1477
+ # Responder
1478
+ "Responder",
1479
+ # Builder
1480
+ "AlertPayloadBuilder",
1481
+ # Templates
1482
+ "AlertTemplate",
1483
+ "DefaultAlertTemplate",
1484
+ "MinimalAlertTemplate",
1485
+ "DetailedAlertTemplate",
1486
+ "register_template",
1487
+ "get_template",
1488
+ # HTTP
1489
+ "OpsGenieHTTPClient",
1490
+ "OpsGenieAPIError",
1491
+ # Config & Action
1492
+ "OpsGenieConfig",
1493
+ "OpsGenieAction",
1494
+ # Factory functions
1495
+ "create_opsgenie_action",
1496
+ "create_critical_alert",
1497
+ "create_team_alert",
1498
+ "create_escalation_alert",
1499
+ ]