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,760 @@
|
|
|
1
|
+
"""Integration with multi-tenancy and Truthound core functionality.
|
|
2
|
+
|
|
3
|
+
This module provides integration points between the RBAC system
|
|
4
|
+
and Truthound's multi-tenancy, validation, checkpoint, and other features.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import functools
|
|
10
|
+
from contextlib import contextmanager
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from datetime import datetime, timezone
|
|
13
|
+
from typing import TYPE_CHECKING, Any, Callable, Iterator, TypeVar
|
|
14
|
+
|
|
15
|
+
from truthound.rbac.core import (
|
|
16
|
+
AccessContext,
|
|
17
|
+
AccessDecision,
|
|
18
|
+
Permission,
|
|
19
|
+
PermissionAction,
|
|
20
|
+
PermissionDeniedError,
|
|
21
|
+
PermissionEffect,
|
|
22
|
+
Principal,
|
|
23
|
+
PrincipalType,
|
|
24
|
+
ResourceType,
|
|
25
|
+
Role,
|
|
26
|
+
RoleType,
|
|
27
|
+
SecurityContext,
|
|
28
|
+
)
|
|
29
|
+
from truthound.rbac.policy import PolicyEngine
|
|
30
|
+
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from truthound.multitenancy.core import Tenant
|
|
33
|
+
from truthound.multitenancy.manager import TenantManager
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# =============================================================================
|
|
40
|
+
# Tenant-Aware RBAC Configuration
|
|
41
|
+
# =============================================================================
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class TenantRBACConfig:
|
|
46
|
+
"""Configuration for tenant-aware RBAC."""
|
|
47
|
+
|
|
48
|
+
# Tenant isolation
|
|
49
|
+
isolate_roles_by_tenant: bool = True
|
|
50
|
+
isolate_principals_by_tenant: bool = True
|
|
51
|
+
|
|
52
|
+
# Cross-tenant access
|
|
53
|
+
allow_cross_tenant_superuser: bool = True
|
|
54
|
+
cross_tenant_roles: set[str] = field(
|
|
55
|
+
default_factory=lambda: {"system_admin", "platform_admin"}
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Role inheritance
|
|
59
|
+
inherit_platform_roles: bool = True
|
|
60
|
+
platform_roles: set[str] = field(
|
|
61
|
+
default_factory=lambda: {"viewer", "editor", "admin"}
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Automatic role assignment
|
|
65
|
+
auto_assign_tenant_member: bool = True
|
|
66
|
+
default_tenant_role: str = "tenant_member"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# =============================================================================
|
|
70
|
+
# Tenant-Aware Policy Evaluator
|
|
71
|
+
# =============================================================================
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class TenantAwarePolicyEvaluator:
|
|
75
|
+
"""Evaluator that considers tenant context in access decisions.
|
|
76
|
+
|
|
77
|
+
This evaluator ensures that principals can only access resources
|
|
78
|
+
within their tenant unless they have cross-tenant permissions.
|
|
79
|
+
|
|
80
|
+
Example:
|
|
81
|
+
>>> evaluator = TenantAwarePolicyEvaluator(config)
|
|
82
|
+
>>> engine.add_evaluator(evaluator)
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
def __init__(
|
|
86
|
+
self,
|
|
87
|
+
config: TenantRBACConfig | None = None,
|
|
88
|
+
tenant_manager: "TenantManager | None" = None,
|
|
89
|
+
) -> None:
|
|
90
|
+
self._config = config or TenantRBACConfig()
|
|
91
|
+
self._tenant_manager = tenant_manager
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def name(self) -> str:
|
|
95
|
+
return "tenant_isolation"
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def priority(self) -> int:
|
|
99
|
+
return 1000 # High priority - checked early
|
|
100
|
+
|
|
101
|
+
def evaluate(self, context: AccessContext) -> AccessDecision:
|
|
102
|
+
"""Evaluate tenant isolation rules."""
|
|
103
|
+
if not context.principal:
|
|
104
|
+
return AccessDecision.deny("No principal in context")
|
|
105
|
+
|
|
106
|
+
principal_tenant = context.principal.tenant_id
|
|
107
|
+
resource_tenant = context.resource_attributes.get("tenant_id")
|
|
108
|
+
|
|
109
|
+
# If no tenant context, skip this evaluator
|
|
110
|
+
if not principal_tenant and not resource_tenant:
|
|
111
|
+
return AccessDecision(
|
|
112
|
+
allowed=True,
|
|
113
|
+
reason="No tenant context - skipping tenant isolation",
|
|
114
|
+
effect=PermissionEffect.ALLOW,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# Check for cross-tenant access
|
|
118
|
+
if principal_tenant and resource_tenant:
|
|
119
|
+
if principal_tenant != resource_tenant:
|
|
120
|
+
# Check if principal has cross-tenant role
|
|
121
|
+
if self._has_cross_tenant_access(context.principal):
|
|
122
|
+
return AccessDecision.allow(
|
|
123
|
+
f"Cross-tenant access granted via role"
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
return AccessDecision.deny(
|
|
127
|
+
f"Tenant isolation: principal tenant {principal_tenant} "
|
|
128
|
+
f"cannot access resource in tenant {resource_tenant}"
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
return AccessDecision(
|
|
132
|
+
allowed=True,
|
|
133
|
+
reason="Tenant isolation check passed",
|
|
134
|
+
effect=PermissionEffect.ALLOW,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
def _has_cross_tenant_access(self, principal: Principal) -> bool:
|
|
138
|
+
"""Check if principal has cross-tenant access roles."""
|
|
139
|
+
if not self._config.allow_cross_tenant_superuser:
|
|
140
|
+
return False
|
|
141
|
+
|
|
142
|
+
return bool(principal.roles.intersection(self._config.cross_tenant_roles))
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# =============================================================================
|
|
146
|
+
# Tenant-Scoped Role Management
|
|
147
|
+
# =============================================================================
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class TenantScopedRoleManager:
|
|
151
|
+
"""Manages roles within a tenant context.
|
|
152
|
+
|
|
153
|
+
Ensures that roles are properly scoped to tenants and handles
|
|
154
|
+
role inheritance from platform to tenant level.
|
|
155
|
+
|
|
156
|
+
Example:
|
|
157
|
+
>>> manager = TenantScopedRoleManager(role_store, config)
|
|
158
|
+
>>> role = manager.create_tenant_role(
|
|
159
|
+
... tenant_id="tenant_123",
|
|
160
|
+
... name="Data Analyst",
|
|
161
|
+
... permissions={"dataset:read", "validation:execute"},
|
|
162
|
+
... )
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
def __init__(
|
|
166
|
+
self,
|
|
167
|
+
role_store: Any,
|
|
168
|
+
config: TenantRBACConfig | None = None,
|
|
169
|
+
) -> None:
|
|
170
|
+
from truthound.rbac.storage import MemoryRoleStore
|
|
171
|
+
|
|
172
|
+
self._role_store = role_store or MemoryRoleStore()
|
|
173
|
+
self._config = config or TenantRBACConfig()
|
|
174
|
+
|
|
175
|
+
def create_tenant_role(
|
|
176
|
+
self,
|
|
177
|
+
tenant_id: str,
|
|
178
|
+
name: str,
|
|
179
|
+
permissions: set[str] | None = None,
|
|
180
|
+
parent_roles: set[str] | None = None,
|
|
181
|
+
description: str = "",
|
|
182
|
+
) -> Role:
|
|
183
|
+
"""Create a role scoped to a tenant.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
tenant_id: Tenant ID
|
|
187
|
+
name: Role name
|
|
188
|
+
permissions: Permission strings
|
|
189
|
+
parent_roles: Parent role IDs
|
|
190
|
+
description: Role description
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
Created Role object.
|
|
194
|
+
"""
|
|
195
|
+
from truthound.rbac.core import generate_role_id
|
|
196
|
+
|
|
197
|
+
role_id = f"{tenant_id}:{generate_role_id(name)}"
|
|
198
|
+
|
|
199
|
+
role = Role(
|
|
200
|
+
id=role_id,
|
|
201
|
+
name=name,
|
|
202
|
+
description=description,
|
|
203
|
+
role_type=RoleType.CUSTOM,
|
|
204
|
+
permissions={Permission.parse(p) for p in (permissions or set())},
|
|
205
|
+
parent_roles=parent_roles or set(),
|
|
206
|
+
tenant_id=tenant_id,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
self._role_store.save(role)
|
|
210
|
+
return role
|
|
211
|
+
|
|
212
|
+
def get_tenant_roles(self, tenant_id: str) -> list[Role]:
|
|
213
|
+
"""Get all roles for a tenant."""
|
|
214
|
+
return self._role_store.list(tenant_id=tenant_id)
|
|
215
|
+
|
|
216
|
+
def get_inherited_roles(self, tenant_id: str) -> list[Role]:
|
|
217
|
+
"""Get platform roles that a tenant inherits."""
|
|
218
|
+
if not self._config.inherit_platform_roles:
|
|
219
|
+
return []
|
|
220
|
+
|
|
221
|
+
all_roles = []
|
|
222
|
+
for role_id in self._config.platform_roles:
|
|
223
|
+
role = self._role_store.get(role_id)
|
|
224
|
+
if role and role.tenant_id is None:
|
|
225
|
+
all_roles.append(role)
|
|
226
|
+
|
|
227
|
+
return all_roles
|
|
228
|
+
|
|
229
|
+
def assign_default_role(
|
|
230
|
+
self,
|
|
231
|
+
principal: Principal,
|
|
232
|
+
tenant_id: str,
|
|
233
|
+
) -> Principal:
|
|
234
|
+
"""Assign the default tenant role to a principal.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
principal: Principal to update
|
|
238
|
+
tenant_id: Tenant ID
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
Updated Principal.
|
|
242
|
+
"""
|
|
243
|
+
if not self._config.auto_assign_tenant_member:
|
|
244
|
+
return principal
|
|
245
|
+
|
|
246
|
+
default_role_id = f"{tenant_id}:{self._config.default_tenant_role}"
|
|
247
|
+
|
|
248
|
+
# Check if role exists, create if not
|
|
249
|
+
if not self._role_store.exists(default_role_id):
|
|
250
|
+
self.create_tenant_role(
|
|
251
|
+
tenant_id=tenant_id,
|
|
252
|
+
name="Tenant Member",
|
|
253
|
+
permissions={"dataset:read", "validation:read"},
|
|
254
|
+
description="Default role for tenant members",
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
principal.add_role(default_role_id)
|
|
258
|
+
return principal
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
# =============================================================================
|
|
262
|
+
# Tenant-Scoped Principal Management
|
|
263
|
+
# =============================================================================
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
class TenantScopedPrincipalManager:
|
|
267
|
+
"""Manages principals within a tenant context.
|
|
268
|
+
|
|
269
|
+
Example:
|
|
270
|
+
>>> manager = TenantScopedPrincipalManager(principal_store)
|
|
271
|
+
>>> principal = manager.create_tenant_principal(
|
|
272
|
+
... tenant_id="tenant_123",
|
|
273
|
+
... name="john.doe@example.com",
|
|
274
|
+
... )
|
|
275
|
+
"""
|
|
276
|
+
|
|
277
|
+
def __init__(
|
|
278
|
+
self,
|
|
279
|
+
principal_store: Any,
|
|
280
|
+
role_manager: TenantScopedRoleManager | None = None,
|
|
281
|
+
config: TenantRBACConfig | None = None,
|
|
282
|
+
) -> None:
|
|
283
|
+
from truthound.rbac.storage import MemoryPrincipalStore
|
|
284
|
+
|
|
285
|
+
self._principal_store = principal_store or MemoryPrincipalStore()
|
|
286
|
+
self._role_manager = role_manager
|
|
287
|
+
self._config = config or TenantRBACConfig()
|
|
288
|
+
|
|
289
|
+
def create_tenant_principal(
|
|
290
|
+
self,
|
|
291
|
+
tenant_id: str,
|
|
292
|
+
name: str,
|
|
293
|
+
email: str = "",
|
|
294
|
+
principal_type: PrincipalType = PrincipalType.USER,
|
|
295
|
+
roles: set[str] | None = None,
|
|
296
|
+
attributes: dict[str, Any] | None = None,
|
|
297
|
+
) -> Principal:
|
|
298
|
+
"""Create a principal scoped to a tenant.
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
tenant_id: Tenant ID
|
|
302
|
+
name: Principal name
|
|
303
|
+
email: Principal email
|
|
304
|
+
principal_type: Type of principal
|
|
305
|
+
roles: Initial roles
|
|
306
|
+
attributes: ABAC attributes
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
Created Principal object.
|
|
310
|
+
"""
|
|
311
|
+
from truthound.rbac.core import generate_principal_id
|
|
312
|
+
|
|
313
|
+
principal_id = generate_principal_id(f"{tenant_id}_user")
|
|
314
|
+
|
|
315
|
+
principal = Principal(
|
|
316
|
+
id=principal_id,
|
|
317
|
+
type=principal_type,
|
|
318
|
+
name=name,
|
|
319
|
+
email=email,
|
|
320
|
+
roles=roles or set(),
|
|
321
|
+
attributes=attributes or {},
|
|
322
|
+
tenant_id=tenant_id,
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
# Assign default tenant role
|
|
326
|
+
if self._role_manager and self._config.auto_assign_tenant_member:
|
|
327
|
+
principal = self._role_manager.assign_default_role(principal, tenant_id)
|
|
328
|
+
|
|
329
|
+
self._principal_store.save(principal)
|
|
330
|
+
return principal
|
|
331
|
+
|
|
332
|
+
def get_tenant_principals(self, tenant_id: str) -> list[Principal]:
|
|
333
|
+
"""Get all principals for a tenant."""
|
|
334
|
+
return self._principal_store.list(tenant_id=tenant_id)
|
|
335
|
+
|
|
336
|
+
def is_tenant_member(self, principal: Principal, tenant_id: str) -> bool:
|
|
337
|
+
"""Check if principal is a member of a tenant."""
|
|
338
|
+
if principal.tenant_id == tenant_id:
|
|
339
|
+
return True
|
|
340
|
+
|
|
341
|
+
# Check for cross-tenant access
|
|
342
|
+
if self._config.allow_cross_tenant_superuser:
|
|
343
|
+
return bool(principal.roles.intersection(self._config.cross_tenant_roles))
|
|
344
|
+
|
|
345
|
+
return False
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
# =============================================================================
|
|
349
|
+
# Integration with Truthound Multitenancy Module
|
|
350
|
+
# =============================================================================
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
class MultitenancyRBACIntegration:
|
|
354
|
+
"""Integration between RBAC and multitenancy modules.
|
|
355
|
+
|
|
356
|
+
Provides utilities for creating principals from tenants,
|
|
357
|
+
syncing tenant membership, and enforcing tenant-scoped access.
|
|
358
|
+
|
|
359
|
+
Example:
|
|
360
|
+
>>> integration = MultitenancyRBACIntegration(
|
|
361
|
+
... rbac_manager=rbac_manager,
|
|
362
|
+
... tenant_manager=tenant_manager,
|
|
363
|
+
... )
|
|
364
|
+
>>> principal = integration.get_or_create_tenant_admin(tenant)
|
|
365
|
+
"""
|
|
366
|
+
|
|
367
|
+
def __init__(
|
|
368
|
+
self,
|
|
369
|
+
rbac_manager: "RBACManager | None" = None,
|
|
370
|
+
tenant_manager: "TenantManager | None" = None,
|
|
371
|
+
config: TenantRBACConfig | None = None,
|
|
372
|
+
) -> None:
|
|
373
|
+
self._rbac_manager = rbac_manager
|
|
374
|
+
self._tenant_manager = tenant_manager
|
|
375
|
+
self._config = config or TenantRBACConfig()
|
|
376
|
+
|
|
377
|
+
def get_or_create_tenant_admin(
|
|
378
|
+
self,
|
|
379
|
+
tenant: "Tenant",
|
|
380
|
+
) -> Principal:
|
|
381
|
+
"""Get or create the admin principal for a tenant.
|
|
382
|
+
|
|
383
|
+
Args:
|
|
384
|
+
tenant: Tenant object
|
|
385
|
+
|
|
386
|
+
Returns:
|
|
387
|
+
Principal for tenant admin.
|
|
388
|
+
"""
|
|
389
|
+
if not self._rbac_manager:
|
|
390
|
+
raise RuntimeError("RBAC manager not configured")
|
|
391
|
+
|
|
392
|
+
admin_id = f"tenant_admin:{tenant.id}"
|
|
393
|
+
principal = self._rbac_manager.principal_store.get(admin_id)
|
|
394
|
+
|
|
395
|
+
if principal:
|
|
396
|
+
return principal
|
|
397
|
+
|
|
398
|
+
# Create admin principal
|
|
399
|
+
principal = Principal(
|
|
400
|
+
id=admin_id,
|
|
401
|
+
type=PrincipalType.USER,
|
|
402
|
+
name=f"Admin for {tenant.name}",
|
|
403
|
+
email=tenant.owner_email,
|
|
404
|
+
roles={f"{tenant.id}:admin", "tenant_admin"},
|
|
405
|
+
tenant_id=tenant.id,
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
self._rbac_manager.principal_store.save(principal)
|
|
409
|
+
return principal
|
|
410
|
+
|
|
411
|
+
def sync_tenant_membership(
|
|
412
|
+
self,
|
|
413
|
+
principal: Principal,
|
|
414
|
+
tenant: "Tenant",
|
|
415
|
+
) -> Principal:
|
|
416
|
+
"""Sync principal membership with tenant.
|
|
417
|
+
|
|
418
|
+
Ensures the principal has appropriate roles based on their
|
|
419
|
+
relationship to the tenant.
|
|
420
|
+
|
|
421
|
+
Args:
|
|
422
|
+
principal: Principal to sync
|
|
423
|
+
tenant: Tenant to sync with
|
|
424
|
+
|
|
425
|
+
Returns:
|
|
426
|
+
Updated Principal.
|
|
427
|
+
"""
|
|
428
|
+
if principal.tenant_id != tenant.id:
|
|
429
|
+
return principal
|
|
430
|
+
|
|
431
|
+
# Ensure basic tenant membership role
|
|
432
|
+
member_role = f"{tenant.id}:member"
|
|
433
|
+
if member_role not in principal.roles:
|
|
434
|
+
principal.add_role(member_role)
|
|
435
|
+
|
|
436
|
+
# Check if principal is tenant owner
|
|
437
|
+
if principal.email and principal.email == tenant.owner_email:
|
|
438
|
+
owner_role = f"{tenant.id}:owner"
|
|
439
|
+
if owner_role not in principal.roles:
|
|
440
|
+
principal.add_role(owner_role)
|
|
441
|
+
|
|
442
|
+
return principal
|
|
443
|
+
|
|
444
|
+
def create_tenant_roles(self, tenant: "Tenant") -> list[Role]:
|
|
445
|
+
"""Create default roles for a new tenant.
|
|
446
|
+
|
|
447
|
+
Args:
|
|
448
|
+
tenant: Tenant to create roles for
|
|
449
|
+
|
|
450
|
+
Returns:
|
|
451
|
+
List of created roles.
|
|
452
|
+
"""
|
|
453
|
+
if not self._rbac_manager:
|
|
454
|
+
raise RuntimeError("RBAC manager not configured")
|
|
455
|
+
|
|
456
|
+
roles = []
|
|
457
|
+
|
|
458
|
+
# Owner role - full access within tenant
|
|
459
|
+
owner_role = Role(
|
|
460
|
+
id=f"{tenant.id}:owner",
|
|
461
|
+
name=f"{tenant.name} Owner",
|
|
462
|
+
description="Full access to all tenant resources",
|
|
463
|
+
role_type=RoleType.SYSTEM,
|
|
464
|
+
permissions={
|
|
465
|
+
Permission("*", "*", scope="tenant"),
|
|
466
|
+
},
|
|
467
|
+
tenant_id=tenant.id,
|
|
468
|
+
)
|
|
469
|
+
self._rbac_manager.role_store.save(owner_role)
|
|
470
|
+
roles.append(owner_role)
|
|
471
|
+
|
|
472
|
+
# Admin role
|
|
473
|
+
admin_role = Role(
|
|
474
|
+
id=f"{tenant.id}:admin",
|
|
475
|
+
name=f"{tenant.name} Admin",
|
|
476
|
+
description="Administrative access to tenant resources",
|
|
477
|
+
role_type=RoleType.SYSTEM,
|
|
478
|
+
permissions={
|
|
479
|
+
Permission("dataset", "*"),
|
|
480
|
+
Permission("validation", "*"),
|
|
481
|
+
Permission("checkpoint", "*"),
|
|
482
|
+
Permission("user", "read"),
|
|
483
|
+
Permission("user", "update"),
|
|
484
|
+
Permission("role", "read"),
|
|
485
|
+
},
|
|
486
|
+
parent_roles={f"{tenant.id}:member"},
|
|
487
|
+
tenant_id=tenant.id,
|
|
488
|
+
)
|
|
489
|
+
self._rbac_manager.role_store.save(admin_role)
|
|
490
|
+
roles.append(admin_role)
|
|
491
|
+
|
|
492
|
+
# Member role - basic access
|
|
493
|
+
member_role = Role(
|
|
494
|
+
id=f"{tenant.id}:member",
|
|
495
|
+
name=f"{tenant.name} Member",
|
|
496
|
+
description="Basic access to tenant resources",
|
|
497
|
+
role_type=RoleType.SYSTEM,
|
|
498
|
+
permissions={
|
|
499
|
+
Permission("dataset", "read"),
|
|
500
|
+
Permission("validation", "read"),
|
|
501
|
+
Permission("validation", "execute"),
|
|
502
|
+
Permission("checkpoint", "read"),
|
|
503
|
+
},
|
|
504
|
+
tenant_id=tenant.id,
|
|
505
|
+
)
|
|
506
|
+
self._rbac_manager.role_store.save(member_role)
|
|
507
|
+
roles.append(member_role)
|
|
508
|
+
|
|
509
|
+
# Viewer role - read-only
|
|
510
|
+
viewer_role = Role(
|
|
511
|
+
id=f"{tenant.id}:viewer",
|
|
512
|
+
name=f"{tenant.name} Viewer",
|
|
513
|
+
description="Read-only access to tenant resources",
|
|
514
|
+
role_type=RoleType.SYSTEM,
|
|
515
|
+
permissions={
|
|
516
|
+
Permission("dataset", "read"),
|
|
517
|
+
Permission("validation", "read"),
|
|
518
|
+
Permission("checkpoint", "read"),
|
|
519
|
+
},
|
|
520
|
+
tenant_id=tenant.id,
|
|
521
|
+
)
|
|
522
|
+
self._rbac_manager.role_store.save(viewer_role)
|
|
523
|
+
roles.append(viewer_role)
|
|
524
|
+
|
|
525
|
+
return roles
|
|
526
|
+
|
|
527
|
+
def check_tenant_access(
|
|
528
|
+
self,
|
|
529
|
+
principal: Principal,
|
|
530
|
+
tenant: "Tenant",
|
|
531
|
+
resource: str,
|
|
532
|
+
action: str,
|
|
533
|
+
) -> AccessDecision:
|
|
534
|
+
"""Check if principal can access resource within tenant.
|
|
535
|
+
|
|
536
|
+
Args:
|
|
537
|
+
principal: Principal requesting access
|
|
538
|
+
tenant: Tenant context
|
|
539
|
+
resource: Resource being accessed
|
|
540
|
+
action: Action being performed
|
|
541
|
+
|
|
542
|
+
Returns:
|
|
543
|
+
Access decision.
|
|
544
|
+
"""
|
|
545
|
+
if not self._rbac_manager:
|
|
546
|
+
raise RuntimeError("RBAC manager not configured")
|
|
547
|
+
|
|
548
|
+
# Check tenant membership
|
|
549
|
+
if principal.tenant_id != tenant.id:
|
|
550
|
+
# Check cross-tenant access
|
|
551
|
+
if not principal.roles.intersection(self._config.cross_tenant_roles):
|
|
552
|
+
return AccessDecision.deny(
|
|
553
|
+
f"Principal {principal.id} is not a member of tenant {tenant.id}"
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
# Check permission
|
|
557
|
+
return self._rbac_manager.check(
|
|
558
|
+
principal=principal,
|
|
559
|
+
resource=resource,
|
|
560
|
+
action=action,
|
|
561
|
+
resource_attributes={"tenant_id": tenant.id},
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
# =============================================================================
|
|
566
|
+
# Decorators for Tenant-Aware RBAC
|
|
567
|
+
# =============================================================================
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
def require_tenant_permission(
|
|
571
|
+
resource: str,
|
|
572
|
+
action: str,
|
|
573
|
+
use_current_tenant: bool = True,
|
|
574
|
+
) -> Callable[[F], F]:
|
|
575
|
+
"""Decorator that requires permission within the current tenant context.
|
|
576
|
+
|
|
577
|
+
Example:
|
|
578
|
+
>>> @require_tenant_permission("dataset", "read")
|
|
579
|
+
... def get_dataset(dataset_id: str):
|
|
580
|
+
... return load_dataset(dataset_id)
|
|
581
|
+
"""
|
|
582
|
+
|
|
583
|
+
def decorator(func: F) -> F:
|
|
584
|
+
@functools.wraps(func)
|
|
585
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
586
|
+
from truthound.multitenancy.core import TenantContext
|
|
587
|
+
|
|
588
|
+
principal = SecurityContext.get_current_principal()
|
|
589
|
+
if not principal:
|
|
590
|
+
raise PermissionDeniedError("No authenticated principal")
|
|
591
|
+
|
|
592
|
+
tenant = TenantContext.get_current_tenant() if use_current_tenant else None
|
|
593
|
+
tenant_id = tenant.id if tenant else principal.tenant_id
|
|
594
|
+
|
|
595
|
+
if not tenant_id:
|
|
596
|
+
raise PermissionDeniedError("No tenant context")
|
|
597
|
+
|
|
598
|
+
# Get RBAC manager
|
|
599
|
+
rbac_manager = _get_default_rbac_manager()
|
|
600
|
+
|
|
601
|
+
decision = rbac_manager.check(
|
|
602
|
+
principal=principal,
|
|
603
|
+
resource=resource,
|
|
604
|
+
action=action,
|
|
605
|
+
resource_attributes={"tenant_id": tenant_id},
|
|
606
|
+
)
|
|
607
|
+
|
|
608
|
+
if not decision.allowed:
|
|
609
|
+
raise PermissionDeniedError(
|
|
610
|
+
decision.reason,
|
|
611
|
+
principal_id=principal.id,
|
|
612
|
+
resource=resource,
|
|
613
|
+
action=action,
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
return func(*args, **kwargs)
|
|
617
|
+
|
|
618
|
+
return wrapper # type: ignore
|
|
619
|
+
|
|
620
|
+
return decorator
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
def require_tenant_role(
|
|
624
|
+
role: str | set[str],
|
|
625
|
+
use_current_tenant: bool = True,
|
|
626
|
+
) -> Callable[[F], F]:
|
|
627
|
+
"""Decorator that requires a tenant-scoped role.
|
|
628
|
+
|
|
629
|
+
The role name will be prefixed with the tenant ID.
|
|
630
|
+
|
|
631
|
+
Example:
|
|
632
|
+
>>> @require_tenant_role("admin")
|
|
633
|
+
... def admin_function():
|
|
634
|
+
... # Requires {tenant_id}:admin role
|
|
635
|
+
... pass
|
|
636
|
+
"""
|
|
637
|
+
|
|
638
|
+
def decorator(func: F) -> F:
|
|
639
|
+
@functools.wraps(func)
|
|
640
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
641
|
+
from truthound.multitenancy.core import TenantContext
|
|
642
|
+
|
|
643
|
+
principal = SecurityContext.get_current_principal()
|
|
644
|
+
if not principal:
|
|
645
|
+
raise PermissionDeniedError("No authenticated principal")
|
|
646
|
+
|
|
647
|
+
tenant = TenantContext.get_current_tenant() if use_current_tenant else None
|
|
648
|
+
tenant_id = tenant.id if tenant else principal.tenant_id
|
|
649
|
+
|
|
650
|
+
if not tenant_id:
|
|
651
|
+
raise PermissionDeniedError("No tenant context")
|
|
652
|
+
|
|
653
|
+
# Build tenant-scoped role names
|
|
654
|
+
roles = {role} if isinstance(role, str) else role
|
|
655
|
+
tenant_roles = {f"{tenant_id}:{r}" for r in roles}
|
|
656
|
+
|
|
657
|
+
# Check if principal has any of the tenant-scoped roles
|
|
658
|
+
if not tenant_roles.intersection(principal.roles):
|
|
659
|
+
raise PermissionDeniedError(
|
|
660
|
+
f"Requires one of roles: {tenant_roles}",
|
|
661
|
+
principal_id=principal.id,
|
|
662
|
+
)
|
|
663
|
+
|
|
664
|
+
return func(*args, **kwargs)
|
|
665
|
+
|
|
666
|
+
return wrapper # type: ignore
|
|
667
|
+
|
|
668
|
+
return decorator
|
|
669
|
+
|
|
670
|
+
|
|
671
|
+
@contextmanager
|
|
672
|
+
def tenant_rbac_context(
|
|
673
|
+
tenant: "Tenant | str",
|
|
674
|
+
principal: Principal,
|
|
675
|
+
) -> Iterator[tuple["Tenant", Principal]]:
|
|
676
|
+
"""Context manager for tenant-scoped RBAC operations.
|
|
677
|
+
|
|
678
|
+
Example:
|
|
679
|
+
>>> with tenant_rbac_context(tenant, principal) as (t, p):
|
|
680
|
+
... # Operations within tenant and principal context
|
|
681
|
+
... run_validation()
|
|
682
|
+
"""
|
|
683
|
+
from truthound.multitenancy.core import TenantContext
|
|
684
|
+
|
|
685
|
+
# Get tenant manager if needed
|
|
686
|
+
if isinstance(tenant, str):
|
|
687
|
+
from truthound.multitenancy.manager import get_tenant_manager
|
|
688
|
+
tenant_mgr = get_tenant_manager()
|
|
689
|
+
tenant = tenant_mgr.require(tenant)
|
|
690
|
+
|
|
691
|
+
with TenantContext.set_current(tenant):
|
|
692
|
+
with SecurityContext.set_principal(principal):
|
|
693
|
+
yield tenant, principal
|
|
694
|
+
|
|
695
|
+
|
|
696
|
+
# =============================================================================
|
|
697
|
+
# Integration Hooks
|
|
698
|
+
# =============================================================================
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
def create_tenant_rbac_hooks(
|
|
702
|
+
integration: MultitenancyRBACIntegration,
|
|
703
|
+
) -> dict[str, Callable[..., Any]]:
|
|
704
|
+
"""Create hooks for tenant lifecycle events.
|
|
705
|
+
|
|
706
|
+
These hooks can be registered with TenantManager to automatically
|
|
707
|
+
create roles and principals when tenants are created.
|
|
708
|
+
|
|
709
|
+
Example:
|
|
710
|
+
>>> hooks = create_tenant_rbac_hooks(integration)
|
|
711
|
+
>>> config = TenantManagerConfig(
|
|
712
|
+
... on_create=[hooks["on_create"]],
|
|
713
|
+
... on_delete=[hooks["on_delete"]],
|
|
714
|
+
... )
|
|
715
|
+
"""
|
|
716
|
+
|
|
717
|
+
def on_tenant_create(tenant: "Tenant") -> None:
|
|
718
|
+
"""Called when a new tenant is created."""
|
|
719
|
+
# Create default roles
|
|
720
|
+
integration.create_tenant_roles(tenant)
|
|
721
|
+
|
|
722
|
+
# Create admin principal
|
|
723
|
+
integration.get_or_create_tenant_admin(tenant)
|
|
724
|
+
|
|
725
|
+
def on_tenant_delete(tenant_id: str) -> None:
|
|
726
|
+
"""Called when a tenant is deleted."""
|
|
727
|
+
# Clean up roles and principals would be handled here
|
|
728
|
+
# In production, this should remove all tenant-scoped entities
|
|
729
|
+
pass
|
|
730
|
+
|
|
731
|
+
return {
|
|
732
|
+
"on_create": on_tenant_create,
|
|
733
|
+
"on_delete": on_tenant_delete,
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
|
|
737
|
+
# =============================================================================
|
|
738
|
+
# Default Manager Access
|
|
739
|
+
# =============================================================================
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
_default_rbac_manager: "RBACManager | None" = None
|
|
743
|
+
|
|
744
|
+
|
|
745
|
+
def set_default_rbac_manager(manager: "RBACManager") -> None:
|
|
746
|
+
"""Set the default RBAC manager for decorator use."""
|
|
747
|
+
global _default_rbac_manager
|
|
748
|
+
_default_rbac_manager = manager
|
|
749
|
+
|
|
750
|
+
|
|
751
|
+
def _get_default_rbac_manager() -> "RBACManager":
|
|
752
|
+
"""Get the default RBAC manager."""
|
|
753
|
+
if _default_rbac_manager is None:
|
|
754
|
+
raise RuntimeError("No default RBAC manager configured")
|
|
755
|
+
return _default_rbac_manager
|
|
756
|
+
|
|
757
|
+
|
|
758
|
+
# Import for type hints (avoid circular import)
|
|
759
|
+
if TYPE_CHECKING:
|
|
760
|
+
from truthound.rbac.manager import RBACManager
|