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,1018 @@
|
|
|
1
|
+
"""OpenTelemetry integration testing and validation utilities.
|
|
2
|
+
|
|
3
|
+
This module provides comprehensive testing and validation for OpenTelemetry
|
|
4
|
+
integration, including mock exporters, integration validators, and
|
|
5
|
+
diagnostic tools for Jaeger/Zipkin connectivity.
|
|
6
|
+
|
|
7
|
+
Key Features:
|
|
8
|
+
- Mock span exporters for testing
|
|
9
|
+
- Integration validators for Jaeger/Zipkin
|
|
10
|
+
- Diagnostic tools for troubleshooting
|
|
11
|
+
- End-to-end tracing verification
|
|
12
|
+
- Configuration validation
|
|
13
|
+
|
|
14
|
+
Example:
|
|
15
|
+
from truthound.observability.tracing.integration import (
|
|
16
|
+
TracingValidator,
|
|
17
|
+
MockSpanExporter,
|
|
18
|
+
validate_jaeger_connection,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# Validate Jaeger connection
|
|
22
|
+
result = validate_jaeger_connection("http://localhost:14268")
|
|
23
|
+
if result.success:
|
|
24
|
+
print("Jaeger is reachable")
|
|
25
|
+
|
|
26
|
+
# Run integration tests
|
|
27
|
+
validator = TracingValidator()
|
|
28
|
+
report = validator.validate_all()
|
|
29
|
+
print(report)
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from __future__ import annotations
|
|
33
|
+
|
|
34
|
+
import json
|
|
35
|
+
import socket
|
|
36
|
+
import threading
|
|
37
|
+
import time
|
|
38
|
+
from dataclasses import dataclass, field
|
|
39
|
+
from datetime import datetime
|
|
40
|
+
from enum import Enum
|
|
41
|
+
from typing import Any, Callable
|
|
42
|
+
from urllib.parse import urlparse
|
|
43
|
+
|
|
44
|
+
from truthound.observability.tracing.span import (
|
|
45
|
+
Span,
|
|
46
|
+
SpanContextData,
|
|
47
|
+
SpanKind,
|
|
48
|
+
StatusCode,
|
|
49
|
+
)
|
|
50
|
+
from truthound.observability.tracing.processor import SpanProcessor
|
|
51
|
+
from truthound.observability.tracing.exporter import SpanExporter, ExportResult
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# =============================================================================
|
|
55
|
+
# Mock Exporter for Testing
|
|
56
|
+
# =============================================================================
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class MockSpanExporter(SpanExporter):
|
|
60
|
+
"""Mock span exporter for testing purposes.
|
|
61
|
+
|
|
62
|
+
Collects exported spans in memory for verification.
|
|
63
|
+
|
|
64
|
+
Example:
|
|
65
|
+
exporter = MockSpanExporter()
|
|
66
|
+
provider.add_processor(SimpleSpanProcessor(exporter))
|
|
67
|
+
|
|
68
|
+
with tracer.start_as_current_span("test"):
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
assert len(exporter.spans) == 1
|
|
72
|
+
assert exporter.spans[0].name == "test"
|
|
73
|
+
|
|
74
|
+
Attributes:
|
|
75
|
+
spans: List of exported spans
|
|
76
|
+
export_count: Number of export calls
|
|
77
|
+
last_export_time: Time of last export
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
def __init__(self, max_spans: int = 10000):
|
|
81
|
+
"""Initialize mock exporter.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
max_spans: Maximum spans to keep in memory
|
|
85
|
+
"""
|
|
86
|
+
self.max_spans = max_spans
|
|
87
|
+
self.spans: list[Span] = []
|
|
88
|
+
self.export_count = 0
|
|
89
|
+
self.last_export_time: datetime | None = None
|
|
90
|
+
self._lock = threading.Lock()
|
|
91
|
+
self._shutdown = False
|
|
92
|
+
|
|
93
|
+
def export(self, spans: list[Span]) -> ExportResult:
|
|
94
|
+
"""Export spans to memory.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
spans: Spans to export
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
ExportResult.SUCCESS
|
|
101
|
+
"""
|
|
102
|
+
if self._shutdown:
|
|
103
|
+
return ExportResult.SUCCESS
|
|
104
|
+
|
|
105
|
+
with self._lock:
|
|
106
|
+
self.spans.extend(spans)
|
|
107
|
+
self.export_count += 1
|
|
108
|
+
self.last_export_time = datetime.now()
|
|
109
|
+
|
|
110
|
+
# Trim if over limit
|
|
111
|
+
if len(self.spans) > self.max_spans:
|
|
112
|
+
self.spans = self.spans[-self.max_spans:]
|
|
113
|
+
|
|
114
|
+
return ExportResult.SUCCESS
|
|
115
|
+
|
|
116
|
+
def shutdown(self) -> bool:
|
|
117
|
+
"""Shutdown exporter."""
|
|
118
|
+
self._shutdown = True
|
|
119
|
+
return True
|
|
120
|
+
|
|
121
|
+
def force_flush(self, timeout_millis: int = 30000) -> bool:
|
|
122
|
+
"""Force flush (no-op for mock)."""
|
|
123
|
+
return True
|
|
124
|
+
|
|
125
|
+
def clear(self) -> None:
|
|
126
|
+
"""Clear collected spans."""
|
|
127
|
+
with self._lock:
|
|
128
|
+
self.spans.clear()
|
|
129
|
+
self.export_count = 0
|
|
130
|
+
|
|
131
|
+
def get_span_by_name(self, name: str) -> Span | None:
|
|
132
|
+
"""Find span by name.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
name: Span name to find
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
First matching span or None
|
|
139
|
+
"""
|
|
140
|
+
with self._lock:
|
|
141
|
+
for span in self.spans:
|
|
142
|
+
if span.name == name:
|
|
143
|
+
return span
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
def get_spans_by_name(self, name: str) -> list[Span]:
|
|
147
|
+
"""Find all spans with given name.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
name: Span name to find
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
List of matching spans
|
|
154
|
+
"""
|
|
155
|
+
with self._lock:
|
|
156
|
+
return [s for s in self.spans if s.name == name]
|
|
157
|
+
|
|
158
|
+
def get_span_by_trace_id(self, trace_id: str) -> list[Span]:
|
|
159
|
+
"""Find all spans in a trace.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
trace_id: Trace ID to find
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
List of spans in the trace
|
|
166
|
+
"""
|
|
167
|
+
with self._lock:
|
|
168
|
+
return [
|
|
169
|
+
s for s in self.spans
|
|
170
|
+
if s.context.trace_id == trace_id
|
|
171
|
+
]
|
|
172
|
+
|
|
173
|
+
def get_stats(self) -> dict[str, Any]:
|
|
174
|
+
"""Get exporter statistics.
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
Statistics dictionary
|
|
178
|
+
"""
|
|
179
|
+
with self._lock:
|
|
180
|
+
return {
|
|
181
|
+
"span_count": len(self.spans),
|
|
182
|
+
"export_count": self.export_count,
|
|
183
|
+
"last_export_time": (
|
|
184
|
+
self.last_export_time.isoformat()
|
|
185
|
+
if self.last_export_time else None
|
|
186
|
+
),
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
# =============================================================================
|
|
191
|
+
# Recording Processor for Testing
|
|
192
|
+
# =============================================================================
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class RecordingSpanProcessor(SpanProcessor):
|
|
196
|
+
"""Span processor that records events for testing.
|
|
197
|
+
|
|
198
|
+
Records all start and end events for verification.
|
|
199
|
+
|
|
200
|
+
Example:
|
|
201
|
+
processor = RecordingSpanProcessor()
|
|
202
|
+
provider.add_processor(processor)
|
|
203
|
+
|
|
204
|
+
with tracer.start_as_current_span("test"):
|
|
205
|
+
pass
|
|
206
|
+
|
|
207
|
+
assert len(processor.started_spans) == 1
|
|
208
|
+
assert len(processor.ended_spans) == 1
|
|
209
|
+
"""
|
|
210
|
+
|
|
211
|
+
def __init__(self):
|
|
212
|
+
"""Initialize processor."""
|
|
213
|
+
self.started_spans: list[tuple[Span, SpanContextData | None]] = []
|
|
214
|
+
self.ended_spans: list[Span] = []
|
|
215
|
+
self._lock = threading.Lock()
|
|
216
|
+
|
|
217
|
+
def on_start(self, span: Span, parent_context: SpanContextData | None) -> None:
|
|
218
|
+
"""Record span start.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
span: Started span
|
|
222
|
+
parent_context: Parent span context
|
|
223
|
+
"""
|
|
224
|
+
with self._lock:
|
|
225
|
+
self.started_spans.append((span, parent_context))
|
|
226
|
+
|
|
227
|
+
def on_end(self, span: Span) -> None:
|
|
228
|
+
"""Record span end.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
span: Ended span
|
|
232
|
+
"""
|
|
233
|
+
with self._lock:
|
|
234
|
+
self.ended_spans.append(span)
|
|
235
|
+
|
|
236
|
+
def shutdown(self) -> bool:
|
|
237
|
+
"""Shutdown processor."""
|
|
238
|
+
return True
|
|
239
|
+
|
|
240
|
+
def force_flush(self, timeout_millis: int = 30000) -> bool:
|
|
241
|
+
"""Force flush (no-op)."""
|
|
242
|
+
return True
|
|
243
|
+
|
|
244
|
+
def clear(self) -> None:
|
|
245
|
+
"""Clear recorded spans."""
|
|
246
|
+
with self._lock:
|
|
247
|
+
self.started_spans.clear()
|
|
248
|
+
self.ended_spans.clear()
|
|
249
|
+
|
|
250
|
+
def get_stats(self) -> dict[str, Any]:
|
|
251
|
+
"""Get processor statistics."""
|
|
252
|
+
with self._lock:
|
|
253
|
+
return {
|
|
254
|
+
"started_count": len(self.started_spans),
|
|
255
|
+
"ended_count": len(self.ended_spans),
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
# =============================================================================
|
|
260
|
+
# Connection Validators
|
|
261
|
+
# =============================================================================
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
class ConnectionStatus(str, Enum):
|
|
265
|
+
"""Status of backend connection."""
|
|
266
|
+
|
|
267
|
+
CONNECTED = "connected"
|
|
268
|
+
UNREACHABLE = "unreachable"
|
|
269
|
+
AUTH_FAILED = "auth_failed"
|
|
270
|
+
TIMEOUT = "timeout"
|
|
271
|
+
ERROR = "error"
|
|
272
|
+
NOT_CONFIGURED = "not_configured"
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
@dataclass
|
|
276
|
+
class ConnectionResult:
|
|
277
|
+
"""Result of connection validation.
|
|
278
|
+
|
|
279
|
+
Attributes:
|
|
280
|
+
status: Connection status
|
|
281
|
+
endpoint: Tested endpoint
|
|
282
|
+
latency_ms: Response latency in milliseconds
|
|
283
|
+
message: Status message
|
|
284
|
+
details: Additional details
|
|
285
|
+
"""
|
|
286
|
+
|
|
287
|
+
status: ConnectionStatus
|
|
288
|
+
endpoint: str = ""
|
|
289
|
+
latency_ms: float = 0.0
|
|
290
|
+
message: str = ""
|
|
291
|
+
details: dict[str, Any] = field(default_factory=dict)
|
|
292
|
+
|
|
293
|
+
@property
|
|
294
|
+
def success(self) -> bool:
|
|
295
|
+
"""Check if connection was successful."""
|
|
296
|
+
return self.status == ConnectionStatus.CONNECTED
|
|
297
|
+
|
|
298
|
+
def to_dict(self) -> dict[str, Any]:
|
|
299
|
+
"""Convert to dictionary."""
|
|
300
|
+
return {
|
|
301
|
+
"status": self.status.value,
|
|
302
|
+
"endpoint": self.endpoint,
|
|
303
|
+
"latency_ms": self.latency_ms,
|
|
304
|
+
"message": self.message,
|
|
305
|
+
"success": self.success,
|
|
306
|
+
"details": self.details,
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def validate_jaeger_connection(
|
|
311
|
+
endpoint: str,
|
|
312
|
+
timeout: float = 5.0,
|
|
313
|
+
) -> ConnectionResult:
|
|
314
|
+
"""Validate Jaeger backend connection.
|
|
315
|
+
|
|
316
|
+
Supports both HTTP (Thrift) and gRPC endpoints.
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
endpoint: Jaeger endpoint URL
|
|
320
|
+
timeout: Connection timeout in seconds
|
|
321
|
+
|
|
322
|
+
Returns:
|
|
323
|
+
ConnectionResult with status
|
|
324
|
+
|
|
325
|
+
Example:
|
|
326
|
+
# HTTP endpoint (Thrift)
|
|
327
|
+
result = validate_jaeger_connection("http://localhost:14268")
|
|
328
|
+
|
|
329
|
+
# gRPC endpoint
|
|
330
|
+
result = validate_jaeger_connection("grpc://localhost:14250")
|
|
331
|
+
"""
|
|
332
|
+
start_time = time.time()
|
|
333
|
+
|
|
334
|
+
try:
|
|
335
|
+
parsed = urlparse(endpoint)
|
|
336
|
+
host = parsed.hostname or "localhost"
|
|
337
|
+
port = parsed.port or (14268 if parsed.scheme == "http" else 14250)
|
|
338
|
+
|
|
339
|
+
# Try socket connection first
|
|
340
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
341
|
+
sock.settimeout(timeout)
|
|
342
|
+
|
|
343
|
+
try:
|
|
344
|
+
sock.connect((host, port))
|
|
345
|
+
sock.close()
|
|
346
|
+
except socket.timeout:
|
|
347
|
+
return ConnectionResult(
|
|
348
|
+
status=ConnectionStatus.TIMEOUT,
|
|
349
|
+
endpoint=endpoint,
|
|
350
|
+
latency_ms=(time.time() - start_time) * 1000,
|
|
351
|
+
message=f"Connection to {host}:{port} timed out",
|
|
352
|
+
)
|
|
353
|
+
except socket.error as e:
|
|
354
|
+
return ConnectionResult(
|
|
355
|
+
status=ConnectionStatus.UNREACHABLE,
|
|
356
|
+
endpoint=endpoint,
|
|
357
|
+
latency_ms=(time.time() - start_time) * 1000,
|
|
358
|
+
message=f"Cannot connect to {host}:{port}: {e}",
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
# For HTTP, try health endpoint
|
|
362
|
+
if parsed.scheme in ("http", "https"):
|
|
363
|
+
try:
|
|
364
|
+
import urllib.request
|
|
365
|
+
|
|
366
|
+
health_url = f"{endpoint.rstrip('/')}/health"
|
|
367
|
+
req = urllib.request.Request(health_url, method="GET")
|
|
368
|
+
req.add_header("User-Agent", "truthound-validator/1.0")
|
|
369
|
+
|
|
370
|
+
with urllib.request.urlopen(req, timeout=timeout) as response:
|
|
371
|
+
if response.status == 200:
|
|
372
|
+
return ConnectionResult(
|
|
373
|
+
status=ConnectionStatus.CONNECTED,
|
|
374
|
+
endpoint=endpoint,
|
|
375
|
+
latency_ms=(time.time() - start_time) * 1000,
|
|
376
|
+
message="Jaeger is healthy",
|
|
377
|
+
details={"http_status": response.status},
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
except Exception as e:
|
|
381
|
+
# Health check failed, but socket connected
|
|
382
|
+
return ConnectionResult(
|
|
383
|
+
status=ConnectionStatus.CONNECTED,
|
|
384
|
+
endpoint=endpoint,
|
|
385
|
+
latency_ms=(time.time() - start_time) * 1000,
|
|
386
|
+
message="Jaeger socket reachable (health check failed)",
|
|
387
|
+
details={"warning": str(e)},
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
return ConnectionResult(
|
|
391
|
+
status=ConnectionStatus.CONNECTED,
|
|
392
|
+
endpoint=endpoint,
|
|
393
|
+
latency_ms=(time.time() - start_time) * 1000,
|
|
394
|
+
message="Jaeger is reachable",
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
except Exception as e:
|
|
398
|
+
return ConnectionResult(
|
|
399
|
+
status=ConnectionStatus.ERROR,
|
|
400
|
+
endpoint=endpoint,
|
|
401
|
+
latency_ms=(time.time() - start_time) * 1000,
|
|
402
|
+
message=f"Validation error: {e}",
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def validate_zipkin_connection(
|
|
407
|
+
endpoint: str,
|
|
408
|
+
timeout: float = 5.0,
|
|
409
|
+
) -> ConnectionResult:
|
|
410
|
+
"""Validate Zipkin backend connection.
|
|
411
|
+
|
|
412
|
+
Args:
|
|
413
|
+
endpoint: Zipkin endpoint URL (e.g., http://localhost:9411)
|
|
414
|
+
timeout: Connection timeout in seconds
|
|
415
|
+
|
|
416
|
+
Returns:
|
|
417
|
+
ConnectionResult with status
|
|
418
|
+
|
|
419
|
+
Example:
|
|
420
|
+
result = validate_zipkin_connection("http://localhost:9411")
|
|
421
|
+
"""
|
|
422
|
+
start_time = time.time()
|
|
423
|
+
|
|
424
|
+
try:
|
|
425
|
+
parsed = urlparse(endpoint)
|
|
426
|
+
host = parsed.hostname or "localhost"
|
|
427
|
+
port = parsed.port or 9411
|
|
428
|
+
|
|
429
|
+
# Try HTTP API endpoint
|
|
430
|
+
try:
|
|
431
|
+
import urllib.request
|
|
432
|
+
|
|
433
|
+
api_url = f"{endpoint.rstrip('/')}/api/v2/services"
|
|
434
|
+
req = urllib.request.Request(api_url, method="GET")
|
|
435
|
+
req.add_header("User-Agent", "truthound-validator/1.0")
|
|
436
|
+
req.add_header("Accept", "application/json")
|
|
437
|
+
|
|
438
|
+
with urllib.request.urlopen(req, timeout=timeout) as response:
|
|
439
|
+
if response.status == 200:
|
|
440
|
+
return ConnectionResult(
|
|
441
|
+
status=ConnectionStatus.CONNECTED,
|
|
442
|
+
endpoint=endpoint,
|
|
443
|
+
latency_ms=(time.time() - start_time) * 1000,
|
|
444
|
+
message="Zipkin is healthy",
|
|
445
|
+
details={"http_status": response.status},
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
except urllib.error.HTTPError as e:
|
|
449
|
+
if e.code == 401:
|
|
450
|
+
return ConnectionResult(
|
|
451
|
+
status=ConnectionStatus.AUTH_FAILED,
|
|
452
|
+
endpoint=endpoint,
|
|
453
|
+
latency_ms=(time.time() - start_time) * 1000,
|
|
454
|
+
message="Authentication required",
|
|
455
|
+
)
|
|
456
|
+
elif e.code == 404:
|
|
457
|
+
# API might be different version, but service is reachable
|
|
458
|
+
return ConnectionResult(
|
|
459
|
+
status=ConnectionStatus.CONNECTED,
|
|
460
|
+
endpoint=endpoint,
|
|
461
|
+
latency_ms=(time.time() - start_time) * 1000,
|
|
462
|
+
message="Zipkin reachable (API version mismatch)",
|
|
463
|
+
details={"http_status": e.code},
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
except urllib.error.URLError as e:
|
|
467
|
+
return ConnectionResult(
|
|
468
|
+
status=ConnectionStatus.UNREACHABLE,
|
|
469
|
+
endpoint=endpoint,
|
|
470
|
+
latency_ms=(time.time() - start_time) * 1000,
|
|
471
|
+
message=f"Cannot connect to Zipkin: {e.reason}",
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
except Exception as e:
|
|
475
|
+
return ConnectionResult(
|
|
476
|
+
status=ConnectionStatus.ERROR,
|
|
477
|
+
endpoint=endpoint,
|
|
478
|
+
latency_ms=(time.time() - start_time) * 1000,
|
|
479
|
+
message=f"Connection error: {e}",
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
# Fallback: try socket
|
|
483
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
484
|
+
sock.settimeout(timeout)
|
|
485
|
+
|
|
486
|
+
try:
|
|
487
|
+
sock.connect((host, port))
|
|
488
|
+
sock.close()
|
|
489
|
+
return ConnectionResult(
|
|
490
|
+
status=ConnectionStatus.CONNECTED,
|
|
491
|
+
endpoint=endpoint,
|
|
492
|
+
latency_ms=(time.time() - start_time) * 1000,
|
|
493
|
+
message="Zipkin socket reachable",
|
|
494
|
+
)
|
|
495
|
+
except socket.timeout:
|
|
496
|
+
return ConnectionResult(
|
|
497
|
+
status=ConnectionStatus.TIMEOUT,
|
|
498
|
+
endpoint=endpoint,
|
|
499
|
+
latency_ms=(time.time() - start_time) * 1000,
|
|
500
|
+
message="Connection timed out",
|
|
501
|
+
)
|
|
502
|
+
except socket.error as e:
|
|
503
|
+
return ConnectionResult(
|
|
504
|
+
status=ConnectionStatus.UNREACHABLE,
|
|
505
|
+
endpoint=endpoint,
|
|
506
|
+
latency_ms=(time.time() - start_time) * 1000,
|
|
507
|
+
message=f"Cannot connect: {e}",
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
except Exception as e:
|
|
511
|
+
return ConnectionResult(
|
|
512
|
+
status=ConnectionStatus.ERROR,
|
|
513
|
+
endpoint=endpoint,
|
|
514
|
+
latency_ms=(time.time() - start_time) * 1000,
|
|
515
|
+
message=f"Validation error: {e}",
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
def validate_otlp_connection(
|
|
520
|
+
endpoint: str,
|
|
521
|
+
timeout: float = 5.0,
|
|
522
|
+
use_tls: bool = False,
|
|
523
|
+
) -> ConnectionResult:
|
|
524
|
+
"""Validate OTLP (OpenTelemetry Protocol) endpoint connection.
|
|
525
|
+
|
|
526
|
+
Args:
|
|
527
|
+
endpoint: OTLP endpoint (e.g., http://localhost:4317)
|
|
528
|
+
timeout: Connection timeout in seconds
|
|
529
|
+
use_tls: Whether to use TLS
|
|
530
|
+
|
|
531
|
+
Returns:
|
|
532
|
+
ConnectionResult with status
|
|
533
|
+
"""
|
|
534
|
+
start_time = time.time()
|
|
535
|
+
|
|
536
|
+
try:
|
|
537
|
+
parsed = urlparse(endpoint)
|
|
538
|
+
host = parsed.hostname or "localhost"
|
|
539
|
+
port = parsed.port or 4317
|
|
540
|
+
|
|
541
|
+
# Try socket connection
|
|
542
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
543
|
+
sock.settimeout(timeout)
|
|
544
|
+
|
|
545
|
+
try:
|
|
546
|
+
sock.connect((host, port))
|
|
547
|
+
sock.close()
|
|
548
|
+
|
|
549
|
+
return ConnectionResult(
|
|
550
|
+
status=ConnectionStatus.CONNECTED,
|
|
551
|
+
endpoint=endpoint,
|
|
552
|
+
latency_ms=(time.time() - start_time) * 1000,
|
|
553
|
+
message="OTLP endpoint is reachable",
|
|
554
|
+
details={"protocol": "grpc" if port == 4317 else "http"},
|
|
555
|
+
)
|
|
556
|
+
|
|
557
|
+
except socket.timeout:
|
|
558
|
+
return ConnectionResult(
|
|
559
|
+
status=ConnectionStatus.TIMEOUT,
|
|
560
|
+
endpoint=endpoint,
|
|
561
|
+
latency_ms=(time.time() - start_time) * 1000,
|
|
562
|
+
message="Connection timed out",
|
|
563
|
+
)
|
|
564
|
+
|
|
565
|
+
except socket.error as e:
|
|
566
|
+
return ConnectionResult(
|
|
567
|
+
status=ConnectionStatus.UNREACHABLE,
|
|
568
|
+
endpoint=endpoint,
|
|
569
|
+
latency_ms=(time.time() - start_time) * 1000,
|
|
570
|
+
message=f"Cannot connect: {e}",
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
except Exception as e:
|
|
574
|
+
return ConnectionResult(
|
|
575
|
+
status=ConnectionStatus.ERROR,
|
|
576
|
+
endpoint=endpoint,
|
|
577
|
+
latency_ms=(time.time() - start_time) * 1000,
|
|
578
|
+
message=f"Validation error: {e}",
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
# =============================================================================
|
|
583
|
+
# Tracing Validator
|
|
584
|
+
# =============================================================================
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
@dataclass
|
|
588
|
+
class ValidationReport:
|
|
589
|
+
"""Comprehensive tracing validation report.
|
|
590
|
+
|
|
591
|
+
Attributes:
|
|
592
|
+
success: Overall validation success
|
|
593
|
+
provider_valid: TracerProvider is working
|
|
594
|
+
span_creation_valid: Spans can be created
|
|
595
|
+
context_propagation_valid: Context propagation works
|
|
596
|
+
exporter_valid: Exporter is working
|
|
597
|
+
backend_connections: Backend connection results
|
|
598
|
+
errors: List of errors
|
|
599
|
+
warnings: List of warnings
|
|
600
|
+
duration_ms: Total validation duration
|
|
601
|
+
"""
|
|
602
|
+
|
|
603
|
+
success: bool = True
|
|
604
|
+
provider_valid: bool = False
|
|
605
|
+
span_creation_valid: bool = False
|
|
606
|
+
context_propagation_valid: bool = False
|
|
607
|
+
exporter_valid: bool = False
|
|
608
|
+
backend_connections: dict[str, ConnectionResult] = field(default_factory=dict)
|
|
609
|
+
errors: list[str] = field(default_factory=list)
|
|
610
|
+
warnings: list[str] = field(default_factory=list)
|
|
611
|
+
duration_ms: float = 0.0
|
|
612
|
+
|
|
613
|
+
def to_dict(self) -> dict[str, Any]:
|
|
614
|
+
"""Convert to dictionary."""
|
|
615
|
+
return {
|
|
616
|
+
"success": self.success,
|
|
617
|
+
"provider_valid": self.provider_valid,
|
|
618
|
+
"span_creation_valid": self.span_creation_valid,
|
|
619
|
+
"context_propagation_valid": self.context_propagation_valid,
|
|
620
|
+
"exporter_valid": self.exporter_valid,
|
|
621
|
+
"backend_connections": {
|
|
622
|
+
k: v.to_dict()
|
|
623
|
+
for k, v in self.backend_connections.items()
|
|
624
|
+
},
|
|
625
|
+
"errors": self.errors,
|
|
626
|
+
"warnings": self.warnings,
|
|
627
|
+
"duration_ms": self.duration_ms,
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
def __str__(self) -> str:
|
|
631
|
+
"""Human-readable report."""
|
|
632
|
+
lines = [
|
|
633
|
+
"OpenTelemetry Tracing Validation Report",
|
|
634
|
+
"=" * 40,
|
|
635
|
+
f"Overall Status: {'PASS' if self.success else 'FAIL'}",
|
|
636
|
+
"",
|
|
637
|
+
"Component Status:",
|
|
638
|
+
f" Provider: {'✓' if self.provider_valid else '✗'}",
|
|
639
|
+
f" Span Creation: {'✓' if self.span_creation_valid else '✗'}",
|
|
640
|
+
f" Context Propagation: {'✓' if self.context_propagation_valid else '✗'}",
|
|
641
|
+
f" Exporter: {'✓' if self.exporter_valid else '✗'}",
|
|
642
|
+
"",
|
|
643
|
+
]
|
|
644
|
+
|
|
645
|
+
if self.backend_connections:
|
|
646
|
+
lines.append("Backend Connections:")
|
|
647
|
+
for name, result in self.backend_connections.items():
|
|
648
|
+
status = "✓" if result.success else "✗"
|
|
649
|
+
lines.append(f" {name}: {status} ({result.message})")
|
|
650
|
+
lines.append("")
|
|
651
|
+
|
|
652
|
+
if self.errors:
|
|
653
|
+
lines.append("Errors:")
|
|
654
|
+
for err in self.errors:
|
|
655
|
+
lines.append(f" - {err}")
|
|
656
|
+
lines.append("")
|
|
657
|
+
|
|
658
|
+
if self.warnings:
|
|
659
|
+
lines.append("Warnings:")
|
|
660
|
+
for warn in self.warnings:
|
|
661
|
+
lines.append(f" - {warn}")
|
|
662
|
+
lines.append("")
|
|
663
|
+
|
|
664
|
+
lines.append(f"Duration: {self.duration_ms:.2f}ms")
|
|
665
|
+
|
|
666
|
+
return "\n".join(lines)
|
|
667
|
+
|
|
668
|
+
|
|
669
|
+
class TracingValidator:
|
|
670
|
+
"""Validates OpenTelemetry tracing integration.
|
|
671
|
+
|
|
672
|
+
Provides comprehensive validation of tracing setup including
|
|
673
|
+
provider configuration, span creation, context propagation,
|
|
674
|
+
and backend connectivity.
|
|
675
|
+
|
|
676
|
+
Example:
|
|
677
|
+
validator = TracingValidator()
|
|
678
|
+
|
|
679
|
+
# Validate everything
|
|
680
|
+
report = validator.validate_all()
|
|
681
|
+
print(report)
|
|
682
|
+
|
|
683
|
+
# Validate specific components
|
|
684
|
+
result = validator.validate_span_creation()
|
|
685
|
+
"""
|
|
686
|
+
|
|
687
|
+
def __init__(
|
|
688
|
+
self,
|
|
689
|
+
jaeger_endpoint: str | None = None,
|
|
690
|
+
zipkin_endpoint: str | None = None,
|
|
691
|
+
otlp_endpoint: str | None = None,
|
|
692
|
+
):
|
|
693
|
+
"""Initialize validator.
|
|
694
|
+
|
|
695
|
+
Args:
|
|
696
|
+
jaeger_endpoint: Jaeger endpoint URL
|
|
697
|
+
zipkin_endpoint: Zipkin endpoint URL
|
|
698
|
+
otlp_endpoint: OTLP endpoint URL
|
|
699
|
+
"""
|
|
700
|
+
self.jaeger_endpoint = jaeger_endpoint
|
|
701
|
+
self.zipkin_endpoint = zipkin_endpoint
|
|
702
|
+
self.otlp_endpoint = otlp_endpoint
|
|
703
|
+
|
|
704
|
+
def validate_all(self) -> ValidationReport:
|
|
705
|
+
"""Run all validations.
|
|
706
|
+
|
|
707
|
+
Returns:
|
|
708
|
+
Comprehensive validation report
|
|
709
|
+
"""
|
|
710
|
+
start_time = time.time()
|
|
711
|
+
report = ValidationReport()
|
|
712
|
+
|
|
713
|
+
# Validate provider
|
|
714
|
+
try:
|
|
715
|
+
report.provider_valid = self.validate_provider()
|
|
716
|
+
except Exception as e:
|
|
717
|
+
report.errors.append(f"Provider validation error: {e}")
|
|
718
|
+
|
|
719
|
+
# Validate span creation
|
|
720
|
+
try:
|
|
721
|
+
report.span_creation_valid = self.validate_span_creation()
|
|
722
|
+
except Exception as e:
|
|
723
|
+
report.errors.append(f"Span creation validation error: {e}")
|
|
724
|
+
|
|
725
|
+
# Validate context propagation
|
|
726
|
+
try:
|
|
727
|
+
report.context_propagation_valid = self.validate_context_propagation()
|
|
728
|
+
except Exception as e:
|
|
729
|
+
report.errors.append(f"Context propagation validation error: {e}")
|
|
730
|
+
|
|
731
|
+
# Validate exporter
|
|
732
|
+
try:
|
|
733
|
+
report.exporter_valid = self.validate_exporter()
|
|
734
|
+
except Exception as e:
|
|
735
|
+
report.errors.append(f"Exporter validation error: {e}")
|
|
736
|
+
|
|
737
|
+
# Validate backend connections
|
|
738
|
+
if self.jaeger_endpoint:
|
|
739
|
+
result = validate_jaeger_connection(self.jaeger_endpoint)
|
|
740
|
+
report.backend_connections["jaeger"] = result
|
|
741
|
+
if not result.success:
|
|
742
|
+
report.warnings.append(f"Jaeger not reachable: {result.message}")
|
|
743
|
+
|
|
744
|
+
if self.zipkin_endpoint:
|
|
745
|
+
result = validate_zipkin_connection(self.zipkin_endpoint)
|
|
746
|
+
report.backend_connections["zipkin"] = result
|
|
747
|
+
if not result.success:
|
|
748
|
+
report.warnings.append(f"Zipkin not reachable: {result.message}")
|
|
749
|
+
|
|
750
|
+
if self.otlp_endpoint:
|
|
751
|
+
result = validate_otlp_connection(self.otlp_endpoint)
|
|
752
|
+
report.backend_connections["otlp"] = result
|
|
753
|
+
if not result.success:
|
|
754
|
+
report.warnings.append(f"OTLP endpoint not reachable: {result.message}")
|
|
755
|
+
|
|
756
|
+
# Overall success
|
|
757
|
+
report.success = (
|
|
758
|
+
report.provider_valid and
|
|
759
|
+
report.span_creation_valid and
|
|
760
|
+
report.context_propagation_valid and
|
|
761
|
+
len(report.errors) == 0
|
|
762
|
+
)
|
|
763
|
+
|
|
764
|
+
report.duration_ms = (time.time() - start_time) * 1000
|
|
765
|
+
return report
|
|
766
|
+
|
|
767
|
+
def validate_provider(self) -> bool:
|
|
768
|
+
"""Validate TracerProvider is working.
|
|
769
|
+
|
|
770
|
+
Returns:
|
|
771
|
+
True if provider is valid
|
|
772
|
+
"""
|
|
773
|
+
from truthound.observability.tracing.provider import (
|
|
774
|
+
TracerProvider,
|
|
775
|
+
get_tracer_provider,
|
|
776
|
+
)
|
|
777
|
+
|
|
778
|
+
# Check global provider exists
|
|
779
|
+
provider = get_tracer_provider()
|
|
780
|
+
if provider is None:
|
|
781
|
+
return False
|
|
782
|
+
|
|
783
|
+
# Check we can get a tracer
|
|
784
|
+
tracer = provider.get_tracer("validation-test")
|
|
785
|
+
if tracer is None:
|
|
786
|
+
return False
|
|
787
|
+
|
|
788
|
+
return True
|
|
789
|
+
|
|
790
|
+
def validate_span_creation(self) -> bool:
|
|
791
|
+
"""Validate spans can be created.
|
|
792
|
+
|
|
793
|
+
Returns:
|
|
794
|
+
True if span creation works
|
|
795
|
+
"""
|
|
796
|
+
from truthound.observability.tracing.provider import (
|
|
797
|
+
TracerProvider,
|
|
798
|
+
Tracer,
|
|
799
|
+
)
|
|
800
|
+
|
|
801
|
+
# Create isolated provider for testing
|
|
802
|
+
mock_exporter = MockSpanExporter()
|
|
803
|
+
from truthound.observability.tracing.processor import SimpleSpanProcessor
|
|
804
|
+
|
|
805
|
+
provider = TracerProvider()
|
|
806
|
+
provider.add_processor(SimpleSpanProcessor(mock_exporter))
|
|
807
|
+
|
|
808
|
+
tracer = provider.get_tracer("validation-test")
|
|
809
|
+
|
|
810
|
+
# Create a span
|
|
811
|
+
with tracer.start_as_current_span("test-span") as span:
|
|
812
|
+
span.set_attribute("test.key", "test-value")
|
|
813
|
+
|
|
814
|
+
# Verify span was created
|
|
815
|
+
if len(mock_exporter.spans) != 1:
|
|
816
|
+
return False
|
|
817
|
+
|
|
818
|
+
exported_span = mock_exporter.spans[0]
|
|
819
|
+
if exported_span.name != "test-span":
|
|
820
|
+
return False
|
|
821
|
+
|
|
822
|
+
if exported_span.attributes.get("test.key") != "test-value":
|
|
823
|
+
return False
|
|
824
|
+
|
|
825
|
+
return True
|
|
826
|
+
|
|
827
|
+
def validate_context_propagation(self) -> bool:
|
|
828
|
+
"""Validate context propagation works.
|
|
829
|
+
|
|
830
|
+
Returns:
|
|
831
|
+
True if context propagation works
|
|
832
|
+
"""
|
|
833
|
+
from truthound.observability.tracing.provider import (
|
|
834
|
+
TracerProvider,
|
|
835
|
+
get_current_span,
|
|
836
|
+
)
|
|
837
|
+
from truthound.observability.tracing.processor import SimpleSpanProcessor
|
|
838
|
+
|
|
839
|
+
mock_exporter = MockSpanExporter()
|
|
840
|
+
provider = TracerProvider()
|
|
841
|
+
provider.add_processor(SimpleSpanProcessor(mock_exporter))
|
|
842
|
+
|
|
843
|
+
tracer = provider.get_tracer("validation-test")
|
|
844
|
+
|
|
845
|
+
# Create parent span
|
|
846
|
+
with tracer.start_as_current_span("parent") as parent:
|
|
847
|
+
parent_trace_id = parent.context.trace_id
|
|
848
|
+
|
|
849
|
+
# Create child span
|
|
850
|
+
with tracer.start_as_current_span("child") as child:
|
|
851
|
+
# Verify child has same trace ID
|
|
852
|
+
if child.context.trace_id != parent_trace_id:
|
|
853
|
+
return False
|
|
854
|
+
|
|
855
|
+
# Verify parent is set correctly
|
|
856
|
+
if child.parent is None:
|
|
857
|
+
return False
|
|
858
|
+
|
|
859
|
+
if child.parent.span_id != parent.context.span_id:
|
|
860
|
+
return False
|
|
861
|
+
|
|
862
|
+
# Verify we exported both spans
|
|
863
|
+
if len(mock_exporter.spans) != 2:
|
|
864
|
+
return False
|
|
865
|
+
|
|
866
|
+
return True
|
|
867
|
+
|
|
868
|
+
def validate_exporter(self) -> bool:
|
|
869
|
+
"""Validate exporter functionality.
|
|
870
|
+
|
|
871
|
+
Returns:
|
|
872
|
+
True if exporter works
|
|
873
|
+
"""
|
|
874
|
+
# Test mock exporter
|
|
875
|
+
exporter = MockSpanExporter()
|
|
876
|
+
|
|
877
|
+
from truthound.observability.tracing.span import Span, SpanContextData
|
|
878
|
+
|
|
879
|
+
# Create test span
|
|
880
|
+
context = SpanContextData(
|
|
881
|
+
trace_id="0" * 32,
|
|
882
|
+
span_id="0" * 16,
|
|
883
|
+
trace_flags=1,
|
|
884
|
+
)
|
|
885
|
+
|
|
886
|
+
span = Span(
|
|
887
|
+
name="test-export",
|
|
888
|
+
context=context,
|
|
889
|
+
kind=SpanKind.INTERNAL,
|
|
890
|
+
)
|
|
891
|
+
span.end()
|
|
892
|
+
|
|
893
|
+
# Export
|
|
894
|
+
result = exporter.export([span])
|
|
895
|
+
if result != ExportResult.SUCCESS:
|
|
896
|
+
return False
|
|
897
|
+
|
|
898
|
+
if len(exporter.spans) != 1:
|
|
899
|
+
return False
|
|
900
|
+
|
|
901
|
+
return True
|
|
902
|
+
|
|
903
|
+
|
|
904
|
+
# =============================================================================
|
|
905
|
+
# Diagnostic Tools
|
|
906
|
+
# =============================================================================
|
|
907
|
+
|
|
908
|
+
|
|
909
|
+
def diagnose_tracing_setup() -> dict[str, Any]:
|
|
910
|
+
"""Run comprehensive tracing diagnostics.
|
|
911
|
+
|
|
912
|
+
Returns:
|
|
913
|
+
Diagnostic information dictionary
|
|
914
|
+
"""
|
|
915
|
+
diagnostics = {
|
|
916
|
+
"timestamp": datetime.now().isoformat(),
|
|
917
|
+
"python_version": None,
|
|
918
|
+
"truthound_version": None,
|
|
919
|
+
"otel_packages": {},
|
|
920
|
+
"provider_info": {},
|
|
921
|
+
"environment": {},
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
# Python version
|
|
925
|
+
import sys
|
|
926
|
+
diagnostics["python_version"] = sys.version
|
|
927
|
+
|
|
928
|
+
# Truthound version
|
|
929
|
+
try:
|
|
930
|
+
import truthound
|
|
931
|
+
diagnostics["truthound_version"] = getattr(truthound, "__version__", "unknown")
|
|
932
|
+
except ImportError:
|
|
933
|
+
diagnostics["truthound_version"] = "not installed"
|
|
934
|
+
|
|
935
|
+
# OpenTelemetry packages
|
|
936
|
+
otel_packages = [
|
|
937
|
+
"opentelemetry-api",
|
|
938
|
+
"opentelemetry-sdk",
|
|
939
|
+
"opentelemetry-exporter-jaeger",
|
|
940
|
+
"opentelemetry-exporter-zipkin",
|
|
941
|
+
"opentelemetry-exporter-otlp",
|
|
942
|
+
]
|
|
943
|
+
|
|
944
|
+
for pkg in otel_packages:
|
|
945
|
+
try:
|
|
946
|
+
import importlib.metadata
|
|
947
|
+
version = importlib.metadata.version(pkg.replace("-", "_"))
|
|
948
|
+
diagnostics["otel_packages"][pkg] = version
|
|
949
|
+
except Exception:
|
|
950
|
+
diagnostics["otel_packages"][pkg] = "not installed"
|
|
951
|
+
|
|
952
|
+
# Provider info
|
|
953
|
+
try:
|
|
954
|
+
from truthound.observability.tracing.provider import get_tracer_provider
|
|
955
|
+
|
|
956
|
+
provider = get_tracer_provider()
|
|
957
|
+
diagnostics["provider_info"] = {
|
|
958
|
+
"type": type(provider).__name__,
|
|
959
|
+
"resource": str(provider.resource) if hasattr(provider, "resource") else "N/A",
|
|
960
|
+
"sampler": str(provider.sampler) if hasattr(provider, "sampler") else "N/A",
|
|
961
|
+
}
|
|
962
|
+
except Exception as e:
|
|
963
|
+
diagnostics["provider_info"] = {"error": str(e)}
|
|
964
|
+
|
|
965
|
+
# Environment variables
|
|
966
|
+
import os
|
|
967
|
+
otel_env_vars = [
|
|
968
|
+
"OTEL_SERVICE_NAME",
|
|
969
|
+
"OTEL_EXPORTER_JAEGER_ENDPOINT",
|
|
970
|
+
"OTEL_EXPORTER_ZIPKIN_ENDPOINT",
|
|
971
|
+
"OTEL_EXPORTER_OTLP_ENDPOINT",
|
|
972
|
+
"OTEL_TRACES_SAMPLER",
|
|
973
|
+
"OTEL_TRACES_EXPORTER",
|
|
974
|
+
]
|
|
975
|
+
|
|
976
|
+
for var in otel_env_vars:
|
|
977
|
+
value = os.environ.get(var)
|
|
978
|
+
if value:
|
|
979
|
+
diagnostics["environment"][var] = value
|
|
980
|
+
|
|
981
|
+
return diagnostics
|
|
982
|
+
|
|
983
|
+
|
|
984
|
+
def create_test_trace(
|
|
985
|
+
tracer_name: str = "test-tracer",
|
|
986
|
+
span_count: int = 3,
|
|
987
|
+
) -> list[Span]:
|
|
988
|
+
"""Create a test trace for verification.
|
|
989
|
+
|
|
990
|
+
Args:
|
|
991
|
+
tracer_name: Name for test tracer
|
|
992
|
+
span_count: Number of nested spans to create
|
|
993
|
+
|
|
994
|
+
Returns:
|
|
995
|
+
List of created spans
|
|
996
|
+
"""
|
|
997
|
+
from truthound.observability.tracing.provider import TracerProvider
|
|
998
|
+
from truthound.observability.tracing.processor import SimpleSpanProcessor
|
|
999
|
+
|
|
1000
|
+
mock_exporter = MockSpanExporter()
|
|
1001
|
+
provider = TracerProvider()
|
|
1002
|
+
provider.add_processor(SimpleSpanProcessor(mock_exporter))
|
|
1003
|
+
|
|
1004
|
+
tracer = provider.get_tracer(tracer_name)
|
|
1005
|
+
|
|
1006
|
+
def create_nested_spans(depth: int, current: int = 0):
|
|
1007
|
+
if current >= depth:
|
|
1008
|
+
return
|
|
1009
|
+
|
|
1010
|
+
with tracer.start_as_current_span(f"span-{current}") as span:
|
|
1011
|
+
span.set_attribute("depth", current)
|
|
1012
|
+
span.set_attribute("timestamp", datetime.now().isoformat())
|
|
1013
|
+
time.sleep(0.001) # Small delay for realistic timing
|
|
1014
|
+
create_nested_spans(depth, current + 1)
|
|
1015
|
+
|
|
1016
|
+
create_nested_spans(span_count)
|
|
1017
|
+
|
|
1018
|
+
return list(mock_exporter.spans)
|