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,1177 @@
|
|
|
1
|
+
"""Bridge components for cross-backend interoperability.
|
|
2
|
+
|
|
3
|
+
This module provides bridge classes that enable using components from
|
|
4
|
+
one backend with the other:
|
|
5
|
+
|
|
6
|
+
- Use OpenTelemetry SDK exporters with Truthound spans
|
|
7
|
+
- Use Truthound processors with OpenTelemetry spans
|
|
8
|
+
- Bridge samplers, propagators, and resources
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
import threading
|
|
15
|
+
import time
|
|
16
|
+
from abc import ABC, abstractmethod
|
|
17
|
+
from dataclasses import dataclass, field
|
|
18
|
+
from typing import Any, Mapping, Sequence
|
|
19
|
+
|
|
20
|
+
from truthound.observability.tracing.otel.detection import is_otel_sdk_available
|
|
21
|
+
from truthound.observability.tracing.otel.adapter import SpanContextAdapter
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# =============================================================================
|
|
27
|
+
# Span Context Bridge
|
|
28
|
+
# =============================================================================
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class SpanContextBridge:
|
|
32
|
+
"""Bridges span contexts between Truthound and OpenTelemetry formats.
|
|
33
|
+
|
|
34
|
+
Handles conversion of trace/span IDs, trace flags, and trace state
|
|
35
|
+
between the two format conventions.
|
|
36
|
+
|
|
37
|
+
Example:
|
|
38
|
+
>>> bridge = SpanContextBridge()
|
|
39
|
+
>>> otel_ctx = bridge.truthound_to_otel(truthound_ctx)
|
|
40
|
+
>>> truthound_ctx = bridge.otel_to_truthound(otel_ctx)
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def truthound_to_otel(self, ctx: Any) -> Any:
|
|
44
|
+
"""Convert Truthound SpanContextData to OTEL SpanContext.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
ctx: Truthound SpanContextData.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
OpenTelemetry SpanContext.
|
|
51
|
+
|
|
52
|
+
Raises:
|
|
53
|
+
ImportError: If OTEL SDK is not available.
|
|
54
|
+
"""
|
|
55
|
+
if not is_otel_sdk_available():
|
|
56
|
+
raise ImportError("OpenTelemetry SDK not available")
|
|
57
|
+
|
|
58
|
+
from opentelemetry.trace import SpanContext, TraceState
|
|
59
|
+
|
|
60
|
+
# Parse trace state
|
|
61
|
+
trace_state = TraceState()
|
|
62
|
+
if hasattr(ctx, "trace_state") and ctx.trace_state:
|
|
63
|
+
state_str = ctx.trace_state if isinstance(ctx.trace_state, str) else str(ctx.trace_state)
|
|
64
|
+
for pair in state_str.split(","):
|
|
65
|
+
if "=" in pair:
|
|
66
|
+
key, value = pair.split("=", 1)
|
|
67
|
+
trace_state = trace_state.add(key.strip(), value.strip())
|
|
68
|
+
|
|
69
|
+
# Convert IDs
|
|
70
|
+
trace_id = ctx.trace_id
|
|
71
|
+
span_id = ctx.span_id
|
|
72
|
+
|
|
73
|
+
if isinstance(trace_id, str):
|
|
74
|
+
trace_id = int(trace_id, 16)
|
|
75
|
+
if isinstance(span_id, str):
|
|
76
|
+
span_id = int(span_id, 16)
|
|
77
|
+
|
|
78
|
+
return SpanContext(
|
|
79
|
+
trace_id=trace_id,
|
|
80
|
+
span_id=span_id,
|
|
81
|
+
is_remote=getattr(ctx, "is_remote", False),
|
|
82
|
+
trace_flags=ctx.trace_flags,
|
|
83
|
+
trace_state=trace_state,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
def otel_to_truthound(self, ctx: Any) -> Any:
|
|
87
|
+
"""Convert OTEL SpanContext to Truthound SpanContextData.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
ctx: OpenTelemetry SpanContext.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Truthound SpanContextData.
|
|
94
|
+
"""
|
|
95
|
+
from truthound.observability.tracing.span import SpanContextData
|
|
96
|
+
|
|
97
|
+
# Convert trace state to string
|
|
98
|
+
trace_state = ""
|
|
99
|
+
if hasattr(ctx, "trace_state") and ctx.trace_state:
|
|
100
|
+
pairs = [f"{k}={v}" for k, v in ctx.trace_state.items()]
|
|
101
|
+
trace_state = ",".join(pairs)
|
|
102
|
+
|
|
103
|
+
# Format IDs as hex strings
|
|
104
|
+
trace_id = ctx.trace_id
|
|
105
|
+
span_id = ctx.span_id
|
|
106
|
+
|
|
107
|
+
if isinstance(trace_id, int):
|
|
108
|
+
trace_id = format(trace_id, "032x")
|
|
109
|
+
if isinstance(span_id, int):
|
|
110
|
+
span_id = format(span_id, "016x")
|
|
111
|
+
|
|
112
|
+
return SpanContextData(
|
|
113
|
+
trace_id=trace_id,
|
|
114
|
+
span_id=span_id,
|
|
115
|
+
trace_flags=ctx.trace_flags,
|
|
116
|
+
trace_state=trace_state,
|
|
117
|
+
is_remote=ctx.is_remote,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# =============================================================================
|
|
122
|
+
# Span Bridge
|
|
123
|
+
# =============================================================================
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class SpanBridge:
|
|
127
|
+
"""Bridges spans between Truthound and OpenTelemetry formats.
|
|
128
|
+
|
|
129
|
+
Converts span data including attributes, events, links, and status.
|
|
130
|
+
|
|
131
|
+
Example:
|
|
132
|
+
>>> bridge = SpanBridge()
|
|
133
|
+
>>> otel_data = bridge.truthound_to_otel_data(truthound_span)
|
|
134
|
+
>>> # Use otel_data with OTEL exporter
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
def __init__(self) -> None:
|
|
138
|
+
"""Initialize span bridge."""
|
|
139
|
+
self._context_bridge = SpanContextBridge()
|
|
140
|
+
|
|
141
|
+
def truthound_to_otel_data(self, span: Any) -> dict[str, Any]:
|
|
142
|
+
"""Convert Truthound span to OTEL-compatible data dict.
|
|
143
|
+
|
|
144
|
+
This is useful for bridging to OTEL exporters.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
span: Truthound Span.
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
Dictionary with OTEL-compatible span data.
|
|
151
|
+
"""
|
|
152
|
+
from truthound.observability.tracing.span import StatusCode, SpanKind
|
|
153
|
+
|
|
154
|
+
# Map status code
|
|
155
|
+
status_map = {
|
|
156
|
+
StatusCode.UNSET: 0,
|
|
157
|
+
StatusCode.OK: 1,
|
|
158
|
+
StatusCode.ERROR: 2,
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
# Map span kind
|
|
162
|
+
kind_map = {
|
|
163
|
+
SpanKind.INTERNAL: 0,
|
|
164
|
+
SpanKind.SERVER: 1,
|
|
165
|
+
SpanKind.CLIENT: 2,
|
|
166
|
+
SpanKind.PRODUCER: 3,
|
|
167
|
+
SpanKind.CONSUMER: 4,
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
status = span.status if hasattr(span, "status") else (StatusCode.UNSET, "")
|
|
171
|
+
if isinstance(status, tuple):
|
|
172
|
+
status_code, status_message = status
|
|
173
|
+
else:
|
|
174
|
+
status_code = status
|
|
175
|
+
status_message = ""
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
"trace_id": span.context.trace_id if hasattr(span, "context") else "",
|
|
179
|
+
"span_id": span.context.span_id if hasattr(span, "context") else "",
|
|
180
|
+
"parent_span_id": span.parent.span_id if hasattr(span, "parent") and span.parent else None,
|
|
181
|
+
"name": span.name,
|
|
182
|
+
"kind": kind_map.get(span.kind, 0) if hasattr(span, "kind") else 0,
|
|
183
|
+
"start_time_ns": int(span.start_time * 1_000_000_000) if hasattr(span, "start_time") else 0,
|
|
184
|
+
"end_time_ns": int(span.end_time * 1_000_000_000) if hasattr(span, "end_time") and span.end_time else 0,
|
|
185
|
+
"attributes": dict(span.attributes) if hasattr(span, "attributes") else {},
|
|
186
|
+
"events": [
|
|
187
|
+
{
|
|
188
|
+
"name": e.name,
|
|
189
|
+
"timestamp_ns": int(e.timestamp * 1_000_000_000),
|
|
190
|
+
"attributes": dict(e.attributes),
|
|
191
|
+
}
|
|
192
|
+
for e in (span.events if hasattr(span, "events") else [])
|
|
193
|
+
],
|
|
194
|
+
"links": [
|
|
195
|
+
{
|
|
196
|
+
"trace_id": l.context.trace_id,
|
|
197
|
+
"span_id": l.context.span_id,
|
|
198
|
+
"attributes": dict(l.attributes),
|
|
199
|
+
}
|
|
200
|
+
for l in (span.links if hasattr(span, "links") else [])
|
|
201
|
+
],
|
|
202
|
+
"status": {
|
|
203
|
+
"code": status_map.get(status_code, 0),
|
|
204
|
+
"message": status_message,
|
|
205
|
+
},
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
def otel_to_truthound_data(self, span_data: dict[str, Any]) -> dict[str, Any]:
|
|
209
|
+
"""Convert OTEL span data to Truthound-compatible format.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
span_data: OTEL span data dictionary.
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
Truthound-compatible span data.
|
|
216
|
+
"""
|
|
217
|
+
# Map status code back
|
|
218
|
+
status_code_map = {0: "UNSET", 1: "OK", 2: "ERROR"}
|
|
219
|
+
|
|
220
|
+
# Map kind back
|
|
221
|
+
kind_map = {0: "INTERNAL", 1: "SERVER", 2: "CLIENT", 3: "PRODUCER", 4: "CONSUMER"}
|
|
222
|
+
|
|
223
|
+
status = span_data.get("status", {})
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
"trace_id": span_data.get("trace_id", ""),
|
|
227
|
+
"span_id": span_data.get("span_id", ""),
|
|
228
|
+
"parent_span_id": span_data.get("parent_span_id"),
|
|
229
|
+
"name": span_data.get("name", ""),
|
|
230
|
+
"kind": kind_map.get(span_data.get("kind", 0), "INTERNAL"),
|
|
231
|
+
"start_time": span_data.get("start_time_ns", 0) / 1_000_000_000,
|
|
232
|
+
"end_time": span_data.get("end_time_ns", 0) / 1_000_000_000 if span_data.get("end_time_ns") else None,
|
|
233
|
+
"attributes": span_data.get("attributes", {}),
|
|
234
|
+
"events": span_data.get("events", []),
|
|
235
|
+
"links": span_data.get("links", []),
|
|
236
|
+
"status_code": status_code_map.get(status.get("code", 0), "UNSET"),
|
|
237
|
+
"status_message": status.get("message", ""),
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
# =============================================================================
|
|
242
|
+
# Span Processor Bridge
|
|
243
|
+
# =============================================================================
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
class SpanProcessorBridge:
|
|
247
|
+
"""Bridges span processors between backends.
|
|
248
|
+
|
|
249
|
+
Allows using OTEL span processors with Truthound spans and vice versa.
|
|
250
|
+
|
|
251
|
+
Example:
|
|
252
|
+
>>> # Use OTEL processor with Truthound
|
|
253
|
+
>>> bridge = SpanProcessorBridge()
|
|
254
|
+
>>> truthound_processor = bridge.wrap_otel_processor(otel_processor)
|
|
255
|
+
>>> provider.add_processor(truthound_processor)
|
|
256
|
+
"""
|
|
257
|
+
|
|
258
|
+
def __init__(self) -> None:
|
|
259
|
+
"""Initialize processor bridge."""
|
|
260
|
+
self._span_bridge = SpanBridge()
|
|
261
|
+
self._context_bridge = SpanContextBridge()
|
|
262
|
+
|
|
263
|
+
def wrap_otel_processor(self, otel_processor: Any) -> "TruthoundProcessorWrapper":
|
|
264
|
+
"""Wrap an OTEL processor for use with Truthound.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
otel_processor: OpenTelemetry SpanProcessor.
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
Truthound-compatible processor wrapper.
|
|
271
|
+
"""
|
|
272
|
+
return TruthoundProcessorWrapper(otel_processor, self._span_bridge, self._context_bridge)
|
|
273
|
+
|
|
274
|
+
def wrap_truthound_processor(self, truthound_processor: Any) -> "OTELProcessorWrapper":
|
|
275
|
+
"""Wrap a Truthound processor for use with OTEL SDK.
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
truthound_processor: Truthound SpanProcessor.
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
OTEL-compatible processor wrapper.
|
|
282
|
+
"""
|
|
283
|
+
return OTELProcessorWrapper(truthound_processor, self._span_bridge, self._context_bridge)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
class TruthoundProcessorWrapper:
|
|
287
|
+
"""Wraps an OTEL processor for use with Truthound's TracerProvider.
|
|
288
|
+
|
|
289
|
+
Translates Truthound spans to OTEL format before passing to the
|
|
290
|
+
wrapped processor.
|
|
291
|
+
"""
|
|
292
|
+
|
|
293
|
+
def __init__(
|
|
294
|
+
self,
|
|
295
|
+
otel_processor: Any,
|
|
296
|
+
span_bridge: SpanBridge,
|
|
297
|
+
context_bridge: SpanContextBridge,
|
|
298
|
+
) -> None:
|
|
299
|
+
"""Initialize wrapper.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
otel_processor: OTEL processor to wrap.
|
|
303
|
+
span_bridge: Span bridge for conversions.
|
|
304
|
+
context_bridge: Context bridge for conversions.
|
|
305
|
+
"""
|
|
306
|
+
self._processor = otel_processor
|
|
307
|
+
self._span_bridge = span_bridge
|
|
308
|
+
self._context_bridge = context_bridge
|
|
309
|
+
|
|
310
|
+
def on_start(self, span: Any, parent_context: Any | None = None) -> None:
|
|
311
|
+
"""Called when a span starts.
|
|
312
|
+
|
|
313
|
+
Args:
|
|
314
|
+
span: Truthound span.
|
|
315
|
+
parent_context: Parent context.
|
|
316
|
+
"""
|
|
317
|
+
if not is_otel_sdk_available():
|
|
318
|
+
return
|
|
319
|
+
|
|
320
|
+
try:
|
|
321
|
+
# OTEL processor expects OTEL span, but we can try to pass data
|
|
322
|
+
# Most processors don't heavily use on_start
|
|
323
|
+
pass
|
|
324
|
+
except Exception as e:
|
|
325
|
+
logger.debug(f"Error in wrapped on_start: {e}")
|
|
326
|
+
|
|
327
|
+
def on_end(self, span: Any) -> None:
|
|
328
|
+
"""Called when a span ends.
|
|
329
|
+
|
|
330
|
+
Args:
|
|
331
|
+
span: Truthound span.
|
|
332
|
+
"""
|
|
333
|
+
if not is_otel_sdk_available():
|
|
334
|
+
return
|
|
335
|
+
|
|
336
|
+
try:
|
|
337
|
+
from opentelemetry.sdk.trace import ReadableSpan
|
|
338
|
+
|
|
339
|
+
# Create a readable span from Truthound span
|
|
340
|
+
otel_span = _create_readable_span_from_truthound(
|
|
341
|
+
span, self._span_bridge, self._context_bridge
|
|
342
|
+
)
|
|
343
|
+
self._processor.on_end(otel_span)
|
|
344
|
+
except Exception as e:
|
|
345
|
+
logger.debug(f"Error in wrapped on_end: {e}")
|
|
346
|
+
|
|
347
|
+
def shutdown(self) -> bool:
|
|
348
|
+
"""Shutdown the processor."""
|
|
349
|
+
try:
|
|
350
|
+
self._processor.shutdown()
|
|
351
|
+
return True
|
|
352
|
+
except Exception:
|
|
353
|
+
return False
|
|
354
|
+
|
|
355
|
+
def force_flush(self, timeout_millis: int = 30000) -> bool:
|
|
356
|
+
"""Force flush the processor."""
|
|
357
|
+
try:
|
|
358
|
+
return self._processor.force_flush(timeout_millis)
|
|
359
|
+
except Exception:
|
|
360
|
+
return False
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
class OTELProcessorWrapper:
|
|
364
|
+
"""Wraps a Truthound processor for use with OTEL SDK's TracerProvider.
|
|
365
|
+
|
|
366
|
+
Translates OTEL spans to Truthound format before passing to the
|
|
367
|
+
wrapped processor.
|
|
368
|
+
"""
|
|
369
|
+
|
|
370
|
+
def __init__(
|
|
371
|
+
self,
|
|
372
|
+
truthound_processor: Any,
|
|
373
|
+
span_bridge: SpanBridge,
|
|
374
|
+
context_bridge: SpanContextBridge,
|
|
375
|
+
) -> None:
|
|
376
|
+
"""Initialize wrapper.
|
|
377
|
+
|
|
378
|
+
Args:
|
|
379
|
+
truthound_processor: Truthound processor to wrap.
|
|
380
|
+
span_bridge: Span bridge for conversions.
|
|
381
|
+
context_bridge: Context bridge for conversions.
|
|
382
|
+
"""
|
|
383
|
+
self._processor = truthound_processor
|
|
384
|
+
self._span_bridge = span_bridge
|
|
385
|
+
self._context_bridge = context_bridge
|
|
386
|
+
|
|
387
|
+
def on_start(self, span: Any, parent_context: Any = None) -> None:
|
|
388
|
+
"""Called when a span starts.
|
|
389
|
+
|
|
390
|
+
Args:
|
|
391
|
+
span: OTEL span.
|
|
392
|
+
parent_context: OTEL context.
|
|
393
|
+
"""
|
|
394
|
+
try:
|
|
395
|
+
# Convert parent context if provided
|
|
396
|
+
truthound_parent = None
|
|
397
|
+
if parent_context:
|
|
398
|
+
from opentelemetry.trace import get_current_span
|
|
399
|
+
|
|
400
|
+
current = get_current_span(parent_context)
|
|
401
|
+
if current and hasattr(current, "get_span_context"):
|
|
402
|
+
truthound_parent = self._context_bridge.otel_to_truthound(
|
|
403
|
+
current.get_span_context()
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
# Create minimal Truthound span wrapper
|
|
407
|
+
wrapper = _create_truthound_span_from_otel(span, self._context_bridge)
|
|
408
|
+
self._processor.on_start(wrapper, truthound_parent)
|
|
409
|
+
except Exception as e:
|
|
410
|
+
logger.debug(f"Error in wrapped on_start: {e}")
|
|
411
|
+
|
|
412
|
+
def on_end(self, span: Any) -> None:
|
|
413
|
+
"""Called when a span ends.
|
|
414
|
+
|
|
415
|
+
Args:
|
|
416
|
+
span: OTEL span (ReadableSpan).
|
|
417
|
+
"""
|
|
418
|
+
try:
|
|
419
|
+
wrapper = _create_truthound_span_from_otel(span, self._context_bridge)
|
|
420
|
+
self._processor.on_end(wrapper)
|
|
421
|
+
except Exception as e:
|
|
422
|
+
logger.debug(f"Error in wrapped on_end: {e}")
|
|
423
|
+
|
|
424
|
+
def shutdown(self) -> bool:
|
|
425
|
+
"""Shutdown the processor."""
|
|
426
|
+
try:
|
|
427
|
+
return self._processor.shutdown()
|
|
428
|
+
except Exception:
|
|
429
|
+
return False
|
|
430
|
+
|
|
431
|
+
def force_flush(self, timeout_millis: int = 30000) -> bool:
|
|
432
|
+
"""Force flush the processor."""
|
|
433
|
+
try:
|
|
434
|
+
return self._processor.force_flush(timeout_millis)
|
|
435
|
+
except Exception:
|
|
436
|
+
return False
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
# =============================================================================
|
|
440
|
+
# Span Exporter Bridge
|
|
441
|
+
# =============================================================================
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
class SpanExporterBridge:
|
|
445
|
+
"""Bridges span exporters between backends.
|
|
446
|
+
|
|
447
|
+
Allows using OTEL exporters with Truthound spans and vice versa.
|
|
448
|
+
|
|
449
|
+
Example:
|
|
450
|
+
>>> # Use OTEL OTLP exporter with Truthound
|
|
451
|
+
>>> from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
|
|
452
|
+
>>>
|
|
453
|
+
>>> bridge = SpanExporterBridge()
|
|
454
|
+
>>> truthound_exporter = bridge.wrap_otel_exporter(OTLPSpanExporter())
|
|
455
|
+
>>> provider.add_processor(SimpleSpanProcessor(truthound_exporter))
|
|
456
|
+
"""
|
|
457
|
+
|
|
458
|
+
def __init__(self) -> None:
|
|
459
|
+
"""Initialize exporter bridge."""
|
|
460
|
+
self._span_bridge = SpanBridge()
|
|
461
|
+
self._context_bridge = SpanContextBridge()
|
|
462
|
+
|
|
463
|
+
def wrap_otel_exporter(self, otel_exporter: Any) -> "TruthoundExporterWrapper":
|
|
464
|
+
"""Wrap an OTEL exporter for use with Truthound.
|
|
465
|
+
|
|
466
|
+
Args:
|
|
467
|
+
otel_exporter: OpenTelemetry SpanExporter.
|
|
468
|
+
|
|
469
|
+
Returns:
|
|
470
|
+
Truthound-compatible exporter wrapper.
|
|
471
|
+
"""
|
|
472
|
+
return TruthoundExporterWrapper(otel_exporter, self._span_bridge, self._context_bridge)
|
|
473
|
+
|
|
474
|
+
def wrap_truthound_exporter(self, truthound_exporter: Any) -> "OTELExporterWrapper":
|
|
475
|
+
"""Wrap a Truthound exporter for use with OTEL SDK.
|
|
476
|
+
|
|
477
|
+
Args:
|
|
478
|
+
truthound_exporter: Truthound SpanExporter.
|
|
479
|
+
|
|
480
|
+
Returns:
|
|
481
|
+
OTEL-compatible exporter wrapper.
|
|
482
|
+
"""
|
|
483
|
+
return OTELExporterWrapper(truthound_exporter, self._span_bridge, self._context_bridge)
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
class TruthoundExporterWrapper:
|
|
487
|
+
"""Wraps an OTEL exporter for use with Truthound.
|
|
488
|
+
|
|
489
|
+
Converts Truthound spans to OTEL ReadableSpan format before exporting.
|
|
490
|
+
"""
|
|
491
|
+
|
|
492
|
+
def __init__(
|
|
493
|
+
self,
|
|
494
|
+
otel_exporter: Any,
|
|
495
|
+
span_bridge: SpanBridge,
|
|
496
|
+
context_bridge: SpanContextBridge,
|
|
497
|
+
) -> None:
|
|
498
|
+
"""Initialize wrapper.
|
|
499
|
+
|
|
500
|
+
Args:
|
|
501
|
+
otel_exporter: OTEL exporter to wrap.
|
|
502
|
+
span_bridge: Span bridge for conversions.
|
|
503
|
+
context_bridge: Context bridge for conversions.
|
|
504
|
+
"""
|
|
505
|
+
self._exporter = otel_exporter
|
|
506
|
+
self._span_bridge = span_bridge
|
|
507
|
+
self._context_bridge = context_bridge
|
|
508
|
+
self._shutdown = False
|
|
509
|
+
|
|
510
|
+
def export(self, spans: Sequence[Any]) -> Any:
|
|
511
|
+
"""Export spans.
|
|
512
|
+
|
|
513
|
+
Args:
|
|
514
|
+
spans: Truthound spans to export.
|
|
515
|
+
|
|
516
|
+
Returns:
|
|
517
|
+
Export result.
|
|
518
|
+
"""
|
|
519
|
+
if self._shutdown:
|
|
520
|
+
from truthound.observability.tracing.exporter import ExportResult
|
|
521
|
+
return ExportResult.FAILURE
|
|
522
|
+
|
|
523
|
+
if not is_otel_sdk_available():
|
|
524
|
+
from truthound.observability.tracing.exporter import ExportResult
|
|
525
|
+
return ExportResult.FAILURE
|
|
526
|
+
|
|
527
|
+
try:
|
|
528
|
+
# Convert to OTEL readable spans
|
|
529
|
+
otel_spans = [
|
|
530
|
+
_create_readable_span_from_truthound(s, self._span_bridge, self._context_bridge)
|
|
531
|
+
for s in spans
|
|
532
|
+
]
|
|
533
|
+
|
|
534
|
+
# Export using OTEL exporter
|
|
535
|
+
result = self._exporter.export(otel_spans)
|
|
536
|
+
|
|
537
|
+
# Convert result
|
|
538
|
+
from opentelemetry.sdk.trace.export import SpanExportResult
|
|
539
|
+
from truthound.observability.tracing.exporter import ExportResult
|
|
540
|
+
|
|
541
|
+
result_map = {
|
|
542
|
+
SpanExportResult.SUCCESS: ExportResult.SUCCESS,
|
|
543
|
+
SpanExportResult.FAILURE: ExportResult.FAILURE,
|
|
544
|
+
}
|
|
545
|
+
return result_map.get(result, ExportResult.FAILURE)
|
|
546
|
+
|
|
547
|
+
except Exception as e:
|
|
548
|
+
logger.error(f"Error exporting spans via OTEL: {e}")
|
|
549
|
+
from truthound.observability.tracing.exporter import ExportResult
|
|
550
|
+
return ExportResult.FAILURE
|
|
551
|
+
|
|
552
|
+
def shutdown(self) -> None:
|
|
553
|
+
"""Shutdown the exporter."""
|
|
554
|
+
self._shutdown = True
|
|
555
|
+
try:
|
|
556
|
+
self._exporter.shutdown()
|
|
557
|
+
except Exception:
|
|
558
|
+
pass
|
|
559
|
+
|
|
560
|
+
def force_flush(self, timeout_millis: int = 30000) -> bool:
|
|
561
|
+
"""Force flush the exporter."""
|
|
562
|
+
try:
|
|
563
|
+
if hasattr(self._exporter, "force_flush"):
|
|
564
|
+
return self._exporter.force_flush(timeout_millis)
|
|
565
|
+
return True
|
|
566
|
+
except Exception:
|
|
567
|
+
return False
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
class OTELExporterWrapper:
|
|
571
|
+
"""Wraps a Truthound exporter for use with OTEL SDK.
|
|
572
|
+
|
|
573
|
+
Converts OTEL ReadableSpan to Truthound span format before exporting.
|
|
574
|
+
"""
|
|
575
|
+
|
|
576
|
+
def __init__(
|
|
577
|
+
self,
|
|
578
|
+
truthound_exporter: Any,
|
|
579
|
+
span_bridge: SpanBridge,
|
|
580
|
+
context_bridge: SpanContextBridge,
|
|
581
|
+
) -> None:
|
|
582
|
+
"""Initialize wrapper.
|
|
583
|
+
|
|
584
|
+
Args:
|
|
585
|
+
truthound_exporter: Truthound exporter to wrap.
|
|
586
|
+
span_bridge: Span bridge for conversions.
|
|
587
|
+
context_bridge: Context bridge for conversions.
|
|
588
|
+
"""
|
|
589
|
+
self._exporter = truthound_exporter
|
|
590
|
+
self._span_bridge = span_bridge
|
|
591
|
+
self._context_bridge = context_bridge
|
|
592
|
+
self._shutdown = False
|
|
593
|
+
|
|
594
|
+
def export(self, spans: Sequence[Any]) -> Any:
|
|
595
|
+
"""Export spans.
|
|
596
|
+
|
|
597
|
+
Args:
|
|
598
|
+
spans: OTEL spans (ReadableSpan) to export.
|
|
599
|
+
|
|
600
|
+
Returns:
|
|
601
|
+
OTEL SpanExportResult.
|
|
602
|
+
"""
|
|
603
|
+
if not is_otel_sdk_available():
|
|
604
|
+
from opentelemetry.sdk.trace.export import SpanExportResult
|
|
605
|
+
return SpanExportResult.FAILURE
|
|
606
|
+
|
|
607
|
+
try:
|
|
608
|
+
# Convert to Truthound spans
|
|
609
|
+
truthound_spans = [
|
|
610
|
+
_create_truthound_span_from_otel(s, self._context_bridge)
|
|
611
|
+
for s in spans
|
|
612
|
+
]
|
|
613
|
+
|
|
614
|
+
# Export using Truthound exporter
|
|
615
|
+
result = self._exporter.export(truthound_spans)
|
|
616
|
+
|
|
617
|
+
# Convert result
|
|
618
|
+
from opentelemetry.sdk.trace.export import SpanExportResult
|
|
619
|
+
from truthound.observability.tracing.exporter import ExportResult
|
|
620
|
+
|
|
621
|
+
result_map = {
|
|
622
|
+
ExportResult.SUCCESS: SpanExportResult.SUCCESS,
|
|
623
|
+
ExportResult.FAILURE: SpanExportResult.FAILURE,
|
|
624
|
+
ExportResult.RETRY: SpanExportResult.FAILURE,
|
|
625
|
+
}
|
|
626
|
+
return result_map.get(result, SpanExportResult.FAILURE)
|
|
627
|
+
|
|
628
|
+
except Exception as e:
|
|
629
|
+
logger.error(f"Error exporting spans via Truthound: {e}")
|
|
630
|
+
from opentelemetry.sdk.trace.export import SpanExportResult
|
|
631
|
+
return SpanExportResult.FAILURE
|
|
632
|
+
|
|
633
|
+
def shutdown(self) -> None:
|
|
634
|
+
"""Shutdown the exporter."""
|
|
635
|
+
self._shutdown = True
|
|
636
|
+
try:
|
|
637
|
+
self._exporter.shutdown()
|
|
638
|
+
except Exception:
|
|
639
|
+
pass
|
|
640
|
+
|
|
641
|
+
def force_flush(self, timeout_millis: int = 30000) -> bool:
|
|
642
|
+
"""Force flush the exporter."""
|
|
643
|
+
try:
|
|
644
|
+
if hasattr(self._exporter, "force_flush"):
|
|
645
|
+
return self._exporter.force_flush(timeout_millis)
|
|
646
|
+
return True
|
|
647
|
+
except Exception:
|
|
648
|
+
return False
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
# =============================================================================
|
|
652
|
+
# Sampler Bridge
|
|
653
|
+
# =============================================================================
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
class SamplerBridge:
|
|
657
|
+
"""Bridges samplers between backends.
|
|
658
|
+
|
|
659
|
+
Allows using OTEL samplers with Truthound and vice versa.
|
|
660
|
+
"""
|
|
661
|
+
|
|
662
|
+
def __init__(self) -> None:
|
|
663
|
+
"""Initialize sampler bridge."""
|
|
664
|
+
self._context_bridge = SpanContextBridge()
|
|
665
|
+
|
|
666
|
+
def wrap_otel_sampler(self, otel_sampler: Any) -> "TruthoundSamplerWrapper":
|
|
667
|
+
"""Wrap an OTEL sampler for use with Truthound.
|
|
668
|
+
|
|
669
|
+
Args:
|
|
670
|
+
otel_sampler: OpenTelemetry Sampler.
|
|
671
|
+
|
|
672
|
+
Returns:
|
|
673
|
+
Truthound-compatible sampler wrapper.
|
|
674
|
+
"""
|
|
675
|
+
return TruthoundSamplerWrapper(otel_sampler, self._context_bridge)
|
|
676
|
+
|
|
677
|
+
def wrap_truthound_sampler(self, truthound_sampler: Any) -> "OTELSamplerWrapper":
|
|
678
|
+
"""Wrap a Truthound sampler for use with OTEL SDK.
|
|
679
|
+
|
|
680
|
+
Args:
|
|
681
|
+
truthound_sampler: Truthound Sampler.
|
|
682
|
+
|
|
683
|
+
Returns:
|
|
684
|
+
OTEL-compatible sampler wrapper.
|
|
685
|
+
"""
|
|
686
|
+
return OTELSamplerWrapper(truthound_sampler, self._context_bridge)
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
class TruthoundSamplerWrapper:
|
|
690
|
+
"""Wraps an OTEL sampler for use with Truthound."""
|
|
691
|
+
|
|
692
|
+
def __init__(self, otel_sampler: Any, context_bridge: SpanContextBridge) -> None:
|
|
693
|
+
self._sampler = otel_sampler
|
|
694
|
+
self._context_bridge = context_bridge
|
|
695
|
+
|
|
696
|
+
def should_sample(
|
|
697
|
+
self,
|
|
698
|
+
parent_context: Any,
|
|
699
|
+
trace_id: str | int,
|
|
700
|
+
name: str,
|
|
701
|
+
kind: Any = None,
|
|
702
|
+
attributes: Mapping[str, Any] | None = None,
|
|
703
|
+
links: Sequence[Any] | None = None,
|
|
704
|
+
) -> Any:
|
|
705
|
+
"""Make sampling decision."""
|
|
706
|
+
from truthound.observability.tracing.sampler import SamplingResult, SamplingDecision
|
|
707
|
+
|
|
708
|
+
if not is_otel_sdk_available():
|
|
709
|
+
return SamplingResult(decision=SamplingDecision.RECORD_AND_SAMPLE)
|
|
710
|
+
|
|
711
|
+
try:
|
|
712
|
+
from opentelemetry.trace import SpanKind
|
|
713
|
+
|
|
714
|
+
# Convert kind
|
|
715
|
+
otel_kind = SpanKind.INTERNAL
|
|
716
|
+
if kind and hasattr(kind, "name"):
|
|
717
|
+
kind_map = {
|
|
718
|
+
"INTERNAL": SpanKind.INTERNAL,
|
|
719
|
+
"SERVER": SpanKind.SERVER,
|
|
720
|
+
"CLIENT": SpanKind.CLIENT,
|
|
721
|
+
"PRODUCER": SpanKind.PRODUCER,
|
|
722
|
+
"CONSUMER": SpanKind.CONSUMER,
|
|
723
|
+
}
|
|
724
|
+
otel_kind = kind_map.get(kind.name, SpanKind.INTERNAL)
|
|
725
|
+
|
|
726
|
+
# Convert trace_id
|
|
727
|
+
if isinstance(trace_id, str):
|
|
728
|
+
trace_id = int(trace_id, 16)
|
|
729
|
+
|
|
730
|
+
# Call OTEL sampler
|
|
731
|
+
otel_result = self._sampler.should_sample(
|
|
732
|
+
parent_context=None, # OTEL uses Context, not SpanContext directly
|
|
733
|
+
trace_id=trace_id,
|
|
734
|
+
name=name,
|
|
735
|
+
kind=otel_kind,
|
|
736
|
+
attributes=dict(attributes) if attributes else None,
|
|
737
|
+
links=links,
|
|
738
|
+
)
|
|
739
|
+
|
|
740
|
+
# Convert result
|
|
741
|
+
from opentelemetry.sdk.trace.sampling import Decision
|
|
742
|
+
|
|
743
|
+
decision_map = {
|
|
744
|
+
Decision.DROP: SamplingDecision.DROP,
|
|
745
|
+
Decision.RECORD_ONLY: SamplingDecision.RECORD_ONLY,
|
|
746
|
+
Decision.RECORD_AND_SAMPLE: SamplingDecision.RECORD_AND_SAMPLE,
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
return SamplingResult(
|
|
750
|
+
decision=decision_map.get(otel_result.decision, SamplingDecision.RECORD_AND_SAMPLE),
|
|
751
|
+
attributes=dict(otel_result.attributes) if otel_result.attributes else {},
|
|
752
|
+
)
|
|
753
|
+
|
|
754
|
+
except Exception as e:
|
|
755
|
+
logger.debug(f"Error in sampler wrapper: {e}")
|
|
756
|
+
return SamplingResult(decision=SamplingDecision.RECORD_AND_SAMPLE)
|
|
757
|
+
|
|
758
|
+
def get_description(self) -> str:
|
|
759
|
+
"""Get sampler description."""
|
|
760
|
+
return f"OTELWrapper({self._sampler.get_description()})"
|
|
761
|
+
|
|
762
|
+
|
|
763
|
+
class OTELSamplerWrapper:
|
|
764
|
+
"""Wraps a Truthound sampler for use with OTEL SDK."""
|
|
765
|
+
|
|
766
|
+
def __init__(self, truthound_sampler: Any, context_bridge: SpanContextBridge) -> None:
|
|
767
|
+
self._sampler = truthound_sampler
|
|
768
|
+
self._context_bridge = context_bridge
|
|
769
|
+
|
|
770
|
+
def should_sample(
|
|
771
|
+
self,
|
|
772
|
+
parent_context: Any,
|
|
773
|
+
trace_id: int,
|
|
774
|
+
name: str,
|
|
775
|
+
kind: Any = None,
|
|
776
|
+
attributes: Mapping[str, Any] | None = None,
|
|
777
|
+
links: Sequence[Any] | None = None,
|
|
778
|
+
) -> Any:
|
|
779
|
+
"""Make sampling decision."""
|
|
780
|
+
if not is_otel_sdk_available():
|
|
781
|
+
from opentelemetry.sdk.trace.sampling import Decision, SamplingResult
|
|
782
|
+
return SamplingResult(decision=Decision.RECORD_AND_SAMPLE, attributes={})
|
|
783
|
+
|
|
784
|
+
try:
|
|
785
|
+
from opentelemetry.sdk.trace.sampling import Decision, SamplingResult
|
|
786
|
+
from truthound.observability.tracing.sampler import SamplingDecision
|
|
787
|
+
|
|
788
|
+
# Call Truthound sampler
|
|
789
|
+
result = self._sampler.should_sample(
|
|
790
|
+
parent_context=None,
|
|
791
|
+
trace_id=format(trace_id, "032x"),
|
|
792
|
+
name=name,
|
|
793
|
+
kind=kind,
|
|
794
|
+
attributes=attributes,
|
|
795
|
+
links=links,
|
|
796
|
+
)
|
|
797
|
+
|
|
798
|
+
# Convert result
|
|
799
|
+
decision_map = {
|
|
800
|
+
SamplingDecision.DROP: Decision.DROP,
|
|
801
|
+
SamplingDecision.RECORD_ONLY: Decision.RECORD_ONLY,
|
|
802
|
+
SamplingDecision.RECORD_AND_SAMPLE: Decision.RECORD_AND_SAMPLE,
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
return SamplingResult(
|
|
806
|
+
decision=decision_map.get(result.decision, Decision.RECORD_AND_SAMPLE),
|
|
807
|
+
attributes=result.attributes,
|
|
808
|
+
)
|
|
809
|
+
|
|
810
|
+
except Exception as e:
|
|
811
|
+
logger.debug(f"Error in sampler wrapper: {e}")
|
|
812
|
+
from opentelemetry.sdk.trace.sampling import Decision, SamplingResult
|
|
813
|
+
return SamplingResult(decision=Decision.RECORD_AND_SAMPLE, attributes={})
|
|
814
|
+
|
|
815
|
+
def get_description(self) -> str:
|
|
816
|
+
"""Get sampler description."""
|
|
817
|
+
return f"TruthoundWrapper({self._sampler.get_description()})"
|
|
818
|
+
|
|
819
|
+
|
|
820
|
+
# =============================================================================
|
|
821
|
+
# Propagator Bridge
|
|
822
|
+
# =============================================================================
|
|
823
|
+
|
|
824
|
+
|
|
825
|
+
class PropagatorBridge:
|
|
826
|
+
"""Bridges propagators between backends.
|
|
827
|
+
|
|
828
|
+
Allows using OTEL propagators with Truthound and vice versa.
|
|
829
|
+
"""
|
|
830
|
+
|
|
831
|
+
def __init__(self) -> None:
|
|
832
|
+
"""Initialize propagator bridge."""
|
|
833
|
+
self._context_bridge = SpanContextBridge()
|
|
834
|
+
|
|
835
|
+
def wrap_otel_propagator(self, otel_propagator: Any) -> "TruthoundPropagatorWrapper":
|
|
836
|
+
"""Wrap an OTEL propagator for use with Truthound."""
|
|
837
|
+
return TruthoundPropagatorWrapper(otel_propagator, self._context_bridge)
|
|
838
|
+
|
|
839
|
+
def wrap_truthound_propagator(self, truthound_propagator: Any) -> "OTELPropagatorWrapper":
|
|
840
|
+
"""Wrap a Truthound propagator for use with OTEL SDK."""
|
|
841
|
+
return OTELPropagatorWrapper(truthound_propagator, self._context_bridge)
|
|
842
|
+
|
|
843
|
+
|
|
844
|
+
class TruthoundPropagatorWrapper:
|
|
845
|
+
"""Wraps an OTEL propagator for use with Truthound."""
|
|
846
|
+
|
|
847
|
+
def __init__(self, otel_propagator: Any, context_bridge: SpanContextBridge) -> None:
|
|
848
|
+
self._propagator = otel_propagator
|
|
849
|
+
self._context_bridge = context_bridge
|
|
850
|
+
|
|
851
|
+
def extract(
|
|
852
|
+
self,
|
|
853
|
+
carrier: Mapping[str, str],
|
|
854
|
+
context: Any = None,
|
|
855
|
+
getter: Any = None,
|
|
856
|
+
) -> Any:
|
|
857
|
+
"""Extract context from carrier."""
|
|
858
|
+
if not is_otel_sdk_available():
|
|
859
|
+
return None
|
|
860
|
+
|
|
861
|
+
try:
|
|
862
|
+
from opentelemetry.propagate import extract
|
|
863
|
+
|
|
864
|
+
otel_context = extract(carrier)
|
|
865
|
+
from opentelemetry.trace import get_current_span
|
|
866
|
+
|
|
867
|
+
span = get_current_span(otel_context)
|
|
868
|
+
if span and hasattr(span, "get_span_context"):
|
|
869
|
+
return self._context_bridge.otel_to_truthound(span.get_span_context())
|
|
870
|
+
except Exception as e:
|
|
871
|
+
logger.debug(f"Error extracting context: {e}")
|
|
872
|
+
|
|
873
|
+
return None
|
|
874
|
+
|
|
875
|
+
def inject(
|
|
876
|
+
self,
|
|
877
|
+
carrier: dict[str, str],
|
|
878
|
+
context: Any = None,
|
|
879
|
+
setter: Any = None,
|
|
880
|
+
) -> None:
|
|
881
|
+
"""Inject context into carrier."""
|
|
882
|
+
if not is_otel_sdk_available() or context is None:
|
|
883
|
+
return
|
|
884
|
+
|
|
885
|
+
try:
|
|
886
|
+
from opentelemetry.propagate import inject
|
|
887
|
+
from opentelemetry.trace import set_span_in_context, NonRecordingSpan
|
|
888
|
+
|
|
889
|
+
otel_ctx = self._context_bridge.truthound_to_otel(context)
|
|
890
|
+
span = NonRecordingSpan(otel_ctx)
|
|
891
|
+
otel_context = set_span_in_context(span)
|
|
892
|
+
inject(carrier, context=otel_context)
|
|
893
|
+
except Exception as e:
|
|
894
|
+
logger.debug(f"Error injecting context: {e}")
|
|
895
|
+
|
|
896
|
+
@property
|
|
897
|
+
def fields(self) -> set[str]:
|
|
898
|
+
"""Get propagator fields."""
|
|
899
|
+
if hasattr(self._propagator, "fields"):
|
|
900
|
+
return set(self._propagator.fields)
|
|
901
|
+
return {"traceparent", "tracestate"}
|
|
902
|
+
|
|
903
|
+
|
|
904
|
+
class OTELPropagatorWrapper:
|
|
905
|
+
"""Wraps a Truthound propagator for use with OTEL SDK."""
|
|
906
|
+
|
|
907
|
+
def __init__(self, truthound_propagator: Any, context_bridge: SpanContextBridge) -> None:
|
|
908
|
+
self._propagator = truthound_propagator
|
|
909
|
+
self._context_bridge = context_bridge
|
|
910
|
+
|
|
911
|
+
def extract(
|
|
912
|
+
self,
|
|
913
|
+
carrier: Mapping[str, str],
|
|
914
|
+
context: Any = None,
|
|
915
|
+
getter: Any = None,
|
|
916
|
+
) -> Any:
|
|
917
|
+
"""Extract context from carrier."""
|
|
918
|
+
try:
|
|
919
|
+
truthound_ctx = self._propagator.extract(carrier)
|
|
920
|
+
if truthound_ctx:
|
|
921
|
+
from opentelemetry.trace import set_span_in_context, NonRecordingSpan
|
|
922
|
+
|
|
923
|
+
otel_ctx = self._context_bridge.truthound_to_otel(truthound_ctx)
|
|
924
|
+
span = NonRecordingSpan(otel_ctx)
|
|
925
|
+
return set_span_in_context(span, context)
|
|
926
|
+
except Exception as e:
|
|
927
|
+
logger.debug(f"Error extracting context: {e}")
|
|
928
|
+
|
|
929
|
+
return context
|
|
930
|
+
|
|
931
|
+
def inject(
|
|
932
|
+
self,
|
|
933
|
+
carrier: dict[str, str],
|
|
934
|
+
context: Any = None,
|
|
935
|
+
setter: Any = None,
|
|
936
|
+
) -> None:
|
|
937
|
+
"""Inject context into carrier."""
|
|
938
|
+
if not is_otel_sdk_available():
|
|
939
|
+
return
|
|
940
|
+
|
|
941
|
+
try:
|
|
942
|
+
from opentelemetry.trace import get_current_span
|
|
943
|
+
|
|
944
|
+
span = get_current_span(context)
|
|
945
|
+
if span and hasattr(span, "get_span_context"):
|
|
946
|
+
truthound_ctx = self._context_bridge.otel_to_truthound(span.get_span_context())
|
|
947
|
+
self._propagator.inject(carrier, truthound_ctx)
|
|
948
|
+
except Exception as e:
|
|
949
|
+
logger.debug(f"Error injecting context: {e}")
|
|
950
|
+
|
|
951
|
+
@property
|
|
952
|
+
def fields(self) -> set[str]:
|
|
953
|
+
"""Get propagator fields."""
|
|
954
|
+
if hasattr(self._propagator, "fields"):
|
|
955
|
+
return self._propagator.fields
|
|
956
|
+
return {"traceparent", "tracestate"}
|
|
957
|
+
|
|
958
|
+
|
|
959
|
+
# =============================================================================
|
|
960
|
+
# Helper Functions
|
|
961
|
+
# =============================================================================
|
|
962
|
+
|
|
963
|
+
|
|
964
|
+
def _create_readable_span_from_truthound(
|
|
965
|
+
span: Any,
|
|
966
|
+
span_bridge: SpanBridge,
|
|
967
|
+
context_bridge: SpanContextBridge,
|
|
968
|
+
) -> Any:
|
|
969
|
+
"""Create an OTEL ReadableSpan from a Truthound span.
|
|
970
|
+
|
|
971
|
+
This creates a minimal ReadableSpan-like object for exporting.
|
|
972
|
+
"""
|
|
973
|
+
if not is_otel_sdk_available():
|
|
974
|
+
raise ImportError("OpenTelemetry SDK not available")
|
|
975
|
+
|
|
976
|
+
from opentelemetry.sdk.trace import ReadableSpan
|
|
977
|
+
from opentelemetry.trace import SpanKind, StatusCode, Status
|
|
978
|
+
from opentelemetry.sdk.trace.export import SpanExportResult
|
|
979
|
+
|
|
980
|
+
# Get span data
|
|
981
|
+
data = span_bridge.truthound_to_otel_data(span)
|
|
982
|
+
|
|
983
|
+
# Create SpanContext
|
|
984
|
+
otel_context = context_bridge.truthound_to_otel(span.context)
|
|
985
|
+
|
|
986
|
+
# Create parent context if available
|
|
987
|
+
parent_context = None
|
|
988
|
+
if hasattr(span, "parent") and span.parent:
|
|
989
|
+
parent_context = context_bridge.truthound_to_otel(span.parent)
|
|
990
|
+
|
|
991
|
+
# Map kind
|
|
992
|
+
kind_map = {
|
|
993
|
+
0: SpanKind.INTERNAL,
|
|
994
|
+
1: SpanKind.SERVER,
|
|
995
|
+
2: SpanKind.CLIENT,
|
|
996
|
+
3: SpanKind.PRODUCER,
|
|
997
|
+
4: SpanKind.CONSUMER,
|
|
998
|
+
}
|
|
999
|
+
span_kind = kind_map.get(data["kind"], SpanKind.INTERNAL)
|
|
1000
|
+
|
|
1001
|
+
# Map status
|
|
1002
|
+
status_code_map = {0: StatusCode.UNSET, 1: StatusCode.OK, 2: StatusCode.ERROR}
|
|
1003
|
+
status = Status(
|
|
1004
|
+
status_code=status_code_map.get(data["status"]["code"], StatusCode.UNSET),
|
|
1005
|
+
description=data["status"]["message"],
|
|
1006
|
+
)
|
|
1007
|
+
|
|
1008
|
+
# Create a ReadableSpan-like object
|
|
1009
|
+
# Note: This is a simplified version; full implementation would need
|
|
1010
|
+
# to properly implement the ReadableSpan interface
|
|
1011
|
+
class _ReadableSpanAdapter:
|
|
1012
|
+
def __init__(self):
|
|
1013
|
+
self._context = otel_context
|
|
1014
|
+
self._parent = parent_context
|
|
1015
|
+
self._name = data["name"]
|
|
1016
|
+
self._kind = span_kind
|
|
1017
|
+
self._start_time = data["start_time_ns"]
|
|
1018
|
+
self._end_time = data["end_time_ns"]
|
|
1019
|
+
self._attributes = data["attributes"]
|
|
1020
|
+
self._events = tuple()
|
|
1021
|
+
self._links = tuple()
|
|
1022
|
+
self._status = status
|
|
1023
|
+
self._resource = None
|
|
1024
|
+
self._instrumentation_scope = None
|
|
1025
|
+
|
|
1026
|
+
def get_span_context(self):
|
|
1027
|
+
return self._context
|
|
1028
|
+
|
|
1029
|
+
@property
|
|
1030
|
+
def name(self):
|
|
1031
|
+
return self._name
|
|
1032
|
+
|
|
1033
|
+
@property
|
|
1034
|
+
def context(self):
|
|
1035
|
+
return self._context
|
|
1036
|
+
|
|
1037
|
+
@property
|
|
1038
|
+
def parent(self):
|
|
1039
|
+
return self._parent
|
|
1040
|
+
|
|
1041
|
+
@property
|
|
1042
|
+
def kind(self):
|
|
1043
|
+
return self._kind
|
|
1044
|
+
|
|
1045
|
+
@property
|
|
1046
|
+
def start_time(self):
|
|
1047
|
+
return self._start_time
|
|
1048
|
+
|
|
1049
|
+
@property
|
|
1050
|
+
def end_time(self):
|
|
1051
|
+
return self._end_time
|
|
1052
|
+
|
|
1053
|
+
@property
|
|
1054
|
+
def attributes(self):
|
|
1055
|
+
return self._attributes
|
|
1056
|
+
|
|
1057
|
+
@property
|
|
1058
|
+
def events(self):
|
|
1059
|
+
return self._events
|
|
1060
|
+
|
|
1061
|
+
@property
|
|
1062
|
+
def links(self):
|
|
1063
|
+
return self._links
|
|
1064
|
+
|
|
1065
|
+
@property
|
|
1066
|
+
def status(self):
|
|
1067
|
+
return self._status
|
|
1068
|
+
|
|
1069
|
+
@property
|
|
1070
|
+
def resource(self):
|
|
1071
|
+
return self._resource
|
|
1072
|
+
|
|
1073
|
+
@property
|
|
1074
|
+
def instrumentation_scope(self):
|
|
1075
|
+
return self._instrumentation_scope
|
|
1076
|
+
|
|
1077
|
+
return _ReadableSpanAdapter()
|
|
1078
|
+
|
|
1079
|
+
|
|
1080
|
+
def _create_truthound_span_from_otel(
|
|
1081
|
+
otel_span: Any,
|
|
1082
|
+
context_bridge: SpanContextBridge,
|
|
1083
|
+
) -> Any:
|
|
1084
|
+
"""Create a Truthound Span-like object from an OTEL span.
|
|
1085
|
+
|
|
1086
|
+
This creates a minimal span wrapper for Truthound processors.
|
|
1087
|
+
"""
|
|
1088
|
+
from truthound.observability.tracing.span import SpanContextData, SpanKind, StatusCode
|
|
1089
|
+
|
|
1090
|
+
# Get context
|
|
1091
|
+
otel_context = otel_span.get_span_context() if hasattr(otel_span, "get_span_context") else otel_span.context
|
|
1092
|
+
truthound_context = context_bridge.otel_to_truthound(otel_context)
|
|
1093
|
+
|
|
1094
|
+
# Map kind
|
|
1095
|
+
kind_map = {
|
|
1096
|
+
"INTERNAL": SpanKind.INTERNAL,
|
|
1097
|
+
"SERVER": SpanKind.SERVER,
|
|
1098
|
+
"CLIENT": SpanKind.CLIENT,
|
|
1099
|
+
"PRODUCER": SpanKind.PRODUCER,
|
|
1100
|
+
"CONSUMER": SpanKind.CONSUMER,
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
span_kind = SpanKind.INTERNAL
|
|
1104
|
+
if hasattr(otel_span, "kind") and otel_span.kind:
|
|
1105
|
+
span_kind = kind_map.get(otel_span.kind.name, SpanKind.INTERNAL)
|
|
1106
|
+
|
|
1107
|
+
# Create wrapper class
|
|
1108
|
+
class _TruthoundSpanAdapter:
|
|
1109
|
+
def __init__(self):
|
|
1110
|
+
self._context = truthound_context
|
|
1111
|
+
self._name = otel_span.name if hasattr(otel_span, "name") else ""
|
|
1112
|
+
self._kind = span_kind
|
|
1113
|
+
self._start_time = otel_span.start_time / 1_000_000_000 if hasattr(otel_span, "start_time") else time.time()
|
|
1114
|
+
self._end_time = otel_span.end_time / 1_000_000_000 if hasattr(otel_span, "end_time") and otel_span.end_time else None
|
|
1115
|
+
self._attributes = dict(otel_span.attributes) if hasattr(otel_span, "attributes") and otel_span.attributes else {}
|
|
1116
|
+
self._events = []
|
|
1117
|
+
self._links = []
|
|
1118
|
+
self._status = (StatusCode.UNSET, "")
|
|
1119
|
+
self._parent = None
|
|
1120
|
+
|
|
1121
|
+
if hasattr(otel_span, "parent") and otel_span.parent:
|
|
1122
|
+
self._parent = context_bridge.otel_to_truthound(otel_span.parent)
|
|
1123
|
+
|
|
1124
|
+
if hasattr(otel_span, "status") and otel_span.status:
|
|
1125
|
+
status_map = {"UNSET": StatusCode.UNSET, "OK": StatusCode.OK, "ERROR": StatusCode.ERROR}
|
|
1126
|
+
self._status = (
|
|
1127
|
+
status_map.get(otel_span.status.status_code.name, StatusCode.UNSET),
|
|
1128
|
+
otel_span.status.description or "",
|
|
1129
|
+
)
|
|
1130
|
+
|
|
1131
|
+
@property
|
|
1132
|
+
def context(self):
|
|
1133
|
+
return self._context
|
|
1134
|
+
|
|
1135
|
+
@property
|
|
1136
|
+
def name(self):
|
|
1137
|
+
return self._name
|
|
1138
|
+
|
|
1139
|
+
@property
|
|
1140
|
+
def kind(self):
|
|
1141
|
+
return self._kind
|
|
1142
|
+
|
|
1143
|
+
@property
|
|
1144
|
+
def start_time(self):
|
|
1145
|
+
return self._start_time
|
|
1146
|
+
|
|
1147
|
+
@property
|
|
1148
|
+
def end_time(self):
|
|
1149
|
+
return self._end_time
|
|
1150
|
+
|
|
1151
|
+
@property
|
|
1152
|
+
def attributes(self):
|
|
1153
|
+
return self._attributes
|
|
1154
|
+
|
|
1155
|
+
@property
|
|
1156
|
+
def events(self):
|
|
1157
|
+
return self._events
|
|
1158
|
+
|
|
1159
|
+
@property
|
|
1160
|
+
def links(self):
|
|
1161
|
+
return self._links
|
|
1162
|
+
|
|
1163
|
+
@property
|
|
1164
|
+
def status(self):
|
|
1165
|
+
return self._status
|
|
1166
|
+
|
|
1167
|
+
@property
|
|
1168
|
+
def parent(self):
|
|
1169
|
+
return self._parent
|
|
1170
|
+
|
|
1171
|
+
@property
|
|
1172
|
+
def duration_ms(self):
|
|
1173
|
+
if self._end_time is None:
|
|
1174
|
+
return None
|
|
1175
|
+
return (self._end_time - self._start_time) * 1000
|
|
1176
|
+
|
|
1177
|
+
return _TruthoundSpanAdapter()
|