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,1052 @@
|
|
|
1
|
+
"""RBAC manager for role-based access control.
|
|
2
|
+
|
|
3
|
+
This module provides the central RBACManager class that orchestrates
|
|
4
|
+
all RBAC-related operations including role management, principal management,
|
|
5
|
+
permission checking, and policy evaluation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import threading
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from datetime import datetime, timezone
|
|
13
|
+
from typing import Any, Callable, Iterator
|
|
14
|
+
|
|
15
|
+
from truthound.rbac.core import (
|
|
16
|
+
AccessContext,
|
|
17
|
+
AccessDecision,
|
|
18
|
+
Permission,
|
|
19
|
+
PermissionAction,
|
|
20
|
+
PermissionDeniedError,
|
|
21
|
+
Principal,
|
|
22
|
+
PrincipalStore,
|
|
23
|
+
PrincipalType,
|
|
24
|
+
Role,
|
|
25
|
+
RoleNotFoundError,
|
|
26
|
+
RoleStore,
|
|
27
|
+
RoleType,
|
|
28
|
+
SecurityContext,
|
|
29
|
+
generate_principal_id,
|
|
30
|
+
generate_role_id,
|
|
31
|
+
)
|
|
32
|
+
from truthound.rbac.policy import (
|
|
33
|
+
PolicyCombination,
|
|
34
|
+
PolicyEngine,
|
|
35
|
+
PolicyEngineConfig,
|
|
36
|
+
RoleBasedEvaluator,
|
|
37
|
+
)
|
|
38
|
+
from truthound.rbac.storage import (
|
|
39
|
+
MemoryPrincipalStore,
|
|
40
|
+
MemoryRoleStore,
|
|
41
|
+
create_principal_store,
|
|
42
|
+
create_role_store,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# =============================================================================
|
|
47
|
+
# Configuration
|
|
48
|
+
# =============================================================================
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class RBACManagerConfig:
|
|
53
|
+
"""Configuration for the RBAC manager."""
|
|
54
|
+
|
|
55
|
+
# Default settings for new principals
|
|
56
|
+
default_principal_type: PrincipalType = PrincipalType.USER
|
|
57
|
+
default_principal_roles: set[str] = field(default_factory=set)
|
|
58
|
+
|
|
59
|
+
# Policy evaluation
|
|
60
|
+
policy_combination: PolicyCombination = PolicyCombination.DENY_OVERRIDES
|
|
61
|
+
cache_decisions: bool = True
|
|
62
|
+
cache_ttl_seconds: int = 300
|
|
63
|
+
|
|
64
|
+
# Security settings
|
|
65
|
+
require_authentication: bool = True
|
|
66
|
+
anonymous_principal: Principal | None = None
|
|
67
|
+
|
|
68
|
+
# Lifecycle hooks
|
|
69
|
+
on_role_create: list[Callable[[Role], None]] = field(default_factory=list)
|
|
70
|
+
on_role_update: list[Callable[[Role, Role], None]] = field(default_factory=list)
|
|
71
|
+
on_role_delete: list[Callable[[str], None]] = field(default_factory=list)
|
|
72
|
+
on_principal_create: list[Callable[[Principal], None]] = field(default_factory=list)
|
|
73
|
+
on_principal_update: list[Callable[[Principal, Principal], None]] = field(default_factory=list)
|
|
74
|
+
on_principal_delete: list[Callable[[str], None]] = field(default_factory=list)
|
|
75
|
+
on_access_decision: list[Callable[[AccessContext, AccessDecision], None]] = field(default_factory=list)
|
|
76
|
+
|
|
77
|
+
# Audit
|
|
78
|
+
audit_access_decisions: bool = True
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# =============================================================================
|
|
82
|
+
# RBAC Manager
|
|
83
|
+
# =============================================================================
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class RBACManager:
|
|
87
|
+
"""Central manager for all RBAC operations.
|
|
88
|
+
|
|
89
|
+
The RBACManager provides a unified interface for:
|
|
90
|
+
- CRUD operations on roles and principals
|
|
91
|
+
- Permission checking and policy evaluation
|
|
92
|
+
- Role inheritance management
|
|
93
|
+
- Security context management
|
|
94
|
+
|
|
95
|
+
Example:
|
|
96
|
+
>>> manager = RBACManager()
|
|
97
|
+
>>>
|
|
98
|
+
>>> # Create a role
|
|
99
|
+
>>> role = manager.create_role(
|
|
100
|
+
... name="Data Analyst",
|
|
101
|
+
... permissions={"dataset:read", "validation:execute"},
|
|
102
|
+
... )
|
|
103
|
+
>>>
|
|
104
|
+
>>> # Create a principal
|
|
105
|
+
>>> principal = manager.create_principal(
|
|
106
|
+
... name="john.doe@example.com",
|
|
107
|
+
... roles={role.id},
|
|
108
|
+
... )
|
|
109
|
+
>>>
|
|
110
|
+
>>> # Check permission
|
|
111
|
+
>>> decision = manager.check(principal, "dataset", "read")
|
|
112
|
+
>>> print(decision.allowed) # True
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
def __init__(
|
|
116
|
+
self,
|
|
117
|
+
role_store: RoleStore | None = None,
|
|
118
|
+
principal_store: PrincipalStore | None = None,
|
|
119
|
+
policy_engine: PolicyEngine | None = None,
|
|
120
|
+
config: RBACManagerConfig | None = None,
|
|
121
|
+
) -> None:
|
|
122
|
+
self._role_store = role_store or MemoryRoleStore()
|
|
123
|
+
self._principal_store = principal_store or MemoryPrincipalStore()
|
|
124
|
+
self._config = config or RBACManagerConfig()
|
|
125
|
+
|
|
126
|
+
# Create or use provided policy engine
|
|
127
|
+
if policy_engine:
|
|
128
|
+
self._engine = policy_engine
|
|
129
|
+
else:
|
|
130
|
+
engine_config = PolicyEngineConfig(
|
|
131
|
+
combination=self._config.policy_combination,
|
|
132
|
+
cache_decisions=self._config.cache_decisions,
|
|
133
|
+
cache_ttl_seconds=self._config.cache_ttl_seconds,
|
|
134
|
+
)
|
|
135
|
+
self._engine = PolicyEngine(config=engine_config)
|
|
136
|
+
# Add default role-based evaluator
|
|
137
|
+
self._engine.add_evaluator(RoleBasedEvaluator(self._role_store))
|
|
138
|
+
|
|
139
|
+
self._lock = threading.RLock()
|
|
140
|
+
|
|
141
|
+
# Initialize default roles
|
|
142
|
+
self._init_default_roles()
|
|
143
|
+
|
|
144
|
+
# =========================================================================
|
|
145
|
+
# Properties
|
|
146
|
+
# =========================================================================
|
|
147
|
+
|
|
148
|
+
@property
|
|
149
|
+
def role_store(self) -> RoleStore:
|
|
150
|
+
"""Get the role store."""
|
|
151
|
+
return self._role_store
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def principal_store(self) -> PrincipalStore:
|
|
155
|
+
"""Get the principal store."""
|
|
156
|
+
return self._principal_store
|
|
157
|
+
|
|
158
|
+
@property
|
|
159
|
+
def engine(self) -> PolicyEngine:
|
|
160
|
+
"""Get the policy engine."""
|
|
161
|
+
return self._engine
|
|
162
|
+
|
|
163
|
+
@property
|
|
164
|
+
def config(self) -> RBACManagerConfig:
|
|
165
|
+
"""Get the configuration."""
|
|
166
|
+
return self._config
|
|
167
|
+
|
|
168
|
+
# =========================================================================
|
|
169
|
+
# Role Management
|
|
170
|
+
# =========================================================================
|
|
171
|
+
|
|
172
|
+
def create_role(
|
|
173
|
+
self,
|
|
174
|
+
name: str,
|
|
175
|
+
role_id: str | None = None,
|
|
176
|
+
permissions: set[str | Permission] | None = None,
|
|
177
|
+
parent_roles: set[str] | None = None,
|
|
178
|
+
role_type: RoleType = RoleType.CUSTOM,
|
|
179
|
+
description: str = "",
|
|
180
|
+
tenant_id: str | None = None,
|
|
181
|
+
**kwargs: Any,
|
|
182
|
+
) -> Role:
|
|
183
|
+
"""Create a new role.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
name: Human-readable role name
|
|
187
|
+
role_id: Optional custom ID (auto-generated if not provided)
|
|
188
|
+
permissions: Permission strings or objects
|
|
189
|
+
parent_roles: Parent role IDs for inheritance
|
|
190
|
+
role_type: Type of role
|
|
191
|
+
description: Role description
|
|
192
|
+
tenant_id: Tenant ID for scoped roles
|
|
193
|
+
**kwargs: Additional role attributes
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
Created Role object.
|
|
197
|
+
|
|
198
|
+
Example:
|
|
199
|
+
>>> role = manager.create_role(
|
|
200
|
+
... name="Editor",
|
|
201
|
+
... permissions={"dataset:read", "dataset:update"},
|
|
202
|
+
... )
|
|
203
|
+
"""
|
|
204
|
+
with self._lock:
|
|
205
|
+
# Generate ID if not provided
|
|
206
|
+
if not role_id:
|
|
207
|
+
role_id = generate_role_id(name)
|
|
208
|
+
if tenant_id:
|
|
209
|
+
role_id = f"{tenant_id}:{role_id}"
|
|
210
|
+
|
|
211
|
+
# Check for duplicates
|
|
212
|
+
if self._role_store.exists(role_id):
|
|
213
|
+
raise ValueError(f"Role already exists: {role_id}")
|
|
214
|
+
|
|
215
|
+
# Parse permissions
|
|
216
|
+
parsed_permissions = set()
|
|
217
|
+
for perm in (permissions or set()):
|
|
218
|
+
if isinstance(perm, str):
|
|
219
|
+
parsed_permissions.add(Permission.parse(perm))
|
|
220
|
+
else:
|
|
221
|
+
parsed_permissions.add(perm)
|
|
222
|
+
|
|
223
|
+
# Create role
|
|
224
|
+
role = Role(
|
|
225
|
+
id=role_id,
|
|
226
|
+
name=name,
|
|
227
|
+
description=description,
|
|
228
|
+
role_type=role_type,
|
|
229
|
+
permissions=parsed_permissions,
|
|
230
|
+
parent_roles=parent_roles or set(),
|
|
231
|
+
tenant_id=tenant_id,
|
|
232
|
+
**kwargs,
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
self._role_store.save(role)
|
|
236
|
+
|
|
237
|
+
# Call hooks
|
|
238
|
+
for hook in self._config.on_role_create:
|
|
239
|
+
try:
|
|
240
|
+
hook(role)
|
|
241
|
+
except Exception:
|
|
242
|
+
pass
|
|
243
|
+
|
|
244
|
+
return role
|
|
245
|
+
|
|
246
|
+
def get_role(self, role_id: str) -> Role | None:
|
|
247
|
+
"""Get a role by ID.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
role_id: Role ID
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
Role object if found, None otherwise.
|
|
254
|
+
"""
|
|
255
|
+
return self._role_store.get(role_id)
|
|
256
|
+
|
|
257
|
+
def require_role(self, role_id: str) -> Role:
|
|
258
|
+
"""Get a role by ID, raising if not found.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
role_id: Role ID
|
|
262
|
+
|
|
263
|
+
Returns:
|
|
264
|
+
Role object.
|
|
265
|
+
|
|
266
|
+
Raises:
|
|
267
|
+
RoleNotFoundError: If role not found.
|
|
268
|
+
"""
|
|
269
|
+
role = self.get_role(role_id)
|
|
270
|
+
if not role:
|
|
271
|
+
raise RoleNotFoundError(f"Role not found: {role_id}")
|
|
272
|
+
return role
|
|
273
|
+
|
|
274
|
+
def list_roles(
|
|
275
|
+
self,
|
|
276
|
+
tenant_id: str | None = None,
|
|
277
|
+
role_type: RoleType | None = None,
|
|
278
|
+
enabled: bool | None = None,
|
|
279
|
+
) -> list[Role]:
|
|
280
|
+
"""List roles with optional filters.
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
tenant_id: Filter by tenant
|
|
284
|
+
role_type: Filter by role type
|
|
285
|
+
enabled: Filter by enabled status
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
List of Role objects.
|
|
289
|
+
"""
|
|
290
|
+
return self._role_store.list(
|
|
291
|
+
tenant_id=tenant_id,
|
|
292
|
+
role_type=role_type,
|
|
293
|
+
enabled=enabled,
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
def update_role(
|
|
297
|
+
self,
|
|
298
|
+
role_id: str,
|
|
299
|
+
**updates: Any,
|
|
300
|
+
) -> Role:
|
|
301
|
+
"""Update a role.
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
role_id: Role ID
|
|
305
|
+
**updates: Fields to update
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
Updated Role object.
|
|
309
|
+
|
|
310
|
+
Example:
|
|
311
|
+
>>> role = manager.update_role(
|
|
312
|
+
... "editor",
|
|
313
|
+
... name="Senior Editor",
|
|
314
|
+
... description="Updated description",
|
|
315
|
+
... )
|
|
316
|
+
"""
|
|
317
|
+
with self._lock:
|
|
318
|
+
role = self.require_role(role_id)
|
|
319
|
+
old_role = Role.from_dict(role.to_dict()) # Copy
|
|
320
|
+
|
|
321
|
+
# Apply updates
|
|
322
|
+
for key, value in updates.items():
|
|
323
|
+
if hasattr(role, key):
|
|
324
|
+
setattr(role, key, value)
|
|
325
|
+
|
|
326
|
+
role.updated_at = datetime.now(timezone.utc)
|
|
327
|
+
self._role_store.save(role)
|
|
328
|
+
|
|
329
|
+
# Call hooks
|
|
330
|
+
for hook in self._config.on_role_update:
|
|
331
|
+
try:
|
|
332
|
+
hook(old_role, role)
|
|
333
|
+
except Exception:
|
|
334
|
+
pass
|
|
335
|
+
|
|
336
|
+
return role
|
|
337
|
+
|
|
338
|
+
def delete_role(self, role_id: str) -> bool:
|
|
339
|
+
"""Delete a role.
|
|
340
|
+
|
|
341
|
+
Args:
|
|
342
|
+
role_id: Role ID
|
|
343
|
+
|
|
344
|
+
Returns:
|
|
345
|
+
True if deleted, False if not found.
|
|
346
|
+
"""
|
|
347
|
+
with self._lock:
|
|
348
|
+
deleted = self._role_store.delete(role_id)
|
|
349
|
+
|
|
350
|
+
if deleted:
|
|
351
|
+
for hook in self._config.on_role_delete:
|
|
352
|
+
try:
|
|
353
|
+
hook(role_id)
|
|
354
|
+
except Exception:
|
|
355
|
+
pass
|
|
356
|
+
|
|
357
|
+
return deleted
|
|
358
|
+
|
|
359
|
+
def add_permission_to_role(
|
|
360
|
+
self,
|
|
361
|
+
role_id: str,
|
|
362
|
+
permission: str | Permission,
|
|
363
|
+
) -> Role:
|
|
364
|
+
"""Add a permission to a role.
|
|
365
|
+
|
|
366
|
+
Args:
|
|
367
|
+
role_id: Role ID
|
|
368
|
+
permission: Permission string or object
|
|
369
|
+
|
|
370
|
+
Returns:
|
|
371
|
+
Updated Role object.
|
|
372
|
+
"""
|
|
373
|
+
with self._lock:
|
|
374
|
+
role = self.require_role(role_id)
|
|
375
|
+
|
|
376
|
+
if isinstance(permission, str):
|
|
377
|
+
permission = Permission.parse(permission)
|
|
378
|
+
|
|
379
|
+
role.add_permission(permission)
|
|
380
|
+
role.updated_at = datetime.now(timezone.utc)
|
|
381
|
+
self._role_store.save(role)
|
|
382
|
+
|
|
383
|
+
return role
|
|
384
|
+
|
|
385
|
+
def remove_permission_from_role(
|
|
386
|
+
self,
|
|
387
|
+
role_id: str,
|
|
388
|
+
permission: str | Permission,
|
|
389
|
+
) -> Role:
|
|
390
|
+
"""Remove a permission from a role.
|
|
391
|
+
|
|
392
|
+
Args:
|
|
393
|
+
role_id: Role ID
|
|
394
|
+
permission: Permission string or object
|
|
395
|
+
|
|
396
|
+
Returns:
|
|
397
|
+
Updated Role object.
|
|
398
|
+
"""
|
|
399
|
+
with self._lock:
|
|
400
|
+
role = self.require_role(role_id)
|
|
401
|
+
|
|
402
|
+
if isinstance(permission, str):
|
|
403
|
+
permission = Permission.parse(permission)
|
|
404
|
+
|
|
405
|
+
role.remove_permission(permission)
|
|
406
|
+
role.updated_at = datetime.now(timezone.utc)
|
|
407
|
+
self._role_store.save(role)
|
|
408
|
+
|
|
409
|
+
return role
|
|
410
|
+
|
|
411
|
+
def get_all_role_permissions(self, role_id: str) -> set[Permission]:
|
|
412
|
+
"""Get all permissions for a role, including inherited ones.
|
|
413
|
+
|
|
414
|
+
Args:
|
|
415
|
+
role_id: Role ID
|
|
416
|
+
|
|
417
|
+
Returns:
|
|
418
|
+
Set of Permission objects.
|
|
419
|
+
"""
|
|
420
|
+
return self._role_store.get_all_permissions(role_id)
|
|
421
|
+
|
|
422
|
+
# =========================================================================
|
|
423
|
+
# Principal Management
|
|
424
|
+
# =========================================================================
|
|
425
|
+
|
|
426
|
+
def create_principal(
|
|
427
|
+
self,
|
|
428
|
+
name: str,
|
|
429
|
+
principal_id: str | None = None,
|
|
430
|
+
principal_type: PrincipalType | None = None,
|
|
431
|
+
email: str = "",
|
|
432
|
+
roles: set[str] | None = None,
|
|
433
|
+
permissions: set[str | Permission] | None = None,
|
|
434
|
+
attributes: dict[str, Any] | None = None,
|
|
435
|
+
tenant_id: str | None = None,
|
|
436
|
+
**kwargs: Any,
|
|
437
|
+
) -> Principal:
|
|
438
|
+
"""Create a new principal.
|
|
439
|
+
|
|
440
|
+
Args:
|
|
441
|
+
name: Principal name
|
|
442
|
+
principal_id: Optional custom ID
|
|
443
|
+
principal_type: Type of principal
|
|
444
|
+
email: Principal email
|
|
445
|
+
roles: Role IDs to assign
|
|
446
|
+
permissions: Direct permissions
|
|
447
|
+
attributes: ABAC attributes
|
|
448
|
+
tenant_id: Tenant ID
|
|
449
|
+
**kwargs: Additional attributes
|
|
450
|
+
|
|
451
|
+
Returns:
|
|
452
|
+
Created Principal object.
|
|
453
|
+
|
|
454
|
+
Example:
|
|
455
|
+
>>> principal = manager.create_principal(
|
|
456
|
+
... name="john.doe@example.com",
|
|
457
|
+
... email="john.doe@example.com",
|
|
458
|
+
... roles={"editor"},
|
|
459
|
+
... )
|
|
460
|
+
"""
|
|
461
|
+
with self._lock:
|
|
462
|
+
# Generate ID if not provided
|
|
463
|
+
if not principal_id:
|
|
464
|
+
prefix = "user"
|
|
465
|
+
if tenant_id:
|
|
466
|
+
prefix = f"{tenant_id}_{prefix}"
|
|
467
|
+
principal_id = generate_principal_id(prefix)
|
|
468
|
+
|
|
469
|
+
# Use default type from config
|
|
470
|
+
if principal_type is None:
|
|
471
|
+
principal_type = self._config.default_principal_type
|
|
472
|
+
|
|
473
|
+
# Parse direct permissions
|
|
474
|
+
parsed_permissions = set()
|
|
475
|
+
for perm in (permissions or set()):
|
|
476
|
+
if isinstance(perm, str):
|
|
477
|
+
parsed_permissions.add(Permission.parse(perm))
|
|
478
|
+
else:
|
|
479
|
+
parsed_permissions.add(perm)
|
|
480
|
+
|
|
481
|
+
# Combine with default roles
|
|
482
|
+
all_roles = set(roles or set())
|
|
483
|
+
all_roles.update(self._config.default_principal_roles)
|
|
484
|
+
|
|
485
|
+
# Create principal
|
|
486
|
+
principal = Principal(
|
|
487
|
+
id=principal_id,
|
|
488
|
+
type=principal_type,
|
|
489
|
+
name=name,
|
|
490
|
+
email=email,
|
|
491
|
+
roles=all_roles,
|
|
492
|
+
direct_permissions=parsed_permissions,
|
|
493
|
+
attributes=attributes or {},
|
|
494
|
+
tenant_id=tenant_id,
|
|
495
|
+
**kwargs,
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
self._principal_store.save(principal)
|
|
499
|
+
|
|
500
|
+
# Call hooks
|
|
501
|
+
for hook in self._config.on_principal_create:
|
|
502
|
+
try:
|
|
503
|
+
hook(principal)
|
|
504
|
+
except Exception:
|
|
505
|
+
pass
|
|
506
|
+
|
|
507
|
+
return principal
|
|
508
|
+
|
|
509
|
+
def get_principal(self, principal_id: str) -> Principal | None:
|
|
510
|
+
"""Get a principal by ID.
|
|
511
|
+
|
|
512
|
+
Args:
|
|
513
|
+
principal_id: Principal ID
|
|
514
|
+
|
|
515
|
+
Returns:
|
|
516
|
+
Principal object if found, None otherwise.
|
|
517
|
+
"""
|
|
518
|
+
return self._principal_store.get(principal_id)
|
|
519
|
+
|
|
520
|
+
def get_principal_by_email(self, email: str) -> Principal | None:
|
|
521
|
+
"""Get a principal by email.
|
|
522
|
+
|
|
523
|
+
Args:
|
|
524
|
+
email: Principal email
|
|
525
|
+
|
|
526
|
+
Returns:
|
|
527
|
+
Principal object if found, None otherwise.
|
|
528
|
+
"""
|
|
529
|
+
return self._principal_store.get_by_email(email)
|
|
530
|
+
|
|
531
|
+
def require_principal(self, principal_id: str) -> Principal:
|
|
532
|
+
"""Get a principal by ID, raising if not found.
|
|
533
|
+
|
|
534
|
+
Args:
|
|
535
|
+
principal_id: Principal ID
|
|
536
|
+
|
|
537
|
+
Returns:
|
|
538
|
+
Principal object.
|
|
539
|
+
|
|
540
|
+
Raises:
|
|
541
|
+
ValueError: If principal not found.
|
|
542
|
+
"""
|
|
543
|
+
principal = self.get_principal(principal_id)
|
|
544
|
+
if not principal:
|
|
545
|
+
raise ValueError(f"Principal not found: {principal_id}")
|
|
546
|
+
return principal
|
|
547
|
+
|
|
548
|
+
def list_principals(
|
|
549
|
+
self,
|
|
550
|
+
tenant_id: str | None = None,
|
|
551
|
+
principal_type: PrincipalType | None = None,
|
|
552
|
+
role_id: str | None = None,
|
|
553
|
+
) -> list[Principal]:
|
|
554
|
+
"""List principals with optional filters.
|
|
555
|
+
|
|
556
|
+
Args:
|
|
557
|
+
tenant_id: Filter by tenant
|
|
558
|
+
principal_type: Filter by type
|
|
559
|
+
role_id: Filter by role
|
|
560
|
+
|
|
561
|
+
Returns:
|
|
562
|
+
List of Principal objects.
|
|
563
|
+
"""
|
|
564
|
+
return self._principal_store.list(
|
|
565
|
+
tenant_id=tenant_id,
|
|
566
|
+
principal_type=principal_type,
|
|
567
|
+
role_id=role_id,
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
def update_principal(
|
|
571
|
+
self,
|
|
572
|
+
principal_id: str,
|
|
573
|
+
**updates: Any,
|
|
574
|
+
) -> Principal:
|
|
575
|
+
"""Update a principal.
|
|
576
|
+
|
|
577
|
+
Args:
|
|
578
|
+
principal_id: Principal ID
|
|
579
|
+
**updates: Fields to update
|
|
580
|
+
|
|
581
|
+
Returns:
|
|
582
|
+
Updated Principal object.
|
|
583
|
+
"""
|
|
584
|
+
with self._lock:
|
|
585
|
+
principal = self.require_principal(principal_id)
|
|
586
|
+
old_principal = Principal.from_dict(principal.to_dict())
|
|
587
|
+
|
|
588
|
+
for key, value in updates.items():
|
|
589
|
+
if hasattr(principal, key):
|
|
590
|
+
setattr(principal, key, value)
|
|
591
|
+
|
|
592
|
+
self._principal_store.save(principal)
|
|
593
|
+
|
|
594
|
+
for hook in self._config.on_principal_update:
|
|
595
|
+
try:
|
|
596
|
+
hook(old_principal, principal)
|
|
597
|
+
except Exception:
|
|
598
|
+
pass
|
|
599
|
+
|
|
600
|
+
return principal
|
|
601
|
+
|
|
602
|
+
def delete_principal(self, principal_id: str) -> bool:
|
|
603
|
+
"""Delete a principal.
|
|
604
|
+
|
|
605
|
+
Args:
|
|
606
|
+
principal_id: Principal ID
|
|
607
|
+
|
|
608
|
+
Returns:
|
|
609
|
+
True if deleted, False if not found.
|
|
610
|
+
"""
|
|
611
|
+
with self._lock:
|
|
612
|
+
deleted = self._principal_store.delete(principal_id)
|
|
613
|
+
|
|
614
|
+
if deleted:
|
|
615
|
+
for hook in self._config.on_principal_delete:
|
|
616
|
+
try:
|
|
617
|
+
hook(principal_id)
|
|
618
|
+
except Exception:
|
|
619
|
+
pass
|
|
620
|
+
|
|
621
|
+
return deleted
|
|
622
|
+
|
|
623
|
+
def assign_role(self, principal_id: str, role_id: str) -> Principal:
|
|
624
|
+
"""Assign a role to a principal.
|
|
625
|
+
|
|
626
|
+
Args:
|
|
627
|
+
principal_id: Principal ID
|
|
628
|
+
role_id: Role ID
|
|
629
|
+
|
|
630
|
+
Returns:
|
|
631
|
+
Updated Principal object.
|
|
632
|
+
"""
|
|
633
|
+
with self._lock:
|
|
634
|
+
principal = self.require_principal(principal_id)
|
|
635
|
+
principal.add_role(role_id)
|
|
636
|
+
self._principal_store.save(principal)
|
|
637
|
+
return principal
|
|
638
|
+
|
|
639
|
+
def revoke_role(self, principal_id: str, role_id: str) -> Principal:
|
|
640
|
+
"""Revoke a role from a principal.
|
|
641
|
+
|
|
642
|
+
Args:
|
|
643
|
+
principal_id: Principal ID
|
|
644
|
+
role_id: Role ID
|
|
645
|
+
|
|
646
|
+
Returns:
|
|
647
|
+
Updated Principal object.
|
|
648
|
+
"""
|
|
649
|
+
with self._lock:
|
|
650
|
+
principal = self.require_principal(principal_id)
|
|
651
|
+
principal.remove_role(role_id)
|
|
652
|
+
self._principal_store.save(principal)
|
|
653
|
+
return principal
|
|
654
|
+
|
|
655
|
+
def get_principal_permissions(self, principal_id: str) -> set[Permission]:
|
|
656
|
+
"""Get all effective permissions for a principal.
|
|
657
|
+
|
|
658
|
+
Includes permissions from all roles and direct permissions.
|
|
659
|
+
|
|
660
|
+
Args:
|
|
661
|
+
principal_id: Principal ID
|
|
662
|
+
|
|
663
|
+
Returns:
|
|
664
|
+
Set of Permission objects.
|
|
665
|
+
"""
|
|
666
|
+
principal = self.require_principal(principal_id)
|
|
667
|
+
permissions = set(principal.direct_permissions)
|
|
668
|
+
|
|
669
|
+
for role_id in principal.roles:
|
|
670
|
+
role_perms = self.get_all_role_permissions(role_id)
|
|
671
|
+
permissions.update(role_perms)
|
|
672
|
+
|
|
673
|
+
return permissions
|
|
674
|
+
|
|
675
|
+
# =========================================================================
|
|
676
|
+
# Permission Checking
|
|
677
|
+
# =========================================================================
|
|
678
|
+
|
|
679
|
+
def check(
|
|
680
|
+
self,
|
|
681
|
+
principal: Principal | str,
|
|
682
|
+
resource: str,
|
|
683
|
+
action: str | PermissionAction,
|
|
684
|
+
resource_attributes: dict[str, Any] | None = None,
|
|
685
|
+
) -> AccessDecision:
|
|
686
|
+
"""Check if a principal has permission.
|
|
687
|
+
|
|
688
|
+
Args:
|
|
689
|
+
principal: Principal or principal ID
|
|
690
|
+
resource: Resource being accessed
|
|
691
|
+
action: Action being performed
|
|
692
|
+
resource_attributes: Resource attributes for ABAC
|
|
693
|
+
|
|
694
|
+
Returns:
|
|
695
|
+
Access decision.
|
|
696
|
+
|
|
697
|
+
Example:
|
|
698
|
+
>>> decision = manager.check(principal, "dataset", "read")
|
|
699
|
+
>>> if decision.allowed:
|
|
700
|
+
... print("Access granted")
|
|
701
|
+
"""
|
|
702
|
+
if isinstance(principal, str):
|
|
703
|
+
principal = self.require_principal(principal)
|
|
704
|
+
|
|
705
|
+
decision = self._engine.check(
|
|
706
|
+
principal=principal,
|
|
707
|
+
resource=resource,
|
|
708
|
+
action=action,
|
|
709
|
+
resource_attributes=resource_attributes,
|
|
710
|
+
)
|
|
711
|
+
|
|
712
|
+
# Call hooks
|
|
713
|
+
if self._config.audit_access_decisions:
|
|
714
|
+
context = AccessContext(
|
|
715
|
+
principal=principal,
|
|
716
|
+
resource=resource,
|
|
717
|
+
action=action,
|
|
718
|
+
resource_attributes=resource_attributes or {},
|
|
719
|
+
)
|
|
720
|
+
for hook in self._config.on_access_decision:
|
|
721
|
+
try:
|
|
722
|
+
hook(context, decision)
|
|
723
|
+
except Exception:
|
|
724
|
+
pass
|
|
725
|
+
|
|
726
|
+
return decision
|
|
727
|
+
|
|
728
|
+
def require(
|
|
729
|
+
self,
|
|
730
|
+
principal: Principal | str,
|
|
731
|
+
resource: str,
|
|
732
|
+
action: str | PermissionAction,
|
|
733
|
+
resource_attributes: dict[str, Any] | None = None,
|
|
734
|
+
) -> None:
|
|
735
|
+
"""Require permission, raising if denied.
|
|
736
|
+
|
|
737
|
+
Args:
|
|
738
|
+
principal: Principal or principal ID
|
|
739
|
+
resource: Resource being accessed
|
|
740
|
+
action: Action being performed
|
|
741
|
+
resource_attributes: Resource attributes
|
|
742
|
+
|
|
743
|
+
Raises:
|
|
744
|
+
PermissionDeniedError: If access is denied.
|
|
745
|
+
"""
|
|
746
|
+
decision = self.check(principal, resource, action, resource_attributes)
|
|
747
|
+
|
|
748
|
+
if not decision.allowed:
|
|
749
|
+
if isinstance(principal, str):
|
|
750
|
+
principal_id = principal
|
|
751
|
+
else:
|
|
752
|
+
principal_id = principal.id
|
|
753
|
+
|
|
754
|
+
raise PermissionDeniedError(
|
|
755
|
+
decision.reason,
|
|
756
|
+
principal_id=principal_id,
|
|
757
|
+
resource=resource,
|
|
758
|
+
action=action if isinstance(action, str) else action.value,
|
|
759
|
+
)
|
|
760
|
+
|
|
761
|
+
def has_permission(
|
|
762
|
+
self,
|
|
763
|
+
principal: Principal | str,
|
|
764
|
+
permission: str | Permission,
|
|
765
|
+
) -> bool:
|
|
766
|
+
"""Check if principal has a specific permission.
|
|
767
|
+
|
|
768
|
+
Args:
|
|
769
|
+
principal: Principal or principal ID
|
|
770
|
+
permission: Permission string or object
|
|
771
|
+
|
|
772
|
+
Returns:
|
|
773
|
+
True if principal has the permission.
|
|
774
|
+
"""
|
|
775
|
+
if isinstance(principal, str):
|
|
776
|
+
principal = self.require_principal(principal)
|
|
777
|
+
|
|
778
|
+
if isinstance(permission, str):
|
|
779
|
+
permission = Permission.parse(permission)
|
|
780
|
+
|
|
781
|
+
# Check direct permissions
|
|
782
|
+
for perm in principal.direct_permissions:
|
|
783
|
+
if perm.matches(permission):
|
|
784
|
+
return True
|
|
785
|
+
|
|
786
|
+
# Check role permissions
|
|
787
|
+
for role_id in principal.roles:
|
|
788
|
+
role_perms = self.get_all_role_permissions(role_id)
|
|
789
|
+
for perm in role_perms:
|
|
790
|
+
if perm.matches(permission):
|
|
791
|
+
return True
|
|
792
|
+
|
|
793
|
+
return False
|
|
794
|
+
|
|
795
|
+
def has_role(
|
|
796
|
+
self,
|
|
797
|
+
principal: Principal | str,
|
|
798
|
+
role: str | set[str],
|
|
799
|
+
require_all: bool = False,
|
|
800
|
+
) -> bool:
|
|
801
|
+
"""Check if principal has specific role(s).
|
|
802
|
+
|
|
803
|
+
Args:
|
|
804
|
+
principal: Principal or principal ID
|
|
805
|
+
role: Role ID or set of role IDs
|
|
806
|
+
require_all: If True, require all roles
|
|
807
|
+
|
|
808
|
+
Returns:
|
|
809
|
+
True if principal has the role(s).
|
|
810
|
+
"""
|
|
811
|
+
if isinstance(principal, str):
|
|
812
|
+
principal = self.require_principal(principal)
|
|
813
|
+
|
|
814
|
+
required_roles = {role} if isinstance(role, str) else role
|
|
815
|
+
|
|
816
|
+
if require_all:
|
|
817
|
+
return required_roles.issubset(principal.roles)
|
|
818
|
+
else:
|
|
819
|
+
return bool(required_roles.intersection(principal.roles))
|
|
820
|
+
|
|
821
|
+
# =========================================================================
|
|
822
|
+
# Context Management
|
|
823
|
+
# =========================================================================
|
|
824
|
+
|
|
825
|
+
def context(self, principal: Principal | str) -> SecurityContext:
|
|
826
|
+
"""Get a context manager for principal operations.
|
|
827
|
+
|
|
828
|
+
Args:
|
|
829
|
+
principal: Principal or ID
|
|
830
|
+
|
|
831
|
+
Returns:
|
|
832
|
+
Context manager that sets security context.
|
|
833
|
+
|
|
834
|
+
Example:
|
|
835
|
+
>>> with manager.context(principal):
|
|
836
|
+
... # All operations use this principal
|
|
837
|
+
... run_operation()
|
|
838
|
+
"""
|
|
839
|
+
if isinstance(principal, str):
|
|
840
|
+
principal = self.require_principal(principal)
|
|
841
|
+
|
|
842
|
+
return SecurityContext.set_principal(principal)
|
|
843
|
+
|
|
844
|
+
def current_principal(self) -> Principal | None:
|
|
845
|
+
"""Get the current principal from context.
|
|
846
|
+
|
|
847
|
+
Returns:
|
|
848
|
+
Current Principal or None.
|
|
849
|
+
"""
|
|
850
|
+
return SecurityContext.get_current_principal()
|
|
851
|
+
|
|
852
|
+
def require_current_principal(self) -> Principal:
|
|
853
|
+
"""Get the current principal, raising if not set.
|
|
854
|
+
|
|
855
|
+
Returns:
|
|
856
|
+
Current Principal.
|
|
857
|
+
|
|
858
|
+
Raises:
|
|
859
|
+
PermissionDeniedError: If no principal in context.
|
|
860
|
+
"""
|
|
861
|
+
return SecurityContext.require_principal()
|
|
862
|
+
|
|
863
|
+
# =========================================================================
|
|
864
|
+
# Default Roles
|
|
865
|
+
# =========================================================================
|
|
866
|
+
|
|
867
|
+
def _init_default_roles(self) -> None:
|
|
868
|
+
"""Initialize default system roles."""
|
|
869
|
+
default_roles = [
|
|
870
|
+
Role(
|
|
871
|
+
id="system_admin",
|
|
872
|
+
name="System Admin",
|
|
873
|
+
description="Full system access",
|
|
874
|
+
role_type=RoleType.SYSTEM,
|
|
875
|
+
permissions={Permission.all()},
|
|
876
|
+
),
|
|
877
|
+
Role(
|
|
878
|
+
id="tenant_admin",
|
|
879
|
+
name="Tenant Admin",
|
|
880
|
+
description="Full access within a tenant",
|
|
881
|
+
role_type=RoleType.SYSTEM,
|
|
882
|
+
permissions={
|
|
883
|
+
Permission("*", "*", scope="tenant"),
|
|
884
|
+
},
|
|
885
|
+
),
|
|
886
|
+
Role(
|
|
887
|
+
id="viewer",
|
|
888
|
+
name="Viewer",
|
|
889
|
+
description="Read-only access",
|
|
890
|
+
role_type=RoleType.SYSTEM,
|
|
891
|
+
permissions={
|
|
892
|
+
Permission("*", "read"),
|
|
893
|
+
Permission("*", "list"),
|
|
894
|
+
},
|
|
895
|
+
),
|
|
896
|
+
Role(
|
|
897
|
+
id="editor",
|
|
898
|
+
name="Editor",
|
|
899
|
+
description="Read and write access",
|
|
900
|
+
role_type=RoleType.SYSTEM,
|
|
901
|
+
permissions={
|
|
902
|
+
Permission("*", "read"),
|
|
903
|
+
Permission("*", "list"),
|
|
904
|
+
Permission("*", "create"),
|
|
905
|
+
Permission("*", "update"),
|
|
906
|
+
},
|
|
907
|
+
parent_roles={"viewer"},
|
|
908
|
+
),
|
|
909
|
+
Role(
|
|
910
|
+
id="admin",
|
|
911
|
+
name="Admin",
|
|
912
|
+
description="Full access except system settings",
|
|
913
|
+
role_type=RoleType.SYSTEM,
|
|
914
|
+
permissions={
|
|
915
|
+
Permission("*", "*"),
|
|
916
|
+
},
|
|
917
|
+
parent_roles={"editor"},
|
|
918
|
+
),
|
|
919
|
+
]
|
|
920
|
+
|
|
921
|
+
for role in default_roles:
|
|
922
|
+
if not self._role_store.exists(role.id):
|
|
923
|
+
self._role_store.save(role)
|
|
924
|
+
|
|
925
|
+
|
|
926
|
+
# =============================================================================
|
|
927
|
+
# Global Manager
|
|
928
|
+
# =============================================================================
|
|
929
|
+
|
|
930
|
+
|
|
931
|
+
_default_manager: RBACManager | None = None
|
|
932
|
+
_manager_lock = threading.Lock()
|
|
933
|
+
|
|
934
|
+
|
|
935
|
+
def get_rbac_manager() -> RBACManager:
|
|
936
|
+
"""Get the global RBAC manager.
|
|
937
|
+
|
|
938
|
+
Returns:
|
|
939
|
+
RBACManager instance.
|
|
940
|
+
"""
|
|
941
|
+
global _default_manager
|
|
942
|
+
with _manager_lock:
|
|
943
|
+
if _default_manager is None:
|
|
944
|
+
_default_manager = RBACManager()
|
|
945
|
+
return _default_manager
|
|
946
|
+
|
|
947
|
+
|
|
948
|
+
def set_rbac_manager(manager: RBACManager) -> None:
|
|
949
|
+
"""Set the global RBAC manager.
|
|
950
|
+
|
|
951
|
+
Args:
|
|
952
|
+
manager: RBACManager instance to use globally.
|
|
953
|
+
"""
|
|
954
|
+
global _default_manager
|
|
955
|
+
with _manager_lock:
|
|
956
|
+
_default_manager = manager
|
|
957
|
+
|
|
958
|
+
|
|
959
|
+
def configure_rbac_manager(
|
|
960
|
+
role_store: RoleStore | None = None,
|
|
961
|
+
principal_store: PrincipalStore | None = None,
|
|
962
|
+
policy_engine: PolicyEngine | None = None,
|
|
963
|
+
config: RBACManagerConfig | None = None,
|
|
964
|
+
) -> RBACManager:
|
|
965
|
+
"""Configure and set the global RBAC manager.
|
|
966
|
+
|
|
967
|
+
Args:
|
|
968
|
+
role_store: Role storage backend
|
|
969
|
+
principal_store: Principal storage backend
|
|
970
|
+
policy_engine: Policy engine
|
|
971
|
+
config: Manager configuration
|
|
972
|
+
|
|
973
|
+
Returns:
|
|
974
|
+
Configured RBACManager.
|
|
975
|
+
"""
|
|
976
|
+
manager = RBACManager(
|
|
977
|
+
role_store=role_store,
|
|
978
|
+
principal_store=principal_store,
|
|
979
|
+
policy_engine=policy_engine,
|
|
980
|
+
config=config,
|
|
981
|
+
)
|
|
982
|
+
set_rbac_manager(manager)
|
|
983
|
+
return manager
|
|
984
|
+
|
|
985
|
+
|
|
986
|
+
# =============================================================================
|
|
987
|
+
# Convenience Functions
|
|
988
|
+
# =============================================================================
|
|
989
|
+
|
|
990
|
+
|
|
991
|
+
def create_role(name: str, **kwargs: Any) -> Role:
|
|
992
|
+
"""Create a role using the global manager.
|
|
993
|
+
|
|
994
|
+
See RBACManager.create_role for full documentation.
|
|
995
|
+
"""
|
|
996
|
+
return get_rbac_manager().create_role(name=name, **kwargs)
|
|
997
|
+
|
|
998
|
+
|
|
999
|
+
def get_role(role_id: str) -> Role | None:
|
|
1000
|
+
"""Get a role using the global manager.
|
|
1001
|
+
|
|
1002
|
+
See RBACManager.get_role for full documentation.
|
|
1003
|
+
"""
|
|
1004
|
+
return get_rbac_manager().get_role(role_id)
|
|
1005
|
+
|
|
1006
|
+
|
|
1007
|
+
def create_principal(name: str, **kwargs: Any) -> Principal:
|
|
1008
|
+
"""Create a principal using the global manager.
|
|
1009
|
+
|
|
1010
|
+
See RBACManager.create_principal for full documentation.
|
|
1011
|
+
"""
|
|
1012
|
+
return get_rbac_manager().create_principal(name=name, **kwargs)
|
|
1013
|
+
|
|
1014
|
+
|
|
1015
|
+
def get_principal(principal_id: str) -> Principal | None:
|
|
1016
|
+
"""Get a principal using the global manager.
|
|
1017
|
+
|
|
1018
|
+
See RBACManager.get_principal for full documentation.
|
|
1019
|
+
"""
|
|
1020
|
+
return get_rbac_manager().get_principal(principal_id)
|
|
1021
|
+
|
|
1022
|
+
|
|
1023
|
+
def check_permission(
|
|
1024
|
+
principal: Principal | str,
|
|
1025
|
+
resource: str,
|
|
1026
|
+
action: str,
|
|
1027
|
+
) -> AccessDecision:
|
|
1028
|
+
"""Check permission using the global manager.
|
|
1029
|
+
|
|
1030
|
+
See RBACManager.check for full documentation.
|
|
1031
|
+
"""
|
|
1032
|
+
return get_rbac_manager().check(principal, resource, action)
|
|
1033
|
+
|
|
1034
|
+
|
|
1035
|
+
def require_permission(
|
|
1036
|
+
principal: Principal | str,
|
|
1037
|
+
resource: str,
|
|
1038
|
+
action: str,
|
|
1039
|
+
) -> None:
|
|
1040
|
+
"""Require permission using the global manager.
|
|
1041
|
+
|
|
1042
|
+
See RBACManager.require for full documentation.
|
|
1043
|
+
"""
|
|
1044
|
+
get_rbac_manager().require(principal, resource, action)
|
|
1045
|
+
|
|
1046
|
+
|
|
1047
|
+
def current_principal() -> Principal | None:
|
|
1048
|
+
"""Get the current principal from context.
|
|
1049
|
+
|
|
1050
|
+
See RBACManager.current_principal for full documentation.
|
|
1051
|
+
"""
|
|
1052
|
+
return get_rbac_manager().current_principal()
|