pycharter 0.0.22__py3-none-any.whl → 0.0.24__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.
- api/main.py +27 -1
- api/models/docs.py +68 -0
- api/models/evolution.py +117 -0
- api/models/tracking.py +111 -0
- api/models/validation.py +46 -6
- api/routes/v1/__init__.py +14 -1
- api/routes/v1/docs.py +187 -0
- api/routes/v1/evolution.py +337 -0
- api/routes/v1/templates.py +211 -27
- api/routes/v1/tracking.py +301 -0
- api/routes/v1/validation.py +68 -31
- pycharter/__init__.py +268 -58
- pycharter/data/templates/contract/template_coercion_rules.yaml +57 -0
- pycharter/data/templates/contract/template_contract.yaml +122 -0
- pycharter/data/templates/contract/template_metadata.yaml +68 -0
- pycharter/data/templates/contract/template_schema.yaml +100 -0
- pycharter/data/templates/contract/template_validation_rules.yaml +75 -0
- pycharter/data/templates/etl/README.md +224 -0
- pycharter/data/templates/etl/extract_cloud_azure.yaml +24 -0
- pycharter/data/templates/etl/extract_cloud_gcs.yaml +25 -0
- pycharter/data/templates/etl/extract_cloud_s3.yaml +30 -0
- pycharter/data/templates/etl/extract_database.yaml +34 -0
- pycharter/data/templates/etl/extract_database_ssh.yaml +40 -0
- pycharter/data/templates/etl/extract_file_csv.yaml +21 -0
- pycharter/data/templates/etl/extract_file_glob.yaml +25 -0
- pycharter/data/templates/etl/extract_file_json.yaml +24 -0
- pycharter/data/templates/etl/extract_file_parquet.yaml +20 -0
- pycharter/data/templates/etl/extract_http_paginated.yaml +79 -0
- pycharter/data/templates/etl/extract_http_path_params.yaml +38 -0
- pycharter/data/templates/etl/extract_http_simple.yaml +62 -0
- pycharter/data/templates/etl/load_cloud_azure.yaml +24 -0
- pycharter/data/templates/etl/load_cloud_gcs.yaml +22 -0
- pycharter/data/templates/etl/load_cloud_s3.yaml +27 -0
- pycharter/data/templates/etl/load_file.yaml +34 -0
- pycharter/data/templates/etl/load_insert.yaml +18 -0
- pycharter/data/templates/etl/load_postgresql.yaml +39 -0
- pycharter/data/templates/etl/load_sqlite.yaml +21 -0
- pycharter/data/templates/etl/load_truncate_and_load.yaml +20 -0
- pycharter/data/templates/etl/load_upsert.yaml +25 -0
- pycharter/data/templates/etl/load_with_dlq.yaml +34 -0
- pycharter/data/templates/etl/load_with_ssh_tunnel.yaml +35 -0
- pycharter/data/templates/etl/pipeline_http_to_db.yaml +75 -0
- pycharter/data/templates/etl/transform_combined.yaml +48 -0
- pycharter/data/templates/etl/transform_custom_function.yaml +58 -0
- pycharter/data/templates/etl/transform_jsonata.yaml +51 -0
- pycharter/data/templates/etl/transform_simple.yaml +59 -0
- pycharter/db/schemas/.ipynb_checkpoints/data_contract-checkpoint.py +160 -0
- pycharter/docs_generator/__init__.py +43 -0
- pycharter/docs_generator/generator.py +465 -0
- pycharter/docs_generator/renderers.py +247 -0
- pycharter/etl_generator/__init__.py +168 -80
- pycharter/etl_generator/builder.py +121 -0
- pycharter/etl_generator/config_loader.py +394 -0
- pycharter/etl_generator/config_validator.py +418 -0
- pycharter/etl_generator/context.py +132 -0
- pycharter/etl_generator/expression.py +499 -0
- pycharter/etl_generator/extractors/__init__.py +30 -0
- pycharter/etl_generator/extractors/base.py +70 -0
- pycharter/etl_generator/extractors/cloud_storage.py +530 -0
- pycharter/etl_generator/extractors/database.py +221 -0
- pycharter/etl_generator/extractors/factory.py +185 -0
- pycharter/etl_generator/extractors/file.py +475 -0
- pycharter/etl_generator/extractors/http.py +895 -0
- pycharter/etl_generator/extractors/streaming.py +57 -0
- pycharter/etl_generator/loaders/__init__.py +41 -0
- pycharter/etl_generator/loaders/base.py +35 -0
- pycharter/etl_generator/loaders/cloud.py +87 -0
- pycharter/etl_generator/loaders/cloud_storage_loader.py +275 -0
- pycharter/etl_generator/loaders/database.py +274 -0
- pycharter/etl_generator/loaders/factory.py +180 -0
- pycharter/etl_generator/loaders/file.py +72 -0
- pycharter/etl_generator/loaders/file_loader.py +130 -0
- pycharter/etl_generator/pipeline.py +743 -0
- pycharter/etl_generator/protocols.py +54 -0
- pycharter/etl_generator/result.py +63 -0
- pycharter/etl_generator/schemas/__init__.py +49 -0
- pycharter/etl_generator/transformers/__init__.py +49 -0
- pycharter/etl_generator/transformers/base.py +63 -0
- pycharter/etl_generator/transformers/config.py +45 -0
- pycharter/etl_generator/transformers/custom_function.py +101 -0
- pycharter/etl_generator/transformers/jsonata_transformer.py +56 -0
- pycharter/etl_generator/transformers/operations.py +218 -0
- pycharter/etl_generator/transformers/pipeline.py +54 -0
- pycharter/etl_generator/transformers/simple_operations.py +131 -0
- pycharter/quality/__init__.py +25 -0
- pycharter/quality/tracking/__init__.py +64 -0
- pycharter/quality/tracking/collector.py +318 -0
- pycharter/quality/tracking/exporters.py +238 -0
- pycharter/quality/tracking/models.py +194 -0
- pycharter/quality/tracking/store.py +385 -0
- pycharter/runtime_validator/__init__.py +20 -7
- pycharter/runtime_validator/builder.py +328 -0
- pycharter/runtime_validator/validator.py +311 -7
- pycharter/runtime_validator/validator_core.py +61 -0
- pycharter/schema_evolution/__init__.py +61 -0
- pycharter/schema_evolution/compatibility.py +270 -0
- pycharter/schema_evolution/diff.py +496 -0
- pycharter/schema_evolution/models.py +201 -0
- pycharter/shared/__init__.py +56 -0
- pycharter/shared/errors.py +296 -0
- pycharter/shared/protocols.py +234 -0
- {pycharter-0.0.22.dist-info → pycharter-0.0.24.dist-info}/METADATA +146 -26
- pycharter-0.0.24.dist-info/RECORD +543 -0
- {pycharter-0.0.22.dist-info → pycharter-0.0.24.dist-info}/WHEEL +1 -1
- ui/static/404/index.html +1 -1
- ui/static/404.html +1 -1
- ui/static/__next.__PAGE__.txt +1 -1
- ui/static/__next._full.txt +1 -1
- ui/static/__next._head.txt +1 -1
- ui/static/__next._index.txt +1 -1
- ui/static/__next._tree.txt +1 -1
- ui/static/_next/static/chunks/26dfc590f7714c03.js +1 -0
- ui/static/_next/static/chunks/34d289e6db2ef551.js +1 -0
- ui/static/_next/static/chunks/99508d9d5869cc27.js +1 -0
- ui/static/_next/static/chunks/b313c35a6ba76574.js +1 -0
- ui/static/_not-found/__next._full.txt +1 -1
- ui/static/_not-found/__next._head.txt +1 -1
- ui/static/_not-found/__next._index.txt +1 -1
- ui/static/_not-found/__next._not-found.__PAGE__.txt +1 -1
- ui/static/_not-found/__next._not-found.txt +1 -1
- ui/static/_not-found/__next._tree.txt +1 -1
- ui/static/_not-found/index.html +1 -1
- ui/static/_not-found/index.txt +1 -1
- ui/static/contracts/__next._full.txt +2 -2
- ui/static/contracts/__next._head.txt +1 -1
- ui/static/contracts/__next._index.txt +1 -1
- ui/static/contracts/__next._tree.txt +1 -1
- ui/static/contracts/__next.contracts.__PAGE__.txt +2 -2
- ui/static/contracts/__next.contracts.txt +1 -1
- ui/static/contracts/index.html +1 -1
- ui/static/contracts/index.txt +2 -2
- ui/static/documentation/__next._full.txt +1 -1
- ui/static/documentation/__next._head.txt +1 -1
- ui/static/documentation/__next._index.txt +1 -1
- ui/static/documentation/__next._tree.txt +1 -1
- ui/static/documentation/__next.documentation.__PAGE__.txt +1 -1
- ui/static/documentation/__next.documentation.txt +1 -1
- ui/static/documentation/index.html +2 -2
- ui/static/documentation/index.txt +1 -1
- ui/static/index.html +1 -1
- ui/static/index.txt +1 -1
- ui/static/metadata/__next._full.txt +1 -1
- ui/static/metadata/__next._head.txt +1 -1
- ui/static/metadata/__next._index.txt +1 -1
- ui/static/metadata/__next._tree.txt +1 -1
- ui/static/metadata/__next.metadata.__PAGE__.txt +1 -1
- ui/static/metadata/__next.metadata.txt +1 -1
- ui/static/metadata/index.html +1 -1
- ui/static/metadata/index.txt +1 -1
- ui/static/quality/__next._full.txt +2 -2
- ui/static/quality/__next._head.txt +1 -1
- ui/static/quality/__next._index.txt +1 -1
- ui/static/quality/__next._tree.txt +1 -1
- ui/static/quality/__next.quality.__PAGE__.txt +2 -2
- ui/static/quality/__next.quality.txt +1 -1
- ui/static/quality/index.html +2 -2
- ui/static/quality/index.txt +2 -2
- ui/static/rules/__next._full.txt +1 -1
- ui/static/rules/__next._head.txt +1 -1
- ui/static/rules/__next._index.txt +1 -1
- ui/static/rules/__next._tree.txt +1 -1
- ui/static/rules/__next.rules.__PAGE__.txt +1 -1
- ui/static/rules/__next.rules.txt +1 -1
- ui/static/rules/index.html +1 -1
- ui/static/rules/index.txt +1 -1
- ui/static/schemas/__next._full.txt +1 -1
- ui/static/schemas/__next._head.txt +1 -1
- ui/static/schemas/__next._index.txt +1 -1
- ui/static/schemas/__next._tree.txt +1 -1
- ui/static/schemas/__next.schemas.__PAGE__.txt +1 -1
- ui/static/schemas/__next.schemas.txt +1 -1
- ui/static/schemas/index.html +1 -1
- ui/static/schemas/index.txt +1 -1
- ui/static/settings/__next._full.txt +1 -1
- ui/static/settings/__next._head.txt +1 -1
- ui/static/settings/__next._index.txt +1 -1
- ui/static/settings/__next._tree.txt +1 -1
- ui/static/settings/__next.settings.__PAGE__.txt +1 -1
- ui/static/settings/__next.settings.txt +1 -1
- ui/static/settings/index.html +1 -1
- ui/static/settings/index.txt +1 -1
- ui/static/static/404/index.html +1 -1
- ui/static/static/404.html +1 -1
- ui/static/static/__next.__PAGE__.txt +1 -1
- ui/static/static/__next._full.txt +2 -2
- ui/static/static/__next._head.txt +1 -1
- ui/static/static/__next._index.txt +2 -2
- ui/static/static/__next._tree.txt +2 -2
- ui/static/static/_next/static/chunks/13d4a0fbd74c1ee4.js +1 -0
- ui/static/static/_next/static/chunks/2edb43b48432ac04.js +441 -0
- ui/static/static/_next/static/chunks/d2363397e1b2bcab.css +1 -0
- ui/static/static/_next/static/chunks/f7d1a90dd75d2572.js +1 -0
- ui/static/static/_not-found/__next._full.txt +2 -2
- ui/static/static/_not-found/__next._head.txt +1 -1
- ui/static/static/_not-found/__next._index.txt +2 -2
- ui/static/static/_not-found/__next._not-found.__PAGE__.txt +1 -1
- ui/static/static/_not-found/__next._not-found.txt +1 -1
- ui/static/static/_not-found/__next._tree.txt +2 -2
- ui/static/static/_not-found/index.html +1 -1
- ui/static/static/_not-found/index.txt +2 -2
- ui/static/static/contracts/__next._full.txt +3 -3
- ui/static/static/contracts/__next._head.txt +1 -1
- ui/static/static/contracts/__next._index.txt +2 -2
- ui/static/static/contracts/__next._tree.txt +2 -2
- ui/static/static/contracts/__next.contracts.__PAGE__.txt +2 -2
- ui/static/static/contracts/__next.contracts.txt +1 -1
- ui/static/static/contracts/index.html +1 -1
- ui/static/static/contracts/index.txt +3 -3
- ui/static/static/documentation/__next._full.txt +3 -3
- ui/static/static/documentation/__next._head.txt +1 -1
- ui/static/static/documentation/__next._index.txt +2 -2
- ui/static/static/documentation/__next._tree.txt +2 -2
- ui/static/static/documentation/__next.documentation.__PAGE__.txt +2 -2
- ui/static/static/documentation/__next.documentation.txt +1 -1
- ui/static/static/documentation/index.html +2 -2
- ui/static/static/documentation/index.txt +3 -3
- ui/static/static/index.html +1 -1
- ui/static/static/index.txt +2 -2
- ui/static/static/metadata/__next._full.txt +2 -2
- ui/static/static/metadata/__next._head.txt +1 -1
- ui/static/static/metadata/__next._index.txt +2 -2
- ui/static/static/metadata/__next._tree.txt +2 -2
- ui/static/static/metadata/__next.metadata.__PAGE__.txt +1 -1
- ui/static/static/metadata/__next.metadata.txt +1 -1
- ui/static/static/metadata/index.html +1 -1
- ui/static/static/metadata/index.txt +2 -2
- ui/static/static/quality/__next._full.txt +2 -2
- ui/static/static/quality/__next._head.txt +1 -1
- ui/static/static/quality/__next._index.txt +2 -2
- ui/static/static/quality/__next._tree.txt +2 -2
- ui/static/static/quality/__next.quality.__PAGE__.txt +1 -1
- ui/static/static/quality/__next.quality.txt +1 -1
- ui/static/static/quality/index.html +2 -2
- ui/static/static/quality/index.txt +2 -2
- ui/static/static/rules/__next._full.txt +2 -2
- ui/static/static/rules/__next._head.txt +1 -1
- ui/static/static/rules/__next._index.txt +2 -2
- ui/static/static/rules/__next._tree.txt +2 -2
- ui/static/static/rules/__next.rules.__PAGE__.txt +1 -1
- ui/static/static/rules/__next.rules.txt +1 -1
- ui/static/static/rules/index.html +1 -1
- ui/static/static/rules/index.txt +2 -2
- ui/static/static/schemas/__next._full.txt +2 -2
- ui/static/static/schemas/__next._head.txt +1 -1
- ui/static/static/schemas/__next._index.txt +2 -2
- ui/static/static/schemas/__next._tree.txt +2 -2
- ui/static/static/schemas/__next.schemas.__PAGE__.txt +1 -1
- ui/static/static/schemas/__next.schemas.txt +1 -1
- ui/static/static/schemas/index.html +1 -1
- ui/static/static/schemas/index.txt +2 -2
- ui/static/static/settings/__next._full.txt +2 -2
- ui/static/static/settings/__next._head.txt +1 -1
- ui/static/static/settings/__next._index.txt +2 -2
- ui/static/static/settings/__next._tree.txt +2 -2
- ui/static/static/settings/__next.settings.__PAGE__.txt +1 -1
- ui/static/static/settings/__next.settings.txt +1 -1
- ui/static/static/settings/index.html +1 -1
- ui/static/static/settings/index.txt +2 -2
- ui/static/static/static/.gitkeep +0 -0
- ui/static/static/static/404/index.html +1 -0
- ui/static/static/static/404.html +1 -0
- ui/static/static/static/__next.__PAGE__.txt +10 -0
- ui/static/static/static/__next._full.txt +30 -0
- ui/static/static/static/__next._head.txt +7 -0
- ui/static/static/static/__next._index.txt +9 -0
- ui/static/static/static/__next._tree.txt +2 -0
- ui/static/static/static/_next/static/chunks/222442f6da32302a.js +1 -0
- ui/static/static/static/_next/static/chunks/247eb132b7f7b574.js +1 -0
- ui/static/static/static/_next/static/chunks/297d55555b71baba.js +1 -0
- ui/static/static/static/_next/static/chunks/2ab439ce003cd691.js +1 -0
- ui/static/static/static/_next/static/chunks/414e77373f8ff61c.js +1 -0
- ui/static/static/static/_next/static/chunks/49ca65abd26ae49e.js +1 -0
- ui/static/static/static/_next/static/chunks/652ad0aa26265c47.js +2 -0
- ui/static/static/static/_next/static/chunks/9667e7a3d359eb39.js +1 -0
- ui/static/static/static/_next/static/chunks/9c23f44fff36548a.js +1 -0
- ui/static/static/static/_next/static/chunks/a6dad97d9634a72d.js +1 -0
- ui/static/static/static/_next/static/chunks/b32a0963684b9933.js +4 -0
- ui/static/static/static/_next/static/chunks/c69f6cba366bd988.js +1 -0
- ui/static/static/static/_next/static/chunks/db913959c675cea6.js +1 -0
- ui/static/static/static/_next/static/chunks/f061a4be97bfc3b3.js +1 -0
- ui/static/static/static/_next/static/chunks/f2e7afeab1178138.js +1 -0
- ui/static/static/static/_next/static/chunks/ff1a16fafef87110.js +1 -0
- ui/static/static/static/_next/static/chunks/turbopack-ffcb7ab6794027ef.js +3 -0
- ui/static/static/static/_next/static/tNTkVW6puVXC4bAm4WrHl/_buildManifest.js +11 -0
- ui/static/static/static/_next/static/tNTkVW6puVXC4bAm4WrHl/_ssgManifest.js +1 -0
- ui/static/static/static/_not-found/__next._full.txt +17 -0
- ui/static/static/static/_not-found/__next._head.txt +7 -0
- ui/static/static/static/_not-found/__next._index.txt +9 -0
- ui/static/static/static/_not-found/__next._not-found.__PAGE__.txt +5 -0
- ui/static/static/static/_not-found/__next._not-found.txt +4 -0
- ui/static/static/static/_not-found/__next._tree.txt +2 -0
- ui/static/static/static/_not-found/index.html +1 -0
- ui/static/static/static/_not-found/index.txt +17 -0
- ui/static/static/static/contracts/__next._full.txt +21 -0
- ui/static/static/static/contracts/__next._head.txt +7 -0
- ui/static/static/static/contracts/__next._index.txt +9 -0
- ui/static/static/static/contracts/__next._tree.txt +2 -0
- ui/static/static/static/contracts/__next.contracts.__PAGE__.txt +9 -0
- ui/static/static/static/contracts/__next.contracts.txt +4 -0
- ui/static/static/static/contracts/index.html +1 -0
- ui/static/static/static/contracts/index.txt +21 -0
- ui/static/static/static/documentation/__next._full.txt +21 -0
- ui/static/static/static/documentation/__next._head.txt +7 -0
- ui/static/static/static/documentation/__next._index.txt +9 -0
- ui/static/static/static/documentation/__next._tree.txt +2 -0
- ui/static/static/static/documentation/__next.documentation.__PAGE__.txt +9 -0
- ui/static/static/static/documentation/__next.documentation.txt +4 -0
- ui/static/static/static/documentation/index.html +93 -0
- ui/static/static/static/documentation/index.txt +21 -0
- ui/static/static/static/index.html +1 -0
- ui/static/static/static/index.txt +30 -0
- ui/static/static/static/metadata/__next._full.txt +21 -0
- ui/static/static/static/metadata/__next._head.txt +7 -0
- ui/static/static/static/metadata/__next._index.txt +9 -0
- ui/static/static/static/metadata/__next._tree.txt +2 -0
- ui/static/static/static/metadata/__next.metadata.__PAGE__.txt +9 -0
- ui/static/static/static/metadata/__next.metadata.txt +4 -0
- ui/static/static/static/metadata/index.html +1 -0
- ui/static/static/static/metadata/index.txt +21 -0
- ui/static/static/static/quality/__next._full.txt +21 -0
- ui/static/static/static/quality/__next._head.txt +7 -0
- ui/static/static/static/quality/__next._index.txt +9 -0
- ui/static/static/static/quality/__next._tree.txt +2 -0
- ui/static/static/static/quality/__next.quality.__PAGE__.txt +9 -0
- ui/static/static/static/quality/__next.quality.txt +4 -0
- ui/static/static/static/quality/index.html +2 -0
- ui/static/static/static/quality/index.txt +21 -0
- ui/static/static/static/rules/__next._full.txt +21 -0
- ui/static/static/static/rules/__next._head.txt +7 -0
- ui/static/static/static/rules/__next._index.txt +9 -0
- ui/static/static/static/rules/__next._tree.txt +2 -0
- ui/static/static/static/rules/__next.rules.__PAGE__.txt +9 -0
- ui/static/static/static/rules/__next.rules.txt +4 -0
- ui/static/static/static/rules/index.html +1 -0
- ui/static/static/static/rules/index.txt +21 -0
- ui/static/static/static/schemas/__next._full.txt +21 -0
- ui/static/static/static/schemas/__next._head.txt +7 -0
- ui/static/static/static/schemas/__next._index.txt +9 -0
- ui/static/static/static/schemas/__next._tree.txt +2 -0
- ui/static/static/static/schemas/__next.schemas.__PAGE__.txt +9 -0
- ui/static/static/static/schemas/__next.schemas.txt +4 -0
- ui/static/static/static/schemas/index.html +1 -0
- ui/static/static/static/schemas/index.txt +21 -0
- ui/static/static/static/settings/__next._full.txt +21 -0
- ui/static/static/static/settings/__next._head.txt +7 -0
- ui/static/static/static/settings/__next._index.txt +9 -0
- ui/static/static/static/settings/__next._tree.txt +2 -0
- ui/static/static/static/settings/__next.settings.__PAGE__.txt +9 -0
- ui/static/static/static/settings/__next.settings.txt +4 -0
- ui/static/static/static/settings/index.html +1 -0
- ui/static/static/static/settings/index.txt +21 -0
- ui/static/static/static/validation/__next._full.txt +21 -0
- ui/static/static/static/validation/__next._head.txt +7 -0
- ui/static/static/static/validation/__next._index.txt +9 -0
- ui/static/static/static/validation/__next._tree.txt +2 -0
- ui/static/static/static/validation/__next.validation.__PAGE__.txt +9 -0
- ui/static/static/static/validation/__next.validation.txt +4 -0
- ui/static/static/static/validation/index.html +1 -0
- ui/static/static/static/validation/index.txt +21 -0
- ui/static/static/validation/__next._full.txt +2 -2
- ui/static/static/validation/__next._head.txt +1 -1
- ui/static/static/validation/__next._index.txt +2 -2
- ui/static/static/validation/__next._tree.txt +2 -2
- ui/static/static/validation/__next.validation.__PAGE__.txt +1 -1
- ui/static/static/validation/__next.validation.txt +1 -1
- ui/static/static/validation/index.html +1 -1
- ui/static/static/validation/index.txt +2 -2
- ui/static/validation/__next._full.txt +2 -2
- ui/static/validation/__next._head.txt +1 -1
- ui/static/validation/__next._index.txt +1 -1
- ui/static/validation/__next._tree.txt +1 -1
- ui/static/validation/__next.validation.__PAGE__.txt +2 -2
- ui/static/validation/__next.validation.txt +1 -1
- ui/static/validation/index.html +1 -1
- ui/static/validation/index.txt +2 -2
- pycharter/data/templates/template_coercion_rules.yaml +0 -15
- pycharter/data/templates/template_contract.yaml +0 -587
- pycharter/data/templates/template_metadata.yaml +0 -38
- pycharter/data/templates/template_schema.yaml +0 -22
- pycharter/data/templates/template_transform_advanced.yaml +0 -50
- pycharter/data/templates/template_transform_simple.yaml +0 -59
- pycharter/data/templates/template_validation_rules.yaml +0 -29
- pycharter/etl_generator/extraction.py +0 -916
- pycharter/etl_generator/factory.py +0 -174
- pycharter/etl_generator/orchestrator.py +0 -1650
- pycharter/integrations/__init__.py +0 -19
- pycharter/integrations/kafka.py +0 -178
- pycharter/integrations/streaming.py +0 -100
- pycharter-0.0.22.dist-info/RECORD +0 -358
- {pycharter-0.0.22.dist-info → pycharter-0.0.24.dist-info}/entry_points.txt +0 -0
- {pycharter-0.0.22.dist-info → pycharter-0.0.24.dist-info}/licenses/LICENSE +0 -0
- {pycharter-0.0.22.dist-info → pycharter-0.0.24.dist-info}/top_level.txt +0 -0
- /ui/static/_next/static/{0rYA78L88aUyD2Uh38hhX → 2gKjNv6YvE6BcIdFthBLs}/_buildManifest.js +0 -0
- /ui/static/_next/static/{0rYA78L88aUyD2Uh38hhX → 2gKjNv6YvE6BcIdFthBLs}/_ssgManifest.js +0 -0
- /ui/static/static/_next/static/{tNTkVW6puVXC4bAm4WrHl → 0rYA78L88aUyD2Uh38hhX}/_buildManifest.js +0 -0
- /ui/static/static/_next/static/{tNTkVW6puVXC4bAm4WrHl → 0rYA78L88aUyD2Uh38hhX}/_ssgManifest.js +0 -0
- /ui/static/{_next → static/_next}/static/chunks/c4fa4f4114b7c352.js +0 -0
- /ui/static/static/{_next → static/_next}/static/chunks/4e310fe5005770a3.css +0 -0
- /ui/static/{_next → static/static/_next}/static/chunks/5e04d10c4a7b58a3.js +0 -0
- /ui/static/static/{_next → static/_next}/static/chunks/5fc14c00a2779dc5.js +0 -0
- /ui/static/{_next → static/static/_next}/static/chunks/75d88a058d8ffaa6.js +0 -0
- /ui/static/{_next → static/static/_next}/static/chunks/8c89634cf6bad76f.js +0 -0
- /ui/static/static/{_next → static/_next}/static/chunks/b584574fdc8ab13e.js +0 -0
- /ui/static/static/{_next → static/_next}/static/chunks/d5989c94d3614b3a.js +0 -0
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unified configuration loader for ETL pipelines.
|
|
3
|
+
|
|
4
|
+
Supports both single-file (pipeline.yaml) and multi-file (extract.yaml, transform.yaml, load.yaml) formats.
|
|
5
|
+
Handles variable resolution and config validation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Dict, Optional, Tuple, Union
|
|
12
|
+
|
|
13
|
+
import yaml
|
|
14
|
+
|
|
15
|
+
from pycharter.shared.errors import ConfigLoadError
|
|
16
|
+
from pycharter.etl_generator.config_validator import (
|
|
17
|
+
ConfigValidator,
|
|
18
|
+
ConfigValidationError,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# Variable pattern: ${VAR_NAME} or ${VAR_NAME:-default} or ${VAR_NAME:?error}
|
|
23
|
+
VARIABLE_PATTERN = re.compile(r'\$\{([^}:]+)(?::([?-])([^}]*))?\}')
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class PipelineConfig:
|
|
27
|
+
"""
|
|
28
|
+
Loaded and validated pipeline configuration.
|
|
29
|
+
|
|
30
|
+
Attributes:
|
|
31
|
+
extract: Extract configuration dict
|
|
32
|
+
transform: Transform configuration dict (may be empty)
|
|
33
|
+
load: Load configuration dict
|
|
34
|
+
name: Pipeline name (from config or directory name)
|
|
35
|
+
version: Pipeline version (if specified)
|
|
36
|
+
source_path: Path to the config file or directory
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
extract: Dict[str, Any],
|
|
42
|
+
transform: Dict[str, Any],
|
|
43
|
+
load: Dict[str, Any],
|
|
44
|
+
name: Optional[str] = None,
|
|
45
|
+
version: Optional[str] = None,
|
|
46
|
+
source_path: Optional[str] = None,
|
|
47
|
+
):
|
|
48
|
+
self.extract = extract
|
|
49
|
+
self.transform = transform
|
|
50
|
+
self.load = load
|
|
51
|
+
self.name = name
|
|
52
|
+
self.version = version
|
|
53
|
+
self.source_path = source_path
|
|
54
|
+
|
|
55
|
+
def __repr__(self) -> str:
|
|
56
|
+
return f"PipelineConfig(name={self.name!r}, source={self.source_path!r})"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class ConfigLoader:
|
|
60
|
+
"""
|
|
61
|
+
Loads ETL pipeline configurations from files or directories.
|
|
62
|
+
|
|
63
|
+
Supports:
|
|
64
|
+
- Single-file format: pipeline.yaml with extract, transform, load sections
|
|
65
|
+
- Multi-file format: Directory with extract.yaml, transform.yaml, load.yaml
|
|
66
|
+
- Variable resolution: ${VAR}, ${VAR:-default}, ${VAR:?error}
|
|
67
|
+
- Config validation with clear error messages
|
|
68
|
+
|
|
69
|
+
Usage:
|
|
70
|
+
loader = ConfigLoader()
|
|
71
|
+
|
|
72
|
+
# Load from single file
|
|
73
|
+
config = loader.load("pipelines/users/pipeline.yaml")
|
|
74
|
+
|
|
75
|
+
# Load from directory
|
|
76
|
+
config = loader.load("pipelines/users/")
|
|
77
|
+
|
|
78
|
+
# Load with variables
|
|
79
|
+
config = loader.load("pipelines/users/", variables={"API_KEY": "xxx"})
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
def __init__(
|
|
83
|
+
self,
|
|
84
|
+
validate: bool = True,
|
|
85
|
+
strict: bool = True,
|
|
86
|
+
):
|
|
87
|
+
"""
|
|
88
|
+
Initialize the config loader.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
validate: If True, validate configs against schemas
|
|
92
|
+
strict: If True, raise errors on validation failure
|
|
93
|
+
"""
|
|
94
|
+
self.validate = validate
|
|
95
|
+
self.strict = strict
|
|
96
|
+
self._validator = ConfigValidator(strict=strict) if validate else None
|
|
97
|
+
|
|
98
|
+
def load(
|
|
99
|
+
self,
|
|
100
|
+
path: Union[str, Path],
|
|
101
|
+
variables: Optional[Dict[str, str]] = None,
|
|
102
|
+
) -> PipelineConfig:
|
|
103
|
+
"""
|
|
104
|
+
Load pipeline configuration from a file or directory.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
path: Path to config file or directory
|
|
108
|
+
variables: Additional variables for resolution (in addition to env vars)
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
PipelineConfig with loaded and validated configuration
|
|
112
|
+
|
|
113
|
+
Raises:
|
|
114
|
+
ConfigLoadError: If config files cannot be found or loaded
|
|
115
|
+
ConfigValidationError: If config validation fails
|
|
116
|
+
"""
|
|
117
|
+
path = Path(path).resolve()
|
|
118
|
+
|
|
119
|
+
if path.is_file():
|
|
120
|
+
return self._load_single_file(path, variables)
|
|
121
|
+
elif path.is_dir():
|
|
122
|
+
return self._load_multi_file(path, variables)
|
|
123
|
+
else:
|
|
124
|
+
# Check if it's a path without extension
|
|
125
|
+
for ext in (".yaml", ".yml"):
|
|
126
|
+
single_file = path.with_suffix(ext)
|
|
127
|
+
if single_file.exists():
|
|
128
|
+
return self._load_single_file(single_file, variables)
|
|
129
|
+
|
|
130
|
+
# Check if adding pipeline.yaml works
|
|
131
|
+
pipeline_file = path / "pipeline.yaml"
|
|
132
|
+
if pipeline_file.exists():
|
|
133
|
+
return self._load_single_file(pipeline_file, variables)
|
|
134
|
+
|
|
135
|
+
raise ConfigLoadError(
|
|
136
|
+
f"Config not found: {path}. Expected a YAML file or directory with config files.",
|
|
137
|
+
str(path),
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
def _load_single_file(
|
|
141
|
+
self,
|
|
142
|
+
path: Path,
|
|
143
|
+
variables: Optional[Dict[str, str]] = None,
|
|
144
|
+
) -> PipelineConfig:
|
|
145
|
+
"""Load configuration from a single pipeline.yaml file."""
|
|
146
|
+
config = self._load_yaml(path, variables, str(path.parent))
|
|
147
|
+
|
|
148
|
+
if not isinstance(config, dict):
|
|
149
|
+
raise ConfigLoadError(
|
|
150
|
+
f"Invalid config format: expected a dict, got {type(config).__name__}",
|
|
151
|
+
str(path),
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# Extract sections
|
|
155
|
+
extract = config.get("extract")
|
|
156
|
+
transform = config.get("transform", {})
|
|
157
|
+
load = config.get("load")
|
|
158
|
+
|
|
159
|
+
if extract is None:
|
|
160
|
+
raise ConfigLoadError(
|
|
161
|
+
"Missing 'extract' section in pipeline config",
|
|
162
|
+
str(path),
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
if load is None:
|
|
166
|
+
raise ConfigLoadError(
|
|
167
|
+
"Missing 'load' section in pipeline config",
|
|
168
|
+
str(path),
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
# Normalize transform config
|
|
172
|
+
transform = self._normalize_transform(transform)
|
|
173
|
+
|
|
174
|
+
# Validate if enabled
|
|
175
|
+
if self._validator:
|
|
176
|
+
self._validator.validate_pipeline(config, str(path))
|
|
177
|
+
|
|
178
|
+
return PipelineConfig(
|
|
179
|
+
extract=extract,
|
|
180
|
+
transform=transform,
|
|
181
|
+
load=load,
|
|
182
|
+
name=config.get("name", path.stem),
|
|
183
|
+
version=config.get("version"),
|
|
184
|
+
source_path=str(path),
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
def _load_multi_file(
|
|
188
|
+
self,
|
|
189
|
+
directory: Path,
|
|
190
|
+
variables: Optional[Dict[str, str]] = None,
|
|
191
|
+
) -> PipelineConfig:
|
|
192
|
+
"""Load configuration from separate extract.yaml, transform.yaml, load.yaml files."""
|
|
193
|
+
contract_dir = str(directory)
|
|
194
|
+
|
|
195
|
+
# Load extract config (required)
|
|
196
|
+
extract_path = self._find_config_file(directory, "extract")
|
|
197
|
+
if extract_path is None:
|
|
198
|
+
raise ConfigLoadError(
|
|
199
|
+
f"Missing extract config in {directory}. Expected extract.yaml or extract.yml",
|
|
200
|
+
str(directory),
|
|
201
|
+
)
|
|
202
|
+
extract = self._load_yaml(extract_path, variables, contract_dir)
|
|
203
|
+
|
|
204
|
+
# Load transform config (optional)
|
|
205
|
+
transform_path = self._find_config_file(directory, "transform")
|
|
206
|
+
if transform_path:
|
|
207
|
+
transform = self._load_yaml(transform_path, variables, contract_dir)
|
|
208
|
+
transform = self._normalize_transform(transform)
|
|
209
|
+
else:
|
|
210
|
+
transform = {}
|
|
211
|
+
|
|
212
|
+
# Load load config (required)
|
|
213
|
+
load_path = self._find_config_file(directory, "load")
|
|
214
|
+
if load_path is None:
|
|
215
|
+
raise ConfigLoadError(
|
|
216
|
+
f"Missing load config in {directory}. Expected load.yaml or load.yml",
|
|
217
|
+
str(directory),
|
|
218
|
+
)
|
|
219
|
+
load_config = self._load_yaml(load_path, variables, contract_dir)
|
|
220
|
+
|
|
221
|
+
# Validate if enabled
|
|
222
|
+
if self._validator:
|
|
223
|
+
self._validator.validate_extract(extract, str(extract_path))
|
|
224
|
+
if transform:
|
|
225
|
+
self._validator.validate_transform(
|
|
226
|
+
{"transform": transform} if isinstance(transform, list) else transform,
|
|
227
|
+
str(transform_path) if transform_path else None,
|
|
228
|
+
)
|
|
229
|
+
self._validator.validate_load(load_config, str(load_path))
|
|
230
|
+
|
|
231
|
+
return PipelineConfig(
|
|
232
|
+
extract=extract,
|
|
233
|
+
transform=transform,
|
|
234
|
+
load=load_config,
|
|
235
|
+
name=directory.name,
|
|
236
|
+
source_path=str(directory),
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
def _find_config_file(self, directory: Path, name: str) -> Optional[Path]:
|
|
240
|
+
"""Find a config file by name, checking for .yaml and .yml extensions."""
|
|
241
|
+
for ext in (".yaml", ".yml"):
|
|
242
|
+
path = directory / f"{name}{ext}"
|
|
243
|
+
if path.exists():
|
|
244
|
+
return path
|
|
245
|
+
return None
|
|
246
|
+
|
|
247
|
+
def _load_yaml(
|
|
248
|
+
self,
|
|
249
|
+
path: Path,
|
|
250
|
+
variables: Optional[Dict[str, str]] = None,
|
|
251
|
+
contract_dir: Optional[str] = None,
|
|
252
|
+
) -> Dict[str, Any]:
|
|
253
|
+
"""Load and parse a YAML file with variable resolution."""
|
|
254
|
+
try:
|
|
255
|
+
with open(path) as f:
|
|
256
|
+
content = f.read()
|
|
257
|
+
except FileNotFoundError:
|
|
258
|
+
raise ConfigLoadError(f"Config file not found: {path}", str(path))
|
|
259
|
+
except IOError as e:
|
|
260
|
+
raise ConfigLoadError(f"Error reading config file: {e}", str(path))
|
|
261
|
+
|
|
262
|
+
# Resolve variables in the YAML content
|
|
263
|
+
content = self._resolve_variables(content, variables, contract_dir)
|
|
264
|
+
|
|
265
|
+
try:
|
|
266
|
+
config = yaml.safe_load(content)
|
|
267
|
+
except yaml.YAMLError as e:
|
|
268
|
+
raise ConfigLoadError(f"Invalid YAML: {e}", str(path))
|
|
269
|
+
|
|
270
|
+
return config or {}
|
|
271
|
+
|
|
272
|
+
def _resolve_variables(
|
|
273
|
+
self,
|
|
274
|
+
content: str,
|
|
275
|
+
variables: Optional[Dict[str, str]] = None,
|
|
276
|
+
contract_dir: Optional[str] = None,
|
|
277
|
+
) -> str:
|
|
278
|
+
"""
|
|
279
|
+
Resolve ${VAR} placeholders in content.
|
|
280
|
+
|
|
281
|
+
Supports:
|
|
282
|
+
- ${VAR} - Use variable or env var
|
|
283
|
+
- ${VAR:-default} - Use default if not set
|
|
284
|
+
- ${VAR:?error message} - Raise error if not set
|
|
285
|
+
"""
|
|
286
|
+
variables = variables or {}
|
|
287
|
+
|
|
288
|
+
def replace_var(match):
|
|
289
|
+
var_name = match.group(1)
|
|
290
|
+
modifier = match.group(2) # '-' for default, '?' for required
|
|
291
|
+
modifier_value = match.group(3) # default value or error message
|
|
292
|
+
|
|
293
|
+
# Check variables dict first, then environment
|
|
294
|
+
value = variables.get(var_name) or os.environ.get(var_name)
|
|
295
|
+
|
|
296
|
+
if value:
|
|
297
|
+
return value
|
|
298
|
+
|
|
299
|
+
# Handle modifiers
|
|
300
|
+
if modifier == "-":
|
|
301
|
+
return modifier_value if modifier_value is not None else ""
|
|
302
|
+
elif modifier == "?":
|
|
303
|
+
error_msg = modifier_value or f"Required variable {var_name} is not set"
|
|
304
|
+
raise ConfigLoadError(error_msg, path=None)
|
|
305
|
+
|
|
306
|
+
# No modifier - return original placeholder (will be resolved later or cause error)
|
|
307
|
+
return match.group(0)
|
|
308
|
+
|
|
309
|
+
return VARIABLE_PATTERN.sub(replace_var, content)
|
|
310
|
+
|
|
311
|
+
def _normalize_transform(
|
|
312
|
+
self,
|
|
313
|
+
transform: Union[Dict[str, Any], list, None],
|
|
314
|
+
) -> Union[Dict[str, Any], list]:
|
|
315
|
+
"""
|
|
316
|
+
Normalize transform config to consistent format.
|
|
317
|
+
|
|
318
|
+
Handles:
|
|
319
|
+
- Empty/None -> {}
|
|
320
|
+
- List (ordered) -> list (unchanged)
|
|
321
|
+
- Dict with 'transform' key -> unwrap
|
|
322
|
+
- Dict (legacy) -> dict (unchanged)
|
|
323
|
+
"""
|
|
324
|
+
if transform is None:
|
|
325
|
+
return {}
|
|
326
|
+
|
|
327
|
+
if isinstance(transform, list):
|
|
328
|
+
return transform
|
|
329
|
+
|
|
330
|
+
if isinstance(transform, dict):
|
|
331
|
+
# Check if wrapped in 'transform' key
|
|
332
|
+
if "transform" in transform and len(transform) == 1:
|
|
333
|
+
return transform["transform"]
|
|
334
|
+
return transform
|
|
335
|
+
|
|
336
|
+
return {}
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def load_pipeline_config(
|
|
340
|
+
path: Union[str, Path],
|
|
341
|
+
variables: Optional[Dict[str, str]] = None,
|
|
342
|
+
validate: bool = True,
|
|
343
|
+
) -> PipelineConfig:
|
|
344
|
+
"""
|
|
345
|
+
Convenience function to load a pipeline configuration.
|
|
346
|
+
|
|
347
|
+
Args:
|
|
348
|
+
path: Path to config file or directory
|
|
349
|
+
variables: Additional variables for resolution
|
|
350
|
+
validate: If True, validate configs against schemas
|
|
351
|
+
|
|
352
|
+
Returns:
|
|
353
|
+
PipelineConfig with loaded configuration
|
|
354
|
+
"""
|
|
355
|
+
loader = ConfigLoader(validate=validate)
|
|
356
|
+
return loader.load(path, variables)
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def detect_config_format(path: Union[str, Path]) -> str:
|
|
360
|
+
"""
|
|
361
|
+
Detect whether a path points to single-file or multi-file config.
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
path: Path to check
|
|
365
|
+
|
|
366
|
+
Returns:
|
|
367
|
+
"single" for single-file format, "multi" for multi-file format
|
|
368
|
+
|
|
369
|
+
Raises:
|
|
370
|
+
ConfigLoadError: If config format cannot be determined
|
|
371
|
+
"""
|
|
372
|
+
path = Path(path)
|
|
373
|
+
|
|
374
|
+
if path.is_file():
|
|
375
|
+
return "single"
|
|
376
|
+
|
|
377
|
+
if path.is_dir():
|
|
378
|
+
# Check for pipeline.yaml (single-file in directory)
|
|
379
|
+
if (path / "pipeline.yaml").exists() or (path / "pipeline.yml").exists():
|
|
380
|
+
return "single"
|
|
381
|
+
|
|
382
|
+
# Check for extract.yaml (multi-file)
|
|
383
|
+
if (path / "extract.yaml").exists() or (path / "extract.yml").exists():
|
|
384
|
+
return "multi"
|
|
385
|
+
|
|
386
|
+
# Check with extensions
|
|
387
|
+
for ext in (".yaml", ".yml"):
|
|
388
|
+
if path.with_suffix(ext).exists():
|
|
389
|
+
return "single"
|
|
390
|
+
|
|
391
|
+
raise ConfigLoadError(
|
|
392
|
+
f"Cannot determine config format for: {path}",
|
|
393
|
+
str(path),
|
|
394
|
+
)
|