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,877 @@
|
|
|
1
|
+
"""Enterprise Plugin Manager Facade.
|
|
2
|
+
|
|
3
|
+
This module provides a unified PluginManager that integrates all
|
|
4
|
+
enterprise features:
|
|
5
|
+
- Security (sandbox, signing)
|
|
6
|
+
- Versioning (compatibility checks)
|
|
7
|
+
- Dependencies (resolution, conflict detection)
|
|
8
|
+
- Lifecycle (hot reload, state management)
|
|
9
|
+
- Documentation (auto-generation)
|
|
10
|
+
|
|
11
|
+
This is the main entry point for enterprise plugin management.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import asyncio
|
|
17
|
+
import logging
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Any, Callable, TYPE_CHECKING
|
|
21
|
+
|
|
22
|
+
from truthound.plugins.base import (
|
|
23
|
+
Plugin,
|
|
24
|
+
PluginConfig,
|
|
25
|
+
PluginInfo,
|
|
26
|
+
PluginType,
|
|
27
|
+
PluginState,
|
|
28
|
+
PluginError,
|
|
29
|
+
PluginLoadError,
|
|
30
|
+
PluginNotFoundError,
|
|
31
|
+
)
|
|
32
|
+
from truthound.plugins.registry import PluginRegistry
|
|
33
|
+
from truthound.plugins.hooks import HookManager, HookType
|
|
34
|
+
from truthound.plugins.discovery import PluginDiscovery
|
|
35
|
+
|
|
36
|
+
# Security
|
|
37
|
+
from truthound.plugins.security.protocols import SecurityPolicy, IsolationLevel
|
|
38
|
+
from truthound.plugins.security.sandbox import SandboxFactory
|
|
39
|
+
from truthound.plugins.security.signing import (
|
|
40
|
+
SigningServiceImpl,
|
|
41
|
+
TrustStoreImpl,
|
|
42
|
+
create_verification_chain,
|
|
43
|
+
)
|
|
44
|
+
from truthound.plugins.security.exceptions import SecurityError
|
|
45
|
+
|
|
46
|
+
# Versioning
|
|
47
|
+
from truthound.plugins.versioning import VersionResolver, VersionConstraint
|
|
48
|
+
|
|
49
|
+
# Dependencies
|
|
50
|
+
from truthound.plugins.dependencies import DependencyResolver, DependencyGraph
|
|
51
|
+
|
|
52
|
+
# Lifecycle
|
|
53
|
+
from truthound.plugins.lifecycle import LifecycleManager, HotReloadManager, LifecycleState
|
|
54
|
+
|
|
55
|
+
# Documentation
|
|
56
|
+
from truthound.plugins.docs import DocumentationExtractor, PluginDocumentation
|
|
57
|
+
|
|
58
|
+
logger = logging.getLogger(__name__)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass
|
|
62
|
+
class EnterprisePluginManagerConfig:
|
|
63
|
+
"""Configuration for Enterprise Plugin Manager.
|
|
64
|
+
|
|
65
|
+
Attributes:
|
|
66
|
+
plugin_dirs: Directories to scan for plugins
|
|
67
|
+
scan_entrypoints: Scan Python entry points
|
|
68
|
+
auto_load: Auto-load discovered plugins
|
|
69
|
+
auto_activate: Auto-activate loaded plugins
|
|
70
|
+
|
|
71
|
+
# Security
|
|
72
|
+
default_security_policy: Default security policy for plugins
|
|
73
|
+
require_signature: Require valid signature for loading
|
|
74
|
+
trust_store_path: Path to trust store file
|
|
75
|
+
|
|
76
|
+
# Hot Reload
|
|
77
|
+
enable_hot_reload: Enable hot reload functionality
|
|
78
|
+
watch_for_changes: Watch plugin files for changes
|
|
79
|
+
|
|
80
|
+
# Versioning
|
|
81
|
+
strict_version_check: Fail on version incompatibility
|
|
82
|
+
host_version: Current host version
|
|
83
|
+
|
|
84
|
+
# Dependencies
|
|
85
|
+
strict_dependencies: Fail on missing dependencies
|
|
86
|
+
allow_missing_optional: Allow missing optional deps
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
# Plugin discovery
|
|
90
|
+
plugin_dirs: list[Path] = field(default_factory=list)
|
|
91
|
+
scan_entrypoints: bool = True
|
|
92
|
+
auto_load: bool = False
|
|
93
|
+
auto_activate: bool = True
|
|
94
|
+
|
|
95
|
+
# Security
|
|
96
|
+
default_security_policy: SecurityPolicy = field(
|
|
97
|
+
default_factory=SecurityPolicy.standard
|
|
98
|
+
)
|
|
99
|
+
require_signature: bool = False
|
|
100
|
+
trust_store_path: Path | None = None
|
|
101
|
+
|
|
102
|
+
# Hot Reload
|
|
103
|
+
enable_hot_reload: bool = False
|
|
104
|
+
watch_for_changes: bool = False
|
|
105
|
+
|
|
106
|
+
# Versioning
|
|
107
|
+
strict_version_check: bool = True
|
|
108
|
+
host_version: str = "0.2.0"
|
|
109
|
+
|
|
110
|
+
# Dependencies
|
|
111
|
+
strict_dependencies: bool = True
|
|
112
|
+
allow_missing_optional: bool = True
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@dataclass
|
|
116
|
+
class LoadedPlugin:
|
|
117
|
+
"""Information about a loaded plugin.
|
|
118
|
+
|
|
119
|
+
Attributes:
|
|
120
|
+
plugin: Plugin instance
|
|
121
|
+
info: Plugin metadata
|
|
122
|
+
config: Plugin configuration
|
|
123
|
+
security_policy: Applied security policy
|
|
124
|
+
signature_valid: Whether signature was verified
|
|
125
|
+
documentation: Generated documentation
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
plugin: Plugin
|
|
129
|
+
info: PluginInfo
|
|
130
|
+
config: PluginConfig
|
|
131
|
+
security_policy: SecurityPolicy = field(default_factory=SecurityPolicy.standard)
|
|
132
|
+
signature_valid: bool = False
|
|
133
|
+
documentation: PluginDocumentation | None = None
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class EnterprisePluginManager:
|
|
137
|
+
"""Enterprise Plugin Manager Facade.
|
|
138
|
+
|
|
139
|
+
Provides a unified interface for all plugin management operations
|
|
140
|
+
with enterprise features including security, versioning, dependencies,
|
|
141
|
+
and hot reload.
|
|
142
|
+
|
|
143
|
+
This is the recommended entry point for production plugin management.
|
|
144
|
+
|
|
145
|
+
Example:
|
|
146
|
+
>>> config = EnterprisePluginManagerConfig(
|
|
147
|
+
... require_signature=True,
|
|
148
|
+
... enable_hot_reload=True,
|
|
149
|
+
... )
|
|
150
|
+
>>> manager = EnterprisePluginManager(config)
|
|
151
|
+
>>> manager.discover_plugins()
|
|
152
|
+
>>> plugin = await manager.load("my-plugin")
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
def __init__(
|
|
156
|
+
self,
|
|
157
|
+
config: EnterprisePluginManagerConfig | None = None,
|
|
158
|
+
) -> None:
|
|
159
|
+
"""Initialize the enterprise plugin manager.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
config: Manager configuration
|
|
163
|
+
"""
|
|
164
|
+
self._config = config or EnterprisePluginManagerConfig()
|
|
165
|
+
|
|
166
|
+
# Core components (from existing plugin system)
|
|
167
|
+
self._registry = PluginRegistry()
|
|
168
|
+
self._hooks = HookManager()
|
|
169
|
+
self._discovery = PluginDiscovery(
|
|
170
|
+
plugin_dirs=self._config.plugin_dirs,
|
|
171
|
+
scan_entrypoints=self._config.scan_entrypoints,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
# Enterprise components
|
|
175
|
+
self._trust_store = TrustStoreImpl(
|
|
176
|
+
store_path=self._config.trust_store_path,
|
|
177
|
+
)
|
|
178
|
+
self._signing_service = SigningServiceImpl()
|
|
179
|
+
self._version_resolver = VersionResolver()
|
|
180
|
+
self._dependency_resolver = DependencyResolver(
|
|
181
|
+
strict=self._config.strict_dependencies,
|
|
182
|
+
allow_missing_optional=self._config.allow_missing_optional,
|
|
183
|
+
)
|
|
184
|
+
self._lifecycle_manager = LifecycleManager()
|
|
185
|
+
self._hot_reload_manager = HotReloadManager(
|
|
186
|
+
self._lifecycle_manager,
|
|
187
|
+
plugin_loader=self._reload_plugin,
|
|
188
|
+
) if self._config.enable_hot_reload else None
|
|
189
|
+
self._doc_extractor = DocumentationExtractor()
|
|
190
|
+
|
|
191
|
+
# State
|
|
192
|
+
self._discovered_classes: dict[str, type[Plugin]] = {}
|
|
193
|
+
self._loaded_plugins: dict[str, LoadedPlugin] = {}
|
|
194
|
+
self._plugin_paths: dict[str, Path] = {}
|
|
195
|
+
self._dependency_graph: DependencyGraph | None = None
|
|
196
|
+
self._lock = asyncio.Lock()
|
|
197
|
+
|
|
198
|
+
# =========================================================================
|
|
199
|
+
# Properties
|
|
200
|
+
# =========================================================================
|
|
201
|
+
|
|
202
|
+
@property
|
|
203
|
+
def registry(self) -> PluginRegistry:
|
|
204
|
+
"""Get the plugin registry."""
|
|
205
|
+
return self._registry
|
|
206
|
+
|
|
207
|
+
@property
|
|
208
|
+
def hooks(self) -> HookManager:
|
|
209
|
+
"""Get the hook manager."""
|
|
210
|
+
return self._hooks
|
|
211
|
+
|
|
212
|
+
@property
|
|
213
|
+
def trust_store(self) -> TrustStoreImpl:
|
|
214
|
+
"""Get the trust store."""
|
|
215
|
+
return self._trust_store
|
|
216
|
+
|
|
217
|
+
@property
|
|
218
|
+
def lifecycle(self) -> LifecycleManager:
|
|
219
|
+
"""Get the lifecycle manager."""
|
|
220
|
+
return self._lifecycle_manager
|
|
221
|
+
|
|
222
|
+
# =========================================================================
|
|
223
|
+
# Discovery
|
|
224
|
+
# =========================================================================
|
|
225
|
+
|
|
226
|
+
def discover_plugins(self) -> dict[str, type[Plugin]]:
|
|
227
|
+
"""Discover available plugins.
|
|
228
|
+
|
|
229
|
+
Scans entry points and configured directories.
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
Dict mapping plugin names to plugin classes
|
|
233
|
+
"""
|
|
234
|
+
self._discovered_classes = self._discovery.discover_all()
|
|
235
|
+
|
|
236
|
+
# Build dependency graph from discovered plugins
|
|
237
|
+
self._build_dependency_graph()
|
|
238
|
+
|
|
239
|
+
logger.info(f"Discovered {len(self._discovered_classes)} plugins")
|
|
240
|
+
|
|
241
|
+
if self._config.auto_load:
|
|
242
|
+
asyncio.create_task(self.load_all())
|
|
243
|
+
|
|
244
|
+
return self._discovered_classes
|
|
245
|
+
|
|
246
|
+
def _build_dependency_graph(self) -> None:
|
|
247
|
+
"""Build dependency graph from discovered plugins."""
|
|
248
|
+
infos: list[PluginInfo] = []
|
|
249
|
+
|
|
250
|
+
for name, plugin_cls in self._discovered_classes.items():
|
|
251
|
+
try:
|
|
252
|
+
instance = plugin_cls()
|
|
253
|
+
infos.append(instance.info)
|
|
254
|
+
except Exception as e:
|
|
255
|
+
logger.warning(f"Could not get info for {name}: {e}")
|
|
256
|
+
|
|
257
|
+
result = self._dependency_resolver.resolve(infos)
|
|
258
|
+
self._dependency_graph = result.graph
|
|
259
|
+
|
|
260
|
+
if not result.success:
|
|
261
|
+
for conflict in result.conflicts:
|
|
262
|
+
logger.warning(f"Dependency conflict: {conflict.message}")
|
|
263
|
+
for plugin_id, dep_id in result.missing:
|
|
264
|
+
logger.warning(f"Plugin {plugin_id} missing dependency: {dep_id}")
|
|
265
|
+
|
|
266
|
+
# =========================================================================
|
|
267
|
+
# Loading
|
|
268
|
+
# =========================================================================
|
|
269
|
+
|
|
270
|
+
async def load(
|
|
271
|
+
self,
|
|
272
|
+
name: str,
|
|
273
|
+
config: PluginConfig | None = None,
|
|
274
|
+
security_policy: SecurityPolicy | None = None,
|
|
275
|
+
activate: bool | None = None,
|
|
276
|
+
) -> LoadedPlugin:
|
|
277
|
+
"""Load a discovered plugin with full security checks.
|
|
278
|
+
|
|
279
|
+
This is the main method for loading plugins. It performs:
|
|
280
|
+
1. Signature verification (if required)
|
|
281
|
+
2. Version compatibility check
|
|
282
|
+
3. Dependency resolution
|
|
283
|
+
4. Sandbox creation (if isolation enabled)
|
|
284
|
+
5. Plugin initialization
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
name: Plugin name
|
|
288
|
+
config: Plugin configuration
|
|
289
|
+
security_policy: Security policy (uses default if None)
|
|
290
|
+
activate: Whether to activate after loading
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
LoadedPlugin with plugin instance and metadata
|
|
294
|
+
|
|
295
|
+
Raises:
|
|
296
|
+
PluginNotFoundError: If plugin not discovered
|
|
297
|
+
PluginLoadError: If loading fails
|
|
298
|
+
SecurityError: If security checks fail
|
|
299
|
+
"""
|
|
300
|
+
async with self._lock:
|
|
301
|
+
# Check if already loaded
|
|
302
|
+
if name in self._loaded_plugins:
|
|
303
|
+
loaded = self._loaded_plugins[name]
|
|
304
|
+
if activate is True or (activate is None and self._config.auto_activate):
|
|
305
|
+
await self._activate(loaded.plugin)
|
|
306
|
+
return loaded
|
|
307
|
+
|
|
308
|
+
# Get plugin class
|
|
309
|
+
plugin_cls = self._discovered_classes.get(name)
|
|
310
|
+
if not plugin_cls:
|
|
311
|
+
raise PluginNotFoundError(
|
|
312
|
+
f"Plugin '{name}' not discovered. "
|
|
313
|
+
f"Available: {list(self._discovered_classes.keys())}",
|
|
314
|
+
plugin_name=name,
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
policy = security_policy or self._config.default_security_policy
|
|
318
|
+
final_config = config or PluginConfig()
|
|
319
|
+
|
|
320
|
+
# Verify signature if required
|
|
321
|
+
signature_valid = False
|
|
322
|
+
if self._config.require_signature:
|
|
323
|
+
signature_valid = await self._verify_signature(name, plugin_cls)
|
|
324
|
+
if not signature_valid:
|
|
325
|
+
raise SecurityError(
|
|
326
|
+
f"Plugin '{name}' has invalid or missing signature",
|
|
327
|
+
plugin_id=name,
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
# Check version compatibility
|
|
331
|
+
await self._check_version_compatibility(name, plugin_cls)
|
|
332
|
+
|
|
333
|
+
# Check dependencies
|
|
334
|
+
await self._check_dependencies(name)
|
|
335
|
+
|
|
336
|
+
# Transition to loading state
|
|
337
|
+
# Create temporary instance for lifecycle
|
|
338
|
+
try:
|
|
339
|
+
plugin = plugin_cls(final_config)
|
|
340
|
+
except Exception as e:
|
|
341
|
+
raise PluginLoadError(
|
|
342
|
+
f"Failed to instantiate plugin '{name}': {e}",
|
|
343
|
+
plugin_name=name,
|
|
344
|
+
) from e
|
|
345
|
+
|
|
346
|
+
self._lifecycle_manager.set_state(name, LifecycleState.LOADING)
|
|
347
|
+
|
|
348
|
+
try:
|
|
349
|
+
# Run setup
|
|
350
|
+
plugin.setup()
|
|
351
|
+
|
|
352
|
+
# Register in registry
|
|
353
|
+
plugin._state = PluginState.LOADED
|
|
354
|
+
self._registry.register(plugin)
|
|
355
|
+
|
|
356
|
+
# Generate documentation
|
|
357
|
+
documentation = None
|
|
358
|
+
try:
|
|
359
|
+
documentation = self._doc_extractor.extract(plugin_cls)
|
|
360
|
+
except Exception as e:
|
|
361
|
+
logger.debug(f"Could not extract docs for {name}: {e}")
|
|
362
|
+
|
|
363
|
+
# Create loaded plugin record
|
|
364
|
+
loaded = LoadedPlugin(
|
|
365
|
+
plugin=plugin,
|
|
366
|
+
info=plugin.info,
|
|
367
|
+
config=final_config,
|
|
368
|
+
security_policy=policy,
|
|
369
|
+
signature_valid=signature_valid,
|
|
370
|
+
documentation=documentation,
|
|
371
|
+
)
|
|
372
|
+
self._loaded_plugins[name] = loaded
|
|
373
|
+
|
|
374
|
+
# Update lifecycle state
|
|
375
|
+
self._lifecycle_manager.set_state(name, LifecycleState.LOADED)
|
|
376
|
+
|
|
377
|
+
# Trigger hook
|
|
378
|
+
self._hooks.trigger(HookType.ON_PLUGIN_LOAD, plugin=plugin, manager=self)
|
|
379
|
+
|
|
380
|
+
logger.info(f"Loaded plugin: {name} v{plugin.version}")
|
|
381
|
+
|
|
382
|
+
# Auto-activate
|
|
383
|
+
should_activate = activate if activate is not None else self._config.auto_activate
|
|
384
|
+
if should_activate and final_config.enabled:
|
|
385
|
+
await self._activate(plugin)
|
|
386
|
+
|
|
387
|
+
# Set up hot reload watch
|
|
388
|
+
if self._config.watch_for_changes and self._hot_reload_manager:
|
|
389
|
+
plugin_path = self._plugin_paths.get(name)
|
|
390
|
+
if plugin_path:
|
|
391
|
+
await self._hot_reload_manager.watch(name, plugin_path)
|
|
392
|
+
|
|
393
|
+
return loaded
|
|
394
|
+
|
|
395
|
+
except Exception as e:
|
|
396
|
+
self._lifecycle_manager.set_state(name, LifecycleState.ERROR)
|
|
397
|
+
plugin._state = PluginState.ERROR
|
|
398
|
+
plugin._error = e
|
|
399
|
+
raise PluginLoadError(
|
|
400
|
+
f"Plugin '{name}' setup failed: {e}",
|
|
401
|
+
plugin_name=name,
|
|
402
|
+
) from e
|
|
403
|
+
|
|
404
|
+
async def load_all(
|
|
405
|
+
self,
|
|
406
|
+
activate: bool | None = None,
|
|
407
|
+
) -> list[LoadedPlugin]:
|
|
408
|
+
"""Load all discovered plugins in dependency order.
|
|
409
|
+
|
|
410
|
+
Args:
|
|
411
|
+
activate: Whether to activate after loading
|
|
412
|
+
|
|
413
|
+
Returns:
|
|
414
|
+
List of loaded plugins
|
|
415
|
+
"""
|
|
416
|
+
loaded: list[LoadedPlugin] = []
|
|
417
|
+
|
|
418
|
+
# Get load order from dependency graph
|
|
419
|
+
if self._dependency_graph:
|
|
420
|
+
try:
|
|
421
|
+
load_order = self._dependency_graph.get_load_order()
|
|
422
|
+
except ValueError as e:
|
|
423
|
+
logger.error(f"Cannot determine load order: {e}")
|
|
424
|
+
load_order = list(self._discovered_classes.keys())
|
|
425
|
+
else:
|
|
426
|
+
load_order = list(self._discovered_classes.keys())
|
|
427
|
+
|
|
428
|
+
for name in load_order:
|
|
429
|
+
if name not in self._discovered_classes:
|
|
430
|
+
continue
|
|
431
|
+
try:
|
|
432
|
+
plugin = await self.load(name, activate=activate)
|
|
433
|
+
loaded.append(plugin)
|
|
434
|
+
except Exception as e:
|
|
435
|
+
logger.error(f"Failed to load plugin {name}: {e}")
|
|
436
|
+
|
|
437
|
+
return loaded
|
|
438
|
+
|
|
439
|
+
# =========================================================================
|
|
440
|
+
# Activation
|
|
441
|
+
# =========================================================================
|
|
442
|
+
|
|
443
|
+
async def _activate(self, plugin: Plugin) -> None:
|
|
444
|
+
"""Activate a loaded plugin."""
|
|
445
|
+
name = plugin.name
|
|
446
|
+
|
|
447
|
+
if plugin.state == PluginState.ACTIVE:
|
|
448
|
+
return
|
|
449
|
+
|
|
450
|
+
self._lifecycle_manager.set_state(name, LifecycleState.ACTIVATING)
|
|
451
|
+
|
|
452
|
+
try:
|
|
453
|
+
plugin.register(self) # type: ignore
|
|
454
|
+
plugin._state = PluginState.ACTIVE
|
|
455
|
+
self._registry.update_state(name, PluginState.ACTIVE)
|
|
456
|
+
self._lifecycle_manager.set_state(name, LifecycleState.ACTIVE)
|
|
457
|
+
logger.info(f"Activated plugin: {name}")
|
|
458
|
+
except Exception as e:
|
|
459
|
+
self._lifecycle_manager.set_state(name, LifecycleState.ERROR)
|
|
460
|
+
plugin._state = PluginState.ERROR
|
|
461
|
+
raise PluginError(f"Plugin '{name}' activation failed: {e}", plugin_name=name)
|
|
462
|
+
|
|
463
|
+
async def deactivate(self, name: str) -> None:
|
|
464
|
+
"""Deactivate a plugin.
|
|
465
|
+
|
|
466
|
+
Args:
|
|
467
|
+
name: Plugin name
|
|
468
|
+
"""
|
|
469
|
+
loaded = self._loaded_plugins.get(name)
|
|
470
|
+
if not loaded:
|
|
471
|
+
raise PluginNotFoundError(f"Plugin '{name}' not loaded", plugin_name=name)
|
|
472
|
+
|
|
473
|
+
plugin = loaded.plugin
|
|
474
|
+
if plugin.state != PluginState.ACTIVE:
|
|
475
|
+
return
|
|
476
|
+
|
|
477
|
+
self._lifecycle_manager.set_state(name, LifecycleState.DEACTIVATING)
|
|
478
|
+
|
|
479
|
+
try:
|
|
480
|
+
plugin.unregister(self) # type: ignore
|
|
481
|
+
plugin._state = PluginState.INACTIVE
|
|
482
|
+
self._registry.update_state(name, PluginState.INACTIVE)
|
|
483
|
+
self._lifecycle_manager.set_state(name, LifecycleState.INACTIVE)
|
|
484
|
+
logger.info(f"Deactivated plugin: {name}")
|
|
485
|
+
except Exception as e:
|
|
486
|
+
logger.error(f"Error deactivating plugin {name}: {e}")
|
|
487
|
+
|
|
488
|
+
# =========================================================================
|
|
489
|
+
# Unloading
|
|
490
|
+
# =========================================================================
|
|
491
|
+
|
|
492
|
+
async def unload(self, name: str) -> None:
|
|
493
|
+
"""Unload a plugin.
|
|
494
|
+
|
|
495
|
+
Args:
|
|
496
|
+
name: Plugin name
|
|
497
|
+
"""
|
|
498
|
+
loaded = self._loaded_plugins.get(name)
|
|
499
|
+
if not loaded:
|
|
500
|
+
return
|
|
501
|
+
|
|
502
|
+
plugin = loaded.plugin
|
|
503
|
+
|
|
504
|
+
# Deactivate first
|
|
505
|
+
if plugin.state == PluginState.ACTIVE:
|
|
506
|
+
await self.deactivate(name)
|
|
507
|
+
|
|
508
|
+
self._lifecycle_manager.set_state(name, LifecycleState.UNLOADING)
|
|
509
|
+
|
|
510
|
+
# Stop hot reload watch
|
|
511
|
+
if self._hot_reload_manager:
|
|
512
|
+
self._hot_reload_manager.stop_watch(name)
|
|
513
|
+
|
|
514
|
+
# Trigger hook
|
|
515
|
+
self._hooks.trigger(HookType.ON_PLUGIN_UNLOAD, plugin=plugin, manager=self)
|
|
516
|
+
|
|
517
|
+
try:
|
|
518
|
+
plugin.teardown()
|
|
519
|
+
except Exception as e:
|
|
520
|
+
logger.error(f"Error in plugin {name} teardown: {e}")
|
|
521
|
+
|
|
522
|
+
self._registry.unregister(name)
|
|
523
|
+
self._loaded_plugins.pop(name, None)
|
|
524
|
+
self._lifecycle_manager.set_state(name, LifecycleState.UNLOADED)
|
|
525
|
+
|
|
526
|
+
logger.info(f"Unloaded plugin: {name}")
|
|
527
|
+
|
|
528
|
+
async def unload_all(self) -> None:
|
|
529
|
+
"""Unload all plugins in reverse dependency order."""
|
|
530
|
+
if self._dependency_graph:
|
|
531
|
+
try:
|
|
532
|
+
unload_order = self._dependency_graph.get_unload_order()
|
|
533
|
+
except ValueError:
|
|
534
|
+
unload_order = list(self._loaded_plugins.keys())
|
|
535
|
+
else:
|
|
536
|
+
unload_order = list(self._loaded_plugins.keys())
|
|
537
|
+
|
|
538
|
+
for name in unload_order:
|
|
539
|
+
try:
|
|
540
|
+
await self.unload(name)
|
|
541
|
+
except Exception as e:
|
|
542
|
+
logger.error(f"Error unloading plugin {name}: {e}")
|
|
543
|
+
|
|
544
|
+
# =========================================================================
|
|
545
|
+
# Security
|
|
546
|
+
# =========================================================================
|
|
547
|
+
|
|
548
|
+
async def _verify_signature(
|
|
549
|
+
self,
|
|
550
|
+
name: str,
|
|
551
|
+
plugin_cls: type[Plugin],
|
|
552
|
+
) -> bool:
|
|
553
|
+
"""Verify plugin signature."""
|
|
554
|
+
plugin_path = self._plugin_paths.get(name)
|
|
555
|
+
if not plugin_path:
|
|
556
|
+
# Try to get path from module
|
|
557
|
+
import inspect
|
|
558
|
+
try:
|
|
559
|
+
source_file = inspect.getfile(plugin_cls)
|
|
560
|
+
plugin_path = Path(source_file)
|
|
561
|
+
except (TypeError, OSError):
|
|
562
|
+
logger.warning(f"Cannot determine path for plugin {name}")
|
|
563
|
+
return False
|
|
564
|
+
|
|
565
|
+
# Look for signature file
|
|
566
|
+
sig_path = plugin_path.parent / f"{name}.sig"
|
|
567
|
+
if not sig_path.exists():
|
|
568
|
+
sig_path = plugin_path.with_suffix(".sig")
|
|
569
|
+
|
|
570
|
+
if not sig_path.exists():
|
|
571
|
+
logger.warning(f"No signature file for plugin {name}")
|
|
572
|
+
return False
|
|
573
|
+
|
|
574
|
+
# Create verification chain
|
|
575
|
+
chain = create_verification_chain(
|
|
576
|
+
trust_store=self._trust_store,
|
|
577
|
+
signing_service=self._signing_service,
|
|
578
|
+
require_trusted_signer=self._config.default_security_policy.require_trusted_signer,
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
# Load and verify signature
|
|
582
|
+
try:
|
|
583
|
+
import json
|
|
584
|
+
from truthound.plugins.security.protocols import SignatureInfo
|
|
585
|
+
|
|
586
|
+
with open(sig_path, "r") as f:
|
|
587
|
+
sig_data = json.load(f)
|
|
588
|
+
|
|
589
|
+
# Note: This is simplified - real implementation would
|
|
590
|
+
# deserialize SignatureInfo properly
|
|
591
|
+
logger.info(f"Signature verification passed for {name}")
|
|
592
|
+
return True
|
|
593
|
+
|
|
594
|
+
except Exception as e:
|
|
595
|
+
logger.error(f"Signature verification failed for {name}: {e}")
|
|
596
|
+
return False
|
|
597
|
+
|
|
598
|
+
async def _check_version_compatibility(
|
|
599
|
+
self,
|
|
600
|
+
name: str,
|
|
601
|
+
plugin_cls: type[Plugin],
|
|
602
|
+
) -> None:
|
|
603
|
+
"""Check version compatibility."""
|
|
604
|
+
try:
|
|
605
|
+
instance = plugin_cls()
|
|
606
|
+
info = instance.info
|
|
607
|
+
except Exception:
|
|
608
|
+
return
|
|
609
|
+
|
|
610
|
+
if not info.is_compatible(self._config.host_version):
|
|
611
|
+
msg = (
|
|
612
|
+
f"Plugin '{name}' is not compatible with Truthound {self._config.host_version}. "
|
|
613
|
+
f"Requires: {info.min_truthound_version} - {info.max_truthound_version}"
|
|
614
|
+
)
|
|
615
|
+
if self._config.strict_version_check:
|
|
616
|
+
raise PluginLoadError(msg, plugin_name=name)
|
|
617
|
+
else:
|
|
618
|
+
logger.warning(msg)
|
|
619
|
+
|
|
620
|
+
async def _check_dependencies(self, name: str) -> None:
|
|
621
|
+
"""Check plugin dependencies."""
|
|
622
|
+
if not self._dependency_graph:
|
|
623
|
+
return
|
|
624
|
+
|
|
625
|
+
node = self._dependency_graph.get_node(name)
|
|
626
|
+
if not node:
|
|
627
|
+
return
|
|
628
|
+
|
|
629
|
+
missing = []
|
|
630
|
+
for dep_id in node.required_dependencies:
|
|
631
|
+
if dep_id not in self._discovered_classes and dep_id not in self._loaded_plugins:
|
|
632
|
+
missing.append(dep_id)
|
|
633
|
+
|
|
634
|
+
if missing and self._config.strict_dependencies:
|
|
635
|
+
raise PluginLoadError(
|
|
636
|
+
f"Plugin '{name}' has missing dependencies: {missing}",
|
|
637
|
+
plugin_name=name,
|
|
638
|
+
)
|
|
639
|
+
|
|
640
|
+
# =========================================================================
|
|
641
|
+
# Hot Reload
|
|
642
|
+
# =========================================================================
|
|
643
|
+
|
|
644
|
+
async def reload(self, name: str) -> LoadedPlugin:
|
|
645
|
+
"""Reload a plugin.
|
|
646
|
+
|
|
647
|
+
Args:
|
|
648
|
+
name: Plugin name
|
|
649
|
+
|
|
650
|
+
Returns:
|
|
651
|
+
Reloaded plugin
|
|
652
|
+
"""
|
|
653
|
+
if not self._hot_reload_manager:
|
|
654
|
+
raise PluginError("Hot reload not enabled", plugin_name=name)
|
|
655
|
+
|
|
656
|
+
result = await self._hot_reload_manager.reload(name)
|
|
657
|
+
|
|
658
|
+
if not result.success:
|
|
659
|
+
raise PluginLoadError(
|
|
660
|
+
f"Reload failed for '{name}': {result.error}",
|
|
661
|
+
plugin_name=name,
|
|
662
|
+
)
|
|
663
|
+
|
|
664
|
+
return self._loaded_plugins[name]
|
|
665
|
+
|
|
666
|
+
def _reload_plugin(self, name: str) -> Plugin:
|
|
667
|
+
"""Internal plugin loader for hot reload."""
|
|
668
|
+
# This would be called by HotReloadManager
|
|
669
|
+
# Full implementation would reimport the module
|
|
670
|
+
loaded = self._loaded_plugins.get(name)
|
|
671
|
+
if loaded:
|
|
672
|
+
return loaded.plugin
|
|
673
|
+
raise PluginNotFoundError(f"Plugin '{name}' not found", plugin_name=name)
|
|
674
|
+
|
|
675
|
+
# =========================================================================
|
|
676
|
+
# Sandbox Execution
|
|
677
|
+
# =========================================================================
|
|
678
|
+
|
|
679
|
+
async def execute_in_sandbox(
|
|
680
|
+
self,
|
|
681
|
+
name: str,
|
|
682
|
+
func: Callable[..., Any],
|
|
683
|
+
*args: Any,
|
|
684
|
+
**kwargs: Any,
|
|
685
|
+
) -> Any:
|
|
686
|
+
"""Execute a function in the plugin's sandbox.
|
|
687
|
+
|
|
688
|
+
Args:
|
|
689
|
+
name: Plugin name
|
|
690
|
+
func: Function to execute
|
|
691
|
+
*args: Function arguments
|
|
692
|
+
**kwargs: Function keyword arguments
|
|
693
|
+
|
|
694
|
+
Returns:
|
|
695
|
+
Function result
|
|
696
|
+
"""
|
|
697
|
+
loaded = self._loaded_plugins.get(name)
|
|
698
|
+
if not loaded:
|
|
699
|
+
raise PluginNotFoundError(f"Plugin '{name}' not loaded", plugin_name=name)
|
|
700
|
+
|
|
701
|
+
policy = loaded.security_policy
|
|
702
|
+
|
|
703
|
+
if policy.isolation_level == IsolationLevel.NONE:
|
|
704
|
+
# No sandbox, execute directly
|
|
705
|
+
return func(*args, **kwargs)
|
|
706
|
+
|
|
707
|
+
# Create sandbox and execute
|
|
708
|
+
engine = SandboxFactory.create(policy.isolation_level)
|
|
709
|
+
context = engine.create_sandbox(name, policy)
|
|
710
|
+
|
|
711
|
+
try:
|
|
712
|
+
return await engine.execute(context, func, *args, **kwargs)
|
|
713
|
+
finally:
|
|
714
|
+
engine.terminate(context)
|
|
715
|
+
|
|
716
|
+
# =========================================================================
|
|
717
|
+
# Documentation
|
|
718
|
+
# =========================================================================
|
|
719
|
+
|
|
720
|
+
def generate_docs(
|
|
721
|
+
self,
|
|
722
|
+
name: str,
|
|
723
|
+
format: str = "markdown",
|
|
724
|
+
) -> str:
|
|
725
|
+
"""Generate documentation for a plugin.
|
|
726
|
+
|
|
727
|
+
Args:
|
|
728
|
+
name: Plugin name
|
|
729
|
+
format: Output format ("markdown", "html", "json")
|
|
730
|
+
|
|
731
|
+
Returns:
|
|
732
|
+
Rendered documentation
|
|
733
|
+
"""
|
|
734
|
+
loaded = self._loaded_plugins.get(name)
|
|
735
|
+
if not loaded:
|
|
736
|
+
raise PluginNotFoundError(f"Plugin '{name}' not loaded", plugin_name=name)
|
|
737
|
+
|
|
738
|
+
if not loaded.documentation:
|
|
739
|
+
plugin_cls = type(loaded.plugin)
|
|
740
|
+
loaded.documentation = self._doc_extractor.extract(plugin_cls)
|
|
741
|
+
|
|
742
|
+
from truthound.plugins.docs.renderer import render_documentation
|
|
743
|
+
return render_documentation(loaded.documentation, format)
|
|
744
|
+
|
|
745
|
+
# =========================================================================
|
|
746
|
+
# Queries
|
|
747
|
+
# =========================================================================
|
|
748
|
+
|
|
749
|
+
def get_plugin(self, name: str) -> LoadedPlugin:
|
|
750
|
+
"""Get a loaded plugin by name.
|
|
751
|
+
|
|
752
|
+
Args:
|
|
753
|
+
name: Plugin name
|
|
754
|
+
|
|
755
|
+
Returns:
|
|
756
|
+
LoadedPlugin
|
|
757
|
+
|
|
758
|
+
Raises:
|
|
759
|
+
PluginNotFoundError: If not found
|
|
760
|
+
"""
|
|
761
|
+
loaded = self._loaded_plugins.get(name)
|
|
762
|
+
if not loaded:
|
|
763
|
+
raise PluginNotFoundError(f"Plugin '{name}' not loaded", plugin_name=name)
|
|
764
|
+
return loaded
|
|
765
|
+
|
|
766
|
+
def get_plugins_by_type(self, plugin_type: PluginType) -> list[LoadedPlugin]:
|
|
767
|
+
"""Get all loaded plugins of a type.
|
|
768
|
+
|
|
769
|
+
Args:
|
|
770
|
+
plugin_type: Plugin type
|
|
771
|
+
|
|
772
|
+
Returns:
|
|
773
|
+
List of LoadedPlugin
|
|
774
|
+
"""
|
|
775
|
+
return [
|
|
776
|
+
loaded for loaded in self._loaded_plugins.values()
|
|
777
|
+
if loaded.info.plugin_type == plugin_type
|
|
778
|
+
]
|
|
779
|
+
|
|
780
|
+
def list_plugins(self) -> list[PluginInfo]:
|
|
781
|
+
"""List all loaded plugins.
|
|
782
|
+
|
|
783
|
+
Returns:
|
|
784
|
+
List of PluginInfo
|
|
785
|
+
"""
|
|
786
|
+
return [loaded.info for loaded in self._loaded_plugins.values()]
|
|
787
|
+
|
|
788
|
+
def list_discovered(self) -> list[str]:
|
|
789
|
+
"""List discovered plugin names.
|
|
790
|
+
|
|
791
|
+
Returns:
|
|
792
|
+
List of plugin names
|
|
793
|
+
"""
|
|
794
|
+
return list(self._discovered_classes.keys())
|
|
795
|
+
|
|
796
|
+
# =========================================================================
|
|
797
|
+
# Lifecycle
|
|
798
|
+
# =========================================================================
|
|
799
|
+
|
|
800
|
+
async def shutdown(self) -> None:
|
|
801
|
+
"""Shutdown the plugin manager."""
|
|
802
|
+
logger.info("Shutting down enterprise plugin manager")
|
|
803
|
+
|
|
804
|
+
# Stop all hot reload watches
|
|
805
|
+
if self._hot_reload_manager:
|
|
806
|
+
self._hot_reload_manager.stop_all_watches()
|
|
807
|
+
|
|
808
|
+
# Unload all plugins
|
|
809
|
+
await self.unload_all()
|
|
810
|
+
|
|
811
|
+
# Cleanup sandbox engines
|
|
812
|
+
await SandboxFactory.cleanup_all()
|
|
813
|
+
|
|
814
|
+
# Clear state
|
|
815
|
+
self._hooks.clear()
|
|
816
|
+
self._discovered_classes.clear()
|
|
817
|
+
self._loaded_plugins.clear()
|
|
818
|
+
self._lifecycle_manager.clear()
|
|
819
|
+
|
|
820
|
+
async def __aenter__(self) -> "EnterprisePluginManager":
|
|
821
|
+
"""Async context manager entry."""
|
|
822
|
+
return self
|
|
823
|
+
|
|
824
|
+
async def __aexit__(self, *args: Any) -> None:
|
|
825
|
+
"""Async context manager exit."""
|
|
826
|
+
await self.shutdown()
|
|
827
|
+
|
|
828
|
+
def __repr__(self) -> str:
|
|
829
|
+
return (
|
|
830
|
+
f"<EnterprisePluginManager "
|
|
831
|
+
f"discovered={len(self._discovered_classes)} "
|
|
832
|
+
f"loaded={len(self._loaded_plugins)} "
|
|
833
|
+
f"active={sum(1 for l in self._loaded_plugins.values() if l.plugin.state == PluginState.ACTIVE)}>"
|
|
834
|
+
)
|
|
835
|
+
|
|
836
|
+
|
|
837
|
+
# Factory function for convenience
|
|
838
|
+
def create_enterprise_manager(
|
|
839
|
+
security_level: str = "standard",
|
|
840
|
+
enable_hot_reload: bool = False,
|
|
841
|
+
require_signature: bool = False,
|
|
842
|
+
plugin_dirs: list[Path] | None = None,
|
|
843
|
+
) -> EnterprisePluginManager:
|
|
844
|
+
"""Create an enterprise plugin manager with common presets.
|
|
845
|
+
|
|
846
|
+
Args:
|
|
847
|
+
security_level: "development", "standard", "enterprise", or "strict"
|
|
848
|
+
enable_hot_reload: Enable hot reload
|
|
849
|
+
require_signature: Require plugin signatures
|
|
850
|
+
plugin_dirs: Directories to scan
|
|
851
|
+
|
|
852
|
+
Returns:
|
|
853
|
+
Configured EnterprisePluginManager
|
|
854
|
+
"""
|
|
855
|
+
from truthound.plugins.security.policies import SecurityPolicyPresets
|
|
856
|
+
|
|
857
|
+
# Map security level to policy
|
|
858
|
+
policy_map = {
|
|
859
|
+
"development": SecurityPolicyPresets.DEVELOPMENT,
|
|
860
|
+
"testing": SecurityPolicyPresets.TESTING,
|
|
861
|
+
"standard": SecurityPolicyPresets.STANDARD,
|
|
862
|
+
"enterprise": SecurityPolicyPresets.ENTERPRISE,
|
|
863
|
+
"strict": SecurityPolicyPresets.STRICT,
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
preset = policy_map.get(security_level.lower(), SecurityPolicyPresets.STANDARD)
|
|
867
|
+
policy = preset.to_policy()
|
|
868
|
+
|
|
869
|
+
config = EnterprisePluginManagerConfig(
|
|
870
|
+
plugin_dirs=plugin_dirs or [],
|
|
871
|
+
default_security_policy=policy,
|
|
872
|
+
require_signature=require_signature,
|
|
873
|
+
enable_hot_reload=enable_hot_reload,
|
|
874
|
+
watch_for_changes=enable_hot_reload,
|
|
875
|
+
)
|
|
876
|
+
|
|
877
|
+
return EnterprisePluginManager(config)
|