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,952 @@
|
|
|
1
|
+
"""Base classes, protocols, and types for encryption system.
|
|
2
|
+
|
|
3
|
+
This module defines the core abstractions that all encryption implementations
|
|
4
|
+
must follow. It uses Protocol-based structural typing for maximum flexibility
|
|
5
|
+
and follows the same architectural patterns as the compression module.
|
|
6
|
+
|
|
7
|
+
Security Considerations:
|
|
8
|
+
- All symmetric encryption uses authenticated encryption (AEAD)
|
|
9
|
+
- Nonces are generated cryptographically and never reused
|
|
10
|
+
- Keys are derived using strong KDFs (Argon2, PBKDF2, scrypt)
|
|
11
|
+
- Memory is cleared after use where possible
|
|
12
|
+
- Timing-safe comparisons for authentication tags
|
|
13
|
+
|
|
14
|
+
Example:
|
|
15
|
+
>>> from truthound.stores.encryption.base import (
|
|
16
|
+
... EncryptionAlgorithm,
|
|
17
|
+
... EncryptionConfig,
|
|
18
|
+
... )
|
|
19
|
+
>>>
|
|
20
|
+
>>> config = EncryptionConfig(
|
|
21
|
+
... algorithm=EncryptionAlgorithm.AES_256_GCM,
|
|
22
|
+
... key_derivation=KeyDerivation.ARGON2ID,
|
|
23
|
+
... )
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
from abc import ABC, abstractmethod
|
|
29
|
+
from dataclasses import dataclass, field
|
|
30
|
+
from datetime import datetime, timezone
|
|
31
|
+
from enum import Enum, auto
|
|
32
|
+
from typing import (
|
|
33
|
+
Any,
|
|
34
|
+
BinaryIO,
|
|
35
|
+
Callable,
|
|
36
|
+
Iterator,
|
|
37
|
+
Protocol,
|
|
38
|
+
TypeVar,
|
|
39
|
+
runtime_checkable,
|
|
40
|
+
)
|
|
41
|
+
import hashlib
|
|
42
|
+
import hmac
|
|
43
|
+
import os
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# =============================================================================
|
|
47
|
+
# Exceptions
|
|
48
|
+
# =============================================================================
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class EncryptionError(Exception):
|
|
52
|
+
"""Base exception for encryption errors."""
|
|
53
|
+
|
|
54
|
+
def __init__(self, message: str, algorithm: str | None = None) -> None:
|
|
55
|
+
self.algorithm = algorithm
|
|
56
|
+
super().__init__(f"[{algorithm}] {message}" if algorithm else message)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class DecryptionError(EncryptionError):
|
|
60
|
+
"""Error during decryption (authentication failure, corrupted data)."""
|
|
61
|
+
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class KeyError_(EncryptionError):
|
|
66
|
+
"""Error related to encryption keys (invalid, expired, not found)."""
|
|
67
|
+
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class KeyDerivationError(EncryptionError):
|
|
72
|
+
"""Error during key derivation."""
|
|
73
|
+
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class UnsupportedAlgorithmError(EncryptionError):
|
|
78
|
+
"""Requested encryption algorithm is not available."""
|
|
79
|
+
|
|
80
|
+
def __init__(self, algorithm: str, available: list[str] | None = None) -> None:
|
|
81
|
+
self.available = available or []
|
|
82
|
+
msg = f"Algorithm '{algorithm}' is not supported"
|
|
83
|
+
if self.available:
|
|
84
|
+
msg += f". Available: {', '.join(self.available)}"
|
|
85
|
+
super().__init__(msg, algorithm)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class EncryptionConfigError(EncryptionError):
|
|
89
|
+
"""Invalid encryption configuration."""
|
|
90
|
+
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class NonceReuseError(EncryptionError):
|
|
95
|
+
"""Attempted nonce reuse detected (critical security error)."""
|
|
96
|
+
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class KeyExpiredError(KeyError_):
|
|
101
|
+
"""Encryption key has expired."""
|
|
102
|
+
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class IntegrityError(DecryptionError):
|
|
107
|
+
"""Data integrity verification failed."""
|
|
108
|
+
|
|
109
|
+
pass
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# =============================================================================
|
|
113
|
+
# Enums
|
|
114
|
+
# =============================================================================
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class EncryptionAlgorithm(str, Enum):
|
|
118
|
+
"""Supported encryption algorithms.
|
|
119
|
+
|
|
120
|
+
All algorithms use authenticated encryption (AEAD) to provide both
|
|
121
|
+
confidentiality and integrity protection.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
# AES-GCM variants (recommended for most use cases)
|
|
125
|
+
AES_128_GCM = "aes-128-gcm"
|
|
126
|
+
AES_256_GCM = "aes-256-gcm"
|
|
127
|
+
|
|
128
|
+
# ChaCha20-Poly1305 (better for software implementations)
|
|
129
|
+
CHACHA20_POLY1305 = "chacha20-poly1305"
|
|
130
|
+
|
|
131
|
+
# Fernet (high-level, batteries-included)
|
|
132
|
+
FERNET = "fernet"
|
|
133
|
+
|
|
134
|
+
# XChaCha20-Poly1305 (extended nonce for random generation)
|
|
135
|
+
XCHACHA20_POLY1305 = "xchacha20-poly1305"
|
|
136
|
+
|
|
137
|
+
# No encryption (for testing/development)
|
|
138
|
+
NONE = "none"
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
def key_size(self) -> int:
|
|
142
|
+
"""Get key size in bytes."""
|
|
143
|
+
key_sizes = {
|
|
144
|
+
self.AES_128_GCM: 16,
|
|
145
|
+
self.AES_256_GCM: 32,
|
|
146
|
+
self.CHACHA20_POLY1305: 32,
|
|
147
|
+
self.XCHACHA20_POLY1305: 32,
|
|
148
|
+
self.FERNET: 32,
|
|
149
|
+
self.NONE: 0,
|
|
150
|
+
}
|
|
151
|
+
return key_sizes.get(self, 32)
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def nonce_size(self) -> int:
|
|
155
|
+
"""Get nonce/IV size in bytes."""
|
|
156
|
+
nonce_sizes = {
|
|
157
|
+
self.AES_128_GCM: 12,
|
|
158
|
+
self.AES_256_GCM: 12,
|
|
159
|
+
self.CHACHA20_POLY1305: 12,
|
|
160
|
+
self.XCHACHA20_POLY1305: 24,
|
|
161
|
+
self.FERNET: 16,
|
|
162
|
+
self.NONE: 0,
|
|
163
|
+
}
|
|
164
|
+
return nonce_sizes.get(self, 12)
|
|
165
|
+
|
|
166
|
+
@property
|
|
167
|
+
def tag_size(self) -> int:
|
|
168
|
+
"""Get authentication tag size in bytes."""
|
|
169
|
+
tag_sizes = {
|
|
170
|
+
self.AES_128_GCM: 16,
|
|
171
|
+
self.AES_256_GCM: 16,
|
|
172
|
+
self.CHACHA20_POLY1305: 16,
|
|
173
|
+
self.XCHACHA20_POLY1305: 16,
|
|
174
|
+
self.FERNET: 32, # HMAC-SHA256
|
|
175
|
+
self.NONE: 0,
|
|
176
|
+
}
|
|
177
|
+
return tag_sizes.get(self, 16)
|
|
178
|
+
|
|
179
|
+
@property
|
|
180
|
+
def is_aead(self) -> bool:
|
|
181
|
+
"""Check if algorithm provides authenticated encryption."""
|
|
182
|
+
return self != self.NONE
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class KeyDerivation(str, Enum):
|
|
186
|
+
"""Key derivation functions for password-based encryption."""
|
|
187
|
+
|
|
188
|
+
# Argon2 variants (recommended)
|
|
189
|
+
ARGON2ID = "argon2id"
|
|
190
|
+
ARGON2I = "argon2i"
|
|
191
|
+
ARGON2D = "argon2d"
|
|
192
|
+
|
|
193
|
+
# PBKDF2 (widely compatible)
|
|
194
|
+
PBKDF2_SHA256 = "pbkdf2-sha256"
|
|
195
|
+
PBKDF2_SHA512 = "pbkdf2-sha512"
|
|
196
|
+
|
|
197
|
+
# scrypt (memory-hard)
|
|
198
|
+
SCRYPT = "scrypt"
|
|
199
|
+
|
|
200
|
+
# HKDF (for key expansion, not password derivation)
|
|
201
|
+
HKDF_SHA256 = "hkdf-sha256"
|
|
202
|
+
HKDF_SHA512 = "hkdf-sha512"
|
|
203
|
+
|
|
204
|
+
# Direct key (no derivation)
|
|
205
|
+
NONE = "none"
|
|
206
|
+
|
|
207
|
+
@property
|
|
208
|
+
def is_password_based(self) -> bool:
|
|
209
|
+
"""Check if this KDF is suitable for password-based key derivation."""
|
|
210
|
+
return self in (
|
|
211
|
+
self.ARGON2ID,
|
|
212
|
+
self.ARGON2I,
|
|
213
|
+
self.ARGON2D,
|
|
214
|
+
self.PBKDF2_SHA256,
|
|
215
|
+
self.PBKDF2_SHA512,
|
|
216
|
+
self.SCRYPT,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
@property
|
|
220
|
+
def default_iterations(self) -> int:
|
|
221
|
+
"""Get default iteration count for this KDF."""
|
|
222
|
+
iterations = {
|
|
223
|
+
self.PBKDF2_SHA256: 600_000,
|
|
224
|
+
self.PBKDF2_SHA512: 210_000,
|
|
225
|
+
self.ARGON2ID: 3,
|
|
226
|
+
self.ARGON2I: 4,
|
|
227
|
+
self.ARGON2D: 3,
|
|
228
|
+
self.SCRYPT: 1,
|
|
229
|
+
}
|
|
230
|
+
return iterations.get(self, 1)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class KeyType(str, Enum):
|
|
234
|
+
"""Types of encryption keys."""
|
|
235
|
+
|
|
236
|
+
# Symmetric keys
|
|
237
|
+
SYMMETRIC = "symmetric"
|
|
238
|
+
DATA_ENCRYPTION_KEY = "dek"
|
|
239
|
+
KEY_ENCRYPTION_KEY = "kek"
|
|
240
|
+
|
|
241
|
+
# For envelope encryption
|
|
242
|
+
MASTER_KEY = "master"
|
|
243
|
+
DERIVED_KEY = "derived"
|
|
244
|
+
|
|
245
|
+
# Session keys
|
|
246
|
+
SESSION = "session"
|
|
247
|
+
EPHEMERAL = "ephemeral"
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
class EncryptionMode(Enum):
|
|
251
|
+
"""Encryption operation mode."""
|
|
252
|
+
|
|
253
|
+
ENCRYPT = auto()
|
|
254
|
+
DECRYPT = auto()
|
|
255
|
+
BOTH = auto()
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
# =============================================================================
|
|
259
|
+
# Data Classes
|
|
260
|
+
# =============================================================================
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
@dataclass
|
|
264
|
+
class KeyDerivationConfig:
|
|
265
|
+
"""Configuration for key derivation.
|
|
266
|
+
|
|
267
|
+
Attributes:
|
|
268
|
+
kdf: Key derivation function to use.
|
|
269
|
+
salt_size: Size of salt in bytes.
|
|
270
|
+
iterations: Number of iterations (for PBKDF2).
|
|
271
|
+
memory_cost: Memory cost in KiB (for Argon2).
|
|
272
|
+
parallelism: Degree of parallelism (for Argon2).
|
|
273
|
+
time_cost: Time cost / iterations (for Argon2).
|
|
274
|
+
n: CPU/memory cost parameter (for scrypt).
|
|
275
|
+
r: Block size parameter (for scrypt).
|
|
276
|
+
p: Parallelization parameter (for scrypt).
|
|
277
|
+
"""
|
|
278
|
+
|
|
279
|
+
kdf: KeyDerivation = KeyDerivation.ARGON2ID
|
|
280
|
+
salt_size: int = 16
|
|
281
|
+
iterations: int | None = None # Uses KDF default if None
|
|
282
|
+
memory_cost: int = 65536 # 64 MiB for Argon2
|
|
283
|
+
parallelism: int = 4
|
|
284
|
+
time_cost: int = 3
|
|
285
|
+
n: int = 2**14 # scrypt CPU/memory cost
|
|
286
|
+
r: int = 8 # scrypt block size
|
|
287
|
+
p: int = 1 # scrypt parallelism
|
|
288
|
+
|
|
289
|
+
def get_iterations(self) -> int:
|
|
290
|
+
"""Get effective iterations count."""
|
|
291
|
+
return self.iterations or self.kdf.default_iterations
|
|
292
|
+
|
|
293
|
+
def validate(self) -> None:
|
|
294
|
+
"""Validate configuration."""
|
|
295
|
+
if self.salt_size < 8:
|
|
296
|
+
raise EncryptionConfigError("salt_size must be at least 8 bytes")
|
|
297
|
+
if self.memory_cost < 8:
|
|
298
|
+
raise EncryptionConfigError("memory_cost must be at least 8 KiB")
|
|
299
|
+
if self.parallelism < 1:
|
|
300
|
+
raise EncryptionConfigError("parallelism must be at least 1")
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
@dataclass
|
|
304
|
+
class EncryptionConfig:
|
|
305
|
+
"""Configuration for encryption operations.
|
|
306
|
+
|
|
307
|
+
Attributes:
|
|
308
|
+
algorithm: Encryption algorithm to use.
|
|
309
|
+
key_derivation: Key derivation configuration.
|
|
310
|
+
chunk_size: Size of chunks for streaming encryption.
|
|
311
|
+
include_header: Include metadata header in encrypted output.
|
|
312
|
+
verify_on_decrypt: Verify integrity on decryption (always true for AEAD).
|
|
313
|
+
associated_data: Additional authenticated data (AAD).
|
|
314
|
+
key_id: Identifier for the encryption key (for key management).
|
|
315
|
+
"""
|
|
316
|
+
|
|
317
|
+
algorithm: EncryptionAlgorithm = EncryptionAlgorithm.AES_256_GCM
|
|
318
|
+
key_derivation: KeyDerivationConfig = field(default_factory=KeyDerivationConfig)
|
|
319
|
+
chunk_size: int = 64 * 1024 # 64KB
|
|
320
|
+
include_header: bool = True
|
|
321
|
+
verify_on_decrypt: bool = True
|
|
322
|
+
associated_data: bytes | None = None
|
|
323
|
+
key_id: str | None = None
|
|
324
|
+
|
|
325
|
+
def validate(self) -> None:
|
|
326
|
+
"""Validate configuration."""
|
|
327
|
+
if self.chunk_size <= 0:
|
|
328
|
+
raise EncryptionConfigError("chunk_size must be positive")
|
|
329
|
+
self.key_derivation.validate()
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
@dataclass
|
|
333
|
+
class EncryptionKey:
|
|
334
|
+
"""Represents an encryption key with metadata.
|
|
335
|
+
|
|
336
|
+
Attributes:
|
|
337
|
+
key_id: Unique identifier for this key.
|
|
338
|
+
key_material: The actual key bytes (should be cleared after use).
|
|
339
|
+
algorithm: Algorithm this key is for.
|
|
340
|
+
key_type: Type of key.
|
|
341
|
+
created_at: When the key was created.
|
|
342
|
+
expires_at: When the key expires (None = never).
|
|
343
|
+
version: Key version for rotation tracking.
|
|
344
|
+
metadata: Additional key metadata.
|
|
345
|
+
"""
|
|
346
|
+
|
|
347
|
+
key_id: str
|
|
348
|
+
key_material: bytes
|
|
349
|
+
algorithm: EncryptionAlgorithm
|
|
350
|
+
key_type: KeyType = KeyType.DATA_ENCRYPTION_KEY
|
|
351
|
+
created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
352
|
+
expires_at: datetime | None = None
|
|
353
|
+
version: int = 1
|
|
354
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
355
|
+
|
|
356
|
+
def __post_init__(self) -> None:
|
|
357
|
+
"""Validate key after creation."""
|
|
358
|
+
expected_size = self.algorithm.key_size
|
|
359
|
+
if expected_size > 0 and len(self.key_material) != expected_size:
|
|
360
|
+
raise KeyError_(
|
|
361
|
+
f"Key size mismatch: expected {expected_size} bytes, "
|
|
362
|
+
f"got {len(self.key_material)} bytes",
|
|
363
|
+
self.algorithm.value,
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
@property
|
|
367
|
+
def is_expired(self) -> bool:
|
|
368
|
+
"""Check if key has expired."""
|
|
369
|
+
if self.expires_at is None:
|
|
370
|
+
return False
|
|
371
|
+
return datetime.now(timezone.utc) > self.expires_at
|
|
372
|
+
|
|
373
|
+
def validate(self) -> None:
|
|
374
|
+
"""Validate key is usable."""
|
|
375
|
+
if self.is_expired:
|
|
376
|
+
raise KeyExpiredError(
|
|
377
|
+
f"Key '{self.key_id}' expired at {self.expires_at}",
|
|
378
|
+
self.algorithm.value,
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
def clear(self) -> None:
|
|
382
|
+
"""Clear key material from memory (best-effort)."""
|
|
383
|
+
if self.key_material:
|
|
384
|
+
# Overwrite with zeros (best-effort, Python may have copies)
|
|
385
|
+
self.key_material = b"\x00" * len(self.key_material)
|
|
386
|
+
|
|
387
|
+
def to_dict(self, include_key: bool = False) -> dict[str, Any]:
|
|
388
|
+
"""Convert to dictionary (optionally excluding key material)."""
|
|
389
|
+
result = {
|
|
390
|
+
"key_id": self.key_id,
|
|
391
|
+
"algorithm": self.algorithm.value,
|
|
392
|
+
"key_type": self.key_type.value,
|
|
393
|
+
"created_at": self.created_at.isoformat(),
|
|
394
|
+
"expires_at": self.expires_at.isoformat() if self.expires_at else None,
|
|
395
|
+
"version": self.version,
|
|
396
|
+
"metadata": self.metadata,
|
|
397
|
+
}
|
|
398
|
+
if include_key:
|
|
399
|
+
import base64
|
|
400
|
+
|
|
401
|
+
result["key_material"] = base64.b64encode(self.key_material).decode()
|
|
402
|
+
return result
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
@dataclass
|
|
406
|
+
class EncryptionMetrics:
|
|
407
|
+
"""Metrics from an encryption operation.
|
|
408
|
+
|
|
409
|
+
Attributes:
|
|
410
|
+
plaintext_size: Size of plaintext data in bytes.
|
|
411
|
+
ciphertext_size: Size of ciphertext data in bytes.
|
|
412
|
+
overhead_bytes: Encryption overhead (nonce + tag).
|
|
413
|
+
encryption_time_ms: Time taken to encrypt in milliseconds.
|
|
414
|
+
decryption_time_ms: Time taken to decrypt in milliseconds.
|
|
415
|
+
algorithm: Algorithm used.
|
|
416
|
+
key_derivation_time_ms: Time for key derivation (if applicable).
|
|
417
|
+
"""
|
|
418
|
+
|
|
419
|
+
plaintext_size: int = 0
|
|
420
|
+
ciphertext_size: int = 0
|
|
421
|
+
overhead_bytes: int = 0
|
|
422
|
+
encryption_time_ms: float = 0.0
|
|
423
|
+
decryption_time_ms: float = 0.0
|
|
424
|
+
algorithm: EncryptionAlgorithm = EncryptionAlgorithm.NONE
|
|
425
|
+
key_derivation_time_ms: float = 0.0
|
|
426
|
+
|
|
427
|
+
@property
|
|
428
|
+
def overhead_percent(self) -> float:
|
|
429
|
+
"""Calculate overhead percentage."""
|
|
430
|
+
if self.plaintext_size == 0:
|
|
431
|
+
return 0.0
|
|
432
|
+
return (self.overhead_bytes / self.plaintext_size) * 100
|
|
433
|
+
|
|
434
|
+
@property
|
|
435
|
+
def throughput_encrypt_mbps(self) -> float:
|
|
436
|
+
"""Calculate encryption throughput in MB/s."""
|
|
437
|
+
if self.encryption_time_ms == 0:
|
|
438
|
+
return 0.0
|
|
439
|
+
return (self.plaintext_size / 1024 / 1024) / (self.encryption_time_ms / 1000)
|
|
440
|
+
|
|
441
|
+
@property
|
|
442
|
+
def throughput_decrypt_mbps(self) -> float:
|
|
443
|
+
"""Calculate decryption throughput in MB/s."""
|
|
444
|
+
if self.decryption_time_ms == 0:
|
|
445
|
+
return 0.0
|
|
446
|
+
return (self.plaintext_size / 1024 / 1024) / (self.decryption_time_ms / 1000)
|
|
447
|
+
|
|
448
|
+
def to_dict(self) -> dict[str, Any]:
|
|
449
|
+
"""Convert to dictionary."""
|
|
450
|
+
return {
|
|
451
|
+
"plaintext_size": self.plaintext_size,
|
|
452
|
+
"ciphertext_size": self.ciphertext_size,
|
|
453
|
+
"overhead_bytes": self.overhead_bytes,
|
|
454
|
+
"overhead_percent": round(self.overhead_percent, 2),
|
|
455
|
+
"encryption_time_ms": round(self.encryption_time_ms, 2),
|
|
456
|
+
"decryption_time_ms": round(self.decryption_time_ms, 2),
|
|
457
|
+
"throughput_encrypt_mbps": round(self.throughput_encrypt_mbps, 2),
|
|
458
|
+
"throughput_decrypt_mbps": round(self.throughput_decrypt_mbps, 2),
|
|
459
|
+
"algorithm": self.algorithm.value,
|
|
460
|
+
"key_derivation_time_ms": round(self.key_derivation_time_ms, 2),
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
@dataclass
|
|
465
|
+
class EncryptionResult:
|
|
466
|
+
"""Result of an encryption operation.
|
|
467
|
+
|
|
468
|
+
Attributes:
|
|
469
|
+
ciphertext: Encrypted data bytes.
|
|
470
|
+
nonce: Nonce/IV used for encryption.
|
|
471
|
+
tag: Authentication tag.
|
|
472
|
+
metrics: Encryption metrics.
|
|
473
|
+
header: Optional metadata header.
|
|
474
|
+
key_id: ID of key used (for key management).
|
|
475
|
+
"""
|
|
476
|
+
|
|
477
|
+
ciphertext: bytes
|
|
478
|
+
nonce: bytes
|
|
479
|
+
tag: bytes
|
|
480
|
+
metrics: EncryptionMetrics
|
|
481
|
+
header: dict[str, Any] = field(default_factory=dict)
|
|
482
|
+
key_id: str | None = None
|
|
483
|
+
|
|
484
|
+
def to_bytes(self, include_header: bool = False) -> bytes:
|
|
485
|
+
"""Serialize to bytes format.
|
|
486
|
+
|
|
487
|
+
Format: [header_len (4 bytes)][header_json][nonce][ciphertext][tag]
|
|
488
|
+
Or without header: [nonce][ciphertext][tag]
|
|
489
|
+
"""
|
|
490
|
+
import json
|
|
491
|
+
|
|
492
|
+
if include_header and self.header:
|
|
493
|
+
header_bytes = json.dumps(self.header).encode()
|
|
494
|
+
header_len = len(header_bytes).to_bytes(4, "big")
|
|
495
|
+
return header_len + header_bytes + self.nonce + self.ciphertext + self.tag
|
|
496
|
+
return self.nonce + self.ciphertext + self.tag
|
|
497
|
+
|
|
498
|
+
@classmethod
|
|
499
|
+
def from_bytes(
|
|
500
|
+
cls,
|
|
501
|
+
data: bytes,
|
|
502
|
+
algorithm: EncryptionAlgorithm,
|
|
503
|
+
has_header: bool = False,
|
|
504
|
+
) -> "EncryptionResult":
|
|
505
|
+
"""Deserialize from bytes format."""
|
|
506
|
+
import json
|
|
507
|
+
|
|
508
|
+
header: dict[str, Any] = {}
|
|
509
|
+
offset = 0
|
|
510
|
+
|
|
511
|
+
if has_header:
|
|
512
|
+
header_len = int.from_bytes(data[:4], "big")
|
|
513
|
+
offset = 4
|
|
514
|
+
header = json.loads(data[offset : offset + header_len].decode())
|
|
515
|
+
offset += header_len
|
|
516
|
+
|
|
517
|
+
nonce_size = algorithm.nonce_size
|
|
518
|
+
tag_size = algorithm.tag_size
|
|
519
|
+
|
|
520
|
+
nonce = data[offset : offset + nonce_size]
|
|
521
|
+
offset += nonce_size
|
|
522
|
+
|
|
523
|
+
ciphertext = data[offset : -tag_size] if tag_size > 0 else data[offset:]
|
|
524
|
+
tag = data[-tag_size:] if tag_size > 0 else b""
|
|
525
|
+
|
|
526
|
+
return cls(
|
|
527
|
+
ciphertext=ciphertext,
|
|
528
|
+
nonce=nonce,
|
|
529
|
+
tag=tag,
|
|
530
|
+
metrics=EncryptionMetrics(algorithm=algorithm),
|
|
531
|
+
header=header,
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
@dataclass
|
|
536
|
+
class EncryptionStats:
|
|
537
|
+
"""Aggregated encryption statistics across multiple operations.
|
|
538
|
+
|
|
539
|
+
Attributes:
|
|
540
|
+
total_operations: Number of encryption operations.
|
|
541
|
+
total_plaintext_bytes: Total bytes encrypted.
|
|
542
|
+
total_ciphertext_bytes: Total bytes of ciphertext.
|
|
543
|
+
total_encryption_time_ms: Total encryption time.
|
|
544
|
+
total_decryption_time_ms: Total decryption time.
|
|
545
|
+
algorithm_usage: Count of each algorithm used.
|
|
546
|
+
errors: Number of errors encountered.
|
|
547
|
+
"""
|
|
548
|
+
|
|
549
|
+
total_operations: int = 0
|
|
550
|
+
total_plaintext_bytes: int = 0
|
|
551
|
+
total_ciphertext_bytes: int = 0
|
|
552
|
+
total_encryption_time_ms: float = 0.0
|
|
553
|
+
total_decryption_time_ms: float = 0.0
|
|
554
|
+
algorithm_usage: dict[str, int] = field(default_factory=dict)
|
|
555
|
+
errors: int = 0
|
|
556
|
+
|
|
557
|
+
def record(self, metrics: EncryptionMetrics) -> None:
|
|
558
|
+
"""Record metrics from an encryption operation."""
|
|
559
|
+
self.total_operations += 1
|
|
560
|
+
self.total_plaintext_bytes += metrics.plaintext_size
|
|
561
|
+
self.total_ciphertext_bytes += metrics.ciphertext_size
|
|
562
|
+
self.total_encryption_time_ms += metrics.encryption_time_ms
|
|
563
|
+
self.total_decryption_time_ms += metrics.decryption_time_ms
|
|
564
|
+
|
|
565
|
+
algo = metrics.algorithm.value
|
|
566
|
+
self.algorithm_usage[algo] = self.algorithm_usage.get(algo, 0) + 1
|
|
567
|
+
|
|
568
|
+
@property
|
|
569
|
+
def average_overhead(self) -> float:
|
|
570
|
+
"""Calculate average overhead percentage."""
|
|
571
|
+
if self.total_plaintext_bytes == 0:
|
|
572
|
+
return 0.0
|
|
573
|
+
overhead = self.total_ciphertext_bytes - self.total_plaintext_bytes
|
|
574
|
+
return (overhead / self.total_plaintext_bytes) * 100
|
|
575
|
+
|
|
576
|
+
def to_dict(self) -> dict[str, Any]:
|
|
577
|
+
"""Convert to dictionary."""
|
|
578
|
+
return {
|
|
579
|
+
"total_operations": self.total_operations,
|
|
580
|
+
"total_plaintext_bytes": self.total_plaintext_bytes,
|
|
581
|
+
"total_ciphertext_bytes": self.total_ciphertext_bytes,
|
|
582
|
+
"average_overhead_percent": round(self.average_overhead, 2),
|
|
583
|
+
"total_encryption_time_ms": round(self.total_encryption_time_ms, 2),
|
|
584
|
+
"total_decryption_time_ms": round(self.total_decryption_time_ms, 2),
|
|
585
|
+
"algorithm_usage": self.algorithm_usage,
|
|
586
|
+
"errors": self.errors,
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
# =============================================================================
|
|
591
|
+
# Header Format
|
|
592
|
+
# =============================================================================
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
@dataclass
|
|
596
|
+
class EncryptionHeader:
|
|
597
|
+
"""Encryption metadata header.
|
|
598
|
+
|
|
599
|
+
This header is prepended to encrypted data to enable self-describing
|
|
600
|
+
encryption format. The header itself is authenticated but not encrypted.
|
|
601
|
+
|
|
602
|
+
Attributes:
|
|
603
|
+
version: Header format version.
|
|
604
|
+
algorithm: Encryption algorithm used.
|
|
605
|
+
kdf: Key derivation function used.
|
|
606
|
+
salt: Salt used for key derivation (if applicable).
|
|
607
|
+
key_id: ID of the key used.
|
|
608
|
+
created_at: Timestamp of encryption.
|
|
609
|
+
nonce_size: Size of nonce in bytes.
|
|
610
|
+
tag_size: Size of authentication tag in bytes.
|
|
611
|
+
extra: Additional metadata.
|
|
612
|
+
"""
|
|
613
|
+
|
|
614
|
+
version: int = 1
|
|
615
|
+
algorithm: EncryptionAlgorithm = EncryptionAlgorithm.AES_256_GCM
|
|
616
|
+
kdf: KeyDerivation = KeyDerivation.NONE
|
|
617
|
+
salt: bytes = b""
|
|
618
|
+
key_id: str | None = None
|
|
619
|
+
created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
620
|
+
nonce_size: int = 12
|
|
621
|
+
tag_size: int = 16
|
|
622
|
+
extra: dict[str, Any] = field(default_factory=dict)
|
|
623
|
+
|
|
624
|
+
def to_bytes(self) -> bytes:
|
|
625
|
+
"""Serialize header to bytes."""
|
|
626
|
+
import json
|
|
627
|
+
|
|
628
|
+
data = {
|
|
629
|
+
"v": self.version,
|
|
630
|
+
"alg": self.algorithm.value,
|
|
631
|
+
"kdf": self.kdf.value,
|
|
632
|
+
"salt": self.salt.hex() if self.salt else "",
|
|
633
|
+
"kid": self.key_id,
|
|
634
|
+
"ts": self.created_at.isoformat(),
|
|
635
|
+
"ns": self.nonce_size,
|
|
636
|
+
"ts_": self.tag_size,
|
|
637
|
+
"ext": self.extra,
|
|
638
|
+
}
|
|
639
|
+
header_json = json.dumps(data, separators=(",", ":")).encode()
|
|
640
|
+
return len(header_json).to_bytes(4, "big") + header_json
|
|
641
|
+
|
|
642
|
+
@classmethod
|
|
643
|
+
def from_bytes(cls, data: bytes) -> tuple["EncryptionHeader", int]:
|
|
644
|
+
"""Deserialize header from bytes.
|
|
645
|
+
|
|
646
|
+
Returns:
|
|
647
|
+
Tuple of (header, bytes_consumed).
|
|
648
|
+
"""
|
|
649
|
+
import json
|
|
650
|
+
|
|
651
|
+
header_len = int.from_bytes(data[:4], "big")
|
|
652
|
+
header_json = data[4 : 4 + header_len]
|
|
653
|
+
d = json.loads(header_json.decode())
|
|
654
|
+
|
|
655
|
+
header = cls(
|
|
656
|
+
version=d["v"],
|
|
657
|
+
algorithm=EncryptionAlgorithm(d["alg"]),
|
|
658
|
+
kdf=KeyDerivation(d["kdf"]),
|
|
659
|
+
salt=bytes.fromhex(d["salt"]) if d["salt"] else b"",
|
|
660
|
+
key_id=d.get("kid"),
|
|
661
|
+
created_at=datetime.fromisoformat(d["ts"]),
|
|
662
|
+
nonce_size=d["ns"],
|
|
663
|
+
tag_size=d["ts_"],
|
|
664
|
+
extra=d.get("ext", {}),
|
|
665
|
+
)
|
|
666
|
+
return header, 4 + header_len
|
|
667
|
+
|
|
668
|
+
|
|
669
|
+
# =============================================================================
|
|
670
|
+
# Protocols
|
|
671
|
+
# =============================================================================
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
@runtime_checkable
|
|
675
|
+
class Encryptor(Protocol):
|
|
676
|
+
"""Protocol for encryption implementations."""
|
|
677
|
+
|
|
678
|
+
@property
|
|
679
|
+
def algorithm(self) -> EncryptionAlgorithm:
|
|
680
|
+
"""Get the encryption algorithm."""
|
|
681
|
+
...
|
|
682
|
+
|
|
683
|
+
def encrypt(self, plaintext: bytes, key: bytes) -> bytes:
|
|
684
|
+
"""Encrypt plaintext data.
|
|
685
|
+
|
|
686
|
+
Args:
|
|
687
|
+
plaintext: Data to encrypt.
|
|
688
|
+
key: Encryption key.
|
|
689
|
+
|
|
690
|
+
Returns:
|
|
691
|
+
Encrypted data (nonce + ciphertext + tag).
|
|
692
|
+
"""
|
|
693
|
+
...
|
|
694
|
+
|
|
695
|
+
def encrypt_with_metrics(
|
|
696
|
+
self,
|
|
697
|
+
plaintext: bytes,
|
|
698
|
+
key: bytes,
|
|
699
|
+
aad: bytes | None = None,
|
|
700
|
+
) -> EncryptionResult:
|
|
701
|
+
"""Encrypt data and return detailed result with metrics.
|
|
702
|
+
|
|
703
|
+
Args:
|
|
704
|
+
plaintext: Data to encrypt.
|
|
705
|
+
key: Encryption key.
|
|
706
|
+
aad: Additional authenticated data.
|
|
707
|
+
|
|
708
|
+
Returns:
|
|
709
|
+
Encryption result with metrics.
|
|
710
|
+
"""
|
|
711
|
+
...
|
|
712
|
+
|
|
713
|
+
|
|
714
|
+
@runtime_checkable
|
|
715
|
+
class Decryptor(Protocol):
|
|
716
|
+
"""Protocol for decryption implementations."""
|
|
717
|
+
|
|
718
|
+
@property
|
|
719
|
+
def algorithm(self) -> EncryptionAlgorithm:
|
|
720
|
+
"""Get the encryption algorithm."""
|
|
721
|
+
...
|
|
722
|
+
|
|
723
|
+
def decrypt(self, ciphertext: bytes, key: bytes) -> bytes:
|
|
724
|
+
"""Decrypt ciphertext data.
|
|
725
|
+
|
|
726
|
+
Args:
|
|
727
|
+
ciphertext: Data to decrypt (nonce + ciphertext + tag).
|
|
728
|
+
key: Decryption key.
|
|
729
|
+
|
|
730
|
+
Returns:
|
|
731
|
+
Decrypted plaintext.
|
|
732
|
+
|
|
733
|
+
Raises:
|
|
734
|
+
DecryptionError: If decryption or authentication fails.
|
|
735
|
+
"""
|
|
736
|
+
...
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
@runtime_checkable
|
|
740
|
+
class KeyDeriver(Protocol):
|
|
741
|
+
"""Protocol for key derivation implementations."""
|
|
742
|
+
|
|
743
|
+
def derive_key(
|
|
744
|
+
self,
|
|
745
|
+
password: str | bytes,
|
|
746
|
+
salt: bytes,
|
|
747
|
+
key_size: int,
|
|
748
|
+
) -> bytes:
|
|
749
|
+
"""Derive a key from password/passphrase.
|
|
750
|
+
|
|
751
|
+
Args:
|
|
752
|
+
password: Password or passphrase.
|
|
753
|
+
salt: Cryptographic salt.
|
|
754
|
+
key_size: Desired key size in bytes.
|
|
755
|
+
|
|
756
|
+
Returns:
|
|
757
|
+
Derived key.
|
|
758
|
+
"""
|
|
759
|
+
...
|
|
760
|
+
|
|
761
|
+
|
|
762
|
+
@runtime_checkable
|
|
763
|
+
class StreamingEncryptor(Protocol):
|
|
764
|
+
"""Protocol for streaming encryption."""
|
|
765
|
+
|
|
766
|
+
def write(self, data: bytes) -> bytes:
|
|
767
|
+
"""Write data to encryption stream.
|
|
768
|
+
|
|
769
|
+
Args:
|
|
770
|
+
data: Plaintext chunk to encrypt.
|
|
771
|
+
|
|
772
|
+
Returns:
|
|
773
|
+
Encrypted data (may be buffered).
|
|
774
|
+
"""
|
|
775
|
+
...
|
|
776
|
+
|
|
777
|
+
def flush(self) -> bytes:
|
|
778
|
+
"""Flush buffered data.
|
|
779
|
+
|
|
780
|
+
Returns:
|
|
781
|
+
Encrypted data from buffer.
|
|
782
|
+
"""
|
|
783
|
+
...
|
|
784
|
+
|
|
785
|
+
def finalize(self) -> bytes:
|
|
786
|
+
"""Finalize encryption and get remaining data with tag.
|
|
787
|
+
|
|
788
|
+
Returns:
|
|
789
|
+
Final encrypted data including authentication tag.
|
|
790
|
+
"""
|
|
791
|
+
...
|
|
792
|
+
|
|
793
|
+
|
|
794
|
+
@runtime_checkable
|
|
795
|
+
class StreamingDecryptor(Protocol):
|
|
796
|
+
"""Protocol for streaming decryption."""
|
|
797
|
+
|
|
798
|
+
def write(self, data: bytes) -> bytes:
|
|
799
|
+
"""Write encrypted data and get decrypted output.
|
|
800
|
+
|
|
801
|
+
Args:
|
|
802
|
+
data: Ciphertext chunk.
|
|
803
|
+
|
|
804
|
+
Returns:
|
|
805
|
+
Decrypted plaintext.
|
|
806
|
+
"""
|
|
807
|
+
...
|
|
808
|
+
|
|
809
|
+
def finalize(self) -> bytes:
|
|
810
|
+
"""Finalize decryption and verify authentication.
|
|
811
|
+
|
|
812
|
+
Returns:
|
|
813
|
+
Any remaining decrypted data.
|
|
814
|
+
|
|
815
|
+
Raises:
|
|
816
|
+
IntegrityError: If authentication fails.
|
|
817
|
+
"""
|
|
818
|
+
...
|
|
819
|
+
|
|
820
|
+
|
|
821
|
+
@runtime_checkable
|
|
822
|
+
class KeyManager(Protocol):
|
|
823
|
+
"""Protocol for key management."""
|
|
824
|
+
|
|
825
|
+
def get_key(self, key_id: str) -> EncryptionKey:
|
|
826
|
+
"""Retrieve a key by ID.
|
|
827
|
+
|
|
828
|
+
Args:
|
|
829
|
+
key_id: Key identifier.
|
|
830
|
+
|
|
831
|
+
Returns:
|
|
832
|
+
Encryption key.
|
|
833
|
+
|
|
834
|
+
Raises:
|
|
835
|
+
KeyError_: If key not found.
|
|
836
|
+
"""
|
|
837
|
+
...
|
|
838
|
+
|
|
839
|
+
def store_key(self, key: EncryptionKey) -> None:
|
|
840
|
+
"""Store a key.
|
|
841
|
+
|
|
842
|
+
Args:
|
|
843
|
+
key: Key to store.
|
|
844
|
+
"""
|
|
845
|
+
...
|
|
846
|
+
|
|
847
|
+
def rotate_key(self, key_id: str) -> EncryptionKey:
|
|
848
|
+
"""Rotate a key (create new version).
|
|
849
|
+
|
|
850
|
+
Args:
|
|
851
|
+
key_id: Key to rotate.
|
|
852
|
+
|
|
853
|
+
Returns:
|
|
854
|
+
New key version.
|
|
855
|
+
"""
|
|
856
|
+
...
|
|
857
|
+
|
|
858
|
+
def delete_key(self, key_id: str) -> None:
|
|
859
|
+
"""Delete a key.
|
|
860
|
+
|
|
861
|
+
Args:
|
|
862
|
+
key_id: Key to delete.
|
|
863
|
+
"""
|
|
864
|
+
...
|
|
865
|
+
|
|
866
|
+
|
|
867
|
+
# =============================================================================
|
|
868
|
+
# Utility Functions
|
|
869
|
+
# =============================================================================
|
|
870
|
+
|
|
871
|
+
|
|
872
|
+
def generate_key(algorithm: EncryptionAlgorithm) -> bytes:
|
|
873
|
+
"""Generate a cryptographically secure random key.
|
|
874
|
+
|
|
875
|
+
Args:
|
|
876
|
+
algorithm: Algorithm to generate key for.
|
|
877
|
+
|
|
878
|
+
Returns:
|
|
879
|
+
Random key bytes.
|
|
880
|
+
"""
|
|
881
|
+
return os.urandom(algorithm.key_size)
|
|
882
|
+
|
|
883
|
+
|
|
884
|
+
def generate_nonce(algorithm: EncryptionAlgorithm) -> bytes:
|
|
885
|
+
"""Generate a cryptographically secure random nonce.
|
|
886
|
+
|
|
887
|
+
Args:
|
|
888
|
+
algorithm: Algorithm to generate nonce for.
|
|
889
|
+
|
|
890
|
+
Returns:
|
|
891
|
+
Random nonce bytes.
|
|
892
|
+
"""
|
|
893
|
+
return os.urandom(algorithm.nonce_size)
|
|
894
|
+
|
|
895
|
+
|
|
896
|
+
def generate_salt(size: int = 16) -> bytes:
|
|
897
|
+
"""Generate a cryptographically secure random salt.
|
|
898
|
+
|
|
899
|
+
Args:
|
|
900
|
+
size: Salt size in bytes.
|
|
901
|
+
|
|
902
|
+
Returns:
|
|
903
|
+
Random salt bytes.
|
|
904
|
+
"""
|
|
905
|
+
return os.urandom(size)
|
|
906
|
+
|
|
907
|
+
|
|
908
|
+
def generate_key_id() -> str:
|
|
909
|
+
"""Generate a unique key identifier.
|
|
910
|
+
|
|
911
|
+
Returns:
|
|
912
|
+
Unique key ID string.
|
|
913
|
+
"""
|
|
914
|
+
return os.urandom(16).hex()
|
|
915
|
+
|
|
916
|
+
|
|
917
|
+
def constant_time_compare(a: bytes, b: bytes) -> bool:
|
|
918
|
+
"""Compare two byte strings in constant time.
|
|
919
|
+
|
|
920
|
+
This prevents timing attacks when comparing authentication tags.
|
|
921
|
+
|
|
922
|
+
Args:
|
|
923
|
+
a: First byte string.
|
|
924
|
+
b: Second byte string.
|
|
925
|
+
|
|
926
|
+
Returns:
|
|
927
|
+
True if equal, False otherwise.
|
|
928
|
+
"""
|
|
929
|
+
return hmac.compare_digest(a, b)
|
|
930
|
+
|
|
931
|
+
|
|
932
|
+
def secure_hash(data: bytes, algorithm: str = "sha256") -> str:
|
|
933
|
+
"""Compute secure hash of data.
|
|
934
|
+
|
|
935
|
+
Args:
|
|
936
|
+
data: Data to hash.
|
|
937
|
+
algorithm: Hash algorithm name.
|
|
938
|
+
|
|
939
|
+
Returns:
|
|
940
|
+
Hex-encoded hash.
|
|
941
|
+
"""
|
|
942
|
+
return hashlib.new(algorithm, data).hexdigest()
|
|
943
|
+
|
|
944
|
+
|
|
945
|
+
# =============================================================================
|
|
946
|
+
# Type Variables
|
|
947
|
+
# =============================================================================
|
|
948
|
+
|
|
949
|
+
T = TypeVar("T")
|
|
950
|
+
EncryptorT = TypeVar("EncryptorT", bound=Encryptor)
|
|
951
|
+
DecryptorT = TypeVar("DecryptorT", bound=Decryptor)
|
|
952
|
+
KeyDeriverT = TypeVar("KeyDeriverT", bound=KeyDeriver)
|