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,1136 @@
1
+ """Optimized cron expression parser and scheduler.
2
+
3
+ This module provides a high-performance cron expression parser with support
4
+ for standard and extended syntax, efficient next-run calculation, and
5
+ comprehensive validation.
6
+
7
+ Design Principles:
8
+ 1. Immutable expressions: Thread-safe by design
9
+ 2. Lazy evaluation: Parse once, evaluate many times
10
+ 3. Efficient iteration: O(1) memory for next-run calculation
11
+ 4. Extensible: Easy to add new field types or syntax
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import calendar
17
+ import re
18
+ from abc import ABC, abstractmethod
19
+ from dataclasses import dataclass, field
20
+ from datetime import datetime, timedelta
21
+ from enum import Enum, auto
22
+ from functools import cached_property
23
+ from typing import Any, Iterator, Set, FrozenSet
24
+
25
+
26
+ # =============================================================================
27
+ # Exceptions
28
+ # =============================================================================
29
+
30
+
31
+ class CronParseError(ValueError):
32
+ """Raised when cron expression parsing fails."""
33
+
34
+ def __init__(self, message: str, expression: str = "", position: int = -1) -> None:
35
+ self.expression = expression
36
+ self.position = position
37
+ super().__init__(message)
38
+
39
+
40
+ # =============================================================================
41
+ # Field Types
42
+ # =============================================================================
43
+
44
+
45
+ class CronFieldType(Enum):
46
+ """Types of cron fields."""
47
+
48
+ SECOND = auto()
49
+ MINUTE = auto()
50
+ HOUR = auto()
51
+ DAY_OF_MONTH = auto()
52
+ MONTH = auto()
53
+ DAY_OF_WEEK = auto()
54
+ YEAR = auto()
55
+
56
+
57
+ @dataclass(frozen=True)
58
+ class FieldConstraints:
59
+ """Constraints for a cron field."""
60
+
61
+ min_value: int
62
+ max_value: int
63
+ names: dict[str, int] = field(default_factory=dict)
64
+ supports_l: bool = False
65
+ supports_w: bool = False
66
+ supports_hash: bool = False
67
+ supports_question: bool = False
68
+
69
+
70
+ # Field constraint definitions
71
+ FIELD_CONSTRAINTS: dict[CronFieldType, FieldConstraints] = {
72
+ CronFieldType.SECOND: FieldConstraints(0, 59),
73
+ CronFieldType.MINUTE: FieldConstraints(0, 59),
74
+ CronFieldType.HOUR: FieldConstraints(0, 23),
75
+ CronFieldType.DAY_OF_MONTH: FieldConstraints(
76
+ 1, 31,
77
+ supports_l=True,
78
+ supports_w=True,
79
+ supports_question=True,
80
+ ),
81
+ CronFieldType.MONTH: FieldConstraints(
82
+ 1, 12,
83
+ names={
84
+ "JAN": 1, "FEB": 2, "MAR": 3, "APR": 4,
85
+ "MAY": 5, "JUN": 6, "JUL": 7, "AUG": 8,
86
+ "SEP": 9, "OCT": 10, "NOV": 11, "DEC": 12,
87
+ },
88
+ ),
89
+ CronFieldType.DAY_OF_WEEK: FieldConstraints(
90
+ 0, 6,
91
+ names={
92
+ "SUN": 0, "MON": 1, "TUE": 2, "WED": 3,
93
+ "THU": 4, "FRI": 5, "SAT": 6,
94
+ },
95
+ supports_l=True,
96
+ supports_hash=True,
97
+ supports_question=True,
98
+ ),
99
+ CronFieldType.YEAR: FieldConstraints(1970, 2099),
100
+ }
101
+
102
+
103
+ # =============================================================================
104
+ # Cron Field
105
+ # =============================================================================
106
+
107
+
108
+ class CronField:
109
+ """Represents a parsed cron field.
110
+
111
+ A CronField contains the set of valid values for a particular field
112
+ (minute, hour, etc.) along with any special modifiers (L, W, #).
113
+
114
+ Attributes:
115
+ field_type: The type of field (MINUTE, HOUR, etc.)
116
+ values: Frozen set of valid integer values
117
+ is_any: True if field matches any value (*)
118
+ last: True if L modifier is present
119
+ weekday_nearest: Day number if W modifier is present
120
+ nth_weekday: Tuple of (weekday, nth) if # modifier is present
121
+ """
122
+
123
+ __slots__ = (
124
+ "_field_type",
125
+ "_values",
126
+ "_is_any",
127
+ "_last",
128
+ "_weekday_nearest",
129
+ "_nth_weekday",
130
+ "_original",
131
+ )
132
+
133
+ def __init__(
134
+ self,
135
+ field_type: CronFieldType,
136
+ values: FrozenSet[int],
137
+ *,
138
+ is_any: bool = False,
139
+ last: bool = False,
140
+ weekday_nearest: int | None = None,
141
+ nth_weekday: tuple[int, int] | None = None,
142
+ original: str = "",
143
+ ) -> None:
144
+ """Initialize cron field.
145
+
146
+ Args:
147
+ field_type: Type of this field.
148
+ values: Set of valid values.
149
+ is_any: Matches any value.
150
+ last: L modifier present.
151
+ weekday_nearest: W modifier value.
152
+ nth_weekday: (weekday, nth) for # modifier.
153
+ original: Original expression string.
154
+ """
155
+ self._field_type = field_type
156
+ self._values = values
157
+ self._is_any = is_any
158
+ self._last = last
159
+ self._weekday_nearest = weekday_nearest
160
+ self._nth_weekday = nth_weekday
161
+ self._original = original
162
+
163
+ @property
164
+ def field_type(self) -> CronFieldType:
165
+ return self._field_type
166
+
167
+ @property
168
+ def values(self) -> FrozenSet[int]:
169
+ return self._values
170
+
171
+ @property
172
+ def is_any(self) -> bool:
173
+ return self._is_any
174
+
175
+ @property
176
+ def has_special(self) -> bool:
177
+ """Check if field has special modifiers."""
178
+ return self._last or self._weekday_nearest is not None or self._nth_weekday is not None
179
+
180
+ def matches(self, value: int, context: "MatchContext | None" = None) -> bool:
181
+ """Check if a value matches this field.
182
+
183
+ Args:
184
+ value: Value to check.
185
+ context: Optional context for special modifiers.
186
+
187
+ Returns:
188
+ True if value matches.
189
+ """
190
+ if self._is_any:
191
+ return True
192
+
193
+ # Handle special modifiers
194
+ if context:
195
+ if self._last:
196
+ if self._field_type == CronFieldType.DAY_OF_MONTH:
197
+ last_day = calendar.monthrange(context.year, context.month)[1]
198
+ return value == last_day
199
+ elif self._field_type == CronFieldType.DAY_OF_WEEK:
200
+ # Last occurrence of weekday in month
201
+ return self._is_last_weekday(value, context)
202
+
203
+ if self._weekday_nearest is not None:
204
+ return self._is_nearest_weekday(value, context)
205
+
206
+ if self._nth_weekday is not None:
207
+ return self._is_nth_weekday(value, context)
208
+
209
+ return value in self._values
210
+
211
+ def _is_last_weekday(self, value: int, context: "MatchContext") -> bool:
212
+ """Check if value is last occurrence of weekday in month."""
213
+ if not self._values:
214
+ return False
215
+
216
+ target_weekday = next(iter(self._values))
217
+ last_day = calendar.monthrange(context.year, context.month)[1]
218
+
219
+ # Find last occurrence of target weekday
220
+ for day in range(last_day, 0, -1):
221
+ dt = datetime(context.year, context.month, day)
222
+ if dt.weekday() == (target_weekday - 1) % 7: # Convert to Python weekday
223
+ return context.day == day
224
+
225
+ return False
226
+
227
+ def _is_nearest_weekday(self, value: int, context: "MatchContext") -> bool:
228
+ """Check if value is nearest weekday to specified day."""
229
+ target_day = self._weekday_nearest
230
+ if target_day is None:
231
+ return False
232
+
233
+ dt = datetime(context.year, context.month, target_day)
234
+ weekday = dt.weekday()
235
+
236
+ if weekday == 5: # Saturday -> Friday
237
+ nearest = target_day - 1
238
+ elif weekday == 6: # Sunday -> Monday
239
+ nearest = target_day + 1
240
+ else:
241
+ nearest = target_day
242
+
243
+ # Handle month boundaries
244
+ last_day = calendar.monthrange(context.year, context.month)[1]
245
+ nearest = max(1, min(nearest, last_day))
246
+
247
+ return context.day == nearest
248
+
249
+ def _is_nth_weekday(self, value: int, context: "MatchContext") -> bool:
250
+ """Check if value is nth occurrence of weekday in month."""
251
+ if self._nth_weekday is None:
252
+ return False
253
+
254
+ target_weekday, nth = self._nth_weekday
255
+
256
+ # Find nth occurrence
257
+ count = 0
258
+ for day in range(1, calendar.monthrange(context.year, context.month)[1] + 1):
259
+ dt = datetime(context.year, context.month, day)
260
+ if dt.weekday() == (target_weekday - 1) % 7:
261
+ count += 1
262
+ if count == nth:
263
+ return context.day == day
264
+
265
+ return False
266
+
267
+ def __repr__(self) -> str:
268
+ return f"CronField({self._field_type.name}, {self._original!r})"
269
+
270
+
271
+ @dataclass
272
+ class MatchContext:
273
+ """Context for matching special cron modifiers."""
274
+
275
+ year: int
276
+ month: int
277
+ day: int
278
+
279
+
280
+ # =============================================================================
281
+ # Cron Parser
282
+ # =============================================================================
283
+
284
+
285
+ class CronParser:
286
+ """Parser for cron expressions.
287
+
288
+ Supports:
289
+ - Standard 5-field cron (minute hour day month weekday)
290
+ - Extended 6-field cron (second minute hour day month weekday)
291
+ - Extended 7-field cron (second minute hour day month weekday year)
292
+ - Special expressions (@yearly, @monthly, etc.)
293
+ """
294
+
295
+ # Predefined expression aliases
296
+ ALIASES: dict[str, str] = {
297
+ "@yearly": "0 0 1 1 *",
298
+ "@annually": "0 0 1 1 *",
299
+ "@monthly": "0 0 1 * *",
300
+ "@weekly": "0 0 * * 0",
301
+ "@daily": "0 0 * * *",
302
+ "@midnight": "0 0 * * *",
303
+ "@hourly": "0 * * * *",
304
+ "@every_minute": "* * * * *",
305
+ "@every_second": "* * * * * *", # 6-field
306
+ }
307
+
308
+ def __init__(self, expression: str) -> None:
309
+ """Initialize parser with expression.
310
+
311
+ Args:
312
+ expression: Cron expression string.
313
+ """
314
+ self._original = expression.strip()
315
+ self._expression = self._resolve_alias(self._original)
316
+ self._fields: list[CronField] = []
317
+
318
+ def _resolve_alias(self, expression: str) -> str:
319
+ """Resolve predefined aliases."""
320
+ lower = expression.lower()
321
+ if lower in self.ALIASES:
322
+ return self.ALIASES[lower]
323
+ return expression
324
+
325
+ def parse(self) -> list[CronField]:
326
+ """Parse the cron expression.
327
+
328
+ Returns:
329
+ List of CronField objects.
330
+
331
+ Raises:
332
+ CronParseError: If expression is invalid.
333
+ """
334
+ parts = self._expression.split()
335
+
336
+ if len(parts) == 5:
337
+ # Standard: minute hour day month weekday
338
+ field_types = [
339
+ CronFieldType.MINUTE,
340
+ CronFieldType.HOUR,
341
+ CronFieldType.DAY_OF_MONTH,
342
+ CronFieldType.MONTH,
343
+ CronFieldType.DAY_OF_WEEK,
344
+ ]
345
+ elif len(parts) == 6:
346
+ # Extended: second minute hour day month weekday
347
+ field_types = [
348
+ CronFieldType.SECOND,
349
+ CronFieldType.MINUTE,
350
+ CronFieldType.HOUR,
351
+ CronFieldType.DAY_OF_MONTH,
352
+ CronFieldType.MONTH,
353
+ CronFieldType.DAY_OF_WEEK,
354
+ ]
355
+ elif len(parts) == 7:
356
+ # Extended with year: second minute hour day month weekday year
357
+ field_types = [
358
+ CronFieldType.SECOND,
359
+ CronFieldType.MINUTE,
360
+ CronFieldType.HOUR,
361
+ CronFieldType.DAY_OF_MONTH,
362
+ CronFieldType.MONTH,
363
+ CronFieldType.DAY_OF_WEEK,
364
+ CronFieldType.YEAR,
365
+ ]
366
+ else:
367
+ raise CronParseError(
368
+ f"Invalid number of fields: {len(parts)}. "
369
+ "Expected 5, 6, or 7 fields.",
370
+ self._original,
371
+ )
372
+
373
+ self._fields = [
374
+ self._parse_field(part, field_type)
375
+ for part, field_type in zip(parts, field_types)
376
+ ]
377
+
378
+ return self._fields
379
+
380
+ def _parse_field(self, part: str, field_type: CronFieldType) -> CronField:
381
+ """Parse a single cron field.
382
+
383
+ Args:
384
+ part: Field expression string.
385
+ field_type: Type of this field.
386
+
387
+ Returns:
388
+ Parsed CronField.
389
+ """
390
+ constraints = FIELD_CONSTRAINTS[field_type]
391
+ original = part
392
+
393
+ # Handle ? (no specific value)
394
+ if part == "?":
395
+ if not constraints.supports_question:
396
+ raise CronParseError(
397
+ f"? not supported for {field_type.name}",
398
+ self._original,
399
+ )
400
+ return CronField(field_type, frozenset(), is_any=True, original=original)
401
+
402
+ # Handle * (any value)
403
+ if part == "*":
404
+ return CronField(field_type, frozenset(), is_any=True, original=original)
405
+
406
+ # Handle L (last)
407
+ if "L" in part.upper():
408
+ return self._parse_last(part, field_type, constraints, original)
409
+
410
+ # Handle W (weekday nearest) - only if it ends with W and is numeric+W
411
+ if part.upper().endswith("W") and constraints.supports_w:
412
+ # Check if it's actually a W modifier (number + W or LW)
413
+ prefix = part.upper()[:-1]
414
+ if prefix.isdigit() or prefix == "L":
415
+ return self._parse_weekday(part, field_type, constraints, original)
416
+
417
+ # Handle # (nth weekday)
418
+ if "#" in part:
419
+ return self._parse_nth(part, field_type, constraints, original)
420
+
421
+ # Parse normal expression (ranges, lists, steps)
422
+ values = self._parse_values(part, field_type, constraints)
423
+
424
+ return CronField(field_type, frozenset(values), original=original)
425
+
426
+ def _parse_last(
427
+ self,
428
+ part: str,
429
+ field_type: CronFieldType,
430
+ constraints: FieldConstraints,
431
+ original: str,
432
+ ) -> CronField:
433
+ """Parse L (last) modifier."""
434
+ if not constraints.supports_l:
435
+ raise CronParseError(
436
+ f"L not supported for {field_type.name}",
437
+ self._original,
438
+ )
439
+
440
+ part_upper = part.upper()
441
+
442
+ if part_upper == "L":
443
+ # Just L - last day of month or last day of week
444
+ return CronField(field_type, frozenset(), last=True, original=original)
445
+
446
+ # Handle nL (e.g., 5L = last Friday)
447
+ if part_upper.endswith("L"):
448
+ weekday_str = part_upper[:-1]
449
+ weekday = self._resolve_value(weekday_str, constraints)
450
+ return CronField(
451
+ field_type,
452
+ frozenset([weekday]),
453
+ last=True,
454
+ original=original,
455
+ )
456
+
457
+ raise CronParseError(f"Invalid L expression: {part}", self._original)
458
+
459
+ def _parse_weekday(
460
+ self,
461
+ part: str,
462
+ field_type: CronFieldType,
463
+ constraints: FieldConstraints,
464
+ original: str,
465
+ ) -> CronField:
466
+ """Parse W (nearest weekday) modifier."""
467
+ if not constraints.supports_w:
468
+ raise CronParseError(
469
+ f"W not supported for {field_type.name}",
470
+ self._original,
471
+ )
472
+
473
+ part_upper = part.upper()
474
+
475
+ if part_upper == "LW":
476
+ # Last weekday of month
477
+ return CronField(field_type, frozenset(), last=True, original=original)
478
+
479
+ # Handle nW (e.g., 15W = nearest weekday to 15th)
480
+ if part_upper.endswith("W"):
481
+ day_str = part_upper[:-1]
482
+ day = int(day_str)
483
+ if day < constraints.min_value or day > constraints.max_value:
484
+ raise CronParseError(
485
+ f"Day {day} out of range for W",
486
+ self._original,
487
+ )
488
+ return CronField(
489
+ field_type,
490
+ frozenset(),
491
+ weekday_nearest=day,
492
+ original=original,
493
+ )
494
+
495
+ raise CronParseError(f"Invalid W expression: {part}", self._original)
496
+
497
+ def _parse_nth(
498
+ self,
499
+ part: str,
500
+ field_type: CronFieldType,
501
+ constraints: FieldConstraints,
502
+ original: str,
503
+ ) -> CronField:
504
+ """Parse # (nth weekday) modifier."""
505
+ if not constraints.supports_hash:
506
+ raise CronParseError(
507
+ f"# not supported for {field_type.name}",
508
+ self._original,
509
+ )
510
+
511
+ parts = part.split("#")
512
+ if len(parts) != 2:
513
+ raise CronParseError(f"Invalid # expression: {part}", self._original)
514
+
515
+ weekday = self._resolve_value(parts[0], constraints)
516
+ nth = int(parts[1])
517
+
518
+ if nth < 1 or nth > 5:
519
+ raise CronParseError(f"Invalid nth value: {nth}", self._original)
520
+
521
+ return CronField(
522
+ field_type,
523
+ frozenset([weekday]),
524
+ nth_weekday=(weekday, nth),
525
+ original=original,
526
+ )
527
+
528
+ def _parse_values(
529
+ self,
530
+ part: str,
531
+ field_type: CronFieldType,
532
+ constraints: FieldConstraints,
533
+ ) -> set[int]:
534
+ """Parse normal cron values (ranges, lists, steps)."""
535
+ values: set[int] = set()
536
+
537
+ # Handle comma-separated list
538
+ for segment in part.split(","):
539
+ segment = segment.strip()
540
+
541
+ # Handle step (*/n or n-m/s)
542
+ if "/" in segment:
543
+ values.update(self._parse_step(segment, constraints))
544
+ # Handle range (n-m)
545
+ elif "-" in segment:
546
+ values.update(self._parse_range(segment, constraints))
547
+ # Handle single value
548
+ else:
549
+ value = self._resolve_value(segment, constraints)
550
+ values.add(value)
551
+
552
+ return values
553
+
554
+ def _parse_step(
555
+ self,
556
+ segment: str,
557
+ constraints: FieldConstraints,
558
+ ) -> set[int]:
559
+ """Parse step expression (*/n or n-m/s)."""
560
+ parts = segment.split("/")
561
+ if len(parts) != 2:
562
+ raise CronParseError(f"Invalid step: {segment}", self._original)
563
+
564
+ step = int(parts[1])
565
+ if step <= 0:
566
+ raise CronParseError(f"Step must be positive: {step}", self._original)
567
+
568
+ base = parts[0]
569
+
570
+ if base == "*":
571
+ start = constraints.min_value
572
+ end = constraints.max_value
573
+ elif "-" in base:
574
+ range_parts = base.split("-")
575
+ start = self._resolve_value(range_parts[0], constraints)
576
+ end = self._resolve_value(range_parts[1], constraints)
577
+ else:
578
+ start = self._resolve_value(base, constraints)
579
+ end = constraints.max_value
580
+
581
+ return set(range(start, end + 1, step))
582
+
583
+ def _parse_range(
584
+ self,
585
+ segment: str,
586
+ constraints: FieldConstraints,
587
+ ) -> set[int]:
588
+ """Parse range expression (n-m)."""
589
+ parts = segment.split("-")
590
+ if len(parts) != 2:
591
+ raise CronParseError(f"Invalid range: {segment}", self._original)
592
+
593
+ start = self._resolve_value(parts[0], constraints)
594
+ end = self._resolve_value(parts[1], constraints)
595
+
596
+ if start > end:
597
+ # Handle wraparound (e.g., FRI-MON)
598
+ values = set(range(start, constraints.max_value + 1))
599
+ values.update(range(constraints.min_value, end + 1))
600
+ return values
601
+
602
+ return set(range(start, end + 1))
603
+
604
+ def _resolve_value(self, value: str, constraints: FieldConstraints) -> int:
605
+ """Resolve a value (number or name) to integer."""
606
+ value = value.strip().upper()
607
+
608
+ # Check named values
609
+ if value in constraints.names:
610
+ return constraints.names[value]
611
+
612
+ # Parse as integer
613
+ try:
614
+ num = int(value)
615
+ if num < constraints.min_value or num > constraints.max_value:
616
+ raise CronParseError(
617
+ f"Value {num} out of range "
618
+ f"[{constraints.min_value}-{constraints.max_value}]",
619
+ self._original,
620
+ )
621
+ return num
622
+ except ValueError:
623
+ raise CronParseError(
624
+ f"Invalid value: {value}",
625
+ self._original,
626
+ )
627
+
628
+
629
+ # =============================================================================
630
+ # Cron Expression
631
+ # =============================================================================
632
+
633
+
634
+ class CronExpression:
635
+ """Parsed cron expression with efficient next-run calculation.
636
+
637
+ CronExpression is immutable and thread-safe. It can be used to:
638
+ - Check if a datetime matches the expression
639
+ - Calculate the next matching datetime
640
+ - Iterate over matching datetimes
641
+
642
+ Example:
643
+ >>> expr = CronExpression.parse("0 9 * * MON-FRI")
644
+ >>> expr.matches(datetime(2024, 1, 15, 9, 0)) # True (Monday)
645
+ >>> expr.next() # Next matching datetime
646
+ >>> list(expr.iter(limit=5)) # Next 5 matching datetimes
647
+ """
648
+
649
+ __slots__ = (
650
+ "_expression",
651
+ "_fields",
652
+ "_has_seconds",
653
+ "_has_years",
654
+ "_field_map",
655
+ )
656
+
657
+ def __init__(self, expression: str, fields: list[CronField]) -> None:
658
+ """Initialize cron expression.
659
+
660
+ Args:
661
+ expression: Original expression string.
662
+ fields: Parsed cron fields.
663
+ """
664
+ self._expression = expression
665
+ self._fields = tuple(fields)
666
+ self._has_seconds = len(fields) >= 6
667
+ self._has_years = len(fields) >= 7
668
+
669
+ # Build field map for quick access
670
+ self._field_map: dict[CronFieldType, CronField] = {
671
+ f.field_type: f for f in fields
672
+ }
673
+
674
+ @classmethod
675
+ def parse(cls, expression: str) -> "CronExpression":
676
+ """Parse a cron expression.
677
+
678
+ Args:
679
+ expression: Cron expression string.
680
+
681
+ Returns:
682
+ Parsed CronExpression.
683
+
684
+ Raises:
685
+ CronParseError: If expression is invalid.
686
+ """
687
+ parser = CronParser(expression)
688
+ fields = parser.parse()
689
+ return cls(expression, fields)
690
+
691
+ @classmethod
692
+ def builder(cls) -> "CronBuilder":
693
+ """Create a cron expression builder.
694
+
695
+ Returns:
696
+ CronBuilder for fluent construction.
697
+ """
698
+ return CronBuilder()
699
+
700
+ # Predefined expression factories
701
+ @classmethod
702
+ def yearly(cls) -> "CronExpression":
703
+ return cls.parse("0 0 1 1 *")
704
+
705
+ @classmethod
706
+ def monthly(cls) -> "CronExpression":
707
+ return cls.parse("0 0 1 * *")
708
+
709
+ @classmethod
710
+ def weekly(cls) -> "CronExpression":
711
+ return cls.parse("0 0 * * 0")
712
+
713
+ @classmethod
714
+ def daily(cls) -> "CronExpression":
715
+ return cls.parse("0 0 * * *")
716
+
717
+ @classmethod
718
+ def hourly(cls) -> "CronExpression":
719
+ return cls.parse("0 * * * *")
720
+
721
+ @classmethod
722
+ def every_n_minutes(cls, n: int) -> "CronExpression":
723
+ return cls.parse(f"*/{n} * * * *")
724
+
725
+ @classmethod
726
+ def every_n_hours(cls, n: int) -> "CronExpression":
727
+ return cls.parse(f"0 */{n} * * *")
728
+
729
+ @property
730
+ def expression(self) -> str:
731
+ """Get original expression string."""
732
+ return self._expression
733
+
734
+ @property
735
+ def fields(self) -> tuple[CronField, ...]:
736
+ """Get parsed fields."""
737
+ return self._fields
738
+
739
+ @property
740
+ def has_seconds(self) -> bool:
741
+ """Check if expression includes seconds."""
742
+ return self._has_seconds
743
+
744
+ def get_field(self, field_type: CronFieldType) -> CronField | None:
745
+ """Get a specific field by type."""
746
+ return self._field_map.get(field_type)
747
+
748
+ def matches(self, dt: datetime) -> bool:
749
+ """Check if a datetime matches this expression.
750
+
751
+ Args:
752
+ dt: Datetime to check.
753
+
754
+ Returns:
755
+ True if datetime matches.
756
+ """
757
+ context = MatchContext(year=dt.year, month=dt.month, day=dt.day)
758
+
759
+ # Check each field
760
+ for field in self._fields:
761
+ if field.field_type == CronFieldType.SECOND:
762
+ if not field.matches(dt.second, context):
763
+ return False
764
+ elif field.field_type == CronFieldType.MINUTE:
765
+ if not field.matches(dt.minute, context):
766
+ return False
767
+ elif field.field_type == CronFieldType.HOUR:
768
+ if not field.matches(dt.hour, context):
769
+ return False
770
+ elif field.field_type == CronFieldType.DAY_OF_MONTH:
771
+ if not field.matches(dt.day, context):
772
+ return False
773
+ elif field.field_type == CronFieldType.MONTH:
774
+ if not field.matches(dt.month, context):
775
+ return False
776
+ elif field.field_type == CronFieldType.DAY_OF_WEEK:
777
+ # Python weekday: Monday=0, Sunday=6
778
+ # Cron weekday: Sunday=0, Saturday=6
779
+ cron_weekday = (dt.weekday() + 1) % 7
780
+ if not field.matches(cron_weekday, context):
781
+ return False
782
+ elif field.field_type == CronFieldType.YEAR:
783
+ if not field.matches(dt.year, context):
784
+ return False
785
+
786
+ return True
787
+
788
+ def next(self, after: datetime | None = None) -> datetime | None:
789
+ """Get next matching datetime.
790
+
791
+ Args:
792
+ after: Start searching after this datetime (default: now).
793
+
794
+ Returns:
795
+ Next matching datetime, or None if none found within 4 years.
796
+ """
797
+ if after is None:
798
+ after = datetime.now()
799
+
800
+ # Start from next second/minute
801
+ if self._has_seconds:
802
+ current = after.replace(microsecond=0) + timedelta(seconds=1)
803
+ else:
804
+ current = after.replace(second=0, microsecond=0) + timedelta(minutes=1)
805
+
806
+ # Search limit: 4 years
807
+ end = current + timedelta(days=365 * 4)
808
+
809
+ while current < end:
810
+ if self.matches(current):
811
+ return current
812
+
813
+ # Advance to next candidate
814
+ current = self._advance(current)
815
+
816
+ return None
817
+
818
+ def next_n(self, n: int, after: datetime | None = None) -> list[datetime]:
819
+ """Get next n matching datetimes.
820
+
821
+ Args:
822
+ n: Number of matches to find.
823
+ after: Start searching after this datetime.
824
+
825
+ Returns:
826
+ List of matching datetimes.
827
+ """
828
+ results = []
829
+ current = after
830
+
831
+ for _ in range(n):
832
+ next_dt = self.next(current)
833
+ if next_dt is None:
834
+ break
835
+ results.append(next_dt)
836
+ current = next_dt
837
+
838
+ return results
839
+
840
+ def iter(
841
+ self,
842
+ after: datetime | None = None,
843
+ limit: int | None = None,
844
+ ) -> "CronIterator":
845
+ """Create iterator over matching datetimes.
846
+
847
+ Args:
848
+ after: Start after this datetime.
849
+ limit: Maximum number of matches.
850
+
851
+ Returns:
852
+ CronIterator.
853
+ """
854
+ return CronIterator(self, after, limit)
855
+
856
+ def _advance(self, current: datetime) -> datetime:
857
+ """Advance to next candidate datetime.
858
+
859
+ Uses field constraints to skip non-matching times.
860
+ """
861
+ # Check month
862
+ month_field = self._field_map.get(CronFieldType.MONTH)
863
+ if month_field and not month_field.is_any:
864
+ if current.month not in month_field.values:
865
+ # Skip to next valid month
866
+ for m in sorted(month_field.values):
867
+ if m > current.month:
868
+ return current.replace(month=m, day=1, hour=0, minute=0, second=0)
869
+ # Wrap to next year
870
+ return current.replace(
871
+ year=current.year + 1,
872
+ month=min(month_field.values),
873
+ day=1, hour=0, minute=0, second=0,
874
+ )
875
+
876
+ # Check day of month
877
+ dom_field = self._field_map.get(CronFieldType.DAY_OF_MONTH)
878
+ if dom_field and not dom_field.is_any and not dom_field.has_special:
879
+ if current.day not in dom_field.values:
880
+ # Skip to next valid day
881
+ for d in sorted(dom_field.values):
882
+ if d > current.day:
883
+ try:
884
+ return current.replace(day=d, hour=0, minute=0, second=0)
885
+ except ValueError:
886
+ pass # Day doesn't exist in this month
887
+ # Wrap to next month
888
+ if current.month == 12:
889
+ return current.replace(
890
+ year=current.year + 1,
891
+ month=1, day=1, hour=0, minute=0, second=0,
892
+ )
893
+ return current.replace(
894
+ month=current.month + 1,
895
+ day=1, hour=0, minute=0, second=0,
896
+ )
897
+
898
+ # Default: advance by smallest unit
899
+ if self._has_seconds:
900
+ return current + timedelta(seconds=1)
901
+ return current + timedelta(minutes=1)
902
+
903
+ def __repr__(self) -> str:
904
+ return f"CronExpression({self._expression!r})"
905
+
906
+ def __str__(self) -> str:
907
+ return self._expression
908
+
909
+ def __eq__(self, other: object) -> bool:
910
+ if isinstance(other, CronExpression):
911
+ return self._expression == other._expression
912
+ return NotImplemented
913
+
914
+ def __hash__(self) -> int:
915
+ return hash(self._expression)
916
+
917
+
918
+ # =============================================================================
919
+ # Cron Iterator
920
+ # =============================================================================
921
+
922
+
923
+ class CronIterator(Iterator[datetime]):
924
+ """Iterator over matching datetimes.
925
+
926
+ Efficiently iterates without storing all matches in memory.
927
+ """
928
+
929
+ def __init__(
930
+ self,
931
+ expression: CronExpression,
932
+ after: datetime | None = None,
933
+ limit: int | None = None,
934
+ ) -> None:
935
+ """Initialize iterator.
936
+
937
+ Args:
938
+ expression: Cron expression.
939
+ after: Start after this datetime.
940
+ limit: Maximum matches.
941
+ """
942
+ self._expression = expression
943
+ self._current = after
944
+ self._limit = limit
945
+ self._count = 0
946
+
947
+ def __iter__(self) -> "CronIterator":
948
+ return self
949
+
950
+ def __next__(self) -> datetime:
951
+ if self._limit is not None and self._count >= self._limit:
952
+ raise StopIteration
953
+
954
+ next_dt = self._expression.next(self._current)
955
+ if next_dt is None:
956
+ raise StopIteration
957
+
958
+ self._current = next_dt
959
+ self._count += 1
960
+
961
+ return next_dt
962
+
963
+
964
+ # =============================================================================
965
+ # Cron Builder
966
+ # =============================================================================
967
+
968
+
969
+ class CronBuilder:
970
+ """Fluent builder for cron expressions.
971
+
972
+ Example:
973
+ >>> expr = (CronBuilder()
974
+ ... .at_minute(0, 30)
975
+ ... .at_hour(9, 17)
976
+ ... .on_weekdays()
977
+ ... .build())
978
+ """
979
+
980
+ def __init__(self) -> None:
981
+ """Initialize builder with defaults (every minute)."""
982
+ self._second: str = "0"
983
+ self._minute: str = "*"
984
+ self._hour: str = "*"
985
+ self._day_of_month: str = "*"
986
+ self._month: str = "*"
987
+ self._day_of_week: str = "*"
988
+ self._include_seconds: bool = False
989
+
990
+ def with_seconds(self) -> "CronBuilder":
991
+ """Include seconds field in expression."""
992
+ self._include_seconds = True
993
+ return self
994
+
995
+ def at_second(self, *seconds: int) -> "CronBuilder":
996
+ """Set specific seconds."""
997
+ self._include_seconds = True
998
+ self._second = ",".join(str(s) for s in seconds)
999
+ return self
1000
+
1001
+ def every_n_seconds(self, n: int) -> "CronBuilder":
1002
+ """Run every n seconds."""
1003
+ self._include_seconds = True
1004
+ self._second = f"*/{n}"
1005
+ return self
1006
+
1007
+ def at_minute(self, *minutes: int) -> "CronBuilder":
1008
+ """Set specific minutes."""
1009
+ self._minute = ",".join(str(m) for m in minutes)
1010
+ return self
1011
+
1012
+ def every_n_minutes(self, n: int) -> "CronBuilder":
1013
+ """Run every n minutes."""
1014
+ self._minute = f"*/{n}"
1015
+ return self
1016
+
1017
+ def at_hour(self, *hours: int) -> "CronBuilder":
1018
+ """Set specific hours."""
1019
+ self._hour = ",".join(str(h) for h in hours)
1020
+ return self
1021
+
1022
+ def every_n_hours(self, n: int) -> "CronBuilder":
1023
+ """Run every n hours."""
1024
+ self._hour = f"*/{n}"
1025
+ return self
1026
+
1027
+ def on_day(self, *days: int) -> "CronBuilder":
1028
+ """Set specific days of month."""
1029
+ self._day_of_month = ",".join(str(d) for d in days)
1030
+ return self
1031
+
1032
+ def on_last_day(self) -> "CronBuilder":
1033
+ """Run on last day of month."""
1034
+ self._day_of_month = "L"
1035
+ return self
1036
+
1037
+ def on_weekday_nearest(self, day: int) -> "CronBuilder":
1038
+ """Run on weekday nearest to specified day."""
1039
+ self._day_of_month = f"{day}W"
1040
+ return self
1041
+
1042
+ def in_month(self, *months: int | str) -> "CronBuilder":
1043
+ """Set specific months."""
1044
+ self._month = ",".join(str(m).upper() for m in months)
1045
+ return self
1046
+
1047
+ def on_weekday(self, *weekdays: int | str) -> "CronBuilder":
1048
+ """Set specific weekdays (0=SUN, 6=SAT)."""
1049
+ self._day_of_week = ",".join(str(w).upper() for w in weekdays)
1050
+ return self
1051
+
1052
+ def on_weekdays(self) -> "CronBuilder":
1053
+ """Run Monday through Friday."""
1054
+ self._day_of_week = "MON-FRI"
1055
+ return self
1056
+
1057
+ def on_weekends(self) -> "CronBuilder":
1058
+ """Run Saturday and Sunday."""
1059
+ self._day_of_week = "SAT,SUN"
1060
+ return self
1061
+
1062
+ def every_day(self) -> "CronBuilder":
1063
+ """Run every day."""
1064
+ self._day_of_month = "*"
1065
+ self._day_of_week = "*"
1066
+ return self
1067
+
1068
+ def daily_at(self, hour: int, minute: int = 0) -> "CronBuilder":
1069
+ """Run daily at specific time."""
1070
+ self._minute = str(minute)
1071
+ self._hour = str(hour)
1072
+ return self
1073
+
1074
+ def hourly_at(self, minute: int) -> "CronBuilder":
1075
+ """Run hourly at specific minute."""
1076
+ self._minute = str(minute)
1077
+ return self
1078
+
1079
+ def build(self) -> CronExpression:
1080
+ """Build the cron expression.
1081
+
1082
+ Returns:
1083
+ Parsed CronExpression.
1084
+ """
1085
+ if self._include_seconds:
1086
+ expr = (
1087
+ f"{self._second} {self._minute} {self._hour} "
1088
+ f"{self._day_of_month} {self._month} {self._day_of_week}"
1089
+ )
1090
+ else:
1091
+ expr = (
1092
+ f"{self._minute} {self._hour} "
1093
+ f"{self._day_of_month} {self._month} {self._day_of_week}"
1094
+ )
1095
+
1096
+ return CronExpression.parse(expr)
1097
+
1098
+
1099
+ # =============================================================================
1100
+ # Validation Functions
1101
+ # =============================================================================
1102
+
1103
+
1104
+ def validate_expression(expression: str) -> list[str]:
1105
+ """Validate a cron expression.
1106
+
1107
+ Args:
1108
+ expression: Cron expression to validate.
1109
+
1110
+ Returns:
1111
+ List of validation errors (empty if valid).
1112
+ """
1113
+ errors = []
1114
+
1115
+ try:
1116
+ CronExpression.parse(expression)
1117
+ except CronParseError as e:
1118
+ errors.append(str(e))
1119
+
1120
+ return errors
1121
+
1122
+
1123
+ def is_valid_expression(expression: str) -> bool:
1124
+ """Check if a cron expression is valid.
1125
+
1126
+ Args:
1127
+ expression: Cron expression to check.
1128
+
1129
+ Returns:
1130
+ True if valid.
1131
+ """
1132
+ try:
1133
+ CronExpression.parse(expression)
1134
+ return True
1135
+ except CronParseError:
1136
+ return False