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,630 @@
|
|
|
1
|
+
"""Tenant isolation strategies for multi-tenancy.
|
|
2
|
+
|
|
3
|
+
This module provides different data isolation strategies to ensure
|
|
4
|
+
tenant data separation at various levels of isolation.
|
|
5
|
+
|
|
6
|
+
Isolation Levels (from lowest to highest):
|
|
7
|
+
- SHARED: Column-based filtering (tenant_id column)
|
|
8
|
+
- ROW_LEVEL: Row-level security with additional checks
|
|
9
|
+
- SCHEMA: Each tenant has its own schema
|
|
10
|
+
- DATABASE: Each tenant has its own database
|
|
11
|
+
- INSTANCE: Complete instance isolation (out of scope for this module)
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import hashlib
|
|
17
|
+
from abc import ABC
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from typing import TYPE_CHECKING, Any, Callable
|
|
20
|
+
|
|
21
|
+
from truthound.multitenancy.core import (
|
|
22
|
+
IsolationLevel,
|
|
23
|
+
IsolationStrategy,
|
|
24
|
+
Tenant,
|
|
25
|
+
TenantContext,
|
|
26
|
+
TenantError,
|
|
27
|
+
TenantIsolationError,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
import polars as pl
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# =============================================================================
|
|
35
|
+
# Shared/Column-Based Isolation
|
|
36
|
+
# =============================================================================
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class SharedIsolation(IsolationStrategy):
|
|
40
|
+
"""Shared isolation using a tenant ID column.
|
|
41
|
+
|
|
42
|
+
This is the simplest form of isolation where all tenants share
|
|
43
|
+
the same tables and isolation is enforced by filtering on a
|
|
44
|
+
tenant_id column.
|
|
45
|
+
|
|
46
|
+
Pros:
|
|
47
|
+
- Simple to implement
|
|
48
|
+
- Easy to manage schema changes
|
|
49
|
+
- Lower operational overhead
|
|
50
|
+
|
|
51
|
+
Cons:
|
|
52
|
+
- Risk of data leakage if filters are forgotten
|
|
53
|
+
- Performance impact from WHERE clauses
|
|
54
|
+
- Noisy neighbor issues possible
|
|
55
|
+
|
|
56
|
+
Example:
|
|
57
|
+
>>> strategy = SharedIsolation(tenant_column="tenant_id")
|
|
58
|
+
>>> filtered_df = strategy.apply_filter(df, tenant)
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def __init__(
|
|
62
|
+
self,
|
|
63
|
+
tenant_column: str = "tenant_id",
|
|
64
|
+
validate_on_apply: bool = True,
|
|
65
|
+
) -> None:
|
|
66
|
+
self._tenant_column = tenant_column
|
|
67
|
+
self._validate_on_apply = validate_on_apply
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def isolation_level(self) -> IsolationLevel:
|
|
71
|
+
"""The isolation level this strategy provides."""
|
|
72
|
+
return IsolationLevel.SHARED
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def tenant_column(self) -> str:
|
|
76
|
+
"""The column used for tenant identification."""
|
|
77
|
+
return self._tenant_column
|
|
78
|
+
|
|
79
|
+
def apply_filter(
|
|
80
|
+
self,
|
|
81
|
+
df: "pl.LazyFrame",
|
|
82
|
+
tenant: Tenant,
|
|
83
|
+
) -> "pl.LazyFrame":
|
|
84
|
+
"""Apply tenant filter to a LazyFrame."""
|
|
85
|
+
import polars as pl
|
|
86
|
+
|
|
87
|
+
if self._validate_on_apply:
|
|
88
|
+
self.validate_isolation(tenant)
|
|
89
|
+
|
|
90
|
+
# Get the actual column name from tenant or use default
|
|
91
|
+
column = tenant.tenant_column or self._tenant_column
|
|
92
|
+
|
|
93
|
+
# Check if column exists
|
|
94
|
+
if column not in df.collect_schema().names():
|
|
95
|
+
# If column doesn't exist, this might be non-tenant data
|
|
96
|
+
# Return empty frame for safety
|
|
97
|
+
return df.filter(pl.lit(False))
|
|
98
|
+
|
|
99
|
+
return df.filter(pl.col(column) == tenant.id)
|
|
100
|
+
|
|
101
|
+
def add_tenant_column(
|
|
102
|
+
self,
|
|
103
|
+
df: "pl.LazyFrame",
|
|
104
|
+
tenant: Tenant,
|
|
105
|
+
) -> "pl.LazyFrame":
|
|
106
|
+
"""Add tenant identifier to a LazyFrame."""
|
|
107
|
+
import polars as pl
|
|
108
|
+
|
|
109
|
+
column = tenant.tenant_column or self._tenant_column
|
|
110
|
+
return df.with_columns(pl.lit(tenant.id).alias(column))
|
|
111
|
+
|
|
112
|
+
def get_namespace(self, tenant: Tenant) -> str:
|
|
113
|
+
"""Get the namespace for a tenant (same for all tenants)."""
|
|
114
|
+
return "shared"
|
|
115
|
+
|
|
116
|
+
def validate_column_exists(self, df: "pl.LazyFrame") -> bool:
|
|
117
|
+
"""Check if the tenant column exists in the dataframe."""
|
|
118
|
+
return self._tenant_column in df.collect_schema().names()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# =============================================================================
|
|
122
|
+
# Row-Level Security Isolation
|
|
123
|
+
# =============================================================================
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@dataclass
|
|
127
|
+
class RowLevelPolicy:
|
|
128
|
+
"""A row-level security policy.
|
|
129
|
+
|
|
130
|
+
Example:
|
|
131
|
+
>>> policy = RowLevelPolicy(
|
|
132
|
+
... name="owner_access",
|
|
133
|
+
... condition=lambda row, tenant: row["owner_id"] == tenant.owner_id,
|
|
134
|
+
... )
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
name: str
|
|
138
|
+
condition: Callable[[dict[str, Any], Tenant], bool]
|
|
139
|
+
priority: int = 0
|
|
140
|
+
enabled: bool = True
|
|
141
|
+
|
|
142
|
+
def check(self, row: dict[str, Any], tenant: Tenant) -> bool:
|
|
143
|
+
"""Check if the policy allows access."""
|
|
144
|
+
if not self.enabled:
|
|
145
|
+
return True
|
|
146
|
+
return self.condition(row, tenant)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class RowLevelIsolation(IsolationStrategy):
|
|
150
|
+
"""Row-level isolation with additional security policies.
|
|
151
|
+
|
|
152
|
+
Extends shared isolation with additional row-level checks
|
|
153
|
+
that can enforce more complex access rules.
|
|
154
|
+
|
|
155
|
+
Example:
|
|
156
|
+
>>> strategy = RowLevelIsolation(tenant_column="tenant_id")
|
|
157
|
+
>>> strategy.add_policy(RowLevelPolicy(
|
|
158
|
+
... name="owner_only",
|
|
159
|
+
... condition=lambda row, tenant: row.get("owner") == tenant.owner_id,
|
|
160
|
+
... ))
|
|
161
|
+
>>> filtered_df = strategy.apply_filter(df, tenant)
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
def __init__(
|
|
165
|
+
self,
|
|
166
|
+
tenant_column: str = "tenant_id",
|
|
167
|
+
validate_on_apply: bool = True,
|
|
168
|
+
fail_closed: bool = True,
|
|
169
|
+
) -> None:
|
|
170
|
+
self._tenant_column = tenant_column
|
|
171
|
+
self._validate_on_apply = validate_on_apply
|
|
172
|
+
self._fail_closed = fail_closed # Deny access on policy errors
|
|
173
|
+
self._policies: list[RowLevelPolicy] = []
|
|
174
|
+
|
|
175
|
+
@property
|
|
176
|
+
def isolation_level(self) -> IsolationLevel:
|
|
177
|
+
"""The isolation level this strategy provides."""
|
|
178
|
+
return IsolationLevel.ROW_LEVEL
|
|
179
|
+
|
|
180
|
+
def add_policy(self, policy: RowLevelPolicy) -> None:
|
|
181
|
+
"""Add a row-level security policy."""
|
|
182
|
+
self._policies.append(policy)
|
|
183
|
+
self._policies.sort(key=lambda p: p.priority, reverse=True)
|
|
184
|
+
|
|
185
|
+
def remove_policy(self, name: str) -> bool:
|
|
186
|
+
"""Remove a policy by name."""
|
|
187
|
+
for i, policy in enumerate(self._policies):
|
|
188
|
+
if policy.name == name:
|
|
189
|
+
del self._policies[i]
|
|
190
|
+
return True
|
|
191
|
+
return False
|
|
192
|
+
|
|
193
|
+
def apply_filter(
|
|
194
|
+
self,
|
|
195
|
+
df: "pl.LazyFrame",
|
|
196
|
+
tenant: Tenant,
|
|
197
|
+
) -> "pl.LazyFrame":
|
|
198
|
+
"""Apply tenant filter with row-level policies."""
|
|
199
|
+
import polars as pl
|
|
200
|
+
|
|
201
|
+
if self._validate_on_apply:
|
|
202
|
+
self.validate_isolation(tenant)
|
|
203
|
+
|
|
204
|
+
column = tenant.tenant_column or self._tenant_column
|
|
205
|
+
|
|
206
|
+
# First apply basic tenant filter
|
|
207
|
+
if column in df.collect_schema().names():
|
|
208
|
+
df = df.filter(pl.col(column) == tenant.id)
|
|
209
|
+
elif self._fail_closed:
|
|
210
|
+
return df.filter(pl.lit(False))
|
|
211
|
+
|
|
212
|
+
# Additional row-level policies would be applied here
|
|
213
|
+
# Note: Complex row-level policies may require collecting data
|
|
214
|
+
# For now, we return the tenant-filtered frame
|
|
215
|
+
return df
|
|
216
|
+
|
|
217
|
+
def add_tenant_column(
|
|
218
|
+
self,
|
|
219
|
+
df: "pl.LazyFrame",
|
|
220
|
+
tenant: Tenant,
|
|
221
|
+
) -> "pl.LazyFrame":
|
|
222
|
+
"""Add tenant identifier to a LazyFrame."""
|
|
223
|
+
import polars as pl
|
|
224
|
+
|
|
225
|
+
column = tenant.tenant_column or self._tenant_column
|
|
226
|
+
return df.with_columns(pl.lit(tenant.id).alias(column))
|
|
227
|
+
|
|
228
|
+
def get_namespace(self, tenant: Tenant) -> str:
|
|
229
|
+
"""Get the namespace for a tenant."""
|
|
230
|
+
return "shared"
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
# =============================================================================
|
|
234
|
+
# Schema-Based Isolation
|
|
235
|
+
# =============================================================================
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
@dataclass
|
|
239
|
+
class SchemaConfig:
|
|
240
|
+
"""Configuration for schema-based isolation."""
|
|
241
|
+
|
|
242
|
+
schema_prefix: str = "tenant_"
|
|
243
|
+
create_schema_on_demand: bool = True
|
|
244
|
+
default_schema: str = "public"
|
|
245
|
+
schema_template: str = "{prefix}{tenant_slug}"
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
class SchemaIsolation(IsolationStrategy):
|
|
249
|
+
"""Schema-based isolation where each tenant has its own schema.
|
|
250
|
+
|
|
251
|
+
Each tenant's data lives in a separate database schema, providing
|
|
252
|
+
stronger isolation than row-level filtering.
|
|
253
|
+
|
|
254
|
+
Pros:
|
|
255
|
+
- Stronger isolation than row-level
|
|
256
|
+
- Easier to backup/restore individual tenants
|
|
257
|
+
- Clear data boundaries
|
|
258
|
+
- Can have tenant-specific schema customizations
|
|
259
|
+
|
|
260
|
+
Cons:
|
|
261
|
+
- More schemas to manage
|
|
262
|
+
- Cross-tenant queries are harder
|
|
263
|
+
- Schema migration complexity
|
|
264
|
+
|
|
265
|
+
Example:
|
|
266
|
+
>>> strategy = SchemaIsolation(
|
|
267
|
+
... config=SchemaConfig(schema_prefix="t_"),
|
|
268
|
+
... )
|
|
269
|
+
>>> namespace = strategy.get_namespace(tenant)
|
|
270
|
+
>>> # Returns: "t_acme_corp"
|
|
271
|
+
"""
|
|
272
|
+
|
|
273
|
+
def __init__(
|
|
274
|
+
self,
|
|
275
|
+
config: SchemaConfig | None = None,
|
|
276
|
+
) -> None:
|
|
277
|
+
self._config = config or SchemaConfig()
|
|
278
|
+
|
|
279
|
+
@property
|
|
280
|
+
def isolation_level(self) -> IsolationLevel:
|
|
281
|
+
"""The isolation level this strategy provides."""
|
|
282
|
+
return IsolationLevel.SCHEMA
|
|
283
|
+
|
|
284
|
+
def get_namespace(self, tenant: Tenant) -> str:
|
|
285
|
+
"""Get the schema name for a tenant."""
|
|
286
|
+
if tenant.schema_name:
|
|
287
|
+
return tenant.schema_name
|
|
288
|
+
|
|
289
|
+
return self._config.schema_template.format(
|
|
290
|
+
prefix=self._config.schema_prefix,
|
|
291
|
+
tenant_slug=tenant.slug,
|
|
292
|
+
tenant_id=tenant.id,
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
def apply_filter(
|
|
296
|
+
self,
|
|
297
|
+
df: "pl.LazyFrame",
|
|
298
|
+
tenant: Tenant,
|
|
299
|
+
) -> "pl.LazyFrame":
|
|
300
|
+
"""Apply tenant filter.
|
|
301
|
+
|
|
302
|
+
For schema isolation, the data should already be from the
|
|
303
|
+
tenant's schema. This method validates that assumption.
|
|
304
|
+
"""
|
|
305
|
+
if self._validate_on_apply:
|
|
306
|
+
self.validate_isolation(tenant)
|
|
307
|
+
|
|
308
|
+
# In schema isolation, no additional filtering needed
|
|
309
|
+
# The data source should be configured to use the correct schema
|
|
310
|
+
return df
|
|
311
|
+
|
|
312
|
+
def _validate_on_apply(self, tenant: Tenant) -> None:
|
|
313
|
+
"""Validate the tenant context."""
|
|
314
|
+
self.validate_isolation(tenant)
|
|
315
|
+
|
|
316
|
+
def add_tenant_column(
|
|
317
|
+
self,
|
|
318
|
+
df: "pl.LazyFrame",
|
|
319
|
+
tenant: Tenant,
|
|
320
|
+
) -> "pl.LazyFrame":
|
|
321
|
+
"""Add tenant identifier (optional in schema isolation)."""
|
|
322
|
+
import polars as pl
|
|
323
|
+
|
|
324
|
+
# In schema isolation, tenant column is optional but can be added
|
|
325
|
+
# for audit/logging purposes
|
|
326
|
+
column = tenant.tenant_column or "tenant_id"
|
|
327
|
+
return df.with_columns(pl.lit(tenant.id).alias(column))
|
|
328
|
+
|
|
329
|
+
def get_qualified_table_name(
|
|
330
|
+
self,
|
|
331
|
+
table_name: str,
|
|
332
|
+
tenant: Tenant,
|
|
333
|
+
) -> str:
|
|
334
|
+
"""Get the fully qualified table name for a tenant."""
|
|
335
|
+
schema = self.get_namespace(tenant)
|
|
336
|
+
return f"{schema}.{table_name}"
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
# =============================================================================
|
|
340
|
+
# Database-Based Isolation
|
|
341
|
+
# =============================================================================
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
@dataclass
|
|
345
|
+
class DatabaseConfig:
|
|
346
|
+
"""Configuration for database-based isolation."""
|
|
347
|
+
|
|
348
|
+
database_prefix: str = "truthound_"
|
|
349
|
+
create_database_on_demand: bool = False # Usually requires admin
|
|
350
|
+
database_template: str = "{prefix}{tenant_slug}"
|
|
351
|
+
connection_pool_per_tenant: bool = True
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
class DatabaseIsolation(IsolationStrategy):
|
|
355
|
+
"""Database-based isolation where each tenant has its own database.
|
|
356
|
+
|
|
357
|
+
Each tenant's data lives in a completely separate database,
|
|
358
|
+
providing the strongest isolation short of separate instances.
|
|
359
|
+
|
|
360
|
+
Pros:
|
|
361
|
+
- Strongest isolation within shared infrastructure
|
|
362
|
+
- Complete data separation
|
|
363
|
+
- Independent backup/restore
|
|
364
|
+
- Independent scaling possible
|
|
365
|
+
|
|
366
|
+
Cons:
|
|
367
|
+
- Highest operational overhead
|
|
368
|
+
- Connection pool management complexity
|
|
369
|
+
- Cross-tenant analytics very difficult
|
|
370
|
+
|
|
371
|
+
Example:
|
|
372
|
+
>>> strategy = DatabaseIsolation(
|
|
373
|
+
... config=DatabaseConfig(database_prefix="th_"),
|
|
374
|
+
... )
|
|
375
|
+
>>> db_name = strategy.get_namespace(tenant)
|
|
376
|
+
>>> # Returns: "th_acme_corp"
|
|
377
|
+
"""
|
|
378
|
+
|
|
379
|
+
def __init__(
|
|
380
|
+
self,
|
|
381
|
+
config: DatabaseConfig | None = None,
|
|
382
|
+
) -> None:
|
|
383
|
+
self._config = config or DatabaseConfig()
|
|
384
|
+
self._connection_registry: dict[str, Any] = {}
|
|
385
|
+
|
|
386
|
+
@property
|
|
387
|
+
def isolation_level(self) -> IsolationLevel:
|
|
388
|
+
"""The isolation level this strategy provides."""
|
|
389
|
+
return IsolationLevel.DATABASE
|
|
390
|
+
|
|
391
|
+
def get_namespace(self, tenant: Tenant) -> str:
|
|
392
|
+
"""Get the database name for a tenant."""
|
|
393
|
+
if tenant.database_name:
|
|
394
|
+
return tenant.database_name
|
|
395
|
+
|
|
396
|
+
return self._config.database_template.format(
|
|
397
|
+
prefix=self._config.database_prefix,
|
|
398
|
+
tenant_slug=tenant.slug,
|
|
399
|
+
tenant_id=tenant.id,
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
def apply_filter(
|
|
403
|
+
self,
|
|
404
|
+
df: "pl.LazyFrame",
|
|
405
|
+
tenant: Tenant,
|
|
406
|
+
) -> "pl.LazyFrame":
|
|
407
|
+
"""Apply tenant filter.
|
|
408
|
+
|
|
409
|
+
For database isolation, the data should already be from the
|
|
410
|
+
tenant's database. No additional filtering needed.
|
|
411
|
+
"""
|
|
412
|
+
self.validate_isolation(tenant)
|
|
413
|
+
return df
|
|
414
|
+
|
|
415
|
+
def add_tenant_column(
|
|
416
|
+
self,
|
|
417
|
+
df: "pl.LazyFrame",
|
|
418
|
+
tenant: Tenant,
|
|
419
|
+
) -> "pl.LazyFrame":
|
|
420
|
+
"""Add tenant identifier (optional in database isolation)."""
|
|
421
|
+
import polars as pl
|
|
422
|
+
|
|
423
|
+
column = tenant.tenant_column or "tenant_id"
|
|
424
|
+
return df.with_columns(pl.lit(tenant.id).alias(column))
|
|
425
|
+
|
|
426
|
+
def get_connection_string(
|
|
427
|
+
self,
|
|
428
|
+
base_connection: str,
|
|
429
|
+
tenant: Tenant,
|
|
430
|
+
) -> str:
|
|
431
|
+
"""Get the connection string for a tenant's database.
|
|
432
|
+
|
|
433
|
+
This is a simple implementation that replaces the database
|
|
434
|
+
name in the connection string. Production implementations
|
|
435
|
+
should use proper connection string parsing.
|
|
436
|
+
"""
|
|
437
|
+
db_name = self.get_namespace(tenant)
|
|
438
|
+
# Simple replacement - production should use proper parsing
|
|
439
|
+
# e.g., postgresql://user:pass@host/db -> postgresql://user:pass@host/tenant_db
|
|
440
|
+
if "//" in base_connection and "/" in base_connection.split("//")[1]:
|
|
441
|
+
parts = base_connection.rsplit("/", 1)
|
|
442
|
+
return f"{parts[0]}/{db_name}"
|
|
443
|
+
return base_connection
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
# =============================================================================
|
|
447
|
+
# Composite Isolation (Multiple Strategies)
|
|
448
|
+
# =============================================================================
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
class CompositeIsolation(IsolationStrategy):
|
|
452
|
+
"""Composite isolation that applies multiple strategies.
|
|
453
|
+
|
|
454
|
+
Useful for defense-in-depth where you want both row-level
|
|
455
|
+
filtering AND schema isolation checks.
|
|
456
|
+
|
|
457
|
+
Example:
|
|
458
|
+
>>> composite = CompositeIsolation([
|
|
459
|
+
... RowLevelIsolation(tenant_column="tenant_id"),
|
|
460
|
+
... SchemaIsolation(config=SchemaConfig()),
|
|
461
|
+
... ])
|
|
462
|
+
>>> filtered_df = composite.apply_filter(df, tenant)
|
|
463
|
+
"""
|
|
464
|
+
|
|
465
|
+
def __init__(
|
|
466
|
+
self,
|
|
467
|
+
strategies: list[IsolationStrategy],
|
|
468
|
+
primary_isolation: IsolationLevel = IsolationLevel.ROW_LEVEL,
|
|
469
|
+
) -> None:
|
|
470
|
+
if not strategies:
|
|
471
|
+
raise ValueError("At least one strategy is required")
|
|
472
|
+
self._strategies = strategies
|
|
473
|
+
self._primary_isolation = primary_isolation
|
|
474
|
+
|
|
475
|
+
@property
|
|
476
|
+
def isolation_level(self) -> IsolationLevel:
|
|
477
|
+
"""The highest isolation level among strategies."""
|
|
478
|
+
return self._primary_isolation
|
|
479
|
+
|
|
480
|
+
def apply_filter(
|
|
481
|
+
self,
|
|
482
|
+
df: "pl.LazyFrame",
|
|
483
|
+
tenant: Tenant,
|
|
484
|
+
) -> "pl.LazyFrame":
|
|
485
|
+
"""Apply all isolation strategies."""
|
|
486
|
+
for strategy in self._strategies:
|
|
487
|
+
df = strategy.apply_filter(df, tenant)
|
|
488
|
+
return df
|
|
489
|
+
|
|
490
|
+
def add_tenant_column(
|
|
491
|
+
self,
|
|
492
|
+
df: "pl.LazyFrame",
|
|
493
|
+
tenant: Tenant,
|
|
494
|
+
) -> "pl.LazyFrame":
|
|
495
|
+
"""Add tenant column using primary strategy."""
|
|
496
|
+
return self._strategies[0].add_tenant_column(df, tenant)
|
|
497
|
+
|
|
498
|
+
def get_namespace(self, tenant: Tenant) -> str:
|
|
499
|
+
"""Get namespace from the most isolated strategy."""
|
|
500
|
+
# Find strategy with highest isolation level
|
|
501
|
+
for level in [
|
|
502
|
+
IsolationLevel.DATABASE,
|
|
503
|
+
IsolationLevel.SCHEMA,
|
|
504
|
+
IsolationLevel.ROW_LEVEL,
|
|
505
|
+
IsolationLevel.SHARED,
|
|
506
|
+
]:
|
|
507
|
+
for strategy in self._strategies:
|
|
508
|
+
if strategy.isolation_level == level:
|
|
509
|
+
return strategy.get_namespace(tenant)
|
|
510
|
+
return self._strategies[0].get_namespace(tenant)
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
# =============================================================================
|
|
514
|
+
# Isolation Manager
|
|
515
|
+
# =============================================================================
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
class IsolationManager:
|
|
519
|
+
"""Manages isolation strategies for the multi-tenant system.
|
|
520
|
+
|
|
521
|
+
Provides a central point for configuring and applying isolation
|
|
522
|
+
strategies based on tenant configuration.
|
|
523
|
+
|
|
524
|
+
Example:
|
|
525
|
+
>>> manager = IsolationManager()
|
|
526
|
+
>>> manager.register(IsolationLevel.ROW_LEVEL, RowLevelIsolation())
|
|
527
|
+
>>> manager.register(IsolationLevel.SCHEMA, SchemaIsolation())
|
|
528
|
+
>>>
|
|
529
|
+
>>> # Apply isolation based on tenant's configured level
|
|
530
|
+
>>> filtered_df = manager.apply_filter(df, tenant)
|
|
531
|
+
"""
|
|
532
|
+
|
|
533
|
+
def __init__(self) -> None:
|
|
534
|
+
self._strategies: dict[IsolationLevel, IsolationStrategy] = {}
|
|
535
|
+
self._default_level = IsolationLevel.ROW_LEVEL
|
|
536
|
+
|
|
537
|
+
# Register default strategies
|
|
538
|
+
self._strategies[IsolationLevel.SHARED] = SharedIsolation()
|
|
539
|
+
self._strategies[IsolationLevel.ROW_LEVEL] = RowLevelIsolation()
|
|
540
|
+
self._strategies[IsolationLevel.SCHEMA] = SchemaIsolation()
|
|
541
|
+
self._strategies[IsolationLevel.DATABASE] = DatabaseIsolation()
|
|
542
|
+
|
|
543
|
+
def register(
|
|
544
|
+
self,
|
|
545
|
+
level: IsolationLevel,
|
|
546
|
+
strategy: IsolationStrategy,
|
|
547
|
+
) -> None:
|
|
548
|
+
"""Register an isolation strategy for a level."""
|
|
549
|
+
self._strategies[level] = strategy
|
|
550
|
+
|
|
551
|
+
def get_strategy(self, tenant: Tenant) -> IsolationStrategy:
|
|
552
|
+
"""Get the isolation strategy for a tenant."""
|
|
553
|
+
level = tenant.isolation_level
|
|
554
|
+
if level not in self._strategies:
|
|
555
|
+
level = self._default_level
|
|
556
|
+
return self._strategies[level]
|
|
557
|
+
|
|
558
|
+
def apply_filter(
|
|
559
|
+
self,
|
|
560
|
+
df: "pl.LazyFrame",
|
|
561
|
+
tenant: Tenant,
|
|
562
|
+
) -> "pl.LazyFrame":
|
|
563
|
+
"""Apply the appropriate isolation filter for a tenant."""
|
|
564
|
+
strategy = self.get_strategy(tenant)
|
|
565
|
+
return strategy.apply_filter(df, tenant)
|
|
566
|
+
|
|
567
|
+
def add_tenant_column(
|
|
568
|
+
self,
|
|
569
|
+
df: "pl.LazyFrame",
|
|
570
|
+
tenant: Tenant,
|
|
571
|
+
) -> "pl.LazyFrame":
|
|
572
|
+
"""Add tenant column using appropriate strategy."""
|
|
573
|
+
strategy = self.get_strategy(tenant)
|
|
574
|
+
return strategy.add_tenant_column(df, tenant)
|
|
575
|
+
|
|
576
|
+
def get_namespace(self, tenant: Tenant) -> str:
|
|
577
|
+
"""Get the namespace for a tenant."""
|
|
578
|
+
strategy = self.get_strategy(tenant)
|
|
579
|
+
return strategy.get_namespace(tenant)
|
|
580
|
+
|
|
581
|
+
def set_default_level(self, level: IsolationLevel) -> None:
|
|
582
|
+
"""Set the default isolation level."""
|
|
583
|
+
self._default_level = level
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
# =============================================================================
|
|
587
|
+
# Factory Function
|
|
588
|
+
# =============================================================================
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
def create_isolation_strategy(
|
|
592
|
+
level: IsolationLevel,
|
|
593
|
+
**kwargs: Any,
|
|
594
|
+
) -> IsolationStrategy:
|
|
595
|
+
"""Create an isolation strategy.
|
|
596
|
+
|
|
597
|
+
Args:
|
|
598
|
+
level: Isolation level
|
|
599
|
+
**kwargs: Strategy-specific configuration
|
|
600
|
+
|
|
601
|
+
Returns:
|
|
602
|
+
Configured IsolationStrategy instance.
|
|
603
|
+
|
|
604
|
+
Example:
|
|
605
|
+
>>> strategy = create_isolation_strategy(
|
|
606
|
+
... IsolationLevel.SCHEMA,
|
|
607
|
+
... schema_prefix="t_",
|
|
608
|
+
... )
|
|
609
|
+
"""
|
|
610
|
+
if level == IsolationLevel.SHARED:
|
|
611
|
+
return SharedIsolation(
|
|
612
|
+
tenant_column=kwargs.get("tenant_column", "tenant_id"),
|
|
613
|
+
)
|
|
614
|
+
elif level == IsolationLevel.ROW_LEVEL:
|
|
615
|
+
return RowLevelIsolation(
|
|
616
|
+
tenant_column=kwargs.get("tenant_column", "tenant_id"),
|
|
617
|
+
fail_closed=kwargs.get("fail_closed", True),
|
|
618
|
+
)
|
|
619
|
+
elif level == IsolationLevel.SCHEMA:
|
|
620
|
+
config = SchemaConfig(
|
|
621
|
+
schema_prefix=kwargs.get("schema_prefix", "tenant_"),
|
|
622
|
+
)
|
|
623
|
+
return SchemaIsolation(config=config)
|
|
624
|
+
elif level == IsolationLevel.DATABASE:
|
|
625
|
+
config = DatabaseConfig(
|
|
626
|
+
database_prefix=kwargs.get("database_prefix", "truthound_"),
|
|
627
|
+
)
|
|
628
|
+
return DatabaseIsolation(config=config)
|
|
629
|
+
else:
|
|
630
|
+
raise ValueError(f"Unsupported isolation level: {level}")
|