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,708 @@
|
|
|
1
|
+
"""Async base classes for checkpoint actions and triggers.
|
|
2
|
+
|
|
3
|
+
This module provides async-compatible base classes that enable non-blocking
|
|
4
|
+
execution of checkpoint pipelines, supporting high-throughput enterprise
|
|
5
|
+
workloads.
|
|
6
|
+
|
|
7
|
+
Design Principles:
|
|
8
|
+
1. Backward Compatibility: Sync actions work in async context via run_in_executor
|
|
9
|
+
2. Gradual Migration: Mixed sync/async actions in same checkpoint
|
|
10
|
+
3. Protocol-Based: Uses Protocol for duck typing flexibility
|
|
11
|
+
4. Composable: Async middleware/decorator support
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import asyncio
|
|
17
|
+
import functools
|
|
18
|
+
from abc import ABC, abstractmethod
|
|
19
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
20
|
+
from dataclasses import dataclass, field
|
|
21
|
+
from datetime import datetime
|
|
22
|
+
from typing import (
|
|
23
|
+
TYPE_CHECKING,
|
|
24
|
+
Any,
|
|
25
|
+
AsyncIterator,
|
|
26
|
+
Awaitable,
|
|
27
|
+
Callable,
|
|
28
|
+
Generic,
|
|
29
|
+
Protocol,
|
|
30
|
+
TypeVar,
|
|
31
|
+
Union,
|
|
32
|
+
runtime_checkable,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
from truthound.checkpoint.actions.base import (
|
|
36
|
+
ActionConfig,
|
|
37
|
+
ActionResult,
|
|
38
|
+
ActionStatus,
|
|
39
|
+
BaseAction,
|
|
40
|
+
NotifyCondition,
|
|
41
|
+
)
|
|
42
|
+
from truthound.checkpoint.triggers.base import (
|
|
43
|
+
BaseTrigger,
|
|
44
|
+
TriggerConfig,
|
|
45
|
+
TriggerResult,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
if TYPE_CHECKING:
|
|
49
|
+
from truthound.checkpoint.checkpoint import CheckpointResult
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# Type variables
|
|
53
|
+
ConfigT = TypeVar("ConfigT", bound=ActionConfig)
|
|
54
|
+
TriggerConfigT = TypeVar("TriggerConfigT", bound=TriggerConfig)
|
|
55
|
+
T = TypeVar("T")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# =============================================================================
|
|
59
|
+
# Protocols for Duck Typing
|
|
60
|
+
# =============================================================================
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@runtime_checkable
|
|
64
|
+
class AsyncExecutable(Protocol):
|
|
65
|
+
"""Protocol for async-executable actions."""
|
|
66
|
+
|
|
67
|
+
async def execute_async(
|
|
68
|
+
self, checkpoint_result: "CheckpointResult"
|
|
69
|
+
) -> ActionResult:
|
|
70
|
+
"""Execute asynchronously."""
|
|
71
|
+
...
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@runtime_checkable
|
|
75
|
+
class SyncExecutable(Protocol):
|
|
76
|
+
"""Protocol for sync-executable actions."""
|
|
77
|
+
|
|
78
|
+
def execute(self, checkpoint_result: "CheckpointResult") -> ActionResult:
|
|
79
|
+
"""Execute synchronously."""
|
|
80
|
+
...
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# =============================================================================
|
|
84
|
+
# Async Action Base Class
|
|
85
|
+
# =============================================================================
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class AsyncBaseAction(ABC, Generic[ConfigT]):
|
|
89
|
+
"""Async-native base class for checkpoint actions.
|
|
90
|
+
|
|
91
|
+
This class provides the same interface as BaseAction but with async/await
|
|
92
|
+
support for non-blocking I/O operations like HTTP requests, database queries,
|
|
93
|
+
and file operations.
|
|
94
|
+
|
|
95
|
+
Key Features:
|
|
96
|
+
- Native async/await support
|
|
97
|
+
- Automatic timeout handling via asyncio.wait_for
|
|
98
|
+
- Retry with exponential backoff
|
|
99
|
+
- Graceful cancellation support
|
|
100
|
+
- Mixed execution: can be used alongside sync actions
|
|
101
|
+
|
|
102
|
+
Example:
|
|
103
|
+
>>> class AsyncSlackNotification(AsyncBaseAction[SlackConfig]):
|
|
104
|
+
... action_type = "async_slack"
|
|
105
|
+
...
|
|
106
|
+
... async def _execute_async(self, result: CheckpointResult) -> ActionResult:
|
|
107
|
+
... async with aiohttp.ClientSession() as session:
|
|
108
|
+
... await session.post(self._config.webhook_url, json=payload)
|
|
109
|
+
... return ActionResult(status=ActionStatus.SUCCESS, ...)
|
|
110
|
+
|
|
111
|
+
Attributes:
|
|
112
|
+
action_type: String identifier for this action type.
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
action_type: str = "async_base"
|
|
116
|
+
|
|
117
|
+
def __init__(self, config: ConfigT | None = None, **kwargs: Any) -> None:
|
|
118
|
+
"""Initialize the async action.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
config: Action configuration. If None, uses default.
|
|
122
|
+
**kwargs: Additional config options.
|
|
123
|
+
"""
|
|
124
|
+
self._config = config or self._default_config()
|
|
125
|
+
|
|
126
|
+
for key, value in kwargs.items():
|
|
127
|
+
if hasattr(self._config, key):
|
|
128
|
+
setattr(self._config, key, value)
|
|
129
|
+
|
|
130
|
+
@classmethod
|
|
131
|
+
@abstractmethod
|
|
132
|
+
def _default_config(cls) -> ConfigT:
|
|
133
|
+
"""Create default configuration."""
|
|
134
|
+
pass
|
|
135
|
+
|
|
136
|
+
@property
|
|
137
|
+
def config(self) -> ConfigT:
|
|
138
|
+
"""Get action configuration."""
|
|
139
|
+
return self._config
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def name(self) -> str:
|
|
143
|
+
"""Get action name."""
|
|
144
|
+
return self._config.name or f"{self.action_type}_{id(self)}"
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
def enabled(self) -> bool:
|
|
148
|
+
"""Check if action is enabled."""
|
|
149
|
+
return self._config.enabled
|
|
150
|
+
|
|
151
|
+
def should_run(self, result_status: str) -> bool:
|
|
152
|
+
"""Check if action should run based on status."""
|
|
153
|
+
if not self.enabled:
|
|
154
|
+
return False
|
|
155
|
+
|
|
156
|
+
notify_condition = self._config.notify_on
|
|
157
|
+
if isinstance(notify_condition, str):
|
|
158
|
+
notify_condition = NotifyCondition(notify_condition.lower())
|
|
159
|
+
|
|
160
|
+
return notify_condition.should_notify(result_status)
|
|
161
|
+
|
|
162
|
+
async def execute_async(
|
|
163
|
+
self, checkpoint_result: "CheckpointResult"
|
|
164
|
+
) -> ActionResult:
|
|
165
|
+
"""Execute the action asynchronously with timeout and retry.
|
|
166
|
+
|
|
167
|
+
This method wraps _execute_async with:
|
|
168
|
+
- Condition checking (notify_on)
|
|
169
|
+
- Timeout handling
|
|
170
|
+
- Retry logic with exponential backoff
|
|
171
|
+
- Timing measurement
|
|
172
|
+
- Error handling
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
checkpoint_result: The checkpoint result to process.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
ActionResult with execution outcome.
|
|
179
|
+
"""
|
|
180
|
+
started_at = datetime.now()
|
|
181
|
+
result_status = (
|
|
182
|
+
checkpoint_result.status.value
|
|
183
|
+
if hasattr(checkpoint_result.status, "value")
|
|
184
|
+
else str(checkpoint_result.status)
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
# Check if should run
|
|
188
|
+
if not self.should_run(result_status):
|
|
189
|
+
return ActionResult(
|
|
190
|
+
action_name=self.name,
|
|
191
|
+
action_type=self.action_type,
|
|
192
|
+
status=ActionStatus.SKIPPED,
|
|
193
|
+
message=f"Skipped: notify_on={self._config.notify_on}, status={result_status}",
|
|
194
|
+
started_at=started_at,
|
|
195
|
+
completed_at=datetime.now(),
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
# Execute with retries
|
|
199
|
+
last_error: Exception | None = None
|
|
200
|
+
retry_delay = self._config.retry_delay_seconds
|
|
201
|
+
|
|
202
|
+
for attempt in range(self._config.retry_count + 1):
|
|
203
|
+
try:
|
|
204
|
+
# Apply timeout
|
|
205
|
+
result = await asyncio.wait_for(
|
|
206
|
+
self._execute_async(checkpoint_result),
|
|
207
|
+
timeout=self._config.timeout_seconds,
|
|
208
|
+
)
|
|
209
|
+
result.started_at = started_at
|
|
210
|
+
result.completed_at = datetime.now()
|
|
211
|
+
result.duration_ms = (
|
|
212
|
+
result.completed_at - started_at
|
|
213
|
+
).total_seconds() * 1000
|
|
214
|
+
return result
|
|
215
|
+
|
|
216
|
+
except asyncio.TimeoutError:
|
|
217
|
+
last_error = asyncio.TimeoutError(
|
|
218
|
+
f"Action timed out after {self._config.timeout_seconds}s"
|
|
219
|
+
)
|
|
220
|
+
if attempt < self._config.retry_count:
|
|
221
|
+
await asyncio.sleep(retry_delay)
|
|
222
|
+
retry_delay *= 2 # Exponential backoff
|
|
223
|
+
|
|
224
|
+
except asyncio.CancelledError:
|
|
225
|
+
# Propagate cancellation
|
|
226
|
+
raise
|
|
227
|
+
|
|
228
|
+
except Exception as e:
|
|
229
|
+
last_error = e
|
|
230
|
+
if attempt < self._config.retry_count:
|
|
231
|
+
await asyncio.sleep(retry_delay)
|
|
232
|
+
retry_delay *= 2
|
|
233
|
+
|
|
234
|
+
# All retries failed
|
|
235
|
+
completed_at = datetime.now()
|
|
236
|
+
return ActionResult(
|
|
237
|
+
action_name=self.name,
|
|
238
|
+
action_type=self.action_type,
|
|
239
|
+
status=ActionStatus.ERROR,
|
|
240
|
+
message=f"Failed after {self._config.retry_count + 1} attempts",
|
|
241
|
+
started_at=started_at,
|
|
242
|
+
completed_at=completed_at,
|
|
243
|
+
duration_ms=(completed_at - started_at).total_seconds() * 1000,
|
|
244
|
+
error=str(last_error),
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
@abstractmethod
|
|
248
|
+
async def _execute_async(
|
|
249
|
+
self, checkpoint_result: "CheckpointResult"
|
|
250
|
+
) -> ActionResult:
|
|
251
|
+
"""Execute the action implementation asynchronously.
|
|
252
|
+
|
|
253
|
+
Subclasses must implement this method.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
checkpoint_result: The checkpoint result to process.
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
ActionResult with execution outcome.
|
|
260
|
+
"""
|
|
261
|
+
pass
|
|
262
|
+
|
|
263
|
+
def validate_config(self) -> list[str]:
|
|
264
|
+
"""Validate configuration."""
|
|
265
|
+
return []
|
|
266
|
+
|
|
267
|
+
def __repr__(self) -> str:
|
|
268
|
+
return f"{self.__class__.__name__}(name={self.name!r})"
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
# =============================================================================
|
|
272
|
+
# Async Trigger Base Class
|
|
273
|
+
# =============================================================================
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
class AsyncBaseTrigger(ABC, Generic[TriggerConfigT]):
|
|
277
|
+
"""Async-native base class for triggers.
|
|
278
|
+
|
|
279
|
+
Supports async trigger checking for event-based and polling triggers
|
|
280
|
+
that need non-blocking I/O.
|
|
281
|
+
|
|
282
|
+
Example:
|
|
283
|
+
>>> class AsyncKafkaTrigger(AsyncBaseTrigger[KafkaConfig]):
|
|
284
|
+
... async def should_trigger_async(self) -> TriggerResult:
|
|
285
|
+
... message = await self._consumer.poll()
|
|
286
|
+
... return TriggerResult(should_run=message is not None)
|
|
287
|
+
"""
|
|
288
|
+
|
|
289
|
+
trigger_type: str = "async_base"
|
|
290
|
+
|
|
291
|
+
def __init__(
|
|
292
|
+
self, config: TriggerConfigT | None = None, **kwargs: Any
|
|
293
|
+
) -> None:
|
|
294
|
+
self._config = config or self._default_config()
|
|
295
|
+
|
|
296
|
+
for key, value in kwargs.items():
|
|
297
|
+
if hasattr(self._config, key):
|
|
298
|
+
setattr(self._config, key, value)
|
|
299
|
+
|
|
300
|
+
self._status = "stopped"
|
|
301
|
+
self._run_count = 0
|
|
302
|
+
self._last_run: datetime | None = None
|
|
303
|
+
self._checkpoint: Any = None
|
|
304
|
+
|
|
305
|
+
@classmethod
|
|
306
|
+
@abstractmethod
|
|
307
|
+
def _default_config(cls) -> TriggerConfigT:
|
|
308
|
+
"""Create default configuration."""
|
|
309
|
+
pass
|
|
310
|
+
|
|
311
|
+
@property
|
|
312
|
+
def config(self) -> TriggerConfigT:
|
|
313
|
+
return self._config
|
|
314
|
+
|
|
315
|
+
@property
|
|
316
|
+
def name(self) -> str:
|
|
317
|
+
return self._config.name or f"{self.trigger_type}_{id(self)}"
|
|
318
|
+
|
|
319
|
+
@property
|
|
320
|
+
def enabled(self) -> bool:
|
|
321
|
+
return self._config.enabled
|
|
322
|
+
|
|
323
|
+
def attach(self, checkpoint: Any) -> None:
|
|
324
|
+
self._checkpoint = checkpoint
|
|
325
|
+
|
|
326
|
+
@abstractmethod
|
|
327
|
+
async def should_trigger_async(self) -> TriggerResult:
|
|
328
|
+
"""Check trigger condition asynchronously."""
|
|
329
|
+
pass
|
|
330
|
+
|
|
331
|
+
def should_trigger(self) -> TriggerResult:
|
|
332
|
+
"""Sync wrapper for compatibility."""
|
|
333
|
+
return asyncio.get_event_loop().run_until_complete(
|
|
334
|
+
self.should_trigger_async()
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
async def start_async(self) -> None:
|
|
338
|
+
"""Start the trigger asynchronously."""
|
|
339
|
+
if not self.enabled:
|
|
340
|
+
return
|
|
341
|
+
self._status = "active"
|
|
342
|
+
await self._on_start_async()
|
|
343
|
+
|
|
344
|
+
async def stop_async(self) -> None:
|
|
345
|
+
"""Stop the trigger asynchronously."""
|
|
346
|
+
self._status = "stopped"
|
|
347
|
+
await self._on_stop_async()
|
|
348
|
+
|
|
349
|
+
async def _on_start_async(self) -> None:
|
|
350
|
+
"""Called when trigger starts. Override for custom behavior."""
|
|
351
|
+
pass
|
|
352
|
+
|
|
353
|
+
async def _on_stop_async(self) -> None:
|
|
354
|
+
"""Called when trigger stops. Override for custom behavior."""
|
|
355
|
+
pass
|
|
356
|
+
|
|
357
|
+
def record_run(self) -> None:
|
|
358
|
+
self._run_count += 1
|
|
359
|
+
self._last_run = datetime.now()
|
|
360
|
+
|
|
361
|
+
def validate_config(self) -> list[str]:
|
|
362
|
+
return []
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
# =============================================================================
|
|
366
|
+
# Adapter: Sync to Async
|
|
367
|
+
# =============================================================================
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
class SyncActionAdapter(AsyncBaseAction[ConfigT]):
|
|
371
|
+
"""Adapter to run sync actions in async context.
|
|
372
|
+
|
|
373
|
+
Wraps synchronous BaseAction instances to work seamlessly in async
|
|
374
|
+
checkpoint pipelines by running them in a thread pool executor.
|
|
375
|
+
|
|
376
|
+
Example:
|
|
377
|
+
>>> sync_action = SlackNotification(webhook_url="...")
|
|
378
|
+
>>> async_action = SyncActionAdapter(sync_action)
|
|
379
|
+
>>> await async_action.execute_async(result)
|
|
380
|
+
"""
|
|
381
|
+
|
|
382
|
+
action_type = "sync_adapter"
|
|
383
|
+
|
|
384
|
+
def __init__(
|
|
385
|
+
self,
|
|
386
|
+
wrapped_action: BaseAction[ConfigT],
|
|
387
|
+
executor: ThreadPoolExecutor | None = None,
|
|
388
|
+
) -> None:
|
|
389
|
+
"""Initialize the adapter.
|
|
390
|
+
|
|
391
|
+
Args:
|
|
392
|
+
wrapped_action: The sync action to wrap.
|
|
393
|
+
executor: Optional thread pool executor. If None, uses default.
|
|
394
|
+
"""
|
|
395
|
+
self._wrapped = wrapped_action
|
|
396
|
+
self._executor = executor
|
|
397
|
+
# Use wrapped action's config
|
|
398
|
+
self._config = wrapped_action.config
|
|
399
|
+
|
|
400
|
+
@classmethod
|
|
401
|
+
def _default_config(cls) -> ConfigT:
|
|
402
|
+
# Not used, config comes from wrapped action
|
|
403
|
+
raise NotImplementedError
|
|
404
|
+
|
|
405
|
+
@property
|
|
406
|
+
def name(self) -> str:
|
|
407
|
+
return self._wrapped.name
|
|
408
|
+
|
|
409
|
+
@property
|
|
410
|
+
def action_type(self) -> str:
|
|
411
|
+
return f"async_{self._wrapped.action_type}"
|
|
412
|
+
|
|
413
|
+
def should_run(self, result_status: str) -> bool:
|
|
414
|
+
return self._wrapped.should_run(result_status)
|
|
415
|
+
|
|
416
|
+
async def _execute_async(
|
|
417
|
+
self, checkpoint_result: "CheckpointResult"
|
|
418
|
+
) -> ActionResult:
|
|
419
|
+
"""Run sync action in thread pool."""
|
|
420
|
+
loop = asyncio.get_running_loop()
|
|
421
|
+
|
|
422
|
+
# Run sync execute in executor
|
|
423
|
+
result = await loop.run_in_executor(
|
|
424
|
+
self._executor,
|
|
425
|
+
self._wrapped.execute,
|
|
426
|
+
checkpoint_result,
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
return result
|
|
430
|
+
|
|
431
|
+
def validate_config(self) -> list[str]:
|
|
432
|
+
return self._wrapped.validate_config()
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def adapt_to_async(
|
|
436
|
+
action: BaseAction[ConfigT] | AsyncBaseAction[ConfigT],
|
|
437
|
+
executor: ThreadPoolExecutor | None = None,
|
|
438
|
+
) -> AsyncBaseAction[ConfigT]:
|
|
439
|
+
"""Adapt any action to async interface.
|
|
440
|
+
|
|
441
|
+
Args:
|
|
442
|
+
action: Sync or async action.
|
|
443
|
+
executor: Optional thread pool for sync actions.
|
|
444
|
+
|
|
445
|
+
Returns:
|
|
446
|
+
Async-compatible action.
|
|
447
|
+
|
|
448
|
+
Raises:
|
|
449
|
+
TypeError: If action is not a valid action type.
|
|
450
|
+
"""
|
|
451
|
+
# Check if already async
|
|
452
|
+
if isinstance(action, AsyncBaseAction):
|
|
453
|
+
return action
|
|
454
|
+
|
|
455
|
+
# Check if it's a sync action
|
|
456
|
+
if isinstance(action, BaseAction):
|
|
457
|
+
return SyncActionAdapter(action, executor)
|
|
458
|
+
|
|
459
|
+
# Check if it has the required interface (duck typing)
|
|
460
|
+
if hasattr(action, "execute") and callable(getattr(action, "execute")):
|
|
461
|
+
return SyncActionAdapter(action, executor) # type: ignore
|
|
462
|
+
|
|
463
|
+
raise TypeError(
|
|
464
|
+
f"Cannot adapt {type(action).__name__} to async. "
|
|
465
|
+
f"Expected BaseAction or AsyncBaseAction, got {type(action)}"
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
# =============================================================================
|
|
470
|
+
# Async Execution Context
|
|
471
|
+
# =============================================================================
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
@dataclass
|
|
475
|
+
class AsyncExecutionContext:
|
|
476
|
+
"""Context for async checkpoint execution.
|
|
477
|
+
|
|
478
|
+
Provides shared resources and configuration for async execution.
|
|
479
|
+
|
|
480
|
+
Attributes:
|
|
481
|
+
executor: Thread pool for sync action execution.
|
|
482
|
+
semaphore: Concurrency limiter.
|
|
483
|
+
timeout: Default timeout for actions.
|
|
484
|
+
cancel_on_first_error: Stop remaining actions on first error.
|
|
485
|
+
"""
|
|
486
|
+
|
|
487
|
+
executor: ThreadPoolExecutor | None = None
|
|
488
|
+
semaphore: asyncio.Semaphore | None = None
|
|
489
|
+
timeout: float = 30.0
|
|
490
|
+
cancel_on_first_error: bool = False
|
|
491
|
+
_owned_executor: bool = field(default=False, repr=False)
|
|
492
|
+
|
|
493
|
+
def __post_init__(self) -> None:
|
|
494
|
+
if self.executor is None:
|
|
495
|
+
self.executor = ThreadPoolExecutor(max_workers=4)
|
|
496
|
+
self._owned_executor = True
|
|
497
|
+
|
|
498
|
+
async def __aenter__(self) -> "AsyncExecutionContext":
|
|
499
|
+
return self
|
|
500
|
+
|
|
501
|
+
async def __aexit__(self, *args: Any) -> None:
|
|
502
|
+
if self._owned_executor and self.executor:
|
|
503
|
+
self.executor.shutdown(wait=False)
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
# =============================================================================
|
|
507
|
+
# Action Execution Strategies
|
|
508
|
+
# =============================================================================
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
class ExecutionStrategy(ABC):
|
|
512
|
+
"""Base class for action execution strategies."""
|
|
513
|
+
|
|
514
|
+
@abstractmethod
|
|
515
|
+
async def execute(
|
|
516
|
+
self,
|
|
517
|
+
actions: list[AsyncBaseAction[Any]],
|
|
518
|
+
checkpoint_result: "CheckpointResult",
|
|
519
|
+
context: AsyncExecutionContext | None = None,
|
|
520
|
+
) -> list[ActionResult]:
|
|
521
|
+
"""Execute actions according to strategy."""
|
|
522
|
+
pass
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
class SequentialStrategy(ExecutionStrategy):
|
|
526
|
+
"""Execute actions one after another.
|
|
527
|
+
|
|
528
|
+
Use when actions have dependencies or order matters.
|
|
529
|
+
"""
|
|
530
|
+
|
|
531
|
+
async def execute(
|
|
532
|
+
self,
|
|
533
|
+
actions: list[AsyncBaseAction[Any]],
|
|
534
|
+
checkpoint_result: "CheckpointResult",
|
|
535
|
+
context: AsyncExecutionContext | None = None,
|
|
536
|
+
) -> list[ActionResult]:
|
|
537
|
+
results = []
|
|
538
|
+
for action in actions:
|
|
539
|
+
result = await action.execute_async(checkpoint_result)
|
|
540
|
+
results.append(result)
|
|
541
|
+
|
|
542
|
+
if context and context.cancel_on_first_error:
|
|
543
|
+
if result.status == ActionStatus.ERROR:
|
|
544
|
+
break
|
|
545
|
+
|
|
546
|
+
return results
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
class ConcurrentStrategy(ExecutionStrategy):
|
|
550
|
+
"""Execute all actions concurrently.
|
|
551
|
+
|
|
552
|
+
Use when actions are independent and can run in parallel.
|
|
553
|
+
"""
|
|
554
|
+
|
|
555
|
+
def __init__(self, max_concurrency: int | None = None) -> None:
|
|
556
|
+
self.max_concurrency = max_concurrency
|
|
557
|
+
|
|
558
|
+
async def execute(
|
|
559
|
+
self,
|
|
560
|
+
actions: list[AsyncBaseAction[Any]],
|
|
561
|
+
checkpoint_result: "CheckpointResult",
|
|
562
|
+
context: AsyncExecutionContext | None = None,
|
|
563
|
+
) -> list[ActionResult]:
|
|
564
|
+
semaphore = None
|
|
565
|
+
if self.max_concurrency:
|
|
566
|
+
semaphore = asyncio.Semaphore(self.max_concurrency)
|
|
567
|
+
elif context and context.semaphore:
|
|
568
|
+
semaphore = context.semaphore
|
|
569
|
+
|
|
570
|
+
async def run_with_semaphore(
|
|
571
|
+
action: AsyncBaseAction[Any],
|
|
572
|
+
) -> ActionResult:
|
|
573
|
+
if semaphore:
|
|
574
|
+
async with semaphore:
|
|
575
|
+
return await action.execute_async(checkpoint_result)
|
|
576
|
+
return await action.execute_async(checkpoint_result)
|
|
577
|
+
|
|
578
|
+
tasks = [run_with_semaphore(action) for action in actions]
|
|
579
|
+
return await asyncio.gather(*tasks, return_exceptions=False)
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
class PipelineStrategy(ExecutionStrategy):
|
|
583
|
+
"""Execute actions in pipeline stages.
|
|
584
|
+
|
|
585
|
+
Actions are grouped into stages. Within a stage, actions run concurrently.
|
|
586
|
+
Stages execute sequentially.
|
|
587
|
+
"""
|
|
588
|
+
|
|
589
|
+
def __init__(self, stages: list[list[int]]) -> None:
|
|
590
|
+
"""Initialize pipeline.
|
|
591
|
+
|
|
592
|
+
Args:
|
|
593
|
+
stages: List of action index groups.
|
|
594
|
+
e.g., [[0, 1], [2], [3, 4]] = 3 stages
|
|
595
|
+
"""
|
|
596
|
+
self.stages = stages
|
|
597
|
+
|
|
598
|
+
async def execute(
|
|
599
|
+
self,
|
|
600
|
+
actions: list[AsyncBaseAction[Any]],
|
|
601
|
+
checkpoint_result: "CheckpointResult",
|
|
602
|
+
context: AsyncExecutionContext | None = None,
|
|
603
|
+
) -> list[ActionResult]:
|
|
604
|
+
all_results: list[ActionResult | None] = [None] * len(actions)
|
|
605
|
+
|
|
606
|
+
for stage_indices in self.stages:
|
|
607
|
+
stage_actions = [
|
|
608
|
+
(i, actions[i]) for i in stage_indices if i < len(actions)
|
|
609
|
+
]
|
|
610
|
+
|
|
611
|
+
tasks = [
|
|
612
|
+
action.execute_async(checkpoint_result)
|
|
613
|
+
for _, action in stage_actions
|
|
614
|
+
]
|
|
615
|
+
stage_results = await asyncio.gather(*tasks)
|
|
616
|
+
|
|
617
|
+
for (idx, _), result in zip(stage_actions, stage_results):
|
|
618
|
+
all_results[idx] = result
|
|
619
|
+
|
|
620
|
+
return [r for r in all_results if r is not None]
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
# =============================================================================
|
|
624
|
+
# Async Action Decorators
|
|
625
|
+
# =============================================================================
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
def with_retry(
|
|
629
|
+
max_retries: int = 3,
|
|
630
|
+
delay: float = 1.0,
|
|
631
|
+
backoff: float = 2.0,
|
|
632
|
+
exceptions: tuple[type[Exception], ...] = (Exception,),
|
|
633
|
+
) -> Callable[
|
|
634
|
+
[Callable[..., Awaitable[T]]],
|
|
635
|
+
Callable[..., Awaitable[T]],
|
|
636
|
+
]:
|
|
637
|
+
"""Decorator for async retry logic.
|
|
638
|
+
|
|
639
|
+
Args:
|
|
640
|
+
max_retries: Maximum retry attempts.
|
|
641
|
+
delay: Initial delay between retries.
|
|
642
|
+
backoff: Multiplier for delay after each retry.
|
|
643
|
+
exceptions: Exception types to retry on.
|
|
644
|
+
"""
|
|
645
|
+
|
|
646
|
+
def decorator(
|
|
647
|
+
func: Callable[..., Awaitable[T]],
|
|
648
|
+
) -> Callable[..., Awaitable[T]]:
|
|
649
|
+
@functools.wraps(func)
|
|
650
|
+
async def wrapper(*args: Any, **kwargs: Any) -> T:
|
|
651
|
+
current_delay = delay
|
|
652
|
+
last_exception: Exception | None = None
|
|
653
|
+
|
|
654
|
+
for attempt in range(max_retries + 1):
|
|
655
|
+
try:
|
|
656
|
+
return await func(*args, **kwargs)
|
|
657
|
+
except exceptions as e:
|
|
658
|
+
last_exception = e
|
|
659
|
+
if attempt < max_retries:
|
|
660
|
+
await asyncio.sleep(current_delay)
|
|
661
|
+
current_delay *= backoff
|
|
662
|
+
|
|
663
|
+
raise last_exception or Exception("Retry failed")
|
|
664
|
+
|
|
665
|
+
return wrapper
|
|
666
|
+
|
|
667
|
+
return decorator
|
|
668
|
+
|
|
669
|
+
|
|
670
|
+
def with_timeout(
|
|
671
|
+
seconds: float,
|
|
672
|
+
) -> Callable[
|
|
673
|
+
[Callable[..., Awaitable[T]]],
|
|
674
|
+
Callable[..., Awaitable[T]],
|
|
675
|
+
]:
|
|
676
|
+
"""Decorator for async timeout."""
|
|
677
|
+
|
|
678
|
+
def decorator(
|
|
679
|
+
func: Callable[..., Awaitable[T]],
|
|
680
|
+
) -> Callable[..., Awaitable[T]]:
|
|
681
|
+
@functools.wraps(func)
|
|
682
|
+
async def wrapper(*args: Any, **kwargs: Any) -> T:
|
|
683
|
+
return await asyncio.wait_for(func(*args, **kwargs), timeout=seconds)
|
|
684
|
+
|
|
685
|
+
return wrapper
|
|
686
|
+
|
|
687
|
+
return decorator
|
|
688
|
+
|
|
689
|
+
|
|
690
|
+
def with_semaphore(
|
|
691
|
+
semaphore: asyncio.Semaphore,
|
|
692
|
+
) -> Callable[
|
|
693
|
+
[Callable[..., Awaitable[T]]],
|
|
694
|
+
Callable[..., Awaitable[T]],
|
|
695
|
+
]:
|
|
696
|
+
"""Decorator to limit concurrency."""
|
|
697
|
+
|
|
698
|
+
def decorator(
|
|
699
|
+
func: Callable[..., Awaitable[T]],
|
|
700
|
+
) -> Callable[..., Awaitable[T]]:
|
|
701
|
+
@functools.wraps(func)
|
|
702
|
+
async def wrapper(*args: Any, **kwargs: Any) -> T:
|
|
703
|
+
async with semaphore:
|
|
704
|
+
return await func(*args, **kwargs)
|
|
705
|
+
|
|
706
|
+
return wrapper
|
|
707
|
+
|
|
708
|
+
return decorator
|