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,953 @@
|
|
|
1
|
+
"""Encryption provider implementations.
|
|
2
|
+
|
|
3
|
+
This module provides concrete implementations of encryption algorithms.
|
|
4
|
+
All implementations use authenticated encryption (AEAD) for security.
|
|
5
|
+
|
|
6
|
+
Supported Algorithms:
|
|
7
|
+
- AES-256-GCM (recommended for most use cases)
|
|
8
|
+
- AES-128-GCM (faster, still secure)
|
|
9
|
+
- ChaCha20-Poly1305 (better for software, mobile)
|
|
10
|
+
- XChaCha20-Poly1305 (extended nonce, safer random nonce)
|
|
11
|
+
- Fernet (high-level, batteries-included)
|
|
12
|
+
|
|
13
|
+
Example:
|
|
14
|
+
>>> from truthound.stores.encryption.providers import (
|
|
15
|
+
... get_encryptor,
|
|
16
|
+
... AesGcmEncryptor,
|
|
17
|
+
... )
|
|
18
|
+
>>>
|
|
19
|
+
>>> # Using factory function
|
|
20
|
+
>>> encryptor = get_encryptor("aes-256-gcm")
|
|
21
|
+
>>> key = encryptor.generate_key()
|
|
22
|
+
>>> encrypted = encryptor.encrypt(b"secret data", key)
|
|
23
|
+
>>> decrypted = encryptor.decrypt(encrypted, key)
|
|
24
|
+
>>>
|
|
25
|
+
>>> # Direct instantiation
|
|
26
|
+
>>> aes = AesGcmEncryptor(key_size=32)
|
|
27
|
+
>>> result = aes.encrypt_with_metrics(b"data", key)
|
|
28
|
+
>>> print(result.metrics.to_dict())
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
import time
|
|
34
|
+
from abc import ABC, abstractmethod
|
|
35
|
+
from dataclasses import dataclass
|
|
36
|
+
from typing import Any, Callable
|
|
37
|
+
|
|
38
|
+
from truthound.stores.encryption.base import (
|
|
39
|
+
DecryptionError,
|
|
40
|
+
EncryptionAlgorithm,
|
|
41
|
+
EncryptionConfig,
|
|
42
|
+
EncryptionError,
|
|
43
|
+
EncryptionMetrics,
|
|
44
|
+
EncryptionResult,
|
|
45
|
+
IntegrityError,
|
|
46
|
+
UnsupportedAlgorithmError,
|
|
47
|
+
constant_time_compare,
|
|
48
|
+
generate_key,
|
|
49
|
+
generate_nonce,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# =============================================================================
|
|
54
|
+
# Base Encryptor
|
|
55
|
+
# =============================================================================
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class BaseEncryptor(ABC):
|
|
59
|
+
"""Base class for all encryption implementations.
|
|
60
|
+
|
|
61
|
+
Provides common functionality for encryption providers including
|
|
62
|
+
metrics tracking, key validation, and consistent interface.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
def __init__(self, algorithm: EncryptionAlgorithm) -> None:
|
|
66
|
+
"""Initialize encryptor.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
algorithm: Encryption algorithm this provider implements.
|
|
70
|
+
"""
|
|
71
|
+
self._algorithm = algorithm
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def algorithm(self) -> EncryptionAlgorithm:
|
|
75
|
+
"""Get the encryption algorithm."""
|
|
76
|
+
return self._algorithm
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def key_size(self) -> int:
|
|
80
|
+
"""Get required key size in bytes."""
|
|
81
|
+
return self._algorithm.key_size
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def nonce_size(self) -> int:
|
|
85
|
+
"""Get nonce size in bytes."""
|
|
86
|
+
return self._algorithm.nonce_size
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def tag_size(self) -> int:
|
|
90
|
+
"""Get authentication tag size in bytes."""
|
|
91
|
+
return self._algorithm.tag_size
|
|
92
|
+
|
|
93
|
+
def generate_key(self) -> bytes:
|
|
94
|
+
"""Generate a new random key for this algorithm."""
|
|
95
|
+
return generate_key(self._algorithm)
|
|
96
|
+
|
|
97
|
+
def generate_nonce(self) -> bytes:
|
|
98
|
+
"""Generate a new random nonce for this algorithm."""
|
|
99
|
+
return generate_nonce(self._algorithm)
|
|
100
|
+
|
|
101
|
+
def _validate_key(self, key: bytes) -> None:
|
|
102
|
+
"""Validate key size."""
|
|
103
|
+
if len(key) != self.key_size:
|
|
104
|
+
raise EncryptionError(
|
|
105
|
+
f"Invalid key size: expected {self.key_size} bytes, "
|
|
106
|
+
f"got {len(key)} bytes",
|
|
107
|
+
self._algorithm.value,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
@abstractmethod
|
|
111
|
+
def _encrypt_impl(
|
|
112
|
+
self,
|
|
113
|
+
plaintext: bytes,
|
|
114
|
+
key: bytes,
|
|
115
|
+
nonce: bytes,
|
|
116
|
+
aad: bytes | None = None,
|
|
117
|
+
) -> tuple[bytes, bytes]:
|
|
118
|
+
"""Implementation-specific encryption.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
plaintext: Data to encrypt.
|
|
122
|
+
key: Encryption key.
|
|
123
|
+
nonce: Nonce/IV.
|
|
124
|
+
aad: Additional authenticated data.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
Tuple of (ciphertext, tag).
|
|
128
|
+
"""
|
|
129
|
+
...
|
|
130
|
+
|
|
131
|
+
@abstractmethod
|
|
132
|
+
def _decrypt_impl(
|
|
133
|
+
self,
|
|
134
|
+
ciphertext: bytes,
|
|
135
|
+
key: bytes,
|
|
136
|
+
nonce: bytes,
|
|
137
|
+
tag: bytes,
|
|
138
|
+
aad: bytes | None = None,
|
|
139
|
+
) -> bytes:
|
|
140
|
+
"""Implementation-specific decryption.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
ciphertext: Encrypted data.
|
|
144
|
+
key: Decryption key.
|
|
145
|
+
nonce: Nonce/IV used for encryption.
|
|
146
|
+
tag: Authentication tag.
|
|
147
|
+
aad: Additional authenticated data.
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
Decrypted plaintext.
|
|
151
|
+
|
|
152
|
+
Raises:
|
|
153
|
+
DecryptionError: If decryption fails.
|
|
154
|
+
"""
|
|
155
|
+
...
|
|
156
|
+
|
|
157
|
+
def encrypt(
|
|
158
|
+
self,
|
|
159
|
+
plaintext: bytes,
|
|
160
|
+
key: bytes,
|
|
161
|
+
nonce: bytes | None = None,
|
|
162
|
+
aad: bytes | None = None,
|
|
163
|
+
) -> bytes:
|
|
164
|
+
"""Encrypt plaintext data.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
plaintext: Data to encrypt.
|
|
168
|
+
key: Encryption key.
|
|
169
|
+
nonce: Optional nonce (generated if not provided).
|
|
170
|
+
aad: Additional authenticated data.
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
Encrypted data in format: nonce || ciphertext || tag
|
|
174
|
+
"""
|
|
175
|
+
self._validate_key(key)
|
|
176
|
+
if nonce is None:
|
|
177
|
+
nonce = self.generate_nonce()
|
|
178
|
+
|
|
179
|
+
ciphertext, tag = self._encrypt_impl(plaintext, key, nonce, aad)
|
|
180
|
+
return nonce + ciphertext + tag
|
|
181
|
+
|
|
182
|
+
def encrypt_with_metrics(
|
|
183
|
+
self,
|
|
184
|
+
plaintext: bytes,
|
|
185
|
+
key: bytes,
|
|
186
|
+
nonce: bytes | None = None,
|
|
187
|
+
aad: bytes | None = None,
|
|
188
|
+
) -> EncryptionResult:
|
|
189
|
+
"""Encrypt data with detailed metrics.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
plaintext: Data to encrypt.
|
|
193
|
+
key: Encryption key.
|
|
194
|
+
nonce: Optional nonce (generated if not provided).
|
|
195
|
+
aad: Additional authenticated data.
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
Encryption result with metrics.
|
|
199
|
+
"""
|
|
200
|
+
self._validate_key(key)
|
|
201
|
+
if nonce is None:
|
|
202
|
+
nonce = self.generate_nonce()
|
|
203
|
+
|
|
204
|
+
start_time = time.perf_counter()
|
|
205
|
+
ciphertext, tag = self._encrypt_impl(plaintext, key, nonce, aad)
|
|
206
|
+
elapsed_ms = (time.perf_counter() - start_time) * 1000
|
|
207
|
+
|
|
208
|
+
metrics = EncryptionMetrics(
|
|
209
|
+
plaintext_size=len(plaintext),
|
|
210
|
+
ciphertext_size=len(ciphertext) + len(nonce) + len(tag),
|
|
211
|
+
overhead_bytes=len(nonce) + len(tag),
|
|
212
|
+
encryption_time_ms=elapsed_ms,
|
|
213
|
+
algorithm=self._algorithm,
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
return EncryptionResult(
|
|
217
|
+
ciphertext=ciphertext,
|
|
218
|
+
nonce=nonce,
|
|
219
|
+
tag=tag,
|
|
220
|
+
metrics=metrics,
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
def decrypt(
|
|
224
|
+
self,
|
|
225
|
+
data: bytes,
|
|
226
|
+
key: bytes,
|
|
227
|
+
aad: bytes | None = None,
|
|
228
|
+
) -> bytes:
|
|
229
|
+
"""Decrypt ciphertext data.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
data: Encrypted data in format: nonce || ciphertext || tag
|
|
233
|
+
key: Decryption key.
|
|
234
|
+
aad: Additional authenticated data.
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
Decrypted plaintext.
|
|
238
|
+
|
|
239
|
+
Raises:
|
|
240
|
+
DecryptionError: If decryption or authentication fails.
|
|
241
|
+
"""
|
|
242
|
+
self._validate_key(key)
|
|
243
|
+
|
|
244
|
+
nonce = data[: self.nonce_size]
|
|
245
|
+
tag = data[-self.tag_size :]
|
|
246
|
+
ciphertext = data[self.nonce_size : -self.tag_size]
|
|
247
|
+
|
|
248
|
+
return self._decrypt_impl(ciphertext, key, nonce, tag, aad)
|
|
249
|
+
|
|
250
|
+
def decrypt_with_metrics(
|
|
251
|
+
self,
|
|
252
|
+
data: bytes,
|
|
253
|
+
key: bytes,
|
|
254
|
+
aad: bytes | None = None,
|
|
255
|
+
) -> tuple[bytes, EncryptionMetrics]:
|
|
256
|
+
"""Decrypt data with detailed metrics.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
data: Encrypted data.
|
|
260
|
+
key: Decryption key.
|
|
261
|
+
aad: Additional authenticated data.
|
|
262
|
+
|
|
263
|
+
Returns:
|
|
264
|
+
Tuple of (plaintext, metrics).
|
|
265
|
+
"""
|
|
266
|
+
start_time = time.perf_counter()
|
|
267
|
+
plaintext = self.decrypt(data, key, aad)
|
|
268
|
+
elapsed_ms = (time.perf_counter() - start_time) * 1000
|
|
269
|
+
|
|
270
|
+
metrics = EncryptionMetrics(
|
|
271
|
+
plaintext_size=len(plaintext),
|
|
272
|
+
ciphertext_size=len(data),
|
|
273
|
+
overhead_bytes=self.nonce_size + self.tag_size,
|
|
274
|
+
decryption_time_ms=elapsed_ms,
|
|
275
|
+
algorithm=self._algorithm,
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
return plaintext, metrics
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
# =============================================================================
|
|
282
|
+
# AES-GCM Implementations
|
|
283
|
+
# =============================================================================
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
class AesGcmEncryptor(BaseEncryptor):
|
|
287
|
+
"""AES-GCM authenticated encryption.
|
|
288
|
+
|
|
289
|
+
AES-GCM is the recommended algorithm for most use cases. It provides
|
|
290
|
+
both confidentiality and integrity using a single key.
|
|
291
|
+
|
|
292
|
+
Key Features:
|
|
293
|
+
- Hardware acceleration on modern CPUs (AES-NI)
|
|
294
|
+
- Widely supported and standardized
|
|
295
|
+
- 128-bit authentication tag
|
|
296
|
+
- 96-bit nonce (never reuse with same key!)
|
|
297
|
+
|
|
298
|
+
Example:
|
|
299
|
+
>>> aes = AesGcmEncryptor(key_size=32) # AES-256
|
|
300
|
+
>>> key = aes.generate_key()
|
|
301
|
+
>>> encrypted = aes.encrypt(b"secret", key)
|
|
302
|
+
>>> decrypted = aes.decrypt(encrypted, key)
|
|
303
|
+
"""
|
|
304
|
+
|
|
305
|
+
def __init__(self, key_size: int = 32) -> None:
|
|
306
|
+
"""Initialize AES-GCM encryptor.
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
key_size: Key size in bytes (16 for AES-128, 32 for AES-256).
|
|
310
|
+
"""
|
|
311
|
+
if key_size == 16:
|
|
312
|
+
algorithm = EncryptionAlgorithm.AES_128_GCM
|
|
313
|
+
elif key_size == 32:
|
|
314
|
+
algorithm = EncryptionAlgorithm.AES_256_GCM
|
|
315
|
+
else:
|
|
316
|
+
raise EncryptionError(
|
|
317
|
+
f"Invalid AES key size: {key_size}. Use 16 or 32 bytes."
|
|
318
|
+
)
|
|
319
|
+
super().__init__(algorithm)
|
|
320
|
+
self._aesgcm: Any = None
|
|
321
|
+
|
|
322
|
+
def _get_aesgcm(self, key: bytes) -> Any:
|
|
323
|
+
"""Get AESGCM cipher instance."""
|
|
324
|
+
try:
|
|
325
|
+
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
|
326
|
+
except ImportError as e:
|
|
327
|
+
raise UnsupportedAlgorithmError(
|
|
328
|
+
self._algorithm.value,
|
|
329
|
+
available=["Install 'cryptography' package: pip install cryptography"],
|
|
330
|
+
) from e
|
|
331
|
+
return AESGCM(key)
|
|
332
|
+
|
|
333
|
+
def _encrypt_impl(
|
|
334
|
+
self,
|
|
335
|
+
plaintext: bytes,
|
|
336
|
+
key: bytes,
|
|
337
|
+
nonce: bytes,
|
|
338
|
+
aad: bytes | None = None,
|
|
339
|
+
) -> tuple[bytes, bytes]:
|
|
340
|
+
"""Encrypt using AES-GCM."""
|
|
341
|
+
aesgcm = self._get_aesgcm(key)
|
|
342
|
+
# cryptography library returns ciphertext || tag
|
|
343
|
+
result = aesgcm.encrypt(nonce, plaintext, aad)
|
|
344
|
+
ciphertext = result[:-16] # Everything except last 16 bytes
|
|
345
|
+
tag = result[-16:] # Last 16 bytes is the tag
|
|
346
|
+
return ciphertext, tag
|
|
347
|
+
|
|
348
|
+
def _decrypt_impl(
|
|
349
|
+
self,
|
|
350
|
+
ciphertext: bytes,
|
|
351
|
+
key: bytes,
|
|
352
|
+
nonce: bytes,
|
|
353
|
+
tag: bytes,
|
|
354
|
+
aad: bytes | None = None,
|
|
355
|
+
) -> bytes:
|
|
356
|
+
"""Decrypt using AES-GCM."""
|
|
357
|
+
try:
|
|
358
|
+
from cryptography.exceptions import InvalidTag
|
|
359
|
+
except ImportError as e:
|
|
360
|
+
raise UnsupportedAlgorithmError(
|
|
361
|
+
self._algorithm.value,
|
|
362
|
+
available=["Install 'cryptography' package"],
|
|
363
|
+
) from e
|
|
364
|
+
|
|
365
|
+
aesgcm = self._get_aesgcm(key)
|
|
366
|
+
try:
|
|
367
|
+
# cryptography expects ciphertext || tag
|
|
368
|
+
return aesgcm.decrypt(nonce, ciphertext + tag, aad)
|
|
369
|
+
except InvalidTag as e:
|
|
370
|
+
raise IntegrityError(
|
|
371
|
+
"Authentication failed: data may be corrupted or tampered",
|
|
372
|
+
self._algorithm.value,
|
|
373
|
+
) from e
|
|
374
|
+
except Exception as e:
|
|
375
|
+
raise DecryptionError(str(e), self._algorithm.value) from e
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
# =============================================================================
|
|
379
|
+
# ChaCha20-Poly1305 Implementation
|
|
380
|
+
# =============================================================================
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
class ChaCha20Poly1305Encryptor(BaseEncryptor):
|
|
384
|
+
"""ChaCha20-Poly1305 authenticated encryption.
|
|
385
|
+
|
|
386
|
+
ChaCha20-Poly1305 is a modern AEAD cipher that performs well in
|
|
387
|
+
software implementations (no hardware acceleration needed).
|
|
388
|
+
|
|
389
|
+
Key Features:
|
|
390
|
+
- Excellent software performance
|
|
391
|
+
- 256-bit key, 96-bit nonce
|
|
392
|
+
- 128-bit authentication tag
|
|
393
|
+
- Constant-time implementation
|
|
394
|
+
|
|
395
|
+
Example:
|
|
396
|
+
>>> chacha = ChaCha20Poly1305Encryptor()
|
|
397
|
+
>>> key = chacha.generate_key()
|
|
398
|
+
>>> encrypted = chacha.encrypt(b"secret", key)
|
|
399
|
+
"""
|
|
400
|
+
|
|
401
|
+
def __init__(self) -> None:
|
|
402
|
+
"""Initialize ChaCha20-Poly1305 encryptor."""
|
|
403
|
+
super().__init__(EncryptionAlgorithm.CHACHA20_POLY1305)
|
|
404
|
+
|
|
405
|
+
def _get_chacha(self, key: bytes) -> Any:
|
|
406
|
+
"""Get ChaCha20Poly1305 cipher instance."""
|
|
407
|
+
try:
|
|
408
|
+
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
|
|
409
|
+
except ImportError as e:
|
|
410
|
+
raise UnsupportedAlgorithmError(
|
|
411
|
+
self._algorithm.value,
|
|
412
|
+
available=["Install 'cryptography' package"],
|
|
413
|
+
) from e
|
|
414
|
+
return ChaCha20Poly1305(key)
|
|
415
|
+
|
|
416
|
+
def _encrypt_impl(
|
|
417
|
+
self,
|
|
418
|
+
plaintext: bytes,
|
|
419
|
+
key: bytes,
|
|
420
|
+
nonce: bytes,
|
|
421
|
+
aad: bytes | None = None,
|
|
422
|
+
) -> tuple[bytes, bytes]:
|
|
423
|
+
"""Encrypt using ChaCha20-Poly1305."""
|
|
424
|
+
chacha = self._get_chacha(key)
|
|
425
|
+
result = chacha.encrypt(nonce, plaintext, aad)
|
|
426
|
+
ciphertext = result[:-16]
|
|
427
|
+
tag = result[-16:]
|
|
428
|
+
return ciphertext, tag
|
|
429
|
+
|
|
430
|
+
def _decrypt_impl(
|
|
431
|
+
self,
|
|
432
|
+
ciphertext: bytes,
|
|
433
|
+
key: bytes,
|
|
434
|
+
nonce: bytes,
|
|
435
|
+
tag: bytes,
|
|
436
|
+
aad: bytes | None = None,
|
|
437
|
+
) -> bytes:
|
|
438
|
+
"""Decrypt using ChaCha20-Poly1305."""
|
|
439
|
+
try:
|
|
440
|
+
from cryptography.exceptions import InvalidTag
|
|
441
|
+
except ImportError as e:
|
|
442
|
+
raise UnsupportedAlgorithmError(
|
|
443
|
+
self._algorithm.value,
|
|
444
|
+
available=["Install 'cryptography' package"],
|
|
445
|
+
) from e
|
|
446
|
+
|
|
447
|
+
chacha = self._get_chacha(key)
|
|
448
|
+
try:
|
|
449
|
+
return chacha.decrypt(nonce, ciphertext + tag, aad)
|
|
450
|
+
except InvalidTag as e:
|
|
451
|
+
raise IntegrityError(
|
|
452
|
+
"Authentication failed: data may be corrupted or tampered",
|
|
453
|
+
self._algorithm.value,
|
|
454
|
+
) from e
|
|
455
|
+
except Exception as e:
|
|
456
|
+
raise DecryptionError(str(e), self._algorithm.value) from e
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
# =============================================================================
|
|
460
|
+
# XChaCha20-Poly1305 Implementation
|
|
461
|
+
# =============================================================================
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
class XChaCha20Poly1305Encryptor(BaseEncryptor):
|
|
465
|
+
"""XChaCha20-Poly1305 authenticated encryption.
|
|
466
|
+
|
|
467
|
+
XChaCha20-Poly1305 extends ChaCha20-Poly1305 with a 192-bit nonce,
|
|
468
|
+
making it safe to generate nonces randomly without collision risk.
|
|
469
|
+
|
|
470
|
+
Key Features:
|
|
471
|
+
- 192-bit nonce (safe for random generation)
|
|
472
|
+
- Same security as ChaCha20-Poly1305
|
|
473
|
+
- Ideal for file encryption
|
|
474
|
+
|
|
475
|
+
Note:
|
|
476
|
+
Requires the 'pynacl' package for native implementation.
|
|
477
|
+
Falls back to a cryptography-based implementation if not available.
|
|
478
|
+
|
|
479
|
+
Example:
|
|
480
|
+
>>> xchacha = XChaCha20Poly1305Encryptor()
|
|
481
|
+
>>> key = xchacha.generate_key()
|
|
482
|
+
>>> encrypted = xchacha.encrypt(b"secret", key)
|
|
483
|
+
"""
|
|
484
|
+
|
|
485
|
+
def __init__(self) -> None:
|
|
486
|
+
"""Initialize XChaCha20-Poly1305 encryptor."""
|
|
487
|
+
super().__init__(EncryptionAlgorithm.XCHACHA20_POLY1305)
|
|
488
|
+
self._use_nacl = self._check_nacl()
|
|
489
|
+
|
|
490
|
+
def _check_nacl(self) -> bool:
|
|
491
|
+
"""Check if pynacl is available."""
|
|
492
|
+
try:
|
|
493
|
+
import nacl.secret # noqa: F401
|
|
494
|
+
|
|
495
|
+
return True
|
|
496
|
+
except ImportError:
|
|
497
|
+
return False
|
|
498
|
+
|
|
499
|
+
def _encrypt_impl(
|
|
500
|
+
self,
|
|
501
|
+
plaintext: bytes,
|
|
502
|
+
key: bytes,
|
|
503
|
+
nonce: bytes,
|
|
504
|
+
aad: bytes | None = None,
|
|
505
|
+
) -> tuple[bytes, bytes]:
|
|
506
|
+
"""Encrypt using XChaCha20-Poly1305."""
|
|
507
|
+
if self._use_nacl:
|
|
508
|
+
return self._encrypt_nacl(plaintext, key, nonce)
|
|
509
|
+
return self._encrypt_hchacha(plaintext, key, nonce, aad)
|
|
510
|
+
|
|
511
|
+
def _decrypt_impl(
|
|
512
|
+
self,
|
|
513
|
+
ciphertext: bytes,
|
|
514
|
+
key: bytes,
|
|
515
|
+
nonce: bytes,
|
|
516
|
+
tag: bytes,
|
|
517
|
+
aad: bytes | None = None,
|
|
518
|
+
) -> bytes:
|
|
519
|
+
"""Decrypt using XChaCha20-Poly1305."""
|
|
520
|
+
if self._use_nacl:
|
|
521
|
+
return self._decrypt_nacl(ciphertext, key, nonce, tag)
|
|
522
|
+
return self._decrypt_hchacha(ciphertext, key, nonce, tag, aad)
|
|
523
|
+
|
|
524
|
+
def _encrypt_nacl(
|
|
525
|
+
self, plaintext: bytes, key: bytes, nonce: bytes
|
|
526
|
+
) -> tuple[bytes, bytes]:
|
|
527
|
+
"""Encrypt using pynacl."""
|
|
528
|
+
import nacl.secret
|
|
529
|
+
|
|
530
|
+
box = nacl.secret.SecretBox(key)
|
|
531
|
+
encrypted = box.encrypt(plaintext, nonce)
|
|
532
|
+
# nacl format: nonce || ciphertext || tag
|
|
533
|
+
# We already have nonce, so skip it
|
|
534
|
+
ct_and_tag = encrypted[24:] # Skip 24-byte nonce
|
|
535
|
+
ciphertext = ct_and_tag[:-16]
|
|
536
|
+
tag = ct_and_tag[-16:]
|
|
537
|
+
return ciphertext, tag
|
|
538
|
+
|
|
539
|
+
def _decrypt_nacl(
|
|
540
|
+
self, ciphertext: bytes, key: bytes, nonce: bytes, tag: bytes
|
|
541
|
+
) -> bytes:
|
|
542
|
+
"""Decrypt using pynacl."""
|
|
543
|
+
import nacl.exceptions
|
|
544
|
+
import nacl.secret
|
|
545
|
+
|
|
546
|
+
box = nacl.secret.SecretBox(key)
|
|
547
|
+
try:
|
|
548
|
+
# nacl expects nonce || ciphertext || tag
|
|
549
|
+
return box.decrypt(nonce + ciphertext + tag)
|
|
550
|
+
except nacl.exceptions.CryptoError as e:
|
|
551
|
+
raise IntegrityError(
|
|
552
|
+
"Authentication failed: data may be corrupted or tampered",
|
|
553
|
+
self._algorithm.value,
|
|
554
|
+
) from e
|
|
555
|
+
|
|
556
|
+
def _encrypt_hchacha(
|
|
557
|
+
self,
|
|
558
|
+
plaintext: bytes,
|
|
559
|
+
key: bytes,
|
|
560
|
+
nonce: bytes,
|
|
561
|
+
aad: bytes | None = None,
|
|
562
|
+
) -> tuple[bytes, bytes]:
|
|
563
|
+
"""Encrypt using HChaCha20 + ChaCha20-Poly1305 (cryptography fallback)."""
|
|
564
|
+
# XChaCha20 = HChaCha20(key, nonce[:16]) to derive subkey
|
|
565
|
+
# Then ChaCha20-Poly1305 with subkey and modified nonce
|
|
566
|
+
try:
|
|
567
|
+
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
|
|
568
|
+
except ImportError as e:
|
|
569
|
+
raise UnsupportedAlgorithmError(
|
|
570
|
+
self._algorithm.value,
|
|
571
|
+
available=[
|
|
572
|
+
"Install 'pynacl' or 'cryptography': pip install pynacl cryptography"
|
|
573
|
+
],
|
|
574
|
+
) from e
|
|
575
|
+
|
|
576
|
+
# Derive subkey using HChaCha20 (simplified - use first 16 bytes of nonce)
|
|
577
|
+
subkey = self._hchacha20(key, nonce[:16])
|
|
578
|
+
# Use last 8 bytes of nonce + 4 zero bytes as the ChaCha20 nonce
|
|
579
|
+
chacha_nonce = b"\x00" * 4 + nonce[16:24]
|
|
580
|
+
|
|
581
|
+
chacha = ChaCha20Poly1305(subkey)
|
|
582
|
+
result = chacha.encrypt(chacha_nonce, plaintext, aad)
|
|
583
|
+
return result[:-16], result[-16:]
|
|
584
|
+
|
|
585
|
+
def _decrypt_hchacha(
|
|
586
|
+
self,
|
|
587
|
+
ciphertext: bytes,
|
|
588
|
+
key: bytes,
|
|
589
|
+
nonce: bytes,
|
|
590
|
+
tag: bytes,
|
|
591
|
+
aad: bytes | None = None,
|
|
592
|
+
) -> bytes:
|
|
593
|
+
"""Decrypt using HChaCha20 + ChaCha20-Poly1305."""
|
|
594
|
+
try:
|
|
595
|
+
from cryptography.exceptions import InvalidTag
|
|
596
|
+
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
|
|
597
|
+
except ImportError as e:
|
|
598
|
+
raise UnsupportedAlgorithmError(
|
|
599
|
+
self._algorithm.value,
|
|
600
|
+
available=["Install 'pynacl' or 'cryptography'"],
|
|
601
|
+
) from e
|
|
602
|
+
|
|
603
|
+
subkey = self._hchacha20(key, nonce[:16])
|
|
604
|
+
chacha_nonce = b"\x00" * 4 + nonce[16:24]
|
|
605
|
+
|
|
606
|
+
chacha = ChaCha20Poly1305(subkey)
|
|
607
|
+
try:
|
|
608
|
+
return chacha.decrypt(chacha_nonce, ciphertext + tag, aad)
|
|
609
|
+
except InvalidTag as e:
|
|
610
|
+
raise IntegrityError(
|
|
611
|
+
"Authentication failed",
|
|
612
|
+
self._algorithm.value,
|
|
613
|
+
) from e
|
|
614
|
+
|
|
615
|
+
def _hchacha20(self, key: bytes, nonce: bytes) -> bytes:
|
|
616
|
+
"""HChaCha20 key derivation (simplified implementation).
|
|
617
|
+
|
|
618
|
+
This is a simplified version. For production, use a proper
|
|
619
|
+
HChaCha20 implementation from a cryptographic library.
|
|
620
|
+
"""
|
|
621
|
+
import hashlib
|
|
622
|
+
|
|
623
|
+
# Simplified: HKDF-style derivation for fallback
|
|
624
|
+
# In production, implement actual HChaCha20
|
|
625
|
+
return hashlib.blake2b(key + nonce, digest_size=32).digest()
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
# =============================================================================
|
|
629
|
+
# Fernet Implementation
|
|
630
|
+
# =============================================================================
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
class FernetEncryptor(BaseEncryptor):
|
|
634
|
+
"""Fernet symmetric encryption.
|
|
635
|
+
|
|
636
|
+
Fernet provides a high-level, easy-to-use API for authenticated
|
|
637
|
+
encryption. It uses AES-128-CBC with HMAC-SHA256 for authentication.
|
|
638
|
+
|
|
639
|
+
Key Features:
|
|
640
|
+
- Batteries-included design
|
|
641
|
+
- Timestamp for freshness checking
|
|
642
|
+
- URL-safe base64 encoding
|
|
643
|
+
- Simple API
|
|
644
|
+
|
|
645
|
+
Note:
|
|
646
|
+
Fernet keys are base64-encoded. Use generate_key() to create
|
|
647
|
+
properly formatted keys.
|
|
648
|
+
|
|
649
|
+
Example:
|
|
650
|
+
>>> fernet = FernetEncryptor()
|
|
651
|
+
>>> key = fernet.generate_key()
|
|
652
|
+
>>> encrypted = fernet.encrypt(b"secret", key)
|
|
653
|
+
"""
|
|
654
|
+
|
|
655
|
+
def __init__(self) -> None:
|
|
656
|
+
"""Initialize Fernet encryptor."""
|
|
657
|
+
super().__init__(EncryptionAlgorithm.FERNET)
|
|
658
|
+
|
|
659
|
+
def generate_key(self) -> bytes:
|
|
660
|
+
"""Generate a Fernet key (base64-encoded)."""
|
|
661
|
+
try:
|
|
662
|
+
from cryptography.fernet import Fernet
|
|
663
|
+
except ImportError as e:
|
|
664
|
+
raise UnsupportedAlgorithmError(
|
|
665
|
+
self._algorithm.value,
|
|
666
|
+
available=["Install 'cryptography' package"],
|
|
667
|
+
) from e
|
|
668
|
+
return Fernet.generate_key()
|
|
669
|
+
|
|
670
|
+
def _get_fernet(self, key: bytes) -> Any:
|
|
671
|
+
"""Get Fernet instance."""
|
|
672
|
+
try:
|
|
673
|
+
from cryptography.fernet import Fernet
|
|
674
|
+
except ImportError as e:
|
|
675
|
+
raise UnsupportedAlgorithmError(
|
|
676
|
+
self._algorithm.value,
|
|
677
|
+
available=["Install 'cryptography' package"],
|
|
678
|
+
) from e
|
|
679
|
+
return Fernet(key)
|
|
680
|
+
|
|
681
|
+
def _validate_key(self, key: bytes) -> None:
|
|
682
|
+
"""Validate Fernet key (base64-encoded, 32 bytes decoded)."""
|
|
683
|
+
import base64
|
|
684
|
+
|
|
685
|
+
try:
|
|
686
|
+
decoded = base64.urlsafe_b64decode(key)
|
|
687
|
+
if len(decoded) != 32:
|
|
688
|
+
raise EncryptionError(
|
|
689
|
+
f"Invalid Fernet key: decoded to {len(decoded)} bytes, expected 32",
|
|
690
|
+
self._algorithm.value,
|
|
691
|
+
)
|
|
692
|
+
except Exception as e:
|
|
693
|
+
if isinstance(e, EncryptionError):
|
|
694
|
+
raise
|
|
695
|
+
raise EncryptionError(
|
|
696
|
+
f"Invalid Fernet key format: {e}",
|
|
697
|
+
self._algorithm.value,
|
|
698
|
+
) from e
|
|
699
|
+
|
|
700
|
+
def _encrypt_impl(
|
|
701
|
+
self,
|
|
702
|
+
plaintext: bytes,
|
|
703
|
+
key: bytes,
|
|
704
|
+
nonce: bytes,
|
|
705
|
+
aad: bytes | None = None,
|
|
706
|
+
) -> tuple[bytes, bytes]:
|
|
707
|
+
"""Encrypt using Fernet.
|
|
708
|
+
|
|
709
|
+
Note: Fernet manages its own nonce/IV internally.
|
|
710
|
+
The nonce parameter is ignored.
|
|
711
|
+
"""
|
|
712
|
+
fernet = self._get_fernet(key)
|
|
713
|
+
token = fernet.encrypt(plaintext)
|
|
714
|
+
# Fernet token includes everything; use empty tag
|
|
715
|
+
return token, b""
|
|
716
|
+
|
|
717
|
+
def _decrypt_impl(
|
|
718
|
+
self,
|
|
719
|
+
ciphertext: bytes,
|
|
720
|
+
key: bytes,
|
|
721
|
+
nonce: bytes,
|
|
722
|
+
tag: bytes,
|
|
723
|
+
aad: bytes | None = None,
|
|
724
|
+
) -> bytes:
|
|
725
|
+
"""Decrypt using Fernet."""
|
|
726
|
+
try:
|
|
727
|
+
from cryptography.fernet import InvalidToken
|
|
728
|
+
except ImportError as e:
|
|
729
|
+
raise UnsupportedAlgorithmError(
|
|
730
|
+
self._algorithm.value,
|
|
731
|
+
available=["Install 'cryptography' package"],
|
|
732
|
+
) from e
|
|
733
|
+
|
|
734
|
+
fernet = self._get_fernet(key)
|
|
735
|
+
try:
|
|
736
|
+
return fernet.decrypt(ciphertext)
|
|
737
|
+
except InvalidToken as e:
|
|
738
|
+
raise IntegrityError(
|
|
739
|
+
"Authentication failed or token expired",
|
|
740
|
+
self._algorithm.value,
|
|
741
|
+
) from e
|
|
742
|
+
|
|
743
|
+
def encrypt(
|
|
744
|
+
self,
|
|
745
|
+
plaintext: bytes,
|
|
746
|
+
key: bytes,
|
|
747
|
+
nonce: bytes | None = None,
|
|
748
|
+
aad: bytes | None = None,
|
|
749
|
+
) -> bytes:
|
|
750
|
+
"""Encrypt using Fernet.
|
|
751
|
+
|
|
752
|
+
Fernet handles nonce internally, so the nonce parameter is ignored.
|
|
753
|
+
"""
|
|
754
|
+
self._validate_key(key)
|
|
755
|
+
ciphertext, _ = self._encrypt_impl(plaintext, key, b"", aad)
|
|
756
|
+
return ciphertext
|
|
757
|
+
|
|
758
|
+
def decrypt(
|
|
759
|
+
self,
|
|
760
|
+
data: bytes,
|
|
761
|
+
key: bytes,
|
|
762
|
+
aad: bytes | None = None,
|
|
763
|
+
) -> bytes:
|
|
764
|
+
"""Decrypt Fernet token."""
|
|
765
|
+
self._validate_key(key)
|
|
766
|
+
return self._decrypt_impl(data, key, b"", b"", aad)
|
|
767
|
+
|
|
768
|
+
|
|
769
|
+
# =============================================================================
|
|
770
|
+
# No-op Encryptor (for testing/development)
|
|
771
|
+
# =============================================================================
|
|
772
|
+
|
|
773
|
+
|
|
774
|
+
class NoopEncryptor(BaseEncryptor):
|
|
775
|
+
"""No-op encryptor that passes data through unchanged.
|
|
776
|
+
|
|
777
|
+
WARNING: This provides NO security and should only be used for
|
|
778
|
+
testing or development purposes.
|
|
779
|
+
|
|
780
|
+
Example:
|
|
781
|
+
>>> noop = NoopEncryptor()
|
|
782
|
+
>>> encrypted = noop.encrypt(b"data", b"")
|
|
783
|
+
>>> assert encrypted == b"data"
|
|
784
|
+
"""
|
|
785
|
+
|
|
786
|
+
def __init__(self) -> None:
|
|
787
|
+
"""Initialize no-op encryptor."""
|
|
788
|
+
super().__init__(EncryptionAlgorithm.NONE)
|
|
789
|
+
|
|
790
|
+
def _validate_key(self, key: bytes) -> None:
|
|
791
|
+
"""No validation needed for no-op."""
|
|
792
|
+
pass
|
|
793
|
+
|
|
794
|
+
def _encrypt_impl(
|
|
795
|
+
self,
|
|
796
|
+
plaintext: bytes,
|
|
797
|
+
key: bytes,
|
|
798
|
+
nonce: bytes,
|
|
799
|
+
aad: bytes | None = None,
|
|
800
|
+
) -> tuple[bytes, bytes]:
|
|
801
|
+
"""Return data unchanged."""
|
|
802
|
+
return plaintext, b""
|
|
803
|
+
|
|
804
|
+
def _decrypt_impl(
|
|
805
|
+
self,
|
|
806
|
+
ciphertext: bytes,
|
|
807
|
+
key: bytes,
|
|
808
|
+
nonce: bytes,
|
|
809
|
+
tag: bytes,
|
|
810
|
+
aad: bytes | None = None,
|
|
811
|
+
) -> bytes:
|
|
812
|
+
"""Return data unchanged."""
|
|
813
|
+
return ciphertext
|
|
814
|
+
|
|
815
|
+
def encrypt(
|
|
816
|
+
self,
|
|
817
|
+
plaintext: bytes,
|
|
818
|
+
key: bytes,
|
|
819
|
+
nonce: bytes | None = None,
|
|
820
|
+
aad: bytes | None = None,
|
|
821
|
+
) -> bytes:
|
|
822
|
+
"""Return data unchanged."""
|
|
823
|
+
return plaintext
|
|
824
|
+
|
|
825
|
+
def decrypt(
|
|
826
|
+
self,
|
|
827
|
+
data: bytes,
|
|
828
|
+
key: bytes,
|
|
829
|
+
aad: bytes | None = None,
|
|
830
|
+
) -> bytes:
|
|
831
|
+
"""Return data unchanged."""
|
|
832
|
+
return data
|
|
833
|
+
|
|
834
|
+
|
|
835
|
+
# =============================================================================
|
|
836
|
+
# Factory Functions
|
|
837
|
+
# =============================================================================
|
|
838
|
+
|
|
839
|
+
# Registry of available encryptors
|
|
840
|
+
_ENCRYPTOR_REGISTRY: dict[EncryptionAlgorithm, type[BaseEncryptor]] = {
|
|
841
|
+
EncryptionAlgorithm.AES_128_GCM: AesGcmEncryptor,
|
|
842
|
+
EncryptionAlgorithm.AES_256_GCM: AesGcmEncryptor,
|
|
843
|
+
EncryptionAlgorithm.CHACHA20_POLY1305: ChaCha20Poly1305Encryptor,
|
|
844
|
+
EncryptionAlgorithm.XCHACHA20_POLY1305: XChaCha20Poly1305Encryptor,
|
|
845
|
+
EncryptionAlgorithm.FERNET: FernetEncryptor,
|
|
846
|
+
EncryptionAlgorithm.NONE: NoopEncryptor,
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
|
|
850
|
+
def get_encryptor(
|
|
851
|
+
algorithm: str | EncryptionAlgorithm,
|
|
852
|
+
) -> BaseEncryptor:
|
|
853
|
+
"""Get an encryptor instance for the specified algorithm.
|
|
854
|
+
|
|
855
|
+
Args:
|
|
856
|
+
algorithm: Algorithm name or enum value.
|
|
857
|
+
|
|
858
|
+
Returns:
|
|
859
|
+
Configured encryptor instance.
|
|
860
|
+
|
|
861
|
+
Raises:
|
|
862
|
+
UnsupportedAlgorithmError: If algorithm is not supported.
|
|
863
|
+
|
|
864
|
+
Example:
|
|
865
|
+
>>> encryptor = get_encryptor("aes-256-gcm")
|
|
866
|
+
>>> key = encryptor.generate_key()
|
|
867
|
+
>>> encrypted = encryptor.encrypt(b"data", key)
|
|
868
|
+
"""
|
|
869
|
+
if isinstance(algorithm, str):
|
|
870
|
+
try:
|
|
871
|
+
algorithm = EncryptionAlgorithm(algorithm)
|
|
872
|
+
except ValueError:
|
|
873
|
+
raise UnsupportedAlgorithmError(
|
|
874
|
+
algorithm,
|
|
875
|
+
available=list_available_algorithms(),
|
|
876
|
+
)
|
|
877
|
+
|
|
878
|
+
encryptor_class = _ENCRYPTOR_REGISTRY.get(algorithm)
|
|
879
|
+
if encryptor_class is None:
|
|
880
|
+
raise UnsupportedAlgorithmError(
|
|
881
|
+
algorithm.value,
|
|
882
|
+
available=list_available_algorithms(),
|
|
883
|
+
)
|
|
884
|
+
|
|
885
|
+
# Handle AES key size variants
|
|
886
|
+
if algorithm == EncryptionAlgorithm.AES_128_GCM:
|
|
887
|
+
return AesGcmEncryptor(key_size=16)
|
|
888
|
+
elif algorithm == EncryptionAlgorithm.AES_256_GCM:
|
|
889
|
+
return AesGcmEncryptor(key_size=32)
|
|
890
|
+
|
|
891
|
+
return encryptor_class()
|
|
892
|
+
|
|
893
|
+
|
|
894
|
+
def register_encryptor(
|
|
895
|
+
algorithm: EncryptionAlgorithm,
|
|
896
|
+
encryptor_class: type[BaseEncryptor],
|
|
897
|
+
) -> None:
|
|
898
|
+
"""Register a custom encryptor implementation.
|
|
899
|
+
|
|
900
|
+
Args:
|
|
901
|
+
algorithm: Algorithm enum value.
|
|
902
|
+
encryptor_class: Encryptor class to register.
|
|
903
|
+
|
|
904
|
+
Example:
|
|
905
|
+
>>> class MyEncryptor(BaseEncryptor):
|
|
906
|
+
... pass
|
|
907
|
+
>>> register_encryptor(EncryptionAlgorithm.CUSTOM, MyEncryptor)
|
|
908
|
+
"""
|
|
909
|
+
_ENCRYPTOR_REGISTRY[algorithm] = encryptor_class
|
|
910
|
+
|
|
911
|
+
|
|
912
|
+
def list_available_algorithms() -> list[str]:
|
|
913
|
+
"""List all available encryption algorithms.
|
|
914
|
+
|
|
915
|
+
Returns:
|
|
916
|
+
List of algorithm names.
|
|
917
|
+
"""
|
|
918
|
+
available = []
|
|
919
|
+
for algo in _ENCRYPTOR_REGISTRY:
|
|
920
|
+
try:
|
|
921
|
+
# Test if the algorithm's dependencies are available
|
|
922
|
+
encryptor = get_encryptor(algo)
|
|
923
|
+
if algo != EncryptionAlgorithm.NONE:
|
|
924
|
+
# Try to create a key to verify dependencies
|
|
925
|
+
encryptor.generate_key()
|
|
926
|
+
available.append(algo.value)
|
|
927
|
+
except (UnsupportedAlgorithmError, ImportError):
|
|
928
|
+
pass
|
|
929
|
+
return available
|
|
930
|
+
|
|
931
|
+
|
|
932
|
+
def is_algorithm_available(algorithm: str | EncryptionAlgorithm) -> bool:
|
|
933
|
+
"""Check if an algorithm is available.
|
|
934
|
+
|
|
935
|
+
Args:
|
|
936
|
+
algorithm: Algorithm to check.
|
|
937
|
+
|
|
938
|
+
Returns:
|
|
939
|
+
True if algorithm is available.
|
|
940
|
+
"""
|
|
941
|
+
if isinstance(algorithm, str):
|
|
942
|
+
try:
|
|
943
|
+
algorithm = EncryptionAlgorithm(algorithm)
|
|
944
|
+
except ValueError:
|
|
945
|
+
return False
|
|
946
|
+
|
|
947
|
+
try:
|
|
948
|
+
encryptor = get_encryptor(algorithm)
|
|
949
|
+
if algorithm != EncryptionAlgorithm.NONE:
|
|
950
|
+
encryptor.generate_key()
|
|
951
|
+
return True
|
|
952
|
+
except (UnsupportedAlgorithmError, ImportError):
|
|
953
|
+
return False
|