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,1133 @@
|
|
|
1
|
+
"""Enterprise configuration management system for Truthound.
|
|
2
|
+
|
|
3
|
+
This module provides environment-aware configuration management with support for:
|
|
4
|
+
- Multiple environments (dev, staging, prod)
|
|
5
|
+
- Multiple configuration sources (files, env vars, Vault, AWS Secrets)
|
|
6
|
+
- Configuration validation
|
|
7
|
+
- Hot reloading
|
|
8
|
+
- Type-safe configuration access
|
|
9
|
+
|
|
10
|
+
Architecture:
|
|
11
|
+
ConfigSource[] (ordered by priority)
|
|
12
|
+
|
|
|
13
|
+
+---> EnvConfigSource (environment variables)
|
|
14
|
+
+---> FileConfigSource (YAML, JSON, TOML)
|
|
15
|
+
+---> VaultConfigSource (HashiCorp Vault)
|
|
16
|
+
+---> AwsSecretsSource (AWS Secrets Manager)
|
|
17
|
+
|
|
|
18
|
+
v
|
|
19
|
+
ConfigManager
|
|
20
|
+
|
|
|
21
|
+
+---> Merge & Validate
|
|
22
|
+
|
|
|
23
|
+
v
|
|
24
|
+
ConfigProfile (typed access)
|
|
25
|
+
|
|
26
|
+
Usage:
|
|
27
|
+
>>> from truthound.infrastructure.config import (
|
|
28
|
+
... get_config, load_config, Environment,
|
|
29
|
+
... )
|
|
30
|
+
>>>
|
|
31
|
+
>>> # Load configuration for production
|
|
32
|
+
>>> config = load_config(
|
|
33
|
+
... environment=Environment.PRODUCTION,
|
|
34
|
+
... config_path="config/",
|
|
35
|
+
... use_vault=True,
|
|
36
|
+
... )
|
|
37
|
+
>>>
|
|
38
|
+
>>> # Access configuration
|
|
39
|
+
>>> db_host = config.get("database.host")
|
|
40
|
+
>>> log_level = config.get("logging.level", default="INFO")
|
|
41
|
+
>>>
|
|
42
|
+
>>> # Type-safe access
|
|
43
|
+
>>> max_workers = config.get_int("workers.max", default=4)
|
|
44
|
+
>>> debug = config.get_bool("debug", default=False)
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
from __future__ import annotations
|
|
48
|
+
|
|
49
|
+
import json
|
|
50
|
+
import os
|
|
51
|
+
import re
|
|
52
|
+
import threading
|
|
53
|
+
import time
|
|
54
|
+
from abc import ABC, abstractmethod
|
|
55
|
+
from dataclasses import dataclass, field
|
|
56
|
+
from datetime import datetime, timezone
|
|
57
|
+
from enum import Enum
|
|
58
|
+
from pathlib import Path
|
|
59
|
+
from typing import Any, Callable, Generic, Iterator, TypeVar, overload
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
import yaml
|
|
63
|
+
HAS_YAML = True
|
|
64
|
+
except ImportError:
|
|
65
|
+
HAS_YAML = False
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
import tomllib
|
|
69
|
+
HAS_TOML = True
|
|
70
|
+
except ImportError:
|
|
71
|
+
try:
|
|
72
|
+
import tomli as tomllib # type: ignore
|
|
73
|
+
HAS_TOML = True
|
|
74
|
+
except ImportError:
|
|
75
|
+
HAS_TOML = False
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# =============================================================================
|
|
79
|
+
# Environment Types
|
|
80
|
+
# =============================================================================
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class Environment(Enum):
|
|
84
|
+
"""Application environments."""
|
|
85
|
+
|
|
86
|
+
DEVELOPMENT = "development"
|
|
87
|
+
TESTING = "testing"
|
|
88
|
+
STAGING = "staging"
|
|
89
|
+
PRODUCTION = "production"
|
|
90
|
+
LOCAL = "local"
|
|
91
|
+
|
|
92
|
+
@classmethod
|
|
93
|
+
def from_string(cls, value: str) -> "Environment":
|
|
94
|
+
"""Convert string to Environment.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
value: Environment string (case-insensitive).
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
Environment enum value.
|
|
101
|
+
"""
|
|
102
|
+
mapping = {
|
|
103
|
+
"dev": cls.DEVELOPMENT,
|
|
104
|
+
"development": cls.DEVELOPMENT,
|
|
105
|
+
"test": cls.TESTING,
|
|
106
|
+
"testing": cls.TESTING,
|
|
107
|
+
"stage": cls.STAGING,
|
|
108
|
+
"staging": cls.STAGING,
|
|
109
|
+
"prod": cls.PRODUCTION,
|
|
110
|
+
"production": cls.PRODUCTION,
|
|
111
|
+
"local": cls.LOCAL,
|
|
112
|
+
}
|
|
113
|
+
return mapping.get(value.lower(), cls.DEVELOPMENT)
|
|
114
|
+
|
|
115
|
+
@classmethod
|
|
116
|
+
def current(cls) -> "Environment":
|
|
117
|
+
"""Get current environment from ENV variable.
|
|
118
|
+
|
|
119
|
+
Checks: ENVIRONMENT, ENV, TRUTHOUND_ENV
|
|
120
|
+
"""
|
|
121
|
+
for var in ("TRUTHOUND_ENV", "ENVIRONMENT", "ENV"):
|
|
122
|
+
value = os.getenv(var)
|
|
123
|
+
if value:
|
|
124
|
+
return cls.from_string(value)
|
|
125
|
+
return cls.DEVELOPMENT
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def is_production(self) -> bool:
|
|
129
|
+
"""Check if this is a production environment."""
|
|
130
|
+
return self in (Environment.PRODUCTION, Environment.STAGING)
|
|
131
|
+
|
|
132
|
+
@property
|
|
133
|
+
def is_development(self) -> bool:
|
|
134
|
+
"""Check if this is a development environment."""
|
|
135
|
+
return self in (Environment.DEVELOPMENT, Environment.LOCAL, Environment.TESTING)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
# =============================================================================
|
|
139
|
+
# Configuration Errors
|
|
140
|
+
# =============================================================================
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class ConfigError(Exception):
|
|
144
|
+
"""Base configuration error."""
|
|
145
|
+
|
|
146
|
+
pass
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class ConfigValidationError(ConfigError):
|
|
150
|
+
"""Configuration validation error."""
|
|
151
|
+
|
|
152
|
+
def __init__(self, errors: list[str]) -> None:
|
|
153
|
+
self.errors = errors
|
|
154
|
+
super().__init__(f"Configuration validation failed: {', '.join(errors)}")
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class ConfigSourceError(ConfigError):
|
|
158
|
+
"""Configuration source error."""
|
|
159
|
+
|
|
160
|
+
pass
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# =============================================================================
|
|
164
|
+
# Configuration Sources
|
|
165
|
+
# =============================================================================
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class ConfigSource(ABC):
|
|
169
|
+
"""Abstract base class for configuration sources.
|
|
170
|
+
|
|
171
|
+
Configuration sources provide key-value pairs from various backends.
|
|
172
|
+
Sources are processed in priority order (highest first).
|
|
173
|
+
"""
|
|
174
|
+
|
|
175
|
+
def __init__(self, priority: int = 0) -> None:
|
|
176
|
+
"""Initialize config source.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
priority: Source priority (higher = processed later, overrides earlier).
|
|
180
|
+
"""
|
|
181
|
+
self._priority = priority
|
|
182
|
+
|
|
183
|
+
@property
|
|
184
|
+
def priority(self) -> int:
|
|
185
|
+
"""Get source priority."""
|
|
186
|
+
return self._priority
|
|
187
|
+
|
|
188
|
+
@abstractmethod
|
|
189
|
+
def load(self) -> dict[str, Any]:
|
|
190
|
+
"""Load configuration from source.
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
Dictionary of configuration values.
|
|
194
|
+
"""
|
|
195
|
+
pass
|
|
196
|
+
|
|
197
|
+
def reload(self) -> dict[str, Any]:
|
|
198
|
+
"""Reload configuration (default: same as load)."""
|
|
199
|
+
return self.load()
|
|
200
|
+
|
|
201
|
+
@property
|
|
202
|
+
def supports_reload(self) -> bool:
|
|
203
|
+
"""Check if source supports hot reload."""
|
|
204
|
+
return False
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class EnvConfigSource(ConfigSource):
|
|
208
|
+
"""Environment variable configuration source.
|
|
209
|
+
|
|
210
|
+
Reads configuration from environment variables with prefix.
|
|
211
|
+
|
|
212
|
+
Example:
|
|
213
|
+
TRUTHOUND_DATABASE_HOST=localhost
|
|
214
|
+
TRUTHOUND_DATABASE_PORT=5432
|
|
215
|
+
|
|
216
|
+
Will produce:
|
|
217
|
+
{"database": {"host": "localhost", "port": "5432"}}
|
|
218
|
+
"""
|
|
219
|
+
|
|
220
|
+
def __init__(
|
|
221
|
+
self,
|
|
222
|
+
prefix: str = "TRUTHOUND",
|
|
223
|
+
separator: str = "_",
|
|
224
|
+
priority: int = 100,
|
|
225
|
+
) -> None:
|
|
226
|
+
"""Initialize environment source.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
prefix: Environment variable prefix.
|
|
230
|
+
separator: Separator for nested keys.
|
|
231
|
+
priority: Source priority.
|
|
232
|
+
"""
|
|
233
|
+
super().__init__(priority)
|
|
234
|
+
self._prefix = prefix
|
|
235
|
+
self._separator = separator
|
|
236
|
+
|
|
237
|
+
def load(self) -> dict[str, Any]:
|
|
238
|
+
"""Load configuration from environment."""
|
|
239
|
+
result: dict[str, Any] = {}
|
|
240
|
+
prefix = f"{self._prefix}{self._separator}"
|
|
241
|
+
|
|
242
|
+
for key, value in os.environ.items():
|
|
243
|
+
if key.startswith(prefix):
|
|
244
|
+
# Remove prefix and convert to nested dict
|
|
245
|
+
config_key = key[len(prefix) :].lower()
|
|
246
|
+
parts = config_key.split(self._separator)
|
|
247
|
+
|
|
248
|
+
# Navigate/create nested structure
|
|
249
|
+
current = result
|
|
250
|
+
for part in parts[:-1]:
|
|
251
|
+
if part not in current:
|
|
252
|
+
current[part] = {}
|
|
253
|
+
current = current[part]
|
|
254
|
+
|
|
255
|
+
# Set value (with type inference)
|
|
256
|
+
current[parts[-1]] = self._parse_value(value)
|
|
257
|
+
|
|
258
|
+
return result
|
|
259
|
+
|
|
260
|
+
def _parse_value(self, value: str) -> Any:
|
|
261
|
+
"""Parse string value to appropriate type."""
|
|
262
|
+
# Boolean
|
|
263
|
+
if value.lower() in ("true", "yes", "1", "on"):
|
|
264
|
+
return True
|
|
265
|
+
if value.lower() in ("false", "no", "0", "off"):
|
|
266
|
+
return False
|
|
267
|
+
|
|
268
|
+
# None
|
|
269
|
+
if value.lower() in ("null", "none", ""):
|
|
270
|
+
return None
|
|
271
|
+
|
|
272
|
+
# Number
|
|
273
|
+
try:
|
|
274
|
+
if "." in value:
|
|
275
|
+
return float(value)
|
|
276
|
+
return int(value)
|
|
277
|
+
except ValueError:
|
|
278
|
+
pass
|
|
279
|
+
|
|
280
|
+
# JSON array/object
|
|
281
|
+
if value.startswith(("[", "{")):
|
|
282
|
+
try:
|
|
283
|
+
return json.loads(value)
|
|
284
|
+
except json.JSONDecodeError:
|
|
285
|
+
pass
|
|
286
|
+
|
|
287
|
+
return value
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
class FileConfigSource(ConfigSource):
|
|
291
|
+
"""File-based configuration source.
|
|
292
|
+
|
|
293
|
+
Supports YAML, JSON, and TOML formats.
|
|
294
|
+
Automatically detects format from file extension.
|
|
295
|
+
"""
|
|
296
|
+
|
|
297
|
+
def __init__(
|
|
298
|
+
self,
|
|
299
|
+
path: str | Path,
|
|
300
|
+
*,
|
|
301
|
+
required: bool = False,
|
|
302
|
+
priority: int = 50,
|
|
303
|
+
watch: bool = False,
|
|
304
|
+
) -> None:
|
|
305
|
+
"""Initialize file source.
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
path: Path to configuration file.
|
|
309
|
+
required: Raise error if file not found.
|
|
310
|
+
priority: Source priority.
|
|
311
|
+
watch: Enable file watching for hot reload.
|
|
312
|
+
"""
|
|
313
|
+
super().__init__(priority)
|
|
314
|
+
self._path = Path(path)
|
|
315
|
+
self._required = required
|
|
316
|
+
self._watch = watch
|
|
317
|
+
self._last_modified: float = 0
|
|
318
|
+
self._cached: dict[str, Any] = {}
|
|
319
|
+
|
|
320
|
+
def load(self) -> dict[str, Any]:
|
|
321
|
+
"""Load configuration from file."""
|
|
322
|
+
if not self._path.exists():
|
|
323
|
+
if self._required:
|
|
324
|
+
raise ConfigSourceError(f"Configuration file not found: {self._path}")
|
|
325
|
+
return {}
|
|
326
|
+
|
|
327
|
+
try:
|
|
328
|
+
content = self._path.read_text(encoding="utf-8")
|
|
329
|
+
self._last_modified = self._path.stat().st_mtime
|
|
330
|
+
|
|
331
|
+
suffix = self._path.suffix.lower()
|
|
332
|
+
|
|
333
|
+
if suffix in (".yaml", ".yml"):
|
|
334
|
+
if not HAS_YAML:
|
|
335
|
+
raise ConfigSourceError("PyYAML not installed")
|
|
336
|
+
self._cached = yaml.safe_load(content) or {}
|
|
337
|
+
|
|
338
|
+
elif suffix == ".json":
|
|
339
|
+
self._cached = json.loads(content)
|
|
340
|
+
|
|
341
|
+
elif suffix == ".toml":
|
|
342
|
+
if not HAS_TOML:
|
|
343
|
+
raise ConfigSourceError("tomllib/tomli not installed")
|
|
344
|
+
self._cached = tomllib.loads(content)
|
|
345
|
+
|
|
346
|
+
else:
|
|
347
|
+
raise ConfigSourceError(f"Unsupported file format: {suffix}")
|
|
348
|
+
|
|
349
|
+
return self._cached
|
|
350
|
+
|
|
351
|
+
except Exception as e:
|
|
352
|
+
if self._required:
|
|
353
|
+
raise ConfigSourceError(f"Failed to load config: {e}")
|
|
354
|
+
return {}
|
|
355
|
+
|
|
356
|
+
def reload(self) -> dict[str, Any]:
|
|
357
|
+
"""Reload if file has changed."""
|
|
358
|
+
if self._path.exists():
|
|
359
|
+
mtime = self._path.stat().st_mtime
|
|
360
|
+
if mtime > self._last_modified:
|
|
361
|
+
return self.load()
|
|
362
|
+
return self._cached
|
|
363
|
+
|
|
364
|
+
@property
|
|
365
|
+
def supports_reload(self) -> bool:
|
|
366
|
+
return True
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
class VaultConfigSource(ConfigSource):
|
|
370
|
+
"""HashiCorp Vault configuration source.
|
|
371
|
+
|
|
372
|
+
Reads secrets from Vault KV v2 engine.
|
|
373
|
+
"""
|
|
374
|
+
|
|
375
|
+
def __init__(
|
|
376
|
+
self,
|
|
377
|
+
url: str,
|
|
378
|
+
path: str,
|
|
379
|
+
*,
|
|
380
|
+
token: str | None = None,
|
|
381
|
+
role: str | None = None,
|
|
382
|
+
mount_point: str = "secret",
|
|
383
|
+
priority: int = 200,
|
|
384
|
+
) -> None:
|
|
385
|
+
"""Initialize Vault source.
|
|
386
|
+
|
|
387
|
+
Args:
|
|
388
|
+
url: Vault server URL.
|
|
389
|
+
path: Secret path.
|
|
390
|
+
token: Vault token (or use VAULT_TOKEN env var).
|
|
391
|
+
role: AppRole for authentication.
|
|
392
|
+
mount_point: KV mount point.
|
|
393
|
+
priority: Source priority.
|
|
394
|
+
"""
|
|
395
|
+
super().__init__(priority)
|
|
396
|
+
self._url = url.rstrip("/")
|
|
397
|
+
self._path = path
|
|
398
|
+
self._token = token or os.getenv("VAULT_TOKEN")
|
|
399
|
+
self._role = role
|
|
400
|
+
self._mount_point = mount_point
|
|
401
|
+
self._cached: dict[str, Any] = {}
|
|
402
|
+
|
|
403
|
+
def load(self) -> dict[str, Any]:
|
|
404
|
+
"""Load secrets from Vault."""
|
|
405
|
+
try:
|
|
406
|
+
import urllib.request
|
|
407
|
+
import urllib.error
|
|
408
|
+
|
|
409
|
+
url = f"{self._url}/v1/{self._mount_point}/data/{self._path}"
|
|
410
|
+
headers = {"X-Vault-Token": self._token}
|
|
411
|
+
|
|
412
|
+
request = urllib.request.Request(url, headers=headers)
|
|
413
|
+
|
|
414
|
+
with urllib.request.urlopen(request, timeout=30) as response:
|
|
415
|
+
data = json.loads(response.read().decode("utf-8"))
|
|
416
|
+
self._cached = data.get("data", {}).get("data", {})
|
|
417
|
+
return self._cached
|
|
418
|
+
|
|
419
|
+
except Exception:
|
|
420
|
+
return {}
|
|
421
|
+
|
|
422
|
+
def reload(self) -> dict[str, Any]:
|
|
423
|
+
"""Reload from Vault."""
|
|
424
|
+
return self.load()
|
|
425
|
+
|
|
426
|
+
@property
|
|
427
|
+
def supports_reload(self) -> bool:
|
|
428
|
+
return True
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
class AwsSecretsSource(ConfigSource):
|
|
432
|
+
"""AWS Secrets Manager configuration source.
|
|
433
|
+
|
|
434
|
+
Reads secrets from AWS Secrets Manager.
|
|
435
|
+
"""
|
|
436
|
+
|
|
437
|
+
def __init__(
|
|
438
|
+
self,
|
|
439
|
+
secret_name: str,
|
|
440
|
+
*,
|
|
441
|
+
region: str | None = None,
|
|
442
|
+
priority: int = 200,
|
|
443
|
+
) -> None:
|
|
444
|
+
"""Initialize AWS Secrets source.
|
|
445
|
+
|
|
446
|
+
Args:
|
|
447
|
+
secret_name: Secret name or ARN.
|
|
448
|
+
region: AWS region.
|
|
449
|
+
priority: Source priority.
|
|
450
|
+
"""
|
|
451
|
+
super().__init__(priority)
|
|
452
|
+
self._secret_name = secret_name
|
|
453
|
+
self._region = region or os.getenv("AWS_REGION", "us-east-1")
|
|
454
|
+
self._cached: dict[str, Any] = {}
|
|
455
|
+
|
|
456
|
+
def load(self) -> dict[str, Any]:
|
|
457
|
+
"""Load secrets from AWS Secrets Manager."""
|
|
458
|
+
try:
|
|
459
|
+
import boto3
|
|
460
|
+
|
|
461
|
+
client = boto3.client(
|
|
462
|
+
"secretsmanager",
|
|
463
|
+
region_name=self._region,
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
response = client.get_secret_value(SecretId=self._secret_name)
|
|
467
|
+
secret_string = response.get("SecretString", "{}")
|
|
468
|
+
self._cached = json.loads(secret_string)
|
|
469
|
+
return self._cached
|
|
470
|
+
|
|
471
|
+
except Exception:
|
|
472
|
+
return {}
|
|
473
|
+
|
|
474
|
+
def reload(self) -> dict[str, Any]:
|
|
475
|
+
"""Reload from AWS."""
|
|
476
|
+
return self.load()
|
|
477
|
+
|
|
478
|
+
@property
|
|
479
|
+
def supports_reload(self) -> bool:
|
|
480
|
+
return True
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
# =============================================================================
|
|
484
|
+
# Configuration Schema & Validation
|
|
485
|
+
# =============================================================================
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
@dataclass
|
|
489
|
+
class ConfigField:
|
|
490
|
+
"""Configuration field definition for validation."""
|
|
491
|
+
|
|
492
|
+
name: str
|
|
493
|
+
type: type | tuple[type, ...] = str
|
|
494
|
+
required: bool = False
|
|
495
|
+
default: Any = None
|
|
496
|
+
min_value: float | None = None
|
|
497
|
+
max_value: float | None = None
|
|
498
|
+
pattern: str | None = None
|
|
499
|
+
choices: list[Any] | None = None
|
|
500
|
+
description: str = ""
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
@dataclass
|
|
504
|
+
class ConfigSchema:
|
|
505
|
+
"""Configuration schema for validation.
|
|
506
|
+
|
|
507
|
+
Example:
|
|
508
|
+
>>> schema = ConfigSchema(
|
|
509
|
+
... fields=[
|
|
510
|
+
... ConfigField("database.host", str, required=True),
|
|
511
|
+
... ConfigField("database.port", int, default=5432, min_value=1, max_value=65535),
|
|
512
|
+
... ConfigField("logging.level", str, choices=["DEBUG", "INFO", "WARNING", "ERROR"]),
|
|
513
|
+
... ]
|
|
514
|
+
... )
|
|
515
|
+
"""
|
|
516
|
+
|
|
517
|
+
fields: list[ConfigField] = field(default_factory=list)
|
|
518
|
+
|
|
519
|
+
def add_field(
|
|
520
|
+
self,
|
|
521
|
+
name: str,
|
|
522
|
+
type: type = str,
|
|
523
|
+
**kwargs: Any,
|
|
524
|
+
) -> "ConfigSchema":
|
|
525
|
+
"""Add a field to the schema."""
|
|
526
|
+
self.fields.append(ConfigField(name=name, type=type, **kwargs))
|
|
527
|
+
return self
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
class ConfigValidator:
|
|
531
|
+
"""Configuration validator.
|
|
532
|
+
|
|
533
|
+
Validates configuration against a schema.
|
|
534
|
+
"""
|
|
535
|
+
|
|
536
|
+
def __init__(self, schema: ConfigSchema) -> None:
|
|
537
|
+
"""Initialize validator.
|
|
538
|
+
|
|
539
|
+
Args:
|
|
540
|
+
schema: Configuration schema.
|
|
541
|
+
"""
|
|
542
|
+
self._schema = schema
|
|
543
|
+
|
|
544
|
+
def validate(self, config: dict[str, Any]) -> list[str]:
|
|
545
|
+
"""Validate configuration.
|
|
546
|
+
|
|
547
|
+
Args:
|
|
548
|
+
config: Configuration dictionary.
|
|
549
|
+
|
|
550
|
+
Returns:
|
|
551
|
+
List of validation errors (empty if valid).
|
|
552
|
+
"""
|
|
553
|
+
errors: list[str] = []
|
|
554
|
+
|
|
555
|
+
for field_def in self._schema.fields:
|
|
556
|
+
value = self._get_nested(config, field_def.name)
|
|
557
|
+
|
|
558
|
+
# Required check
|
|
559
|
+
if value is None:
|
|
560
|
+
if field_def.required:
|
|
561
|
+
errors.append(f"Required field '{field_def.name}' is missing")
|
|
562
|
+
continue
|
|
563
|
+
|
|
564
|
+
# Type check
|
|
565
|
+
if not isinstance(value, field_def.type):
|
|
566
|
+
errors.append(
|
|
567
|
+
f"Field '{field_def.name}' should be {field_def.type.__name__}, "
|
|
568
|
+
f"got {type(value).__name__}"
|
|
569
|
+
)
|
|
570
|
+
continue
|
|
571
|
+
|
|
572
|
+
# Range check
|
|
573
|
+
if isinstance(value, (int, float)):
|
|
574
|
+
if field_def.min_value is not None and value < field_def.min_value:
|
|
575
|
+
errors.append(
|
|
576
|
+
f"Field '{field_def.name}' must be >= {field_def.min_value}"
|
|
577
|
+
)
|
|
578
|
+
if field_def.max_value is not None and value > field_def.max_value:
|
|
579
|
+
errors.append(
|
|
580
|
+
f"Field '{field_def.name}' must be <= {field_def.max_value}"
|
|
581
|
+
)
|
|
582
|
+
|
|
583
|
+
# Pattern check
|
|
584
|
+
if isinstance(value, str) and field_def.pattern:
|
|
585
|
+
if not re.match(field_def.pattern, value):
|
|
586
|
+
errors.append(
|
|
587
|
+
f"Field '{field_def.name}' must match pattern '{field_def.pattern}'"
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
# Choices check
|
|
591
|
+
if field_def.choices and value not in field_def.choices:
|
|
592
|
+
errors.append(
|
|
593
|
+
f"Field '{field_def.name}' must be one of {field_def.choices}"
|
|
594
|
+
)
|
|
595
|
+
|
|
596
|
+
return errors
|
|
597
|
+
|
|
598
|
+
def _get_nested(self, config: dict[str, Any], key: str) -> Any:
|
|
599
|
+
"""Get nested configuration value."""
|
|
600
|
+
parts = key.split(".")
|
|
601
|
+
current = config
|
|
602
|
+
for part in parts:
|
|
603
|
+
if not isinstance(current, dict):
|
|
604
|
+
return None
|
|
605
|
+
current = current.get(part)
|
|
606
|
+
if current is None:
|
|
607
|
+
return None
|
|
608
|
+
return current
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
# =============================================================================
|
|
612
|
+
# Configuration Profile
|
|
613
|
+
# =============================================================================
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
class ConfigProfile:
|
|
617
|
+
"""Typed configuration access with environment awareness.
|
|
618
|
+
|
|
619
|
+
Provides type-safe access to configuration values with defaults.
|
|
620
|
+
|
|
621
|
+
Example:
|
|
622
|
+
>>> profile = ConfigProfile(config_dict, environment=Environment.PRODUCTION)
|
|
623
|
+
>>> host = profile.get("database.host", default="localhost")
|
|
624
|
+
>>> port = profile.get_int("database.port", default=5432)
|
|
625
|
+
>>> debug = profile.get_bool("debug", default=False)
|
|
626
|
+
"""
|
|
627
|
+
|
|
628
|
+
def __init__(
|
|
629
|
+
self,
|
|
630
|
+
config: dict[str, Any],
|
|
631
|
+
*,
|
|
632
|
+
environment: Environment = Environment.DEVELOPMENT,
|
|
633
|
+
) -> None:
|
|
634
|
+
"""Initialize configuration profile.
|
|
635
|
+
|
|
636
|
+
Args:
|
|
637
|
+
config: Configuration dictionary.
|
|
638
|
+
environment: Current environment.
|
|
639
|
+
"""
|
|
640
|
+
self._config = config
|
|
641
|
+
self._environment = environment
|
|
642
|
+
self._cache: dict[str, Any] = {}
|
|
643
|
+
|
|
644
|
+
@property
|
|
645
|
+
def environment(self) -> Environment:
|
|
646
|
+
"""Get current environment."""
|
|
647
|
+
return self._environment
|
|
648
|
+
|
|
649
|
+
@property
|
|
650
|
+
def is_production(self) -> bool:
|
|
651
|
+
"""Check if production environment."""
|
|
652
|
+
return self._environment.is_production
|
|
653
|
+
|
|
654
|
+
@property
|
|
655
|
+
def is_development(self) -> bool:
|
|
656
|
+
"""Check if development environment."""
|
|
657
|
+
return self._environment.is_development
|
|
658
|
+
|
|
659
|
+
def get(
|
|
660
|
+
self,
|
|
661
|
+
key: str,
|
|
662
|
+
default: Any = None,
|
|
663
|
+
*,
|
|
664
|
+
required: bool = False,
|
|
665
|
+
) -> Any:
|
|
666
|
+
"""Get configuration value.
|
|
667
|
+
|
|
668
|
+
Args:
|
|
669
|
+
key: Configuration key (dot-separated for nesting).
|
|
670
|
+
default: Default value if not found.
|
|
671
|
+
required: Raise error if not found.
|
|
672
|
+
|
|
673
|
+
Returns:
|
|
674
|
+
Configuration value.
|
|
675
|
+
"""
|
|
676
|
+
if key in self._cache:
|
|
677
|
+
return self._cache[key]
|
|
678
|
+
|
|
679
|
+
value = self._get_nested(key)
|
|
680
|
+
|
|
681
|
+
if value is None:
|
|
682
|
+
if required:
|
|
683
|
+
raise ConfigError(f"Required configuration '{key}' not found")
|
|
684
|
+
return default
|
|
685
|
+
|
|
686
|
+
self._cache[key] = value
|
|
687
|
+
return value
|
|
688
|
+
|
|
689
|
+
def get_str(self, key: str, default: str = "") -> str:
|
|
690
|
+
"""Get string configuration value."""
|
|
691
|
+
value = self.get(key, default)
|
|
692
|
+
return str(value) if value is not None else default
|
|
693
|
+
|
|
694
|
+
def get_int(self, key: str, default: int = 0) -> int:
|
|
695
|
+
"""Get integer configuration value."""
|
|
696
|
+
value = self.get(key, default)
|
|
697
|
+
if isinstance(value, int):
|
|
698
|
+
return value
|
|
699
|
+
try:
|
|
700
|
+
return int(value)
|
|
701
|
+
except (TypeError, ValueError):
|
|
702
|
+
return default
|
|
703
|
+
|
|
704
|
+
def get_float(self, key: str, default: float = 0.0) -> float:
|
|
705
|
+
"""Get float configuration value."""
|
|
706
|
+
value = self.get(key, default)
|
|
707
|
+
if isinstance(value, float):
|
|
708
|
+
return value
|
|
709
|
+
try:
|
|
710
|
+
return float(value)
|
|
711
|
+
except (TypeError, ValueError):
|
|
712
|
+
return default
|
|
713
|
+
|
|
714
|
+
def get_bool(self, key: str, default: bool = False) -> bool:
|
|
715
|
+
"""Get boolean configuration value."""
|
|
716
|
+
value = self.get(key, default)
|
|
717
|
+
if isinstance(value, bool):
|
|
718
|
+
return value
|
|
719
|
+
if isinstance(value, str):
|
|
720
|
+
return value.lower() in ("true", "yes", "1", "on")
|
|
721
|
+
return bool(value)
|
|
722
|
+
|
|
723
|
+
def get_list(self, key: str, default: list[Any] | None = None) -> list[Any]:
|
|
724
|
+
"""Get list configuration value."""
|
|
725
|
+
value = self.get(key, default)
|
|
726
|
+
if isinstance(value, list):
|
|
727
|
+
return value
|
|
728
|
+
if value is None:
|
|
729
|
+
return default or []
|
|
730
|
+
return [value]
|
|
731
|
+
|
|
732
|
+
def get_dict(
|
|
733
|
+
self, key: str, default: dict[str, Any] | None = None
|
|
734
|
+
) -> dict[str, Any]:
|
|
735
|
+
"""Get dictionary configuration value."""
|
|
736
|
+
value = self.get(key, default)
|
|
737
|
+
if isinstance(value, dict):
|
|
738
|
+
return value
|
|
739
|
+
return default or {}
|
|
740
|
+
|
|
741
|
+
def _get_nested(self, key: str) -> Any:
|
|
742
|
+
"""Get nested configuration value."""
|
|
743
|
+
parts = key.split(".")
|
|
744
|
+
current = self._config
|
|
745
|
+
|
|
746
|
+
for part in parts:
|
|
747
|
+
if not isinstance(current, dict):
|
|
748
|
+
return None
|
|
749
|
+
current = current.get(part)
|
|
750
|
+
if current is None:
|
|
751
|
+
return None
|
|
752
|
+
|
|
753
|
+
return current
|
|
754
|
+
|
|
755
|
+
def to_dict(self) -> dict[str, Any]:
|
|
756
|
+
"""Get full configuration as dictionary."""
|
|
757
|
+
return self._config.copy()
|
|
758
|
+
|
|
759
|
+
def __contains__(self, key: str) -> bool:
|
|
760
|
+
"""Check if key exists."""
|
|
761
|
+
return self._get_nested(key) is not None
|
|
762
|
+
|
|
763
|
+
def __getitem__(self, key: str) -> Any:
|
|
764
|
+
"""Get configuration value by key."""
|
|
765
|
+
value = self.get(key)
|
|
766
|
+
if value is None:
|
|
767
|
+
raise KeyError(key)
|
|
768
|
+
return value
|
|
769
|
+
|
|
770
|
+
|
|
771
|
+
# =============================================================================
|
|
772
|
+
# Configuration Manager
|
|
773
|
+
# =============================================================================
|
|
774
|
+
|
|
775
|
+
|
|
776
|
+
class ConfigManager:
|
|
777
|
+
"""Central configuration manager.
|
|
778
|
+
|
|
779
|
+
Manages multiple configuration sources and provides unified access.
|
|
780
|
+
|
|
781
|
+
Example:
|
|
782
|
+
>>> manager = ConfigManager(environment=Environment.PRODUCTION)
|
|
783
|
+
>>> manager.add_source(FileConfigSource("config/base.yaml"))
|
|
784
|
+
>>> manager.add_source(FileConfigSource("config/production.yaml"))
|
|
785
|
+
>>> manager.add_source(EnvConfigSource())
|
|
786
|
+
>>>
|
|
787
|
+
>>> config = manager.load()
|
|
788
|
+
>>> print(config.get("database.host"))
|
|
789
|
+
"""
|
|
790
|
+
|
|
791
|
+
def __init__(
|
|
792
|
+
self,
|
|
793
|
+
environment: Environment | None = None,
|
|
794
|
+
*,
|
|
795
|
+
auto_reload: bool = False,
|
|
796
|
+
reload_interval: float = 60.0,
|
|
797
|
+
) -> None:
|
|
798
|
+
"""Initialize configuration manager.
|
|
799
|
+
|
|
800
|
+
Args:
|
|
801
|
+
environment: Application environment.
|
|
802
|
+
auto_reload: Enable automatic reloading.
|
|
803
|
+
reload_interval: Reload check interval in seconds.
|
|
804
|
+
"""
|
|
805
|
+
self._environment = environment or Environment.current()
|
|
806
|
+
self._sources: list[ConfigSource] = []
|
|
807
|
+
self._config: dict[str, Any] = {}
|
|
808
|
+
self._profile: ConfigProfile | None = None
|
|
809
|
+
self._schema: ConfigSchema | None = None
|
|
810
|
+
self._lock = threading.RLock()
|
|
811
|
+
self._auto_reload = auto_reload
|
|
812
|
+
self._reload_interval = reload_interval
|
|
813
|
+
self._reload_thread: threading.Thread | None = None
|
|
814
|
+
self._running = False
|
|
815
|
+
self._callbacks: list[Callable[[ConfigProfile], None]] = []
|
|
816
|
+
|
|
817
|
+
@property
|
|
818
|
+
def environment(self) -> Environment:
|
|
819
|
+
"""Get current environment."""
|
|
820
|
+
return self._environment
|
|
821
|
+
|
|
822
|
+
def add_source(self, source: ConfigSource) -> "ConfigManager":
|
|
823
|
+
"""Add a configuration source.
|
|
824
|
+
|
|
825
|
+
Args:
|
|
826
|
+
source: Configuration source.
|
|
827
|
+
|
|
828
|
+
Returns:
|
|
829
|
+
Self for chaining.
|
|
830
|
+
"""
|
|
831
|
+
with self._lock:
|
|
832
|
+
self._sources.append(source)
|
|
833
|
+
self._sources.sort(key=lambda s: s.priority)
|
|
834
|
+
return self
|
|
835
|
+
|
|
836
|
+
def set_schema(self, schema: ConfigSchema) -> "ConfigManager":
|
|
837
|
+
"""Set configuration schema for validation.
|
|
838
|
+
|
|
839
|
+
Args:
|
|
840
|
+
schema: Configuration schema.
|
|
841
|
+
|
|
842
|
+
Returns:
|
|
843
|
+
Self for chaining.
|
|
844
|
+
"""
|
|
845
|
+
self._schema = schema
|
|
846
|
+
return self
|
|
847
|
+
|
|
848
|
+
def on_reload(self, callback: Callable[[ConfigProfile], None]) -> "ConfigManager":
|
|
849
|
+
"""Register reload callback.
|
|
850
|
+
|
|
851
|
+
Args:
|
|
852
|
+
callback: Function to call on reload.
|
|
853
|
+
|
|
854
|
+
Returns:
|
|
855
|
+
Self for chaining.
|
|
856
|
+
"""
|
|
857
|
+
self._callbacks.append(callback)
|
|
858
|
+
return self
|
|
859
|
+
|
|
860
|
+
def load(self, validate: bool = True) -> ConfigProfile:
|
|
861
|
+
"""Load configuration from all sources.
|
|
862
|
+
|
|
863
|
+
Args:
|
|
864
|
+
validate: Validate against schema.
|
|
865
|
+
|
|
866
|
+
Returns:
|
|
867
|
+
ConfigProfile instance.
|
|
868
|
+
"""
|
|
869
|
+
with self._lock:
|
|
870
|
+
self._config = {}
|
|
871
|
+
|
|
872
|
+
# Load from sources in priority order
|
|
873
|
+
for source in self._sources:
|
|
874
|
+
try:
|
|
875
|
+
source_config = source.load()
|
|
876
|
+
self._merge_config(self._config, source_config)
|
|
877
|
+
except Exception:
|
|
878
|
+
pass # Skip failed sources
|
|
879
|
+
|
|
880
|
+
# Validate
|
|
881
|
+
if validate and self._schema:
|
|
882
|
+
validator = ConfigValidator(self._schema)
|
|
883
|
+
errors = validator.validate(self._config)
|
|
884
|
+
if errors:
|
|
885
|
+
raise ConfigValidationError(errors)
|
|
886
|
+
|
|
887
|
+
self._profile = ConfigProfile(
|
|
888
|
+
self._config,
|
|
889
|
+
environment=self._environment,
|
|
890
|
+
)
|
|
891
|
+
|
|
892
|
+
# Start auto-reload if enabled
|
|
893
|
+
if self._auto_reload and not self._running:
|
|
894
|
+
self._start_reload_thread()
|
|
895
|
+
|
|
896
|
+
return self._profile
|
|
897
|
+
|
|
898
|
+
def reload(self) -> ConfigProfile:
|
|
899
|
+
"""Reload configuration from sources that support it.
|
|
900
|
+
|
|
901
|
+
Returns:
|
|
902
|
+
Updated ConfigProfile.
|
|
903
|
+
"""
|
|
904
|
+
with self._lock:
|
|
905
|
+
changed = False
|
|
906
|
+
|
|
907
|
+
for source in self._sources:
|
|
908
|
+
if source.supports_reload:
|
|
909
|
+
try:
|
|
910
|
+
old_config = self._config.copy()
|
|
911
|
+
source_config = source.reload()
|
|
912
|
+
self._merge_config(self._config, source_config)
|
|
913
|
+
if self._config != old_config:
|
|
914
|
+
changed = True
|
|
915
|
+
except Exception:
|
|
916
|
+
pass
|
|
917
|
+
|
|
918
|
+
if changed:
|
|
919
|
+
self._profile = ConfigProfile(
|
|
920
|
+
self._config,
|
|
921
|
+
environment=self._environment,
|
|
922
|
+
)
|
|
923
|
+
|
|
924
|
+
# Notify callbacks
|
|
925
|
+
for callback in self._callbacks:
|
|
926
|
+
try:
|
|
927
|
+
callback(self._profile)
|
|
928
|
+
except Exception:
|
|
929
|
+
pass
|
|
930
|
+
|
|
931
|
+
return self._profile or ConfigProfile({})
|
|
932
|
+
|
|
933
|
+
def _merge_config(self, base: dict[str, Any], override: dict[str, Any]) -> None:
|
|
934
|
+
"""Deep merge configuration dictionaries."""
|
|
935
|
+
for key, value in override.items():
|
|
936
|
+
if (
|
|
937
|
+
key in base
|
|
938
|
+
and isinstance(base[key], dict)
|
|
939
|
+
and isinstance(value, dict)
|
|
940
|
+
):
|
|
941
|
+
self._merge_config(base[key], value)
|
|
942
|
+
else:
|
|
943
|
+
base[key] = value
|
|
944
|
+
|
|
945
|
+
def _start_reload_thread(self) -> None:
|
|
946
|
+
"""Start background reload thread."""
|
|
947
|
+
self._running = True
|
|
948
|
+
self._reload_thread = threading.Thread(
|
|
949
|
+
target=self._reload_loop,
|
|
950
|
+
daemon=True,
|
|
951
|
+
name="config-reload",
|
|
952
|
+
)
|
|
953
|
+
self._reload_thread.start()
|
|
954
|
+
|
|
955
|
+
def _reload_loop(self) -> None:
|
|
956
|
+
"""Background reload loop."""
|
|
957
|
+
while self._running:
|
|
958
|
+
time.sleep(self._reload_interval)
|
|
959
|
+
try:
|
|
960
|
+
self.reload()
|
|
961
|
+
except Exception:
|
|
962
|
+
pass
|
|
963
|
+
|
|
964
|
+
def stop(self) -> None:
|
|
965
|
+
"""Stop auto-reload."""
|
|
966
|
+
self._running = False
|
|
967
|
+
if self._reload_thread:
|
|
968
|
+
self._reload_thread.join(timeout=5)
|
|
969
|
+
self._reload_thread = None
|
|
970
|
+
|
|
971
|
+
@property
|
|
972
|
+
def config(self) -> ConfigProfile:
|
|
973
|
+
"""Get current configuration profile."""
|
|
974
|
+
if self._profile is None:
|
|
975
|
+
return self.load()
|
|
976
|
+
return self._profile
|
|
977
|
+
|
|
978
|
+
|
|
979
|
+
# =============================================================================
|
|
980
|
+
# Default Schema
|
|
981
|
+
# =============================================================================
|
|
982
|
+
|
|
983
|
+
|
|
984
|
+
def create_default_schema() -> ConfigSchema:
|
|
985
|
+
"""Create default Truthound configuration schema."""
|
|
986
|
+
schema = ConfigSchema()
|
|
987
|
+
|
|
988
|
+
# Logging
|
|
989
|
+
schema.add_field("logging.level", str, default="INFO",
|
|
990
|
+
choices=["TRACE", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"])
|
|
991
|
+
schema.add_field("logging.format", str, default="console",
|
|
992
|
+
choices=["console", "json", "logfmt"])
|
|
993
|
+
|
|
994
|
+
# Metrics
|
|
995
|
+
schema.add_field("metrics.enabled", bool, default=True)
|
|
996
|
+
schema.add_field("metrics.port", int, default=9090, min_value=1, max_value=65535)
|
|
997
|
+
|
|
998
|
+
# Database
|
|
999
|
+
schema.add_field("database.host", str, default="localhost")
|
|
1000
|
+
schema.add_field("database.port", int, default=5432, min_value=1, max_value=65535)
|
|
1001
|
+
schema.add_field("database.pool_size", int, default=10, min_value=1, max_value=100)
|
|
1002
|
+
|
|
1003
|
+
# Validation
|
|
1004
|
+
schema.add_field("validation.timeout", int, default=300, min_value=1)
|
|
1005
|
+
schema.add_field("validation.max_workers", int, default=4, min_value=1, max_value=32)
|
|
1006
|
+
|
|
1007
|
+
return schema
|
|
1008
|
+
|
|
1009
|
+
|
|
1010
|
+
# =============================================================================
|
|
1011
|
+
# Global Configuration
|
|
1012
|
+
# =============================================================================
|
|
1013
|
+
|
|
1014
|
+
_global_manager: ConfigManager | None = None
|
|
1015
|
+
_lock = threading.Lock()
|
|
1016
|
+
|
|
1017
|
+
|
|
1018
|
+
def load_config(
|
|
1019
|
+
*,
|
|
1020
|
+
environment: Environment | str | None = None,
|
|
1021
|
+
config_path: str | Path | None = None,
|
|
1022
|
+
env_prefix: str = "TRUTHOUND",
|
|
1023
|
+
use_vault: bool = False,
|
|
1024
|
+
vault_url: str = "",
|
|
1025
|
+
vault_path: str = "",
|
|
1026
|
+
use_aws_secrets: bool = False,
|
|
1027
|
+
aws_secret_name: str = "",
|
|
1028
|
+
auto_reload: bool = False,
|
|
1029
|
+
validate: bool = True,
|
|
1030
|
+
) -> ConfigProfile:
|
|
1031
|
+
"""Load configuration.
|
|
1032
|
+
|
|
1033
|
+
Args:
|
|
1034
|
+
environment: Application environment.
|
|
1035
|
+
config_path: Path to configuration files.
|
|
1036
|
+
env_prefix: Environment variable prefix.
|
|
1037
|
+
use_vault: Enable HashiCorp Vault.
|
|
1038
|
+
vault_url: Vault server URL.
|
|
1039
|
+
vault_path: Vault secret path.
|
|
1040
|
+
use_aws_secrets: Enable AWS Secrets Manager.
|
|
1041
|
+
aws_secret_name: AWS secret name.
|
|
1042
|
+
auto_reload: Enable auto-reload.
|
|
1043
|
+
validate: Validate configuration.
|
|
1044
|
+
|
|
1045
|
+
Returns:
|
|
1046
|
+
ConfigProfile instance.
|
|
1047
|
+
"""
|
|
1048
|
+
global _global_manager
|
|
1049
|
+
|
|
1050
|
+
with _lock:
|
|
1051
|
+
if isinstance(environment, str):
|
|
1052
|
+
environment = Environment.from_string(environment)
|
|
1053
|
+
elif environment is None:
|
|
1054
|
+
environment = Environment.current()
|
|
1055
|
+
|
|
1056
|
+
manager = ConfigManager(
|
|
1057
|
+
environment=environment,
|
|
1058
|
+
auto_reload=auto_reload,
|
|
1059
|
+
)
|
|
1060
|
+
|
|
1061
|
+
# Add file sources
|
|
1062
|
+
if config_path:
|
|
1063
|
+
path = Path(config_path)
|
|
1064
|
+
|
|
1065
|
+
# Base config
|
|
1066
|
+
for ext in (".yaml", ".yml", ".json", ".toml"):
|
|
1067
|
+
base_file = path / f"base{ext}"
|
|
1068
|
+
if base_file.exists():
|
|
1069
|
+
manager.add_source(FileConfigSource(base_file, priority=10))
|
|
1070
|
+
break
|
|
1071
|
+
|
|
1072
|
+
# Environment-specific config
|
|
1073
|
+
for ext in (".yaml", ".yml", ".json", ".toml"):
|
|
1074
|
+
env_file = path / f"{environment.value}{ext}"
|
|
1075
|
+
if env_file.exists():
|
|
1076
|
+
manager.add_source(FileConfigSource(env_file, priority=20))
|
|
1077
|
+
break
|
|
1078
|
+
|
|
1079
|
+
# Local overrides
|
|
1080
|
+
for ext in (".yaml", ".yml", ".json", ".toml"):
|
|
1081
|
+
local_file = path / f"local{ext}"
|
|
1082
|
+
if local_file.exists():
|
|
1083
|
+
manager.add_source(FileConfigSource(local_file, priority=30))
|
|
1084
|
+
break
|
|
1085
|
+
|
|
1086
|
+
# Add environment variables
|
|
1087
|
+
manager.add_source(EnvConfigSource(prefix=env_prefix, priority=100))
|
|
1088
|
+
|
|
1089
|
+
# Add Vault
|
|
1090
|
+
if use_vault and vault_url and vault_path:
|
|
1091
|
+
manager.add_source(
|
|
1092
|
+
VaultConfigSource(vault_url, vault_path, priority=200)
|
|
1093
|
+
)
|
|
1094
|
+
|
|
1095
|
+
# Add AWS Secrets
|
|
1096
|
+
if use_aws_secrets and aws_secret_name:
|
|
1097
|
+
manager.add_source(
|
|
1098
|
+
AwsSecretsSource(aws_secret_name, priority=200)
|
|
1099
|
+
)
|
|
1100
|
+
|
|
1101
|
+
# Set default schema
|
|
1102
|
+
manager.set_schema(create_default_schema())
|
|
1103
|
+
|
|
1104
|
+
_global_manager = manager
|
|
1105
|
+
return manager.load(validate=validate)
|
|
1106
|
+
|
|
1107
|
+
|
|
1108
|
+
def get_config() -> ConfigProfile:
|
|
1109
|
+
"""Get the global configuration.
|
|
1110
|
+
|
|
1111
|
+
Returns:
|
|
1112
|
+
ConfigProfile instance.
|
|
1113
|
+
"""
|
|
1114
|
+
global _global_manager
|
|
1115
|
+
|
|
1116
|
+
with _lock:
|
|
1117
|
+
if _global_manager is None:
|
|
1118
|
+
return load_config()
|
|
1119
|
+
return _global_manager.config
|
|
1120
|
+
|
|
1121
|
+
|
|
1122
|
+
def reload_config() -> ConfigProfile:
|
|
1123
|
+
"""Reload the global configuration.
|
|
1124
|
+
|
|
1125
|
+
Returns:
|
|
1126
|
+
Updated ConfigProfile.
|
|
1127
|
+
"""
|
|
1128
|
+
global _global_manager
|
|
1129
|
+
|
|
1130
|
+
with _lock:
|
|
1131
|
+
if _global_manager is None:
|
|
1132
|
+
return load_config()
|
|
1133
|
+
return _global_manager.reload()
|