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,780 @@
|
|
|
1
|
+
"""Advanced Compensation Strategies.
|
|
2
|
+
|
|
3
|
+
This module provides advanced compensation strategies for enterprise
|
|
4
|
+
saga patterns, including semantic compensation, pivot transactions,
|
|
5
|
+
and countermeasure strategies.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
from abc import ABC, abstractmethod
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
from enum import Enum, auto
|
|
15
|
+
from typing import TYPE_CHECKING, Any, Callable
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from truthound.checkpoint.actions.base import ActionResult, BaseAction
|
|
19
|
+
from truthound.checkpoint.checkpoint import CheckpointResult
|
|
20
|
+
from truthound.checkpoint.transaction.base import CompensationResult, TransactionContext
|
|
21
|
+
from truthound.checkpoint.transaction.saga.definition import SagaDefinition, SagaStepDefinition
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class CompensationPolicy(str, Enum):
|
|
28
|
+
"""Compensation policy types."""
|
|
29
|
+
|
|
30
|
+
# Standard backward compensation (default Saga pattern)
|
|
31
|
+
BACKWARD = "backward"
|
|
32
|
+
|
|
33
|
+
# Forward recovery - try to complete remaining steps
|
|
34
|
+
FORWARD = "forward"
|
|
35
|
+
|
|
36
|
+
# Semantic compensation - compensate with semantically equivalent actions
|
|
37
|
+
SEMANTIC = "semantic"
|
|
38
|
+
|
|
39
|
+
# Pivot transaction - commit point after which no compensation
|
|
40
|
+
PIVOT = "pivot"
|
|
41
|
+
|
|
42
|
+
# Countermeasure - corrective actions instead of undo
|
|
43
|
+
COUNTERMEASURE = "countermeasure"
|
|
44
|
+
|
|
45
|
+
# Parallel compensation - compensate multiple steps in parallel
|
|
46
|
+
PARALLEL = "parallel"
|
|
47
|
+
|
|
48
|
+
# Selective compensation - only compensate specific steps
|
|
49
|
+
SELECTIVE = "selective"
|
|
50
|
+
|
|
51
|
+
# Best effort - try to compensate, continue on failure
|
|
52
|
+
BEST_EFFORT = "best_effort"
|
|
53
|
+
|
|
54
|
+
def __str__(self) -> str:
|
|
55
|
+
return self.value
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class CompensationPriority(str, Enum):
|
|
59
|
+
"""Priority for compensation execution."""
|
|
60
|
+
|
|
61
|
+
CRITICAL = "critical" # Must compensate, fail saga if not possible
|
|
62
|
+
HIGH = "high" # Highly important, retry multiple times
|
|
63
|
+
NORMAL = "normal" # Standard compensation
|
|
64
|
+
LOW = "low" # Can skip if necessary
|
|
65
|
+
OPTIONAL = "optional" # Best effort, ignore failures
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass
|
|
69
|
+
class CompensationPlanStep:
|
|
70
|
+
"""A single step in a compensation plan.
|
|
71
|
+
|
|
72
|
+
Attributes:
|
|
73
|
+
step_id: ID of the step to compensate.
|
|
74
|
+
step_name: Name of the step.
|
|
75
|
+
priority: Compensation priority.
|
|
76
|
+
strategy: Strategy for this step.
|
|
77
|
+
dependencies: Steps that must be compensated first.
|
|
78
|
+
action: Compensation action or function.
|
|
79
|
+
estimated_duration_ms: Estimated compensation duration.
|
|
80
|
+
can_parallel: Whether can run in parallel with others.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
step_id: str
|
|
84
|
+
step_name: str
|
|
85
|
+
priority: CompensationPriority = CompensationPriority.NORMAL
|
|
86
|
+
strategy: str = "backward"
|
|
87
|
+
dependencies: list[str] = field(default_factory=list)
|
|
88
|
+
action: "BaseAction[Any] | Callable[..., Any]" | None = None
|
|
89
|
+
estimated_duration_ms: float = 0.0
|
|
90
|
+
can_parallel: bool = False
|
|
91
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass
|
|
95
|
+
class CompensationPlan:
|
|
96
|
+
"""Complete compensation plan for a saga.
|
|
97
|
+
|
|
98
|
+
Attributes:
|
|
99
|
+
saga_id: ID of the saga.
|
|
100
|
+
policy: Overall compensation policy.
|
|
101
|
+
steps: Ordered list of compensation steps.
|
|
102
|
+
created_at: When the plan was created.
|
|
103
|
+
pivot_step_id: ID of pivot step (if using pivot strategy).
|
|
104
|
+
metadata: Additional plan metadata.
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
saga_id: str
|
|
108
|
+
policy: CompensationPolicy
|
|
109
|
+
steps: list[CompensationPlanStep] = field(default_factory=list)
|
|
110
|
+
created_at: datetime = field(default_factory=datetime.now)
|
|
111
|
+
pivot_step_id: str | None = None
|
|
112
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
113
|
+
|
|
114
|
+
def add_step(self, step: CompensationPlanStep) -> "CompensationPlan":
|
|
115
|
+
"""Add a step to the plan.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
step: Compensation step to add.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Self for chaining.
|
|
122
|
+
"""
|
|
123
|
+
self.steps.append(step)
|
|
124
|
+
return self
|
|
125
|
+
|
|
126
|
+
def get_execution_order(self) -> list[CompensationPlanStep]:
|
|
127
|
+
"""Get steps in execution order based on dependencies.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
Steps in order of execution.
|
|
131
|
+
"""
|
|
132
|
+
# Simple topological sort
|
|
133
|
+
result = []
|
|
134
|
+
remaining = list(self.steps)
|
|
135
|
+
completed = set()
|
|
136
|
+
|
|
137
|
+
while remaining:
|
|
138
|
+
for step in list(remaining):
|
|
139
|
+
deps_satisfied = all(
|
|
140
|
+
dep in completed for dep in step.dependencies
|
|
141
|
+
)
|
|
142
|
+
if deps_satisfied:
|
|
143
|
+
result.append(step)
|
|
144
|
+
completed.add(step.step_id)
|
|
145
|
+
remaining.remove(step)
|
|
146
|
+
break
|
|
147
|
+
else:
|
|
148
|
+
# No progress - might be cycle or missing dep
|
|
149
|
+
result.extend(remaining)
|
|
150
|
+
break
|
|
151
|
+
|
|
152
|
+
return result
|
|
153
|
+
|
|
154
|
+
def get_parallel_groups(self) -> list[list[CompensationPlanStep]]:
|
|
155
|
+
"""Get steps grouped for parallel execution.
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
List of step groups that can run in parallel.
|
|
159
|
+
"""
|
|
160
|
+
groups = []
|
|
161
|
+
current_group = []
|
|
162
|
+
completed = set()
|
|
163
|
+
|
|
164
|
+
for step in self.get_execution_order():
|
|
165
|
+
# Check if all dependencies are complete
|
|
166
|
+
deps_complete = all(d in completed for d in step.dependencies)
|
|
167
|
+
|
|
168
|
+
if step.can_parallel and deps_complete:
|
|
169
|
+
current_group.append(step)
|
|
170
|
+
else:
|
|
171
|
+
if current_group:
|
|
172
|
+
groups.append(current_group)
|
|
173
|
+
completed.update(s.step_id for s in current_group)
|
|
174
|
+
current_group = []
|
|
175
|
+
groups.append([step])
|
|
176
|
+
completed.add(step.step_id)
|
|
177
|
+
|
|
178
|
+
if current_group:
|
|
179
|
+
groups.append(current_group)
|
|
180
|
+
|
|
181
|
+
return groups
|
|
182
|
+
|
|
183
|
+
def to_dict(self) -> dict[str, Any]:
|
|
184
|
+
"""Convert to dictionary for serialization."""
|
|
185
|
+
return {
|
|
186
|
+
"saga_id": self.saga_id,
|
|
187
|
+
"policy": self.policy.value,
|
|
188
|
+
"steps": [
|
|
189
|
+
{
|
|
190
|
+
"step_id": s.step_id,
|
|
191
|
+
"step_name": s.step_name,
|
|
192
|
+
"priority": s.priority.value,
|
|
193
|
+
"strategy": s.strategy,
|
|
194
|
+
"dependencies": s.dependencies,
|
|
195
|
+
"can_parallel": s.can_parallel,
|
|
196
|
+
}
|
|
197
|
+
for s in self.steps
|
|
198
|
+
],
|
|
199
|
+
"pivot_step_id": self.pivot_step_id,
|
|
200
|
+
"created_at": self.created_at.isoformat(),
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class CompensationPlanner:
|
|
205
|
+
"""Generates compensation plans for sagas.
|
|
206
|
+
|
|
207
|
+
This class analyzes a saga definition and generates an appropriate
|
|
208
|
+
compensation plan based on the configured policy and step characteristics.
|
|
209
|
+
"""
|
|
210
|
+
|
|
211
|
+
def __init__(self, policy: CompensationPolicy = CompensationPolicy.BACKWARD) -> None:
|
|
212
|
+
"""Initialize the planner.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
policy: Default compensation policy.
|
|
216
|
+
"""
|
|
217
|
+
self._default_policy = policy
|
|
218
|
+
|
|
219
|
+
def create_plan(
|
|
220
|
+
self,
|
|
221
|
+
saga: "SagaDefinition",
|
|
222
|
+
completed_steps: list[str],
|
|
223
|
+
failed_step: str | None = None,
|
|
224
|
+
policy: CompensationPolicy | None = None,
|
|
225
|
+
) -> CompensationPlan:
|
|
226
|
+
"""Create a compensation plan for the saga.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
saga: Saga definition.
|
|
230
|
+
completed_steps: List of completed step IDs.
|
|
231
|
+
failed_step: ID of the failed step (if any).
|
|
232
|
+
policy: Override policy for this plan.
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
Compensation plan.
|
|
236
|
+
"""
|
|
237
|
+
effective_policy = policy or self._default_policy
|
|
238
|
+
|
|
239
|
+
plan = CompensationPlan(
|
|
240
|
+
saga_id=saga.saga_id,
|
|
241
|
+
policy=effective_policy,
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
# Find pivot step
|
|
245
|
+
pivot_step = saga.get_pivot_step()
|
|
246
|
+
if pivot_step:
|
|
247
|
+
plan.pivot_step_id = pivot_step.step_id
|
|
248
|
+
|
|
249
|
+
# Get steps to compensate
|
|
250
|
+
steps_to_compensate = self._determine_compensatable_steps(
|
|
251
|
+
saga, completed_steps, failed_step, effective_policy, pivot_step
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
# Build plan based on policy
|
|
255
|
+
if effective_policy == CompensationPolicy.BACKWARD:
|
|
256
|
+
self._build_backward_plan(plan, saga, steps_to_compensate)
|
|
257
|
+
elif effective_policy == CompensationPolicy.FORWARD:
|
|
258
|
+
self._build_forward_plan(plan, saga, completed_steps, failed_step)
|
|
259
|
+
elif effective_policy == CompensationPolicy.SEMANTIC:
|
|
260
|
+
self._build_semantic_plan(plan, saga, steps_to_compensate)
|
|
261
|
+
elif effective_policy == CompensationPolicy.PIVOT:
|
|
262
|
+
self._build_pivot_plan(plan, saga, completed_steps, pivot_step)
|
|
263
|
+
elif effective_policy == CompensationPolicy.COUNTERMEASURE:
|
|
264
|
+
self._build_countermeasure_plan(plan, saga, steps_to_compensate)
|
|
265
|
+
elif effective_policy == CompensationPolicy.PARALLEL:
|
|
266
|
+
self._build_parallel_plan(plan, saga, steps_to_compensate)
|
|
267
|
+
elif effective_policy == CompensationPolicy.SELECTIVE:
|
|
268
|
+
self._build_selective_plan(plan, saga, steps_to_compensate, failed_step)
|
|
269
|
+
elif effective_policy == CompensationPolicy.BEST_EFFORT:
|
|
270
|
+
self._build_best_effort_plan(plan, saga, steps_to_compensate)
|
|
271
|
+
|
|
272
|
+
return plan
|
|
273
|
+
|
|
274
|
+
def _determine_compensatable_steps(
|
|
275
|
+
self,
|
|
276
|
+
saga: "SagaDefinition",
|
|
277
|
+
completed_steps: list[str],
|
|
278
|
+
failed_step: str | None,
|
|
279
|
+
policy: CompensationPolicy,
|
|
280
|
+
pivot_step: "SagaStepDefinition | None",
|
|
281
|
+
) -> list["SagaStepDefinition"]:
|
|
282
|
+
"""Determine which steps need compensation."""
|
|
283
|
+
compensatable = []
|
|
284
|
+
|
|
285
|
+
# Check if we passed the pivot point
|
|
286
|
+
if pivot_step and pivot_step.step_id in completed_steps:
|
|
287
|
+
# Past pivot - no compensation allowed
|
|
288
|
+
logger.info(
|
|
289
|
+
f"Saga passed pivot point at {pivot_step.step_id}, "
|
|
290
|
+
"compensation not possible"
|
|
291
|
+
)
|
|
292
|
+
return []
|
|
293
|
+
|
|
294
|
+
for step in saga.steps:
|
|
295
|
+
# Only compensate completed steps
|
|
296
|
+
if step.step_id not in completed_steps:
|
|
297
|
+
continue
|
|
298
|
+
|
|
299
|
+
# Skip the failed step itself (wasn't completed)
|
|
300
|
+
if step.step_id == failed_step:
|
|
301
|
+
continue
|
|
302
|
+
|
|
303
|
+
# Must have compensation defined
|
|
304
|
+
if not step.has_compensation():
|
|
305
|
+
continue
|
|
306
|
+
|
|
307
|
+
compensatable.append(step)
|
|
308
|
+
|
|
309
|
+
return compensatable
|
|
310
|
+
|
|
311
|
+
def _build_backward_plan(
|
|
312
|
+
self,
|
|
313
|
+
plan: CompensationPlan,
|
|
314
|
+
saga: "SagaDefinition",
|
|
315
|
+
steps: list["SagaStepDefinition"],
|
|
316
|
+
) -> None:
|
|
317
|
+
"""Build backward compensation plan (reverse order)."""
|
|
318
|
+
# Sort by execution order, then reverse
|
|
319
|
+
step_order = {s.step_id: i for i, s in enumerate(saga.steps)}
|
|
320
|
+
sorted_steps = sorted(steps, key=lambda s: step_order.get(s.step_id, 0), reverse=True)
|
|
321
|
+
|
|
322
|
+
prev_step_id = None
|
|
323
|
+
for step in sorted_steps:
|
|
324
|
+
plan_step = CompensationPlanStep(
|
|
325
|
+
step_id=step.step_id,
|
|
326
|
+
step_name=step.name,
|
|
327
|
+
priority=CompensationPriority.NORMAL,
|
|
328
|
+
strategy="backward",
|
|
329
|
+
dependencies=[prev_step_id] if prev_step_id else [],
|
|
330
|
+
action=step.compensation_action or step.compensation_fn,
|
|
331
|
+
)
|
|
332
|
+
plan.add_step(plan_step)
|
|
333
|
+
prev_step_id = step.step_id
|
|
334
|
+
|
|
335
|
+
def _build_forward_plan(
|
|
336
|
+
self,
|
|
337
|
+
plan: CompensationPlan,
|
|
338
|
+
saga: "SagaDefinition",
|
|
339
|
+
completed_steps: list[str],
|
|
340
|
+
failed_step: str | None,
|
|
341
|
+
) -> None:
|
|
342
|
+
"""Build forward recovery plan (try to complete)."""
|
|
343
|
+
# Get remaining steps after failure
|
|
344
|
+
execution_order = saga.get_execution_order()
|
|
345
|
+
failed_index = next(
|
|
346
|
+
(i for i, s in enumerate(execution_order) if s.step_id == failed_step),
|
|
347
|
+
len(execution_order),
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
for step in execution_order[failed_index:]:
|
|
351
|
+
if step.step_id in completed_steps:
|
|
352
|
+
continue
|
|
353
|
+
|
|
354
|
+
# Add as forward recovery step
|
|
355
|
+
plan_step = CompensationPlanStep(
|
|
356
|
+
step_id=step.step_id,
|
|
357
|
+
step_name=step.name,
|
|
358
|
+
priority=CompensationPriority.NORMAL,
|
|
359
|
+
strategy="forward",
|
|
360
|
+
action=step.action,
|
|
361
|
+
metadata={"recovery": True},
|
|
362
|
+
)
|
|
363
|
+
plan.add_step(plan_step)
|
|
364
|
+
|
|
365
|
+
def _build_semantic_plan(
|
|
366
|
+
self,
|
|
367
|
+
plan: CompensationPlan,
|
|
368
|
+
saga: "SagaDefinition",
|
|
369
|
+
steps: list["SagaStepDefinition"],
|
|
370
|
+
) -> None:
|
|
371
|
+
"""Build semantic compensation plan."""
|
|
372
|
+
for step in reversed(steps):
|
|
373
|
+
priority = (
|
|
374
|
+
CompensationPriority.HIGH if step.required else CompensationPriority.NORMAL
|
|
375
|
+
)
|
|
376
|
+
strategy = "semantic" if step.semantic_undo else "backward"
|
|
377
|
+
|
|
378
|
+
plan_step = CompensationPlanStep(
|
|
379
|
+
step_id=step.step_id,
|
|
380
|
+
step_name=step.name,
|
|
381
|
+
priority=priority,
|
|
382
|
+
strategy=strategy,
|
|
383
|
+
action=step.compensation_action or step.compensation_fn,
|
|
384
|
+
metadata={"semantic_undo": step.semantic_undo},
|
|
385
|
+
)
|
|
386
|
+
plan.add_step(plan_step)
|
|
387
|
+
|
|
388
|
+
def _build_pivot_plan(
|
|
389
|
+
self,
|
|
390
|
+
plan: CompensationPlan,
|
|
391
|
+
saga: "SagaDefinition",
|
|
392
|
+
completed_steps: list[str],
|
|
393
|
+
pivot_step: "SagaStepDefinition | None",
|
|
394
|
+
) -> None:
|
|
395
|
+
"""Build compensation plan with pivot transaction support."""
|
|
396
|
+
if pivot_step and pivot_step.step_id in completed_steps:
|
|
397
|
+
# Past pivot - use forward recovery
|
|
398
|
+
plan.policy = CompensationPolicy.FORWARD
|
|
399
|
+
plan.metadata["pivot_passed"] = True
|
|
400
|
+
logger.info("Pivot passed, switching to forward recovery")
|
|
401
|
+
return
|
|
402
|
+
|
|
403
|
+
# Normal backward compensation up to pivot
|
|
404
|
+
compensatable = []
|
|
405
|
+
for step in saga.steps:
|
|
406
|
+
if step.step_id not in completed_steps:
|
|
407
|
+
continue
|
|
408
|
+
if step.is_pivot:
|
|
409
|
+
break
|
|
410
|
+
if step.has_compensation():
|
|
411
|
+
compensatable.append(step)
|
|
412
|
+
|
|
413
|
+
self._build_backward_plan(plan, saga, compensatable)
|
|
414
|
+
|
|
415
|
+
def _build_countermeasure_plan(
|
|
416
|
+
self,
|
|
417
|
+
plan: CompensationPlan,
|
|
418
|
+
saga: "SagaDefinition",
|
|
419
|
+
steps: list["SagaStepDefinition"],
|
|
420
|
+
) -> None:
|
|
421
|
+
"""Build countermeasure compensation plan."""
|
|
422
|
+
for step in saga.steps:
|
|
423
|
+
if not step.is_countermeasure:
|
|
424
|
+
continue
|
|
425
|
+
|
|
426
|
+
plan_step = CompensationPlanStep(
|
|
427
|
+
step_id=step.step_id,
|
|
428
|
+
step_name=step.name,
|
|
429
|
+
priority=CompensationPriority.HIGH,
|
|
430
|
+
strategy="countermeasure",
|
|
431
|
+
action=step.action,
|
|
432
|
+
metadata={"is_countermeasure": True},
|
|
433
|
+
)
|
|
434
|
+
plan.add_step(plan_step)
|
|
435
|
+
|
|
436
|
+
def _build_parallel_plan(
|
|
437
|
+
self,
|
|
438
|
+
plan: CompensationPlan,
|
|
439
|
+
saga: "SagaDefinition",
|
|
440
|
+
steps: list["SagaStepDefinition"],
|
|
441
|
+
) -> None:
|
|
442
|
+
"""Build parallel compensation plan."""
|
|
443
|
+
for step in steps:
|
|
444
|
+
# All steps marked as parallel-capable
|
|
445
|
+
plan_step = CompensationPlanStep(
|
|
446
|
+
step_id=step.step_id,
|
|
447
|
+
step_name=step.name,
|
|
448
|
+
priority=CompensationPriority.NORMAL,
|
|
449
|
+
strategy="parallel",
|
|
450
|
+
action=step.compensation_action or step.compensation_fn,
|
|
451
|
+
can_parallel=True,
|
|
452
|
+
# Dependencies only from step definition
|
|
453
|
+
dependencies=[d.step_id for d in step.dependencies],
|
|
454
|
+
)
|
|
455
|
+
plan.add_step(plan_step)
|
|
456
|
+
|
|
457
|
+
def _build_selective_plan(
|
|
458
|
+
self,
|
|
459
|
+
plan: CompensationPlan,
|
|
460
|
+
saga: "SagaDefinition",
|
|
461
|
+
steps: list["SagaStepDefinition"],
|
|
462
|
+
failed_step: str | None,
|
|
463
|
+
) -> None:
|
|
464
|
+
"""Build selective compensation plan."""
|
|
465
|
+
# Only compensate steps related to the failed step
|
|
466
|
+
if not failed_step:
|
|
467
|
+
return
|
|
468
|
+
|
|
469
|
+
failed_step_def = saga.get_step(failed_step)
|
|
470
|
+
if not failed_step_def:
|
|
471
|
+
return
|
|
472
|
+
|
|
473
|
+
# Find dependent steps
|
|
474
|
+
dependent_ids = set()
|
|
475
|
+
for step in saga.steps:
|
|
476
|
+
for dep in step.dependencies:
|
|
477
|
+
if dep.step_id == failed_step:
|
|
478
|
+
dependent_ids.add(step.step_id)
|
|
479
|
+
|
|
480
|
+
# Compensate only related steps
|
|
481
|
+
for step in reversed(steps):
|
|
482
|
+
is_related = (
|
|
483
|
+
step.step_id in dependent_ids
|
|
484
|
+
or any(d.step_id == failed_step for d in step.dependencies)
|
|
485
|
+
)
|
|
486
|
+
|
|
487
|
+
if is_related and step.has_compensation():
|
|
488
|
+
plan_step = CompensationPlanStep(
|
|
489
|
+
step_id=step.step_id,
|
|
490
|
+
step_name=step.name,
|
|
491
|
+
priority=CompensationPriority.NORMAL,
|
|
492
|
+
strategy="selective",
|
|
493
|
+
action=step.compensation_action or step.compensation_fn,
|
|
494
|
+
)
|
|
495
|
+
plan.add_step(plan_step)
|
|
496
|
+
|
|
497
|
+
def _build_best_effort_plan(
|
|
498
|
+
self,
|
|
499
|
+
plan: CompensationPlan,
|
|
500
|
+
saga: "SagaDefinition",
|
|
501
|
+
steps: list["SagaStepDefinition"],
|
|
502
|
+
) -> None:
|
|
503
|
+
"""Build best-effort compensation plan."""
|
|
504
|
+
for step in reversed(steps):
|
|
505
|
+
plan_step = CompensationPlanStep(
|
|
506
|
+
step_id=step.step_id,
|
|
507
|
+
step_name=step.name,
|
|
508
|
+
priority=CompensationPriority.OPTIONAL,
|
|
509
|
+
strategy="best_effort",
|
|
510
|
+
action=step.compensation_action or step.compensation_fn,
|
|
511
|
+
can_parallel=True, # All can run in parallel
|
|
512
|
+
)
|
|
513
|
+
plan.add_step(plan_step)
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
# =============================================================================
|
|
517
|
+
# Strategy Implementations
|
|
518
|
+
# =============================================================================
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
class CompensationStrategyBase(ABC):
|
|
522
|
+
"""Base class for compensation strategy implementations."""
|
|
523
|
+
|
|
524
|
+
@abstractmethod
|
|
525
|
+
def execute(
|
|
526
|
+
self,
|
|
527
|
+
plan: CompensationPlan,
|
|
528
|
+
checkpoint_result: "CheckpointResult",
|
|
529
|
+
context: "TransactionContext",
|
|
530
|
+
) -> list["CompensationResult"]:
|
|
531
|
+
"""Execute the compensation plan.
|
|
532
|
+
|
|
533
|
+
Args:
|
|
534
|
+
plan: Compensation plan to execute.
|
|
535
|
+
checkpoint_result: Original checkpoint result.
|
|
536
|
+
context: Transaction context.
|
|
537
|
+
|
|
538
|
+
Returns:
|
|
539
|
+
List of compensation results.
|
|
540
|
+
"""
|
|
541
|
+
pass
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
class SemanticCompensation(CompensationStrategyBase):
|
|
545
|
+
"""Semantic compensation strategy.
|
|
546
|
+
|
|
547
|
+
Semantic compensation allows for compensation actions that don't
|
|
548
|
+
strictly undo the original action but provide a semantically
|
|
549
|
+
equivalent outcome.
|
|
550
|
+
|
|
551
|
+
Example:
|
|
552
|
+
- Original: Reserve 10 seats
|
|
553
|
+
- Strict undo: Release 10 seats
|
|
554
|
+
- Semantic: Add 10 seats to waiting list
|
|
555
|
+
"""
|
|
556
|
+
|
|
557
|
+
def __init__(
|
|
558
|
+
self,
|
|
559
|
+
fallback_to_strict: bool = True,
|
|
560
|
+
require_acknowledgment: bool = False,
|
|
561
|
+
) -> None:
|
|
562
|
+
"""Initialize semantic compensation.
|
|
563
|
+
|
|
564
|
+
Args:
|
|
565
|
+
fallback_to_strict: Fall back to strict undo if semantic fails.
|
|
566
|
+
require_acknowledgment: Require external acknowledgment.
|
|
567
|
+
"""
|
|
568
|
+
self._fallback_to_strict = fallback_to_strict
|
|
569
|
+
self._require_acknowledgment = require_acknowledgment
|
|
570
|
+
|
|
571
|
+
def execute(
|
|
572
|
+
self,
|
|
573
|
+
plan: CompensationPlan,
|
|
574
|
+
checkpoint_result: "CheckpointResult",
|
|
575
|
+
context: "TransactionContext",
|
|
576
|
+
) -> list["CompensationResult"]:
|
|
577
|
+
"""Execute semantic compensation."""
|
|
578
|
+
from truthound.checkpoint.transaction.base import CompensationResult
|
|
579
|
+
|
|
580
|
+
results = []
|
|
581
|
+
|
|
582
|
+
for step in plan.get_execution_order():
|
|
583
|
+
is_semantic = step.metadata.get("semantic_undo", False)
|
|
584
|
+
|
|
585
|
+
try:
|
|
586
|
+
if step.action:
|
|
587
|
+
if callable(step.action):
|
|
588
|
+
result = step.action(checkpoint_result, None, context)
|
|
589
|
+
else:
|
|
590
|
+
result = step.action.execute(checkpoint_result)
|
|
591
|
+
|
|
592
|
+
comp_result = CompensationResult(
|
|
593
|
+
action_name=step.step_name,
|
|
594
|
+
success=True,
|
|
595
|
+
details={"semantic": is_semantic},
|
|
596
|
+
)
|
|
597
|
+
else:
|
|
598
|
+
comp_result = CompensationResult(
|
|
599
|
+
action_name=step.step_name,
|
|
600
|
+
success=False,
|
|
601
|
+
error="No compensation action defined",
|
|
602
|
+
)
|
|
603
|
+
|
|
604
|
+
results.append(comp_result)
|
|
605
|
+
|
|
606
|
+
except Exception as e:
|
|
607
|
+
if self._fallback_to_strict and is_semantic:
|
|
608
|
+
logger.info(f"Semantic compensation failed, trying strict: {e}")
|
|
609
|
+
# Could try strict undo here
|
|
610
|
+
comp_result = CompensationResult(
|
|
611
|
+
action_name=step.step_name,
|
|
612
|
+
success=False,
|
|
613
|
+
error=f"Semantic compensation failed: {e}",
|
|
614
|
+
)
|
|
615
|
+
else:
|
|
616
|
+
comp_result = CompensationResult(
|
|
617
|
+
action_name=step.step_name,
|
|
618
|
+
success=False,
|
|
619
|
+
error=str(e),
|
|
620
|
+
)
|
|
621
|
+
results.append(comp_result)
|
|
622
|
+
|
|
623
|
+
return results
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
class PivotTransaction(CompensationStrategyBase):
|
|
627
|
+
"""Pivot transaction strategy.
|
|
628
|
+
|
|
629
|
+
A pivot transaction is a step in a saga that represents a point
|
|
630
|
+
of no return. Once the pivot succeeds, the saga cannot be
|
|
631
|
+
compensated and must proceed forward.
|
|
632
|
+
|
|
633
|
+
Example:
|
|
634
|
+
- Step 1: Reserve seats (compensatable)
|
|
635
|
+
- Step 2: Charge card (compensatable)
|
|
636
|
+
- Step 3: Issue ticket (PIVOT - irreversible)
|
|
637
|
+
- Step 4: Send confirmation (must complete)
|
|
638
|
+
|
|
639
|
+
If step 4 fails, compensation stops at step 3.
|
|
640
|
+
"""
|
|
641
|
+
|
|
642
|
+
def __init__(self, pivot_step_id: str) -> None:
|
|
643
|
+
"""Initialize pivot transaction.
|
|
644
|
+
|
|
645
|
+
Args:
|
|
646
|
+
pivot_step_id: ID of the pivot step.
|
|
647
|
+
"""
|
|
648
|
+
self._pivot_step_id = pivot_step_id
|
|
649
|
+
|
|
650
|
+
def execute(
|
|
651
|
+
self,
|
|
652
|
+
plan: CompensationPlan,
|
|
653
|
+
checkpoint_result: "CheckpointResult",
|
|
654
|
+
context: "TransactionContext",
|
|
655
|
+
) -> list["CompensationResult"]:
|
|
656
|
+
"""Execute pivot-aware compensation."""
|
|
657
|
+
from truthound.checkpoint.transaction.base import CompensationResult
|
|
658
|
+
|
|
659
|
+
results = []
|
|
660
|
+
|
|
661
|
+
for step in plan.get_execution_order():
|
|
662
|
+
# Stop at pivot
|
|
663
|
+
if step.step_id == self._pivot_step_id:
|
|
664
|
+
logger.info(f"Reached pivot step {step.step_id}, stopping compensation")
|
|
665
|
+
break
|
|
666
|
+
|
|
667
|
+
if step.action:
|
|
668
|
+
try:
|
|
669
|
+
if callable(step.action):
|
|
670
|
+
step.action(checkpoint_result, None, context)
|
|
671
|
+
else:
|
|
672
|
+
step.action.execute(checkpoint_result)
|
|
673
|
+
|
|
674
|
+
results.append(
|
|
675
|
+
CompensationResult(
|
|
676
|
+
action_name=step.step_name,
|
|
677
|
+
success=True,
|
|
678
|
+
)
|
|
679
|
+
)
|
|
680
|
+
except Exception as e:
|
|
681
|
+
results.append(
|
|
682
|
+
CompensationResult(
|
|
683
|
+
action_name=step.step_name,
|
|
684
|
+
success=False,
|
|
685
|
+
error=str(e),
|
|
686
|
+
)
|
|
687
|
+
)
|
|
688
|
+
|
|
689
|
+
return results
|
|
690
|
+
|
|
691
|
+
|
|
692
|
+
class CountermeasureStrategy(CompensationStrategyBase):
|
|
693
|
+
"""Countermeasure compensation strategy.
|
|
694
|
+
|
|
695
|
+
Instead of undoing completed actions, countermeasures apply
|
|
696
|
+
corrective actions to handle the failure state.
|
|
697
|
+
|
|
698
|
+
Example:
|
|
699
|
+
- Original failure: Payment declined after inventory reserved
|
|
700
|
+
- Undo: Release inventory (traditional)
|
|
701
|
+
- Countermeasure: Send payment retry notification,
|
|
702
|
+
hold inventory for 24 hours
|
|
703
|
+
"""
|
|
704
|
+
|
|
705
|
+
def __init__(
|
|
706
|
+
self,
|
|
707
|
+
countermeasures: dict[str, Callable[..., Any]] | None = None,
|
|
708
|
+
apply_in_order: bool = True,
|
|
709
|
+
) -> None:
|
|
710
|
+
"""Initialize countermeasure strategy.
|
|
711
|
+
|
|
712
|
+
Args:
|
|
713
|
+
countermeasures: Map of step ID to countermeasure function.
|
|
714
|
+
apply_in_order: Apply countermeasures in saga order.
|
|
715
|
+
"""
|
|
716
|
+
self._countermeasures = countermeasures or {}
|
|
717
|
+
self._apply_in_order = apply_in_order
|
|
718
|
+
|
|
719
|
+
def add_countermeasure(
|
|
720
|
+
self,
|
|
721
|
+
step_id: str,
|
|
722
|
+
action: Callable[..., Any],
|
|
723
|
+
) -> "CountermeasureStrategy":
|
|
724
|
+
"""Add a countermeasure for a step.
|
|
725
|
+
|
|
726
|
+
Args:
|
|
727
|
+
step_id: Step to apply countermeasure for.
|
|
728
|
+
action: Countermeasure action.
|
|
729
|
+
|
|
730
|
+
Returns:
|
|
731
|
+
Self for chaining.
|
|
732
|
+
"""
|
|
733
|
+
self._countermeasures[step_id] = action
|
|
734
|
+
return self
|
|
735
|
+
|
|
736
|
+
def execute(
|
|
737
|
+
self,
|
|
738
|
+
plan: CompensationPlan,
|
|
739
|
+
checkpoint_result: "CheckpointResult",
|
|
740
|
+
context: "TransactionContext",
|
|
741
|
+
) -> list["CompensationResult"]:
|
|
742
|
+
"""Execute countermeasure compensation."""
|
|
743
|
+
from truthound.checkpoint.transaction.base import CompensationResult
|
|
744
|
+
|
|
745
|
+
results = []
|
|
746
|
+
|
|
747
|
+
# Find countermeasure steps
|
|
748
|
+
countermeasure_steps = [
|
|
749
|
+
s for s in plan.steps
|
|
750
|
+
if s.metadata.get("is_countermeasure") or s.step_id in self._countermeasures
|
|
751
|
+
]
|
|
752
|
+
|
|
753
|
+
for step in countermeasure_steps:
|
|
754
|
+
action = self._countermeasures.get(step.step_id, step.action)
|
|
755
|
+
|
|
756
|
+
if action:
|
|
757
|
+
try:
|
|
758
|
+
if callable(action):
|
|
759
|
+
action(checkpoint_result, None, context)
|
|
760
|
+
else:
|
|
761
|
+
action.execute(checkpoint_result)
|
|
762
|
+
|
|
763
|
+
results.append(
|
|
764
|
+
CompensationResult(
|
|
765
|
+
action_name=step.step_name,
|
|
766
|
+
success=True,
|
|
767
|
+
details={"type": "countermeasure"},
|
|
768
|
+
)
|
|
769
|
+
)
|
|
770
|
+
except Exception as e:
|
|
771
|
+
results.append(
|
|
772
|
+
CompensationResult(
|
|
773
|
+
action_name=step.step_name,
|
|
774
|
+
success=False,
|
|
775
|
+
error=str(e),
|
|
776
|
+
details={"type": "countermeasure"},
|
|
777
|
+
)
|
|
778
|
+
)
|
|
779
|
+
|
|
780
|
+
return results
|