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,1150 @@
|
|
|
1
|
+
"""Mixins for common reporter functionality.
|
|
2
|
+
|
|
3
|
+
This module provides reusable mixins that can be combined with reporter
|
|
4
|
+
base classes to add common functionality:
|
|
5
|
+
|
|
6
|
+
- FormattingMixin: Text formatting, tables, colors
|
|
7
|
+
- AggregationMixin: Grouping, counting, statistics
|
|
8
|
+
- FilteringMixin: Filtering by severity, column, validator
|
|
9
|
+
- SerializationMixin: JSON, YAML, XML serialization helpers
|
|
10
|
+
- TemplatingMixin: Template rendering with Jinja2
|
|
11
|
+
- StreamingMixin: Streaming output for large datasets
|
|
12
|
+
|
|
13
|
+
Example:
|
|
14
|
+
>>> class MyReporter(FormattingMixin, FilteringMixin, ValidationReporter[Config]):
|
|
15
|
+
... def render(self, data):
|
|
16
|
+
... issues = self.filter_by_severity(data, min_severity="medium")
|
|
17
|
+
... return self.format_as_table(issues)
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import csv
|
|
23
|
+
import io
|
|
24
|
+
import json
|
|
25
|
+
from abc import ABC
|
|
26
|
+
from collections import defaultdict
|
|
27
|
+
from dataclasses import asdict, is_dataclass
|
|
28
|
+
from datetime import datetime, date
|
|
29
|
+
from enum import Enum
|
|
30
|
+
from typing import (
|
|
31
|
+
TYPE_CHECKING,
|
|
32
|
+
Any,
|
|
33
|
+
Callable,
|
|
34
|
+
Iterator,
|
|
35
|
+
Sequence,
|
|
36
|
+
TypeVar,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
if TYPE_CHECKING:
|
|
40
|
+
from truthound.stores.results import ValidationResult, ValidatorResult
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
T = TypeVar("T")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# =============================================================================
|
|
47
|
+
# Severity Ordering
|
|
48
|
+
# =============================================================================
|
|
49
|
+
|
|
50
|
+
SEVERITY_ORDER = {
|
|
51
|
+
"critical": 0,
|
|
52
|
+
"high": 1,
|
|
53
|
+
"medium": 2,
|
|
54
|
+
"low": 3,
|
|
55
|
+
"info": 4,
|
|
56
|
+
"none": 5,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _get_severity_level(severity: str | None) -> int:
|
|
61
|
+
"""Get numeric severity level for comparison."""
|
|
62
|
+
if severity is None:
|
|
63
|
+
return SEVERITY_ORDER.get("none", 5)
|
|
64
|
+
return SEVERITY_ORDER.get(severity.lower(), 5)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# =============================================================================
|
|
68
|
+
# Formatting Mixin
|
|
69
|
+
# =============================================================================
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class FormattingMixin(ABC):
|
|
73
|
+
"""Mixin providing text formatting utilities.
|
|
74
|
+
|
|
75
|
+
Provides methods for:
|
|
76
|
+
- Table formatting (ASCII, Markdown, HTML)
|
|
77
|
+
- Text alignment and padding
|
|
78
|
+
- Color/style helpers
|
|
79
|
+
- Number formatting
|
|
80
|
+
- Date/time formatting
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
# -------------------------------------------------------------------------
|
|
84
|
+
# Table Formatting
|
|
85
|
+
# -------------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
def format_as_table(
|
|
88
|
+
self,
|
|
89
|
+
rows: Sequence[dict[str, Any]],
|
|
90
|
+
columns: list[str] | None = None,
|
|
91
|
+
headers: dict[str, str] | None = None,
|
|
92
|
+
style: str = "ascii",
|
|
93
|
+
max_width: int | None = None,
|
|
94
|
+
) -> str:
|
|
95
|
+
"""Format data as a text table.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
rows: Sequence of dictionaries representing rows.
|
|
99
|
+
columns: Column names to include (default: all columns from first row).
|
|
100
|
+
headers: Optional header labels (column name -> display name).
|
|
101
|
+
style: Table style ("ascii", "markdown", "simple", "grid").
|
|
102
|
+
max_width: Maximum column width (truncates content).
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
Formatted table as string.
|
|
106
|
+
|
|
107
|
+
Example:
|
|
108
|
+
>>> rows = [{"name": "col1", "count": 10}, {"name": "col2", "count": 5}]
|
|
109
|
+
>>> print(self.format_as_table(rows, style="markdown"))
|
|
110
|
+
| name | count |
|
|
111
|
+
|------|-------|
|
|
112
|
+
| col1 | 10 |
|
|
113
|
+
| col2 | 5 |
|
|
114
|
+
"""
|
|
115
|
+
if not rows:
|
|
116
|
+
return ""
|
|
117
|
+
|
|
118
|
+
# Determine columns
|
|
119
|
+
if columns is None:
|
|
120
|
+
columns = list(rows[0].keys())
|
|
121
|
+
|
|
122
|
+
headers = headers or {}
|
|
123
|
+
|
|
124
|
+
# Calculate column widths
|
|
125
|
+
widths: dict[str, int] = {}
|
|
126
|
+
for col in columns:
|
|
127
|
+
header_text = headers.get(col, col)
|
|
128
|
+
widths[col] = len(str(header_text))
|
|
129
|
+
for row in rows:
|
|
130
|
+
value = str(row.get(col, ""))
|
|
131
|
+
if max_width:
|
|
132
|
+
value = value[:max_width]
|
|
133
|
+
widths[col] = max(widths[col], len(value))
|
|
134
|
+
|
|
135
|
+
# Build table based on style
|
|
136
|
+
if style == "markdown":
|
|
137
|
+
return self._format_markdown_table(rows, columns, headers, widths, max_width)
|
|
138
|
+
elif style == "grid":
|
|
139
|
+
return self._format_grid_table(rows, columns, headers, widths, max_width)
|
|
140
|
+
elif style == "simple":
|
|
141
|
+
return self._format_simple_table(rows, columns, headers, widths, max_width)
|
|
142
|
+
else: # ascii
|
|
143
|
+
return self._format_ascii_table(rows, columns, headers, widths, max_width)
|
|
144
|
+
|
|
145
|
+
def _format_markdown_table(
|
|
146
|
+
self,
|
|
147
|
+
rows: Sequence[dict[str, Any]],
|
|
148
|
+
columns: list[str],
|
|
149
|
+
headers: dict[str, str],
|
|
150
|
+
widths: dict[str, int],
|
|
151
|
+
max_width: int | None,
|
|
152
|
+
) -> str:
|
|
153
|
+
"""Format as Markdown table."""
|
|
154
|
+
lines = []
|
|
155
|
+
|
|
156
|
+
# Header row
|
|
157
|
+
header_cells = [
|
|
158
|
+
headers.get(col, col).ljust(widths[col]) for col in columns
|
|
159
|
+
]
|
|
160
|
+
lines.append("| " + " | ".join(header_cells) + " |")
|
|
161
|
+
|
|
162
|
+
# Separator
|
|
163
|
+
sep_cells = ["-" * widths[col] for col in columns]
|
|
164
|
+
lines.append("| " + " | ".join(sep_cells) + " |")
|
|
165
|
+
|
|
166
|
+
# Data rows
|
|
167
|
+
for row in rows:
|
|
168
|
+
cells = []
|
|
169
|
+
for col in columns:
|
|
170
|
+
value = str(row.get(col, ""))
|
|
171
|
+
if max_width:
|
|
172
|
+
value = value[:max_width]
|
|
173
|
+
cells.append(value.ljust(widths[col]))
|
|
174
|
+
lines.append("| " + " | ".join(cells) + " |")
|
|
175
|
+
|
|
176
|
+
return "\n".join(lines)
|
|
177
|
+
|
|
178
|
+
def _format_ascii_table(
|
|
179
|
+
self,
|
|
180
|
+
rows: Sequence[dict[str, Any]],
|
|
181
|
+
columns: list[str],
|
|
182
|
+
headers: dict[str, str],
|
|
183
|
+
widths: dict[str, int],
|
|
184
|
+
max_width: int | None,
|
|
185
|
+
) -> str:
|
|
186
|
+
"""Format as ASCII table with borders."""
|
|
187
|
+
lines = []
|
|
188
|
+
|
|
189
|
+
# Top border
|
|
190
|
+
border = "+" + "+".join("-" * (widths[col] + 2) for col in columns) + "+"
|
|
191
|
+
lines.append(border)
|
|
192
|
+
|
|
193
|
+
# Header row
|
|
194
|
+
header_cells = [
|
|
195
|
+
" " + headers.get(col, col).ljust(widths[col]) + " " for col in columns
|
|
196
|
+
]
|
|
197
|
+
lines.append("|" + "|".join(header_cells) + "|")
|
|
198
|
+
lines.append(border)
|
|
199
|
+
|
|
200
|
+
# Data rows
|
|
201
|
+
for row in rows:
|
|
202
|
+
cells = []
|
|
203
|
+
for col in columns:
|
|
204
|
+
value = str(row.get(col, ""))
|
|
205
|
+
if max_width:
|
|
206
|
+
value = value[:max_width]
|
|
207
|
+
cells.append(" " + value.ljust(widths[col]) + " ")
|
|
208
|
+
lines.append("|" + "|".join(cells) + "|")
|
|
209
|
+
|
|
210
|
+
lines.append(border)
|
|
211
|
+
return "\n".join(lines)
|
|
212
|
+
|
|
213
|
+
def _format_grid_table(
|
|
214
|
+
self,
|
|
215
|
+
rows: Sequence[dict[str, Any]],
|
|
216
|
+
columns: list[str],
|
|
217
|
+
headers: dict[str, str],
|
|
218
|
+
widths: dict[str, int],
|
|
219
|
+
max_width: int | None,
|
|
220
|
+
) -> str:
|
|
221
|
+
"""Format as grid table with double borders."""
|
|
222
|
+
lines = []
|
|
223
|
+
|
|
224
|
+
# Top border
|
|
225
|
+
border = "╔" + "╤".join("═" * (widths[col] + 2) for col in columns) + "╗"
|
|
226
|
+
lines.append(border)
|
|
227
|
+
|
|
228
|
+
# Header row
|
|
229
|
+
header_cells = [
|
|
230
|
+
" " + headers.get(col, col).ljust(widths[col]) + " " for col in columns
|
|
231
|
+
]
|
|
232
|
+
lines.append("║" + "│".join(header_cells) + "║")
|
|
233
|
+
|
|
234
|
+
# Header separator
|
|
235
|
+
sep = "╠" + "╪".join("═" * (widths[col] + 2) for col in columns) + "╣"
|
|
236
|
+
lines.append(sep)
|
|
237
|
+
|
|
238
|
+
# Data rows
|
|
239
|
+
for i, row in enumerate(rows):
|
|
240
|
+
cells = []
|
|
241
|
+
for col in columns:
|
|
242
|
+
value = str(row.get(col, ""))
|
|
243
|
+
if max_width:
|
|
244
|
+
value = value[:max_width]
|
|
245
|
+
cells.append(" " + value.ljust(widths[col]) + " ")
|
|
246
|
+
lines.append("║" + "│".join(cells) + "║")
|
|
247
|
+
|
|
248
|
+
# Bottom border
|
|
249
|
+
border = "╚" + "╧".join("═" * (widths[col] + 2) for col in columns) + "╝"
|
|
250
|
+
lines.append(border)
|
|
251
|
+
return "\n".join(lines)
|
|
252
|
+
|
|
253
|
+
def _format_simple_table(
|
|
254
|
+
self,
|
|
255
|
+
rows: Sequence[dict[str, Any]],
|
|
256
|
+
columns: list[str],
|
|
257
|
+
headers: dict[str, str],
|
|
258
|
+
widths: dict[str, int],
|
|
259
|
+
max_width: int | None,
|
|
260
|
+
) -> str:
|
|
261
|
+
"""Format as simple table with minimal separators."""
|
|
262
|
+
lines = []
|
|
263
|
+
|
|
264
|
+
# Header row
|
|
265
|
+
header_cells = [headers.get(col, col).ljust(widths[col]) for col in columns]
|
|
266
|
+
lines.append(" ".join(header_cells))
|
|
267
|
+
|
|
268
|
+
# Separator
|
|
269
|
+
sep_cells = ["-" * widths[col] for col in columns]
|
|
270
|
+
lines.append(" ".join(sep_cells))
|
|
271
|
+
|
|
272
|
+
# Data rows
|
|
273
|
+
for row in rows:
|
|
274
|
+
cells = []
|
|
275
|
+
for col in columns:
|
|
276
|
+
value = str(row.get(col, ""))
|
|
277
|
+
if max_width:
|
|
278
|
+
value = value[:max_width]
|
|
279
|
+
cells.append(value.ljust(widths[col]))
|
|
280
|
+
lines.append(" ".join(cells))
|
|
281
|
+
|
|
282
|
+
return "\n".join(lines)
|
|
283
|
+
|
|
284
|
+
# -------------------------------------------------------------------------
|
|
285
|
+
# Text Formatting
|
|
286
|
+
# -------------------------------------------------------------------------
|
|
287
|
+
|
|
288
|
+
def truncate(self, text: str, max_length: int, suffix: str = "...") -> str:
|
|
289
|
+
"""Truncate text to maximum length.
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
text: Text to truncate.
|
|
293
|
+
max_length: Maximum length including suffix.
|
|
294
|
+
suffix: Suffix to add when truncating.
|
|
295
|
+
|
|
296
|
+
Returns:
|
|
297
|
+
Truncated text.
|
|
298
|
+
"""
|
|
299
|
+
if len(text) <= max_length:
|
|
300
|
+
return text
|
|
301
|
+
return text[: max_length - len(suffix)] + suffix
|
|
302
|
+
|
|
303
|
+
def indent(self, text: str, prefix: str = " ", first_line: bool = True) -> str:
|
|
304
|
+
"""Indent text with a prefix.
|
|
305
|
+
|
|
306
|
+
Args:
|
|
307
|
+
text: Text to indent.
|
|
308
|
+
prefix: Prefix to add to each line.
|
|
309
|
+
first_line: Whether to indent the first line.
|
|
310
|
+
|
|
311
|
+
Returns:
|
|
312
|
+
Indented text.
|
|
313
|
+
"""
|
|
314
|
+
lines = text.split("\n")
|
|
315
|
+
if first_line:
|
|
316
|
+
return "\n".join(prefix + line for line in lines)
|
|
317
|
+
return lines[0] + "\n" + "\n".join(prefix + line for line in lines[1:])
|
|
318
|
+
|
|
319
|
+
def wrap(self, text: str, width: int = 80) -> str:
|
|
320
|
+
"""Wrap text to specified width.
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
text: Text to wrap.
|
|
324
|
+
width: Maximum line width.
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
Wrapped text.
|
|
328
|
+
"""
|
|
329
|
+
import textwrap
|
|
330
|
+
|
|
331
|
+
return textwrap.fill(text, width=width)
|
|
332
|
+
|
|
333
|
+
# -------------------------------------------------------------------------
|
|
334
|
+
# Number Formatting
|
|
335
|
+
# -------------------------------------------------------------------------
|
|
336
|
+
|
|
337
|
+
def format_number(
|
|
338
|
+
self,
|
|
339
|
+
value: int | float,
|
|
340
|
+
precision: int = 2,
|
|
341
|
+
thousands_sep: str = ",",
|
|
342
|
+
) -> str:
|
|
343
|
+
"""Format a number with thousands separator.
|
|
344
|
+
|
|
345
|
+
Args:
|
|
346
|
+
value: Number to format.
|
|
347
|
+
precision: Decimal precision for floats.
|
|
348
|
+
thousands_sep: Thousands separator character.
|
|
349
|
+
|
|
350
|
+
Returns:
|
|
351
|
+
Formatted number string.
|
|
352
|
+
"""
|
|
353
|
+
if isinstance(value, float):
|
|
354
|
+
formatted = f"{value:,.{precision}f}"
|
|
355
|
+
else:
|
|
356
|
+
formatted = f"{value:,}"
|
|
357
|
+
return formatted.replace(",", thousands_sep)
|
|
358
|
+
|
|
359
|
+
def format_percentage(
|
|
360
|
+
self,
|
|
361
|
+
value: float,
|
|
362
|
+
precision: int = 1,
|
|
363
|
+
include_sign: bool = False,
|
|
364
|
+
) -> str:
|
|
365
|
+
"""Format a value as percentage.
|
|
366
|
+
|
|
367
|
+
Args:
|
|
368
|
+
value: Value to format (0.0 to 1.0 or 0 to 100).
|
|
369
|
+
precision: Decimal precision.
|
|
370
|
+
include_sign: Whether to include + sign for positive values.
|
|
371
|
+
|
|
372
|
+
Returns:
|
|
373
|
+
Formatted percentage string.
|
|
374
|
+
"""
|
|
375
|
+
# Assume value is already a percentage if > 1
|
|
376
|
+
if abs(value) <= 1:
|
|
377
|
+
value = value * 100
|
|
378
|
+
|
|
379
|
+
if include_sign and value > 0:
|
|
380
|
+
return f"+{value:.{precision}f}%"
|
|
381
|
+
return f"{value:.{precision}f}%"
|
|
382
|
+
|
|
383
|
+
def format_bytes(self, size: int) -> str:
|
|
384
|
+
"""Format byte size as human readable.
|
|
385
|
+
|
|
386
|
+
Args:
|
|
387
|
+
size: Size in bytes.
|
|
388
|
+
|
|
389
|
+
Returns:
|
|
390
|
+
Human readable size (e.g., "1.5 MB").
|
|
391
|
+
"""
|
|
392
|
+
for unit in ["B", "KB", "MB", "GB", "TB"]:
|
|
393
|
+
if abs(size) < 1024:
|
|
394
|
+
return f"{size:.1f} {unit}"
|
|
395
|
+
size /= 1024 # type: ignore
|
|
396
|
+
return f"{size:.1f} PB"
|
|
397
|
+
|
|
398
|
+
# -------------------------------------------------------------------------
|
|
399
|
+
# Date/Time Formatting
|
|
400
|
+
# -------------------------------------------------------------------------
|
|
401
|
+
|
|
402
|
+
def format_datetime(
|
|
403
|
+
self,
|
|
404
|
+
dt: datetime,
|
|
405
|
+
format: str = "%Y-%m-%d %H:%M:%S",
|
|
406
|
+
) -> str:
|
|
407
|
+
"""Format datetime object.
|
|
408
|
+
|
|
409
|
+
Args:
|
|
410
|
+
dt: Datetime to format.
|
|
411
|
+
format: strftime format string.
|
|
412
|
+
|
|
413
|
+
Returns:
|
|
414
|
+
Formatted datetime string.
|
|
415
|
+
"""
|
|
416
|
+
return dt.strftime(format)
|
|
417
|
+
|
|
418
|
+
def format_duration(self, seconds: float) -> str:
|
|
419
|
+
"""Format duration as human readable.
|
|
420
|
+
|
|
421
|
+
Args:
|
|
422
|
+
seconds: Duration in seconds.
|
|
423
|
+
|
|
424
|
+
Returns:
|
|
425
|
+
Human readable duration (e.g., "2h 30m 15s").
|
|
426
|
+
"""
|
|
427
|
+
if seconds < 1:
|
|
428
|
+
return f"{seconds * 1000:.0f}ms"
|
|
429
|
+
if seconds < 60:
|
|
430
|
+
return f"{seconds:.1f}s"
|
|
431
|
+
|
|
432
|
+
minutes, secs = divmod(int(seconds), 60)
|
|
433
|
+
hours, minutes = divmod(minutes, 60)
|
|
434
|
+
days, hours = divmod(hours, 24)
|
|
435
|
+
|
|
436
|
+
parts = []
|
|
437
|
+
if days:
|
|
438
|
+
parts.append(f"{days}d")
|
|
439
|
+
if hours:
|
|
440
|
+
parts.append(f"{hours}h")
|
|
441
|
+
if minutes:
|
|
442
|
+
parts.append(f"{minutes}m")
|
|
443
|
+
if secs or not parts:
|
|
444
|
+
parts.append(f"{secs}s")
|
|
445
|
+
|
|
446
|
+
return " ".join(parts)
|
|
447
|
+
|
|
448
|
+
def format_relative_time(self, dt: datetime) -> str:
|
|
449
|
+
"""Format datetime as relative time.
|
|
450
|
+
|
|
451
|
+
Args:
|
|
452
|
+
dt: Datetime to format.
|
|
453
|
+
|
|
454
|
+
Returns:
|
|
455
|
+
Relative time string (e.g., "5 minutes ago").
|
|
456
|
+
"""
|
|
457
|
+
now = datetime.now(dt.tzinfo)
|
|
458
|
+
delta = now - dt
|
|
459
|
+
|
|
460
|
+
seconds = delta.total_seconds()
|
|
461
|
+
|
|
462
|
+
if seconds < 60:
|
|
463
|
+
return "just now"
|
|
464
|
+
if seconds < 3600:
|
|
465
|
+
mins = int(seconds / 60)
|
|
466
|
+
return f"{mins} minute{'s' if mins != 1 else ''} ago"
|
|
467
|
+
if seconds < 86400:
|
|
468
|
+
hours = int(seconds / 3600)
|
|
469
|
+
return f"{hours} hour{'s' if hours != 1 else ''} ago"
|
|
470
|
+
if seconds < 604800:
|
|
471
|
+
days = int(seconds / 86400)
|
|
472
|
+
return f"{days} day{'s' if days != 1 else ''} ago"
|
|
473
|
+
|
|
474
|
+
return self.format_datetime(dt, "%Y-%m-%d")
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
# =============================================================================
|
|
478
|
+
# Aggregation Mixin
|
|
479
|
+
# =============================================================================
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
class AggregationMixin(ABC):
|
|
483
|
+
"""Mixin providing data aggregation utilities.
|
|
484
|
+
|
|
485
|
+
Provides methods for:
|
|
486
|
+
- Grouping by various keys
|
|
487
|
+
- Counting and statistics
|
|
488
|
+
- Summarization
|
|
489
|
+
"""
|
|
490
|
+
|
|
491
|
+
def group_by_column(
|
|
492
|
+
self,
|
|
493
|
+
results: list["ValidatorResult"],
|
|
494
|
+
) -> dict[str, list["ValidatorResult"]]:
|
|
495
|
+
"""Group validator results by column.
|
|
496
|
+
|
|
497
|
+
Args:
|
|
498
|
+
results: List of validator results.
|
|
499
|
+
|
|
500
|
+
Returns:
|
|
501
|
+
Dictionary mapping column name to results.
|
|
502
|
+
"""
|
|
503
|
+
grouped: dict[str, list[ValidatorResult]] = defaultdict(list)
|
|
504
|
+
for result in results:
|
|
505
|
+
column = result.column or "_table_"
|
|
506
|
+
grouped[column].append(result)
|
|
507
|
+
return dict(grouped)
|
|
508
|
+
|
|
509
|
+
def group_by_severity(
|
|
510
|
+
self,
|
|
511
|
+
results: list["ValidatorResult"],
|
|
512
|
+
) -> dict[str, list["ValidatorResult"]]:
|
|
513
|
+
"""Group validator results by severity.
|
|
514
|
+
|
|
515
|
+
Args:
|
|
516
|
+
results: List of validator results.
|
|
517
|
+
|
|
518
|
+
Returns:
|
|
519
|
+
Dictionary mapping severity to results.
|
|
520
|
+
"""
|
|
521
|
+
grouped: dict[str, list[ValidatorResult]] = defaultdict(list)
|
|
522
|
+
for result in results:
|
|
523
|
+
severity = result.severity or "unknown"
|
|
524
|
+
grouped[severity.lower()].append(result)
|
|
525
|
+
return dict(grouped)
|
|
526
|
+
|
|
527
|
+
def group_by_validator(
|
|
528
|
+
self,
|
|
529
|
+
results: list["ValidatorResult"],
|
|
530
|
+
) -> dict[str, list["ValidatorResult"]]:
|
|
531
|
+
"""Group validator results by validator name.
|
|
532
|
+
|
|
533
|
+
Args:
|
|
534
|
+
results: List of validator results.
|
|
535
|
+
|
|
536
|
+
Returns:
|
|
537
|
+
Dictionary mapping validator name to results.
|
|
538
|
+
"""
|
|
539
|
+
grouped: dict[str, list[ValidatorResult]] = defaultdict(list)
|
|
540
|
+
for result in results:
|
|
541
|
+
grouped[result.validator_name].append(result)
|
|
542
|
+
return dict(grouped)
|
|
543
|
+
|
|
544
|
+
def group_by(
|
|
545
|
+
self,
|
|
546
|
+
results: list["ValidatorResult"],
|
|
547
|
+
key: Callable[["ValidatorResult"], str],
|
|
548
|
+
) -> dict[str, list["ValidatorResult"]]:
|
|
549
|
+
"""Group validator results by custom key function.
|
|
550
|
+
|
|
551
|
+
Args:
|
|
552
|
+
results: List of validator results.
|
|
553
|
+
key: Function to extract grouping key from result.
|
|
554
|
+
|
|
555
|
+
Returns:
|
|
556
|
+
Dictionary mapping key to results.
|
|
557
|
+
"""
|
|
558
|
+
grouped: dict[str, list[ValidatorResult]] = defaultdict(list)
|
|
559
|
+
for result in results:
|
|
560
|
+
grouped[key(result)].append(result)
|
|
561
|
+
return dict(grouped)
|
|
562
|
+
|
|
563
|
+
def count_by_severity(
|
|
564
|
+
self,
|
|
565
|
+
results: list["ValidatorResult"],
|
|
566
|
+
) -> dict[str, int]:
|
|
567
|
+
"""Count results by severity.
|
|
568
|
+
|
|
569
|
+
Args:
|
|
570
|
+
results: List of validator results.
|
|
571
|
+
|
|
572
|
+
Returns:
|
|
573
|
+
Dictionary mapping severity to count.
|
|
574
|
+
"""
|
|
575
|
+
counts: dict[str, int] = defaultdict(int)
|
|
576
|
+
for result in results:
|
|
577
|
+
severity = result.severity or "unknown"
|
|
578
|
+
counts[severity.lower()] += 1
|
|
579
|
+
return dict(counts)
|
|
580
|
+
|
|
581
|
+
def count_by_column(
|
|
582
|
+
self,
|
|
583
|
+
results: list["ValidatorResult"],
|
|
584
|
+
) -> dict[str, int]:
|
|
585
|
+
"""Count results by column.
|
|
586
|
+
|
|
587
|
+
Args:
|
|
588
|
+
results: List of validator results.
|
|
589
|
+
|
|
590
|
+
Returns:
|
|
591
|
+
Dictionary mapping column to count.
|
|
592
|
+
"""
|
|
593
|
+
counts: dict[str, int] = defaultdict(int)
|
|
594
|
+
for result in results:
|
|
595
|
+
column = result.column or "_table_"
|
|
596
|
+
counts[column] += 1
|
|
597
|
+
return dict(counts)
|
|
598
|
+
|
|
599
|
+
def get_summary_stats(
|
|
600
|
+
self,
|
|
601
|
+
result: "ValidationResult",
|
|
602
|
+
) -> dict[str, Any]:
|
|
603
|
+
"""Get summary statistics from validation result.
|
|
604
|
+
|
|
605
|
+
Args:
|
|
606
|
+
result: Validation result.
|
|
607
|
+
|
|
608
|
+
Returns:
|
|
609
|
+
Dictionary with summary statistics.
|
|
610
|
+
"""
|
|
611
|
+
failed = [r for r in result.results if not r.success]
|
|
612
|
+
|
|
613
|
+
return {
|
|
614
|
+
"total_validators": len(result.results),
|
|
615
|
+
"passed": len(result.results) - len(failed),
|
|
616
|
+
"failed": len(failed),
|
|
617
|
+
"pass_rate": (
|
|
618
|
+
(len(result.results) - len(failed)) / len(result.results) * 100
|
|
619
|
+
if result.results
|
|
620
|
+
else 0
|
|
621
|
+
),
|
|
622
|
+
"by_severity": self.count_by_severity(failed),
|
|
623
|
+
"by_column": self.count_by_column(failed),
|
|
624
|
+
"most_affected_column": (
|
|
625
|
+
max(self.count_by_column(failed).items(), key=lambda x: x[1])[0]
|
|
626
|
+
if failed
|
|
627
|
+
else None
|
|
628
|
+
),
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
# =============================================================================
|
|
633
|
+
# Filtering Mixin
|
|
634
|
+
# =============================================================================
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
class FilteringMixin(ABC):
|
|
638
|
+
"""Mixin providing data filtering utilities.
|
|
639
|
+
|
|
640
|
+
Provides methods for:
|
|
641
|
+
- Filtering by severity, column, validator
|
|
642
|
+
- Sorting results
|
|
643
|
+
- Limiting/pagination
|
|
644
|
+
"""
|
|
645
|
+
|
|
646
|
+
def filter_by_severity(
|
|
647
|
+
self,
|
|
648
|
+
results: list["ValidatorResult"],
|
|
649
|
+
min_severity: str | None = None,
|
|
650
|
+
max_severity: str | None = None,
|
|
651
|
+
include_severities: list[str] | None = None,
|
|
652
|
+
exclude_severities: list[str] | None = None,
|
|
653
|
+
) -> list["ValidatorResult"]:
|
|
654
|
+
"""Filter results by severity.
|
|
655
|
+
|
|
656
|
+
Args:
|
|
657
|
+
results: List of validator results.
|
|
658
|
+
min_severity: Minimum severity to include.
|
|
659
|
+
max_severity: Maximum severity to include.
|
|
660
|
+
include_severities: List of severities to include (exclusive with min/max).
|
|
661
|
+
exclude_severities: List of severities to exclude.
|
|
662
|
+
|
|
663
|
+
Returns:
|
|
664
|
+
Filtered list of results.
|
|
665
|
+
"""
|
|
666
|
+
filtered = []
|
|
667
|
+
|
|
668
|
+
for result in results:
|
|
669
|
+
severity = (result.severity or "none").lower()
|
|
670
|
+
level = _get_severity_level(severity)
|
|
671
|
+
|
|
672
|
+
# Check include list
|
|
673
|
+
if include_severities:
|
|
674
|
+
if severity not in [s.lower() for s in include_severities]:
|
|
675
|
+
continue
|
|
676
|
+
else:
|
|
677
|
+
# Check min severity
|
|
678
|
+
if min_severity:
|
|
679
|
+
min_level = _get_severity_level(min_severity)
|
|
680
|
+
if level > min_level:
|
|
681
|
+
continue
|
|
682
|
+
|
|
683
|
+
# Check max severity
|
|
684
|
+
if max_severity:
|
|
685
|
+
max_level = _get_severity_level(max_severity)
|
|
686
|
+
if level < max_level:
|
|
687
|
+
continue
|
|
688
|
+
|
|
689
|
+
# Check exclude list
|
|
690
|
+
if exclude_severities:
|
|
691
|
+
if severity in [s.lower() for s in exclude_severities]:
|
|
692
|
+
continue
|
|
693
|
+
|
|
694
|
+
filtered.append(result)
|
|
695
|
+
|
|
696
|
+
return filtered
|
|
697
|
+
|
|
698
|
+
def filter_by_column(
|
|
699
|
+
self,
|
|
700
|
+
results: list["ValidatorResult"],
|
|
701
|
+
include_columns: list[str] | None = None,
|
|
702
|
+
exclude_columns: list[str] | None = None,
|
|
703
|
+
) -> list["ValidatorResult"]:
|
|
704
|
+
"""Filter results by column.
|
|
705
|
+
|
|
706
|
+
Args:
|
|
707
|
+
results: List of validator results.
|
|
708
|
+
include_columns: Columns to include.
|
|
709
|
+
exclude_columns: Columns to exclude.
|
|
710
|
+
|
|
711
|
+
Returns:
|
|
712
|
+
Filtered list of results.
|
|
713
|
+
"""
|
|
714
|
+
filtered = []
|
|
715
|
+
|
|
716
|
+
for result in results:
|
|
717
|
+
column = result.column or "_table_"
|
|
718
|
+
|
|
719
|
+
if include_columns and column not in include_columns:
|
|
720
|
+
continue
|
|
721
|
+
if exclude_columns and column in exclude_columns:
|
|
722
|
+
continue
|
|
723
|
+
|
|
724
|
+
filtered.append(result)
|
|
725
|
+
|
|
726
|
+
return filtered
|
|
727
|
+
|
|
728
|
+
def filter_by_validator(
|
|
729
|
+
self,
|
|
730
|
+
results: list["ValidatorResult"],
|
|
731
|
+
include_validators: list[str] | None = None,
|
|
732
|
+
exclude_validators: list[str] | None = None,
|
|
733
|
+
) -> list["ValidatorResult"]:
|
|
734
|
+
"""Filter results by validator name.
|
|
735
|
+
|
|
736
|
+
Args:
|
|
737
|
+
results: List of validator results.
|
|
738
|
+
include_validators: Validator names to include.
|
|
739
|
+
exclude_validators: Validator names to exclude.
|
|
740
|
+
|
|
741
|
+
Returns:
|
|
742
|
+
Filtered list of results.
|
|
743
|
+
"""
|
|
744
|
+
filtered = []
|
|
745
|
+
|
|
746
|
+
for result in results:
|
|
747
|
+
if include_validators and result.validator_name not in include_validators:
|
|
748
|
+
continue
|
|
749
|
+
if exclude_validators and result.validator_name in exclude_validators:
|
|
750
|
+
continue
|
|
751
|
+
|
|
752
|
+
filtered.append(result)
|
|
753
|
+
|
|
754
|
+
return filtered
|
|
755
|
+
|
|
756
|
+
def filter_failed(
|
|
757
|
+
self,
|
|
758
|
+
results: list["ValidatorResult"],
|
|
759
|
+
) -> list["ValidatorResult"]:
|
|
760
|
+
"""Filter to only failed results.
|
|
761
|
+
|
|
762
|
+
Args:
|
|
763
|
+
results: List of validator results.
|
|
764
|
+
|
|
765
|
+
Returns:
|
|
766
|
+
List of failed results.
|
|
767
|
+
"""
|
|
768
|
+
return [r for r in results if not r.success]
|
|
769
|
+
|
|
770
|
+
def filter_passed(
|
|
771
|
+
self,
|
|
772
|
+
results: list["ValidatorResult"],
|
|
773
|
+
) -> list["ValidatorResult"]:
|
|
774
|
+
"""Filter to only passed results.
|
|
775
|
+
|
|
776
|
+
Args:
|
|
777
|
+
results: List of validator results.
|
|
778
|
+
|
|
779
|
+
Returns:
|
|
780
|
+
List of passed results.
|
|
781
|
+
"""
|
|
782
|
+
return [r for r in results if r.success]
|
|
783
|
+
|
|
784
|
+
def sort_by_severity(
|
|
785
|
+
self,
|
|
786
|
+
results: list["ValidatorResult"],
|
|
787
|
+
ascending: bool = False,
|
|
788
|
+
) -> list["ValidatorResult"]:
|
|
789
|
+
"""Sort results by severity.
|
|
790
|
+
|
|
791
|
+
Args:
|
|
792
|
+
results: List of validator results.
|
|
793
|
+
ascending: If True, sort from low to critical.
|
|
794
|
+
|
|
795
|
+
Returns:
|
|
796
|
+
Sorted list of results.
|
|
797
|
+
"""
|
|
798
|
+
return sorted(
|
|
799
|
+
results,
|
|
800
|
+
key=lambda r: _get_severity_level(r.severity),
|
|
801
|
+
reverse=not ascending,
|
|
802
|
+
)
|
|
803
|
+
|
|
804
|
+
def sort_by_column(
|
|
805
|
+
self,
|
|
806
|
+
results: list["ValidatorResult"],
|
|
807
|
+
ascending: bool = True,
|
|
808
|
+
) -> list["ValidatorResult"]:
|
|
809
|
+
"""Sort results by column name.
|
|
810
|
+
|
|
811
|
+
Args:
|
|
812
|
+
results: List of validator results.
|
|
813
|
+
ascending: Sort order.
|
|
814
|
+
|
|
815
|
+
Returns:
|
|
816
|
+
Sorted list of results.
|
|
817
|
+
"""
|
|
818
|
+
return sorted(
|
|
819
|
+
results,
|
|
820
|
+
key=lambda r: r.column or "",
|
|
821
|
+
reverse=not ascending,
|
|
822
|
+
)
|
|
823
|
+
|
|
824
|
+
def limit(
|
|
825
|
+
self,
|
|
826
|
+
results: list["ValidatorResult"],
|
|
827
|
+
count: int,
|
|
828
|
+
offset: int = 0,
|
|
829
|
+
) -> list["ValidatorResult"]:
|
|
830
|
+
"""Limit results with optional offset.
|
|
831
|
+
|
|
832
|
+
Args:
|
|
833
|
+
results: List of validator results.
|
|
834
|
+
count: Maximum number of results.
|
|
835
|
+
offset: Number of results to skip.
|
|
836
|
+
|
|
837
|
+
Returns:
|
|
838
|
+
Limited list of results.
|
|
839
|
+
"""
|
|
840
|
+
return results[offset : offset + count]
|
|
841
|
+
|
|
842
|
+
|
|
843
|
+
# =============================================================================
|
|
844
|
+
# Serialization Mixin
|
|
845
|
+
# =============================================================================
|
|
846
|
+
|
|
847
|
+
|
|
848
|
+
class SerializationMixin(ABC):
|
|
849
|
+
"""Mixin providing serialization utilities.
|
|
850
|
+
|
|
851
|
+
Provides methods for:
|
|
852
|
+
- JSON serialization with custom encoders
|
|
853
|
+
- XML element building
|
|
854
|
+
- YAML formatting
|
|
855
|
+
- CSV generation
|
|
856
|
+
"""
|
|
857
|
+
|
|
858
|
+
def to_json(
|
|
859
|
+
self,
|
|
860
|
+
data: Any,
|
|
861
|
+
indent: int | None = 2,
|
|
862
|
+
sort_keys: bool = False,
|
|
863
|
+
ensure_ascii: bool = False,
|
|
864
|
+
) -> str:
|
|
865
|
+
"""Serialize data to JSON.
|
|
866
|
+
|
|
867
|
+
Args:
|
|
868
|
+
data: Data to serialize.
|
|
869
|
+
indent: Indentation level (None for compact).
|
|
870
|
+
sort_keys: Whether to sort dictionary keys.
|
|
871
|
+
ensure_ascii: Whether to escape non-ASCII characters.
|
|
872
|
+
|
|
873
|
+
Returns:
|
|
874
|
+
JSON string.
|
|
875
|
+
"""
|
|
876
|
+
return json.dumps(
|
|
877
|
+
data,
|
|
878
|
+
indent=indent,
|
|
879
|
+
sort_keys=sort_keys,
|
|
880
|
+
ensure_ascii=ensure_ascii,
|
|
881
|
+
default=self._json_encoder,
|
|
882
|
+
)
|
|
883
|
+
|
|
884
|
+
def _json_encoder(self, obj: Any) -> Any:
|
|
885
|
+
"""Custom JSON encoder for special types."""
|
|
886
|
+
if isinstance(obj, datetime):
|
|
887
|
+
return obj.isoformat()
|
|
888
|
+
if isinstance(obj, date):
|
|
889
|
+
return obj.isoformat()
|
|
890
|
+
if isinstance(obj, Enum):
|
|
891
|
+
return obj.value
|
|
892
|
+
if is_dataclass(obj) and not isinstance(obj, type):
|
|
893
|
+
return asdict(obj)
|
|
894
|
+
if hasattr(obj, "to_dict"):
|
|
895
|
+
return obj.to_dict()
|
|
896
|
+
raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")
|
|
897
|
+
|
|
898
|
+
def to_csv(
|
|
899
|
+
self,
|
|
900
|
+
rows: Sequence[dict[str, Any]],
|
|
901
|
+
columns: list[str] | None = None,
|
|
902
|
+
delimiter: str = ",",
|
|
903
|
+
include_header: bool = True,
|
|
904
|
+
) -> str:
|
|
905
|
+
"""Serialize data to CSV.
|
|
906
|
+
|
|
907
|
+
Args:
|
|
908
|
+
rows: Sequence of dictionaries.
|
|
909
|
+
columns: Column order (default: keys from first row).
|
|
910
|
+
delimiter: Field delimiter.
|
|
911
|
+
include_header: Whether to include header row.
|
|
912
|
+
|
|
913
|
+
Returns:
|
|
914
|
+
CSV string.
|
|
915
|
+
"""
|
|
916
|
+
if not rows:
|
|
917
|
+
return ""
|
|
918
|
+
|
|
919
|
+
if columns is None:
|
|
920
|
+
columns = list(rows[0].keys())
|
|
921
|
+
|
|
922
|
+
output = io.StringIO()
|
|
923
|
+
writer = csv.DictWriter(
|
|
924
|
+
output,
|
|
925
|
+
fieldnames=columns,
|
|
926
|
+
delimiter=delimiter,
|
|
927
|
+
extrasaction="ignore",
|
|
928
|
+
)
|
|
929
|
+
|
|
930
|
+
if include_header:
|
|
931
|
+
writer.writeheader()
|
|
932
|
+
|
|
933
|
+
for row in rows:
|
|
934
|
+
# Convert non-string values
|
|
935
|
+
converted = {}
|
|
936
|
+
for col in columns:
|
|
937
|
+
value = row.get(col, "")
|
|
938
|
+
if isinstance(value, (list, dict)):
|
|
939
|
+
value = json.dumps(value)
|
|
940
|
+
elif isinstance(value, datetime):
|
|
941
|
+
value = value.isoformat()
|
|
942
|
+
elif isinstance(value, Enum):
|
|
943
|
+
value = value.value
|
|
944
|
+
converted[col] = value
|
|
945
|
+
writer.writerow(converted)
|
|
946
|
+
|
|
947
|
+
return output.getvalue()
|
|
948
|
+
|
|
949
|
+
def to_xml_element(
|
|
950
|
+
self,
|
|
951
|
+
tag: str,
|
|
952
|
+
value: Any = None,
|
|
953
|
+
attributes: dict[str, str] | None = None,
|
|
954
|
+
children: list[str] | None = None,
|
|
955
|
+
) -> str:
|
|
956
|
+
"""Create an XML element string.
|
|
957
|
+
|
|
958
|
+
Args:
|
|
959
|
+
tag: Element tag name.
|
|
960
|
+
value: Text content.
|
|
961
|
+
attributes: Element attributes.
|
|
962
|
+
children: Child element strings.
|
|
963
|
+
|
|
964
|
+
Returns:
|
|
965
|
+
XML element string.
|
|
966
|
+
"""
|
|
967
|
+
attrs = ""
|
|
968
|
+
if attributes:
|
|
969
|
+
attrs = " " + " ".join(f'{k}="{self._escape_xml(str(v))}"' for k, v in attributes.items())
|
|
970
|
+
|
|
971
|
+
if children:
|
|
972
|
+
content = "\n".join(children)
|
|
973
|
+
return f"<{tag}{attrs}>\n{content}\n</{tag}>"
|
|
974
|
+
elif value is not None:
|
|
975
|
+
return f"<{tag}{attrs}>{self._escape_xml(str(value))}</{tag}>"
|
|
976
|
+
else:
|
|
977
|
+
return f"<{tag}{attrs}/>"
|
|
978
|
+
|
|
979
|
+
def _escape_xml(self, text: str) -> str:
|
|
980
|
+
"""Escape special XML characters."""
|
|
981
|
+
return (
|
|
982
|
+
text.replace("&", "&")
|
|
983
|
+
.replace("<", "<")
|
|
984
|
+
.replace(">", ">")
|
|
985
|
+
.replace('"', """)
|
|
986
|
+
.replace("'", "'")
|
|
987
|
+
)
|
|
988
|
+
|
|
989
|
+
|
|
990
|
+
# =============================================================================
|
|
991
|
+
# Templating Mixin
|
|
992
|
+
# =============================================================================
|
|
993
|
+
|
|
994
|
+
|
|
995
|
+
class TemplatingMixin(ABC):
|
|
996
|
+
"""Mixin providing template rendering utilities.
|
|
997
|
+
|
|
998
|
+
Provides methods for:
|
|
999
|
+
- Jinja2 template rendering
|
|
1000
|
+
- Template string interpolation
|
|
1001
|
+
- Conditional content
|
|
1002
|
+
"""
|
|
1003
|
+
|
|
1004
|
+
_jinja_env: Any = None
|
|
1005
|
+
|
|
1006
|
+
def render_template(
|
|
1007
|
+
self,
|
|
1008
|
+
template: str,
|
|
1009
|
+
context: dict[str, Any],
|
|
1010
|
+
) -> str:
|
|
1011
|
+
"""Render a Jinja2 template string.
|
|
1012
|
+
|
|
1013
|
+
Args:
|
|
1014
|
+
template: Jinja2 template string.
|
|
1015
|
+
context: Template context variables.
|
|
1016
|
+
|
|
1017
|
+
Returns:
|
|
1018
|
+
Rendered template.
|
|
1019
|
+
|
|
1020
|
+
Raises:
|
|
1021
|
+
ImportError: If jinja2 is not installed.
|
|
1022
|
+
"""
|
|
1023
|
+
try:
|
|
1024
|
+
from jinja2 import Environment, BaseLoader
|
|
1025
|
+
|
|
1026
|
+
if self._jinja_env is None:
|
|
1027
|
+
self._jinja_env = Environment(loader=BaseLoader())
|
|
1028
|
+
|
|
1029
|
+
tmpl = self._jinja_env.from_string(template)
|
|
1030
|
+
return tmpl.render(**context)
|
|
1031
|
+
|
|
1032
|
+
except ImportError:
|
|
1033
|
+
raise ImportError(
|
|
1034
|
+
"jinja2 is required for template rendering. "
|
|
1035
|
+
"Install with: pip install jinja2"
|
|
1036
|
+
)
|
|
1037
|
+
|
|
1038
|
+
def render_template_file(
|
|
1039
|
+
self,
|
|
1040
|
+
template_path: str,
|
|
1041
|
+
context: dict[str, Any],
|
|
1042
|
+
) -> str:
|
|
1043
|
+
"""Render a Jinja2 template file.
|
|
1044
|
+
|
|
1045
|
+
Args:
|
|
1046
|
+
template_path: Path to template file.
|
|
1047
|
+
context: Template context variables.
|
|
1048
|
+
|
|
1049
|
+
Returns:
|
|
1050
|
+
Rendered template.
|
|
1051
|
+
"""
|
|
1052
|
+
try:
|
|
1053
|
+
from jinja2 import Environment, FileSystemLoader
|
|
1054
|
+
import os
|
|
1055
|
+
|
|
1056
|
+
template_dir = os.path.dirname(template_path)
|
|
1057
|
+
template_name = os.path.basename(template_path)
|
|
1058
|
+
|
|
1059
|
+
env = Environment(loader=FileSystemLoader(template_dir))
|
|
1060
|
+
tmpl = env.get_template(template_name)
|
|
1061
|
+
return tmpl.render(**context)
|
|
1062
|
+
|
|
1063
|
+
except ImportError:
|
|
1064
|
+
raise ImportError(
|
|
1065
|
+
"jinja2 is required for template rendering. "
|
|
1066
|
+
"Install with: pip install jinja2"
|
|
1067
|
+
)
|
|
1068
|
+
|
|
1069
|
+
def interpolate(
|
|
1070
|
+
self,
|
|
1071
|
+
template: str,
|
|
1072
|
+
context: dict[str, Any],
|
|
1073
|
+
) -> str:
|
|
1074
|
+
"""Simple string interpolation without Jinja2.
|
|
1075
|
+
|
|
1076
|
+
Args:
|
|
1077
|
+
template: Template string with {key} placeholders.
|
|
1078
|
+
context: Values to interpolate.
|
|
1079
|
+
|
|
1080
|
+
Returns:
|
|
1081
|
+
Interpolated string.
|
|
1082
|
+
"""
|
|
1083
|
+
return template.format(**context)
|
|
1084
|
+
|
|
1085
|
+
|
|
1086
|
+
# =============================================================================
|
|
1087
|
+
# Streaming Mixin
|
|
1088
|
+
# =============================================================================
|
|
1089
|
+
|
|
1090
|
+
|
|
1091
|
+
class StreamingMixin(ABC):
|
|
1092
|
+
"""Mixin providing streaming output utilities.
|
|
1093
|
+
|
|
1094
|
+
Provides methods for:
|
|
1095
|
+
- Streaming output for large datasets
|
|
1096
|
+
- Progress tracking
|
|
1097
|
+
- Chunked processing
|
|
1098
|
+
"""
|
|
1099
|
+
|
|
1100
|
+
def stream_results(
|
|
1101
|
+
self,
|
|
1102
|
+
results: list["ValidatorResult"],
|
|
1103
|
+
chunk_size: int = 100,
|
|
1104
|
+
) -> Iterator[list["ValidatorResult"]]:
|
|
1105
|
+
"""Stream results in chunks.
|
|
1106
|
+
|
|
1107
|
+
Args:
|
|
1108
|
+
results: List of validator results.
|
|
1109
|
+
chunk_size: Number of results per chunk.
|
|
1110
|
+
|
|
1111
|
+
Yields:
|
|
1112
|
+
Chunks of validator results.
|
|
1113
|
+
"""
|
|
1114
|
+
for i in range(0, len(results), chunk_size):
|
|
1115
|
+
yield results[i : i + chunk_size]
|
|
1116
|
+
|
|
1117
|
+
def stream_lines(
|
|
1118
|
+
self,
|
|
1119
|
+
results: list["ValidatorResult"],
|
|
1120
|
+
formatter: Callable[["ValidatorResult"], str],
|
|
1121
|
+
) -> Iterator[str]:
|
|
1122
|
+
"""Stream formatted lines for each result.
|
|
1123
|
+
|
|
1124
|
+
Args:
|
|
1125
|
+
results: List of validator results.
|
|
1126
|
+
formatter: Function to format each result.
|
|
1127
|
+
|
|
1128
|
+
Yields:
|
|
1129
|
+
Formatted lines.
|
|
1130
|
+
"""
|
|
1131
|
+
for result in results:
|
|
1132
|
+
yield formatter(result)
|
|
1133
|
+
|
|
1134
|
+
def render_streaming(
|
|
1135
|
+
self,
|
|
1136
|
+
results: list["ValidatorResult"],
|
|
1137
|
+
formatter: Callable[["ValidatorResult"], str],
|
|
1138
|
+
separator: str = "\n",
|
|
1139
|
+
) -> str:
|
|
1140
|
+
"""Render all results using streaming formatter.
|
|
1141
|
+
|
|
1142
|
+
Args:
|
|
1143
|
+
results: List of validator results.
|
|
1144
|
+
formatter: Function to format each result.
|
|
1145
|
+
separator: Separator between formatted results.
|
|
1146
|
+
|
|
1147
|
+
Returns:
|
|
1148
|
+
Complete rendered output.
|
|
1149
|
+
"""
|
|
1150
|
+
return separator.join(self.stream_lines(results, formatter))
|