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,1020 @@
1
+ """Dynamic Catalog Loading and Context-Based Messages.
2
+
3
+ This module provides lazy loading of message catalogs and context-aware
4
+ message resolution for enterprise i18n needs.
5
+
6
+ Features:
7
+ - Lazy loading (only load catalogs when needed)
8
+ - Multiple storage backends (filesystem, package resources, URL)
9
+ - Caching with TTL and LRU eviction
10
+ - Namespace support for modular catalogs
11
+ - Context-based message selection
12
+ - Message inheritance and composition
13
+
14
+ Usage:
15
+ from truthound.validators.i18n.loader import (
16
+ CatalogManager,
17
+ MessageContext,
18
+ ContextResolver,
19
+ )
20
+
21
+ # Create manager with lazy loading
22
+ manager = CatalogManager(
23
+ base_path=Path("locales"),
24
+ lazy=True,
25
+ )
26
+
27
+ # Load catalog on demand
28
+ catalog = manager.get_catalog(LocaleInfo.parse("ko"))
29
+
30
+ # Context-based resolution
31
+ resolver = ContextResolver(manager)
32
+ message = resolver.resolve(
33
+ key="validation.failed",
34
+ locale=LocaleInfo.parse("ko"),
35
+ context=MessageContext.FORMAL,
36
+ )
37
+ """
38
+
39
+ from __future__ import annotations
40
+
41
+ import json
42
+ import logging
43
+ import threading
44
+ import time
45
+ import weakref
46
+ from collections import OrderedDict
47
+ from dataclasses import dataclass, field
48
+ from enum import Enum
49
+ from functools import lru_cache
50
+ from pathlib import Path
51
+ from typing import Any, Callable, Iterator
52
+
53
+ from truthound.validators.i18n.protocols import (
54
+ BaseCatalogLoader,
55
+ LocaleInfo,
56
+ MessageContext,
57
+ MessageResolver,
58
+ PluralizedMessage,
59
+ ResolvedMessage,
60
+ )
61
+ from truthound.validators.i18n.plural import get_plural_category, PluralCategory
62
+
63
+
64
+ logger = logging.getLogger(__name__)
65
+
66
+
67
+ # ==============================================================================
68
+ # Cache Implementation
69
+ # ==============================================================================
70
+
71
+ @dataclass
72
+ class CacheEntry:
73
+ """Cache entry with metadata.
74
+
75
+ Attributes:
76
+ value: Cached value
77
+ created_at: Creation timestamp
78
+ accessed_at: Last access timestamp
79
+ ttl: Time-to-live in seconds
80
+ """
81
+ value: Any
82
+ created_at: float = field(default_factory=time.time)
83
+ accessed_at: float = field(default_factory=time.time)
84
+ ttl: float = 3600.0 # 1 hour default
85
+
86
+ @property
87
+ def is_expired(self) -> bool:
88
+ """Check if entry is expired."""
89
+ return time.time() - self.created_at > self.ttl
90
+
91
+ def touch(self) -> None:
92
+ """Update last access time."""
93
+ self.accessed_at = time.time()
94
+
95
+
96
+ class LRUCache:
97
+ """Thread-safe LRU cache with TTL support.
98
+
99
+ Implements a Least Recently Used cache with optional
100
+ time-based expiration for entries.
101
+ """
102
+
103
+ def __init__(
104
+ self,
105
+ max_size: int = 100,
106
+ default_ttl: float = 3600.0,
107
+ ) -> None:
108
+ """Initialize cache.
109
+
110
+ Args:
111
+ max_size: Maximum number of entries
112
+ default_ttl: Default TTL in seconds
113
+ """
114
+ self.max_size = max_size
115
+ self.default_ttl = default_ttl
116
+ self._cache: OrderedDict[str, CacheEntry] = OrderedDict()
117
+ self._lock = threading.RLock()
118
+
119
+ def get(self, key: str) -> Any | None:
120
+ """Get value from cache.
121
+
122
+ Args:
123
+ key: Cache key
124
+
125
+ Returns:
126
+ Cached value or None if not found/expired
127
+ """
128
+ with self._lock:
129
+ entry = self._cache.get(key)
130
+ if entry is None:
131
+ return None
132
+
133
+ if entry.is_expired:
134
+ del self._cache[key]
135
+ return None
136
+
137
+ # Move to end (most recently used)
138
+ entry.touch()
139
+ self._cache.move_to_end(key)
140
+ return entry.value
141
+
142
+ def set(
143
+ self,
144
+ key: str,
145
+ value: Any,
146
+ ttl: float | None = None,
147
+ ) -> None:
148
+ """Set value in cache.
149
+
150
+ Args:
151
+ key: Cache key
152
+ value: Value to cache
153
+ ttl: TTL in seconds (default from constructor)
154
+ """
155
+ with self._lock:
156
+ # Remove oldest if at capacity
157
+ while len(self._cache) >= self.max_size:
158
+ self._cache.popitem(last=False)
159
+
160
+ self._cache[key] = CacheEntry(
161
+ value=value,
162
+ ttl=ttl or self.default_ttl,
163
+ )
164
+
165
+ def delete(self, key: str) -> None:
166
+ """Delete entry from cache.
167
+
168
+ Args:
169
+ key: Cache key
170
+ """
171
+ with self._lock:
172
+ self._cache.pop(key, None)
173
+
174
+ def clear(self) -> None:
175
+ """Clear all entries."""
176
+ with self._lock:
177
+ self._cache.clear()
178
+
179
+ def cleanup_expired(self) -> int:
180
+ """Remove all expired entries.
181
+
182
+ Returns:
183
+ Number of entries removed
184
+ """
185
+ with self._lock:
186
+ expired = [k for k, v in self._cache.items() if v.is_expired]
187
+ for key in expired:
188
+ del self._cache[key]
189
+ return len(expired)
190
+
191
+ def __len__(self) -> int:
192
+ return len(self._cache)
193
+
194
+ def __contains__(self, key: str) -> bool:
195
+ with self._lock:
196
+ entry = self._cache.get(key)
197
+ return entry is not None and not entry.is_expired
198
+
199
+
200
+ # ==============================================================================
201
+ # Catalog Storage Backends
202
+ # ==============================================================================
203
+
204
+ class CatalogStorageBackend:
205
+ """Base class for catalog storage backends."""
206
+
207
+ def load(
208
+ self,
209
+ locale: LocaleInfo,
210
+ namespace: str | None = None,
211
+ ) -> dict[str, str]:
212
+ """Load catalog from storage.
213
+
214
+ Args:
215
+ locale: Target locale
216
+ namespace: Optional namespace
217
+
218
+ Returns:
219
+ Message dictionary
220
+ """
221
+ raise NotImplementedError
222
+
223
+ def save(
224
+ self,
225
+ locale: LocaleInfo,
226
+ catalog: dict[str, str],
227
+ namespace: str | None = None,
228
+ ) -> None:
229
+ """Save catalog to storage.
230
+
231
+ Args:
232
+ locale: Target locale
233
+ catalog: Message dictionary
234
+ namespace: Optional namespace
235
+ """
236
+ raise NotImplementedError
237
+
238
+ def exists(
239
+ self,
240
+ locale: LocaleInfo,
241
+ namespace: str | None = None,
242
+ ) -> bool:
243
+ """Check if catalog exists.
244
+
245
+ Args:
246
+ locale: Target locale
247
+ namespace: Optional namespace
248
+
249
+ Returns:
250
+ True if catalog exists
251
+ """
252
+ raise NotImplementedError
253
+
254
+ def list_locales(self, namespace: str | None = None) -> list[LocaleInfo]:
255
+ """List available locales.
256
+
257
+ Args:
258
+ namespace: Optional namespace filter
259
+
260
+ Returns:
261
+ List of available locales
262
+ """
263
+ raise NotImplementedError
264
+
265
+
266
+ class FileSystemStorage(CatalogStorageBackend):
267
+ """Filesystem-based catalog storage.
268
+
269
+ Stores catalogs as JSON files in a directory structure:
270
+ base_path/
271
+ en/
272
+ validators.json
273
+ errors.json
274
+ ko/
275
+ validators.json
276
+ errors.json
277
+ """
278
+
279
+ def __init__(
280
+ self,
281
+ base_path: Path | str,
282
+ file_extension: str = ".json",
283
+ encoding: str = "utf-8",
284
+ ) -> None:
285
+ """Initialize filesystem storage.
286
+
287
+ Args:
288
+ base_path: Base directory for catalogs
289
+ file_extension: File extension
290
+ encoding: File encoding
291
+ """
292
+ self.base_path = Path(base_path)
293
+ self.file_extension = file_extension
294
+ self.encoding = encoding
295
+
296
+ def _get_path(self, locale: LocaleInfo, namespace: str | None) -> Path:
297
+ """Get path for a catalog file."""
298
+ locale_dir = self.base_path / locale.tag.replace("-", "_")
299
+ filename = f"{namespace or 'messages'}{self.file_extension}"
300
+ return locale_dir / filename
301
+
302
+ def load(
303
+ self,
304
+ locale: LocaleInfo,
305
+ namespace: str | None = None,
306
+ ) -> dict[str, str]:
307
+ path = self._get_path(locale, namespace)
308
+
309
+ if not path.exists():
310
+ # Try fallback paths
311
+ fallback_paths = [
312
+ self.base_path / locale.language / f"{namespace or 'messages'}{self.file_extension}",
313
+ self.base_path / f"{locale.language}{self.file_extension}",
314
+ ]
315
+ for fallback in fallback_paths:
316
+ if fallback.exists():
317
+ path = fallback
318
+ break
319
+ else:
320
+ return {}
321
+
322
+ try:
323
+ with open(path, "r", encoding=self.encoding) as f:
324
+ data = json.load(f)
325
+
326
+ # Handle nested JSON structure
327
+ if "messages" in data:
328
+ return data["messages"]
329
+ return data
330
+
331
+ except Exception as e:
332
+ logger.error(f"Failed to load catalog from {path}: {e}")
333
+ return {}
334
+
335
+ def save(
336
+ self,
337
+ locale: LocaleInfo,
338
+ catalog: dict[str, str],
339
+ namespace: str | None = None,
340
+ ) -> None:
341
+ path = self._get_path(locale, namespace)
342
+ path.parent.mkdir(parents=True, exist_ok=True)
343
+
344
+ try:
345
+ with open(path, "w", encoding=self.encoding) as f:
346
+ json.dump(
347
+ {"locale": locale.tag, "messages": catalog},
348
+ f,
349
+ ensure_ascii=False,
350
+ indent=2,
351
+ )
352
+ except Exception as e:
353
+ logger.error(f"Failed to save catalog to {path}: {e}")
354
+
355
+ def exists(
356
+ self,
357
+ locale: LocaleInfo,
358
+ namespace: str | None = None,
359
+ ) -> bool:
360
+ path = self._get_path(locale, namespace)
361
+ return path.exists()
362
+
363
+ def list_locales(self, namespace: str | None = None) -> list[LocaleInfo]:
364
+ if not self.base_path.exists():
365
+ return []
366
+
367
+ locales = []
368
+ for item in self.base_path.iterdir():
369
+ if item.is_dir():
370
+ locale_tag = item.name.replace("_", "-")
371
+ try:
372
+ locale = LocaleInfo.parse(locale_tag)
373
+ if namespace is None or self.exists(locale, namespace):
374
+ locales.append(locale)
375
+ except Exception:
376
+ continue
377
+
378
+ return locales
379
+
380
+
381
+ class MemoryStorage(CatalogStorageBackend):
382
+ """In-memory catalog storage for testing."""
383
+
384
+ def __init__(self) -> None:
385
+ self._catalogs: dict[str, dict[str, str]] = {}
386
+
387
+ def _key(self, locale: LocaleInfo, namespace: str | None) -> str:
388
+ return f"{locale.tag}:{namespace or 'default'}"
389
+
390
+ def load(
391
+ self,
392
+ locale: LocaleInfo,
393
+ namespace: str | None = None,
394
+ ) -> dict[str, str]:
395
+ return self._catalogs.get(self._key(locale, namespace), {})
396
+
397
+ def save(
398
+ self,
399
+ locale: LocaleInfo,
400
+ catalog: dict[str, str],
401
+ namespace: str | None = None,
402
+ ) -> None:
403
+ self._catalogs[self._key(locale, namespace)] = catalog.copy()
404
+
405
+ def exists(
406
+ self,
407
+ locale: LocaleInfo,
408
+ namespace: str | None = None,
409
+ ) -> bool:
410
+ return self._key(locale, namespace) in self._catalogs
411
+
412
+ def list_locales(self, namespace: str | None = None) -> list[LocaleInfo]:
413
+ locales = []
414
+ ns = namespace or "default"
415
+ for key in self._catalogs:
416
+ parts = key.split(":")
417
+ if len(parts) == 2 and parts[1] == ns:
418
+ try:
419
+ locales.append(LocaleInfo.parse(parts[0]))
420
+ except Exception:
421
+ continue
422
+ return locales
423
+
424
+
425
+ # ==============================================================================
426
+ # Catalog Manager
427
+ # ==============================================================================
428
+
429
+ class CatalogManager(BaseCatalogLoader):
430
+ """Manager for message catalog loading with lazy loading support.
431
+
432
+ Features:
433
+ - Lazy loading of catalogs on first access
434
+ - Caching with configurable TTL
435
+ - Multiple storage backend support
436
+ - Namespace support for modular catalogs
437
+ - Automatic fallback chain resolution
438
+
439
+ Example:
440
+ manager = CatalogManager(
441
+ base_path=Path("locales"),
442
+ lazy=True,
443
+ cache_ttl=3600,
444
+ )
445
+
446
+ # First access loads the catalog
447
+ catalog = manager.get_catalog(LocaleInfo.parse("ko"))
448
+
449
+ # Subsequent accesses use cache
450
+ catalog = manager.get_catalog(LocaleInfo.parse("ko"))
451
+
452
+ # Preload all catalogs
453
+ manager.preload(["en", "ko", "ja"])
454
+ """
455
+
456
+ def __init__(
457
+ self,
458
+ base_path: Path | str | None = None,
459
+ storage: CatalogStorageBackend | None = None,
460
+ lazy: bool = True,
461
+ cache_ttl: float = 3600.0,
462
+ cache_size: int = 50,
463
+ fallback_locale: str = "en",
464
+ ) -> None:
465
+ """Initialize catalog manager.
466
+
467
+ Args:
468
+ base_path: Path for filesystem storage
469
+ storage: Custom storage backend
470
+ lazy: Enable lazy loading
471
+ cache_ttl: Cache TTL in seconds
472
+ cache_size: Maximum cached catalogs
473
+ fallback_locale: Fallback locale code
474
+ """
475
+ super().__init__()
476
+
477
+ if storage:
478
+ self.storage = storage
479
+ elif base_path:
480
+ self.storage = FileSystemStorage(base_path)
481
+ else:
482
+ self.storage = MemoryStorage()
483
+
484
+ self.lazy = lazy
485
+ self.fallback_locale = LocaleInfo.parse(fallback_locale)
486
+ self._cache = LRUCache(max_size=cache_size, default_ttl=cache_ttl)
487
+ self._loading_locks: dict[str, threading.Lock] = {}
488
+ self._lock = threading.Lock()
489
+
490
+ def _get_loading_lock(self, key: str) -> threading.Lock:
491
+ """Get or create a loading lock for a catalog."""
492
+ with self._lock:
493
+ if key not in self._loading_locks:
494
+ self._loading_locks[key] = threading.Lock()
495
+ return self._loading_locks[key]
496
+
497
+ def _do_load(
498
+ self,
499
+ locale: LocaleInfo,
500
+ namespace: str | None = None,
501
+ ) -> dict[str, str]:
502
+ """Load catalog from storage."""
503
+ return self.storage.load(locale, namespace)
504
+
505
+ def get_catalog(
506
+ self,
507
+ locale: LocaleInfo,
508
+ namespace: str | None = None,
509
+ with_fallback: bool = True,
510
+ ) -> dict[str, str]:
511
+ """Get catalog for a locale.
512
+
513
+ Args:
514
+ locale: Target locale
515
+ namespace: Optional namespace
516
+ with_fallback: Include fallback locale messages
517
+
518
+ Returns:
519
+ Message dictionary
520
+ """
521
+ cache_key = f"{locale.tag}:{namespace or 'default'}"
522
+
523
+ # Check cache
524
+ cached = self._cache.get(cache_key)
525
+ if cached is not None:
526
+ return cached
527
+
528
+ # Load with lock to prevent duplicate loads
529
+ lock = self._get_loading_lock(cache_key)
530
+ with lock:
531
+ # Double-check after acquiring lock
532
+ cached = self._cache.get(cache_key)
533
+ if cached is not None:
534
+ return cached
535
+
536
+ # Load catalog
537
+ catalog = self._do_load(locale, namespace)
538
+
539
+ # Apply fallback if needed
540
+ if with_fallback and locale != self.fallback_locale:
541
+ fallback = self._do_load(self.fallback_locale, namespace)
542
+ catalog = {**fallback, **catalog}
543
+
544
+ # Cache and return
545
+ self._cache.set(cache_key, catalog)
546
+ return catalog
547
+
548
+ def load(
549
+ self,
550
+ locale: LocaleInfo,
551
+ namespace: str | None = None,
552
+ ) -> dict[str, str]:
553
+ """Load catalog (implementation of BaseCatalogLoader)."""
554
+ return self.get_catalog(locale, namespace)
555
+
556
+ def preload(
557
+ self,
558
+ locales: list[str | LocaleInfo],
559
+ namespaces: list[str] | None = None,
560
+ ) -> None:
561
+ """Preload catalogs for multiple locales.
562
+
563
+ Args:
564
+ locales: List of locales to preload
565
+ namespaces: Optional list of namespaces
566
+ """
567
+ namespaces = namespaces or [None] # type: ignore
568
+
569
+ for locale in locales:
570
+ if isinstance(locale, str):
571
+ locale = LocaleInfo.parse(locale)
572
+ for namespace in namespaces:
573
+ self.get_catalog(locale, namespace)
574
+
575
+ def reload(
576
+ self,
577
+ locale: LocaleInfo | None = None,
578
+ namespace: str | None = None,
579
+ ) -> None:
580
+ """Reload catalogs from storage.
581
+
582
+ Args:
583
+ locale: Specific locale to reload (or all if None)
584
+ namespace: Specific namespace to reload (or all if None)
585
+ """
586
+ if locale and namespace:
587
+ cache_key = f"{locale.tag}:{namespace}"
588
+ self._cache.delete(cache_key)
589
+ self.get_catalog(locale, namespace)
590
+ elif locale:
591
+ # Reload all namespaces for locale
592
+ for key in list(self._loaded_catalogs.keys()):
593
+ if key.startswith(locale.tag):
594
+ self._cache.delete(key)
595
+ else:
596
+ # Reload all
597
+ self._cache.clear()
598
+
599
+ def get_available_locales(self) -> list[LocaleInfo]:
600
+ """Get list of available locales."""
601
+ return self.storage.list_locales()
602
+
603
+ def add_catalog(
604
+ self,
605
+ locale: LocaleInfo,
606
+ catalog: dict[str, str],
607
+ namespace: str | None = None,
608
+ ) -> None:
609
+ """Add a catalog programmatically.
610
+
611
+ Args:
612
+ locale: Target locale
613
+ catalog: Message dictionary
614
+ namespace: Optional namespace
615
+ """
616
+ self.storage.save(locale, catalog, namespace)
617
+ cache_key = f"{locale.tag}:{namespace or 'default'}"
618
+ self._cache.delete(cache_key)
619
+
620
+
621
+ # ==============================================================================
622
+ # Context-Based Message Resolution
623
+ # ==============================================================================
624
+
625
+ @dataclass
626
+ class ContextualMessage:
627
+ """Message with context variants.
628
+
629
+ Attributes:
630
+ key: Message key
631
+ default: Default message template
632
+ contexts: Context-specific variants
633
+ """
634
+ key: str
635
+ default: str
636
+ contexts: dict[MessageContext, str] = field(default_factory=dict)
637
+
638
+ def get(self, context: MessageContext | None = None) -> str:
639
+ """Get message for context.
640
+
641
+ Args:
642
+ context: Message context
643
+
644
+ Returns:
645
+ Appropriate message template
646
+ """
647
+ if context and context in self.contexts:
648
+ return self.contexts[context]
649
+ return self.default
650
+
651
+
652
+ class ContextResolver(MessageResolver):
653
+ """Context-aware message resolver.
654
+
655
+ Resolves messages considering:
656
+ - User context (formal, informal, technical, etc.)
657
+ - Pluralization
658
+ - Locale fallback
659
+ - Message inheritance
660
+
661
+ Example:
662
+ resolver = ContextResolver(catalog_manager)
663
+
664
+ # Resolve with context
665
+ result = resolver.resolve(
666
+ key="greeting",
667
+ locale=LocaleInfo.parse("ko"),
668
+ context=MessageContext.FORMAL,
669
+ name="김철수",
670
+ )
671
+ # -> "김철수님, 안녕하십니까"
672
+
673
+ # Resolve with plural
674
+ result = resolver.resolve_plural(
675
+ key="file_count",
676
+ count=5,
677
+ locale=LocaleInfo.parse("ru"),
678
+ )
679
+ # -> "5 файлов"
680
+ """
681
+
682
+ def __init__(
683
+ self,
684
+ catalog_manager: CatalogManager,
685
+ context_separator: str = "@",
686
+ plural_separator: str = "#",
687
+ ) -> None:
688
+ """Initialize resolver.
689
+
690
+ Args:
691
+ catalog_manager: Catalog manager instance
692
+ context_separator: Separator for context keys (key@formal)
693
+ plural_separator: Separator for plural keys (key#one)
694
+ """
695
+ self.catalog_manager = catalog_manager
696
+ self.context_separator = context_separator
697
+ self.plural_separator = plural_separator
698
+
699
+ def resolve(
700
+ self,
701
+ key: str,
702
+ locale: LocaleInfo,
703
+ context: MessageContext | None = None,
704
+ **params: Any,
705
+ ) -> ResolvedMessage:
706
+ """Resolve a message with context.
707
+
708
+ Args:
709
+ key: Message key
710
+ locale: Target locale
711
+ context: Message context
712
+ **params: Format parameters
713
+
714
+ Returns:
715
+ Resolved message
716
+ """
717
+ catalog = self.catalog_manager.get_catalog(locale)
718
+ fallback_used = False
719
+
720
+ # Try context-specific key first
721
+ if context:
722
+ context_key = f"{key}{self.context_separator}{context.value}"
723
+ if context_key in catalog:
724
+ template = catalog[context_key]
725
+ elif key in catalog:
726
+ template = catalog[key]
727
+ fallback_used = True
728
+ else:
729
+ template = f"[{key}]"
730
+ fallback_used = True
731
+ else:
732
+ template = catalog.get(key, f"[{key}]")
733
+ fallback_used = key not in catalog
734
+
735
+ # Format message
736
+ try:
737
+ message = template.format(**params)
738
+ except KeyError as e:
739
+ message = f"{template} (missing: {e})"
740
+
741
+ return ResolvedMessage(
742
+ key=key,
743
+ message=message,
744
+ locale=locale,
745
+ context=context,
746
+ fallback=fallback_used,
747
+ )
748
+
749
+ def resolve_plural(
750
+ self,
751
+ key: str,
752
+ count: float | int,
753
+ locale: LocaleInfo,
754
+ context: MessageContext | None = None,
755
+ **params: Any,
756
+ ) -> PluralizedMessage:
757
+ """Resolve a pluralized message.
758
+
759
+ Looks for plural forms using the pattern:
760
+ - key#zero, key#one, key#two, key#few, key#many, key#other
761
+
762
+ Args:
763
+ key: Base message key
764
+ count: Number for pluralization
765
+ locale: Target locale
766
+ context: Message context
767
+ **params: Additional format parameters
768
+
769
+ Returns:
770
+ Pluralized message
771
+ """
772
+ catalog = self.catalog_manager.get_catalog(locale)
773
+ category = get_plural_category(count, locale)
774
+
775
+ # Build key variants
776
+ if context:
777
+ base_key = f"{key}{self.context_separator}{context.value}"
778
+ else:
779
+ base_key = key
780
+
781
+ # Try plural forms
782
+ plural_key = f"{base_key}{self.plural_separator}{category.value}"
783
+ if plural_key in catalog:
784
+ template = catalog[plural_key]
785
+ elif f"{base_key}{self.plural_separator}other" in catalog:
786
+ template = catalog[f"{base_key}{self.plural_separator}other"]
787
+ elif base_key in catalog:
788
+ template = catalog[base_key]
789
+ else:
790
+ template = f"[{key}]"
791
+
792
+ # Format with count
793
+ try:
794
+ message = template.format(count=count, **params)
795
+ except KeyError as e:
796
+ message = f"{template} (missing: {e})"
797
+
798
+ return PluralizedMessage(
799
+ message=message,
800
+ count=count,
801
+ category=category,
802
+ )
803
+
804
+ def get_contextual_message(
805
+ self,
806
+ key: str,
807
+ locale: LocaleInfo,
808
+ ) -> ContextualMessage:
809
+ """Get all context variants for a message.
810
+
811
+ Args:
812
+ key: Message key
813
+ locale: Target locale
814
+
815
+ Returns:
816
+ ContextualMessage with all variants
817
+ """
818
+ catalog = self.catalog_manager.get_catalog(locale)
819
+
820
+ # Get default
821
+ default = catalog.get(key, f"[{key}]")
822
+
823
+ # Get context variants
824
+ contexts: dict[MessageContext, str] = {}
825
+ for ctx in MessageContext:
826
+ context_key = f"{key}{self.context_separator}{ctx.value}"
827
+ if context_key in catalog:
828
+ contexts[ctx] = catalog[context_key]
829
+
830
+ return ContextualMessage(key=key, default=default, contexts=contexts)
831
+
832
+
833
+ # ==============================================================================
834
+ # Message Composition
835
+ # ==============================================================================
836
+
837
+ class MessageComposer:
838
+ """Compose messages from multiple sources.
839
+
840
+ Supports message inheritance and composition patterns.
841
+
842
+ Example:
843
+ composer = MessageComposer(resolver)
844
+
845
+ # Compose from parts
846
+ message = composer.compose(
847
+ template="Result: {status} - {details}",
848
+ parts={
849
+ "status": ("status.success", {}),
850
+ "details": ("details.count", {"count": 5}),
851
+ },
852
+ locale=LocaleInfo.parse("en"),
853
+ )
854
+ """
855
+
856
+ def __init__(self, resolver: ContextResolver) -> None:
857
+ """Initialize composer.
858
+
859
+ Args:
860
+ resolver: Context resolver instance
861
+ """
862
+ self.resolver = resolver
863
+
864
+ def compose(
865
+ self,
866
+ template: str,
867
+ parts: dict[str, tuple[str, dict[str, Any]]],
868
+ locale: LocaleInfo,
869
+ context: MessageContext | None = None,
870
+ ) -> str:
871
+ """Compose a message from parts.
872
+
873
+ Args:
874
+ template: Template with {placeholders}
875
+ parts: Dictionary of placeholder -> (key, params) tuples
876
+ locale: Target locale
877
+ context: Message context
878
+
879
+ Returns:
880
+ Composed message
881
+ """
882
+ resolved_parts = {}
883
+ for name, (key, params) in parts.items():
884
+ result = self.resolver.resolve(key, locale, context, **params)
885
+ resolved_parts[name] = result.message
886
+
887
+ try:
888
+ return template.format(**resolved_parts)
889
+ except KeyError as e:
890
+ return f"{template} (missing part: {e})"
891
+
892
+ def compose_list(
893
+ self,
894
+ items: list[tuple[str, dict[str, Any]]],
895
+ locale: LocaleInfo,
896
+ separator: str = ", ",
897
+ final_separator: str | None = None,
898
+ context: MessageContext | None = None,
899
+ ) -> str:
900
+ """Compose a list of messages.
901
+
902
+ Args:
903
+ items: List of (key, params) tuples
904
+ locale: Target locale
905
+ separator: Item separator
906
+ final_separator: Separator before last item (e.g., " and ")
907
+ context: Message context
908
+
909
+ Returns:
910
+ Composed list string
911
+ """
912
+ resolved = []
913
+ for key, params in items:
914
+ result = self.resolver.resolve(key, locale, context, **params)
915
+ resolved.append(result.message)
916
+
917
+ if not resolved:
918
+ return ""
919
+
920
+ if len(resolved) == 1:
921
+ return resolved[0]
922
+
923
+ if final_separator:
924
+ return f"{separator.join(resolved[:-1])}{final_separator}{resolved[-1]}"
925
+ return separator.join(resolved)
926
+
927
+
928
+ # ==============================================================================
929
+ # Global Instances and Factory Functions
930
+ # ==============================================================================
931
+
932
+ _catalog_manager: CatalogManager | None = None
933
+ _context_resolver: ContextResolver | None = None
934
+
935
+
936
+ def get_catalog_manager(
937
+ base_path: Path | str | None = None,
938
+ **kwargs: Any,
939
+ ) -> CatalogManager:
940
+ """Get or create the global catalog manager.
941
+
942
+ Args:
943
+ base_path: Path for filesystem storage
944
+ **kwargs: Additional configuration
945
+
946
+ Returns:
947
+ CatalogManager instance
948
+ """
949
+ global _catalog_manager
950
+
951
+ if _catalog_manager is None:
952
+ _catalog_manager = CatalogManager(base_path=base_path, **kwargs)
953
+
954
+ return _catalog_manager
955
+
956
+
957
+ def get_context_resolver() -> ContextResolver:
958
+ """Get or create the global context resolver.
959
+
960
+ Returns:
961
+ ContextResolver instance
962
+ """
963
+ global _context_resolver
964
+
965
+ if _context_resolver is None:
966
+ _context_resolver = ContextResolver(get_catalog_manager())
967
+
968
+ return _context_resolver
969
+
970
+
971
+ def resolve_message(
972
+ key: str,
973
+ locale: str | LocaleInfo,
974
+ context: MessageContext | None = None,
975
+ **params: Any,
976
+ ) -> str:
977
+ """Resolve a message (convenience function).
978
+
979
+ Args:
980
+ key: Message key
981
+ locale: Target locale
982
+ context: Message context
983
+ **params: Format parameters
984
+
985
+ Returns:
986
+ Resolved message string
987
+ """
988
+ if isinstance(locale, str):
989
+ locale = LocaleInfo.parse(locale)
990
+
991
+ resolver = get_context_resolver()
992
+ result = resolver.resolve(key, locale, context, **params)
993
+ return result.message
994
+
995
+
996
+ def resolve_plural_message(
997
+ key: str,
998
+ count: float | int,
999
+ locale: str | LocaleInfo,
1000
+ context: MessageContext | None = None,
1001
+ **params: Any,
1002
+ ) -> str:
1003
+ """Resolve a pluralized message (convenience function).
1004
+
1005
+ Args:
1006
+ key: Message key
1007
+ count: Number for pluralization
1008
+ locale: Target locale
1009
+ context: Message context
1010
+ **params: Additional format parameters
1011
+
1012
+ Returns:
1013
+ Pluralized message string
1014
+ """
1015
+ if isinstance(locale, str):
1016
+ locale = LocaleInfo.parse(locale)
1017
+
1018
+ resolver = get_context_resolver()
1019
+ result = resolver.resolve_plural(key, count, locale, context, **params)
1020
+ return result.message