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,1162 @@
1
+ """Translation Management System (TMS) Integration.
2
+
3
+ This module provides integration with external translation management systems
4
+ for enterprise-grade internationalization workflows.
5
+
6
+ Supported TMS Providers:
7
+ - Crowdin (crowdin.com)
8
+ - Lokalise (lokalise.com)
9
+ - Phrase (phrase.com, formerly PhraseApp)
10
+ - Transifex (transifex.com)
11
+ - POEditor (poeditor.com)
12
+
13
+ Features:
14
+ - Catalog synchronization (push/pull)
15
+ - Translation status tracking
16
+ - Webhook support for real-time updates
17
+ - Batch operations
18
+ - Rate limiting and retry logic
19
+
20
+ Usage:
21
+ from truthound.validators.i18n.tms import (
22
+ CrowdinProvider,
23
+ LokaliseProvider,
24
+ TMSManager,
25
+ )
26
+
27
+ # Create provider
28
+ provider = CrowdinProvider(
29
+ api_key="your-api-key",
30
+ project_id="your-project-id",
31
+ )
32
+
33
+ # Sync translations
34
+ updated = provider.sync_catalog(LocaleInfo.parse("ko"), local_catalog)
35
+
36
+ # Check translation status
37
+ status = provider.get_translation_status(LocaleInfo.parse("ko"))
38
+ print(f"Korean translation: {status['validators']:.1%} complete")
39
+ """
40
+
41
+ from __future__ import annotations
42
+
43
+ import hashlib
44
+ import hmac
45
+ import json
46
+ import logging
47
+ import threading
48
+ import time
49
+ from abc import ABC, abstractmethod
50
+ from dataclasses import dataclass, field
51
+ from datetime import datetime, timedelta
52
+ from enum import Enum
53
+ from pathlib import Path
54
+ from typing import Any, Callable
55
+ from urllib.parse import urljoin
56
+
57
+ from truthound.validators.i18n.protocols import (
58
+ BaseTranslationService,
59
+ LocaleInfo,
60
+ )
61
+
62
+
63
+ logger = logging.getLogger(__name__)
64
+
65
+
66
+ # ==============================================================================
67
+ # TMS Configuration and Types
68
+ # ==============================================================================
69
+
70
+ class TMSProvider(str, Enum):
71
+ """Supported TMS providers."""
72
+ CROWDIN = "crowdin"
73
+ LOKALISE = "lokalise"
74
+ PHRASE = "phrase"
75
+ TRANSIFEX = "transifex"
76
+ POEDITOR = "poeditor"
77
+ CUSTOM = "custom"
78
+
79
+
80
+ @dataclass
81
+ class TMSConfig:
82
+ """Configuration for TMS integration.
83
+
84
+ Attributes:
85
+ provider: TMS provider type
86
+ api_key: API key or token
87
+ project_id: Project identifier
88
+ base_url: API base URL (optional, for self-hosted)
89
+ source_locale: Source locale for translations
90
+ file_format: File format for uploads (json, xliff, etc.)
91
+ rate_limit: Maximum requests per second
92
+ retry_attempts: Number of retry attempts
93
+ retry_delay: Initial retry delay in seconds
94
+ webhook_secret: Secret for webhook verification
95
+ timeout: Request timeout in seconds
96
+ """
97
+ provider: TMSProvider
98
+ api_key: str
99
+ project_id: str
100
+ base_url: str | None = None
101
+ source_locale: str = "en"
102
+ file_format: str = "json"
103
+ rate_limit: float = 10.0
104
+ retry_attempts: int = 3
105
+ retry_delay: float = 1.0
106
+ webhook_secret: str | None = None
107
+ timeout: int = 30
108
+
109
+
110
+ @dataclass
111
+ class TranslationKey:
112
+ """A translation key with metadata.
113
+
114
+ Attributes:
115
+ key: Unique key identifier
116
+ source: Source text
117
+ context: Context or description
118
+ tags: Tags for categorization
119
+ max_length: Maximum allowed length
120
+ pluralizable: Whether key supports pluralization
121
+ """
122
+ key: str
123
+ source: str
124
+ context: str = ""
125
+ tags: list[str] = field(default_factory=list)
126
+ max_length: int | None = None
127
+ pluralizable: bool = False
128
+
129
+
130
+ @dataclass
131
+ class TranslationEntry:
132
+ """A translation entry from TMS.
133
+
134
+ Attributes:
135
+ key: Translation key
136
+ source: Source text
137
+ target: Translated text
138
+ locale: Target locale
139
+ is_reviewed: Whether translation is reviewed
140
+ is_approved: Whether translation is approved
141
+ translator: Translator identifier
142
+ updated_at: Last update time
143
+ """
144
+ key: str
145
+ source: str
146
+ target: str
147
+ locale: str
148
+ is_reviewed: bool = False
149
+ is_approved: bool = False
150
+ translator: str | None = None
151
+ updated_at: datetime | None = None
152
+
153
+
154
+ @dataclass
155
+ class TranslationStatus:
156
+ """Translation status for a locale.
157
+
158
+ Attributes:
159
+ locale: Target locale
160
+ total_keys: Total number of keys
161
+ translated: Number of translated keys
162
+ reviewed: Number of reviewed keys
163
+ approved: Number of approved keys
164
+ progress: Overall progress percentage
165
+ last_activity: Last activity time
166
+ """
167
+ locale: str
168
+ total_keys: int = 0
169
+ translated: int = 0
170
+ reviewed: int = 0
171
+ approved: int = 0
172
+ progress: float = 0.0
173
+ last_activity: datetime | None = None
174
+
175
+ @property
176
+ def completion_rate(self) -> float:
177
+ """Get completion rate as percentage."""
178
+ if self.total_keys == 0:
179
+ return 0.0
180
+ return self.translated / self.total_keys
181
+
182
+
183
+ @dataclass
184
+ class WebhookEvent:
185
+ """Webhook event from TMS.
186
+
187
+ Attributes:
188
+ event_type: Type of event
189
+ project_id: Project identifier
190
+ locale: Affected locale
191
+ keys: Affected translation keys
192
+ payload: Raw event payload
193
+ timestamp: Event timestamp
194
+ signature: Event signature for verification
195
+ """
196
+ event_type: str
197
+ project_id: str
198
+ locale: str | None = None
199
+ keys: list[str] = field(default_factory=list)
200
+ payload: dict[str, Any] = field(default_factory=dict)
201
+ timestamp: datetime = field(default_factory=datetime.now)
202
+ signature: str | None = None
203
+
204
+
205
+ # ==============================================================================
206
+ # Rate Limiter
207
+ # ==============================================================================
208
+
209
+ class RateLimiter:
210
+ """Token bucket rate limiter.
211
+
212
+ Implements token bucket algorithm for rate limiting API requests.
213
+ """
214
+
215
+ def __init__(self, rate: float, burst: int | None = None) -> None:
216
+ """Initialize rate limiter.
217
+
218
+ Args:
219
+ rate: Tokens per second
220
+ burst: Maximum burst size (default: 2x rate)
221
+ """
222
+ self.rate = rate
223
+ self.burst = burst or int(rate * 2)
224
+ self.tokens = float(self.burst)
225
+ self.last_update = time.monotonic()
226
+ self._lock = threading.Lock()
227
+
228
+ def acquire(self, tokens: int = 1) -> float:
229
+ """Acquire tokens, blocking if necessary.
230
+
231
+ Args:
232
+ tokens: Number of tokens to acquire
233
+
234
+ Returns:
235
+ Time waited in seconds
236
+ """
237
+ with self._lock:
238
+ now = time.monotonic()
239
+ elapsed = now - self.last_update
240
+ self.tokens = min(self.burst, self.tokens + elapsed * self.rate)
241
+ self.last_update = now
242
+
243
+ if self.tokens >= tokens:
244
+ self.tokens -= tokens
245
+ return 0.0
246
+
247
+ # Need to wait
248
+ wait_time = (tokens - self.tokens) / self.rate
249
+ time.sleep(wait_time)
250
+
251
+ now = time.monotonic()
252
+ elapsed = now - self.last_update
253
+ self.tokens = min(self.burst, self.tokens + elapsed * self.rate)
254
+ self.tokens -= tokens
255
+ self.last_update = now
256
+
257
+ return wait_time
258
+
259
+ def try_acquire(self, tokens: int = 1) -> bool:
260
+ """Try to acquire tokens without blocking.
261
+
262
+ Args:
263
+ tokens: Number of tokens to acquire
264
+
265
+ Returns:
266
+ True if tokens were acquired
267
+ """
268
+ with self._lock:
269
+ now = time.monotonic()
270
+ elapsed = now - self.last_update
271
+ self.tokens = min(self.burst, self.tokens + elapsed * self.rate)
272
+ self.last_update = now
273
+
274
+ if self.tokens >= tokens:
275
+ self.tokens -= tokens
276
+ return True
277
+ return False
278
+
279
+
280
+ # ==============================================================================
281
+ # HTTP Client (Abstract)
282
+ # ==============================================================================
283
+
284
+ class HTTPClient(ABC):
285
+ """Abstract HTTP client for TMS API calls."""
286
+
287
+ @abstractmethod
288
+ def get(self, url: str, headers: dict | None = None) -> dict[str, Any]:
289
+ """Make GET request."""
290
+ pass
291
+
292
+ @abstractmethod
293
+ def post(self, url: str, data: dict | None = None, headers: dict | None = None) -> dict[str, Any]:
294
+ """Make POST request."""
295
+ pass
296
+
297
+ @abstractmethod
298
+ def put(self, url: str, data: dict | None = None, headers: dict | None = None) -> dict[str, Any]:
299
+ """Make PUT request."""
300
+ pass
301
+
302
+ @abstractmethod
303
+ def delete(self, url: str, headers: dict | None = None) -> dict[str, Any]:
304
+ """Make DELETE request."""
305
+ pass
306
+
307
+
308
+ class MockHTTPClient(HTTPClient):
309
+ """Mock HTTP client for testing."""
310
+
311
+ def __init__(self) -> None:
312
+ self.responses: dict[str, Any] = {}
313
+ self.requests: list[dict] = []
314
+
315
+ def add_response(self, method: str, url: str, response: dict[str, Any]) -> None:
316
+ """Add a mock response."""
317
+ key = f"{method}:{url}"
318
+ self.responses[key] = response
319
+
320
+ def get(self, url: str, headers: dict | None = None) -> dict[str, Any]:
321
+ self.requests.append({"method": "GET", "url": url, "headers": headers})
322
+ return self.responses.get(f"GET:{url}", {})
323
+
324
+ def post(self, url: str, data: dict | None = None, headers: dict | None = None) -> dict[str, Any]:
325
+ self.requests.append({"method": "POST", "url": url, "data": data, "headers": headers})
326
+ return self.responses.get(f"POST:{url}", {})
327
+
328
+ def put(self, url: str, data: dict | None = None, headers: dict | None = None) -> dict[str, Any]:
329
+ self.requests.append({"method": "PUT", "url": url, "data": data, "headers": headers})
330
+ return self.responses.get(f"PUT:{url}", {})
331
+
332
+ def delete(self, url: str, headers: dict | None = None) -> dict[str, Any]:
333
+ self.requests.append({"method": "DELETE", "url": url, "headers": headers})
334
+ return self.responses.get(f"DELETE:{url}", {})
335
+
336
+
337
+ # ==============================================================================
338
+ # Base TMS Provider
339
+ # ==============================================================================
340
+
341
+ class BaseTMSProvider(BaseTranslationService, ABC):
342
+ """Base class for TMS provider implementations.
343
+
344
+ Provides common functionality for all TMS integrations:
345
+ - Rate limiting
346
+ - Retry logic
347
+ - Error handling
348
+ - Webhook verification
349
+ """
350
+
351
+ def __init__(
352
+ self,
353
+ config: TMSConfig,
354
+ http_client: HTTPClient | None = None,
355
+ ) -> None:
356
+ """Initialize provider.
357
+
358
+ Args:
359
+ config: TMS configuration
360
+ http_client: HTTP client (for testing)
361
+ """
362
+ super().__init__(config.api_key, config.project_id)
363
+ self.config = config
364
+ self.http_client = http_client or MockHTTPClient()
365
+ self.rate_limiter = RateLimiter(config.rate_limit)
366
+
367
+ def _make_request(
368
+ self,
369
+ method: str,
370
+ endpoint: str,
371
+ data: dict | None = None,
372
+ headers: dict | None = None,
373
+ ) -> dict[str, Any]:
374
+ """Make an API request with rate limiting and retries.
375
+
376
+ Args:
377
+ method: HTTP method
378
+ endpoint: API endpoint
379
+ data: Request data
380
+ headers: Request headers
381
+
382
+ Returns:
383
+ Response data
384
+ """
385
+ # Rate limit
386
+ self.rate_limiter.acquire()
387
+
388
+ # Build URL
389
+ base_url = self.config.base_url or self._get_default_base_url()
390
+ url = urljoin(base_url, endpoint)
391
+
392
+ # Add auth headers
393
+ headers = headers or {}
394
+ headers.update(self._get_auth_headers())
395
+
396
+ # Retry loop
397
+ last_error = None
398
+ for attempt in range(self.config.retry_attempts):
399
+ try:
400
+ if method == "GET":
401
+ return self.http_client.get(url, headers)
402
+ elif method == "POST":
403
+ return self.http_client.post(url, data, headers)
404
+ elif method == "PUT":
405
+ return self.http_client.put(url, data, headers)
406
+ elif method == "DELETE":
407
+ return self.http_client.delete(url, headers)
408
+ else:
409
+ raise ValueError(f"Unsupported method: {method}")
410
+ except Exception as e:
411
+ last_error = e
412
+ if attempt < self.config.retry_attempts - 1:
413
+ delay = self.config.retry_delay * (2 ** attempt)
414
+ logger.warning(f"Request failed, retrying in {delay}s: {e}")
415
+ time.sleep(delay)
416
+
417
+ raise last_error or Exception("Request failed")
418
+
419
+ @abstractmethod
420
+ def _get_default_base_url(self) -> str:
421
+ """Get the default API base URL."""
422
+ pass
423
+
424
+ @abstractmethod
425
+ def _get_auth_headers(self) -> dict[str, str]:
426
+ """Get authentication headers."""
427
+ pass
428
+
429
+ def verify_webhook(self, payload: bytes, signature: str) -> bool:
430
+ """Verify webhook signature.
431
+
432
+ Args:
433
+ payload: Raw webhook payload
434
+ signature: Provided signature
435
+
436
+ Returns:
437
+ True if signature is valid
438
+ """
439
+ if not self.config.webhook_secret:
440
+ logger.warning("No webhook secret configured")
441
+ return False
442
+
443
+ expected = hmac.new(
444
+ self.config.webhook_secret.encode(),
445
+ payload,
446
+ hashlib.sha256,
447
+ ).hexdigest()
448
+
449
+ return hmac.compare_digest(expected, signature)
450
+
451
+ def parse_webhook(self, payload: dict[str, Any]) -> WebhookEvent:
452
+ """Parse a webhook payload into an event.
453
+
454
+ Args:
455
+ payload: Webhook payload
456
+
457
+ Returns:
458
+ WebhookEvent
459
+ """
460
+ return WebhookEvent(
461
+ event_type=payload.get("event", "unknown"),
462
+ project_id=payload.get("project_id", ""),
463
+ locale=payload.get("language"),
464
+ keys=payload.get("keys", []),
465
+ payload=payload,
466
+ )
467
+
468
+
469
+ # ==============================================================================
470
+ # Crowdin Provider
471
+ # ==============================================================================
472
+
473
+ class CrowdinProvider(BaseTMSProvider):
474
+ """Crowdin TMS integration.
475
+
476
+ Crowdin API v2 implementation.
477
+ See: https://developer.crowdin.com/api/v2/
478
+ """
479
+
480
+ def __init__(
481
+ self,
482
+ api_key: str,
483
+ project_id: str,
484
+ organization: str | None = None,
485
+ **kwargs: Any,
486
+ ) -> None:
487
+ """Initialize Crowdin provider.
488
+
489
+ Args:
490
+ api_key: Crowdin API token
491
+ project_id: Crowdin project ID
492
+ organization: Organization domain (for enterprise)
493
+ **kwargs: Additional config options
494
+ """
495
+ config = TMSConfig(
496
+ provider=TMSProvider.CROWDIN,
497
+ api_key=api_key,
498
+ project_id=project_id,
499
+ base_url=f"https://{organization}.api.crowdin.com" if organization else None,
500
+ **kwargs,
501
+ )
502
+ super().__init__(config)
503
+ self.organization = organization
504
+
505
+ def _get_default_base_url(self) -> str:
506
+ return "https://api.crowdin.com/api/v2/"
507
+
508
+ def _get_auth_headers(self) -> dict[str, str]:
509
+ return {
510
+ "Authorization": f"Bearer {self.config.api_key}",
511
+ "Content-Type": "application/json",
512
+ }
513
+
514
+ def sync_catalog(
515
+ self,
516
+ locale: LocaleInfo,
517
+ catalog: dict[str, str],
518
+ ) -> dict[str, str]:
519
+ """Sync local catalog with Crowdin.
520
+
521
+ Downloads translations for the locale and merges with local catalog.
522
+
523
+ Args:
524
+ locale: Target locale
525
+ catalog: Local message catalog
526
+
527
+ Returns:
528
+ Updated catalog with Crowdin translations
529
+ """
530
+ # Get language ID
531
+ language_id = self._locale_to_crowdin(locale)
532
+
533
+ # Build export request
534
+ endpoint = f"projects/{self.config.project_id}/translations/builds"
535
+ data = {
536
+ "targetLanguageIds": [language_id],
537
+ }
538
+
539
+ try:
540
+ # Request translation export
541
+ build_response = self._make_request("POST", endpoint, data)
542
+ build_id = build_response.get("data", {}).get("id")
543
+
544
+ if build_id:
545
+ # Download translations
546
+ download_endpoint = f"projects/{self.config.project_id}/translations/builds/{build_id}/download"
547
+ download_response = self._make_request("GET", download_endpoint)
548
+
549
+ # Parse and merge translations
550
+ translations = self._parse_translations(download_response)
551
+ result = catalog.copy()
552
+ result.update(translations)
553
+ return result
554
+
555
+ except Exception as e:
556
+ logger.error(f"Failed to sync with Crowdin: {e}")
557
+
558
+ return catalog
559
+
560
+ def push_new_keys(
561
+ self,
562
+ keys: list[str],
563
+ source_locale: LocaleInfo,
564
+ source_messages: dict[str, str],
565
+ ) -> bool:
566
+ """Push new keys to Crowdin.
567
+
568
+ Args:
569
+ keys: New translation keys
570
+ source_locale: Source locale
571
+ source_messages: Source message templates
572
+
573
+ Returns:
574
+ True if successful
575
+ """
576
+ if not keys:
577
+ return True
578
+
579
+ endpoint = f"projects/{self.config.project_id}/strings"
580
+
581
+ try:
582
+ for key in keys:
583
+ if key in source_messages:
584
+ data = {
585
+ "text": source_messages[key],
586
+ "identifier": key,
587
+ "context": f"Validator message: {key}",
588
+ }
589
+ self._make_request("POST", endpoint, data)
590
+
591
+ return True
592
+ except Exception as e:
593
+ logger.error(f"Failed to push keys to Crowdin: {e}")
594
+ return False
595
+
596
+ def get_translation_status(
597
+ self,
598
+ locale: LocaleInfo,
599
+ ) -> dict[str, float]:
600
+ """Get translation status from Crowdin.
601
+
602
+ Args:
603
+ locale: Target locale
604
+
605
+ Returns:
606
+ Dictionary with completion percentages
607
+ """
608
+ language_id = self._locale_to_crowdin(locale)
609
+ endpoint = f"projects/{self.config.project_id}/languages/{language_id}/progress"
610
+
611
+ try:
612
+ response = self._make_request("GET", endpoint)
613
+ data = response.get("data", [])
614
+
615
+ # Aggregate by file/namespace
616
+ result: dict[str, float] = {}
617
+ for item in data:
618
+ file_id = item.get("fileId", "default")
619
+ progress = item.get("translationProgress", 0)
620
+ result[str(file_id)] = progress / 100.0
621
+
622
+ # Overall progress
623
+ if data:
624
+ result["overall"] = sum(item.get("translationProgress", 0) for item in data) / len(data) / 100.0
625
+ else:
626
+ result["overall"] = 0.0
627
+
628
+ return result
629
+
630
+ except Exception as e:
631
+ logger.error(f"Failed to get Crowdin status: {e}")
632
+ return {"overall": 0.0}
633
+
634
+ def _locale_to_crowdin(self, locale: LocaleInfo) -> str:
635
+ """Convert LocaleInfo to Crowdin language ID."""
636
+ # Crowdin uses different format for some locales
637
+ mapping = {
638
+ "zh-Hans": "zh-CN",
639
+ "zh-Hant": "zh-TW",
640
+ "pt-BR": "pt-BR",
641
+ "pt-PT": "pt-PT",
642
+ }
643
+ return mapping.get(locale.tag, locale.tag)
644
+
645
+ def _parse_translations(self, response: dict) -> dict[str, str]:
646
+ """Parse Crowdin translation response."""
647
+ # This would parse the actual Crowdin file format
648
+ # For now, return empty dict as placeholder
649
+ return response.get("translations", {})
650
+
651
+
652
+ # ==============================================================================
653
+ # Lokalise Provider
654
+ # ==============================================================================
655
+
656
+ class LokaliseProvider(BaseTMSProvider):
657
+ """Lokalise TMS integration.
658
+
659
+ Lokalise API v2 implementation.
660
+ See: https://developers.lokalise.com/reference/
661
+ """
662
+
663
+ def __init__(
664
+ self,
665
+ api_key: str,
666
+ project_id: str,
667
+ **kwargs: Any,
668
+ ) -> None:
669
+ """Initialize Lokalise provider.
670
+
671
+ Args:
672
+ api_key: Lokalise API token
673
+ project_id: Lokalise project ID
674
+ **kwargs: Additional config options
675
+ """
676
+ config = TMSConfig(
677
+ provider=TMSProvider.LOKALISE,
678
+ api_key=api_key,
679
+ project_id=project_id,
680
+ **kwargs,
681
+ )
682
+ super().__init__(config)
683
+
684
+ def _get_default_base_url(self) -> str:
685
+ return "https://api.lokalise.com/api2/"
686
+
687
+ def _get_auth_headers(self) -> dict[str, str]:
688
+ return {
689
+ "X-Api-Token": self.config.api_key,
690
+ "Content-Type": "application/json",
691
+ }
692
+
693
+ def sync_catalog(
694
+ self,
695
+ locale: LocaleInfo,
696
+ catalog: dict[str, str],
697
+ ) -> dict[str, str]:
698
+ """Sync with Lokalise."""
699
+ # Get keys with translations
700
+ endpoint = f"projects/{self.config.project_id}/keys"
701
+ params = {
702
+ "include_translations": 1,
703
+ "filter_langs": self._locale_to_lokalise(locale),
704
+ }
705
+
706
+ try:
707
+ response = self._make_request("GET", endpoint, params)
708
+ keys = response.get("keys", [])
709
+
710
+ result = catalog.copy()
711
+ for key in keys:
712
+ key_name = key.get("key_name", {})
713
+ if isinstance(key_name, dict):
714
+ key_id = key_name.get("web") or key_name.get("other")
715
+ else:
716
+ key_id = key_name
717
+
718
+ translations = key.get("translations", [])
719
+ for trans in translations:
720
+ if trans.get("language_iso") == self._locale_to_lokalise(locale):
721
+ result[key_id] = trans.get("translation", "")
722
+
723
+ return result
724
+
725
+ except Exception as e:
726
+ logger.error(f"Failed to sync with Lokalise: {e}")
727
+ return catalog
728
+
729
+ def push_new_keys(
730
+ self,
731
+ keys: list[str],
732
+ source_locale: LocaleInfo,
733
+ source_messages: dict[str, str],
734
+ ) -> bool:
735
+ """Push new keys to Lokalise."""
736
+ if not keys:
737
+ return True
738
+
739
+ endpoint = f"projects/{self.config.project_id}/keys"
740
+ lang_iso = self._locale_to_lokalise(source_locale)
741
+
742
+ try:
743
+ key_data = []
744
+ for key in keys:
745
+ if key in source_messages:
746
+ key_data.append({
747
+ "key_name": key,
748
+ "platforms": ["web"],
749
+ "translations": [{
750
+ "language_iso": lang_iso,
751
+ "translation": source_messages[key],
752
+ }],
753
+ })
754
+
755
+ if key_data:
756
+ self._make_request("POST", endpoint, {"keys": key_data})
757
+
758
+ return True
759
+ except Exception as e:
760
+ logger.error(f"Failed to push keys to Lokalise: {e}")
761
+ return False
762
+
763
+ def get_translation_status(
764
+ self,
765
+ locale: LocaleInfo,
766
+ ) -> dict[str, float]:
767
+ """Get translation status from Lokalise."""
768
+ endpoint = f"projects/{self.config.project_id}/languages"
769
+
770
+ try:
771
+ response = self._make_request("GET", endpoint)
772
+ languages = response.get("languages", [])
773
+
774
+ target_iso = self._locale_to_lokalise(locale)
775
+ for lang in languages:
776
+ if lang.get("lang_iso") == target_iso:
777
+ progress = lang.get("progress", 0)
778
+ return {
779
+ "overall": progress / 100.0,
780
+ "words_total": lang.get("words_total", 0),
781
+ "words_done": lang.get("words_done", 0),
782
+ }
783
+
784
+ return {"overall": 0.0}
785
+
786
+ except Exception as e:
787
+ logger.error(f"Failed to get Lokalise status: {e}")
788
+ return {"overall": 0.0}
789
+
790
+ def _locale_to_lokalise(self, locale: LocaleInfo) -> str:
791
+ """Convert LocaleInfo to Lokalise language ISO."""
792
+ if locale.region:
793
+ return f"{locale.language}_{locale.region}"
794
+ return locale.language
795
+
796
+
797
+ # ==============================================================================
798
+ # Phrase (PhraseApp) Provider
799
+ # ==============================================================================
800
+
801
+ class PhraseProvider(BaseTMSProvider):
802
+ """Phrase TMS integration.
803
+
804
+ Phrase Strings API v2 implementation.
805
+ See: https://developers.phrase.com/api/
806
+ """
807
+
808
+ def __init__(
809
+ self,
810
+ api_key: str,
811
+ project_id: str,
812
+ **kwargs: Any,
813
+ ) -> None:
814
+ """Initialize Phrase provider."""
815
+ config = TMSConfig(
816
+ provider=TMSProvider.PHRASE,
817
+ api_key=api_key,
818
+ project_id=project_id,
819
+ **kwargs,
820
+ )
821
+ super().__init__(config)
822
+
823
+ def _get_default_base_url(self) -> str:
824
+ return "https://api.phrase.com/v2/"
825
+
826
+ def _get_auth_headers(self) -> dict[str, str]:
827
+ return {
828
+ "Authorization": f"token {self.config.api_key}",
829
+ "Content-Type": "application/json",
830
+ }
831
+
832
+ def sync_catalog(
833
+ self,
834
+ locale: LocaleInfo,
835
+ catalog: dict[str, str],
836
+ ) -> dict[str, str]:
837
+ """Sync with Phrase."""
838
+ endpoint = f"projects/{self.config.project_id}/locales/{locale.tag}/download"
839
+ params = {"file_format": "nested_json"}
840
+
841
+ try:
842
+ response = self._make_request("GET", endpoint, params)
843
+ result = catalog.copy()
844
+ result.update(self._flatten_translations(response))
845
+ return result
846
+ except Exception as e:
847
+ logger.error(f"Failed to sync with Phrase: {e}")
848
+ return catalog
849
+
850
+ def push_new_keys(
851
+ self,
852
+ keys: list[str],
853
+ source_locale: LocaleInfo,
854
+ source_messages: dict[str, str],
855
+ ) -> bool:
856
+ """Push new keys to Phrase."""
857
+ endpoint = f"projects/{self.config.project_id}/keys"
858
+
859
+ try:
860
+ for key in keys:
861
+ if key in source_messages:
862
+ data = {
863
+ "name": key,
864
+ "description": f"Validator message: {key}",
865
+ "default_translation_content": source_messages[key],
866
+ }
867
+ self._make_request("POST", endpoint, data)
868
+ return True
869
+ except Exception as e:
870
+ logger.error(f"Failed to push keys to Phrase: {e}")
871
+ return False
872
+
873
+ def get_translation_status(
874
+ self,
875
+ locale: LocaleInfo,
876
+ ) -> dict[str, float]:
877
+ """Get translation status from Phrase."""
878
+ endpoint = f"projects/{self.config.project_id}/locales/{locale.tag}"
879
+
880
+ try:
881
+ response = self._make_request("GET", endpoint)
882
+ stats = response.get("statistics", {})
883
+ return {
884
+ "overall": stats.get("translations_completed_progress", 0) / 100.0,
885
+ "keys_total": stats.get("keys_total_count", 0),
886
+ "translations_completed": stats.get("translations_completed_count", 0),
887
+ }
888
+ except Exception as e:
889
+ logger.error(f"Failed to get Phrase status: {e}")
890
+ return {"overall": 0.0}
891
+
892
+ def _flatten_translations(self, nested: dict) -> dict[str, str]:
893
+ """Flatten nested translation structure."""
894
+ result = {}
895
+
896
+ def _flatten(obj: Any, prefix: str = "") -> None:
897
+ if isinstance(obj, dict):
898
+ for k, v in obj.items():
899
+ new_key = f"{prefix}.{k}" if prefix else k
900
+ _flatten(v, new_key)
901
+ else:
902
+ result[prefix] = str(obj)
903
+
904
+ _flatten(nested)
905
+ return result
906
+
907
+
908
+ # ==============================================================================
909
+ # TMS Manager
910
+ # ==============================================================================
911
+
912
+ class TMSManager:
913
+ """Manager for TMS operations.
914
+
915
+ Provides a high-level interface for managing translations across
916
+ multiple TMS providers.
917
+
918
+ Example:
919
+ manager = TMSManager()
920
+
921
+ # Add providers
922
+ manager.add_provider("crowdin", CrowdinProvider(...))
923
+ manager.add_provider("lokalise", LokaliseProvider(...))
924
+
925
+ # Sync translations
926
+ catalog = manager.sync_all(LocaleInfo.parse("ko"), local_catalog)
927
+
928
+ # Push new keys
929
+ manager.push_new_keys(["new.key"], LocaleInfo.parse("en"), source_messages)
930
+ """
931
+
932
+ def __init__(self) -> None:
933
+ self._providers: dict[str, BaseTMSProvider] = {}
934
+ self._webhooks: dict[str, Callable[[WebhookEvent], None]] = {}
935
+
936
+ def add_provider(self, name: str, provider: BaseTMSProvider) -> None:
937
+ """Add a TMS provider.
938
+
939
+ Args:
940
+ name: Provider identifier
941
+ provider: TMS provider instance
942
+ """
943
+ self._providers[name] = provider
944
+
945
+ def remove_provider(self, name: str) -> None:
946
+ """Remove a TMS provider.
947
+
948
+ Args:
949
+ name: Provider identifier
950
+ """
951
+ self._providers.pop(name, None)
952
+
953
+ def get_provider(self, name: str) -> BaseTMSProvider | None:
954
+ """Get a TMS provider by name.
955
+
956
+ Args:
957
+ name: Provider identifier
958
+
959
+ Returns:
960
+ Provider instance or None
961
+ """
962
+ return self._providers.get(name)
963
+
964
+ def sync_catalog(
965
+ self,
966
+ locale: LocaleInfo,
967
+ catalog: dict[str, str],
968
+ provider_name: str | None = None,
969
+ ) -> dict[str, str]:
970
+ """Sync catalog with TMS.
971
+
972
+ Args:
973
+ locale: Target locale
974
+ catalog: Local message catalog
975
+ provider_name: Specific provider (or all if None)
976
+
977
+ Returns:
978
+ Updated catalog
979
+ """
980
+ result = catalog.copy()
981
+
982
+ providers = (
983
+ [self._providers[provider_name]] if provider_name
984
+ else self._providers.values()
985
+ )
986
+
987
+ for provider in providers:
988
+ try:
989
+ result = provider.sync_catalog(locale, result)
990
+ except Exception as e:
991
+ logger.error(f"Failed to sync with {provider.__class__.__name__}: {e}")
992
+
993
+ return result
994
+
995
+ def push_new_keys(
996
+ self,
997
+ keys: list[str],
998
+ source_locale: LocaleInfo,
999
+ source_messages: dict[str, str],
1000
+ provider_name: str | None = None,
1001
+ ) -> bool:
1002
+ """Push new keys to TMS.
1003
+
1004
+ Args:
1005
+ keys: New translation keys
1006
+ source_locale: Source locale
1007
+ source_messages: Source message templates
1008
+ provider_name: Specific provider (or all if None)
1009
+
1010
+ Returns:
1011
+ True if all pushes succeeded
1012
+ """
1013
+ providers = (
1014
+ [self._providers[provider_name]] if provider_name
1015
+ else self._providers.values()
1016
+ )
1017
+
1018
+ success = True
1019
+ for provider in providers:
1020
+ try:
1021
+ if not provider.push_new_keys(keys, source_locale, source_messages):
1022
+ success = False
1023
+ except Exception as e:
1024
+ logger.error(f"Failed to push to {provider.__class__.__name__}: {e}")
1025
+ success = False
1026
+
1027
+ return success
1028
+
1029
+ def get_translation_status(
1030
+ self,
1031
+ locale: LocaleInfo,
1032
+ provider_name: str | None = None,
1033
+ ) -> dict[str, dict[str, float]]:
1034
+ """Get translation status from TMS.
1035
+
1036
+ Args:
1037
+ locale: Target locale
1038
+ provider_name: Specific provider (or all if None)
1039
+
1040
+ Returns:
1041
+ Dictionary of provider name -> status dict
1042
+ """
1043
+ if provider_name:
1044
+ providers = {provider_name: self._providers[provider_name]}
1045
+ else:
1046
+ providers = self._providers
1047
+
1048
+ result = {}
1049
+ for name, provider in providers.items():
1050
+ try:
1051
+ result[name] = provider.get_translation_status(locale)
1052
+ except Exception as e:
1053
+ logger.error(f"Failed to get status from {name}: {e}")
1054
+ result[name] = {"overall": 0.0, "error": str(e)}
1055
+
1056
+ return result
1057
+
1058
+ def register_webhook_handler(
1059
+ self,
1060
+ provider_name: str,
1061
+ handler: Callable[[WebhookEvent], None],
1062
+ ) -> None:
1063
+ """Register a webhook event handler.
1064
+
1065
+ Args:
1066
+ provider_name: Provider identifier
1067
+ handler: Event handler function
1068
+ """
1069
+ self._webhooks[provider_name] = handler
1070
+
1071
+ def handle_webhook(
1072
+ self,
1073
+ provider_name: str,
1074
+ payload: bytes,
1075
+ signature: str | None = None,
1076
+ ) -> bool:
1077
+ """Handle incoming webhook.
1078
+
1079
+ Args:
1080
+ provider_name: Provider identifier
1081
+ payload: Raw webhook payload
1082
+ signature: Webhook signature
1083
+
1084
+ Returns:
1085
+ True if handled successfully
1086
+ """
1087
+ provider = self._providers.get(provider_name)
1088
+ if not provider:
1089
+ logger.error(f"Unknown provider: {provider_name}")
1090
+ return False
1091
+
1092
+ # Verify signature if provided
1093
+ if signature and not provider.verify_webhook(payload, signature):
1094
+ logger.error("Invalid webhook signature")
1095
+ return False
1096
+
1097
+ # Parse event
1098
+ try:
1099
+ payload_dict = json.loads(payload)
1100
+ event = provider.parse_webhook(payload_dict)
1101
+ except Exception as e:
1102
+ logger.error(f"Failed to parse webhook: {e}")
1103
+ return False
1104
+
1105
+ # Call handler
1106
+ handler = self._webhooks.get(provider_name)
1107
+ if handler:
1108
+ try:
1109
+ handler(event)
1110
+ return True
1111
+ except Exception as e:
1112
+ logger.error(f"Webhook handler error: {e}")
1113
+ return False
1114
+
1115
+ return True
1116
+
1117
+
1118
+ # ==============================================================================
1119
+ # Factory Functions
1120
+ # ==============================================================================
1121
+
1122
+ def create_provider(
1123
+ provider_type: TMSProvider | str,
1124
+ api_key: str,
1125
+ project_id: str,
1126
+ **kwargs: Any,
1127
+ ) -> BaseTMSProvider:
1128
+ """Create a TMS provider.
1129
+
1130
+ Args:
1131
+ provider_type: Provider type
1132
+ api_key: API key
1133
+ project_id: Project ID
1134
+ **kwargs: Provider-specific options
1135
+
1136
+ Returns:
1137
+ TMS provider instance
1138
+ """
1139
+ if isinstance(provider_type, str):
1140
+ provider_type = TMSProvider(provider_type.lower())
1141
+
1142
+ if provider_type == TMSProvider.CROWDIN:
1143
+ return CrowdinProvider(api_key, project_id, **kwargs)
1144
+ elif provider_type == TMSProvider.LOKALISE:
1145
+ return LokaliseProvider(api_key, project_id, **kwargs)
1146
+ elif provider_type == TMSProvider.PHRASE:
1147
+ return PhraseProvider(api_key, project_id, **kwargs)
1148
+ else:
1149
+ raise ValueError(f"Unsupported provider: {provider_type}")
1150
+
1151
+
1152
+ # Global manager instance
1153
+ _tms_manager = TMSManager()
1154
+
1155
+
1156
+ def get_tms_manager() -> TMSManager:
1157
+ """Get the global TMS manager.
1158
+
1159
+ Returns:
1160
+ TMSManager instance
1161
+ """
1162
+ return _tms_manager