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.
- truthound/__init__.py +162 -0
- truthound/adapters.py +100 -0
- truthound/api.py +365 -0
- truthound/audit/__init__.py +248 -0
- truthound/audit/core.py +967 -0
- truthound/audit/filters.py +620 -0
- truthound/audit/formatters.py +707 -0
- truthound/audit/logger.py +902 -0
- truthound/audit/middleware.py +571 -0
- truthound/audit/storage.py +1083 -0
- truthound/benchmark/__init__.py +123 -0
- truthound/benchmark/base.py +757 -0
- truthound/benchmark/comparison.py +635 -0
- truthound/benchmark/generators.py +706 -0
- truthound/benchmark/reporters.py +718 -0
- truthound/benchmark/runner.py +635 -0
- truthound/benchmark/scenarios.py +712 -0
- truthound/cache.py +252 -0
- truthound/checkpoint/__init__.py +136 -0
- truthound/checkpoint/actions/__init__.py +164 -0
- truthound/checkpoint/actions/base.py +324 -0
- truthound/checkpoint/actions/custom.py +234 -0
- truthound/checkpoint/actions/discord_notify.py +290 -0
- truthound/checkpoint/actions/email_notify.py +405 -0
- truthound/checkpoint/actions/github_action.py +406 -0
- truthound/checkpoint/actions/opsgenie.py +1499 -0
- truthound/checkpoint/actions/pagerduty.py +226 -0
- truthound/checkpoint/actions/slack_notify.py +233 -0
- truthound/checkpoint/actions/store_result.py +249 -0
- truthound/checkpoint/actions/teams_notify.py +1570 -0
- truthound/checkpoint/actions/telegram_notify.py +419 -0
- truthound/checkpoint/actions/update_docs.py +552 -0
- truthound/checkpoint/actions/webhook.py +293 -0
- truthound/checkpoint/analytics/__init__.py +147 -0
- truthound/checkpoint/analytics/aggregations/__init__.py +23 -0
- truthound/checkpoint/analytics/aggregations/rollup.py +481 -0
- truthound/checkpoint/analytics/aggregations/time_bucket.py +306 -0
- truthound/checkpoint/analytics/analyzers/__init__.py +17 -0
- truthound/checkpoint/analytics/analyzers/anomaly.py +386 -0
- truthound/checkpoint/analytics/analyzers/base.py +270 -0
- truthound/checkpoint/analytics/analyzers/forecast.py +421 -0
- truthound/checkpoint/analytics/analyzers/trend.py +314 -0
- truthound/checkpoint/analytics/models.py +292 -0
- truthound/checkpoint/analytics/protocols.py +549 -0
- truthound/checkpoint/analytics/service.py +718 -0
- truthound/checkpoint/analytics/stores/__init__.py +16 -0
- truthound/checkpoint/analytics/stores/base.py +306 -0
- truthound/checkpoint/analytics/stores/memory_store.py +353 -0
- truthound/checkpoint/analytics/stores/sqlite_store.py +557 -0
- truthound/checkpoint/analytics/stores/timescale_store.py +501 -0
- truthound/checkpoint/async_actions.py +794 -0
- truthound/checkpoint/async_base.py +708 -0
- truthound/checkpoint/async_checkpoint.py +617 -0
- truthound/checkpoint/async_runner.py +639 -0
- truthound/checkpoint/checkpoint.py +527 -0
- truthound/checkpoint/ci/__init__.py +61 -0
- truthound/checkpoint/ci/detector.py +355 -0
- truthound/checkpoint/ci/reporter.py +436 -0
- truthound/checkpoint/ci/templates.py +454 -0
- truthound/checkpoint/circuitbreaker/__init__.py +133 -0
- truthound/checkpoint/circuitbreaker/breaker.py +542 -0
- truthound/checkpoint/circuitbreaker/core.py +252 -0
- truthound/checkpoint/circuitbreaker/detection.py +459 -0
- truthound/checkpoint/circuitbreaker/middleware.py +389 -0
- truthound/checkpoint/circuitbreaker/registry.py +357 -0
- truthound/checkpoint/distributed/__init__.py +139 -0
- truthound/checkpoint/distributed/backends/__init__.py +35 -0
- truthound/checkpoint/distributed/backends/celery_backend.py +503 -0
- truthound/checkpoint/distributed/backends/kubernetes_backend.py +696 -0
- truthound/checkpoint/distributed/backends/local_backend.py +397 -0
- truthound/checkpoint/distributed/backends/ray_backend.py +625 -0
- truthound/checkpoint/distributed/base.py +774 -0
- truthound/checkpoint/distributed/orchestrator.py +765 -0
- truthound/checkpoint/distributed/protocols.py +842 -0
- truthound/checkpoint/distributed/registry.py +449 -0
- truthound/checkpoint/idempotency/__init__.py +120 -0
- truthound/checkpoint/idempotency/core.py +295 -0
- truthound/checkpoint/idempotency/fingerprint.py +454 -0
- truthound/checkpoint/idempotency/locking.py +604 -0
- truthound/checkpoint/idempotency/service.py +592 -0
- truthound/checkpoint/idempotency/stores.py +653 -0
- truthound/checkpoint/monitoring/__init__.py +134 -0
- truthound/checkpoint/monitoring/aggregators/__init__.py +15 -0
- truthound/checkpoint/monitoring/aggregators/base.py +372 -0
- truthound/checkpoint/monitoring/aggregators/realtime.py +300 -0
- truthound/checkpoint/monitoring/aggregators/window.py +493 -0
- truthound/checkpoint/monitoring/collectors/__init__.py +17 -0
- truthound/checkpoint/monitoring/collectors/base.py +257 -0
- truthound/checkpoint/monitoring/collectors/memory_collector.py +617 -0
- truthound/checkpoint/monitoring/collectors/prometheus_collector.py +451 -0
- truthound/checkpoint/monitoring/collectors/redis_collector.py +518 -0
- truthound/checkpoint/monitoring/events.py +410 -0
- truthound/checkpoint/monitoring/protocols.py +636 -0
- truthound/checkpoint/monitoring/service.py +578 -0
- truthound/checkpoint/monitoring/views/__init__.py +17 -0
- truthound/checkpoint/monitoring/views/base.py +172 -0
- truthound/checkpoint/monitoring/views/queue_view.py +220 -0
- truthound/checkpoint/monitoring/views/task_view.py +240 -0
- truthound/checkpoint/monitoring/views/worker_view.py +263 -0
- truthound/checkpoint/registry.py +337 -0
- truthound/checkpoint/runner.py +356 -0
- truthound/checkpoint/transaction/__init__.py +133 -0
- truthound/checkpoint/transaction/base.py +389 -0
- truthound/checkpoint/transaction/compensatable.py +537 -0
- truthound/checkpoint/transaction/coordinator.py +576 -0
- truthound/checkpoint/transaction/executor.py +622 -0
- truthound/checkpoint/transaction/idempotency.py +534 -0
- truthound/checkpoint/transaction/saga/__init__.py +143 -0
- truthound/checkpoint/transaction/saga/builder.py +584 -0
- truthound/checkpoint/transaction/saga/definition.py +515 -0
- truthound/checkpoint/transaction/saga/event_store.py +542 -0
- truthound/checkpoint/transaction/saga/patterns.py +833 -0
- truthound/checkpoint/transaction/saga/runner.py +718 -0
- truthound/checkpoint/transaction/saga/state_machine.py +793 -0
- truthound/checkpoint/transaction/saga/strategies.py +780 -0
- truthound/checkpoint/transaction/saga/testing.py +886 -0
- truthound/checkpoint/triggers/__init__.py +58 -0
- truthound/checkpoint/triggers/base.py +237 -0
- truthound/checkpoint/triggers/event.py +385 -0
- truthound/checkpoint/triggers/schedule.py +355 -0
- truthound/cli.py +2358 -0
- truthound/cli_modules/__init__.py +124 -0
- truthound/cli_modules/advanced/__init__.py +45 -0
- truthound/cli_modules/advanced/benchmark.py +343 -0
- truthound/cli_modules/advanced/docs.py +225 -0
- truthound/cli_modules/advanced/lineage.py +209 -0
- truthound/cli_modules/advanced/ml.py +320 -0
- truthound/cli_modules/advanced/realtime.py +196 -0
- truthound/cli_modules/checkpoint/__init__.py +46 -0
- truthound/cli_modules/checkpoint/init.py +114 -0
- truthound/cli_modules/checkpoint/list.py +71 -0
- truthound/cli_modules/checkpoint/run.py +159 -0
- truthound/cli_modules/checkpoint/validate.py +67 -0
- truthound/cli_modules/common/__init__.py +71 -0
- truthound/cli_modules/common/errors.py +414 -0
- truthound/cli_modules/common/options.py +419 -0
- truthound/cli_modules/common/output.py +507 -0
- truthound/cli_modules/common/protocol.py +552 -0
- truthound/cli_modules/core/__init__.py +48 -0
- truthound/cli_modules/core/check.py +123 -0
- truthound/cli_modules/core/compare.py +104 -0
- truthound/cli_modules/core/learn.py +57 -0
- truthound/cli_modules/core/mask.py +77 -0
- truthound/cli_modules/core/profile.py +65 -0
- truthound/cli_modules/core/scan.py +61 -0
- truthound/cli_modules/profiler/__init__.py +51 -0
- truthound/cli_modules/profiler/auto_profile.py +175 -0
- truthound/cli_modules/profiler/metadata.py +107 -0
- truthound/cli_modules/profiler/suite.py +283 -0
- truthound/cli_modules/registry.py +431 -0
- truthound/cli_modules/scaffolding/__init__.py +89 -0
- truthound/cli_modules/scaffolding/base.py +631 -0
- truthound/cli_modules/scaffolding/commands.py +545 -0
- truthound/cli_modules/scaffolding/plugins.py +1072 -0
- truthound/cli_modules/scaffolding/reporters.py +594 -0
- truthound/cli_modules/scaffolding/validators.py +1127 -0
- truthound/common/__init__.py +18 -0
- truthound/common/resilience/__init__.py +130 -0
- truthound/common/resilience/bulkhead.py +266 -0
- truthound/common/resilience/circuit_breaker.py +516 -0
- truthound/common/resilience/composite.py +332 -0
- truthound/common/resilience/config.py +292 -0
- truthound/common/resilience/protocols.py +217 -0
- truthound/common/resilience/rate_limiter.py +404 -0
- truthound/common/resilience/retry.py +341 -0
- truthound/datadocs/__init__.py +260 -0
- truthound/datadocs/base.py +571 -0
- truthound/datadocs/builder.py +761 -0
- truthound/datadocs/charts.py +764 -0
- truthound/datadocs/dashboard/__init__.py +63 -0
- truthound/datadocs/dashboard/app.py +576 -0
- truthound/datadocs/dashboard/components.py +584 -0
- truthound/datadocs/dashboard/state.py +240 -0
- truthound/datadocs/engine/__init__.py +46 -0
- truthound/datadocs/engine/context.py +376 -0
- truthound/datadocs/engine/pipeline.py +618 -0
- truthound/datadocs/engine/registry.py +469 -0
- truthound/datadocs/exporters/__init__.py +49 -0
- truthound/datadocs/exporters/base.py +198 -0
- truthound/datadocs/exporters/html.py +178 -0
- truthound/datadocs/exporters/json_exporter.py +253 -0
- truthound/datadocs/exporters/markdown.py +284 -0
- truthound/datadocs/exporters/pdf.py +392 -0
- truthound/datadocs/i18n/__init__.py +86 -0
- truthound/datadocs/i18n/catalog.py +960 -0
- truthound/datadocs/i18n/formatting.py +505 -0
- truthound/datadocs/i18n/loader.py +256 -0
- truthound/datadocs/i18n/plurals.py +378 -0
- truthound/datadocs/renderers/__init__.py +42 -0
- truthound/datadocs/renderers/base.py +401 -0
- truthound/datadocs/renderers/custom.py +342 -0
- truthound/datadocs/renderers/jinja.py +697 -0
- truthound/datadocs/sections.py +736 -0
- truthound/datadocs/styles.py +931 -0
- truthound/datadocs/themes/__init__.py +101 -0
- truthound/datadocs/themes/base.py +336 -0
- truthound/datadocs/themes/default.py +417 -0
- truthound/datadocs/themes/enterprise.py +419 -0
- truthound/datadocs/themes/loader.py +336 -0
- truthound/datadocs/themes.py +301 -0
- truthound/datadocs/transformers/__init__.py +57 -0
- truthound/datadocs/transformers/base.py +268 -0
- truthound/datadocs/transformers/enrichers.py +544 -0
- truthound/datadocs/transformers/filters.py +447 -0
- truthound/datadocs/transformers/i18n.py +468 -0
- truthound/datadocs/versioning/__init__.py +62 -0
- truthound/datadocs/versioning/diff.py +639 -0
- truthound/datadocs/versioning/storage.py +497 -0
- truthound/datadocs/versioning/version.py +358 -0
- truthound/datasources/__init__.py +223 -0
- truthound/datasources/_async_protocols.py +222 -0
- truthound/datasources/_protocols.py +159 -0
- truthound/datasources/adapters.py +428 -0
- truthound/datasources/async_base.py +599 -0
- truthound/datasources/async_factory.py +511 -0
- truthound/datasources/base.py +516 -0
- truthound/datasources/factory.py +433 -0
- truthound/datasources/nosql/__init__.py +47 -0
- truthound/datasources/nosql/base.py +487 -0
- truthound/datasources/nosql/elasticsearch.py +801 -0
- truthound/datasources/nosql/mongodb.py +636 -0
- truthound/datasources/pandas_optimized.py +582 -0
- truthound/datasources/pandas_source.py +216 -0
- truthound/datasources/polars_source.py +395 -0
- truthound/datasources/spark_source.py +479 -0
- truthound/datasources/sql/__init__.py +154 -0
- truthound/datasources/sql/base.py +710 -0
- truthound/datasources/sql/bigquery.py +410 -0
- truthound/datasources/sql/cloud_base.py +199 -0
- truthound/datasources/sql/databricks.py +471 -0
- truthound/datasources/sql/mysql.py +316 -0
- truthound/datasources/sql/oracle.py +427 -0
- truthound/datasources/sql/postgresql.py +321 -0
- truthound/datasources/sql/redshift.py +479 -0
- truthound/datasources/sql/snowflake.py +439 -0
- truthound/datasources/sql/sqlite.py +286 -0
- truthound/datasources/sql/sqlserver.py +437 -0
- truthound/datasources/streaming/__init__.py +47 -0
- truthound/datasources/streaming/base.py +350 -0
- truthound/datasources/streaming/kafka.py +670 -0
- truthound/decorators.py +98 -0
- truthound/docs/__init__.py +69 -0
- truthound/docs/extractor.py +971 -0
- truthound/docs/generator.py +601 -0
- truthound/docs/parser.py +1037 -0
- truthound/docs/renderer.py +999 -0
- truthound/drift/__init__.py +22 -0
- truthound/drift/compare.py +189 -0
- truthound/drift/detectors.py +464 -0
- truthound/drift/report.py +160 -0
- truthound/execution/__init__.py +65 -0
- truthound/execution/_protocols.py +324 -0
- truthound/execution/base.py +576 -0
- truthound/execution/distributed/__init__.py +179 -0
- truthound/execution/distributed/aggregations.py +731 -0
- truthound/execution/distributed/arrow_bridge.py +817 -0
- truthound/execution/distributed/base.py +550 -0
- truthound/execution/distributed/dask_engine.py +976 -0
- truthound/execution/distributed/mixins.py +766 -0
- truthound/execution/distributed/protocols.py +756 -0
- truthound/execution/distributed/ray_engine.py +1127 -0
- truthound/execution/distributed/registry.py +446 -0
- truthound/execution/distributed/spark_engine.py +1011 -0
- truthound/execution/distributed/validator_adapter.py +682 -0
- truthound/execution/pandas_engine.py +401 -0
- truthound/execution/polars_engine.py +497 -0
- truthound/execution/pushdown/__init__.py +230 -0
- truthound/execution/pushdown/ast.py +1550 -0
- truthound/execution/pushdown/builder.py +1550 -0
- truthound/execution/pushdown/dialects.py +1072 -0
- truthound/execution/pushdown/executor.py +829 -0
- truthound/execution/pushdown/optimizer.py +1041 -0
- truthound/execution/sql_engine.py +518 -0
- truthound/infrastructure/__init__.py +189 -0
- truthound/infrastructure/audit.py +1515 -0
- truthound/infrastructure/config.py +1133 -0
- truthound/infrastructure/encryption.py +1132 -0
- truthound/infrastructure/logging.py +1503 -0
- truthound/infrastructure/metrics.py +1220 -0
- truthound/lineage/__init__.py +89 -0
- truthound/lineage/base.py +746 -0
- truthound/lineage/impact_analysis.py +474 -0
- truthound/lineage/integrations/__init__.py +22 -0
- truthound/lineage/integrations/openlineage.py +548 -0
- truthound/lineage/tracker.py +512 -0
- truthound/lineage/visualization/__init__.py +33 -0
- truthound/lineage/visualization/protocols.py +145 -0
- truthound/lineage/visualization/renderers/__init__.py +20 -0
- truthound/lineage/visualization/renderers/cytoscape.py +329 -0
- truthound/lineage/visualization/renderers/d3.py +331 -0
- truthound/lineage/visualization/renderers/graphviz.py +276 -0
- truthound/lineage/visualization/renderers/mermaid.py +308 -0
- truthound/maskers.py +113 -0
- truthound/ml/__init__.py +124 -0
- truthound/ml/anomaly_models/__init__.py +31 -0
- truthound/ml/anomaly_models/ensemble.py +362 -0
- truthound/ml/anomaly_models/isolation_forest.py +444 -0
- truthound/ml/anomaly_models/statistical.py +392 -0
- truthound/ml/base.py +1178 -0
- truthound/ml/drift_detection/__init__.py +26 -0
- truthound/ml/drift_detection/concept.py +381 -0
- truthound/ml/drift_detection/distribution.py +361 -0
- truthound/ml/drift_detection/feature.py +442 -0
- truthound/ml/drift_detection/multivariate.py +495 -0
- truthound/ml/monitoring/__init__.py +88 -0
- truthound/ml/monitoring/alerting/__init__.py +33 -0
- truthound/ml/monitoring/alerting/handlers.py +427 -0
- truthound/ml/monitoring/alerting/rules.py +508 -0
- truthound/ml/monitoring/collectors/__init__.py +19 -0
- truthound/ml/monitoring/collectors/composite.py +105 -0
- truthound/ml/monitoring/collectors/drift.py +324 -0
- truthound/ml/monitoring/collectors/performance.py +179 -0
- truthound/ml/monitoring/collectors/quality.py +369 -0
- truthound/ml/monitoring/monitor.py +536 -0
- truthound/ml/monitoring/protocols.py +451 -0
- truthound/ml/monitoring/stores/__init__.py +15 -0
- truthound/ml/monitoring/stores/memory.py +201 -0
- truthound/ml/monitoring/stores/prometheus.py +296 -0
- truthound/ml/rule_learning/__init__.py +25 -0
- truthound/ml/rule_learning/constraint_miner.py +443 -0
- truthound/ml/rule_learning/pattern_learner.py +499 -0
- truthound/ml/rule_learning/profile_learner.py +462 -0
- truthound/multitenancy/__init__.py +326 -0
- truthound/multitenancy/core.py +852 -0
- truthound/multitenancy/integration.py +597 -0
- truthound/multitenancy/isolation.py +630 -0
- truthound/multitenancy/manager.py +770 -0
- truthound/multitenancy/middleware.py +765 -0
- truthound/multitenancy/quota.py +537 -0
- truthound/multitenancy/resolvers.py +603 -0
- truthound/multitenancy/storage.py +703 -0
- truthound/observability/__init__.py +307 -0
- truthound/observability/context.py +531 -0
- truthound/observability/instrumentation.py +611 -0
- truthound/observability/logging.py +887 -0
- truthound/observability/metrics.py +1157 -0
- truthound/observability/tracing/__init__.py +178 -0
- truthound/observability/tracing/baggage.py +310 -0
- truthound/observability/tracing/config.py +426 -0
- truthound/observability/tracing/exporter.py +787 -0
- truthound/observability/tracing/integration.py +1018 -0
- truthound/observability/tracing/otel/__init__.py +146 -0
- truthound/observability/tracing/otel/adapter.py +982 -0
- truthound/observability/tracing/otel/bridge.py +1177 -0
- truthound/observability/tracing/otel/compat.py +681 -0
- truthound/observability/tracing/otel/config.py +691 -0
- truthound/observability/tracing/otel/detection.py +327 -0
- truthound/observability/tracing/otel/protocols.py +426 -0
- truthound/observability/tracing/processor.py +561 -0
- truthound/observability/tracing/propagator.py +757 -0
- truthound/observability/tracing/provider.py +569 -0
- truthound/observability/tracing/resource.py +515 -0
- truthound/observability/tracing/sampler.py +487 -0
- truthound/observability/tracing/span.py +676 -0
- truthound/plugins/__init__.py +198 -0
- truthound/plugins/base.py +599 -0
- truthound/plugins/cli.py +680 -0
- truthound/plugins/dependencies/__init__.py +42 -0
- truthound/plugins/dependencies/graph.py +422 -0
- truthound/plugins/dependencies/resolver.py +417 -0
- truthound/plugins/discovery.py +379 -0
- truthound/plugins/docs/__init__.py +46 -0
- truthound/plugins/docs/extractor.py +444 -0
- truthound/plugins/docs/renderer.py +499 -0
- truthound/plugins/enterprise_manager.py +877 -0
- truthound/plugins/examples/__init__.py +19 -0
- truthound/plugins/examples/custom_validators.py +317 -0
- truthound/plugins/examples/slack_notifier.py +312 -0
- truthound/plugins/examples/xml_reporter.py +254 -0
- truthound/plugins/hooks.py +558 -0
- truthound/plugins/lifecycle/__init__.py +43 -0
- truthound/plugins/lifecycle/hot_reload.py +402 -0
- truthound/plugins/lifecycle/manager.py +371 -0
- truthound/plugins/manager.py +736 -0
- truthound/plugins/registry.py +338 -0
- truthound/plugins/security/__init__.py +93 -0
- truthound/plugins/security/exceptions.py +332 -0
- truthound/plugins/security/policies.py +348 -0
- truthound/plugins/security/protocols.py +643 -0
- truthound/plugins/security/sandbox/__init__.py +45 -0
- truthound/plugins/security/sandbox/context.py +158 -0
- truthound/plugins/security/sandbox/engines/__init__.py +19 -0
- truthound/plugins/security/sandbox/engines/container.py +379 -0
- truthound/plugins/security/sandbox/engines/noop.py +144 -0
- truthound/plugins/security/sandbox/engines/process.py +336 -0
- truthound/plugins/security/sandbox/factory.py +211 -0
- truthound/plugins/security/signing/__init__.py +57 -0
- truthound/plugins/security/signing/service.py +330 -0
- truthound/plugins/security/signing/trust_store.py +368 -0
- truthound/plugins/security/signing/verifier.py +459 -0
- truthound/plugins/versioning/__init__.py +41 -0
- truthound/plugins/versioning/constraints.py +297 -0
- truthound/plugins/versioning/resolver.py +329 -0
- truthound/profiler/__init__.py +1729 -0
- truthound/profiler/_lazy.py +452 -0
- truthound/profiler/ab_testing/__init__.py +80 -0
- truthound/profiler/ab_testing/analysis.py +449 -0
- truthound/profiler/ab_testing/base.py +257 -0
- truthound/profiler/ab_testing/experiment.py +395 -0
- truthound/profiler/ab_testing/tracking.py +368 -0
- truthound/profiler/auto_threshold.py +1170 -0
- truthound/profiler/base.py +579 -0
- truthound/profiler/cache_patterns.py +911 -0
- truthound/profiler/caching.py +1303 -0
- truthound/profiler/column_profiler.py +712 -0
- truthound/profiler/comparison.py +1007 -0
- truthound/profiler/custom_patterns.py +1170 -0
- truthound/profiler/dashboard/__init__.py +50 -0
- truthound/profiler/dashboard/app.py +476 -0
- truthound/profiler/dashboard/components.py +457 -0
- truthound/profiler/dashboard/config.py +72 -0
- truthound/profiler/distributed/__init__.py +83 -0
- truthound/profiler/distributed/base.py +281 -0
- truthound/profiler/distributed/dask_backend.py +498 -0
- truthound/profiler/distributed/local_backend.py +293 -0
- truthound/profiler/distributed/profiler.py +304 -0
- truthound/profiler/distributed/ray_backend.py +374 -0
- truthound/profiler/distributed/spark_backend.py +375 -0
- truthound/profiler/distributed.py +1366 -0
- truthound/profiler/enterprise_sampling.py +1065 -0
- truthound/profiler/errors.py +488 -0
- truthound/profiler/evolution/__init__.py +91 -0
- truthound/profiler/evolution/alerts.py +426 -0
- truthound/profiler/evolution/changes.py +206 -0
- truthound/profiler/evolution/compatibility.py +365 -0
- truthound/profiler/evolution/detector.py +372 -0
- truthound/profiler/evolution/protocols.py +121 -0
- truthound/profiler/generators/__init__.py +48 -0
- truthound/profiler/generators/base.py +384 -0
- truthound/profiler/generators/ml_rules.py +375 -0
- truthound/profiler/generators/pattern_rules.py +384 -0
- truthound/profiler/generators/schema_rules.py +267 -0
- truthound/profiler/generators/stats_rules.py +324 -0
- truthound/profiler/generators/suite_generator.py +857 -0
- truthound/profiler/i18n.py +1542 -0
- truthound/profiler/incremental.py +554 -0
- truthound/profiler/incremental_validation.py +1710 -0
- truthound/profiler/integration/__init__.py +73 -0
- truthound/profiler/integration/adapters.py +345 -0
- truthound/profiler/integration/context.py +371 -0
- truthound/profiler/integration/executor.py +527 -0
- truthound/profiler/integration/naming.py +75 -0
- truthound/profiler/integration/protocols.py +243 -0
- truthound/profiler/memory.py +1185 -0
- truthound/profiler/migration/__init__.py +60 -0
- truthound/profiler/migration/base.py +345 -0
- truthound/profiler/migration/manager.py +444 -0
- truthound/profiler/migration/v1_0_to_v1_1.py +484 -0
- truthound/profiler/ml/__init__.py +73 -0
- truthound/profiler/ml/base.py +244 -0
- truthound/profiler/ml/classifier.py +507 -0
- truthound/profiler/ml/feature_extraction.py +604 -0
- truthound/profiler/ml/pretrained.py +448 -0
- truthound/profiler/ml_inference.py +1276 -0
- truthound/profiler/native_patterns.py +815 -0
- truthound/profiler/observability.py +1184 -0
- truthound/profiler/process_timeout.py +1566 -0
- truthound/profiler/progress.py +568 -0
- truthound/profiler/progress_callbacks.py +1734 -0
- truthound/profiler/quality.py +1345 -0
- truthound/profiler/resilience.py +1180 -0
- truthound/profiler/sampled_matcher.py +794 -0
- truthound/profiler/sampling.py +1288 -0
- truthound/profiler/scheduling/__init__.py +82 -0
- truthound/profiler/scheduling/protocols.py +214 -0
- truthound/profiler/scheduling/scheduler.py +474 -0
- truthound/profiler/scheduling/storage.py +457 -0
- truthound/profiler/scheduling/triggers.py +449 -0
- truthound/profiler/schema.py +603 -0
- truthound/profiler/streaming.py +685 -0
- truthound/profiler/streaming_patterns.py +1354 -0
- truthound/profiler/suite_cli.py +625 -0
- truthound/profiler/suite_config.py +789 -0
- truthound/profiler/suite_export.py +1268 -0
- truthound/profiler/table_profiler.py +547 -0
- truthound/profiler/timeout.py +565 -0
- truthound/profiler/validation.py +1532 -0
- truthound/profiler/visualization/__init__.py +118 -0
- truthound/profiler/visualization/base.py +346 -0
- truthound/profiler/visualization/generator.py +1259 -0
- truthound/profiler/visualization/plotly_renderer.py +811 -0
- truthound/profiler/visualization/renderers.py +669 -0
- truthound/profiler/visualization/sections.py +540 -0
- truthound/profiler/visualization.py +2122 -0
- truthound/profiler/yaml_validation.py +1151 -0
- truthound/py.typed +0 -0
- truthound/ratelimit/__init__.py +248 -0
- truthound/ratelimit/algorithms.py +1108 -0
- truthound/ratelimit/core.py +573 -0
- truthound/ratelimit/integration.py +532 -0
- truthound/ratelimit/limiter.py +663 -0
- truthound/ratelimit/middleware.py +700 -0
- truthound/ratelimit/policy.py +792 -0
- truthound/ratelimit/storage.py +763 -0
- truthound/rbac/__init__.py +340 -0
- truthound/rbac/core.py +976 -0
- truthound/rbac/integration.py +760 -0
- truthound/rbac/manager.py +1052 -0
- truthound/rbac/middleware.py +842 -0
- truthound/rbac/policy.py +954 -0
- truthound/rbac/storage.py +878 -0
- truthound/realtime/__init__.py +141 -0
- truthound/realtime/adapters/__init__.py +43 -0
- truthound/realtime/adapters/base.py +533 -0
- truthound/realtime/adapters/kafka.py +487 -0
- truthound/realtime/adapters/kinesis.py +479 -0
- truthound/realtime/adapters/mock.py +243 -0
- truthound/realtime/base.py +553 -0
- truthound/realtime/factory.py +382 -0
- truthound/realtime/incremental.py +660 -0
- truthound/realtime/processing/__init__.py +67 -0
- truthound/realtime/processing/exactly_once.py +575 -0
- truthound/realtime/processing/state.py +547 -0
- truthound/realtime/processing/windows.py +647 -0
- truthound/realtime/protocols.py +569 -0
- truthound/realtime/streaming.py +605 -0
- truthound/realtime/testing/__init__.py +32 -0
- truthound/realtime/testing/containers.py +615 -0
- truthound/realtime/testing/fixtures.py +484 -0
- truthound/report.py +280 -0
- truthound/reporters/__init__.py +46 -0
- truthound/reporters/_protocols.py +30 -0
- truthound/reporters/base.py +324 -0
- truthound/reporters/ci/__init__.py +66 -0
- truthound/reporters/ci/azure.py +436 -0
- truthound/reporters/ci/base.py +509 -0
- truthound/reporters/ci/bitbucket.py +567 -0
- truthound/reporters/ci/circleci.py +547 -0
- truthound/reporters/ci/detection.py +364 -0
- truthound/reporters/ci/factory.py +182 -0
- truthound/reporters/ci/github.py +388 -0
- truthound/reporters/ci/gitlab.py +471 -0
- truthound/reporters/ci/jenkins.py +525 -0
- truthound/reporters/console_reporter.py +299 -0
- truthound/reporters/factory.py +211 -0
- truthound/reporters/html_reporter.py +524 -0
- truthound/reporters/json_reporter.py +256 -0
- truthound/reporters/markdown_reporter.py +280 -0
- truthound/reporters/sdk/__init__.py +174 -0
- truthound/reporters/sdk/builder.py +558 -0
- truthound/reporters/sdk/mixins.py +1150 -0
- truthound/reporters/sdk/schema.py +1493 -0
- truthound/reporters/sdk/templates.py +666 -0
- truthound/reporters/sdk/testing.py +968 -0
- truthound/scanners.py +170 -0
- truthound/scheduling/__init__.py +122 -0
- truthound/scheduling/cron.py +1136 -0
- truthound/scheduling/presets.py +212 -0
- truthound/schema.py +275 -0
- truthound/secrets/__init__.py +173 -0
- truthound/secrets/base.py +618 -0
- truthound/secrets/cloud.py +682 -0
- truthound/secrets/integration.py +507 -0
- truthound/secrets/manager.py +633 -0
- truthound/secrets/oidc/__init__.py +172 -0
- truthound/secrets/oidc/base.py +902 -0
- truthound/secrets/oidc/credential_provider.py +623 -0
- truthound/secrets/oidc/exchangers.py +1001 -0
- truthound/secrets/oidc/github/__init__.py +110 -0
- truthound/secrets/oidc/github/claims.py +718 -0
- truthound/secrets/oidc/github/enhanced_provider.py +693 -0
- truthound/secrets/oidc/github/trust_policy.py +742 -0
- truthound/secrets/oidc/github/verification.py +723 -0
- truthound/secrets/oidc/github/workflow.py +691 -0
- truthound/secrets/oidc/providers.py +825 -0
- truthound/secrets/providers.py +506 -0
- truthound/secrets/resolver.py +495 -0
- truthound/stores/__init__.py +177 -0
- truthound/stores/backends/__init__.py +18 -0
- truthound/stores/backends/_protocols.py +340 -0
- truthound/stores/backends/azure_blob.py +530 -0
- truthound/stores/backends/concurrent_filesystem.py +915 -0
- truthound/stores/backends/connection_pool.py +1365 -0
- truthound/stores/backends/database.py +743 -0
- truthound/stores/backends/filesystem.py +538 -0
- truthound/stores/backends/gcs.py +399 -0
- truthound/stores/backends/memory.py +354 -0
- truthound/stores/backends/s3.py +434 -0
- truthound/stores/backpressure/__init__.py +84 -0
- truthound/stores/backpressure/base.py +375 -0
- truthound/stores/backpressure/circuit_breaker.py +434 -0
- truthound/stores/backpressure/monitor.py +376 -0
- truthound/stores/backpressure/strategies.py +677 -0
- truthound/stores/base.py +551 -0
- truthound/stores/batching/__init__.py +65 -0
- truthound/stores/batching/base.py +305 -0
- truthound/stores/batching/buffer.py +370 -0
- truthound/stores/batching/store.py +248 -0
- truthound/stores/batching/writer.py +521 -0
- truthound/stores/caching/__init__.py +60 -0
- truthound/stores/caching/backends.py +684 -0
- truthound/stores/caching/base.py +356 -0
- truthound/stores/caching/store.py +305 -0
- truthound/stores/compression/__init__.py +193 -0
- truthound/stores/compression/adaptive.py +694 -0
- truthound/stores/compression/base.py +514 -0
- truthound/stores/compression/pipeline.py +868 -0
- truthound/stores/compression/providers.py +672 -0
- truthound/stores/compression/streaming.py +832 -0
- truthound/stores/concurrency/__init__.py +81 -0
- truthound/stores/concurrency/atomic.py +556 -0
- truthound/stores/concurrency/index.py +775 -0
- truthound/stores/concurrency/locks.py +576 -0
- truthound/stores/concurrency/manager.py +482 -0
- truthound/stores/encryption/__init__.py +297 -0
- truthound/stores/encryption/base.py +952 -0
- truthound/stores/encryption/keys.py +1191 -0
- truthound/stores/encryption/pipeline.py +903 -0
- truthound/stores/encryption/providers.py +953 -0
- truthound/stores/encryption/streaming.py +950 -0
- truthound/stores/expectations.py +227 -0
- truthound/stores/factory.py +246 -0
- truthound/stores/migration/__init__.py +75 -0
- truthound/stores/migration/base.py +480 -0
- truthound/stores/migration/manager.py +347 -0
- truthound/stores/migration/registry.py +382 -0
- truthound/stores/migration/store.py +559 -0
- truthound/stores/observability/__init__.py +106 -0
- truthound/stores/observability/audit.py +718 -0
- truthound/stores/observability/config.py +270 -0
- truthound/stores/observability/factory.py +208 -0
- truthound/stores/observability/metrics.py +636 -0
- truthound/stores/observability/protocols.py +410 -0
- truthound/stores/observability/store.py +570 -0
- truthound/stores/observability/tracing.py +784 -0
- truthound/stores/replication/__init__.py +76 -0
- truthound/stores/replication/base.py +260 -0
- truthound/stores/replication/monitor.py +269 -0
- truthound/stores/replication/store.py +439 -0
- truthound/stores/replication/syncer.py +391 -0
- truthound/stores/results.py +359 -0
- truthound/stores/retention/__init__.py +77 -0
- truthound/stores/retention/base.py +378 -0
- truthound/stores/retention/policies.py +621 -0
- truthound/stores/retention/scheduler.py +279 -0
- truthound/stores/retention/store.py +526 -0
- truthound/stores/streaming/__init__.py +138 -0
- truthound/stores/streaming/base.py +801 -0
- truthound/stores/streaming/database.py +984 -0
- truthound/stores/streaming/filesystem.py +719 -0
- truthound/stores/streaming/reader.py +629 -0
- truthound/stores/streaming/s3.py +843 -0
- truthound/stores/streaming/writer.py +790 -0
- truthound/stores/tiering/__init__.py +108 -0
- truthound/stores/tiering/base.py +462 -0
- truthound/stores/tiering/manager.py +249 -0
- truthound/stores/tiering/policies.py +692 -0
- truthound/stores/tiering/store.py +526 -0
- truthound/stores/versioning/__init__.py +56 -0
- truthound/stores/versioning/base.py +376 -0
- truthound/stores/versioning/store.py +660 -0
- truthound/stores/versioning/strategies.py +353 -0
- truthound/types.py +56 -0
- truthound/validators/__init__.py +774 -0
- truthound/validators/aggregate/__init__.py +27 -0
- truthound/validators/aggregate/central.py +116 -0
- truthound/validators/aggregate/extremes.py +116 -0
- truthound/validators/aggregate/spread.py +118 -0
- truthound/validators/aggregate/sum.py +64 -0
- truthound/validators/aggregate/type.py +78 -0
- truthound/validators/anomaly/__init__.py +93 -0
- truthound/validators/anomaly/base.py +431 -0
- truthound/validators/anomaly/ml_based.py +1190 -0
- truthound/validators/anomaly/multivariate.py +647 -0
- truthound/validators/anomaly/statistical.py +599 -0
- truthound/validators/base.py +1089 -0
- truthound/validators/business_rule/__init__.py +46 -0
- truthound/validators/business_rule/base.py +147 -0
- truthound/validators/business_rule/checksum.py +509 -0
- truthound/validators/business_rule/financial.py +526 -0
- truthound/validators/cache.py +733 -0
- truthound/validators/completeness/__init__.py +39 -0
- truthound/validators/completeness/conditional.py +73 -0
- truthound/validators/completeness/default.py +98 -0
- truthound/validators/completeness/empty.py +103 -0
- truthound/validators/completeness/nan.py +337 -0
- truthound/validators/completeness/null.py +152 -0
- truthound/validators/cross_table/__init__.py +17 -0
- truthound/validators/cross_table/aggregate.py +333 -0
- truthound/validators/cross_table/row_count.py +122 -0
- truthound/validators/datetime/__init__.py +29 -0
- truthound/validators/datetime/format.py +78 -0
- truthound/validators/datetime/freshness.py +269 -0
- truthound/validators/datetime/order.py +73 -0
- truthound/validators/datetime/parseable.py +185 -0
- truthound/validators/datetime/range.py +202 -0
- truthound/validators/datetime/timezone.py +69 -0
- truthound/validators/distribution/__init__.py +49 -0
- truthound/validators/distribution/distribution.py +128 -0
- truthound/validators/distribution/monotonic.py +119 -0
- truthound/validators/distribution/outlier.py +178 -0
- truthound/validators/distribution/quantile.py +80 -0
- truthound/validators/distribution/range.py +254 -0
- truthound/validators/distribution/set.py +125 -0
- truthound/validators/distribution/statistical.py +459 -0
- truthound/validators/drift/__init__.py +79 -0
- truthound/validators/drift/base.py +427 -0
- truthound/validators/drift/multi_feature.py +401 -0
- truthound/validators/drift/numeric.py +395 -0
- truthound/validators/drift/psi.py +446 -0
- truthound/validators/drift/statistical.py +510 -0
- truthound/validators/enterprise.py +1658 -0
- truthound/validators/geospatial/__init__.py +80 -0
- truthound/validators/geospatial/base.py +97 -0
- truthound/validators/geospatial/boundary.py +238 -0
- truthound/validators/geospatial/coordinate.py +351 -0
- truthound/validators/geospatial/distance.py +399 -0
- truthound/validators/geospatial/polygon.py +665 -0
- truthound/validators/i18n/__init__.py +308 -0
- truthound/validators/i18n/bidi.py +571 -0
- truthound/validators/i18n/catalogs.py +570 -0
- truthound/validators/i18n/dialects.py +763 -0
- truthound/validators/i18n/extended_catalogs.py +549 -0
- truthound/validators/i18n/formatting.py +1434 -0
- truthound/validators/i18n/loader.py +1020 -0
- truthound/validators/i18n/messages.py +521 -0
- truthound/validators/i18n/plural.py +683 -0
- truthound/validators/i18n/protocols.py +855 -0
- truthound/validators/i18n/tms.py +1162 -0
- truthound/validators/localization/__init__.py +53 -0
- truthound/validators/localization/base.py +122 -0
- truthound/validators/localization/chinese.py +362 -0
- truthound/validators/localization/japanese.py +275 -0
- truthound/validators/localization/korean.py +524 -0
- truthound/validators/memory/__init__.py +94 -0
- truthound/validators/memory/approximate_knn.py +506 -0
- truthound/validators/memory/base.py +547 -0
- truthound/validators/memory/sgd_online.py +719 -0
- truthound/validators/memory/streaming_ecdf.py +753 -0
- truthound/validators/ml_feature/__init__.py +54 -0
- truthound/validators/ml_feature/base.py +249 -0
- truthound/validators/ml_feature/correlation.py +299 -0
- truthound/validators/ml_feature/leakage.py +344 -0
- truthound/validators/ml_feature/null_impact.py +270 -0
- truthound/validators/ml_feature/scale.py +264 -0
- truthound/validators/multi_column/__init__.py +89 -0
- truthound/validators/multi_column/arithmetic.py +284 -0
- truthound/validators/multi_column/base.py +231 -0
- truthound/validators/multi_column/comparison.py +273 -0
- truthound/validators/multi_column/consistency.py +312 -0
- truthound/validators/multi_column/statistical.py +299 -0
- truthound/validators/optimization/__init__.py +164 -0
- truthound/validators/optimization/aggregation.py +563 -0
- truthound/validators/optimization/covariance.py +556 -0
- truthound/validators/optimization/geo.py +626 -0
- truthound/validators/optimization/graph.py +587 -0
- truthound/validators/optimization/orchestrator.py +970 -0
- truthound/validators/optimization/profiling.py +1312 -0
- truthound/validators/privacy/__init__.py +223 -0
- truthound/validators/privacy/base.py +635 -0
- truthound/validators/privacy/ccpa.py +670 -0
- truthound/validators/privacy/gdpr.py +728 -0
- truthound/validators/privacy/global_patterns.py +604 -0
- truthound/validators/privacy/plugins.py +867 -0
- truthound/validators/profiling/__init__.py +52 -0
- truthound/validators/profiling/base.py +175 -0
- truthound/validators/profiling/cardinality.py +312 -0
- truthound/validators/profiling/entropy.py +391 -0
- truthound/validators/profiling/frequency.py +455 -0
- truthound/validators/pushdown_support.py +660 -0
- truthound/validators/query/__init__.py +91 -0
- truthound/validators/query/aggregate.py +346 -0
- truthound/validators/query/base.py +246 -0
- truthound/validators/query/column.py +249 -0
- truthound/validators/query/expression.py +274 -0
- truthound/validators/query/result.py +323 -0
- truthound/validators/query/row_count.py +264 -0
- truthound/validators/referential/__init__.py +80 -0
- truthound/validators/referential/base.py +395 -0
- truthound/validators/referential/cascade.py +391 -0
- truthound/validators/referential/circular.py +563 -0
- truthound/validators/referential/foreign_key.py +624 -0
- truthound/validators/referential/orphan.py +485 -0
- truthound/validators/registry.py +112 -0
- truthound/validators/schema/__init__.py +41 -0
- truthound/validators/schema/column_count.py +142 -0
- truthound/validators/schema/column_exists.py +80 -0
- truthound/validators/schema/column_order.py +82 -0
- truthound/validators/schema/column_pair.py +85 -0
- truthound/validators/schema/column_pair_set.py +195 -0
- truthound/validators/schema/column_type.py +94 -0
- truthound/validators/schema/multi_column.py +53 -0
- truthound/validators/schema/multi_column_aggregate.py +175 -0
- truthound/validators/schema/referential.py +274 -0
- truthound/validators/schema/table_schema.py +91 -0
- truthound/validators/schema_validator.py +219 -0
- truthound/validators/sdk/__init__.py +250 -0
- truthound/validators/sdk/builder.py +680 -0
- truthound/validators/sdk/decorators.py +474 -0
- truthound/validators/sdk/enterprise/__init__.py +211 -0
- truthound/validators/sdk/enterprise/docs.py +725 -0
- truthound/validators/sdk/enterprise/fuzzing.py +659 -0
- truthound/validators/sdk/enterprise/licensing.py +709 -0
- truthound/validators/sdk/enterprise/manager.py +543 -0
- truthound/validators/sdk/enterprise/resources.py +628 -0
- truthound/validators/sdk/enterprise/sandbox.py +766 -0
- truthound/validators/sdk/enterprise/signing.py +603 -0
- truthound/validators/sdk/enterprise/templates.py +865 -0
- truthound/validators/sdk/enterprise/versioning.py +659 -0
- truthound/validators/sdk/templates.py +757 -0
- truthound/validators/sdk/testing.py +807 -0
- truthound/validators/security/__init__.py +181 -0
- truthound/validators/security/redos/__init__.py +182 -0
- truthound/validators/security/redos/core.py +861 -0
- truthound/validators/security/redos/cpu_monitor.py +593 -0
- truthound/validators/security/redos/cve_database.py +791 -0
- truthound/validators/security/redos/ml/__init__.py +155 -0
- truthound/validators/security/redos/ml/base.py +785 -0
- truthound/validators/security/redos/ml/datasets.py +618 -0
- truthound/validators/security/redos/ml/features.py +359 -0
- truthound/validators/security/redos/ml/models.py +1000 -0
- truthound/validators/security/redos/ml/predictor.py +507 -0
- truthound/validators/security/redos/ml/storage.py +632 -0
- truthound/validators/security/redos/ml/training.py +571 -0
- truthound/validators/security/redos/ml_analyzer.py +937 -0
- truthound/validators/security/redos/optimizer.py +674 -0
- truthound/validators/security/redos/profiler.py +682 -0
- truthound/validators/security/redos/re2_engine.py +709 -0
- truthound/validators/security/redos.py +886 -0
- truthound/validators/security/sql_security.py +1247 -0
- truthound/validators/streaming/__init__.py +126 -0
- truthound/validators/streaming/base.py +292 -0
- truthound/validators/streaming/completeness.py +210 -0
- truthound/validators/streaming/mixin.py +575 -0
- truthound/validators/streaming/range.py +308 -0
- truthound/validators/streaming/sources.py +846 -0
- truthound/validators/string/__init__.py +57 -0
- truthound/validators/string/casing.py +158 -0
- truthound/validators/string/charset.py +96 -0
- truthound/validators/string/format.py +501 -0
- truthound/validators/string/json.py +77 -0
- truthound/validators/string/json_schema.py +184 -0
- truthound/validators/string/length.py +104 -0
- truthound/validators/string/like_pattern.py +237 -0
- truthound/validators/string/regex.py +202 -0
- truthound/validators/string/regex_extended.py +435 -0
- truthound/validators/table/__init__.py +88 -0
- truthound/validators/table/base.py +78 -0
- truthound/validators/table/column_count.py +198 -0
- truthound/validators/table/freshness.py +362 -0
- truthound/validators/table/row_count.py +251 -0
- truthound/validators/table/schema.py +333 -0
- truthound/validators/table/size.py +285 -0
- truthound/validators/timeout/__init__.py +102 -0
- truthound/validators/timeout/advanced/__init__.py +247 -0
- truthound/validators/timeout/advanced/circuit_breaker.py +675 -0
- truthound/validators/timeout/advanced/prediction.py +773 -0
- truthound/validators/timeout/advanced/priority.py +618 -0
- truthound/validators/timeout/advanced/redis_backend.py +770 -0
- truthound/validators/timeout/advanced/retry.py +721 -0
- truthound/validators/timeout/advanced/sampling.py +788 -0
- truthound/validators/timeout/advanced/sla.py +661 -0
- truthound/validators/timeout/advanced/telemetry.py +804 -0
- truthound/validators/timeout/cascade.py +477 -0
- truthound/validators/timeout/deadline.py +657 -0
- truthound/validators/timeout/degradation.py +525 -0
- truthound/validators/timeout/distributed.py +597 -0
- truthound/validators/timeseries/__init__.py +89 -0
- truthound/validators/timeseries/base.py +326 -0
- truthound/validators/timeseries/completeness.py +617 -0
- truthound/validators/timeseries/gap.py +485 -0
- truthound/validators/timeseries/monotonic.py +310 -0
- truthound/validators/timeseries/seasonality.py +422 -0
- truthound/validators/timeseries/trend.py +510 -0
- truthound/validators/uniqueness/__init__.py +59 -0
- truthound/validators/uniqueness/approximate.py +475 -0
- truthound/validators/uniqueness/distinct_values.py +253 -0
- truthound/validators/uniqueness/duplicate.py +118 -0
- truthound/validators/uniqueness/primary_key.py +140 -0
- truthound/validators/uniqueness/unique.py +191 -0
- truthound/validators/uniqueness/within_record.py +599 -0
- truthound/validators/utils.py +756 -0
- truthound-1.0.8.dist-info/METADATA +474 -0
- truthound-1.0.8.dist-info/RECORD +877 -0
- truthound-1.0.8.dist-info/WHEEL +4 -0
- truthound-1.0.8.dist-info/entry_points.txt +2 -0
- truthound-1.0.8.dist-info/licenses/LICENSE +190 -0
|
@@ -0,0 +1,657 @@
|
|
|
1
|
+
"""Deadline propagation and timeout budgeting for distributed validation.
|
|
2
|
+
|
|
3
|
+
This module implements deadline propagation patterns for distributed systems:
|
|
4
|
+
- DeadlineContext: Carries deadline information across service boundaries
|
|
5
|
+
- TimeoutBudget: Manages timeout allocation across multiple operations
|
|
6
|
+
- DeadlinePropagator: Propagates deadlines through async contexts
|
|
7
|
+
|
|
8
|
+
The key insight is that in distributed systems, the overall deadline must be
|
|
9
|
+
shared and tracked across all participating services to prevent cascading
|
|
10
|
+
timeouts and ensure consistent behavior.
|
|
11
|
+
|
|
12
|
+
Example:
|
|
13
|
+
# Set up a deadline context
|
|
14
|
+
with DeadlineContext.from_seconds(60) as ctx:
|
|
15
|
+
# Allocate budget for sub-operations
|
|
16
|
+
budget = ctx.allocate(validators=40, reporting=15, cleanup=5)
|
|
17
|
+
|
|
18
|
+
# Execute with deadline awareness
|
|
19
|
+
result = validate_with_deadline(data, budget.validators)
|
|
20
|
+
|
|
21
|
+
# Check remaining time
|
|
22
|
+
if ctx.remaining_seconds < 10:
|
|
23
|
+
# Use fast path
|
|
24
|
+
...
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import asyncio
|
|
30
|
+
import contextvars
|
|
31
|
+
import functools
|
|
32
|
+
import threading
|
|
33
|
+
import time
|
|
34
|
+
from contextlib import contextmanager
|
|
35
|
+
from dataclasses import dataclass, field
|
|
36
|
+
from datetime import datetime, timedelta, timezone
|
|
37
|
+
from enum import Enum
|
|
38
|
+
from typing import Any, Callable, Generator, TypeVar
|
|
39
|
+
|
|
40
|
+
T = TypeVar("T")
|
|
41
|
+
|
|
42
|
+
# Context variable for deadline propagation
|
|
43
|
+
_deadline_context: contextvars.ContextVar["DeadlineContext | None"] = (
|
|
44
|
+
contextvars.ContextVar("deadline_context", default=None)
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class DeadlineStatus(str, Enum):
|
|
49
|
+
"""Status of a deadline."""
|
|
50
|
+
|
|
51
|
+
ACTIVE = "active" # Deadline is in the future
|
|
52
|
+
EXPIRED = "expired" # Deadline has passed
|
|
53
|
+
CANCELLED = "cancelled" # Deadline was cancelled
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass
|
|
57
|
+
class DeadlineContext:
|
|
58
|
+
"""Context for propagating deadlines across service boundaries.
|
|
59
|
+
|
|
60
|
+
DeadlineContext provides a way to track and propagate deadline information
|
|
61
|
+
in distributed systems. It supports:
|
|
62
|
+
- Absolute deadlines (UTC timestamp)
|
|
63
|
+
- Remaining time calculation
|
|
64
|
+
- Budget allocation for sub-operations
|
|
65
|
+
- Serialization for wire transmission
|
|
66
|
+
|
|
67
|
+
Example:
|
|
68
|
+
# Create from duration
|
|
69
|
+
ctx = DeadlineContext.from_seconds(60)
|
|
70
|
+
|
|
71
|
+
# Check time remaining
|
|
72
|
+
print(f"Remaining: {ctx.remaining_seconds}s")
|
|
73
|
+
|
|
74
|
+
# Create sub-context with portion of budget
|
|
75
|
+
sub_ctx = ctx.with_budget_fraction(0.5)
|
|
76
|
+
|
|
77
|
+
# Serialize for transmission
|
|
78
|
+
data = ctx.to_dict()
|
|
79
|
+
ctx2 = DeadlineContext.from_dict(data)
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
deadline_utc: datetime
|
|
83
|
+
created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
84
|
+
operation_id: str = ""
|
|
85
|
+
parent_id: str = ""
|
|
86
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
87
|
+
_status: DeadlineStatus = field(default=DeadlineStatus.ACTIVE, init=False)
|
|
88
|
+
_cancelled: bool = field(default=False, init=False)
|
|
89
|
+
|
|
90
|
+
@classmethod
|
|
91
|
+
def from_seconds(
|
|
92
|
+
cls,
|
|
93
|
+
seconds: float,
|
|
94
|
+
operation_id: str = "",
|
|
95
|
+
metadata: dict[str, Any] | None = None,
|
|
96
|
+
) -> "DeadlineContext":
|
|
97
|
+
"""Create deadline from seconds from now.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
seconds: Seconds until deadline
|
|
101
|
+
operation_id: Optional operation identifier
|
|
102
|
+
metadata: Optional metadata
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
New DeadlineContext
|
|
106
|
+
"""
|
|
107
|
+
now = datetime.now(timezone.utc)
|
|
108
|
+
deadline = now + timedelta(seconds=seconds)
|
|
109
|
+
return cls(
|
|
110
|
+
deadline_utc=deadline,
|
|
111
|
+
created_at=now,
|
|
112
|
+
operation_id=operation_id,
|
|
113
|
+
metadata=metadata or {},
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
@classmethod
|
|
117
|
+
def from_timestamp(
|
|
118
|
+
cls,
|
|
119
|
+
timestamp_utc: float,
|
|
120
|
+
operation_id: str = "",
|
|
121
|
+
) -> "DeadlineContext":
|
|
122
|
+
"""Create deadline from UTC timestamp.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
timestamp_utc: Unix timestamp in UTC
|
|
126
|
+
operation_id: Optional operation identifier
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
New DeadlineContext
|
|
130
|
+
"""
|
|
131
|
+
deadline = datetime.fromtimestamp(timestamp_utc, tz=timezone.utc)
|
|
132
|
+
return cls(deadline_utc=deadline, operation_id=operation_id)
|
|
133
|
+
|
|
134
|
+
@classmethod
|
|
135
|
+
def from_dict(cls, data: dict[str, Any]) -> "DeadlineContext":
|
|
136
|
+
"""Deserialize from dictionary (for wire transmission).
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
data: Dictionary with deadline data
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
New DeadlineContext
|
|
143
|
+
"""
|
|
144
|
+
deadline = datetime.fromisoformat(data["deadline_utc"])
|
|
145
|
+
if deadline.tzinfo is None:
|
|
146
|
+
deadline = deadline.replace(tzinfo=timezone.utc)
|
|
147
|
+
|
|
148
|
+
created = data.get("created_at")
|
|
149
|
+
if created:
|
|
150
|
+
created = datetime.fromisoformat(created)
|
|
151
|
+
if created.tzinfo is None:
|
|
152
|
+
created = created.replace(tzinfo=timezone.utc)
|
|
153
|
+
else:
|
|
154
|
+
created = datetime.now(timezone.utc)
|
|
155
|
+
|
|
156
|
+
return cls(
|
|
157
|
+
deadline_utc=deadline,
|
|
158
|
+
created_at=created,
|
|
159
|
+
operation_id=data.get("operation_id", ""),
|
|
160
|
+
parent_id=data.get("parent_id", ""),
|
|
161
|
+
metadata=data.get("metadata", {}),
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
def to_dict(self) -> dict[str, Any]:
|
|
165
|
+
"""Serialize to dictionary for wire transmission.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
Dictionary representation
|
|
169
|
+
"""
|
|
170
|
+
return {
|
|
171
|
+
"deadline_utc": self.deadline_utc.isoformat(),
|
|
172
|
+
"created_at": self.created_at.isoformat(),
|
|
173
|
+
"operation_id": self.operation_id,
|
|
174
|
+
"parent_id": self.parent_id,
|
|
175
|
+
"metadata": self.metadata,
|
|
176
|
+
"remaining_seconds": self.remaining_seconds,
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
@property
|
|
180
|
+
def remaining_seconds(self) -> float:
|
|
181
|
+
"""Get remaining time until deadline in seconds."""
|
|
182
|
+
if self._cancelled:
|
|
183
|
+
return 0.0
|
|
184
|
+
now = datetime.now(timezone.utc)
|
|
185
|
+
remaining = (self.deadline_utc - now).total_seconds()
|
|
186
|
+
return max(0.0, remaining)
|
|
187
|
+
|
|
188
|
+
@property
|
|
189
|
+
def remaining_timedelta(self) -> timedelta:
|
|
190
|
+
"""Get remaining time as timedelta."""
|
|
191
|
+
return timedelta(seconds=self.remaining_seconds)
|
|
192
|
+
|
|
193
|
+
@property
|
|
194
|
+
def is_expired(self) -> bool:
|
|
195
|
+
"""Check if deadline has passed."""
|
|
196
|
+
return self.remaining_seconds <= 0
|
|
197
|
+
|
|
198
|
+
@property
|
|
199
|
+
def is_cancelled(self) -> bool:
|
|
200
|
+
"""Check if deadline was cancelled."""
|
|
201
|
+
return self._cancelled
|
|
202
|
+
|
|
203
|
+
@property
|
|
204
|
+
def status(self) -> DeadlineStatus:
|
|
205
|
+
"""Get current deadline status."""
|
|
206
|
+
if self._cancelled:
|
|
207
|
+
return DeadlineStatus.CANCELLED
|
|
208
|
+
if self.is_expired:
|
|
209
|
+
return DeadlineStatus.EXPIRED
|
|
210
|
+
return DeadlineStatus.ACTIVE
|
|
211
|
+
|
|
212
|
+
def cancel(self) -> None:
|
|
213
|
+
"""Cancel the deadline."""
|
|
214
|
+
self._cancelled = True
|
|
215
|
+
self._status = DeadlineStatus.CANCELLED
|
|
216
|
+
|
|
217
|
+
def with_budget_fraction(
|
|
218
|
+
self,
|
|
219
|
+
fraction: float,
|
|
220
|
+
operation_id: str = "",
|
|
221
|
+
) -> "DeadlineContext":
|
|
222
|
+
"""Create a sub-deadline with a fraction of remaining time.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
fraction: Fraction of remaining time (0.0-1.0)
|
|
226
|
+
operation_id: Operation ID for sub-context
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
New DeadlineContext with reduced deadline
|
|
230
|
+
"""
|
|
231
|
+
if fraction <= 0 or fraction > 1:
|
|
232
|
+
raise ValueError(f"Fraction must be in (0, 1], got {fraction}")
|
|
233
|
+
|
|
234
|
+
remaining = self.remaining_seconds * fraction
|
|
235
|
+
return DeadlineContext.from_seconds(
|
|
236
|
+
remaining,
|
|
237
|
+
operation_id=operation_id or self.operation_id,
|
|
238
|
+
metadata={**self.metadata, "parent_id": self.operation_id},
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
def allocate(self, **allocations: float) -> "BudgetAllocation":
|
|
242
|
+
"""Allocate remaining time to named sub-operations.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
**allocations: Named allocations in seconds
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
BudgetAllocation with named deadlines
|
|
249
|
+
|
|
250
|
+
Example:
|
|
251
|
+
allocation = ctx.allocate(
|
|
252
|
+
validation=30,
|
|
253
|
+
reporting=10,
|
|
254
|
+
cleanup=5,
|
|
255
|
+
)
|
|
256
|
+
validate_with_deadline(data, allocation.validation)
|
|
257
|
+
"""
|
|
258
|
+
return BudgetAllocation.from_context(self, allocations)
|
|
259
|
+
|
|
260
|
+
def check(self) -> None:
|
|
261
|
+
"""Check if deadline has expired and raise if so.
|
|
262
|
+
|
|
263
|
+
Raises:
|
|
264
|
+
DeadlineExceededError: If deadline has passed
|
|
265
|
+
"""
|
|
266
|
+
if self.is_expired:
|
|
267
|
+
raise DeadlineExceededError(
|
|
268
|
+
f"Deadline exceeded for operation '{self.operation_id}'"
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
def __enter__(self) -> "DeadlineContext":
|
|
272
|
+
"""Enter context and set as current deadline."""
|
|
273
|
+
self._token = _deadline_context.set(self)
|
|
274
|
+
return self
|
|
275
|
+
|
|
276
|
+
def __exit__(self, *args: Any) -> None:
|
|
277
|
+
"""Exit context and restore previous deadline."""
|
|
278
|
+
_deadline_context.reset(self._token)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
class DeadlineExceededError(Exception):
|
|
282
|
+
"""Raised when a deadline has been exceeded."""
|
|
283
|
+
|
|
284
|
+
def __init__(self, message: str, context: DeadlineContext | None = None):
|
|
285
|
+
super().__init__(message)
|
|
286
|
+
self.context = context
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
@dataclass
|
|
290
|
+
class BudgetAllocation:
|
|
291
|
+
"""Allocation of timeout budget across multiple operations.
|
|
292
|
+
|
|
293
|
+
Provides named access to deadline contexts for sub-operations.
|
|
294
|
+
|
|
295
|
+
Example:
|
|
296
|
+
allocation = BudgetAllocation.from_context(ctx, {
|
|
297
|
+
"validation": 30,
|
|
298
|
+
"reporting": 10,
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
validate(data, allocation.validation.remaining_seconds)
|
|
302
|
+
"""
|
|
303
|
+
|
|
304
|
+
allocations: dict[str, DeadlineContext] = field(default_factory=dict)
|
|
305
|
+
total_allocated: float = 0.0
|
|
306
|
+
remaining_unallocated: float = 0.0
|
|
307
|
+
|
|
308
|
+
@classmethod
|
|
309
|
+
def from_context(
|
|
310
|
+
cls,
|
|
311
|
+
parent: DeadlineContext,
|
|
312
|
+
allocations: dict[str, float],
|
|
313
|
+
) -> "BudgetAllocation":
|
|
314
|
+
"""Create allocation from parent context and seconds per operation.
|
|
315
|
+
|
|
316
|
+
Args:
|
|
317
|
+
parent: Parent deadline context
|
|
318
|
+
allocations: Mapping of operation names to seconds
|
|
319
|
+
|
|
320
|
+
Returns:
|
|
321
|
+
BudgetAllocation with named deadline contexts
|
|
322
|
+
"""
|
|
323
|
+
total = sum(allocations.values())
|
|
324
|
+
remaining = parent.remaining_seconds
|
|
325
|
+
|
|
326
|
+
if total > remaining:
|
|
327
|
+
# Scale down proportionally if over budget
|
|
328
|
+
scale = remaining / total
|
|
329
|
+
allocations = {k: v * scale for k, v in allocations.items()}
|
|
330
|
+
total = remaining
|
|
331
|
+
|
|
332
|
+
result = cls(
|
|
333
|
+
total_allocated=total,
|
|
334
|
+
remaining_unallocated=remaining - total,
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
for name, seconds in allocations.items():
|
|
338
|
+
result.allocations[name] = DeadlineContext.from_seconds(
|
|
339
|
+
seconds,
|
|
340
|
+
operation_id=f"{parent.operation_id}/{name}",
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
return result
|
|
344
|
+
|
|
345
|
+
def __getattr__(self, name: str) -> DeadlineContext:
|
|
346
|
+
"""Get allocation by name."""
|
|
347
|
+
if name in self.allocations:
|
|
348
|
+
return self.allocations[name]
|
|
349
|
+
raise AttributeError(f"No allocation named '{name}'")
|
|
350
|
+
|
|
351
|
+
def get(self, name: str, default: DeadlineContext | None = None) -> DeadlineContext | None:
|
|
352
|
+
"""Get allocation by name with default."""
|
|
353
|
+
return self.allocations.get(name, default)
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
@dataclass
|
|
357
|
+
class TimeoutBudget:
|
|
358
|
+
"""Manages timeout budget across multiple operations.
|
|
359
|
+
|
|
360
|
+
TimeoutBudget tracks total time available and allocates it across
|
|
361
|
+
multiple operations, ensuring the total doesn't exceed the budget.
|
|
362
|
+
|
|
363
|
+
Example:
|
|
364
|
+
budget = TimeoutBudget(total_seconds=120)
|
|
365
|
+
|
|
366
|
+
# Allocate time for operations
|
|
367
|
+
validation_budget = budget.allocate("validation", 60)
|
|
368
|
+
report_budget = budget.allocate("reporting", 30)
|
|
369
|
+
|
|
370
|
+
# Check remaining
|
|
371
|
+
print(f"Remaining: {budget.remaining_seconds}s")
|
|
372
|
+
|
|
373
|
+
# Use allocated time
|
|
374
|
+
with budget.use("validation") as ctx:
|
|
375
|
+
validate(data)
|
|
376
|
+
"""
|
|
377
|
+
|
|
378
|
+
total_seconds: float
|
|
379
|
+
created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
380
|
+
_allocations: dict[str, float] = field(default_factory=dict, init=False)
|
|
381
|
+
_used: dict[str, float] = field(default_factory=dict, init=False)
|
|
382
|
+
_lock: threading.Lock = field(default_factory=threading.Lock, init=False)
|
|
383
|
+
|
|
384
|
+
@property
|
|
385
|
+
def elapsed_seconds(self) -> float:
|
|
386
|
+
"""Get seconds elapsed since budget creation."""
|
|
387
|
+
now = datetime.now(timezone.utc)
|
|
388
|
+
return (now - self.created_at).total_seconds()
|
|
389
|
+
|
|
390
|
+
@property
|
|
391
|
+
def remaining_seconds(self) -> float:
|
|
392
|
+
"""Get remaining seconds in budget."""
|
|
393
|
+
return max(0.0, self.total_seconds - self.elapsed_seconds)
|
|
394
|
+
|
|
395
|
+
@property
|
|
396
|
+
def allocated_seconds(self) -> float:
|
|
397
|
+
"""Get total seconds allocated."""
|
|
398
|
+
with self._lock:
|
|
399
|
+
return sum(self._allocations.values())
|
|
400
|
+
|
|
401
|
+
@property
|
|
402
|
+
def unallocated_seconds(self) -> float:
|
|
403
|
+
"""Get seconds not yet allocated."""
|
|
404
|
+
return max(0.0, self.remaining_seconds - self.allocated_seconds)
|
|
405
|
+
|
|
406
|
+
def allocate(self, name: str, seconds: float) -> DeadlineContext:
|
|
407
|
+
"""Allocate time for a named operation.
|
|
408
|
+
|
|
409
|
+
Args:
|
|
410
|
+
name: Operation name
|
|
411
|
+
seconds: Seconds to allocate
|
|
412
|
+
|
|
413
|
+
Returns:
|
|
414
|
+
DeadlineContext for the operation
|
|
415
|
+
|
|
416
|
+
Raises:
|
|
417
|
+
ValueError: If not enough time remaining
|
|
418
|
+
"""
|
|
419
|
+
with self._lock:
|
|
420
|
+
if seconds > self.unallocated_seconds:
|
|
421
|
+
raise ValueError(
|
|
422
|
+
f"Cannot allocate {seconds}s for '{name}': "
|
|
423
|
+
f"only {self.unallocated_seconds}s remaining"
|
|
424
|
+
)
|
|
425
|
+
self._allocations[name] = seconds
|
|
426
|
+
|
|
427
|
+
return DeadlineContext.from_seconds(seconds, operation_id=name)
|
|
428
|
+
|
|
429
|
+
def allocate_fraction(self, name: str, fraction: float) -> DeadlineContext:
|
|
430
|
+
"""Allocate a fraction of remaining time.
|
|
431
|
+
|
|
432
|
+
Args:
|
|
433
|
+
name: Operation name
|
|
434
|
+
fraction: Fraction of remaining time (0.0-1.0)
|
|
435
|
+
|
|
436
|
+
Returns:
|
|
437
|
+
DeadlineContext for the operation
|
|
438
|
+
"""
|
|
439
|
+
seconds = self.unallocated_seconds * fraction
|
|
440
|
+
return self.allocate(name, seconds)
|
|
441
|
+
|
|
442
|
+
def record_usage(self, name: str, seconds: float) -> None:
|
|
443
|
+
"""Record actual time used for an operation.
|
|
444
|
+
|
|
445
|
+
Args:
|
|
446
|
+
name: Operation name
|
|
447
|
+
seconds: Actual seconds used
|
|
448
|
+
"""
|
|
449
|
+
with self._lock:
|
|
450
|
+
self._used[name] = seconds
|
|
451
|
+
|
|
452
|
+
@contextmanager
|
|
453
|
+
def use(self, name: str, seconds: float | None = None) -> Generator[DeadlineContext, None, None]:
|
|
454
|
+
"""Context manager to allocate and track usage.
|
|
455
|
+
|
|
456
|
+
Args:
|
|
457
|
+
name: Operation name
|
|
458
|
+
seconds: Seconds to allocate (None = use remaining)
|
|
459
|
+
|
|
460
|
+
Yields:
|
|
461
|
+
DeadlineContext for the operation
|
|
462
|
+
"""
|
|
463
|
+
if seconds is None:
|
|
464
|
+
seconds = self.unallocated_seconds * 0.5 # Default to half remaining
|
|
465
|
+
|
|
466
|
+
ctx = self.allocate(name, seconds)
|
|
467
|
+
start = time.time()
|
|
468
|
+
|
|
469
|
+
try:
|
|
470
|
+
yield ctx
|
|
471
|
+
finally:
|
|
472
|
+
actual = time.time() - start
|
|
473
|
+
self.record_usage(name, actual)
|
|
474
|
+
|
|
475
|
+
def get_summary(self) -> dict[str, Any]:
|
|
476
|
+
"""Get budget summary.
|
|
477
|
+
|
|
478
|
+
Returns:
|
|
479
|
+
Dictionary with budget statistics
|
|
480
|
+
"""
|
|
481
|
+
with self._lock:
|
|
482
|
+
return {
|
|
483
|
+
"total_seconds": self.total_seconds,
|
|
484
|
+
"elapsed_seconds": self.elapsed_seconds,
|
|
485
|
+
"remaining_seconds": self.remaining_seconds,
|
|
486
|
+
"allocated": dict(self._allocations),
|
|
487
|
+
"used": dict(self._used),
|
|
488
|
+
"unallocated_seconds": self.unallocated_seconds,
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
class DeadlinePropagator:
|
|
493
|
+
"""Propagates deadlines through async and sync contexts.
|
|
494
|
+
|
|
495
|
+
This class manages deadline context propagation for both synchronous
|
|
496
|
+
and asynchronous code, ensuring deadline information is available
|
|
497
|
+
throughout the call stack.
|
|
498
|
+
|
|
499
|
+
Example:
|
|
500
|
+
propagator = DeadlinePropagator()
|
|
501
|
+
|
|
502
|
+
# Set deadline in async context
|
|
503
|
+
async with propagator.async_context(60) as ctx:
|
|
504
|
+
result = await some_async_operation()
|
|
505
|
+
|
|
506
|
+
# Get current deadline
|
|
507
|
+
current = propagator.current()
|
|
508
|
+
if current and current.remaining_seconds < 10:
|
|
509
|
+
# Use fast path
|
|
510
|
+
...
|
|
511
|
+
"""
|
|
512
|
+
|
|
513
|
+
def __init__(self) -> None:
|
|
514
|
+
self._local = threading.local()
|
|
515
|
+
|
|
516
|
+
@property
|
|
517
|
+
def current(self) -> DeadlineContext | None:
|
|
518
|
+
"""Get current deadline context."""
|
|
519
|
+
# Try context var first (works in async)
|
|
520
|
+
ctx = _deadline_context.get()
|
|
521
|
+
if ctx is not None:
|
|
522
|
+
return ctx
|
|
523
|
+
|
|
524
|
+
# Fall back to thread local
|
|
525
|
+
return getattr(self._local, "deadline", None)
|
|
526
|
+
|
|
527
|
+
@contextmanager
|
|
528
|
+
def context(
|
|
529
|
+
self,
|
|
530
|
+
seconds: float,
|
|
531
|
+
operation_id: str = "",
|
|
532
|
+
) -> Generator[DeadlineContext, None, None]:
|
|
533
|
+
"""Create a deadline context for synchronous code.
|
|
534
|
+
|
|
535
|
+
Args:
|
|
536
|
+
seconds: Seconds until deadline
|
|
537
|
+
operation_id: Optional operation identifier
|
|
538
|
+
|
|
539
|
+
Yields:
|
|
540
|
+
DeadlineContext
|
|
541
|
+
"""
|
|
542
|
+
ctx = DeadlineContext.from_seconds(seconds, operation_id)
|
|
543
|
+
old = getattr(self._local, "deadline", None)
|
|
544
|
+
self._local.deadline = ctx
|
|
545
|
+
|
|
546
|
+
try:
|
|
547
|
+
with ctx:
|
|
548
|
+
yield ctx
|
|
549
|
+
finally:
|
|
550
|
+
self._local.deadline = old
|
|
551
|
+
|
|
552
|
+
@contextmanager
|
|
553
|
+
def async_context(
|
|
554
|
+
self,
|
|
555
|
+
seconds: float,
|
|
556
|
+
operation_id: str = "",
|
|
557
|
+
) -> Generator[DeadlineContext, None, None]:
|
|
558
|
+
"""Create a deadline context for async code.
|
|
559
|
+
|
|
560
|
+
Args:
|
|
561
|
+
seconds: Seconds until deadline
|
|
562
|
+
operation_id: Optional operation identifier
|
|
563
|
+
|
|
564
|
+
Yields:
|
|
565
|
+
DeadlineContext
|
|
566
|
+
"""
|
|
567
|
+
ctx = DeadlineContext.from_seconds(seconds, operation_id)
|
|
568
|
+
token = _deadline_context.set(ctx)
|
|
569
|
+
|
|
570
|
+
try:
|
|
571
|
+
yield ctx
|
|
572
|
+
finally:
|
|
573
|
+
_deadline_context.reset(token)
|
|
574
|
+
|
|
575
|
+
def check(self) -> None:
|
|
576
|
+
"""Check current deadline and raise if expired.
|
|
577
|
+
|
|
578
|
+
Raises:
|
|
579
|
+
DeadlineExceededError: If current deadline has passed
|
|
580
|
+
"""
|
|
581
|
+
ctx = self.current
|
|
582
|
+
if ctx is not None:
|
|
583
|
+
ctx.check()
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
# Module-level propagator instance
|
|
587
|
+
_propagator = DeadlinePropagator()
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
def get_current_deadline() -> DeadlineContext | None:
|
|
591
|
+
"""Get the current deadline context.
|
|
592
|
+
|
|
593
|
+
Returns:
|
|
594
|
+
Current DeadlineContext or None
|
|
595
|
+
"""
|
|
596
|
+
return _propagator.current
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
def check_deadline() -> None:
|
|
600
|
+
"""Check current deadline and raise if expired.
|
|
601
|
+
|
|
602
|
+
Raises:
|
|
603
|
+
DeadlineExceededError: If current deadline has passed
|
|
604
|
+
"""
|
|
605
|
+
_propagator.check()
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
def with_deadline(seconds: float, operation_id: str = "") -> Callable[[Callable[..., T]], Callable[..., T]]:
|
|
609
|
+
"""Decorator to add deadline to a function.
|
|
610
|
+
|
|
611
|
+
Args:
|
|
612
|
+
seconds: Seconds until deadline
|
|
613
|
+
operation_id: Optional operation identifier
|
|
614
|
+
|
|
615
|
+
Returns:
|
|
616
|
+
Decorated function
|
|
617
|
+
|
|
618
|
+
Example:
|
|
619
|
+
@with_deadline(30, "my_operation")
|
|
620
|
+
def process_data(data):
|
|
621
|
+
# Deadline context is available
|
|
622
|
+
ctx = get_current_deadline()
|
|
623
|
+
...
|
|
624
|
+
"""
|
|
625
|
+
def decorator(func: Callable[..., T]) -> Callable[..., T]:
|
|
626
|
+
@functools.wraps(func)
|
|
627
|
+
def wrapper(*args: Any, **kwargs: Any) -> T:
|
|
628
|
+
with _propagator.context(seconds, operation_id):
|
|
629
|
+
return func(*args, **kwargs)
|
|
630
|
+
return wrapper
|
|
631
|
+
return decorator
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
def propagate_deadline(ctx: DeadlineContext) -> Callable[[Callable[..., T]], Callable[..., T]]:
|
|
635
|
+
"""Decorator to propagate an existing deadline context.
|
|
636
|
+
|
|
637
|
+
Args:
|
|
638
|
+
ctx: DeadlineContext to propagate
|
|
639
|
+
|
|
640
|
+
Returns:
|
|
641
|
+
Decorated function
|
|
642
|
+
|
|
643
|
+
Example:
|
|
644
|
+
ctx = DeadlineContext.from_seconds(60)
|
|
645
|
+
|
|
646
|
+
@propagate_deadline(ctx)
|
|
647
|
+
def sub_operation():
|
|
648
|
+
remaining = get_current_deadline().remaining_seconds
|
|
649
|
+
...
|
|
650
|
+
"""
|
|
651
|
+
def decorator(func: Callable[..., T]) -> Callable[..., T]:
|
|
652
|
+
@functools.wraps(func)
|
|
653
|
+
def wrapper(*args: Any, **kwargs: Any) -> T:
|
|
654
|
+
with ctx:
|
|
655
|
+
return func(*args, **kwargs)
|
|
656
|
+
return wrapper
|
|
657
|
+
return decorator
|