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,252 @@
|
|
|
1
|
+
"""Core types and configuration for Circuit Breaker pattern.
|
|
2
|
+
|
|
3
|
+
This module defines:
|
|
4
|
+
- Circuit states (CLOSED, OPEN, HALF_OPEN)
|
|
5
|
+
- Configuration dataclasses
|
|
6
|
+
- Exception types
|
|
7
|
+
- Metrics and event types
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
from enum import Enum
|
|
15
|
+
from typing import Any, Callable
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CircuitState(str, Enum):
|
|
19
|
+
"""Circuit breaker states.
|
|
20
|
+
|
|
21
|
+
State transitions:
|
|
22
|
+
CLOSED -> OPEN: When failure threshold is exceeded
|
|
23
|
+
OPEN -> HALF_OPEN: After recovery timeout expires
|
|
24
|
+
HALF_OPEN -> CLOSED: When test calls succeed
|
|
25
|
+
HALF_OPEN -> OPEN: When test calls fail
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
CLOSED = "closed"
|
|
29
|
+
"""Normal operation - requests pass through."""
|
|
30
|
+
|
|
31
|
+
OPEN = "open"
|
|
32
|
+
"""Circuit tripped - requests fail immediately."""
|
|
33
|
+
|
|
34
|
+
HALF_OPEN = "half_open"
|
|
35
|
+
"""Testing recovery - limited requests allowed."""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class FailureDetectionStrategy(str, Enum):
|
|
39
|
+
"""Strategy for detecting failures."""
|
|
40
|
+
|
|
41
|
+
CONSECUTIVE = "consecutive"
|
|
42
|
+
"""Trip after N consecutive failures."""
|
|
43
|
+
|
|
44
|
+
PERCENTAGE = "percentage"
|
|
45
|
+
"""Trip when failure percentage exceeds threshold."""
|
|
46
|
+
|
|
47
|
+
TIME_WINDOW = "time_window"
|
|
48
|
+
"""Trip based on failures within a time window."""
|
|
49
|
+
|
|
50
|
+
COMPOSITE = "composite"
|
|
51
|
+
"""Combine multiple detection strategies."""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class CircuitBreakerConfig:
|
|
56
|
+
"""Configuration for circuit breaker behavior.
|
|
57
|
+
|
|
58
|
+
Attributes:
|
|
59
|
+
failure_threshold: Number of failures before opening circuit
|
|
60
|
+
recovery_timeout: Seconds to wait before attempting recovery
|
|
61
|
+
half_open_max_calls: Max calls allowed in half-open state
|
|
62
|
+
success_threshold: Successes needed to close from half-open
|
|
63
|
+
detection_strategy: Strategy for detecting failures
|
|
64
|
+
failure_rate_threshold: Failure rate to trip (for percentage strategy)
|
|
65
|
+
time_window_seconds: Window size for time-based detection
|
|
66
|
+
min_calls_in_window: Minimum calls before percentage applies
|
|
67
|
+
excluded_exceptions: Exception types that don't count as failures
|
|
68
|
+
included_exceptions: Only these exceptions count as failures (if set)
|
|
69
|
+
fallback: Optional fallback function when circuit is open
|
|
70
|
+
on_state_change: Callback for state changes
|
|
71
|
+
on_failure: Callback for failures
|
|
72
|
+
on_success: Callback for successes
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
# Thresholds
|
|
76
|
+
failure_threshold: int = 5
|
|
77
|
+
recovery_timeout: float = 30.0
|
|
78
|
+
half_open_max_calls: int = 3
|
|
79
|
+
success_threshold: int = 2
|
|
80
|
+
|
|
81
|
+
# Detection strategy
|
|
82
|
+
detection_strategy: FailureDetectionStrategy = FailureDetectionStrategy.CONSECUTIVE
|
|
83
|
+
failure_rate_threshold: float = 0.5
|
|
84
|
+
time_window_seconds: float = 60.0
|
|
85
|
+
min_calls_in_window: int = 10
|
|
86
|
+
|
|
87
|
+
# Exception filtering
|
|
88
|
+
excluded_exceptions: tuple[type[Exception], ...] = ()
|
|
89
|
+
included_exceptions: tuple[type[Exception], ...] | None = None
|
|
90
|
+
|
|
91
|
+
# Callbacks
|
|
92
|
+
fallback: Callable[..., Any] | None = None
|
|
93
|
+
on_state_change: Callable[[StateChangeEvent], None] | None = None
|
|
94
|
+
on_failure: Callable[[Exception, CircuitBreakerMetrics], None] | None = None
|
|
95
|
+
on_success: Callable[[Any, CircuitBreakerMetrics], None] | None = None
|
|
96
|
+
|
|
97
|
+
def should_count_exception(self, exc: Exception) -> bool:
|
|
98
|
+
"""Determine if an exception should count as a failure."""
|
|
99
|
+
if self.excluded_exceptions and isinstance(exc, self.excluded_exceptions):
|
|
100
|
+
return False
|
|
101
|
+
if self.included_exceptions is not None:
|
|
102
|
+
return isinstance(exc, self.included_exceptions)
|
|
103
|
+
return True
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class CircuitBreakerError(Exception):
|
|
107
|
+
"""Base exception for circuit breaker errors."""
|
|
108
|
+
|
|
109
|
+
def __init__(
|
|
110
|
+
self,
|
|
111
|
+
message: str,
|
|
112
|
+
breaker_name: str | None = None,
|
|
113
|
+
state: CircuitState | None = None,
|
|
114
|
+
):
|
|
115
|
+
super().__init__(message)
|
|
116
|
+
self.breaker_name = breaker_name
|
|
117
|
+
self.state = state
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class CircuitOpenError(CircuitBreakerError):
|
|
121
|
+
"""Raised when attempting to call through an open circuit."""
|
|
122
|
+
|
|
123
|
+
def __init__(
|
|
124
|
+
self,
|
|
125
|
+
breaker_name: str,
|
|
126
|
+
remaining_time: float | None = None,
|
|
127
|
+
):
|
|
128
|
+
message = f"Circuit '{breaker_name}' is open"
|
|
129
|
+
if remaining_time is not None:
|
|
130
|
+
message += f", recovery in {remaining_time:.1f}s"
|
|
131
|
+
super().__init__(message, breaker_name, CircuitState.OPEN)
|
|
132
|
+
self.remaining_time = remaining_time
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class CircuitHalfOpenError(CircuitBreakerError):
|
|
136
|
+
"""Raised when half-open circuit has reached max test calls."""
|
|
137
|
+
|
|
138
|
+
def __init__(self, breaker_name: str, max_calls: int):
|
|
139
|
+
message = f"Circuit '{breaker_name}' is half-open and at max test calls ({max_calls})"
|
|
140
|
+
super().__init__(message, breaker_name, CircuitState.HALF_OPEN)
|
|
141
|
+
self.max_calls = max_calls
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@dataclass
|
|
145
|
+
class CallResult:
|
|
146
|
+
"""Result of a call through the circuit breaker.
|
|
147
|
+
|
|
148
|
+
Attributes:
|
|
149
|
+
success: Whether the call succeeded
|
|
150
|
+
result: The return value if successful
|
|
151
|
+
exception: The exception if failed
|
|
152
|
+
duration_ms: Call duration in milliseconds
|
|
153
|
+
timestamp: When the call was made
|
|
154
|
+
from_fallback: Whether result came from fallback
|
|
155
|
+
"""
|
|
156
|
+
|
|
157
|
+
success: bool
|
|
158
|
+
result: Any = None
|
|
159
|
+
exception: Exception | None = None
|
|
160
|
+
duration_ms: float = 0.0
|
|
161
|
+
timestamp: datetime = field(default_factory=datetime.now)
|
|
162
|
+
from_fallback: bool = False
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@dataclass
|
|
166
|
+
class CircuitBreakerMetrics:
|
|
167
|
+
"""Metrics for circuit breaker monitoring.
|
|
168
|
+
|
|
169
|
+
Attributes:
|
|
170
|
+
name: Breaker name
|
|
171
|
+
state: Current state
|
|
172
|
+
total_calls: Total number of calls
|
|
173
|
+
successful_calls: Number of successful calls
|
|
174
|
+
failed_calls: Number of failed calls
|
|
175
|
+
rejected_calls: Calls rejected due to open circuit
|
|
176
|
+
consecutive_failures: Current consecutive failure count
|
|
177
|
+
consecutive_successes: Current consecutive success count
|
|
178
|
+
failure_rate: Current failure rate (0.0 - 1.0)
|
|
179
|
+
last_failure_time: Time of last failure
|
|
180
|
+
last_success_time: Time of last success
|
|
181
|
+
last_state_change_time: Time of last state change
|
|
182
|
+
time_in_current_state_ms: Time spent in current state
|
|
183
|
+
average_response_time_ms: Average call duration
|
|
184
|
+
"""
|
|
185
|
+
|
|
186
|
+
name: str
|
|
187
|
+
state: CircuitState
|
|
188
|
+
total_calls: int = 0
|
|
189
|
+
successful_calls: int = 0
|
|
190
|
+
failed_calls: int = 0
|
|
191
|
+
rejected_calls: int = 0
|
|
192
|
+
consecutive_failures: int = 0
|
|
193
|
+
consecutive_successes: int = 0
|
|
194
|
+
failure_rate: float = 0.0
|
|
195
|
+
last_failure_time: datetime | None = None
|
|
196
|
+
last_success_time: datetime | None = None
|
|
197
|
+
last_state_change_time: datetime | None = None
|
|
198
|
+
time_in_current_state_ms: float = 0.0
|
|
199
|
+
average_response_time_ms: float = 0.0
|
|
200
|
+
|
|
201
|
+
@property
|
|
202
|
+
def is_healthy(self) -> bool:
|
|
203
|
+
"""Check if circuit is in healthy state."""
|
|
204
|
+
return self.state == CircuitState.CLOSED
|
|
205
|
+
|
|
206
|
+
def to_dict(self) -> dict[str, Any]:
|
|
207
|
+
"""Convert to dictionary for serialization."""
|
|
208
|
+
return {
|
|
209
|
+
"name": self.name,
|
|
210
|
+
"state": self.state.value,
|
|
211
|
+
"total_calls": self.total_calls,
|
|
212
|
+
"successful_calls": self.successful_calls,
|
|
213
|
+
"failed_calls": self.failed_calls,
|
|
214
|
+
"rejected_calls": self.rejected_calls,
|
|
215
|
+
"consecutive_failures": self.consecutive_failures,
|
|
216
|
+
"consecutive_successes": self.consecutive_successes,
|
|
217
|
+
"failure_rate": self.failure_rate,
|
|
218
|
+
"last_failure_time": self.last_failure_time.isoformat() if self.last_failure_time else None,
|
|
219
|
+
"last_success_time": self.last_success_time.isoformat() if self.last_success_time else None,
|
|
220
|
+
"last_state_change_time": self.last_state_change_time.isoformat() if self.last_state_change_time else None,
|
|
221
|
+
"time_in_current_state_ms": self.time_in_current_state_ms,
|
|
222
|
+
"average_response_time_ms": self.average_response_time_ms,
|
|
223
|
+
"is_healthy": self.is_healthy,
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
@dataclass
|
|
228
|
+
class StateChangeEvent:
|
|
229
|
+
"""Event emitted when circuit state changes.
|
|
230
|
+
|
|
231
|
+
Attributes:
|
|
232
|
+
breaker_name: Name of the circuit breaker
|
|
233
|
+
from_state: Previous state
|
|
234
|
+
to_state: New state
|
|
235
|
+
timestamp: When the change occurred
|
|
236
|
+
reason: Reason for state change
|
|
237
|
+
metrics: Metrics at time of change
|
|
238
|
+
"""
|
|
239
|
+
|
|
240
|
+
breaker_name: str
|
|
241
|
+
from_state: CircuitState
|
|
242
|
+
to_state: CircuitState
|
|
243
|
+
timestamp: datetime = field(default_factory=datetime.now)
|
|
244
|
+
reason: str = ""
|
|
245
|
+
metrics: CircuitBreakerMetrics | None = None
|
|
246
|
+
|
|
247
|
+
def __str__(self) -> str:
|
|
248
|
+
return (
|
|
249
|
+
f"CircuitBreaker '{self.breaker_name}' state change: "
|
|
250
|
+
f"{self.from_state.value} -> {self.to_state.value}"
|
|
251
|
+
f"{f' ({self.reason})' if self.reason else ''}"
|
|
252
|
+
)
|
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
"""Failure detection strategies for Circuit Breaker.
|
|
2
|
+
|
|
3
|
+
This module provides different strategies for determining when
|
|
4
|
+
a circuit should trip (open):
|
|
5
|
+
|
|
6
|
+
- ConsecutiveFailureDetector: Trip after N consecutive failures
|
|
7
|
+
- PercentageFailureDetector: Trip when failure rate exceeds threshold
|
|
8
|
+
- TimeWindowFailureDetector: Trip based on failures within time window
|
|
9
|
+
- CompositeFailureDetector: Combine multiple strategies
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from abc import ABC, abstractmethod
|
|
15
|
+
from collections import deque
|
|
16
|
+
from dataclasses import dataclass, field
|
|
17
|
+
from datetime import datetime, timedelta
|
|
18
|
+
from threading import RLock
|
|
19
|
+
from typing import Protocol, runtime_checkable
|
|
20
|
+
|
|
21
|
+
from truthound.checkpoint.circuitbreaker.core import (
|
|
22
|
+
CircuitBreakerConfig,
|
|
23
|
+
FailureDetectionStrategy,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@runtime_checkable
|
|
28
|
+
class FailureDetector(Protocol):
|
|
29
|
+
"""Protocol for failure detection strategies."""
|
|
30
|
+
|
|
31
|
+
def record_success(self) -> None:
|
|
32
|
+
"""Record a successful call."""
|
|
33
|
+
...
|
|
34
|
+
|
|
35
|
+
def record_failure(self, exception: Exception | None = None) -> None:
|
|
36
|
+
"""Record a failed call."""
|
|
37
|
+
...
|
|
38
|
+
|
|
39
|
+
def should_trip(self) -> bool:
|
|
40
|
+
"""Determine if circuit should trip (open)."""
|
|
41
|
+
...
|
|
42
|
+
|
|
43
|
+
def reset(self) -> None:
|
|
44
|
+
"""Reset detector state."""
|
|
45
|
+
...
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def failure_count(self) -> int:
|
|
49
|
+
"""Current failure count."""
|
|
50
|
+
...
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def success_count(self) -> int:
|
|
54
|
+
"""Current success count."""
|
|
55
|
+
...
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class BaseFailureDetector(ABC):
|
|
59
|
+
"""Base class for failure detectors with common functionality."""
|
|
60
|
+
|
|
61
|
+
def __init__(self):
|
|
62
|
+
self._lock = RLock()
|
|
63
|
+
self._failures = 0
|
|
64
|
+
self._successes = 0
|
|
65
|
+
|
|
66
|
+
@abstractmethod
|
|
67
|
+
def record_success(self) -> None:
|
|
68
|
+
"""Record a successful call."""
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
@abstractmethod
|
|
72
|
+
def record_failure(self, exception: Exception | None = None) -> None:
|
|
73
|
+
"""Record a failed call."""
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
@abstractmethod
|
|
77
|
+
def should_trip(self) -> bool:
|
|
78
|
+
"""Determine if circuit should trip."""
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
def reset(self) -> None:
|
|
82
|
+
"""Reset detector state."""
|
|
83
|
+
with self._lock:
|
|
84
|
+
self._failures = 0
|
|
85
|
+
self._successes = 0
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def failure_count(self) -> int:
|
|
89
|
+
"""Current failure count."""
|
|
90
|
+
return self._failures
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def success_count(self) -> int:
|
|
94
|
+
"""Current success count."""
|
|
95
|
+
return self._successes
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class ConsecutiveFailureDetector(BaseFailureDetector):
|
|
99
|
+
"""Trips after N consecutive failures.
|
|
100
|
+
|
|
101
|
+
This is the simplest detection strategy. The circuit trips when
|
|
102
|
+
a specified number of consecutive failures occur. A single success
|
|
103
|
+
resets the counter.
|
|
104
|
+
|
|
105
|
+
Example:
|
|
106
|
+
>>> detector = ConsecutiveFailureDetector(threshold=3)
|
|
107
|
+
>>> detector.record_failure() # count: 1
|
|
108
|
+
>>> detector.record_failure() # count: 2
|
|
109
|
+
>>> detector.should_trip() # False
|
|
110
|
+
>>> detector.record_failure() # count: 3
|
|
111
|
+
>>> detector.should_trip() # True
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
def __init__(self, threshold: int = 5):
|
|
115
|
+
"""Initialize detector.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
threshold: Number of consecutive failures to trip
|
|
119
|
+
"""
|
|
120
|
+
super().__init__()
|
|
121
|
+
self._threshold = threshold
|
|
122
|
+
self._consecutive_failures = 0
|
|
123
|
+
|
|
124
|
+
def record_success(self) -> None:
|
|
125
|
+
"""Record success and reset consecutive failures."""
|
|
126
|
+
with self._lock:
|
|
127
|
+
self._successes += 1
|
|
128
|
+
self._consecutive_failures = 0
|
|
129
|
+
|
|
130
|
+
def record_failure(self, exception: Exception | None = None) -> None:
|
|
131
|
+
"""Record failure and increment consecutive counter."""
|
|
132
|
+
with self._lock:
|
|
133
|
+
self._failures += 1
|
|
134
|
+
self._consecutive_failures += 1
|
|
135
|
+
|
|
136
|
+
def should_trip(self) -> bool:
|
|
137
|
+
"""Trip if consecutive failures >= threshold."""
|
|
138
|
+
with self._lock:
|
|
139
|
+
return self._consecutive_failures >= self._threshold
|
|
140
|
+
|
|
141
|
+
def reset(self) -> None:
|
|
142
|
+
"""Reset all counters."""
|
|
143
|
+
with self._lock:
|
|
144
|
+
super().reset()
|
|
145
|
+
self._consecutive_failures = 0
|
|
146
|
+
|
|
147
|
+
@property
|
|
148
|
+
def consecutive_failures(self) -> int:
|
|
149
|
+
"""Current consecutive failure count."""
|
|
150
|
+
return self._consecutive_failures
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class PercentageFailureDetector(BaseFailureDetector):
|
|
154
|
+
"""Trips when failure percentage exceeds threshold.
|
|
155
|
+
|
|
156
|
+
This strategy considers the overall failure rate rather than
|
|
157
|
+
consecutive failures. Useful for services with occasional
|
|
158
|
+
transient errors.
|
|
159
|
+
|
|
160
|
+
Example:
|
|
161
|
+
>>> detector = PercentageFailureDetector(
|
|
162
|
+
... threshold=0.5, # 50%
|
|
163
|
+
... min_calls=10,
|
|
164
|
+
... )
|
|
165
|
+
>>> for _ in range(6):
|
|
166
|
+
... detector.record_failure()
|
|
167
|
+
>>> for _ in range(4):
|
|
168
|
+
... detector.record_success()
|
|
169
|
+
>>> detector.should_trip() # True (60% failure rate)
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
def __init__(
|
|
173
|
+
self,
|
|
174
|
+
threshold: float = 0.5,
|
|
175
|
+
min_calls: int = 10,
|
|
176
|
+
window_size: int | None = None,
|
|
177
|
+
):
|
|
178
|
+
"""Initialize detector.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
threshold: Failure rate (0.0-1.0) to trip
|
|
182
|
+
min_calls: Minimum calls before percentage applies
|
|
183
|
+
window_size: If set, only consider last N calls
|
|
184
|
+
"""
|
|
185
|
+
super().__init__()
|
|
186
|
+
self._threshold = threshold
|
|
187
|
+
self._min_calls = min_calls
|
|
188
|
+
self._window_size = window_size
|
|
189
|
+
self._calls: deque[bool] = deque(maxlen=window_size)
|
|
190
|
+
|
|
191
|
+
def record_success(self) -> None:
|
|
192
|
+
"""Record success."""
|
|
193
|
+
with self._lock:
|
|
194
|
+
self._successes += 1
|
|
195
|
+
self._calls.append(True)
|
|
196
|
+
|
|
197
|
+
def record_failure(self, exception: Exception | None = None) -> None:
|
|
198
|
+
"""Record failure."""
|
|
199
|
+
with self._lock:
|
|
200
|
+
self._failures += 1
|
|
201
|
+
self._calls.append(False)
|
|
202
|
+
|
|
203
|
+
def should_trip(self) -> bool:
|
|
204
|
+
"""Trip if failure rate >= threshold and min calls met."""
|
|
205
|
+
with self._lock:
|
|
206
|
+
total = len(self._calls) if self._window_size else (self._successes + self._failures)
|
|
207
|
+
if total < self._min_calls:
|
|
208
|
+
return False
|
|
209
|
+
|
|
210
|
+
if self._window_size:
|
|
211
|
+
failures = sum(1 for success in self._calls if not success)
|
|
212
|
+
rate = failures / total
|
|
213
|
+
else:
|
|
214
|
+
rate = self._failures / total if total > 0 else 0.0
|
|
215
|
+
|
|
216
|
+
return rate >= self._threshold
|
|
217
|
+
|
|
218
|
+
def reset(self) -> None:
|
|
219
|
+
"""Reset all counters."""
|
|
220
|
+
with self._lock:
|
|
221
|
+
super().reset()
|
|
222
|
+
self._calls.clear()
|
|
223
|
+
|
|
224
|
+
@property
|
|
225
|
+
def failure_rate(self) -> float:
|
|
226
|
+
"""Current failure rate."""
|
|
227
|
+
with self._lock:
|
|
228
|
+
if self._window_size:
|
|
229
|
+
total = len(self._calls)
|
|
230
|
+
if total == 0:
|
|
231
|
+
return 0.0
|
|
232
|
+
failures = sum(1 for success in self._calls if not success)
|
|
233
|
+
return failures / total
|
|
234
|
+
else:
|
|
235
|
+
total = self._successes + self._failures
|
|
236
|
+
return self._failures / total if total > 0 else 0.0
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
@dataclass
|
|
240
|
+
class TimestampedCall:
|
|
241
|
+
"""A call with timestamp for time-window tracking."""
|
|
242
|
+
|
|
243
|
+
success: bool
|
|
244
|
+
timestamp: datetime = field(default_factory=datetime.now)
|
|
245
|
+
exception_type: str | None = None
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
class TimeWindowFailureDetector(BaseFailureDetector):
|
|
249
|
+
"""Trips based on failures within a sliding time window.
|
|
250
|
+
|
|
251
|
+
This strategy only considers recent calls, allowing the circuit
|
|
252
|
+
to recover naturally as old failures age out of the window.
|
|
253
|
+
|
|
254
|
+
Example:
|
|
255
|
+
>>> detector = TimeWindowFailureDetector(
|
|
256
|
+
... threshold=5,
|
|
257
|
+
... window_seconds=60.0,
|
|
258
|
+
... )
|
|
259
|
+
>>> # 5 failures within 60 seconds will trip
|
|
260
|
+
"""
|
|
261
|
+
|
|
262
|
+
def __init__(
|
|
263
|
+
self,
|
|
264
|
+
threshold: int = 5,
|
|
265
|
+
window_seconds: float = 60.0,
|
|
266
|
+
use_percentage: bool = False,
|
|
267
|
+
percentage_threshold: float = 0.5,
|
|
268
|
+
min_calls: int = 10,
|
|
269
|
+
):
|
|
270
|
+
"""Initialize detector.
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
threshold: Number of failures to trip (absolute mode)
|
|
274
|
+
window_seconds: Time window size in seconds
|
|
275
|
+
use_percentage: Use percentage mode instead of absolute
|
|
276
|
+
percentage_threshold: Failure rate to trip (percentage mode)
|
|
277
|
+
min_calls: Minimum calls for percentage mode
|
|
278
|
+
"""
|
|
279
|
+
super().__init__()
|
|
280
|
+
self._threshold = threshold
|
|
281
|
+
self._window_seconds = window_seconds
|
|
282
|
+
self._use_percentage = use_percentage
|
|
283
|
+
self._percentage_threshold = percentage_threshold
|
|
284
|
+
self._min_calls = min_calls
|
|
285
|
+
self._calls: list[TimestampedCall] = []
|
|
286
|
+
|
|
287
|
+
def _cleanup_old_calls(self) -> None:
|
|
288
|
+
"""Remove calls outside the time window."""
|
|
289
|
+
cutoff = datetime.now() - timedelta(seconds=self._window_seconds)
|
|
290
|
+
self._calls = [c for c in self._calls if c.timestamp >= cutoff]
|
|
291
|
+
|
|
292
|
+
def record_success(self) -> None:
|
|
293
|
+
"""Record success."""
|
|
294
|
+
with self._lock:
|
|
295
|
+
self._successes += 1
|
|
296
|
+
self._calls.append(TimestampedCall(success=True))
|
|
297
|
+
self._cleanup_old_calls()
|
|
298
|
+
|
|
299
|
+
def record_failure(self, exception: Exception | None = None) -> None:
|
|
300
|
+
"""Record failure."""
|
|
301
|
+
with self._lock:
|
|
302
|
+
self._failures += 1
|
|
303
|
+
exc_type = type(exception).__name__ if exception else None
|
|
304
|
+
self._calls.append(TimestampedCall(success=False, exception_type=exc_type))
|
|
305
|
+
self._cleanup_old_calls()
|
|
306
|
+
|
|
307
|
+
def should_trip(self) -> bool:
|
|
308
|
+
"""Trip based on failures in time window."""
|
|
309
|
+
with self._lock:
|
|
310
|
+
self._cleanup_old_calls()
|
|
311
|
+
|
|
312
|
+
failures_in_window = sum(1 for c in self._calls if not c.success)
|
|
313
|
+
total_in_window = len(self._calls)
|
|
314
|
+
|
|
315
|
+
if self._use_percentage:
|
|
316
|
+
if total_in_window < self._min_calls:
|
|
317
|
+
return False
|
|
318
|
+
rate = failures_in_window / total_in_window
|
|
319
|
+
return rate >= self._percentage_threshold
|
|
320
|
+
else:
|
|
321
|
+
return failures_in_window >= self._threshold
|
|
322
|
+
|
|
323
|
+
def reset(self) -> None:
|
|
324
|
+
"""Reset all counters."""
|
|
325
|
+
with self._lock:
|
|
326
|
+
super().reset()
|
|
327
|
+
self._calls.clear()
|
|
328
|
+
|
|
329
|
+
@property
|
|
330
|
+
def calls_in_window(self) -> int:
|
|
331
|
+
"""Number of calls in current window."""
|
|
332
|
+
with self._lock:
|
|
333
|
+
self._cleanup_old_calls()
|
|
334
|
+
return len(self._calls)
|
|
335
|
+
|
|
336
|
+
@property
|
|
337
|
+
def failures_in_window(self) -> int:
|
|
338
|
+
"""Number of failures in current window."""
|
|
339
|
+
with self._lock:
|
|
340
|
+
self._cleanup_old_calls()
|
|
341
|
+
return sum(1 for c in self._calls if not c.success)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
class CompositeFailureDetector(BaseFailureDetector):
|
|
345
|
+
"""Combines multiple detection strategies.
|
|
346
|
+
|
|
347
|
+
This detector can use either AND or OR logic to combine
|
|
348
|
+
multiple detection strategies.
|
|
349
|
+
|
|
350
|
+
Example:
|
|
351
|
+
>>> detector = CompositeFailureDetector(
|
|
352
|
+
... detectors=[
|
|
353
|
+
... ConsecutiveFailureDetector(threshold=3),
|
|
354
|
+
... PercentageFailureDetector(threshold=0.5),
|
|
355
|
+
... ],
|
|
356
|
+
... require_all=False, # OR logic
|
|
357
|
+
... )
|
|
358
|
+
"""
|
|
359
|
+
|
|
360
|
+
def __init__(
|
|
361
|
+
self,
|
|
362
|
+
detectors: list[FailureDetector],
|
|
363
|
+
require_all: bool = False,
|
|
364
|
+
):
|
|
365
|
+
"""Initialize composite detector.
|
|
366
|
+
|
|
367
|
+
Args:
|
|
368
|
+
detectors: List of detectors to combine
|
|
369
|
+
require_all: If True, all must trip (AND). If False, any trips (OR)
|
|
370
|
+
"""
|
|
371
|
+
super().__init__()
|
|
372
|
+
self._detectors = detectors
|
|
373
|
+
self._require_all = require_all
|
|
374
|
+
|
|
375
|
+
def record_success(self) -> None:
|
|
376
|
+
"""Record success on all detectors."""
|
|
377
|
+
with self._lock:
|
|
378
|
+
self._successes += 1
|
|
379
|
+
for detector in self._detectors:
|
|
380
|
+
detector.record_success()
|
|
381
|
+
|
|
382
|
+
def record_failure(self, exception: Exception | None = None) -> None:
|
|
383
|
+
"""Record failure on all detectors."""
|
|
384
|
+
with self._lock:
|
|
385
|
+
self._failures += 1
|
|
386
|
+
for detector in self._detectors:
|
|
387
|
+
detector.record_failure(exception)
|
|
388
|
+
|
|
389
|
+
def should_trip(self) -> bool:
|
|
390
|
+
"""Trip based on combined detector results."""
|
|
391
|
+
with self._lock:
|
|
392
|
+
if self._require_all:
|
|
393
|
+
return all(d.should_trip() for d in self._detectors)
|
|
394
|
+
else:
|
|
395
|
+
return any(d.should_trip() for d in self._detectors)
|
|
396
|
+
|
|
397
|
+
def reset(self) -> None:
|
|
398
|
+
"""Reset all detectors."""
|
|
399
|
+
with self._lock:
|
|
400
|
+
super().reset()
|
|
401
|
+
for detector in self._detectors:
|
|
402
|
+
detector.reset()
|
|
403
|
+
|
|
404
|
+
@property
|
|
405
|
+
def detector_states(self) -> list[dict]:
|
|
406
|
+
"""Get state of all detectors."""
|
|
407
|
+
return [
|
|
408
|
+
{
|
|
409
|
+
"type": type(d).__name__,
|
|
410
|
+
"should_trip": d.should_trip(),
|
|
411
|
+
"failure_count": d.failure_count,
|
|
412
|
+
"success_count": d.success_count,
|
|
413
|
+
}
|
|
414
|
+
for d in self._detectors
|
|
415
|
+
]
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def create_detector(config: CircuitBreakerConfig) -> FailureDetector:
|
|
419
|
+
"""Factory function to create appropriate detector from config.
|
|
420
|
+
|
|
421
|
+
Args:
|
|
422
|
+
config: Circuit breaker configuration
|
|
423
|
+
|
|
424
|
+
Returns:
|
|
425
|
+
Appropriate failure detector based on config.detection_strategy
|
|
426
|
+
"""
|
|
427
|
+
strategy = config.detection_strategy
|
|
428
|
+
|
|
429
|
+
if strategy == FailureDetectionStrategy.CONSECUTIVE:
|
|
430
|
+
return ConsecutiveFailureDetector(threshold=config.failure_threshold)
|
|
431
|
+
|
|
432
|
+
elif strategy == FailureDetectionStrategy.PERCENTAGE:
|
|
433
|
+
return PercentageFailureDetector(
|
|
434
|
+
threshold=config.failure_rate_threshold,
|
|
435
|
+
min_calls=config.min_calls_in_window,
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
elif strategy == FailureDetectionStrategy.TIME_WINDOW:
|
|
439
|
+
return TimeWindowFailureDetector(
|
|
440
|
+
threshold=config.failure_threshold,
|
|
441
|
+
window_seconds=config.time_window_seconds,
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
elif strategy == FailureDetectionStrategy.COMPOSITE:
|
|
445
|
+
# Create a composite with both consecutive and percentage
|
|
446
|
+
return CompositeFailureDetector(
|
|
447
|
+
detectors=[
|
|
448
|
+
ConsecutiveFailureDetector(threshold=config.failure_threshold),
|
|
449
|
+
PercentageFailureDetector(
|
|
450
|
+
threshold=config.failure_rate_threshold,
|
|
451
|
+
min_calls=config.min_calls_in_window,
|
|
452
|
+
),
|
|
453
|
+
],
|
|
454
|
+
require_all=False,
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
else:
|
|
458
|
+
# Default to consecutive
|
|
459
|
+
return ConsecutiveFailureDetector(threshold=config.failure_threshold)
|