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,576 @@
|
|
|
1
|
+
"""Lock strategies for filesystem concurrency control.
|
|
2
|
+
|
|
3
|
+
This module implements the Strategy pattern for file locking, allowing
|
|
4
|
+
different locking mechanisms to be used interchangeably based on platform
|
|
5
|
+
and requirements.
|
|
6
|
+
|
|
7
|
+
Supported strategies:
|
|
8
|
+
- FcntlLockStrategy: POSIX fcntl-based locking (Unix/Linux/macOS)
|
|
9
|
+
- PortalockerStrategy: Cross-platform locking via portalocker library
|
|
10
|
+
- FileLockStrategy: Cross-platform locking via filelock library
|
|
11
|
+
- NoOpLockStrategy: No-op for single-threaded scenarios
|
|
12
|
+
|
|
13
|
+
The module auto-detects the best available strategy for the current platform.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import os
|
|
19
|
+
import sys
|
|
20
|
+
import time
|
|
21
|
+
import threading
|
|
22
|
+
from abc import ABC, abstractmethod
|
|
23
|
+
from contextlib import contextmanager
|
|
24
|
+
from dataclasses import dataclass, field
|
|
25
|
+
from enum import Enum, auto
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
from typing import Any, Iterator, TypeVar
|
|
28
|
+
|
|
29
|
+
# Thread-local storage for lock tracking
|
|
30
|
+
_thread_local = threading.local()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class LockMode(Enum):
|
|
34
|
+
"""Lock acquisition modes."""
|
|
35
|
+
|
|
36
|
+
SHARED = auto() # Read lock - multiple readers allowed
|
|
37
|
+
EXCLUSIVE = auto() # Write lock - exclusive access
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(frozen=True)
|
|
41
|
+
class LockHandle:
|
|
42
|
+
"""Handle representing an acquired lock.
|
|
43
|
+
|
|
44
|
+
This is an opaque handle that must be passed to release the lock.
|
|
45
|
+
It contains information about the lock for debugging and management.
|
|
46
|
+
|
|
47
|
+
Attributes:
|
|
48
|
+
path: Path to the locked file.
|
|
49
|
+
mode: Lock mode (shared or exclusive).
|
|
50
|
+
fd: File descriptor (if applicable).
|
|
51
|
+
timestamp: When the lock was acquired.
|
|
52
|
+
thread_id: ID of the thread that acquired the lock.
|
|
53
|
+
process_id: ID of the process that acquired the lock.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
path: Path
|
|
57
|
+
mode: LockMode
|
|
58
|
+
fd: int | None = None
|
|
59
|
+
timestamp: float = field(default_factory=time.time)
|
|
60
|
+
thread_id: int = field(default_factory=threading.get_ident)
|
|
61
|
+
process_id: int = field(default_factory=os.getpid)
|
|
62
|
+
|
|
63
|
+
def __str__(self) -> str:
|
|
64
|
+
mode_str = "SHARED" if self.mode == LockMode.SHARED else "EXCLUSIVE"
|
|
65
|
+
return f"LockHandle({self.path}, {mode_str}, pid={self.process_id})"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class LockStrategy(ABC):
|
|
69
|
+
"""Abstract base class for lock strategies.
|
|
70
|
+
|
|
71
|
+
Implementations must be thread-safe and handle process-level locking.
|
|
72
|
+
The strategy pattern allows swapping locking mechanisms without changing
|
|
73
|
+
the client code.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
@abstractmethod
|
|
77
|
+
def acquire(
|
|
78
|
+
self,
|
|
79
|
+
path: Path,
|
|
80
|
+
mode: LockMode,
|
|
81
|
+
timeout: float | None = None,
|
|
82
|
+
blocking: bool = True,
|
|
83
|
+
) -> LockHandle:
|
|
84
|
+
"""Acquire a lock on the specified path.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
path: Path to the file to lock.
|
|
88
|
+
mode: Lock mode (shared or exclusive).
|
|
89
|
+
timeout: Maximum time to wait for lock (None = infinite).
|
|
90
|
+
blocking: If False, raise immediately if lock unavailable.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
LockHandle for the acquired lock.
|
|
94
|
+
|
|
95
|
+
Raises:
|
|
96
|
+
LockTimeout: If timeout expires before lock is acquired.
|
|
97
|
+
OSError: If locking fails due to system error.
|
|
98
|
+
"""
|
|
99
|
+
pass
|
|
100
|
+
|
|
101
|
+
@abstractmethod
|
|
102
|
+
def release(self, handle: LockHandle) -> None:
|
|
103
|
+
"""Release a previously acquired lock.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
handle: The lock handle returned by acquire().
|
|
107
|
+
|
|
108
|
+
Raises:
|
|
109
|
+
ValueError: If the handle is invalid or already released.
|
|
110
|
+
"""
|
|
111
|
+
pass
|
|
112
|
+
|
|
113
|
+
@abstractmethod
|
|
114
|
+
def try_acquire(self, path: Path, mode: LockMode) -> LockHandle | None:
|
|
115
|
+
"""Try to acquire a lock without blocking.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
path: Path to the file to lock.
|
|
119
|
+
mode: Lock mode (shared or exclusive).
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
LockHandle if lock acquired, None otherwise.
|
|
123
|
+
"""
|
|
124
|
+
pass
|
|
125
|
+
|
|
126
|
+
@abstractmethod
|
|
127
|
+
def is_locked(self, path: Path) -> bool:
|
|
128
|
+
"""Check if a path is currently locked by any process.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
path: Path to check.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
True if the path is locked.
|
|
135
|
+
"""
|
|
136
|
+
pass
|
|
137
|
+
|
|
138
|
+
@contextmanager
|
|
139
|
+
def lock(
|
|
140
|
+
self,
|
|
141
|
+
path: Path,
|
|
142
|
+
mode: LockMode,
|
|
143
|
+
timeout: float | None = None,
|
|
144
|
+
) -> Iterator[LockHandle]:
|
|
145
|
+
"""Context manager for lock acquisition.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
path: Path to the file to lock.
|
|
149
|
+
mode: Lock mode (shared or exclusive).
|
|
150
|
+
timeout: Maximum time to wait for lock.
|
|
151
|
+
|
|
152
|
+
Yields:
|
|
153
|
+
LockHandle for the acquired lock.
|
|
154
|
+
|
|
155
|
+
Example:
|
|
156
|
+
>>> with strategy.lock(path, LockMode.EXCLUSIVE) as handle:
|
|
157
|
+
... # Exclusive access to file
|
|
158
|
+
... pass
|
|
159
|
+
"""
|
|
160
|
+
handle = self.acquire(path, mode, timeout)
|
|
161
|
+
try:
|
|
162
|
+
yield handle
|
|
163
|
+
finally:
|
|
164
|
+
self.release(handle)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class FcntlLockStrategy(LockStrategy):
|
|
168
|
+
"""POSIX fcntl-based file locking strategy.
|
|
169
|
+
|
|
170
|
+
This strategy uses the fcntl.flock() system call for file locking.
|
|
171
|
+
It's available on Unix-like systems (Linux, macOS, BSD).
|
|
172
|
+
|
|
173
|
+
Features:
|
|
174
|
+
- Advisory locking (cooperative)
|
|
175
|
+
- Automatic release on file descriptor close
|
|
176
|
+
- Works across processes
|
|
177
|
+
- Supports shared and exclusive modes
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
def __init__(self) -> None:
|
|
181
|
+
"""Initialize the fcntl lock strategy."""
|
|
182
|
+
if sys.platform == "win32":
|
|
183
|
+
raise RuntimeError("FcntlLockStrategy is not available on Windows")
|
|
184
|
+
|
|
185
|
+
import fcntl
|
|
186
|
+
|
|
187
|
+
self._fcntl = fcntl
|
|
188
|
+
self._locks: dict[str, tuple[int, Any]] = {} # path -> (fd, file_obj)
|
|
189
|
+
self._lock = threading.RLock()
|
|
190
|
+
|
|
191
|
+
def acquire(
|
|
192
|
+
self,
|
|
193
|
+
path: Path,
|
|
194
|
+
mode: LockMode,
|
|
195
|
+
timeout: float | None = None,
|
|
196
|
+
blocking: bool = True,
|
|
197
|
+
) -> LockHandle:
|
|
198
|
+
"""Acquire a lock using fcntl.flock()."""
|
|
199
|
+
lock_path = self._get_lock_path(path)
|
|
200
|
+
lock_path.parent.mkdir(parents=True, exist_ok=True)
|
|
201
|
+
|
|
202
|
+
# Determine lock operation
|
|
203
|
+
if mode == LockMode.SHARED:
|
|
204
|
+
operation = self._fcntl.LOCK_SH
|
|
205
|
+
else:
|
|
206
|
+
operation = self._fcntl.LOCK_EX
|
|
207
|
+
|
|
208
|
+
if not blocking:
|
|
209
|
+
operation |= self._fcntl.LOCK_NB
|
|
210
|
+
|
|
211
|
+
# Open lock file
|
|
212
|
+
fd = os.open(str(lock_path), os.O_RDWR | os.O_CREAT, 0o666)
|
|
213
|
+
|
|
214
|
+
try:
|
|
215
|
+
if timeout is not None and blocking:
|
|
216
|
+
# Implement timeout with polling
|
|
217
|
+
start_time = time.time()
|
|
218
|
+
while True:
|
|
219
|
+
try:
|
|
220
|
+
self._fcntl.flock(fd, operation | self._fcntl.LOCK_NB)
|
|
221
|
+
break
|
|
222
|
+
except BlockingIOError:
|
|
223
|
+
if time.time() - start_time >= timeout:
|
|
224
|
+
os.close(fd)
|
|
225
|
+
from truthound.stores.concurrency.manager import LockTimeout
|
|
226
|
+
|
|
227
|
+
raise LockTimeout(path, timeout)
|
|
228
|
+
time.sleep(0.01) # 10ms sleep between retries
|
|
229
|
+
else:
|
|
230
|
+
self._fcntl.flock(fd, operation)
|
|
231
|
+
|
|
232
|
+
with self._lock:
|
|
233
|
+
self._locks[str(path)] = (fd, None)
|
|
234
|
+
|
|
235
|
+
return LockHandle(path=path, mode=mode, fd=fd)
|
|
236
|
+
|
|
237
|
+
except (BlockingIOError, OSError) as e:
|
|
238
|
+
os.close(fd)
|
|
239
|
+
if not blocking:
|
|
240
|
+
from truthound.stores.concurrency.manager import LockTimeout
|
|
241
|
+
|
|
242
|
+
raise LockTimeout(path, 0) from e
|
|
243
|
+
raise
|
|
244
|
+
|
|
245
|
+
def release(self, handle: LockHandle) -> None:
|
|
246
|
+
"""Release the fcntl lock."""
|
|
247
|
+
with self._lock:
|
|
248
|
+
key = str(handle.path)
|
|
249
|
+
if key not in self._locks:
|
|
250
|
+
raise ValueError(f"Lock not held: {handle.path}")
|
|
251
|
+
|
|
252
|
+
fd, _ = self._locks.pop(key)
|
|
253
|
+
|
|
254
|
+
try:
|
|
255
|
+
self._fcntl.flock(fd, self._fcntl.LOCK_UN)
|
|
256
|
+
finally:
|
|
257
|
+
os.close(fd)
|
|
258
|
+
|
|
259
|
+
# Clean up lock file if it exists and is empty
|
|
260
|
+
lock_path = self._get_lock_path(handle.path)
|
|
261
|
+
try:
|
|
262
|
+
if lock_path.exists() and lock_path.stat().st_size == 0:
|
|
263
|
+
lock_path.unlink(missing_ok=True)
|
|
264
|
+
except OSError:
|
|
265
|
+
pass
|
|
266
|
+
|
|
267
|
+
def try_acquire(self, path: Path, mode: LockMode) -> LockHandle | None:
|
|
268
|
+
"""Try to acquire without blocking."""
|
|
269
|
+
try:
|
|
270
|
+
return self.acquire(path, mode, blocking=False)
|
|
271
|
+
except Exception:
|
|
272
|
+
return None
|
|
273
|
+
|
|
274
|
+
def is_locked(self, path: Path) -> bool:
|
|
275
|
+
"""Check if path is locked."""
|
|
276
|
+
lock_path = self._get_lock_path(path)
|
|
277
|
+
if not lock_path.exists():
|
|
278
|
+
return False
|
|
279
|
+
|
|
280
|
+
try:
|
|
281
|
+
fd = os.open(str(lock_path), os.O_RDWR | os.O_CREAT, 0o666)
|
|
282
|
+
try:
|
|
283
|
+
self._fcntl.flock(fd, self._fcntl.LOCK_EX | self._fcntl.LOCK_NB)
|
|
284
|
+
self._fcntl.flock(fd, self._fcntl.LOCK_UN)
|
|
285
|
+
return False
|
|
286
|
+
except BlockingIOError:
|
|
287
|
+
return True
|
|
288
|
+
finally:
|
|
289
|
+
os.close(fd)
|
|
290
|
+
except OSError:
|
|
291
|
+
return False
|
|
292
|
+
|
|
293
|
+
def _get_lock_path(self, path: Path) -> Path:
|
|
294
|
+
"""Get the lock file path for a given path."""
|
|
295
|
+
return path.parent / f".{path.name}.lock"
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
class PortalockerStrategy(LockStrategy):
|
|
299
|
+
"""Cross-platform locking using portalocker library.
|
|
300
|
+
|
|
301
|
+
This strategy provides cross-platform file locking using the portalocker
|
|
302
|
+
library. It works on Windows, Linux, and macOS.
|
|
303
|
+
|
|
304
|
+
Requires: pip install portalocker
|
|
305
|
+
"""
|
|
306
|
+
|
|
307
|
+
def __init__(self) -> None:
|
|
308
|
+
"""Initialize the portalocker strategy."""
|
|
309
|
+
try:
|
|
310
|
+
import portalocker
|
|
311
|
+
|
|
312
|
+
self._portalocker = portalocker
|
|
313
|
+
except ImportError as e:
|
|
314
|
+
raise ImportError(
|
|
315
|
+
"portalocker is required for PortalockerStrategy. "
|
|
316
|
+
"Install it with: pip install portalocker"
|
|
317
|
+
) from e
|
|
318
|
+
|
|
319
|
+
self._locks: dict[str, Any] = {}
|
|
320
|
+
self._lock = threading.RLock()
|
|
321
|
+
|
|
322
|
+
def acquire(
|
|
323
|
+
self,
|
|
324
|
+
path: Path,
|
|
325
|
+
mode: LockMode,
|
|
326
|
+
timeout: float | None = None,
|
|
327
|
+
blocking: bool = True,
|
|
328
|
+
) -> LockHandle:
|
|
329
|
+
"""Acquire a lock using portalocker."""
|
|
330
|
+
lock_path = self._get_lock_path(path)
|
|
331
|
+
lock_path.parent.mkdir(parents=True, exist_ok=True)
|
|
332
|
+
|
|
333
|
+
# Determine lock flags
|
|
334
|
+
if mode == LockMode.SHARED:
|
|
335
|
+
flags = self._portalocker.LOCK_SH
|
|
336
|
+
else:
|
|
337
|
+
flags = self._portalocker.LOCK_EX
|
|
338
|
+
|
|
339
|
+
# Open and lock
|
|
340
|
+
lock_file = open(lock_path, "w")
|
|
341
|
+
|
|
342
|
+
try:
|
|
343
|
+
if timeout is not None or not blocking:
|
|
344
|
+
effective_timeout = timeout if timeout is not None else 0
|
|
345
|
+
self._portalocker.lock(
|
|
346
|
+
lock_file,
|
|
347
|
+
flags | self._portalocker.LOCK_NB,
|
|
348
|
+
timeout=effective_timeout,
|
|
349
|
+
)
|
|
350
|
+
else:
|
|
351
|
+
self._portalocker.lock(lock_file, flags)
|
|
352
|
+
|
|
353
|
+
with self._lock:
|
|
354
|
+
self._locks[str(path)] = lock_file
|
|
355
|
+
|
|
356
|
+
return LockHandle(path=path, mode=mode, fd=lock_file.fileno())
|
|
357
|
+
|
|
358
|
+
except self._portalocker.LockException as e:
|
|
359
|
+
lock_file.close()
|
|
360
|
+
from truthound.stores.concurrency.manager import LockTimeout
|
|
361
|
+
|
|
362
|
+
raise LockTimeout(path, timeout or 0) from e
|
|
363
|
+
except Exception:
|
|
364
|
+
lock_file.close()
|
|
365
|
+
raise
|
|
366
|
+
|
|
367
|
+
def release(self, handle: LockHandle) -> None:
|
|
368
|
+
"""Release the portalocker lock."""
|
|
369
|
+
with self._lock:
|
|
370
|
+
key = str(handle.path)
|
|
371
|
+
if key not in self._locks:
|
|
372
|
+
raise ValueError(f"Lock not held: {handle.path}")
|
|
373
|
+
|
|
374
|
+
lock_file = self._locks.pop(key)
|
|
375
|
+
|
|
376
|
+
try:
|
|
377
|
+
self._portalocker.unlock(lock_file)
|
|
378
|
+
finally:
|
|
379
|
+
lock_file.close()
|
|
380
|
+
|
|
381
|
+
def try_acquire(self, path: Path, mode: LockMode) -> LockHandle | None:
|
|
382
|
+
"""Try to acquire without blocking."""
|
|
383
|
+
try:
|
|
384
|
+
return self.acquire(path, mode, timeout=0, blocking=False)
|
|
385
|
+
except Exception:
|
|
386
|
+
return None
|
|
387
|
+
|
|
388
|
+
def is_locked(self, path: Path) -> bool:
|
|
389
|
+
"""Check if path is locked."""
|
|
390
|
+
lock_path = self._get_lock_path(path)
|
|
391
|
+
if not lock_path.exists():
|
|
392
|
+
return False
|
|
393
|
+
|
|
394
|
+
try:
|
|
395
|
+
lock_file = open(lock_path, "w")
|
|
396
|
+
try:
|
|
397
|
+
self._portalocker.lock(
|
|
398
|
+
lock_file,
|
|
399
|
+
self._portalocker.LOCK_EX | self._portalocker.LOCK_NB,
|
|
400
|
+
)
|
|
401
|
+
self._portalocker.unlock(lock_file)
|
|
402
|
+
return False
|
|
403
|
+
except self._portalocker.LockException:
|
|
404
|
+
return True
|
|
405
|
+
finally:
|
|
406
|
+
lock_file.close()
|
|
407
|
+
except OSError:
|
|
408
|
+
return False
|
|
409
|
+
|
|
410
|
+
def _get_lock_path(self, path: Path) -> Path:
|
|
411
|
+
"""Get the lock file path for a given path."""
|
|
412
|
+
return path.parent / f".{path.name}.lock"
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
class FileLockStrategy(LockStrategy):
|
|
416
|
+
"""Cross-platform locking using filelock library.
|
|
417
|
+
|
|
418
|
+
This strategy provides cross-platform file locking using the filelock
|
|
419
|
+
library. It's a simpler alternative to portalocker.
|
|
420
|
+
|
|
421
|
+
Requires: pip install filelock
|
|
422
|
+
"""
|
|
423
|
+
|
|
424
|
+
def __init__(self) -> None:
|
|
425
|
+
"""Initialize the filelock strategy."""
|
|
426
|
+
try:
|
|
427
|
+
import filelock
|
|
428
|
+
|
|
429
|
+
self._filelock = filelock
|
|
430
|
+
except ImportError as e:
|
|
431
|
+
raise ImportError(
|
|
432
|
+
"filelock is required for FileLockStrategy. "
|
|
433
|
+
"Install it with: pip install filelock"
|
|
434
|
+
) from e
|
|
435
|
+
|
|
436
|
+
self._locks: dict[str, Any] = {}
|
|
437
|
+
self._lock = threading.RLock()
|
|
438
|
+
|
|
439
|
+
def acquire(
|
|
440
|
+
self,
|
|
441
|
+
path: Path,
|
|
442
|
+
mode: LockMode,
|
|
443
|
+
timeout: float | None = None,
|
|
444
|
+
blocking: bool = True,
|
|
445
|
+
) -> LockHandle:
|
|
446
|
+
"""Acquire a lock using filelock."""
|
|
447
|
+
lock_path = self._get_lock_path(path)
|
|
448
|
+
lock_path.parent.mkdir(parents=True, exist_ok=True)
|
|
449
|
+
|
|
450
|
+
# filelock only supports exclusive locks
|
|
451
|
+
lock = self._filelock.FileLock(lock_path)
|
|
452
|
+
|
|
453
|
+
try:
|
|
454
|
+
effective_timeout = timeout if blocking else 0
|
|
455
|
+
lock.acquire(timeout=effective_timeout if effective_timeout else -1)
|
|
456
|
+
|
|
457
|
+
with self._lock:
|
|
458
|
+
self._locks[str(path)] = lock
|
|
459
|
+
|
|
460
|
+
return LockHandle(path=path, mode=mode)
|
|
461
|
+
|
|
462
|
+
except self._filelock.Timeout as e:
|
|
463
|
+
from truthound.stores.concurrency.manager import LockTimeout
|
|
464
|
+
|
|
465
|
+
raise LockTimeout(path, timeout or 0) from e
|
|
466
|
+
|
|
467
|
+
def release(self, handle: LockHandle) -> None:
|
|
468
|
+
"""Release the filelock lock."""
|
|
469
|
+
with self._lock:
|
|
470
|
+
key = str(handle.path)
|
|
471
|
+
if key not in self._locks:
|
|
472
|
+
raise ValueError(f"Lock not held: {handle.path}")
|
|
473
|
+
|
|
474
|
+
lock = self._locks.pop(key)
|
|
475
|
+
|
|
476
|
+
lock.release()
|
|
477
|
+
|
|
478
|
+
def try_acquire(self, path: Path, mode: LockMode) -> LockHandle | None:
|
|
479
|
+
"""Try to acquire without blocking."""
|
|
480
|
+
try:
|
|
481
|
+
return self.acquire(path, mode, timeout=0, blocking=False)
|
|
482
|
+
except Exception:
|
|
483
|
+
return None
|
|
484
|
+
|
|
485
|
+
def is_locked(self, path: Path) -> bool:
|
|
486
|
+
"""Check if path is locked."""
|
|
487
|
+
lock_path = self._get_lock_path(path)
|
|
488
|
+
if not lock_path.exists():
|
|
489
|
+
return False
|
|
490
|
+
|
|
491
|
+
lock = self._filelock.FileLock(lock_path)
|
|
492
|
+
try:
|
|
493
|
+
lock.acquire(timeout=0)
|
|
494
|
+
lock.release()
|
|
495
|
+
return False
|
|
496
|
+
except self._filelock.Timeout:
|
|
497
|
+
return True
|
|
498
|
+
|
|
499
|
+
def _get_lock_path(self, path: Path) -> Path:
|
|
500
|
+
"""Get the lock file path for a given path."""
|
|
501
|
+
return path.parent / f".{path.name}.lock"
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
class NoOpLockStrategy(LockStrategy):
|
|
505
|
+
"""No-op lock strategy for single-threaded scenarios.
|
|
506
|
+
|
|
507
|
+
This strategy provides no actual locking, useful for:
|
|
508
|
+
- Single-threaded applications
|
|
509
|
+
- Testing
|
|
510
|
+
- When external locking is already in place
|
|
511
|
+
"""
|
|
512
|
+
|
|
513
|
+
def acquire(
|
|
514
|
+
self,
|
|
515
|
+
path: Path,
|
|
516
|
+
mode: LockMode,
|
|
517
|
+
timeout: float | None = None,
|
|
518
|
+
blocking: bool = True,
|
|
519
|
+
) -> LockHandle:
|
|
520
|
+
"""Return a lock handle without actual locking."""
|
|
521
|
+
return LockHandle(path=path, mode=mode)
|
|
522
|
+
|
|
523
|
+
def release(self, handle: LockHandle) -> None:
|
|
524
|
+
"""No-op release."""
|
|
525
|
+
pass
|
|
526
|
+
|
|
527
|
+
def try_acquire(self, path: Path, mode: LockMode) -> LockHandle | None:
|
|
528
|
+
"""Always succeeds."""
|
|
529
|
+
return LockHandle(path=path, mode=mode)
|
|
530
|
+
|
|
531
|
+
def is_locked(self, path: Path) -> bool:
|
|
532
|
+
"""Always returns False."""
|
|
533
|
+
return False
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
def get_default_lock_strategy() -> LockStrategy:
|
|
537
|
+
"""Get the best available lock strategy for the current platform.
|
|
538
|
+
|
|
539
|
+
Returns:
|
|
540
|
+
The most appropriate LockStrategy for the current environment.
|
|
541
|
+
|
|
542
|
+
Priority order:
|
|
543
|
+
1. FcntlLockStrategy (Unix-like systems)
|
|
544
|
+
2. FileLockStrategy (if filelock is installed)
|
|
545
|
+
3. PortalockerStrategy (if portalocker is installed)
|
|
546
|
+
4. NoOpLockStrategy (fallback)
|
|
547
|
+
"""
|
|
548
|
+
# Try fcntl first on Unix-like systems
|
|
549
|
+
if sys.platform != "win32":
|
|
550
|
+
try:
|
|
551
|
+
return FcntlLockStrategy()
|
|
552
|
+
except Exception:
|
|
553
|
+
pass
|
|
554
|
+
|
|
555
|
+
# Try filelock (simpler API)
|
|
556
|
+
try:
|
|
557
|
+
return FileLockStrategy()
|
|
558
|
+
except ImportError:
|
|
559
|
+
pass
|
|
560
|
+
|
|
561
|
+
# Try portalocker
|
|
562
|
+
try:
|
|
563
|
+
return PortalockerStrategy()
|
|
564
|
+
except ImportError:
|
|
565
|
+
pass
|
|
566
|
+
|
|
567
|
+
# Fallback to no-op
|
|
568
|
+
import warnings
|
|
569
|
+
|
|
570
|
+
warnings.warn(
|
|
571
|
+
"No file locking library available. Using NoOpLockStrategy. "
|
|
572
|
+
"Install 'filelock' or 'portalocker' for proper concurrency control.",
|
|
573
|
+
RuntimeWarning,
|
|
574
|
+
stacklevel=2,
|
|
575
|
+
)
|
|
576
|
+
return NoOpLockStrategy()
|