pycharter 0.0.25__py3-none-any.whl → 0.0.26__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.
- pycharter/__init__.py +6 -0
- pycharter/api/README.md +1 -1
- pycharter/api/dependencies/auth.py +158 -0
- pycharter/api/main.py +30 -2
- pycharter/api/models/etl.py +66 -0
- pycharter/api/routes/v1/__init__.py +4 -0
- pycharter/api/routes/v1/auth.py +97 -0
- pycharter/api/routes/v1/contracts.py +10 -8
- pycharter/api/routes/v1/etl.py +131 -0
- pycharter/cli.py +1 -1
- pycharter/config.py +69 -0
- pycharter/contract_builder/builder.py +32 -37
- pycharter/data/seed/compliance_frameworks.yaml +22 -0
- pycharter/data/seed/contracts.yaml +130 -0
- pycharter/data/seed/data_feeds.yaml +22 -0
- pycharter/data/seed/domains.yaml +13 -0
- pycharter/data/seed/environments.yaml +19 -0
- pycharter/data/seed/owners.yaml +21 -0
- pycharter/data/seed/systems.yaml +13 -0
- pycharter/data/seed/tags.yaml +25 -0
- pycharter/data/templates/contract/README.md +31 -14
- pycharter/data/templates/contract/template_contract.yaml +37 -0
- pycharter/data/templates/etl/README.md +1 -1
- pycharter/data/templates/etl/extract_with_validation.yaml +86 -0
- pycharter/data/templates/etl/load_with_validation.yaml +111 -0
- pycharter/data/templates/etl/settings.yaml +55 -0
- pycharter/db/cli.py +126 -4
- pycharter/db/migrations/versions/20260122000000_change_artifact_unique_constraints_to_title_version.py +2 -2
- pycharter/etl_generator/INTERFACES.md +6 -7
- pycharter/etl_generator/__init__.py +47 -11
- pycharter/etl_generator/config_models.py +673 -0
- pycharter/etl_generator/config_validator.py +133 -157
- pycharter/etl_generator/context.py +3 -0
- pycharter/etl_generator/database.py +5 -1
- pycharter/etl_generator/extractors/__init__.py +4 -2
- pycharter/etl_generator/extractors/cloud_storage.py +9 -9
- pycharter/etl_generator/extractors/database.py +2 -2
- pycharter/etl_generator/extractors/factory.py +15 -33
- pycharter/etl_generator/extractors/file.py +2 -2
- pycharter/etl_generator/extractors/http.py +2 -2
- pycharter/etl_generator/extractors/mongodb.py +393 -0
- pycharter/etl_generator/extractors/streaming.py +2 -2
- pycharter/etl_generator/loaders/__init__.py +15 -9
- pycharter/etl_generator/loaders/{cloud_storage_loader.py → cloud_storage.py} +95 -2
- pycharter/etl_generator/loaders/factory.py +16 -29
- pycharter/etl_generator/loaders/file.py +135 -1
- pycharter/etl_generator/loaders/mongodb.py +416 -0
- pycharter/etl_generator/pipeline.py +283 -164
- pycharter/etl_generator/result.py +16 -0
- pycharter/etl_generator/schemas/__init__.py +71 -42
- pycharter/etl_generator/transformers/config.py +3 -2
- pycharter/etl_generator/transformers/simple_operations.py +57 -4
- pycharter/etl_generator/validation.py +551 -0
- pycharter/runtime_validator/__init__.py +7 -0
- pycharter/runtime_validator/utils.py +33 -0
- pycharter/runtime_validator/validator.py +13 -10
- pycharter/ui/package-lock.json +50 -41
- pycharter/ui/package.json +2 -1
- pycharter/ui/static/404/index.html +1 -1
- pycharter/ui/static/404.html +1 -1
- pycharter/ui/static/__next.__PAGE__.txt +2 -2
- pycharter/ui/static/__next._full.txt +7 -7
- pycharter/ui/static/__next._head.txt +1 -1
- pycharter/ui/static/__next._index.txt +6 -6
- pycharter/ui/static/__next._tree.txt +2 -2
- pycharter/ui/static/_next/static/chunks/0fc1f70b787b8845.js +1 -0
- pycharter/ui/static/_next/static/chunks/17bb8075d7b75663.css +1 -0
- pycharter/ui/static/_next/static/chunks/381932864dcbfdb8.js +1 -0
- pycharter/ui/static/_next/static/chunks/4c951b8e4507e2b3.js +1 -0
- pycharter/ui/static/_next/static/chunks/68b87a6f65abd3ed.js +1 -0
- pycharter/ui/static/_next/static/chunks/78572617b8fae189.js +1 -0
- pycharter/ui/static/_next/static/chunks/8b7be2803e3fe184.js +1 -0
- pycharter/ui/static/_next/static/chunks/a8e529fd1e67f121.js +1 -0
- pycharter/ui/static/_next/static/chunks/c35d998f80be3ff5.js +1 -0
- pycharter/ui/static/_next/static/chunks/e453aa5d01c32c17.js +1 -0
- pycharter/ui/static/_next/static/chunks/f2d240eb057f898a.js +970 -0
- pycharter/ui/static/_next/static/chunks/f7722448f6040846.js +1 -0
- pycharter/ui/static/_not-found/__next._full.txt +12 -12
- pycharter/ui/static/_not-found/__next._head.txt +3 -3
- pycharter/ui/static/_not-found/__next._index.txt +8 -8
- pycharter/ui/static/_not-found/__next._not-found.__PAGE__.txt +2 -2
- pycharter/ui/static/_not-found/__next._not-found.txt +3 -3
- pycharter/ui/static/_not-found/__next._tree.txt +2 -2
- pycharter/ui/static/_not-found/index.html +1 -1
- pycharter/ui/static/_not-found/index.txt +12 -12
- pycharter/ui/static/contracts/__next._full.txt +7 -7
- pycharter/ui/static/contracts/__next._head.txt +1 -1
- pycharter/ui/static/contracts/__next._index.txt +6 -6
- pycharter/ui/static/contracts/__next._tree.txt +2 -2
- pycharter/ui/static/contracts/__next.contracts.__PAGE__.txt +2 -2
- pycharter/ui/static/contracts/__next.contracts.txt +1 -1
- pycharter/ui/static/contracts/index.html +1 -1
- pycharter/ui/static/contracts/index.txt +7 -7
- pycharter/ui/static/documentation/__next._full.txt +7 -7
- pycharter/ui/static/documentation/__next._head.txt +1 -1
- pycharter/ui/static/documentation/__next._index.txt +6 -6
- pycharter/ui/static/documentation/__next._tree.txt +2 -2
- pycharter/ui/static/documentation/__next.documentation.__PAGE__.txt +2 -2
- pycharter/ui/static/documentation/__next.documentation.txt +1 -1
- pycharter/ui/static/documentation/index.html +3 -3
- pycharter/ui/static/documentation/index.txt +7 -7
- pycharter/ui/static/etl/__next._full.txt +21 -0
- pycharter/ui/static/etl/__next._head.txt +7 -0
- pycharter/ui/static/etl/__next._index.txt +9 -0
- pycharter/ui/static/etl/__next._tree.txt +2 -0
- pycharter/ui/static/etl/__next.etl.__PAGE__.txt +9 -0
- pycharter/ui/static/etl/__next.etl.txt +4 -0
- pycharter/ui/static/etl/index.html +2 -0
- pycharter/ui/static/etl/index.txt +21 -0
- pycharter/ui/static/index.html +1 -1
- pycharter/ui/static/index.txt +7 -7
- pycharter/ui/static/metadata/__next._full.txt +7 -7
- pycharter/ui/static/metadata/__next._head.txt +1 -1
- pycharter/ui/static/metadata/__next._index.txt +6 -6
- pycharter/ui/static/metadata/__next._tree.txt +2 -2
- pycharter/ui/static/metadata/__next.metadata.__PAGE__.txt +2 -2
- pycharter/ui/static/metadata/__next.metadata.txt +1 -1
- pycharter/ui/static/metadata/index.html +1 -1
- pycharter/ui/static/metadata/index.txt +7 -7
- pycharter/ui/static/quality/__next._full.txt +7 -7
- pycharter/ui/static/quality/__next._head.txt +1 -1
- pycharter/ui/static/quality/__next._index.txt +6 -6
- pycharter/ui/static/quality/__next._tree.txt +2 -2
- pycharter/ui/static/quality/__next.quality.__PAGE__.txt +2 -2
- pycharter/ui/static/quality/__next.quality.txt +1 -1
- pycharter/ui/static/quality/index.html +2 -2
- pycharter/ui/static/quality/index.txt +7 -7
- pycharter/ui/static/rules/__next._full.txt +7 -7
- pycharter/ui/static/rules/__next._head.txt +1 -1
- pycharter/ui/static/rules/__next._index.txt +6 -6
- pycharter/ui/static/rules/__next._tree.txt +2 -2
- pycharter/ui/static/rules/__next.rules.__PAGE__.txt +2 -2
- pycharter/ui/static/rules/__next.rules.txt +1 -1
- pycharter/ui/static/rules/index.html +1 -1
- pycharter/ui/static/rules/index.txt +7 -7
- pycharter/ui/static/schemas/__next._full.txt +7 -7
- pycharter/ui/static/schemas/__next._head.txt +1 -1
- pycharter/ui/static/schemas/__next._index.txt +6 -6
- pycharter/ui/static/schemas/__next._tree.txt +2 -2
- pycharter/ui/static/schemas/__next.schemas.__PAGE__.txt +2 -2
- pycharter/ui/static/schemas/__next.schemas.txt +1 -1
- pycharter/ui/static/schemas/index.html +1 -1
- pycharter/ui/static/schemas/index.txt +7 -7
- pycharter/ui/static/settings/__next._full.txt +7 -7
- pycharter/ui/static/settings/__next._head.txt +1 -1
- pycharter/ui/static/settings/__next._index.txt +6 -6
- pycharter/ui/static/settings/__next._tree.txt +2 -2
- pycharter/ui/static/settings/__next.settings.__PAGE__.txt +2 -2
- pycharter/ui/static/settings/__next.settings.txt +1 -1
- pycharter/ui/static/settings/index.html +1 -1
- pycharter/ui/static/settings/index.txt +7 -7
- pycharter/ui/static/static/404/index.html +1 -1
- pycharter/ui/static/static/404.html +1 -1
- pycharter/ui/static/static/__next.__PAGE__.txt +1 -1
- pycharter/ui/static/static/__next._full.txt +1 -1
- pycharter/ui/static/static/__next._head.txt +1 -1
- pycharter/ui/static/static/__next._index.txt +1 -1
- pycharter/ui/static/static/__next._tree.txt +1 -1
- pycharter/ui/static/static/_not-found/__next._full.txt +1 -1
- pycharter/ui/static/static/_not-found/__next._head.txt +1 -1
- pycharter/ui/static/static/_not-found/__next._index.txt +1 -1
- pycharter/ui/static/static/_not-found/__next._not-found.__PAGE__.txt +1 -1
- pycharter/ui/static/static/_not-found/__next._not-found.txt +1 -1
- pycharter/ui/static/static/_not-found/__next._tree.txt +1 -1
- pycharter/ui/static/static/_not-found/index.html +1 -1
- pycharter/ui/static/static/_not-found/index.txt +1 -1
- pycharter/ui/static/static/contracts/__next._full.txt +2 -2
- pycharter/ui/static/static/contracts/__next._head.txt +1 -1
- pycharter/ui/static/static/contracts/__next._index.txt +1 -1
- pycharter/ui/static/static/contracts/__next._tree.txt +1 -1
- pycharter/ui/static/static/contracts/__next.contracts.__PAGE__.txt +2 -2
- pycharter/ui/static/static/contracts/__next.contracts.txt +1 -1
- pycharter/ui/static/static/contracts/index.html +1 -1
- pycharter/ui/static/static/contracts/index.txt +2 -2
- pycharter/ui/static/static/documentation/__next._full.txt +1 -1
- pycharter/ui/static/static/documentation/__next._head.txt +1 -1
- pycharter/ui/static/static/documentation/__next._index.txt +1 -1
- pycharter/ui/static/static/documentation/__next._tree.txt +1 -1
- pycharter/ui/static/static/documentation/__next.documentation.__PAGE__.txt +1 -1
- pycharter/ui/static/static/documentation/__next.documentation.txt +1 -1
- pycharter/ui/static/static/documentation/index.html +2 -2
- pycharter/ui/static/static/documentation/index.txt +1 -1
- pycharter/ui/static/static/index.html +1 -1
- pycharter/ui/static/static/index.txt +1 -1
- pycharter/ui/static/static/metadata/__next._full.txt +1 -1
- pycharter/ui/static/static/metadata/__next._head.txt +1 -1
- pycharter/ui/static/static/metadata/__next._index.txt +1 -1
- pycharter/ui/static/static/metadata/__next._tree.txt +1 -1
- pycharter/ui/static/static/metadata/__next.metadata.__PAGE__.txt +1 -1
- pycharter/ui/static/static/metadata/__next.metadata.txt +1 -1
- pycharter/ui/static/static/metadata/index.html +1 -1
- pycharter/ui/static/static/metadata/index.txt +1 -1
- pycharter/ui/static/static/quality/__next._full.txt +2 -2
- pycharter/ui/static/static/quality/__next._head.txt +1 -1
- pycharter/ui/static/static/quality/__next._index.txt +1 -1
- pycharter/ui/static/static/quality/__next._tree.txt +1 -1
- pycharter/ui/static/static/quality/__next.quality.__PAGE__.txt +2 -2
- pycharter/ui/static/static/quality/__next.quality.txt +1 -1
- pycharter/ui/static/static/quality/index.html +2 -2
- pycharter/ui/static/static/quality/index.txt +2 -2
- pycharter/ui/static/static/rules/__next._full.txt +1 -1
- pycharter/ui/static/static/rules/__next._head.txt +1 -1
- pycharter/ui/static/static/rules/__next._index.txt +1 -1
- pycharter/ui/static/static/rules/__next._tree.txt +1 -1
- pycharter/ui/static/static/rules/__next.rules.__PAGE__.txt +1 -1
- pycharter/ui/static/static/rules/__next.rules.txt +1 -1
- pycharter/ui/static/static/rules/index.html +1 -1
- pycharter/ui/static/static/rules/index.txt +1 -1
- pycharter/ui/static/static/schemas/__next._full.txt +1 -1
- pycharter/ui/static/static/schemas/__next._head.txt +1 -1
- pycharter/ui/static/static/schemas/__next._index.txt +1 -1
- pycharter/ui/static/static/schemas/__next._tree.txt +1 -1
- pycharter/ui/static/static/schemas/__next.schemas.__PAGE__.txt +1 -1
- pycharter/ui/static/static/schemas/__next.schemas.txt +1 -1
- pycharter/ui/static/static/schemas/index.html +1 -1
- pycharter/ui/static/static/schemas/index.txt +1 -1
- pycharter/ui/static/static/settings/__next._full.txt +1 -1
- pycharter/ui/static/static/settings/__next._head.txt +1 -1
- pycharter/ui/static/static/settings/__next._index.txt +1 -1
- pycharter/ui/static/static/settings/__next._tree.txt +1 -1
- pycharter/ui/static/static/settings/__next.settings.__PAGE__.txt +1 -1
- pycharter/ui/static/static/settings/__next.settings.txt +1 -1
- pycharter/ui/static/static/settings/index.html +1 -1
- pycharter/ui/static/static/settings/index.txt +1 -1
- pycharter/ui/static/static/static/404/index.html +1 -1
- pycharter/ui/static/static/static/404.html +1 -1
- pycharter/ui/static/static/static/__next.__PAGE__.txt +1 -1
- pycharter/ui/static/static/static/__next._full.txt +2 -2
- pycharter/ui/static/static/static/__next._head.txt +1 -1
- pycharter/ui/static/static/static/__next._index.txt +2 -2
- pycharter/ui/static/static/static/__next._tree.txt +2 -2
- pycharter/ui/static/static/static/_next/static/chunks/f7d1a90dd75d2572.js +1 -0
- pycharter/ui/static/static/static/_not-found/__next._full.txt +2 -2
- pycharter/ui/static/static/static/_not-found/__next._head.txt +1 -1
- pycharter/ui/static/static/static/_not-found/__next._index.txt +2 -2
- pycharter/ui/static/static/static/_not-found/__next._not-found.__PAGE__.txt +1 -1
- pycharter/ui/static/static/static/_not-found/__next._not-found.txt +1 -1
- pycharter/ui/static/static/static/_not-found/__next._tree.txt +2 -2
- pycharter/ui/static/static/static/_not-found/index.html +1 -1
- pycharter/ui/static/static/static/_not-found/index.txt +2 -2
- pycharter/ui/static/static/static/contracts/__next._full.txt +3 -3
- pycharter/ui/static/static/static/contracts/__next._head.txt +1 -1
- pycharter/ui/static/static/static/contracts/__next._index.txt +2 -2
- pycharter/ui/static/static/static/contracts/__next._tree.txt +2 -2
- pycharter/ui/static/static/static/contracts/__next.contracts.__PAGE__.txt +2 -2
- pycharter/ui/static/static/static/contracts/__next.contracts.txt +1 -1
- pycharter/ui/static/static/static/contracts/index.html +1 -1
- pycharter/ui/static/static/static/contracts/index.txt +3 -3
- pycharter/ui/static/static/static/documentation/__next._full.txt +3 -3
- pycharter/ui/static/static/static/documentation/__next._head.txt +1 -1
- pycharter/ui/static/static/static/documentation/__next._index.txt +2 -2
- pycharter/ui/static/static/static/documentation/__next._tree.txt +2 -2
- pycharter/ui/static/static/static/documentation/__next.documentation.__PAGE__.txt +2 -2
- pycharter/ui/static/static/static/documentation/__next.documentation.txt +1 -1
- pycharter/ui/static/static/static/documentation/index.html +2 -2
- pycharter/ui/static/static/static/documentation/index.txt +3 -3
- pycharter/ui/static/static/static/index.html +1 -1
- pycharter/ui/static/static/static/index.txt +2 -2
- pycharter/ui/static/static/static/metadata/__next._full.txt +2 -2
- pycharter/ui/static/static/static/metadata/__next._head.txt +1 -1
- pycharter/ui/static/static/static/metadata/__next._index.txt +2 -2
- pycharter/ui/static/static/static/metadata/__next._tree.txt +2 -2
- pycharter/ui/static/static/static/metadata/__next.metadata.__PAGE__.txt +1 -1
- pycharter/ui/static/static/static/metadata/__next.metadata.txt +1 -1
- pycharter/ui/static/static/static/metadata/index.html +1 -1
- pycharter/ui/static/static/static/metadata/index.txt +2 -2
- pycharter/ui/static/static/static/quality/__next._full.txt +2 -2
- pycharter/ui/static/static/static/quality/__next._head.txt +1 -1
- pycharter/ui/static/static/static/quality/__next._index.txt +2 -2
- pycharter/ui/static/static/static/quality/__next._tree.txt +2 -2
- pycharter/ui/static/static/static/quality/__next.quality.__PAGE__.txt +1 -1
- pycharter/ui/static/static/static/quality/__next.quality.txt +1 -1
- pycharter/ui/static/static/static/quality/index.html +2 -2
- pycharter/ui/static/static/static/quality/index.txt +2 -2
- pycharter/ui/static/static/static/rules/__next._full.txt +2 -2
- pycharter/ui/static/static/static/rules/__next._head.txt +1 -1
- pycharter/ui/static/static/static/rules/__next._index.txt +2 -2
- pycharter/ui/static/static/static/rules/__next._tree.txt +2 -2
- pycharter/ui/static/static/static/rules/__next.rules.__PAGE__.txt +1 -1
- pycharter/ui/static/static/static/rules/__next.rules.txt +1 -1
- pycharter/ui/static/static/static/rules/index.html +1 -1
- pycharter/ui/static/static/static/rules/index.txt +2 -2
- pycharter/ui/static/static/static/schemas/__next._full.txt +2 -2
- pycharter/ui/static/static/static/schemas/__next._head.txt +1 -1
- pycharter/ui/static/static/static/schemas/__next._index.txt +2 -2
- pycharter/ui/static/static/static/schemas/__next._tree.txt +2 -2
- pycharter/ui/static/static/static/schemas/__next.schemas.__PAGE__.txt +1 -1
- pycharter/ui/static/static/static/schemas/__next.schemas.txt +1 -1
- pycharter/ui/static/static/static/schemas/index.html +1 -1
- pycharter/ui/static/static/static/schemas/index.txt +2 -2
- pycharter/ui/static/static/static/settings/__next._full.txt +2 -2
- pycharter/ui/static/static/static/settings/__next._head.txt +1 -1
- pycharter/ui/static/static/static/settings/__next._index.txt +2 -2
- pycharter/ui/static/static/static/settings/__next._tree.txt +2 -2
- pycharter/ui/static/static/static/settings/__next.settings.__PAGE__.txt +1 -1
- pycharter/ui/static/static/static/settings/__next.settings.txt +1 -1
- pycharter/ui/static/static/static/settings/index.html +1 -1
- pycharter/ui/static/static/static/settings/index.txt +2 -2
- pycharter/ui/static/static/static/static/.gitkeep +0 -0
- pycharter/ui/static/static/static/static/404/index.html +1 -0
- pycharter/ui/static/static/static/static/404.html +1 -0
- pycharter/ui/static/static/static/static/__next.__PAGE__.txt +10 -0
- pycharter/ui/static/static/static/static/__next._full.txt +30 -0
- pycharter/ui/static/static/static/static/__next._head.txt +7 -0
- pycharter/ui/static/static/static/static/__next._index.txt +9 -0
- pycharter/ui/static/static/static/static/__next._tree.txt +2 -0
- pycharter/ui/static/static/static/static/_next/static/chunks/222442f6da32302a.js +1 -0
- pycharter/ui/static/static/static/static/_next/static/chunks/247eb132b7f7b574.js +1 -0
- pycharter/ui/static/static/static/static/_next/static/chunks/297d55555b71baba.js +1 -0
- pycharter/ui/static/static/static/static/_next/static/chunks/414e77373f8ff61c.js +1 -0
- pycharter/ui/static/static/static/static/_next/static/chunks/652ad0aa26265c47.js +2 -0
- pycharter/ui/static/static/static/static/_next/static/chunks/9c23f44fff36548a.js +1 -0
- pycharter/ui/static/static/static/static/_next/static/chunks/a6dad97d9634a72d.js +1 -0
- pycharter/ui/static/static/static/static/_next/static/chunks/b32a0963684b9933.js +4 -0
- pycharter/ui/static/static/static/static/_next/static/chunks/db913959c675cea6.js +1 -0
- pycharter/ui/static/static/static/static/_next/static/chunks/f2e7afeab1178138.js +1 -0
- pycharter/ui/static/static/static/static/_next/static/chunks/ff1a16fafef87110.js +1 -0
- pycharter/ui/static/static/static/static/_next/static/chunks/turbopack-ffcb7ab6794027ef.js +3 -0
- pycharter/ui/static/static/static/static/_next/static/tNTkVW6puVXC4bAm4WrHl/_buildManifest.js +11 -0
- pycharter/ui/static/static/static/static/_next/static/tNTkVW6puVXC4bAm4WrHl/_clientMiddlewareManifest.json +1 -0
- pycharter/ui/static/static/static/static/_next/static/tNTkVW6puVXC4bAm4WrHl/_ssgManifest.js +1 -0
- pycharter/ui/static/static/static/static/_not-found/__next._full.txt +17 -0
- pycharter/ui/static/static/static/static/_not-found/__next._head.txt +7 -0
- pycharter/ui/static/static/static/static/_not-found/__next._index.txt +9 -0
- pycharter/ui/static/static/static/static/_not-found/__next._not-found.__PAGE__.txt +5 -0
- pycharter/ui/static/static/static/static/_not-found/__next._not-found.txt +4 -0
- pycharter/ui/static/static/static/static/_not-found/__next._tree.txt +2 -0
- pycharter/ui/static/static/static/static/_not-found/index.html +1 -0
- pycharter/ui/static/static/static/static/_not-found/index.txt +17 -0
- pycharter/ui/static/static/static/static/contracts/__next._full.txt +21 -0
- pycharter/ui/static/static/static/static/contracts/__next._head.txt +7 -0
- pycharter/ui/static/static/static/static/contracts/__next._index.txt +9 -0
- pycharter/ui/static/static/static/static/contracts/__next._tree.txt +2 -0
- pycharter/ui/static/static/static/static/contracts/__next.contracts.__PAGE__.txt +9 -0
- pycharter/ui/static/static/static/static/contracts/__next.contracts.txt +4 -0
- pycharter/ui/static/static/static/static/contracts/index.html +1 -0
- pycharter/ui/static/static/static/static/contracts/index.txt +21 -0
- pycharter/ui/static/static/static/static/documentation/__next._full.txt +21 -0
- pycharter/ui/static/static/static/static/documentation/__next._head.txt +7 -0
- pycharter/ui/static/static/static/static/documentation/__next._index.txt +9 -0
- pycharter/ui/static/static/static/static/documentation/__next._tree.txt +2 -0
- pycharter/ui/static/static/static/static/documentation/__next.documentation.__PAGE__.txt +9 -0
- pycharter/ui/static/static/static/static/documentation/__next.documentation.txt +4 -0
- pycharter/ui/static/static/static/static/documentation/index.html +93 -0
- pycharter/ui/static/static/static/static/documentation/index.txt +21 -0
- pycharter/ui/static/static/static/static/index.html +1 -0
- pycharter/ui/static/static/static/static/index.txt +30 -0
- pycharter/ui/static/static/static/static/metadata/__next._full.txt +21 -0
- pycharter/ui/static/static/static/static/metadata/__next._head.txt +7 -0
- pycharter/ui/static/static/static/static/metadata/__next._index.txt +9 -0
- pycharter/ui/static/static/static/static/metadata/__next._tree.txt +2 -0
- pycharter/ui/static/static/static/static/metadata/__next.metadata.__PAGE__.txt +9 -0
- pycharter/ui/static/static/static/static/metadata/__next.metadata.txt +4 -0
- pycharter/ui/static/static/static/static/metadata/index.html +1 -0
- pycharter/ui/static/static/static/static/metadata/index.txt +21 -0
- pycharter/ui/static/static/static/static/quality/__next._full.txt +21 -0
- pycharter/ui/static/static/static/static/quality/__next._head.txt +7 -0
- pycharter/ui/static/static/static/static/quality/__next._index.txt +9 -0
- pycharter/ui/static/static/static/static/quality/__next._tree.txt +2 -0
- pycharter/ui/static/static/static/static/quality/__next.quality.__PAGE__.txt +9 -0
- pycharter/ui/static/static/static/static/quality/__next.quality.txt +4 -0
- pycharter/ui/static/static/static/static/quality/index.html +2 -0
- pycharter/ui/static/static/static/static/quality/index.txt +21 -0
- pycharter/ui/static/static/static/static/rules/__next._full.txt +21 -0
- pycharter/ui/static/static/static/static/rules/__next._head.txt +7 -0
- pycharter/ui/static/static/static/static/rules/__next._index.txt +9 -0
- pycharter/ui/static/static/static/static/rules/__next._tree.txt +2 -0
- pycharter/ui/static/static/static/static/rules/__next.rules.__PAGE__.txt +9 -0
- pycharter/ui/static/static/static/static/rules/__next.rules.txt +4 -0
- pycharter/ui/static/static/static/static/rules/index.html +1 -0
- pycharter/ui/static/static/static/static/rules/index.txt +21 -0
- pycharter/ui/static/static/static/static/schemas/__next._full.txt +21 -0
- pycharter/ui/static/static/static/static/schemas/__next._head.txt +7 -0
- pycharter/ui/static/static/static/static/schemas/__next._index.txt +9 -0
- pycharter/ui/static/static/static/static/schemas/__next._tree.txt +2 -0
- pycharter/ui/static/static/static/static/schemas/__next.schemas.__PAGE__.txt +9 -0
- pycharter/ui/static/static/static/static/schemas/__next.schemas.txt +4 -0
- pycharter/ui/static/static/static/static/schemas/index.html +1 -0
- pycharter/ui/static/static/static/static/schemas/index.txt +21 -0
- pycharter/ui/static/static/static/static/settings/__next._full.txt +21 -0
- pycharter/ui/static/static/static/static/settings/__next._head.txt +7 -0
- pycharter/ui/static/static/static/static/settings/__next._index.txt +9 -0
- pycharter/ui/static/static/static/static/settings/__next._tree.txt +2 -0
- pycharter/ui/static/static/static/static/settings/__next.settings.__PAGE__.txt +9 -0
- pycharter/ui/static/static/static/static/settings/__next.settings.txt +4 -0
- pycharter/ui/static/static/static/static/settings/index.html +1 -0
- pycharter/ui/static/static/static/static/settings/index.txt +21 -0
- pycharter/ui/static/static/static/static/validation/__next._full.txt +21 -0
- pycharter/ui/static/static/static/static/validation/__next._head.txt +7 -0
- pycharter/ui/static/static/static/static/validation/__next._index.txt +9 -0
- pycharter/ui/static/static/static/static/validation/__next._tree.txt +2 -0
- pycharter/ui/static/static/static/static/validation/__next.validation.__PAGE__.txt +9 -0
- pycharter/ui/static/static/static/static/validation/__next.validation.txt +4 -0
- pycharter/ui/static/static/static/static/validation/index.html +1 -0
- pycharter/ui/static/static/static/static/validation/index.txt +21 -0
- pycharter/ui/static/static/static/validation/__next._full.txt +2 -2
- pycharter/ui/static/static/static/validation/__next._head.txt +1 -1
- pycharter/ui/static/static/static/validation/__next._index.txt +2 -2
- pycharter/ui/static/static/static/validation/__next._tree.txt +2 -2
- pycharter/ui/static/static/static/validation/__next.validation.__PAGE__.txt +1 -1
- pycharter/ui/static/static/static/validation/__next.validation.txt +1 -1
- pycharter/ui/static/static/static/validation/index.html +1 -1
- pycharter/ui/static/static/static/validation/index.txt +2 -2
- pycharter/ui/static/static/validation/__next._full.txt +2 -2
- pycharter/ui/static/static/validation/__next._head.txt +1 -1
- pycharter/ui/static/static/validation/__next._index.txt +1 -1
- pycharter/ui/static/static/validation/__next._tree.txt +1 -1
- pycharter/ui/static/static/validation/__next.validation.__PAGE__.txt +2 -2
- pycharter/ui/static/static/validation/__next.validation.txt +1 -1
- pycharter/ui/static/static/validation/index.html +1 -1
- pycharter/ui/static/static/validation/index.txt +2 -2
- pycharter/ui/static/validation/__next._full.txt +7 -7
- pycharter/ui/static/validation/__next._head.txt +1 -1
- pycharter/ui/static/validation/__next._index.txt +6 -6
- pycharter/ui/static/validation/__next._tree.txt +2 -2
- pycharter/ui/static/validation/__next.validation.__PAGE__.txt +2 -2
- pycharter/ui/static/validation/__next.validation.txt +1 -1
- pycharter/ui/static/validation/index.html +1 -1
- pycharter/ui/static/validation/index.txt +7 -7
- {pycharter-0.0.25.dist-info → pycharter-0.0.26.dist-info}/METADATA +57 -26
- pycharter-0.0.26.dist-info/RECORD +702 -0
- pycharter/etl_generator/config_loader.py +0 -394
- pycharter/etl_generator/loaders/cloud.py +0 -87
- pycharter/etl_generator/loaders/file_loader.py +0 -130
- pycharter/etl_generator/schemas/extract.json +0 -234
- pycharter/etl_generator/schemas/load.json +0 -202
- pycharter/etl_generator/schemas/pipeline.json +0 -94
- pycharter/etl_generator/schemas/transform.json +0 -171
- pycharter-0.0.25.dist-info/RECORD +0 -572
- /pycharter/ui/static/_next/static/{2gKjNv6YvE6BcIdFthBLs → YCnlK66gA7FV5vvcixspB}/_buildManifest.js +0 -0
- /pycharter/ui/static/_next/static/{2gKjNv6YvE6BcIdFthBLs → YCnlK66gA7FV5vvcixspB}/_clientMiddlewareManifest.json +0 -0
- /pycharter/ui/static/_next/static/{2gKjNv6YvE6BcIdFthBLs → YCnlK66gA7FV5vvcixspB}/_ssgManifest.js +0 -0
- /pycharter/ui/static/static/_next/static/{0rYA78L88aUyD2Uh38hhX → 2gKjNv6YvE6BcIdFthBLs}/_buildManifest.js +0 -0
- /pycharter/ui/static/static/_next/static/{0rYA78L88aUyD2Uh38hhX → 2gKjNv6YvE6BcIdFthBLs}/_clientMiddlewareManifest.json +0 -0
- /pycharter/ui/static/static/_next/static/{0rYA78L88aUyD2Uh38hhX → 2gKjNv6YvE6BcIdFthBLs}/_ssgManifest.js +0 -0
- /pycharter/ui/static/{_next → static/_next}/static/chunks/26dfc590f7714c03.js +0 -0
- /pycharter/ui/static/{_next → static/_next}/static/chunks/34d289e6db2ef551.js +0 -0
- /pycharter/ui/static/{_next → static/_next}/static/chunks/99508d9d5869cc27.js +0 -0
- /pycharter/ui/static/{_next → static/_next}/static/chunks/b313c35a6ba76574.js +0 -0
- /pycharter/ui/static/static/static/_next/static/{tNTkVW6puVXC4bAm4WrHl → 0rYA78L88aUyD2Uh38hhX}/_buildManifest.js +0 -0
- /pycharter/ui/static/static/static/_next/static/{tNTkVW6puVXC4bAm4WrHl → 0rYA78L88aUyD2Uh38hhX}/_clientMiddlewareManifest.json +0 -0
- /pycharter/ui/static/static/static/_next/static/{tNTkVW6puVXC4bAm4WrHl → 0rYA78L88aUyD2Uh38hhX}/_ssgManifest.js +0 -0
- /pycharter/ui/static/{_next → static/static/_next}/static/chunks/13d4a0fbd74c1ee4.js +0 -0
- /pycharter/ui/static/{_next → static/static/_next}/static/chunks/2edb43b48432ac04.js +0 -0
- /pycharter/ui/static/static/{_next → static/_next}/static/chunks/c4fa4f4114b7c352.js +0 -0
- /pycharter/ui/static/{_next → static/static/_next}/static/chunks/d2363397e1b2bcab.css +0 -0
- /pycharter/ui/static/{_next → static/static/static/_next}/static/chunks/2ab439ce003cd691.js +0 -0
- /pycharter/ui/static/{_next → static/static/static/_next}/static/chunks/49ca65abd26ae49e.js +0 -0
- /pycharter/ui/static/static/static/{_next → static/_next}/static/chunks/4e310fe5005770a3.css +0 -0
- /pycharter/ui/static/static/{_next → static/static/_next}/static/chunks/5e04d10c4a7b58a3.js +0 -0
- /pycharter/ui/static/static/static/{_next → static/_next}/static/chunks/5fc14c00a2779dc5.js +0 -0
- /pycharter/ui/static/static/{_next → static/static/_next}/static/chunks/75d88a058d8ffaa6.js +0 -0
- /pycharter/ui/static/static/{_next → static/static/_next}/static/chunks/8c89634cf6bad76f.js +0 -0
- /pycharter/ui/static/{_next → static/static/static/_next}/static/chunks/9667e7a3d359eb39.js +0 -0
- /pycharter/ui/static/static/static/{_next → static/_next}/static/chunks/b584574fdc8ab13e.js +0 -0
- /pycharter/ui/static/{_next → static/static/static/_next}/static/chunks/c69f6cba366bd988.js +0 -0
- /pycharter/ui/static/static/static/{_next → static/_next}/static/chunks/d5989c94d3614b3a.js +0 -0
- /pycharter/ui/static/{_next → static/static/static/_next}/static/chunks/f061a4be97bfc3b3.js +0 -0
- {pycharter-0.0.25.dist-info → pycharter-0.0.26.dist-info}/WHEEL +0 -0
- {pycharter-0.0.25.dist-info → pycharter-0.0.26.dist-info}/entry_points.txt +0 -0
- {pycharter-0.0.25.dist-info → pycharter-0.0.26.dist-info}/licenses/LICENSE +0 -0
- {pycharter-0.0.25.dist-info → pycharter-0.0.26.dist-info}/top_level.txt +0 -0
|
@@ -1,394 +0,0 @@
|
|
|
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
|
-
)
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Cloud storage loader for ETL pipelines.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import time
|
|
6
|
-
from typing import Any, Dict, List, Optional
|
|
7
|
-
|
|
8
|
-
from pycharter.etl_generator.loaders.base import BaseLoader
|
|
9
|
-
from pycharter.etl_generator.loaders.cloud_storage_loader import load_to_cloud_storage
|
|
10
|
-
from pycharter.etl_generator.result import LoadResult
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class CloudStorageLoader(BaseLoader):
|
|
14
|
-
"""
|
|
15
|
-
Loader for cloud storage (S3, GCS, Azure).
|
|
16
|
-
|
|
17
|
-
Supports JSON, CSV, Parquet, and JSONL formats.
|
|
18
|
-
|
|
19
|
-
Example:
|
|
20
|
-
>>> loader = CloudStorageLoader(
|
|
21
|
-
... provider="s3",
|
|
22
|
-
... bucket="my-bucket",
|
|
23
|
-
... path="output/data.json",
|
|
24
|
-
... format="json",
|
|
25
|
-
... )
|
|
26
|
-
>>> result = await loader.load(data)
|
|
27
|
-
"""
|
|
28
|
-
|
|
29
|
-
def __init__(
|
|
30
|
-
self,
|
|
31
|
-
provider: str,
|
|
32
|
-
bucket: str,
|
|
33
|
-
path: str,
|
|
34
|
-
credentials: Optional[Dict[str, Any]] = None,
|
|
35
|
-
file_format: str = "json",
|
|
36
|
-
):
|
|
37
|
-
self.provider = provider
|
|
38
|
-
self.bucket = bucket
|
|
39
|
-
self.path = path
|
|
40
|
-
self.credentials = credentials
|
|
41
|
-
self.file_format = file_format
|
|
42
|
-
|
|
43
|
-
@classmethod
|
|
44
|
-
def from_config(cls, config: Dict[str, Any]) -> "CloudStorageLoader":
|
|
45
|
-
"""Create loader from configuration dict."""
|
|
46
|
-
storage_config = config.get("storage", {})
|
|
47
|
-
return cls(
|
|
48
|
-
provider=storage_config.get("provider") or config.get("provider"),
|
|
49
|
-
bucket=storage_config.get("bucket") or config.get("bucket"),
|
|
50
|
-
path=storage_config.get("path") or config.get("path"),
|
|
51
|
-
credentials=storage_config.get("credentials") or config.get("credentials"),
|
|
52
|
-
file_format=config.get("format", "json"),
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
async def load(self, data: List[Dict[str, Any]], **params) -> LoadResult:
|
|
56
|
-
"""Load data to cloud storage."""
|
|
57
|
-
start_time = time.time()
|
|
58
|
-
|
|
59
|
-
if not data:
|
|
60
|
-
return LoadResult(success=True, rows_loaded=0)
|
|
61
|
-
|
|
62
|
-
try:
|
|
63
|
-
load_config = {
|
|
64
|
-
"storage": {
|
|
65
|
-
"provider": self.provider,
|
|
66
|
-
"bucket": self.bucket,
|
|
67
|
-
"path": self.path,
|
|
68
|
-
"credentials": self.credentials,
|
|
69
|
-
},
|
|
70
|
-
"format": self.file_format,
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
result = load_to_cloud_storage(data, load_config)
|
|
74
|
-
|
|
75
|
-
duration = time.time() - start_time
|
|
76
|
-
return LoadResult(
|
|
77
|
-
success=True,
|
|
78
|
-
rows_loaded=result.get("written", 0),
|
|
79
|
-
duration_seconds=duration,
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
except Exception as e:
|
|
83
|
-
return LoadResult(
|
|
84
|
-
success=False,
|
|
85
|
-
error=str(e),
|
|
86
|
-
duration_seconds=time.time() - start_time,
|
|
87
|
-
)
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
File-based loader for ETL orchestrator.
|
|
3
|
-
|
|
4
|
-
Writes transformed data to local files in JSON, CSV, Parquet, or JSONL format.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import io
|
|
8
|
-
import json
|
|
9
|
-
import logging
|
|
10
|
-
from pathlib import Path
|
|
11
|
-
from typing import Any, Dict, List, Optional
|
|
12
|
-
|
|
13
|
-
from pycharter.utils.value_injector import resolve_values
|
|
14
|
-
|
|
15
|
-
logger = logging.getLogger(__name__)
|
|
16
|
-
|
|
17
|
-
SUPPORTED_FORMATS = ("json", "csv", "parquet", "jsonl")
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def load_to_file(
|
|
21
|
-
data: List[Dict[str, Any]],
|
|
22
|
-
load_config: Dict[str, Any],
|
|
23
|
-
contract_dir: Optional[Any] = None,
|
|
24
|
-
config_context: Optional[Dict[str, Any]] = None,
|
|
25
|
-
) -> Dict[str, Any]:
|
|
26
|
-
"""
|
|
27
|
-
Write transformed data to a local file.
|
|
28
|
-
|
|
29
|
-
Load config (destination_type: file):
|
|
30
|
-
file_path: Path to output file (required). Supports ${VAR} resolution.
|
|
31
|
-
format: json | csv | parquet | jsonl (default: json)
|
|
32
|
-
write_mode: overwrite | append (default: overwrite).
|
|
33
|
-
append: for jsonl/csv, appends lines; for json, read-merge-write (array concat).
|
|
34
|
-
|
|
35
|
-
Returns:
|
|
36
|
-
Dict with keys: written, total, path, format
|
|
37
|
-
"""
|
|
38
|
-
source_file = str(contract_dir / "load.yaml") if contract_dir else None
|
|
39
|
-
file_path = load_config.get("file_path")
|
|
40
|
-
if not file_path:
|
|
41
|
-
raise ValueError(
|
|
42
|
-
"File loader requires 'file_path' in load configuration. "
|
|
43
|
-
"Example: file_path: ./output/data.json"
|
|
44
|
-
)
|
|
45
|
-
file_path = resolve_values(
|
|
46
|
-
file_path, context=config_context, source_file=source_file
|
|
47
|
-
)
|
|
48
|
-
path = Path(file_path)
|
|
49
|
-
|
|
50
|
-
fmt = (load_config.get("format") or "json").lower()
|
|
51
|
-
if fmt not in SUPPORTED_FORMATS:
|
|
52
|
-
raise ValueError(
|
|
53
|
-
f"File loader format must be one of {SUPPORTED_FORMATS}, got '{fmt}'"
|
|
54
|
-
)
|
|
55
|
-
write_mode = (load_config.get("write_mode") or "overwrite").lower()
|
|
56
|
-
if write_mode not in ("overwrite", "append"):
|
|
57
|
-
raise ValueError(
|
|
58
|
-
"File loader write_mode must be 'overwrite' or 'append', "
|
|
59
|
-
f"got '{write_mode}'"
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
path.parent.mkdir(parents=True, exist_ok=True)
|
|
63
|
-
|
|
64
|
-
if fmt == "json":
|
|
65
|
-
_write_json(data, path, write_mode)
|
|
66
|
-
elif fmt == "jsonl":
|
|
67
|
-
_write_jsonl(data, path, write_mode)
|
|
68
|
-
elif fmt == "csv":
|
|
69
|
-
_write_csv(data, path, write_mode)
|
|
70
|
-
elif fmt == "parquet":
|
|
71
|
-
_write_parquet(data, path, write_mode)
|
|
72
|
-
|
|
73
|
-
logger.info(f"File loader wrote {len(data)} records to {path} ({fmt})")
|
|
74
|
-
return {"written": len(data), "total": len(data), "path": str(path), "format": fmt}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def _write_json(
|
|
78
|
-
data: List[Dict[str, Any]], path: Path, write_mode: str
|
|
79
|
-
) -> None:
|
|
80
|
-
if write_mode == "append" and path.exists():
|
|
81
|
-
with open(path, "r", encoding="utf-8") as f:
|
|
82
|
-
existing = json.load(f)
|
|
83
|
-
if isinstance(existing, list):
|
|
84
|
-
data = existing + data
|
|
85
|
-
else:
|
|
86
|
-
data = [existing] + data
|
|
87
|
-
with open(path, "w", encoding="utf-8") as f:
|
|
88
|
-
json.dump(data, f, indent=2, default=str)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
def _write_jsonl(
|
|
92
|
-
data: List[Dict[str, Any]], path: Path, write_mode: str
|
|
93
|
-
) -> None:
|
|
94
|
-
mode = "a" if write_mode == "append" and path.exists() else "w"
|
|
95
|
-
with open(path, mode, encoding="utf-8") as f:
|
|
96
|
-
for record in data:
|
|
97
|
-
f.write(json.dumps(record, default=str) + "\n")
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
def _write_csv(
|
|
101
|
-
data: List[Dict[str, Any]], path: Path, write_mode: str
|
|
102
|
-
) -> None:
|
|
103
|
-
if not data:
|
|
104
|
-
return
|
|
105
|
-
import csv
|
|
106
|
-
|
|
107
|
-
mode = "a" if write_mode == "append" and path.exists() else "w"
|
|
108
|
-
newfile = mode == "w"
|
|
109
|
-
with open(path, mode, encoding="utf-8", newline="") as f:
|
|
110
|
-
writer = csv.DictWriter(f, fieldnames=data[0].keys())
|
|
111
|
-
if newfile:
|
|
112
|
-
writer.writeheader()
|
|
113
|
-
writer.writerows(data)
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
def _write_parquet(
|
|
117
|
-
data: List[Dict[str, Any]], path: Path, write_mode: str
|
|
118
|
-
) -> None:
|
|
119
|
-
try:
|
|
120
|
-
import pandas as pd
|
|
121
|
-
except ImportError as e:
|
|
122
|
-
raise ImportError(
|
|
123
|
-
"pandas is required for Parquet file load. "
|
|
124
|
-
"Install with: pip install pandas pyarrow"
|
|
125
|
-
) from e
|
|
126
|
-
df = pd.DataFrame(data)
|
|
127
|
-
if write_mode == "append" and path.exists():
|
|
128
|
-
existing = pd.read_parquet(path)
|
|
129
|
-
df = pd.concat([existing, df], ignore_index=True)
|
|
130
|
-
df.to_parquet(path, index=False)
|