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,1127 @@
|
|
|
1
|
+
"""Validator scaffold generator.
|
|
2
|
+
|
|
3
|
+
This module provides scaffolding for creating custom validators with
|
|
4
|
+
various template variants:
|
|
5
|
+
- basic: Minimal validator with core structure
|
|
6
|
+
- column: Column-level validator with target column support
|
|
7
|
+
- pattern: Pattern matching validator with regex
|
|
8
|
+
- range: Numeric range validator
|
|
9
|
+
- composite: Multi-validator composite
|
|
10
|
+
- full: Full-featured with tests and documentation
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from typing import Any, ClassVar
|
|
16
|
+
|
|
17
|
+
from truthound.cli_modules.scaffolding.base import (
|
|
18
|
+
BaseScaffold,
|
|
19
|
+
ScaffoldConfig,
|
|
20
|
+
ScaffoldResult,
|
|
21
|
+
register_scaffold,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@register_scaffold(
|
|
26
|
+
name="validator",
|
|
27
|
+
description="Generate a custom validator",
|
|
28
|
+
aliases=("val", "v"),
|
|
29
|
+
)
|
|
30
|
+
class ValidatorScaffold(BaseScaffold):
|
|
31
|
+
"""Scaffold generator for validators.
|
|
32
|
+
|
|
33
|
+
Supports multiple template variants for different validation patterns.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
name: ClassVar[str] = "validator"
|
|
37
|
+
description: ClassVar[str] = "Generate a custom validator"
|
|
38
|
+
aliases: ClassVar[tuple[str, ...]] = ("val", "v")
|
|
39
|
+
|
|
40
|
+
TEMPLATE_VARIANTS: ClassVar[tuple[str, ...]] = (
|
|
41
|
+
"basic",
|
|
42
|
+
"column",
|
|
43
|
+
"pattern",
|
|
44
|
+
"range",
|
|
45
|
+
"comparison",
|
|
46
|
+
"composite",
|
|
47
|
+
"full",
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
def get_options(self) -> dict[str, Any]:
|
|
51
|
+
"""Get validator-specific options."""
|
|
52
|
+
return {
|
|
53
|
+
"category": {
|
|
54
|
+
"type": "str",
|
|
55
|
+
"default": "custom",
|
|
56
|
+
"description": "Validator category (e.g., numeric, string, business)",
|
|
57
|
+
},
|
|
58
|
+
"severity": {
|
|
59
|
+
"type": "str",
|
|
60
|
+
"default": "MEDIUM",
|
|
61
|
+
"description": "Default severity level",
|
|
62
|
+
"choices": ["LOW", "MEDIUM", "HIGH", "CRITICAL"],
|
|
63
|
+
},
|
|
64
|
+
"columns": {
|
|
65
|
+
"type": "list[str]",
|
|
66
|
+
"default": None,
|
|
67
|
+
"description": "Target columns for column validators",
|
|
68
|
+
},
|
|
69
|
+
"pattern": {
|
|
70
|
+
"type": "str",
|
|
71
|
+
"default": None,
|
|
72
|
+
"description": "Regex pattern for pattern validators",
|
|
73
|
+
},
|
|
74
|
+
"min_value": {
|
|
75
|
+
"type": "float",
|
|
76
|
+
"default": None,
|
|
77
|
+
"description": "Minimum value for range validators",
|
|
78
|
+
},
|
|
79
|
+
"max_value": {
|
|
80
|
+
"type": "float",
|
|
81
|
+
"default": None,
|
|
82
|
+
"description": "Maximum value for range validators",
|
|
83
|
+
},
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
def _generate_files(self, config: ScaffoldConfig, result: ScaffoldResult) -> None:
|
|
87
|
+
"""Generate validator files based on variant."""
|
|
88
|
+
variant = config.template_variant
|
|
89
|
+
|
|
90
|
+
# Generate main validator file
|
|
91
|
+
if variant == "basic":
|
|
92
|
+
self._generate_basic(config, result)
|
|
93
|
+
elif variant == "column":
|
|
94
|
+
self._generate_column(config, result)
|
|
95
|
+
elif variant == "pattern":
|
|
96
|
+
self._generate_pattern(config, result)
|
|
97
|
+
elif variant == "range":
|
|
98
|
+
self._generate_range(config, result)
|
|
99
|
+
elif variant == "comparison":
|
|
100
|
+
self._generate_comparison(config, result)
|
|
101
|
+
elif variant == "composite":
|
|
102
|
+
self._generate_composite(config, result)
|
|
103
|
+
elif variant == "full":
|
|
104
|
+
self._generate_full(config, result)
|
|
105
|
+
else:
|
|
106
|
+
self._generate_basic(config, result)
|
|
107
|
+
|
|
108
|
+
# Generate __init__.py
|
|
109
|
+
self._generate_init(config, result)
|
|
110
|
+
|
|
111
|
+
# Generate tests if requested
|
|
112
|
+
if config.include_tests:
|
|
113
|
+
self._generate_tests(config, result)
|
|
114
|
+
|
|
115
|
+
# Generate docs if requested
|
|
116
|
+
if config.include_docs:
|
|
117
|
+
self._generate_docs(config, result)
|
|
118
|
+
|
|
119
|
+
def _generate_init(self, config: ScaffoldConfig, result: ScaffoldResult) -> None:
|
|
120
|
+
"""Generate __init__.py file."""
|
|
121
|
+
content = f'''"""Package for {config.title_name} validator."""
|
|
122
|
+
|
|
123
|
+
from {config.name}.validator import {config.class_name}Validator
|
|
124
|
+
|
|
125
|
+
__all__ = ["{config.class_name}Validator"]
|
|
126
|
+
'''
|
|
127
|
+
result.add_file(f"{config.name}/__init__.py", content)
|
|
128
|
+
|
|
129
|
+
def _generate_basic(self, config: ScaffoldConfig, result: ScaffoldResult) -> None:
|
|
130
|
+
"""Generate basic validator template."""
|
|
131
|
+
severity = config.extra.get("severity", "MEDIUM")
|
|
132
|
+
category = config.extra.get("category", config.category)
|
|
133
|
+
|
|
134
|
+
content = f'''{self._get_header(config)}
|
|
135
|
+
|
|
136
|
+
from __future__ import annotations
|
|
137
|
+
|
|
138
|
+
from typing import Any
|
|
139
|
+
|
|
140
|
+
import polars as pl
|
|
141
|
+
|
|
142
|
+
from truthound.validators.base import Validator, ValidationIssue, ValidatorConfig
|
|
143
|
+
from truthound.validators.sdk import custom_validator
|
|
144
|
+
from truthound.types import Severity
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@custom_validator(
|
|
148
|
+
name="{config.name}",
|
|
149
|
+
category="{category}",
|
|
150
|
+
description="{config.description or f'{config.title_name} validator'}",
|
|
151
|
+
version="{config.version}",
|
|
152
|
+
author="{config.author}",
|
|
153
|
+
tags=["{category}"],
|
|
154
|
+
)
|
|
155
|
+
class {config.class_name}Validator(Validator):
|
|
156
|
+
"""{config.description or f'{config.title_name} validator.'}
|
|
157
|
+
|
|
158
|
+
This validator checks data quality based on custom business rules.
|
|
159
|
+
|
|
160
|
+
Example:
|
|
161
|
+
>>> validator = {config.class_name}Validator()
|
|
162
|
+
>>> issues = validator.validate(lf)
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
name: str = "{config.name}"
|
|
166
|
+
category: str = "{category}"
|
|
167
|
+
default_severity: Severity = Severity.{severity}
|
|
168
|
+
|
|
169
|
+
def __init__(
|
|
170
|
+
self,
|
|
171
|
+
config: ValidatorConfig | None = None,
|
|
172
|
+
**kwargs: Any,
|
|
173
|
+
) -> None:
|
|
174
|
+
"""Initialize the validator.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
config: Optional validator configuration
|
|
178
|
+
**kwargs: Additional keyword arguments
|
|
179
|
+
"""
|
|
180
|
+
super().__init__(config, **kwargs)
|
|
181
|
+
|
|
182
|
+
def validate(self, lf: pl.LazyFrame) -> list[ValidationIssue]:
|
|
183
|
+
"""Validate the data.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
lf: Polars LazyFrame to validate
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
List of validation issues found
|
|
190
|
+
"""
|
|
191
|
+
issues: list[ValidationIssue] = []
|
|
192
|
+
total_rows = lf.select(pl.len()).collect().item()
|
|
193
|
+
|
|
194
|
+
if total_rows == 0:
|
|
195
|
+
return issues
|
|
196
|
+
|
|
197
|
+
# TODO: Implement your validation logic here
|
|
198
|
+
# Example:
|
|
199
|
+
# for col in self._get_target_columns(lf):
|
|
200
|
+
# violations = lf.filter(pl.col(col).is_null()).collect()
|
|
201
|
+
# if violations.height > 0:
|
|
202
|
+
# issues.append(ValidationIssue(
|
|
203
|
+
# column=col,
|
|
204
|
+
# issue_type=self.name,
|
|
205
|
+
# count=violations.height,
|
|
206
|
+
# severity=self.default_severity,
|
|
207
|
+
# details=f"Found {{violations.height}} issues in '{{col}}'",
|
|
208
|
+
# ))
|
|
209
|
+
|
|
210
|
+
return issues
|
|
211
|
+
'''
|
|
212
|
+
result.add_file(f"{config.name}/validator.py", content)
|
|
213
|
+
|
|
214
|
+
def _generate_column(self, config: ScaffoldConfig, result: ScaffoldResult) -> None:
|
|
215
|
+
"""Generate column validator template."""
|
|
216
|
+
severity = config.extra.get("severity", "MEDIUM")
|
|
217
|
+
category = config.extra.get("category", config.category)
|
|
218
|
+
|
|
219
|
+
content = f'''{self._get_header(config)}
|
|
220
|
+
|
|
221
|
+
from __future__ import annotations
|
|
222
|
+
|
|
223
|
+
from typing import Any
|
|
224
|
+
|
|
225
|
+
import polars as pl
|
|
226
|
+
|
|
227
|
+
from truthound.validators.base import (
|
|
228
|
+
ColumnValidator,
|
|
229
|
+
ValidationIssue,
|
|
230
|
+
ValidatorConfig,
|
|
231
|
+
)
|
|
232
|
+
from truthound.validators.sdk import custom_validator
|
|
233
|
+
from truthound.types import Severity
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
@custom_validator(
|
|
237
|
+
name="{config.name}",
|
|
238
|
+
category="{category}",
|
|
239
|
+
description="{config.description or f'{config.title_name} column validator'}",
|
|
240
|
+
version="{config.version}",
|
|
241
|
+
author="{config.author}",
|
|
242
|
+
tags=["{category}", "column"],
|
|
243
|
+
)
|
|
244
|
+
class {config.class_name}Validator(ColumnValidator):
|
|
245
|
+
"""{config.description or f'{config.title_name} column validator.'}
|
|
246
|
+
|
|
247
|
+
Validates each column that matches the configured criteria.
|
|
248
|
+
|
|
249
|
+
Example:
|
|
250
|
+
>>> validator = {config.class_name}Validator(columns=["value", "amount"])
|
|
251
|
+
>>> issues = validator.validate(lf)
|
|
252
|
+
"""
|
|
253
|
+
|
|
254
|
+
name: str = "{config.name}"
|
|
255
|
+
category: str = "{category}"
|
|
256
|
+
default_severity: Severity = Severity.{severity}
|
|
257
|
+
|
|
258
|
+
def __init__(
|
|
259
|
+
self,
|
|
260
|
+
config: ValidatorConfig | None = None,
|
|
261
|
+
columns: tuple[str, ...] | list[str] | None = None,
|
|
262
|
+
severity: Severity = Severity.{severity},
|
|
263
|
+
**kwargs: Any,
|
|
264
|
+
) -> None:
|
|
265
|
+
"""Initialize the validator.
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
config: Validator configuration
|
|
269
|
+
columns: Columns to validate (None = all applicable)
|
|
270
|
+
severity: Severity level for issues
|
|
271
|
+
**kwargs: Additional keyword arguments
|
|
272
|
+
"""
|
|
273
|
+
super().__init__(config, columns=columns, **kwargs)
|
|
274
|
+
self._severity = severity
|
|
275
|
+
|
|
276
|
+
def check_column(
|
|
277
|
+
self,
|
|
278
|
+
lf: pl.LazyFrame,
|
|
279
|
+
col: str,
|
|
280
|
+
total_rows: int,
|
|
281
|
+
) -> ValidationIssue | None:
|
|
282
|
+
"""Check a single column for violations.
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
lf: LazyFrame to validate
|
|
286
|
+
col: Column name to check
|
|
287
|
+
total_rows: Total row count
|
|
288
|
+
|
|
289
|
+
Returns:
|
|
290
|
+
ValidationIssue if violations found, None otherwise
|
|
291
|
+
"""
|
|
292
|
+
# TODO: Implement your column validation logic
|
|
293
|
+
# Example: Check for null values
|
|
294
|
+
# violations = lf.filter(pl.col(col).is_null()).collect()
|
|
295
|
+
#
|
|
296
|
+
# if violations.height == 0:
|
|
297
|
+
# return None
|
|
298
|
+
#
|
|
299
|
+
# samples = violations[col].head(5).to_list()
|
|
300
|
+
# return ValidationIssue(
|
|
301
|
+
# column=col,
|
|
302
|
+
# issue_type=self.name,
|
|
303
|
+
# count=violations.height,
|
|
304
|
+
# severity=self._severity,
|
|
305
|
+
# details=f"Found {{violations.height}} issues in '{{col}}'",
|
|
306
|
+
# sample_values=samples,
|
|
307
|
+
# )
|
|
308
|
+
|
|
309
|
+
return None
|
|
310
|
+
'''
|
|
311
|
+
result.add_file(f"{config.name}/validator.py", content)
|
|
312
|
+
|
|
313
|
+
def _generate_pattern(self, config: ScaffoldConfig, result: ScaffoldResult) -> None:
|
|
314
|
+
"""Generate pattern validator template."""
|
|
315
|
+
severity = config.extra.get("severity", "MEDIUM")
|
|
316
|
+
category = config.extra.get("category", config.category)
|
|
317
|
+
pattern = config.extra.get("pattern", r".*")
|
|
318
|
+
|
|
319
|
+
content = f'''{self._get_header(config)}
|
|
320
|
+
|
|
321
|
+
from __future__ import annotations
|
|
322
|
+
|
|
323
|
+
import re
|
|
324
|
+
from typing import Any
|
|
325
|
+
|
|
326
|
+
import polars as pl
|
|
327
|
+
|
|
328
|
+
from truthound.validators.base import (
|
|
329
|
+
Validator,
|
|
330
|
+
ValidationIssue,
|
|
331
|
+
ValidatorConfig,
|
|
332
|
+
RegexValidatorMixin,
|
|
333
|
+
StringValidatorMixin,
|
|
334
|
+
)
|
|
335
|
+
from truthound.validators.sdk import custom_validator
|
|
336
|
+
from truthound.types import Severity
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
@custom_validator(
|
|
340
|
+
name="{config.name}",
|
|
341
|
+
category="{category}",
|
|
342
|
+
description="{config.description or f'{config.title_name} pattern validator'}",
|
|
343
|
+
version="{config.version}",
|
|
344
|
+
author="{config.author}",
|
|
345
|
+
tags=["{category}", "pattern", "regex"],
|
|
346
|
+
)
|
|
347
|
+
class {config.class_name}Validator(Validator, StringValidatorMixin, RegexValidatorMixin):
|
|
348
|
+
"""{config.description or f'{config.title_name} pattern validator.'}
|
|
349
|
+
|
|
350
|
+
Validates string values against a regex pattern.
|
|
351
|
+
|
|
352
|
+
Example:
|
|
353
|
+
>>> validator = {config.class_name}Validator(pattern=r"^[A-Z]{{2,3}}-\\\\d{{4}}$")
|
|
354
|
+
>>> issues = validator.validate(lf)
|
|
355
|
+
"""
|
|
356
|
+
|
|
357
|
+
name: str = "{config.name}"
|
|
358
|
+
category: str = "{category}"
|
|
359
|
+
default_severity: Severity = Severity.{severity}
|
|
360
|
+
|
|
361
|
+
def __init__(
|
|
362
|
+
self,
|
|
363
|
+
config: ValidatorConfig | None = None,
|
|
364
|
+
pattern: str = r"{pattern}",
|
|
365
|
+
columns: tuple[str, ...] | list[str] | None = None,
|
|
366
|
+
case_sensitive: bool = True,
|
|
367
|
+
invert: bool = False,
|
|
368
|
+
severity: Severity = Severity.{severity},
|
|
369
|
+
**kwargs: Any,
|
|
370
|
+
) -> None:
|
|
371
|
+
"""Initialize the validator.
|
|
372
|
+
|
|
373
|
+
Args:
|
|
374
|
+
config: Validator configuration
|
|
375
|
+
pattern: Regex pattern to match
|
|
376
|
+
columns: Columns to validate
|
|
377
|
+
case_sensitive: Whether pattern matching is case sensitive
|
|
378
|
+
invert: If True, match values that DON'T match the pattern
|
|
379
|
+
severity: Severity level for issues
|
|
380
|
+
**kwargs: Additional keyword arguments
|
|
381
|
+
"""
|
|
382
|
+
super().__init__(config, **kwargs)
|
|
383
|
+
self._pattern_str = pattern
|
|
384
|
+
self._columns = columns
|
|
385
|
+
self._case_sensitive = case_sensitive
|
|
386
|
+
self._invert = invert
|
|
387
|
+
self._severity = severity
|
|
388
|
+
|
|
389
|
+
# Compile pattern for validation
|
|
390
|
+
flags = 0 if case_sensitive else re.IGNORECASE
|
|
391
|
+
self._pattern = re.compile(pattern, flags)
|
|
392
|
+
|
|
393
|
+
def validate(self, lf: pl.LazyFrame) -> list[ValidationIssue]:
|
|
394
|
+
"""Validate string columns against pattern.
|
|
395
|
+
|
|
396
|
+
Args:
|
|
397
|
+
lf: LazyFrame to validate
|
|
398
|
+
|
|
399
|
+
Returns:
|
|
400
|
+
List of validation issues
|
|
401
|
+
"""
|
|
402
|
+
issues: list[ValidationIssue] = []
|
|
403
|
+
total_rows = lf.select(pl.len()).collect().item()
|
|
404
|
+
|
|
405
|
+
if total_rows == 0:
|
|
406
|
+
return issues
|
|
407
|
+
|
|
408
|
+
# Get string columns
|
|
409
|
+
columns = self._columns or self._get_string_columns(lf)
|
|
410
|
+
|
|
411
|
+
for column in columns:
|
|
412
|
+
if column not in lf.collect_schema().names():
|
|
413
|
+
continue
|
|
414
|
+
|
|
415
|
+
# Check pattern match
|
|
416
|
+
if self._invert:
|
|
417
|
+
# Find values that match (when we want non-matches)
|
|
418
|
+
violations = lf.filter(
|
|
419
|
+
pl.col(column).str.contains(self._pattern_str)
|
|
420
|
+
).collect()
|
|
421
|
+
else:
|
|
422
|
+
# Find values that don't match (when we want matches)
|
|
423
|
+
violations = lf.filter(
|
|
424
|
+
pl.col(column).is_not_null()
|
|
425
|
+
& ~pl.col(column).str.contains(self._pattern_str)
|
|
426
|
+
).collect()
|
|
427
|
+
|
|
428
|
+
if violations.height == 0:
|
|
429
|
+
continue
|
|
430
|
+
|
|
431
|
+
samples = violations[column].head(5).to_list()
|
|
432
|
+
match_type = "match" if self._invert else "do not match"
|
|
433
|
+
|
|
434
|
+
issues.append(ValidationIssue(
|
|
435
|
+
column=column,
|
|
436
|
+
issue_type=f"{{self.name}}_pattern_mismatch",
|
|
437
|
+
count=violations.height,
|
|
438
|
+
severity=self._severity,
|
|
439
|
+
details=(
|
|
440
|
+
f"Column '{{column}}' has {{violations.height}} values that "
|
|
441
|
+
f"{{match_type}} pattern '{{self._pattern_str}}'"
|
|
442
|
+
),
|
|
443
|
+
sample_values=samples,
|
|
444
|
+
expected=self._pattern_str if not self._invert else f"NOT {{self._pattern_str}}",
|
|
445
|
+
))
|
|
446
|
+
|
|
447
|
+
return issues
|
|
448
|
+
'''
|
|
449
|
+
result.add_file(f"{config.name}/validator.py", content)
|
|
450
|
+
|
|
451
|
+
def _generate_range(self, config: ScaffoldConfig, result: ScaffoldResult) -> None:
|
|
452
|
+
"""Generate range validator template."""
|
|
453
|
+
severity = config.extra.get("severity", "MEDIUM")
|
|
454
|
+
category = config.extra.get("category", config.category)
|
|
455
|
+
min_value = config.extra.get("min_value")
|
|
456
|
+
max_value = config.extra.get("max_value")
|
|
457
|
+
|
|
458
|
+
min_val_str = str(min_value) if min_value is not None else "None"
|
|
459
|
+
max_val_str = str(max_value) if max_value is not None else "None"
|
|
460
|
+
|
|
461
|
+
content = f'''{self._get_header(config)}
|
|
462
|
+
|
|
463
|
+
from __future__ import annotations
|
|
464
|
+
|
|
465
|
+
from typing import Any
|
|
466
|
+
|
|
467
|
+
import polars as pl
|
|
468
|
+
|
|
469
|
+
from truthound.validators.base import (
|
|
470
|
+
Validator,
|
|
471
|
+
ValidationIssue,
|
|
472
|
+
ValidatorConfig,
|
|
473
|
+
NumericValidatorMixin,
|
|
474
|
+
NUMERIC_TYPES,
|
|
475
|
+
)
|
|
476
|
+
from truthound.validators.sdk import custom_validator
|
|
477
|
+
from truthound.types import Severity
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
@custom_validator(
|
|
481
|
+
name="{config.name}",
|
|
482
|
+
category="{category}",
|
|
483
|
+
description="{config.description or f'{config.title_name} range validator'}",
|
|
484
|
+
version="{config.version}",
|
|
485
|
+
author="{config.author}",
|
|
486
|
+
tags=["{category}", "range", "numeric"],
|
|
487
|
+
)
|
|
488
|
+
class {config.class_name}Validator(Validator, NumericValidatorMixin):
|
|
489
|
+
"""{config.description or f'{config.title_name} range validator.'}
|
|
490
|
+
|
|
491
|
+
Validates numeric values are within a specified range.
|
|
492
|
+
|
|
493
|
+
Example:
|
|
494
|
+
>>> validator = {config.class_name}Validator(min_value=0, max_value=100)
|
|
495
|
+
>>> issues = validator.validate(lf)
|
|
496
|
+
"""
|
|
497
|
+
|
|
498
|
+
name: str = "{config.name}"
|
|
499
|
+
category: str = "{category}"
|
|
500
|
+
default_severity: Severity = Severity.{severity}
|
|
501
|
+
|
|
502
|
+
def __init__(
|
|
503
|
+
self,
|
|
504
|
+
config: ValidatorConfig | None = None,
|
|
505
|
+
min_value: float | int | None = {min_val_str},
|
|
506
|
+
max_value: float | int | None = {max_val_str},
|
|
507
|
+
columns: tuple[str, ...] | list[str] | None = None,
|
|
508
|
+
inclusive: bool = True,
|
|
509
|
+
severity: Severity = Severity.{severity},
|
|
510
|
+
**kwargs: Any,
|
|
511
|
+
) -> None:
|
|
512
|
+
"""Initialize the validator.
|
|
513
|
+
|
|
514
|
+
Args:
|
|
515
|
+
config: Validator configuration
|
|
516
|
+
min_value: Minimum allowed value (None = no minimum)
|
|
517
|
+
max_value: Maximum allowed value (None = no maximum)
|
|
518
|
+
columns: Columns to validate
|
|
519
|
+
inclusive: Whether bounds are inclusive
|
|
520
|
+
severity: Severity level for issues
|
|
521
|
+
**kwargs: Additional keyword arguments
|
|
522
|
+
"""
|
|
523
|
+
super().__init__(config, **kwargs)
|
|
524
|
+
self._min_value = min_value
|
|
525
|
+
self._max_value = max_value
|
|
526
|
+
self._columns = columns
|
|
527
|
+
self._inclusive = inclusive
|
|
528
|
+
self._severity = severity
|
|
529
|
+
|
|
530
|
+
if min_value is None and max_value is None:
|
|
531
|
+
raise ValueError("At least one of min_value or max_value must be specified")
|
|
532
|
+
|
|
533
|
+
def _build_violation_expr(self, col: str) -> pl.Expr:
|
|
534
|
+
"""Build expression to find violations."""
|
|
535
|
+
conditions: list[pl.Expr] = []
|
|
536
|
+
|
|
537
|
+
if self._min_value is not None:
|
|
538
|
+
if self._inclusive:
|
|
539
|
+
conditions.append(pl.col(col) < self._min_value)
|
|
540
|
+
else:
|
|
541
|
+
conditions.append(pl.col(col) <= self._min_value)
|
|
542
|
+
|
|
543
|
+
if self._max_value is not None:
|
|
544
|
+
if self._inclusive:
|
|
545
|
+
conditions.append(pl.col(col) > self._max_value)
|
|
546
|
+
else:
|
|
547
|
+
conditions.append(pl.col(col) >= self._max_value)
|
|
548
|
+
|
|
549
|
+
if len(conditions) == 1:
|
|
550
|
+
return conditions[0]
|
|
551
|
+
return conditions[0] | conditions[1]
|
|
552
|
+
|
|
553
|
+
def _format_range(self) -> str:
|
|
554
|
+
"""Format the expected range for display."""
|
|
555
|
+
min_bracket = "[" if self._inclusive else "("
|
|
556
|
+
max_bracket = "]" if self._inclusive else ")"
|
|
557
|
+
min_val = str(self._min_value) if self._min_value is not None else "-∞"
|
|
558
|
+
max_val = str(self._max_value) if self._max_value is not None else "+∞"
|
|
559
|
+
return f"{{min_bracket}}{{min_val}}, {{max_val}}{{max_bracket}}"
|
|
560
|
+
|
|
561
|
+
def validate(self, lf: pl.LazyFrame) -> list[ValidationIssue]:
|
|
562
|
+
"""Validate numeric values are in range.
|
|
563
|
+
|
|
564
|
+
Args:
|
|
565
|
+
lf: LazyFrame to validate
|
|
566
|
+
|
|
567
|
+
Returns:
|
|
568
|
+
List of validation issues
|
|
569
|
+
"""
|
|
570
|
+
issues: list[ValidationIssue] = []
|
|
571
|
+
total_rows = lf.select(pl.len()).collect().item()
|
|
572
|
+
|
|
573
|
+
if total_rows == 0:
|
|
574
|
+
return issues
|
|
575
|
+
|
|
576
|
+
# Get numeric columns
|
|
577
|
+
columns = self._columns or self._get_numeric_columns(lf)
|
|
578
|
+
|
|
579
|
+
for column in columns:
|
|
580
|
+
if column not in lf.collect_schema().names():
|
|
581
|
+
continue
|
|
582
|
+
|
|
583
|
+
violation_expr = self._build_violation_expr(column)
|
|
584
|
+
violations = lf.filter(violation_expr).collect()
|
|
585
|
+
|
|
586
|
+
if violations.height == 0:
|
|
587
|
+
continue
|
|
588
|
+
|
|
589
|
+
samples = violations[column].head(5).to_list()
|
|
590
|
+
|
|
591
|
+
issues.append(ValidationIssue(
|
|
592
|
+
column=column,
|
|
593
|
+
issue_type=f"{{self.name}}_out_of_range",
|
|
594
|
+
count=violations.height,
|
|
595
|
+
severity=self._severity,
|
|
596
|
+
details=(
|
|
597
|
+
f"Column '{{column}}' has {{violations.height}} values "
|
|
598
|
+
f"outside range {{self._format_range()}}"
|
|
599
|
+
),
|
|
600
|
+
sample_values=samples,
|
|
601
|
+
expected=self._format_range(),
|
|
602
|
+
))
|
|
603
|
+
|
|
604
|
+
return issues
|
|
605
|
+
'''
|
|
606
|
+
result.add_file(f"{config.name}/validator.py", content)
|
|
607
|
+
|
|
608
|
+
def _generate_comparison(self, config: ScaffoldConfig, result: ScaffoldResult) -> None:
|
|
609
|
+
"""Generate comparison validator template."""
|
|
610
|
+
severity = config.extra.get("severity", "HIGH")
|
|
611
|
+
category = config.extra.get("category", config.category)
|
|
612
|
+
|
|
613
|
+
content = f'''{self._get_header(config)}
|
|
614
|
+
|
|
615
|
+
from __future__ import annotations
|
|
616
|
+
|
|
617
|
+
from typing import Any
|
|
618
|
+
|
|
619
|
+
import polars as pl
|
|
620
|
+
|
|
621
|
+
from truthound.validators.base import Validator, ValidationIssue, ValidatorConfig
|
|
622
|
+
from truthound.validators.sdk import custom_validator
|
|
623
|
+
from truthound.types import Severity
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
@custom_validator(
|
|
627
|
+
name="{config.name}",
|
|
628
|
+
category="{category}",
|
|
629
|
+
description="{config.description or f'{config.title_name} comparison validator'}",
|
|
630
|
+
version="{config.version}",
|
|
631
|
+
author="{config.author}",
|
|
632
|
+
tags=["{category}", "comparison", "cross_column"],
|
|
633
|
+
)
|
|
634
|
+
class {config.class_name}Validator(Validator):
|
|
635
|
+
"""{config.description or f'{config.title_name} comparison validator.'}
|
|
636
|
+
|
|
637
|
+
Compares values between columns.
|
|
638
|
+
|
|
639
|
+
Supported operators:
|
|
640
|
+
- eq: left == right
|
|
641
|
+
- ne: left != right
|
|
642
|
+
- lt: left < right
|
|
643
|
+
- le: left <= right
|
|
644
|
+
- gt: left > right
|
|
645
|
+
- ge: left >= right
|
|
646
|
+
|
|
647
|
+
Example:
|
|
648
|
+
>>> validator = {config.class_name}Validator(
|
|
649
|
+
... left_column="start_date",
|
|
650
|
+
... right_column="end_date",
|
|
651
|
+
... operator="lt",
|
|
652
|
+
... )
|
|
653
|
+
>>> issues = validator.validate(lf)
|
|
654
|
+
"""
|
|
655
|
+
|
|
656
|
+
name: str = "{config.name}"
|
|
657
|
+
category: str = "{category}"
|
|
658
|
+
default_severity: Severity = Severity.{severity}
|
|
659
|
+
|
|
660
|
+
OPERATORS = {{
|
|
661
|
+
"eq": ("==", lambda l, r: l != r), # Violation when NOT equal
|
|
662
|
+
"ne": ("!=", lambda l, r: l == r), # Violation when equal
|
|
663
|
+
"lt": ("<", lambda l, r: l >= r), # Violation when >=
|
|
664
|
+
"le": ("<=", lambda l, r: l > r), # Violation when >
|
|
665
|
+
"gt": (">", lambda l, r: l <= r), # Violation when <=
|
|
666
|
+
"ge": (">=", lambda l, r: l < r), # Violation when <
|
|
667
|
+
}}
|
|
668
|
+
|
|
669
|
+
def __init__(
|
|
670
|
+
self,
|
|
671
|
+
config: ValidatorConfig | None = None,
|
|
672
|
+
left_column: str = "",
|
|
673
|
+
right_column: str = "",
|
|
674
|
+
operator: str = "eq",
|
|
675
|
+
severity: Severity = Severity.{severity},
|
|
676
|
+
**kwargs: Any,
|
|
677
|
+
) -> None:
|
|
678
|
+
"""Initialize the validator.
|
|
679
|
+
|
|
680
|
+
Args:
|
|
681
|
+
config: Validator configuration
|
|
682
|
+
left_column: Left side column name
|
|
683
|
+
right_column: Right side column name
|
|
684
|
+
operator: Comparison operator
|
|
685
|
+
severity: Severity level for issues
|
|
686
|
+
**kwargs: Additional keyword arguments
|
|
687
|
+
"""
|
|
688
|
+
super().__init__(config, **kwargs)
|
|
689
|
+
self._left_column = left_column
|
|
690
|
+
self._right_column = right_column
|
|
691
|
+
self._operator = operator
|
|
692
|
+
self._severity = severity
|
|
693
|
+
|
|
694
|
+
if not left_column or not right_column:
|
|
695
|
+
raise ValueError("Both left_column and right_column must be specified")
|
|
696
|
+
|
|
697
|
+
if operator not in self.OPERATORS:
|
|
698
|
+
raise ValueError(
|
|
699
|
+
f"Invalid operator '{{operator}}'. "
|
|
700
|
+
f"Valid operators: {{list(self.OPERATORS.keys())}}"
|
|
701
|
+
)
|
|
702
|
+
|
|
703
|
+
def _build_violation_expr(self) -> pl.Expr:
|
|
704
|
+
"""Build expression to find violations."""
|
|
705
|
+
left = pl.col(self._left_column)
|
|
706
|
+
right = pl.col(self._right_column)
|
|
707
|
+
_, expr_builder = self.OPERATORS[self._operator]
|
|
708
|
+
return expr_builder(left, right)
|
|
709
|
+
|
|
710
|
+
def validate(self, lf: pl.LazyFrame) -> list[ValidationIssue]:
|
|
711
|
+
"""Validate column comparison.
|
|
712
|
+
|
|
713
|
+
Args:
|
|
714
|
+
lf: LazyFrame to validate
|
|
715
|
+
|
|
716
|
+
Returns:
|
|
717
|
+
List of validation issues
|
|
718
|
+
"""
|
|
719
|
+
issues: list[ValidationIssue] = []
|
|
720
|
+
|
|
721
|
+
# Check columns exist
|
|
722
|
+
schema = lf.collect_schema()
|
|
723
|
+
available = schema.names()
|
|
724
|
+
|
|
725
|
+
if self._left_column not in available:
|
|
726
|
+
self.logger.warning(f"Column '{{self._left_column}}' not found")
|
|
727
|
+
return issues
|
|
728
|
+
|
|
729
|
+
if self._right_column not in available:
|
|
730
|
+
self.logger.warning(f"Column '{{self._right_column}}' not found")
|
|
731
|
+
return issues
|
|
732
|
+
|
|
733
|
+
total_rows = lf.select(pl.len()).collect().item()
|
|
734
|
+
if total_rows == 0:
|
|
735
|
+
return issues
|
|
736
|
+
|
|
737
|
+
violation_expr = self._build_violation_expr()
|
|
738
|
+
violations = lf.filter(violation_expr).collect()
|
|
739
|
+
|
|
740
|
+
if violations.height == 0:
|
|
741
|
+
return issues
|
|
742
|
+
|
|
743
|
+
samples = violations.select([self._left_column, self._right_column]).head(5).to_dicts()
|
|
744
|
+
symbol, _ = self.OPERATORS[self._operator]
|
|
745
|
+
|
|
746
|
+
issues.append(ValidationIssue(
|
|
747
|
+
column=f"{{self._left_column}}, {{self._right_column}}",
|
|
748
|
+
issue_type=f"{{self.name}}_comparison_failed",
|
|
749
|
+
count=violations.height,
|
|
750
|
+
severity=self._severity,
|
|
751
|
+
details=(
|
|
752
|
+
f"Expected: {{self._left_column}} {{symbol}} {{self._right_column}}. "
|
|
753
|
+
f"Found {{violations.height}} violations."
|
|
754
|
+
),
|
|
755
|
+
sample_values=samples,
|
|
756
|
+
expected=f"{{self._left_column}} {{symbol}} {{self._right_column}}",
|
|
757
|
+
))
|
|
758
|
+
|
|
759
|
+
return issues
|
|
760
|
+
'''
|
|
761
|
+
result.add_file(f"{config.name}/validator.py", content)
|
|
762
|
+
|
|
763
|
+
def _generate_composite(self, config: ScaffoldConfig, result: ScaffoldResult) -> None:
|
|
764
|
+
"""Generate composite validator template."""
|
|
765
|
+
severity = config.extra.get("severity", "MEDIUM")
|
|
766
|
+
category = config.extra.get("category", config.category)
|
|
767
|
+
|
|
768
|
+
content = f'''{self._get_header(config)}
|
|
769
|
+
|
|
770
|
+
from __future__ import annotations
|
|
771
|
+
|
|
772
|
+
from typing import Any
|
|
773
|
+
|
|
774
|
+
import polars as pl
|
|
775
|
+
|
|
776
|
+
from truthound.validators.base import Validator, ValidationIssue, ValidatorConfig
|
|
777
|
+
from truthound.validators.sdk import custom_validator, CompositeValidator
|
|
778
|
+
from truthound.types import Severity
|
|
779
|
+
|
|
780
|
+
|
|
781
|
+
@custom_validator(
|
|
782
|
+
name="{config.name}",
|
|
783
|
+
category="{category}",
|
|
784
|
+
description="{config.description or f'{config.title_name} composite validator'}",
|
|
785
|
+
version="{config.version}",
|
|
786
|
+
author="{config.author}",
|
|
787
|
+
tags=["{category}", "composite"],
|
|
788
|
+
)
|
|
789
|
+
class {config.class_name}Validator(CompositeValidator):
|
|
790
|
+
"""{config.description or f'{config.title_name} composite validator.'}
|
|
791
|
+
|
|
792
|
+
Combines multiple validators into one validation pass.
|
|
793
|
+
|
|
794
|
+
Example:
|
|
795
|
+
>>> validator = {config.class_name}Validator()
|
|
796
|
+
>>> issues = validator.validate(lf)
|
|
797
|
+
|
|
798
|
+
# Or with custom validators
|
|
799
|
+
>>> validator = {config.class_name}Validator(validators=[
|
|
800
|
+
... NullValidator(columns=("id",)),
|
|
801
|
+
... UniqueValidator(columns=("id",)),
|
|
802
|
+
... ])
|
|
803
|
+
"""
|
|
804
|
+
|
|
805
|
+
name: str = "{config.name}"
|
|
806
|
+
category: str = "{category}"
|
|
807
|
+
|
|
808
|
+
def __init__(
|
|
809
|
+
self,
|
|
810
|
+
validators: list[Validator] | None = None,
|
|
811
|
+
config: ValidatorConfig | None = None,
|
|
812
|
+
**kwargs: Any,
|
|
813
|
+
) -> None:
|
|
814
|
+
"""Initialize the validator.
|
|
815
|
+
|
|
816
|
+
Args:
|
|
817
|
+
validators: List of validators to run
|
|
818
|
+
config: Validator configuration
|
|
819
|
+
**kwargs: Additional keyword arguments
|
|
820
|
+
"""
|
|
821
|
+
super().__init__(validators=validators, config=config, **kwargs)
|
|
822
|
+
|
|
823
|
+
def get_validators(self) -> list[Validator]:
|
|
824
|
+
"""Get the list of validators to run.
|
|
825
|
+
|
|
826
|
+
Override this method to define validators at the class level.
|
|
827
|
+
You can combine this with validators passed to __init__.
|
|
828
|
+
|
|
829
|
+
Returns:
|
|
830
|
+
List of Validator instances
|
|
831
|
+
"""
|
|
832
|
+
validators = super().get_validators()
|
|
833
|
+
|
|
834
|
+
# TODO: Add your default validators here
|
|
835
|
+
# Example:
|
|
836
|
+
# from truthound.validators import NullValidator, UniqueValidator
|
|
837
|
+
# validators.extend([
|
|
838
|
+
# NullValidator(columns=("id", "name")),
|
|
839
|
+
# UniqueValidator(columns=("id",)),
|
|
840
|
+
# ])
|
|
841
|
+
|
|
842
|
+
return validators
|
|
843
|
+
'''
|
|
844
|
+
result.add_file(f"{config.name}/validator.py", content)
|
|
845
|
+
|
|
846
|
+
def _generate_full(self, config: ScaffoldConfig, result: ScaffoldResult) -> None:
|
|
847
|
+
"""Generate full-featured validator template."""
|
|
848
|
+
# Generate column validator as the base
|
|
849
|
+
self._generate_column(config, result)
|
|
850
|
+
|
|
851
|
+
# Always include tests and docs for full template
|
|
852
|
+
self._generate_tests(config, result)
|
|
853
|
+
self._generate_docs(config, result)
|
|
854
|
+
self._generate_example(config, result)
|
|
855
|
+
|
|
856
|
+
def _generate_tests(self, config: ScaffoldConfig, result: ScaffoldResult) -> None:
|
|
857
|
+
"""Generate test file."""
|
|
858
|
+
content = f'''"""Tests for {config.class_name}Validator."""
|
|
859
|
+
|
|
860
|
+
import pytest
|
|
861
|
+
import polars as pl
|
|
862
|
+
|
|
863
|
+
from truthound.validators.sdk.testing import (
|
|
864
|
+
ValidatorTestCase,
|
|
865
|
+
create_test_dataframe,
|
|
866
|
+
assert_no_issues,
|
|
867
|
+
assert_has_issue,
|
|
868
|
+
assert_issue_count,
|
|
869
|
+
)
|
|
870
|
+
from {config.name} import {config.class_name}Validator
|
|
871
|
+
|
|
872
|
+
|
|
873
|
+
class Test{config.class_name}Validator(ValidatorTestCase):
|
|
874
|
+
"""Test cases for {config.class_name}Validator."""
|
|
875
|
+
|
|
876
|
+
def test_valid_data_produces_no_issues(self):
|
|
877
|
+
"""Test that valid data produces no issues."""
|
|
878
|
+
# Arrange
|
|
879
|
+
lf = pl.LazyFrame({{
|
|
880
|
+
"column1": [1, 2, 3, 4, 5],
|
|
881
|
+
"column2": ["a", "b", "c", "d", "e"],
|
|
882
|
+
}})
|
|
883
|
+
validator = {config.class_name}Validator()
|
|
884
|
+
|
|
885
|
+
# Act
|
|
886
|
+
issues = validator.validate(lf)
|
|
887
|
+
|
|
888
|
+
# Assert
|
|
889
|
+
assert_no_issues(issues)
|
|
890
|
+
|
|
891
|
+
def test_invalid_data_produces_issues(self):
|
|
892
|
+
"""Test that invalid data produces issues."""
|
|
893
|
+
# Arrange
|
|
894
|
+
lf = pl.LazyFrame({{
|
|
895
|
+
"column1": [None, 2, None, 4, None],
|
|
896
|
+
"column2": ["a", None, "c", None, "e"],
|
|
897
|
+
}})
|
|
898
|
+
validator = {config.class_name}Validator()
|
|
899
|
+
|
|
900
|
+
# Act
|
|
901
|
+
issues = validator.validate(lf)
|
|
902
|
+
|
|
903
|
+
# Assert
|
|
904
|
+
# TODO: Update assertion based on your validation logic
|
|
905
|
+
# Example:
|
|
906
|
+
# assert_has_issue(issues, issue_type="{config.name}")
|
|
907
|
+
# assert_issue_count(issues, 2)
|
|
908
|
+
pass
|
|
909
|
+
|
|
910
|
+
def test_empty_dataframe_returns_no_issues(self):
|
|
911
|
+
"""Test with empty dataframe."""
|
|
912
|
+
# Arrange
|
|
913
|
+
lf = pl.LazyFrame({{
|
|
914
|
+
"column1": [],
|
|
915
|
+
"column2": [],
|
|
916
|
+
}})
|
|
917
|
+
validator = {config.class_name}Validator()
|
|
918
|
+
|
|
919
|
+
# Act
|
|
920
|
+
issues = validator.validate(lf)
|
|
921
|
+
|
|
922
|
+
# Assert
|
|
923
|
+
assert_no_issues(issues)
|
|
924
|
+
|
|
925
|
+
def test_specific_columns(self):
|
|
926
|
+
"""Test validation with specific columns."""
|
|
927
|
+
# Arrange
|
|
928
|
+
lf = pl.LazyFrame({{
|
|
929
|
+
"target_col": [1, 2, 3],
|
|
930
|
+
"other_col": [None, None, None],
|
|
931
|
+
}})
|
|
932
|
+
validator = {config.class_name}Validator(columns=["target_col"])
|
|
933
|
+
|
|
934
|
+
# Act
|
|
935
|
+
issues = validator.validate(lf)
|
|
936
|
+
|
|
937
|
+
# Assert
|
|
938
|
+
# Only target_col should be validated
|
|
939
|
+
for issue in issues:
|
|
940
|
+
assert issue.column == "target_col"
|
|
941
|
+
|
|
942
|
+
def test_severity_override(self):
|
|
943
|
+
"""Test that severity can be overridden."""
|
|
944
|
+
from truthound.types import Severity
|
|
945
|
+
|
|
946
|
+
# Arrange
|
|
947
|
+
lf = pl.LazyFrame({{"column1": [None, 2, 3]}})
|
|
948
|
+
validator = {config.class_name}Validator(severity=Severity.CRITICAL)
|
|
949
|
+
|
|
950
|
+
# Act
|
|
951
|
+
issues = validator.validate(lf)
|
|
952
|
+
|
|
953
|
+
# Assert
|
|
954
|
+
for issue in issues:
|
|
955
|
+
assert issue.severity == Severity.CRITICAL
|
|
956
|
+
|
|
957
|
+
def test_configuration_options(self):
|
|
958
|
+
"""Test validator configuration options."""
|
|
959
|
+
# TODO: Add tests for your specific configuration options
|
|
960
|
+
pass
|
|
961
|
+
|
|
962
|
+
def test_edge_cases(self):
|
|
963
|
+
"""Test edge cases."""
|
|
964
|
+
# TODO: Add edge case tests
|
|
965
|
+
pass
|
|
966
|
+
|
|
967
|
+
|
|
968
|
+
class Test{config.class_name}ValidatorPerformance:
|
|
969
|
+
"""Performance tests for {config.class_name}Validator."""
|
|
970
|
+
|
|
971
|
+
@pytest.mark.slow
|
|
972
|
+
def test_large_dataset_performance(self):
|
|
973
|
+
"""Test performance with large dataset."""
|
|
974
|
+
import time
|
|
975
|
+
|
|
976
|
+
# Arrange
|
|
977
|
+
n_rows = 1_000_000
|
|
978
|
+
lf = pl.LazyFrame({{
|
|
979
|
+
"column1": list(range(n_rows)),
|
|
980
|
+
"column2": ["value"] * n_rows,
|
|
981
|
+
}})
|
|
982
|
+
validator = {config.class_name}Validator()
|
|
983
|
+
|
|
984
|
+
# Act
|
|
985
|
+
start = time.time()
|
|
986
|
+
issues = validator.validate(lf)
|
|
987
|
+
elapsed = time.time() - start
|
|
988
|
+
|
|
989
|
+
# Assert - should complete in reasonable time
|
|
990
|
+
assert elapsed < 10.0, f"Validation took too long: {{elapsed:.2f}}s"
|
|
991
|
+
'''
|
|
992
|
+
result.add_file(f"{config.name}/tests/__init__.py", "")
|
|
993
|
+
result.add_file(f"{config.name}/tests/test_validator.py", content)
|
|
994
|
+
|
|
995
|
+
def _generate_docs(self, config: ScaffoldConfig, result: ScaffoldResult) -> None:
|
|
996
|
+
"""Generate documentation file."""
|
|
997
|
+
content = f'''# {config.class_name}Validator
|
|
998
|
+
|
|
999
|
+
> {config.category} validator
|
|
1000
|
+
|
|
1001
|
+
## Description
|
|
1002
|
+
|
|
1003
|
+
{config.description or 'TODO: Add description'}
|
|
1004
|
+
|
|
1005
|
+
## Installation
|
|
1006
|
+
|
|
1007
|
+
This validator is part of the Truthound package:
|
|
1008
|
+
|
|
1009
|
+
```bash
|
|
1010
|
+
pip install truthound
|
|
1011
|
+
```
|
|
1012
|
+
|
|
1013
|
+
## Usage
|
|
1014
|
+
|
|
1015
|
+
```python
|
|
1016
|
+
from {config.name} import {config.class_name}Validator
|
|
1017
|
+
|
|
1018
|
+
# Create validator
|
|
1019
|
+
validator = {config.class_name}Validator()
|
|
1020
|
+
|
|
1021
|
+
# Validate data
|
|
1022
|
+
issues = validator.validate(lf)
|
|
1023
|
+
```
|
|
1024
|
+
|
|
1025
|
+
## Configuration
|
|
1026
|
+
|
|
1027
|
+
| Parameter | Type | Default | Description |
|
|
1028
|
+
|-----------|------|---------|-------------|
|
|
1029
|
+
| columns | list[str] | None | Columns to validate (None = all) |
|
|
1030
|
+
| severity | Severity | MEDIUM | Default severity level |
|
|
1031
|
+
|
|
1032
|
+
## Examples
|
|
1033
|
+
|
|
1034
|
+
### Basic Usage
|
|
1035
|
+
|
|
1036
|
+
```python
|
|
1037
|
+
import polars as pl
|
|
1038
|
+
from {config.name} import {config.class_name}Validator
|
|
1039
|
+
|
|
1040
|
+
# Create sample data
|
|
1041
|
+
lf = pl.LazyFrame({{
|
|
1042
|
+
"value": [1, 2, 3, 4, 5],
|
|
1043
|
+
"name": ["a", "b", "c", "d", "e"],
|
|
1044
|
+
}})
|
|
1045
|
+
|
|
1046
|
+
# Create and run validator
|
|
1047
|
+
validator = {config.class_name}Validator()
|
|
1048
|
+
issues = validator.validate(lf)
|
|
1049
|
+
|
|
1050
|
+
print(f"Found {{len(issues)}} issues")
|
|
1051
|
+
```
|
|
1052
|
+
|
|
1053
|
+
### With Specific Columns
|
|
1054
|
+
|
|
1055
|
+
```python
|
|
1056
|
+
validator = {config.class_name}Validator(columns=["value"])
|
|
1057
|
+
issues = validator.validate(lf)
|
|
1058
|
+
```
|
|
1059
|
+
|
|
1060
|
+
### With Custom Severity
|
|
1061
|
+
|
|
1062
|
+
```python
|
|
1063
|
+
from truthound.types import Severity
|
|
1064
|
+
|
|
1065
|
+
validator = {config.class_name}Validator(severity=Severity.CRITICAL)
|
|
1066
|
+
issues = validator.validate(lf)
|
|
1067
|
+
```
|
|
1068
|
+
|
|
1069
|
+
## See Also
|
|
1070
|
+
|
|
1071
|
+
- [Validator SDK](https://github.com/seadonggyun4/Truthound/docs/sdk.md)
|
|
1072
|
+
- [Testing Guide](https://github.com/seadonggyun4/Truthound/docs/testing.md)
|
|
1073
|
+
|
|
1074
|
+
---
|
|
1075
|
+
|
|
1076
|
+
*Version: {config.version}*
|
|
1077
|
+
*Author: {config.author or 'Unknown'}*
|
|
1078
|
+
*License: {config.license_type}*
|
|
1079
|
+
'''
|
|
1080
|
+
result.add_file(f"{config.name}/docs/README.md", content)
|
|
1081
|
+
|
|
1082
|
+
def _generate_example(self, config: ScaffoldConfig, result: ScaffoldResult) -> None:
|
|
1083
|
+
"""Generate example usage file."""
|
|
1084
|
+
content = f'''#!/usr/bin/env python3
|
|
1085
|
+
"""Example usage of {config.class_name}Validator."""
|
|
1086
|
+
|
|
1087
|
+
import polars as pl
|
|
1088
|
+
|
|
1089
|
+
from {config.name} import {config.class_name}Validator
|
|
1090
|
+
|
|
1091
|
+
|
|
1092
|
+
def main():
|
|
1093
|
+
"""Run example validation."""
|
|
1094
|
+
# Create sample data
|
|
1095
|
+
lf = pl.LazyFrame({{
|
|
1096
|
+
"id": [1, 2, 3, 4, 5],
|
|
1097
|
+
"value": [10, 20, None, 40, 50],
|
|
1098
|
+
"name": ["Alice", "Bob", "", "David", "Eve"],
|
|
1099
|
+
}})
|
|
1100
|
+
|
|
1101
|
+
print("Sample data:")
|
|
1102
|
+
print(lf.collect())
|
|
1103
|
+
print()
|
|
1104
|
+
|
|
1105
|
+
# Create and run validator
|
|
1106
|
+
validator = {config.class_name}Validator()
|
|
1107
|
+
issues = validator.validate(lf)
|
|
1108
|
+
|
|
1109
|
+
# Print results
|
|
1110
|
+
print(f"Validation Results: {{len(issues)}} issues found")
|
|
1111
|
+
print("-" * 50)
|
|
1112
|
+
|
|
1113
|
+
for issue in issues:
|
|
1114
|
+
print(f" Column: {{issue.column}}")
|
|
1115
|
+
print(f" Type: {{issue.issue_type}}")
|
|
1116
|
+
print(f" Count: {{issue.count}}")
|
|
1117
|
+
print(f" Severity: {{issue.severity.value}}")
|
|
1118
|
+
print(f" Details: {{issue.details}}")
|
|
1119
|
+
if issue.sample_values:
|
|
1120
|
+
print(f" Samples: {{issue.sample_values[:5]}}")
|
|
1121
|
+
print()
|
|
1122
|
+
|
|
1123
|
+
|
|
1124
|
+
if __name__ == "__main__":
|
|
1125
|
+
main()
|
|
1126
|
+
'''
|
|
1127
|
+
result.add_file(f"{config.name}/examples/basic_usage.py", content, executable=True)
|