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,911 @@
|
|
|
1
|
+
"""Pattern-based cache invalidation system.
|
|
2
|
+
|
|
3
|
+
This module provides advanced cache invalidation capabilities using
|
|
4
|
+
glob-style pattern matching, supporting flexible cache management.
|
|
5
|
+
|
|
6
|
+
Key Features:
|
|
7
|
+
- Glob pattern matching (*, **, ?)
|
|
8
|
+
- Regex pattern support
|
|
9
|
+
- Tag-based invalidation
|
|
10
|
+
- Dependency tracking
|
|
11
|
+
- Cascading invalidation
|
|
12
|
+
- Batch operations
|
|
13
|
+
|
|
14
|
+
Example:
|
|
15
|
+
from truthound.profiler.cache_patterns import (
|
|
16
|
+
PatternInvalidator,
|
|
17
|
+
InvalidationPattern,
|
|
18
|
+
PatternType,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# Create invalidator
|
|
22
|
+
invalidator = PatternInvalidator(cache_backend)
|
|
23
|
+
|
|
24
|
+
# Invalidate by glob pattern
|
|
25
|
+
count = invalidator.invalidate("profile:user:*")
|
|
26
|
+
|
|
27
|
+
# Invalidate by regex
|
|
28
|
+
count = invalidator.invalidate(r"profile:user:\d+", pattern_type=PatternType.REGEX)
|
|
29
|
+
|
|
30
|
+
# Invalidate by tags
|
|
31
|
+
count = invalidator.invalidate_by_tags(["user", "session"])
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
from __future__ import annotations
|
|
35
|
+
|
|
36
|
+
import fnmatch
|
|
37
|
+
import re
|
|
38
|
+
import threading
|
|
39
|
+
import time
|
|
40
|
+
from abc import ABC, abstractmethod
|
|
41
|
+
from dataclasses import dataclass, field
|
|
42
|
+
from datetime import datetime, timedelta
|
|
43
|
+
from enum import Enum
|
|
44
|
+
from typing import Any, Callable, Iterator, Protocol
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# =============================================================================
|
|
48
|
+
# Types and Enums
|
|
49
|
+
# =============================================================================
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class PatternType(str, Enum):
|
|
53
|
+
"""Types of invalidation patterns."""
|
|
54
|
+
|
|
55
|
+
GLOB = "glob" # fnmatch-style: *, **, ?
|
|
56
|
+
REGEX = "regex" # Regular expressions
|
|
57
|
+
PREFIX = "prefix" # Simple prefix matching
|
|
58
|
+
EXACT = "exact" # Exact key match
|
|
59
|
+
TAG = "tag" # Tag-based matching
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass(frozen=True)
|
|
63
|
+
class InvalidationPattern:
|
|
64
|
+
"""Represents a cache invalidation pattern.
|
|
65
|
+
|
|
66
|
+
Attributes:
|
|
67
|
+
pattern: The pattern string
|
|
68
|
+
pattern_type: Type of pattern matching
|
|
69
|
+
compiled: Pre-compiled pattern (for regex)
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
pattern: str
|
|
73
|
+
pattern_type: PatternType = PatternType.GLOB
|
|
74
|
+
compiled: re.Pattern | None = field(default=None, compare=False)
|
|
75
|
+
|
|
76
|
+
def __post_init__(self) -> None:
|
|
77
|
+
"""Compile regex patterns."""
|
|
78
|
+
if self.pattern_type == PatternType.REGEX and self.compiled is None:
|
|
79
|
+
try:
|
|
80
|
+
object.__setattr__(self, "compiled", re.compile(self.pattern))
|
|
81
|
+
except re.error as e:
|
|
82
|
+
raise ValueError(f"Invalid regex pattern: {self.pattern}") from e
|
|
83
|
+
|
|
84
|
+
def matches(self, key: str) -> bool:
|
|
85
|
+
"""Check if key matches this pattern.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
key: Cache key to check
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
True if key matches the pattern
|
|
92
|
+
"""
|
|
93
|
+
if self.pattern_type == PatternType.EXACT:
|
|
94
|
+
return key == self.pattern
|
|
95
|
+
|
|
96
|
+
elif self.pattern_type == PatternType.PREFIX:
|
|
97
|
+
return key.startswith(self.pattern)
|
|
98
|
+
|
|
99
|
+
elif self.pattern_type == PatternType.GLOB:
|
|
100
|
+
return self._glob_match(key)
|
|
101
|
+
|
|
102
|
+
elif self.pattern_type == PatternType.REGEX:
|
|
103
|
+
if self.compiled:
|
|
104
|
+
return bool(self.compiled.match(key))
|
|
105
|
+
return bool(re.match(self.pattern, key))
|
|
106
|
+
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
def _glob_match(self, key: str) -> bool:
|
|
110
|
+
"""Match using glob-style patterns.
|
|
111
|
+
|
|
112
|
+
Supports:
|
|
113
|
+
- * : matches any characters except :
|
|
114
|
+
- ** : matches any characters including :
|
|
115
|
+
- ? : matches single character
|
|
116
|
+
"""
|
|
117
|
+
# Convert glob pattern to regex
|
|
118
|
+
regex = self._glob_to_regex(self.pattern)
|
|
119
|
+
return bool(re.match(regex, key))
|
|
120
|
+
|
|
121
|
+
def _glob_to_regex(self, pattern: str) -> str:
|
|
122
|
+
"""Convert glob pattern to regex.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
pattern: Glob pattern
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
Equivalent regex pattern
|
|
129
|
+
"""
|
|
130
|
+
# Escape special regex chars except * and ?
|
|
131
|
+
regex = ""
|
|
132
|
+
i = 0
|
|
133
|
+
|
|
134
|
+
while i < len(pattern):
|
|
135
|
+
c = pattern[i]
|
|
136
|
+
|
|
137
|
+
if c == "*":
|
|
138
|
+
# Check for **
|
|
139
|
+
if i + 1 < len(pattern) and pattern[i + 1] == "*":
|
|
140
|
+
regex += ".*" # Match everything
|
|
141
|
+
i += 2
|
|
142
|
+
continue
|
|
143
|
+
else:
|
|
144
|
+
regex += "[^:]*" # Match except separator
|
|
145
|
+
|
|
146
|
+
elif c == "?":
|
|
147
|
+
regex += "." # Match single char
|
|
148
|
+
|
|
149
|
+
elif c in ".^$+{}[]|()":
|
|
150
|
+
regex += "\\" + c # Escape
|
|
151
|
+
|
|
152
|
+
else:
|
|
153
|
+
regex += c
|
|
154
|
+
|
|
155
|
+
i += 1
|
|
156
|
+
|
|
157
|
+
return f"^{regex}$"
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@dataclass
|
|
161
|
+
class InvalidationResult:
|
|
162
|
+
"""Result of cache invalidation operation.
|
|
163
|
+
|
|
164
|
+
Attributes:
|
|
165
|
+
pattern: Pattern used for invalidation
|
|
166
|
+
keys_matched: Number of keys that matched
|
|
167
|
+
keys_invalidated: Number of keys successfully invalidated
|
|
168
|
+
duration_ms: Operation duration in milliseconds
|
|
169
|
+
errors: List of error messages
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
pattern: str
|
|
173
|
+
keys_matched: int = 0
|
|
174
|
+
keys_invalidated: int = 0
|
|
175
|
+
duration_ms: float = 0.0
|
|
176
|
+
errors: list[str] = field(default_factory=list)
|
|
177
|
+
|
|
178
|
+
@property
|
|
179
|
+
def success(self) -> bool:
|
|
180
|
+
"""Check if invalidation was successful."""
|
|
181
|
+
return len(self.errors) == 0 and self.keys_matched == self.keys_invalidated
|
|
182
|
+
|
|
183
|
+
def to_dict(self) -> dict[str, Any]:
|
|
184
|
+
"""Convert to dictionary."""
|
|
185
|
+
return {
|
|
186
|
+
"pattern": self.pattern,
|
|
187
|
+
"keys_matched": self.keys_matched,
|
|
188
|
+
"keys_invalidated": self.keys_invalidated,
|
|
189
|
+
"duration_ms": self.duration_ms,
|
|
190
|
+
"success": self.success,
|
|
191
|
+
"errors": self.errors,
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
# =============================================================================
|
|
196
|
+
# Cache Key Protocol
|
|
197
|
+
# =============================================================================
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class CacheKeyProvider(Protocol):
|
|
201
|
+
"""Protocol for cache backends that can list keys."""
|
|
202
|
+
|
|
203
|
+
def keys(self, pattern: str = "*") -> Iterator[str]:
|
|
204
|
+
"""Iterate over cache keys matching pattern."""
|
|
205
|
+
...
|
|
206
|
+
|
|
207
|
+
def delete(self, key: str) -> bool:
|
|
208
|
+
"""Delete a single key."""
|
|
209
|
+
...
|
|
210
|
+
|
|
211
|
+
def delete_many(self, keys: list[str]) -> int:
|
|
212
|
+
"""Delete multiple keys."""
|
|
213
|
+
...
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
# =============================================================================
|
|
217
|
+
# Tag Registry
|
|
218
|
+
# =============================================================================
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
@dataclass
|
|
222
|
+
class TagEntry:
|
|
223
|
+
"""Entry in tag registry.
|
|
224
|
+
|
|
225
|
+
Attributes:
|
|
226
|
+
key: Cache key
|
|
227
|
+
tags: Set of tags
|
|
228
|
+
created_at: When entry was created
|
|
229
|
+
"""
|
|
230
|
+
|
|
231
|
+
key: str
|
|
232
|
+
tags: set[str]
|
|
233
|
+
created_at: datetime = field(default_factory=datetime.now)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
class TagRegistry:
|
|
237
|
+
"""Registry for cache key tags.
|
|
238
|
+
|
|
239
|
+
Tracks which cache keys are associated with which tags,
|
|
240
|
+
enabling tag-based invalidation.
|
|
241
|
+
|
|
242
|
+
Example:
|
|
243
|
+
registry = TagRegistry()
|
|
244
|
+
|
|
245
|
+
# Register tags for a key
|
|
246
|
+
registry.register("user:123:profile", {"user", "profile"})
|
|
247
|
+
|
|
248
|
+
# Find keys by tag
|
|
249
|
+
keys = registry.find_by_tag("user")
|
|
250
|
+
|
|
251
|
+
# Find by multiple tags (intersection)
|
|
252
|
+
keys = registry.find_by_tags(["user", "active"])
|
|
253
|
+
"""
|
|
254
|
+
|
|
255
|
+
def __init__(self, max_entries: int = 100000):
|
|
256
|
+
"""Initialize registry.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
max_entries: Maximum number of entries to track
|
|
260
|
+
"""
|
|
261
|
+
self.max_entries = max_entries
|
|
262
|
+
self._entries: dict[str, TagEntry] = {}
|
|
263
|
+
self._tag_index: dict[str, set[str]] = {} # tag -> set of keys
|
|
264
|
+
self._lock = threading.RLock()
|
|
265
|
+
|
|
266
|
+
def register(self, key: str, tags: set[str] | list[str]) -> None:
|
|
267
|
+
"""Register tags for a cache key.
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
key: Cache key
|
|
271
|
+
tags: Tags to associate with key
|
|
272
|
+
"""
|
|
273
|
+
tags = set(tags)
|
|
274
|
+
|
|
275
|
+
with self._lock:
|
|
276
|
+
# Update or create entry
|
|
277
|
+
if key in self._entries:
|
|
278
|
+
old_tags = self._entries[key].tags
|
|
279
|
+
self._entries[key].tags = tags
|
|
280
|
+
|
|
281
|
+
# Update tag index for removed tags
|
|
282
|
+
for tag in old_tags - tags:
|
|
283
|
+
if tag in self._tag_index:
|
|
284
|
+
self._tag_index[tag].discard(key)
|
|
285
|
+
else:
|
|
286
|
+
# Check capacity
|
|
287
|
+
if len(self._entries) >= self.max_entries:
|
|
288
|
+
self._evict_oldest()
|
|
289
|
+
|
|
290
|
+
self._entries[key] = TagEntry(key=key, tags=tags)
|
|
291
|
+
|
|
292
|
+
# Update tag index for added tags
|
|
293
|
+
for tag in tags:
|
|
294
|
+
if tag not in self._tag_index:
|
|
295
|
+
self._tag_index[tag] = set()
|
|
296
|
+
self._tag_index[tag].add(key)
|
|
297
|
+
|
|
298
|
+
def unregister(self, key: str) -> set[str]:
|
|
299
|
+
"""Unregister a cache key.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
key: Cache key to unregister
|
|
303
|
+
|
|
304
|
+
Returns:
|
|
305
|
+
Tags that were associated with the key
|
|
306
|
+
"""
|
|
307
|
+
with self._lock:
|
|
308
|
+
if key not in self._entries:
|
|
309
|
+
return set()
|
|
310
|
+
|
|
311
|
+
entry = self._entries.pop(key)
|
|
312
|
+
|
|
313
|
+
# Update tag index
|
|
314
|
+
for tag in entry.tags:
|
|
315
|
+
if tag in self._tag_index:
|
|
316
|
+
self._tag_index[tag].discard(key)
|
|
317
|
+
if not self._tag_index[tag]:
|
|
318
|
+
del self._tag_index[tag]
|
|
319
|
+
|
|
320
|
+
return entry.tags
|
|
321
|
+
|
|
322
|
+
def get_tags(self, key: str) -> set[str]:
|
|
323
|
+
"""Get tags for a cache key.
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
key: Cache key
|
|
327
|
+
|
|
328
|
+
Returns:
|
|
329
|
+
Set of tags
|
|
330
|
+
"""
|
|
331
|
+
with self._lock:
|
|
332
|
+
if key in self._entries:
|
|
333
|
+
return self._entries[key].tags.copy()
|
|
334
|
+
return set()
|
|
335
|
+
|
|
336
|
+
def find_by_tag(self, tag: str) -> set[str]:
|
|
337
|
+
"""Find all keys with a specific tag.
|
|
338
|
+
|
|
339
|
+
Args:
|
|
340
|
+
tag: Tag to search for
|
|
341
|
+
|
|
342
|
+
Returns:
|
|
343
|
+
Set of cache keys
|
|
344
|
+
"""
|
|
345
|
+
with self._lock:
|
|
346
|
+
return self._tag_index.get(tag, set()).copy()
|
|
347
|
+
|
|
348
|
+
def find_by_tags(
|
|
349
|
+
self,
|
|
350
|
+
tags: list[str],
|
|
351
|
+
match_all: bool = True,
|
|
352
|
+
) -> set[str]:
|
|
353
|
+
"""Find keys matching multiple tags.
|
|
354
|
+
|
|
355
|
+
Args:
|
|
356
|
+
tags: Tags to search for
|
|
357
|
+
match_all: If True, keys must have all tags (AND)
|
|
358
|
+
If False, keys must have any tag (OR)
|
|
359
|
+
|
|
360
|
+
Returns:
|
|
361
|
+
Set of cache keys
|
|
362
|
+
"""
|
|
363
|
+
if not tags:
|
|
364
|
+
return set()
|
|
365
|
+
|
|
366
|
+
with self._lock:
|
|
367
|
+
tag_sets = [
|
|
368
|
+
self._tag_index.get(tag, set())
|
|
369
|
+
for tag in tags
|
|
370
|
+
]
|
|
371
|
+
|
|
372
|
+
if match_all:
|
|
373
|
+
# Intersection (AND)
|
|
374
|
+
result = tag_sets[0].copy()
|
|
375
|
+
for s in tag_sets[1:]:
|
|
376
|
+
result &= s
|
|
377
|
+
else:
|
|
378
|
+
# Union (OR)
|
|
379
|
+
result = set()
|
|
380
|
+
for s in tag_sets:
|
|
381
|
+
result |= s
|
|
382
|
+
|
|
383
|
+
return result
|
|
384
|
+
|
|
385
|
+
def list_tags(self) -> list[str]:
|
|
386
|
+
"""List all known tags.
|
|
387
|
+
|
|
388
|
+
Returns:
|
|
389
|
+
List of tag names
|
|
390
|
+
"""
|
|
391
|
+
with self._lock:
|
|
392
|
+
return list(self._tag_index.keys())
|
|
393
|
+
|
|
394
|
+
def tag_counts(self) -> dict[str, int]:
|
|
395
|
+
"""Get count of keys for each tag.
|
|
396
|
+
|
|
397
|
+
Returns:
|
|
398
|
+
Dictionary mapping tags to key counts
|
|
399
|
+
"""
|
|
400
|
+
with self._lock:
|
|
401
|
+
return {
|
|
402
|
+
tag: len(keys)
|
|
403
|
+
for tag, keys in self._tag_index.items()
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
def clear(self) -> int:
|
|
407
|
+
"""Clear all entries.
|
|
408
|
+
|
|
409
|
+
Returns:
|
|
410
|
+
Number of entries cleared
|
|
411
|
+
"""
|
|
412
|
+
with self._lock:
|
|
413
|
+
count = len(self._entries)
|
|
414
|
+
self._entries.clear()
|
|
415
|
+
self._tag_index.clear()
|
|
416
|
+
return count
|
|
417
|
+
|
|
418
|
+
def _evict_oldest(self) -> None:
|
|
419
|
+
"""Evict oldest entries when at capacity."""
|
|
420
|
+
if not self._entries:
|
|
421
|
+
return
|
|
422
|
+
|
|
423
|
+
# Find oldest entry
|
|
424
|
+
oldest_key = min(
|
|
425
|
+
self._entries.keys(),
|
|
426
|
+
key=lambda k: self._entries[k].created_at,
|
|
427
|
+
)
|
|
428
|
+
self.unregister(oldest_key)
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
# =============================================================================
|
|
432
|
+
# Dependency Tracker
|
|
433
|
+
# =============================================================================
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
class DependencyTracker:
|
|
437
|
+
"""Tracks cache key dependencies for cascading invalidation.
|
|
438
|
+
|
|
439
|
+
When a key is invalidated, all keys that depend on it
|
|
440
|
+
are also invalidated.
|
|
441
|
+
|
|
442
|
+
Example:
|
|
443
|
+
tracker = DependencyTracker()
|
|
444
|
+
|
|
445
|
+
# Key B depends on key A
|
|
446
|
+
tracker.add_dependency("keyB", "keyA")
|
|
447
|
+
|
|
448
|
+
# When A is invalidated, B should also be invalidated
|
|
449
|
+
deps = tracker.get_dependents("keyA") # Returns {"keyB"}
|
|
450
|
+
"""
|
|
451
|
+
|
|
452
|
+
def __init__(self):
|
|
453
|
+
"""Initialize tracker."""
|
|
454
|
+
self._dependencies: dict[str, set[str]] = {} # key -> keys it depends on
|
|
455
|
+
self._dependents: dict[str, set[str]] = {} # key -> keys that depend on it
|
|
456
|
+
self._lock = threading.RLock()
|
|
457
|
+
|
|
458
|
+
def add_dependency(self, key: str, depends_on: str | list[str]) -> None:
|
|
459
|
+
"""Add a dependency.
|
|
460
|
+
|
|
461
|
+
Args:
|
|
462
|
+
key: Cache key that has the dependency
|
|
463
|
+
depends_on: Key(s) that this key depends on
|
|
464
|
+
"""
|
|
465
|
+
if isinstance(depends_on, str):
|
|
466
|
+
depends_on = [depends_on]
|
|
467
|
+
|
|
468
|
+
with self._lock:
|
|
469
|
+
if key not in self._dependencies:
|
|
470
|
+
self._dependencies[key] = set()
|
|
471
|
+
|
|
472
|
+
for dep in depends_on:
|
|
473
|
+
self._dependencies[key].add(dep)
|
|
474
|
+
|
|
475
|
+
if dep not in self._dependents:
|
|
476
|
+
self._dependents[dep] = set()
|
|
477
|
+
self._dependents[dep].add(key)
|
|
478
|
+
|
|
479
|
+
def remove_dependency(self, key: str, depends_on: str | None = None) -> None:
|
|
480
|
+
"""Remove dependency.
|
|
481
|
+
|
|
482
|
+
Args:
|
|
483
|
+
key: Cache key
|
|
484
|
+
depends_on: Specific dependency to remove, or None for all
|
|
485
|
+
"""
|
|
486
|
+
with self._lock:
|
|
487
|
+
if key not in self._dependencies:
|
|
488
|
+
return
|
|
489
|
+
|
|
490
|
+
if depends_on:
|
|
491
|
+
self._dependencies[key].discard(depends_on)
|
|
492
|
+
if depends_on in self._dependents:
|
|
493
|
+
self._dependents[depends_on].discard(key)
|
|
494
|
+
else:
|
|
495
|
+
# Remove all dependencies
|
|
496
|
+
for dep in self._dependencies[key]:
|
|
497
|
+
if dep in self._dependents:
|
|
498
|
+
self._dependents[dep].discard(key)
|
|
499
|
+
del self._dependencies[key]
|
|
500
|
+
|
|
501
|
+
def get_dependencies(self, key: str) -> set[str]:
|
|
502
|
+
"""Get keys that this key depends on.
|
|
503
|
+
|
|
504
|
+
Args:
|
|
505
|
+
key: Cache key
|
|
506
|
+
|
|
507
|
+
Returns:
|
|
508
|
+
Set of dependency keys
|
|
509
|
+
"""
|
|
510
|
+
with self._lock:
|
|
511
|
+
return self._dependencies.get(key, set()).copy()
|
|
512
|
+
|
|
513
|
+
def get_dependents(self, key: str, recursive: bool = True) -> set[str]:
|
|
514
|
+
"""Get keys that depend on this key.
|
|
515
|
+
|
|
516
|
+
Args:
|
|
517
|
+
key: Cache key
|
|
518
|
+
recursive: If True, include transitive dependents
|
|
519
|
+
|
|
520
|
+
Returns:
|
|
521
|
+
Set of dependent keys
|
|
522
|
+
"""
|
|
523
|
+
with self._lock:
|
|
524
|
+
direct = self._dependents.get(key, set()).copy()
|
|
525
|
+
|
|
526
|
+
if not recursive:
|
|
527
|
+
return direct
|
|
528
|
+
|
|
529
|
+
# Find transitive dependents
|
|
530
|
+
all_dependents = direct.copy()
|
|
531
|
+
to_process = list(direct)
|
|
532
|
+
|
|
533
|
+
while to_process:
|
|
534
|
+
current = to_process.pop()
|
|
535
|
+
new_deps = self._dependents.get(current, set()) - all_dependents
|
|
536
|
+
all_dependents |= new_deps
|
|
537
|
+
to_process.extend(new_deps)
|
|
538
|
+
|
|
539
|
+
return all_dependents
|
|
540
|
+
|
|
541
|
+
def unregister(self, key: str) -> None:
|
|
542
|
+
"""Unregister a key and all its relationships.
|
|
543
|
+
|
|
544
|
+
Args:
|
|
545
|
+
key: Cache key to unregister
|
|
546
|
+
"""
|
|
547
|
+
with self._lock:
|
|
548
|
+
# Remove dependencies
|
|
549
|
+
self.remove_dependency(key)
|
|
550
|
+
|
|
551
|
+
# Remove from dependents index
|
|
552
|
+
if key in self._dependents:
|
|
553
|
+
del self._dependents[key]
|
|
554
|
+
|
|
555
|
+
# Remove from other keys' dependencies
|
|
556
|
+
for k in list(self._dependencies.keys()):
|
|
557
|
+
self._dependencies[k].discard(key)
|
|
558
|
+
|
|
559
|
+
def clear(self) -> None:
|
|
560
|
+
"""Clear all dependencies."""
|
|
561
|
+
with self._lock:
|
|
562
|
+
self._dependencies.clear()
|
|
563
|
+
self._dependents.clear()
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
# =============================================================================
|
|
567
|
+
# Pattern Invalidator
|
|
568
|
+
# =============================================================================
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
class PatternInvalidator:
|
|
572
|
+
"""Pattern-based cache invalidation.
|
|
573
|
+
|
|
574
|
+
Provides flexible cache invalidation using various pattern types
|
|
575
|
+
including glob, regex, prefix, and tag-based matching.
|
|
576
|
+
|
|
577
|
+
Example:
|
|
578
|
+
from truthound.profiler.caching import ProfileCache
|
|
579
|
+
|
|
580
|
+
cache = ProfileCache()
|
|
581
|
+
invalidator = PatternInvalidator(cache.backend)
|
|
582
|
+
|
|
583
|
+
# Invalidate all user profiles
|
|
584
|
+
result = invalidator.invalidate("profile:user:*")
|
|
585
|
+
print(f"Invalidated {result.keys_invalidated} keys")
|
|
586
|
+
|
|
587
|
+
# Invalidate by regex
|
|
588
|
+
result = invalidator.invalidate(
|
|
589
|
+
r"profile:user:\d+:session",
|
|
590
|
+
pattern_type=PatternType.REGEX,
|
|
591
|
+
)
|
|
592
|
+
|
|
593
|
+
# Invalidate with cascade
|
|
594
|
+
result = invalidator.invalidate_with_cascade("user:123")
|
|
595
|
+
|
|
596
|
+
Attributes:
|
|
597
|
+
backend: Cache backend that supports key listing
|
|
598
|
+
tag_registry: Optional tag registry for tag-based invalidation
|
|
599
|
+
dependency_tracker: Optional dependency tracker for cascading
|
|
600
|
+
batch_size: Batch size for delete operations
|
|
601
|
+
"""
|
|
602
|
+
|
|
603
|
+
def __init__(
|
|
604
|
+
self,
|
|
605
|
+
backend: Any,
|
|
606
|
+
tag_registry: TagRegistry | None = None,
|
|
607
|
+
dependency_tracker: DependencyTracker | None = None,
|
|
608
|
+
batch_size: int = 1000,
|
|
609
|
+
):
|
|
610
|
+
"""Initialize invalidator.
|
|
611
|
+
|
|
612
|
+
Args:
|
|
613
|
+
backend: Cache backend
|
|
614
|
+
tag_registry: Tag registry instance
|
|
615
|
+
dependency_tracker: Dependency tracker instance
|
|
616
|
+
batch_size: Batch size for deletions
|
|
617
|
+
"""
|
|
618
|
+
self.backend = backend
|
|
619
|
+
self.tag_registry = tag_registry or TagRegistry()
|
|
620
|
+
self.dependency_tracker = dependency_tracker or DependencyTracker()
|
|
621
|
+
self.batch_size = batch_size
|
|
622
|
+
self._lock = threading.RLock()
|
|
623
|
+
|
|
624
|
+
def invalidate(
|
|
625
|
+
self,
|
|
626
|
+
pattern: str,
|
|
627
|
+
pattern_type: PatternType = PatternType.GLOB,
|
|
628
|
+
dry_run: bool = False,
|
|
629
|
+
) -> InvalidationResult:
|
|
630
|
+
"""Invalidate cache entries matching pattern.
|
|
631
|
+
|
|
632
|
+
Args:
|
|
633
|
+
pattern: Invalidation pattern
|
|
634
|
+
pattern_type: Type of pattern matching
|
|
635
|
+
dry_run: If True, only count matches without deleting
|
|
636
|
+
|
|
637
|
+
Returns:
|
|
638
|
+
InvalidationResult with statistics
|
|
639
|
+
"""
|
|
640
|
+
start_time = time.time()
|
|
641
|
+
result = InvalidationResult(pattern=pattern)
|
|
642
|
+
|
|
643
|
+
try:
|
|
644
|
+
inv_pattern = InvalidationPattern(pattern, pattern_type)
|
|
645
|
+
|
|
646
|
+
# Find matching keys
|
|
647
|
+
matching_keys = self._find_matching_keys(inv_pattern)
|
|
648
|
+
result.keys_matched = len(matching_keys)
|
|
649
|
+
|
|
650
|
+
if not dry_run:
|
|
651
|
+
# Delete in batches
|
|
652
|
+
deleted = self._delete_keys(list(matching_keys))
|
|
653
|
+
result.keys_invalidated = deleted
|
|
654
|
+
|
|
655
|
+
# Unregister from tag registry
|
|
656
|
+
for key in matching_keys:
|
|
657
|
+
self.tag_registry.unregister(key)
|
|
658
|
+
self.dependency_tracker.unregister(key)
|
|
659
|
+
|
|
660
|
+
except Exception as e:
|
|
661
|
+
result.errors.append(str(e))
|
|
662
|
+
|
|
663
|
+
result.duration_ms = (time.time() - start_time) * 1000
|
|
664
|
+
return result
|
|
665
|
+
|
|
666
|
+
def invalidate_by_tags(
|
|
667
|
+
self,
|
|
668
|
+
tags: list[str],
|
|
669
|
+
match_all: bool = True,
|
|
670
|
+
dry_run: bool = False,
|
|
671
|
+
) -> InvalidationResult:
|
|
672
|
+
"""Invalidate cache entries by tags.
|
|
673
|
+
|
|
674
|
+
Args:
|
|
675
|
+
tags: Tags to match
|
|
676
|
+
match_all: If True, keys must have all tags
|
|
677
|
+
dry_run: If True, only count matches
|
|
678
|
+
|
|
679
|
+
Returns:
|
|
680
|
+
InvalidationResult
|
|
681
|
+
"""
|
|
682
|
+
start_time = time.time()
|
|
683
|
+
pattern_str = f"tags:[{','.join(tags)}]"
|
|
684
|
+
result = InvalidationResult(pattern=pattern_str)
|
|
685
|
+
|
|
686
|
+
try:
|
|
687
|
+
matching_keys = self.tag_registry.find_by_tags(tags, match_all)
|
|
688
|
+
result.keys_matched = len(matching_keys)
|
|
689
|
+
|
|
690
|
+
if not dry_run:
|
|
691
|
+
deleted = self._delete_keys(list(matching_keys))
|
|
692
|
+
result.keys_invalidated = deleted
|
|
693
|
+
|
|
694
|
+
for key in matching_keys:
|
|
695
|
+
self.tag_registry.unregister(key)
|
|
696
|
+
self.dependency_tracker.unregister(key)
|
|
697
|
+
|
|
698
|
+
except Exception as e:
|
|
699
|
+
result.errors.append(str(e))
|
|
700
|
+
|
|
701
|
+
result.duration_ms = (time.time() - start_time) * 1000
|
|
702
|
+
return result
|
|
703
|
+
|
|
704
|
+
def invalidate_with_cascade(
|
|
705
|
+
self,
|
|
706
|
+
key: str,
|
|
707
|
+
dry_run: bool = False,
|
|
708
|
+
) -> InvalidationResult:
|
|
709
|
+
"""Invalidate a key and all its dependents.
|
|
710
|
+
|
|
711
|
+
Args:
|
|
712
|
+
key: Cache key to invalidate
|
|
713
|
+
dry_run: If True, only count matches
|
|
714
|
+
|
|
715
|
+
Returns:
|
|
716
|
+
InvalidationResult
|
|
717
|
+
"""
|
|
718
|
+
start_time = time.time()
|
|
719
|
+
result = InvalidationResult(pattern=f"cascade:{key}")
|
|
720
|
+
|
|
721
|
+
try:
|
|
722
|
+
# Get all dependents
|
|
723
|
+
dependents = self.dependency_tracker.get_dependents(key, recursive=True)
|
|
724
|
+
all_keys = {key} | dependents
|
|
725
|
+
result.keys_matched = len(all_keys)
|
|
726
|
+
|
|
727
|
+
if not dry_run:
|
|
728
|
+
deleted = self._delete_keys(list(all_keys))
|
|
729
|
+
result.keys_invalidated = deleted
|
|
730
|
+
|
|
731
|
+
for k in all_keys:
|
|
732
|
+
self.tag_registry.unregister(k)
|
|
733
|
+
self.dependency_tracker.unregister(k)
|
|
734
|
+
|
|
735
|
+
except Exception as e:
|
|
736
|
+
result.errors.append(str(e))
|
|
737
|
+
|
|
738
|
+
result.duration_ms = (time.time() - start_time) * 1000
|
|
739
|
+
return result
|
|
740
|
+
|
|
741
|
+
def invalidate_multiple(
|
|
742
|
+
self,
|
|
743
|
+
patterns: list[str],
|
|
744
|
+
pattern_type: PatternType = PatternType.GLOB,
|
|
745
|
+
dry_run: bool = False,
|
|
746
|
+
) -> list[InvalidationResult]:
|
|
747
|
+
"""Invalidate multiple patterns.
|
|
748
|
+
|
|
749
|
+
Args:
|
|
750
|
+
patterns: List of patterns to invalidate
|
|
751
|
+
pattern_type: Type of pattern matching
|
|
752
|
+
dry_run: If True, only count matches
|
|
753
|
+
|
|
754
|
+
Returns:
|
|
755
|
+
List of InvalidationResult for each pattern
|
|
756
|
+
"""
|
|
757
|
+
return [
|
|
758
|
+
self.invalidate(pattern, pattern_type, dry_run)
|
|
759
|
+
for pattern in patterns
|
|
760
|
+
]
|
|
761
|
+
|
|
762
|
+
def _find_matching_keys(self, pattern: InvalidationPattern) -> set[str]:
|
|
763
|
+
"""Find all keys matching pattern.
|
|
764
|
+
|
|
765
|
+
Args:
|
|
766
|
+
pattern: Invalidation pattern
|
|
767
|
+
|
|
768
|
+
Returns:
|
|
769
|
+
Set of matching keys
|
|
770
|
+
"""
|
|
771
|
+
matching = set()
|
|
772
|
+
|
|
773
|
+
# Try to use backend's native pattern support
|
|
774
|
+
if hasattr(self.backend, "keys"):
|
|
775
|
+
try:
|
|
776
|
+
if pattern.pattern_type == PatternType.GLOB:
|
|
777
|
+
# Some backends support glob patterns natively
|
|
778
|
+
for key in self.backend.keys(pattern.pattern):
|
|
779
|
+
matching.add(key)
|
|
780
|
+
return matching
|
|
781
|
+
elif pattern.pattern_type == PatternType.PREFIX:
|
|
782
|
+
for key in self.backend.keys(f"{pattern.pattern}*"):
|
|
783
|
+
matching.add(key)
|
|
784
|
+
return matching
|
|
785
|
+
except (TypeError, NotImplementedError):
|
|
786
|
+
pass
|
|
787
|
+
|
|
788
|
+
# Fall back to scanning all keys
|
|
789
|
+
try:
|
|
790
|
+
for key in self.backend.keys("*"):
|
|
791
|
+
if pattern.matches(key):
|
|
792
|
+
matching.add(key)
|
|
793
|
+
except (TypeError, NotImplementedError):
|
|
794
|
+
pass
|
|
795
|
+
|
|
796
|
+
# For memory backend, scan internal cache
|
|
797
|
+
if hasattr(self.backend, "_cache"):
|
|
798
|
+
for key in list(self.backend._cache.keys()):
|
|
799
|
+
if pattern.matches(key):
|
|
800
|
+
matching.add(key)
|
|
801
|
+
|
|
802
|
+
return matching
|
|
803
|
+
|
|
804
|
+
def _delete_keys(self, keys: list[str]) -> int:
|
|
805
|
+
"""Delete keys from cache.
|
|
806
|
+
|
|
807
|
+
Args:
|
|
808
|
+
keys: Keys to delete
|
|
809
|
+
|
|
810
|
+
Returns:
|
|
811
|
+
Number of keys deleted
|
|
812
|
+
"""
|
|
813
|
+
if not keys:
|
|
814
|
+
return 0
|
|
815
|
+
|
|
816
|
+
# Try batch delete
|
|
817
|
+
if hasattr(self.backend, "delete_many"):
|
|
818
|
+
try:
|
|
819
|
+
total = 0
|
|
820
|
+
for i in range(0, len(keys), self.batch_size):
|
|
821
|
+
batch = keys[i:i + self.batch_size]
|
|
822
|
+
total += self.backend.delete_many(batch)
|
|
823
|
+
return total
|
|
824
|
+
except (TypeError, NotImplementedError):
|
|
825
|
+
pass
|
|
826
|
+
|
|
827
|
+
# Fall back to individual deletes
|
|
828
|
+
deleted = 0
|
|
829
|
+
for key in keys:
|
|
830
|
+
try:
|
|
831
|
+
if self.backend.delete(key):
|
|
832
|
+
deleted += 1
|
|
833
|
+
except Exception:
|
|
834
|
+
pass
|
|
835
|
+
|
|
836
|
+
return deleted
|
|
837
|
+
|
|
838
|
+
def register_with_tags(
|
|
839
|
+
self,
|
|
840
|
+
key: str,
|
|
841
|
+
tags: set[str] | list[str],
|
|
842
|
+
) -> None:
|
|
843
|
+
"""Register a cache key with tags.
|
|
844
|
+
|
|
845
|
+
Args:
|
|
846
|
+
key: Cache key
|
|
847
|
+
tags: Tags to associate
|
|
848
|
+
"""
|
|
849
|
+
self.tag_registry.register(key, tags)
|
|
850
|
+
|
|
851
|
+
def register_dependency(
|
|
852
|
+
self,
|
|
853
|
+
key: str,
|
|
854
|
+
depends_on: str | list[str],
|
|
855
|
+
) -> None:
|
|
856
|
+
"""Register a cache key dependency.
|
|
857
|
+
|
|
858
|
+
Args:
|
|
859
|
+
key: Cache key
|
|
860
|
+
depends_on: Key(s) this key depends on
|
|
861
|
+
"""
|
|
862
|
+
self.dependency_tracker.add_dependency(key, depends_on)
|
|
863
|
+
|
|
864
|
+
def get_stats(self) -> dict[str, Any]:
|
|
865
|
+
"""Get invalidator statistics.
|
|
866
|
+
|
|
867
|
+
Returns:
|
|
868
|
+
Statistics dictionary
|
|
869
|
+
"""
|
|
870
|
+
return {
|
|
871
|
+
"tag_count": len(self.tag_registry.list_tags()),
|
|
872
|
+
"tag_entries": len(self.tag_registry._entries),
|
|
873
|
+
"tag_counts": self.tag_registry.tag_counts(),
|
|
874
|
+
"dependency_count": len(self.dependency_tracker._dependencies),
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
|
|
878
|
+
# =============================================================================
|
|
879
|
+
# Convenience Functions
|
|
880
|
+
# =============================================================================
|
|
881
|
+
|
|
882
|
+
|
|
883
|
+
def create_pattern(
|
|
884
|
+
pattern: str,
|
|
885
|
+
pattern_type: PatternType | str = PatternType.GLOB,
|
|
886
|
+
) -> InvalidationPattern:
|
|
887
|
+
"""Create an invalidation pattern.
|
|
888
|
+
|
|
889
|
+
Args:
|
|
890
|
+
pattern: Pattern string
|
|
891
|
+
pattern_type: Type of pattern
|
|
892
|
+
|
|
893
|
+
Returns:
|
|
894
|
+
InvalidationPattern instance
|
|
895
|
+
"""
|
|
896
|
+
if isinstance(pattern_type, str):
|
|
897
|
+
pattern_type = PatternType(pattern_type.lower())
|
|
898
|
+
|
|
899
|
+
return InvalidationPattern(pattern=pattern, pattern_type=pattern_type)
|
|
900
|
+
|
|
901
|
+
|
|
902
|
+
def glob_to_regex(pattern: str) -> str:
|
|
903
|
+
"""Convert glob pattern to regex pattern.
|
|
904
|
+
|
|
905
|
+
Args:
|
|
906
|
+
pattern: Glob pattern
|
|
907
|
+
|
|
908
|
+
Returns:
|
|
909
|
+
Equivalent regex pattern
|
|
910
|
+
"""
|
|
911
|
+
return InvalidationPattern(pattern, PatternType.GLOB)._glob_to_regex(pattern)
|