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,385 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Quality Tracking Store - Storage backends for metrics.
|
|
3
|
+
|
|
4
|
+
Provides Protocol definition and implementations for storing
|
|
5
|
+
validation metrics.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import sqlite3
|
|
10
|
+
import threading
|
|
11
|
+
from collections import defaultdict
|
|
12
|
+
from datetime import datetime, timedelta
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any, Dict, List, Optional, Protocol
|
|
15
|
+
|
|
16
|
+
from pycharter.quality.tracking.models import MetricsFilter, MetricsSummary, ValidationMetric
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class MetricsStore(Protocol):
|
|
20
|
+
"""
|
|
21
|
+
Protocol for metrics storage backends.
|
|
22
|
+
|
|
23
|
+
Implementations must provide methods for storing, querying,
|
|
24
|
+
and summarizing validation metrics.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def store(self, metric: ValidationMetric) -> None:
|
|
28
|
+
"""Store a validation metric."""
|
|
29
|
+
...
|
|
30
|
+
|
|
31
|
+
def query(self, filters: MetricsFilter) -> List[ValidationMetric]:
|
|
32
|
+
"""Query metrics with filters."""
|
|
33
|
+
...
|
|
34
|
+
|
|
35
|
+
def get_summary(
|
|
36
|
+
self, schema_name: str, window_hours: int = 24
|
|
37
|
+
) -> MetricsSummary:
|
|
38
|
+
"""Get aggregated summary for a schema within a time window."""
|
|
39
|
+
...
|
|
40
|
+
|
|
41
|
+
def delete(self, metric_id: str) -> bool:
|
|
42
|
+
"""Delete a metric by ID. Returns True if deleted."""
|
|
43
|
+
...
|
|
44
|
+
|
|
45
|
+
def clear(self, schema_name: Optional[str] = None) -> int:
|
|
46
|
+
"""Clear metrics. If schema_name provided, only clear that schema. Returns count deleted."""
|
|
47
|
+
...
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class InMemoryMetricsStore:
|
|
51
|
+
"""
|
|
52
|
+
In-memory metrics store for testing and development.
|
|
53
|
+
|
|
54
|
+
Stores metrics in memory. Data is lost when the process ends.
|
|
55
|
+
Thread-safe implementation.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def __init__(self, max_metrics: int = 10000):
|
|
59
|
+
"""
|
|
60
|
+
Initialize in-memory store.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
max_metrics: Maximum number of metrics to store (oldest removed when exceeded)
|
|
64
|
+
"""
|
|
65
|
+
self._metrics: List[ValidationMetric] = []
|
|
66
|
+
self._lock = threading.Lock()
|
|
67
|
+
self._max_metrics = max_metrics
|
|
68
|
+
|
|
69
|
+
def store(self, metric: ValidationMetric) -> None:
|
|
70
|
+
"""Store a validation metric."""
|
|
71
|
+
with self._lock:
|
|
72
|
+
self._metrics.append(metric)
|
|
73
|
+
# Remove oldest if over limit
|
|
74
|
+
if len(self._metrics) > self._max_metrics:
|
|
75
|
+
self._metrics = self._metrics[-self._max_metrics :]
|
|
76
|
+
|
|
77
|
+
def query(self, filters: MetricsFilter) -> List[ValidationMetric]:
|
|
78
|
+
"""Query metrics with filters."""
|
|
79
|
+
with self._lock:
|
|
80
|
+
results = self._metrics.copy()
|
|
81
|
+
|
|
82
|
+
# Apply filters
|
|
83
|
+
if filters.schema_name:
|
|
84
|
+
results = [m for m in results if m.schema_name == filters.schema_name]
|
|
85
|
+
if filters.version:
|
|
86
|
+
results = [m for m in results if m.version == filters.version]
|
|
87
|
+
if filters.since:
|
|
88
|
+
results = [m for m in results if m.timestamp >= filters.since]
|
|
89
|
+
if filters.until:
|
|
90
|
+
results = [m for m in results if m.timestamp <= filters.until]
|
|
91
|
+
if filters.min_validity_rate is not None:
|
|
92
|
+
results = [m for m in results if m.validity_rate >= filters.min_validity_rate]
|
|
93
|
+
|
|
94
|
+
# Sort by timestamp descending (most recent first)
|
|
95
|
+
results.sort(key=lambda m: m.timestamp, reverse=True)
|
|
96
|
+
|
|
97
|
+
# Apply pagination
|
|
98
|
+
if filters.offset:
|
|
99
|
+
results = results[filters.offset :]
|
|
100
|
+
if filters.limit:
|
|
101
|
+
results = results[: filters.limit]
|
|
102
|
+
|
|
103
|
+
return results
|
|
104
|
+
|
|
105
|
+
def get_summary(
|
|
106
|
+
self, schema_name: str, window_hours: int = 24
|
|
107
|
+
) -> MetricsSummary:
|
|
108
|
+
"""Get aggregated summary for a schema within a time window."""
|
|
109
|
+
cutoff = datetime.utcnow() - timedelta(hours=window_hours)
|
|
110
|
+
|
|
111
|
+
filters = MetricsFilter(schema_name=schema_name, since=cutoff, limit=0)
|
|
112
|
+
metrics = self.query(filters)
|
|
113
|
+
|
|
114
|
+
if not metrics:
|
|
115
|
+
return MetricsSummary(
|
|
116
|
+
schema_name=schema_name,
|
|
117
|
+
period_start=cutoff,
|
|
118
|
+
period_end=datetime.utcnow(),
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
return self._compute_summary(schema_name, metrics, cutoff, datetime.utcnow())
|
|
122
|
+
|
|
123
|
+
def _compute_summary(
|
|
124
|
+
self,
|
|
125
|
+
schema_name: str,
|
|
126
|
+
metrics: List[ValidationMetric],
|
|
127
|
+
period_start: datetime,
|
|
128
|
+
period_end: datetime,
|
|
129
|
+
) -> MetricsSummary:
|
|
130
|
+
"""Compute summary statistics from a list of metrics."""
|
|
131
|
+
if not metrics:
|
|
132
|
+
return MetricsSummary(
|
|
133
|
+
schema_name=schema_name,
|
|
134
|
+
period_start=period_start,
|
|
135
|
+
period_end=period_end,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
total_validations = len(metrics)
|
|
139
|
+
total_records = sum(m.record_count for m in metrics)
|
|
140
|
+
total_valid = sum(m.valid_count for m in metrics)
|
|
141
|
+
total_errors = sum(m.error_count for m in metrics)
|
|
142
|
+
|
|
143
|
+
validity_rates = [m.validity_rate for m in metrics]
|
|
144
|
+
avg_validity_rate = sum(validity_rates) / len(validity_rates)
|
|
145
|
+
min_validity_rate = min(validity_rates)
|
|
146
|
+
max_validity_rate = max(validity_rates)
|
|
147
|
+
|
|
148
|
+
completeness_values = [m.completeness for m in metrics]
|
|
149
|
+
avg_completeness = sum(completeness_values) / len(completeness_values)
|
|
150
|
+
|
|
151
|
+
durations = [m.duration_ms for m in metrics]
|
|
152
|
+
avg_duration_ms = sum(durations) / len(durations)
|
|
153
|
+
|
|
154
|
+
# Aggregate error types
|
|
155
|
+
error_counts: Dict[str, int] = defaultdict(int)
|
|
156
|
+
for m in metrics:
|
|
157
|
+
for error_type, count in m.errors_by_type.items():
|
|
158
|
+
error_counts[error_type] += count
|
|
159
|
+
|
|
160
|
+
# Get top 10 error types
|
|
161
|
+
top_errors = dict(
|
|
162
|
+
sorted(error_counts.items(), key=lambda x: x[1], reverse=True)[:10]
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
return MetricsSummary(
|
|
166
|
+
schema_name=schema_name,
|
|
167
|
+
period_start=period_start,
|
|
168
|
+
period_end=period_end,
|
|
169
|
+
total_validations=total_validations,
|
|
170
|
+
total_records=total_records,
|
|
171
|
+
total_valid=total_valid,
|
|
172
|
+
total_errors=total_errors,
|
|
173
|
+
avg_validity_rate=avg_validity_rate,
|
|
174
|
+
min_validity_rate=min_validity_rate,
|
|
175
|
+
max_validity_rate=max_validity_rate,
|
|
176
|
+
avg_completeness=avg_completeness,
|
|
177
|
+
avg_duration_ms=avg_duration_ms,
|
|
178
|
+
top_error_types=top_errors,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
def delete(self, metric_id: str) -> bool:
|
|
182
|
+
"""Delete a metric by ID."""
|
|
183
|
+
with self._lock:
|
|
184
|
+
original_len = len(self._metrics)
|
|
185
|
+
self._metrics = [m for m in self._metrics if m.id != metric_id]
|
|
186
|
+
return len(self._metrics) < original_len
|
|
187
|
+
|
|
188
|
+
def clear(self, schema_name: Optional[str] = None) -> int:
|
|
189
|
+
"""Clear metrics."""
|
|
190
|
+
with self._lock:
|
|
191
|
+
if schema_name:
|
|
192
|
+
original_len = len(self._metrics)
|
|
193
|
+
self._metrics = [m for m in self._metrics if m.schema_name != schema_name]
|
|
194
|
+
return original_len - len(self._metrics)
|
|
195
|
+
else:
|
|
196
|
+
count = len(self._metrics)
|
|
197
|
+
self._metrics = []
|
|
198
|
+
return count
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class SQLiteMetricsStore:
|
|
202
|
+
"""
|
|
203
|
+
SQLite-based persistent metrics store.
|
|
204
|
+
|
|
205
|
+
Stores metrics in a SQLite database for persistence across restarts.
|
|
206
|
+
Thread-safe implementation.
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
def __init__(self, db_path: str = "metrics.db"):
|
|
210
|
+
"""
|
|
211
|
+
Initialize SQLite store.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
db_path: Path to SQLite database file
|
|
215
|
+
"""
|
|
216
|
+
self._db_path = db_path
|
|
217
|
+
self._lock = threading.Lock()
|
|
218
|
+
self._init_db()
|
|
219
|
+
|
|
220
|
+
def _init_db(self) -> None:
|
|
221
|
+
"""Initialize database schema."""
|
|
222
|
+
with self._get_connection() as conn:
|
|
223
|
+
conn.execute("""
|
|
224
|
+
CREATE TABLE IF NOT EXISTS validation_metrics (
|
|
225
|
+
id TEXT PRIMARY KEY,
|
|
226
|
+
schema_name TEXT NOT NULL,
|
|
227
|
+
version TEXT NOT NULL,
|
|
228
|
+
timestamp TEXT NOT NULL,
|
|
229
|
+
record_count INTEGER DEFAULT 0,
|
|
230
|
+
valid_count INTEGER DEFAULT 0,
|
|
231
|
+
error_count INTEGER DEFAULT 0,
|
|
232
|
+
validity_rate REAL DEFAULT 1.0,
|
|
233
|
+
completeness REAL DEFAULT 1.0,
|
|
234
|
+
field_completeness TEXT DEFAULT '{}',
|
|
235
|
+
duration_ms REAL DEFAULT 0.0,
|
|
236
|
+
errors_by_type TEXT DEFAULT '{}',
|
|
237
|
+
metadata TEXT DEFAULT '{}'
|
|
238
|
+
)
|
|
239
|
+
""")
|
|
240
|
+
conn.execute("""
|
|
241
|
+
CREATE INDEX IF NOT EXISTS idx_schema_name
|
|
242
|
+
ON validation_metrics(schema_name)
|
|
243
|
+
""")
|
|
244
|
+
conn.execute("""
|
|
245
|
+
CREATE INDEX IF NOT EXISTS idx_timestamp
|
|
246
|
+
ON validation_metrics(timestamp)
|
|
247
|
+
""")
|
|
248
|
+
conn.commit()
|
|
249
|
+
|
|
250
|
+
def _get_connection(self) -> sqlite3.Connection:
|
|
251
|
+
"""Get database connection."""
|
|
252
|
+
return sqlite3.connect(self._db_path)
|
|
253
|
+
|
|
254
|
+
def store(self, metric: ValidationMetric) -> None:
|
|
255
|
+
"""Store a validation metric."""
|
|
256
|
+
with self._lock:
|
|
257
|
+
with self._get_connection() as conn:
|
|
258
|
+
conn.execute(
|
|
259
|
+
"""
|
|
260
|
+
INSERT OR REPLACE INTO validation_metrics
|
|
261
|
+
(id, schema_name, version, timestamp, record_count, valid_count,
|
|
262
|
+
error_count, validity_rate, completeness, field_completeness,
|
|
263
|
+
duration_ms, errors_by_type, metadata)
|
|
264
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
265
|
+
""",
|
|
266
|
+
(
|
|
267
|
+
metric.id,
|
|
268
|
+
metric.schema_name,
|
|
269
|
+
metric.version,
|
|
270
|
+
metric.timestamp.isoformat(),
|
|
271
|
+
metric.record_count,
|
|
272
|
+
metric.valid_count,
|
|
273
|
+
metric.error_count,
|
|
274
|
+
metric.validity_rate,
|
|
275
|
+
metric.completeness,
|
|
276
|
+
json.dumps(metric.field_completeness),
|
|
277
|
+
metric.duration_ms,
|
|
278
|
+
json.dumps(metric.errors_by_type),
|
|
279
|
+
json.dumps(metric.metadata),
|
|
280
|
+
),
|
|
281
|
+
)
|
|
282
|
+
conn.commit()
|
|
283
|
+
|
|
284
|
+
def query(self, filters: MetricsFilter) -> List[ValidationMetric]:
|
|
285
|
+
"""Query metrics with filters."""
|
|
286
|
+
conditions = []
|
|
287
|
+
params: List[Any] = []
|
|
288
|
+
|
|
289
|
+
if filters.schema_name:
|
|
290
|
+
conditions.append("schema_name = ?")
|
|
291
|
+
params.append(filters.schema_name)
|
|
292
|
+
if filters.version:
|
|
293
|
+
conditions.append("version = ?")
|
|
294
|
+
params.append(filters.version)
|
|
295
|
+
if filters.since:
|
|
296
|
+
conditions.append("timestamp >= ?")
|
|
297
|
+
params.append(filters.since.isoformat())
|
|
298
|
+
if filters.until:
|
|
299
|
+
conditions.append("timestamp <= ?")
|
|
300
|
+
params.append(filters.until.isoformat())
|
|
301
|
+
if filters.min_validity_rate is not None:
|
|
302
|
+
conditions.append("validity_rate >= ?")
|
|
303
|
+
params.append(filters.min_validity_rate)
|
|
304
|
+
|
|
305
|
+
where_clause = " AND ".join(conditions) if conditions else "1=1"
|
|
306
|
+
query = f"""
|
|
307
|
+
SELECT * FROM validation_metrics
|
|
308
|
+
WHERE {where_clause}
|
|
309
|
+
ORDER BY timestamp DESC
|
|
310
|
+
LIMIT ? OFFSET ?
|
|
311
|
+
"""
|
|
312
|
+
params.extend([filters.limit, filters.offset])
|
|
313
|
+
|
|
314
|
+
with self._lock:
|
|
315
|
+
with self._get_connection() as conn:
|
|
316
|
+
conn.row_factory = sqlite3.Row
|
|
317
|
+
cursor = conn.execute(query, params)
|
|
318
|
+
rows = cursor.fetchall()
|
|
319
|
+
|
|
320
|
+
return [self._row_to_metric(row) for row in rows]
|
|
321
|
+
|
|
322
|
+
def _row_to_metric(self, row: sqlite3.Row) -> ValidationMetric:
|
|
323
|
+
"""Convert database row to ValidationMetric."""
|
|
324
|
+
return ValidationMetric(
|
|
325
|
+
id=row["id"],
|
|
326
|
+
schema_name=row["schema_name"],
|
|
327
|
+
version=row["version"],
|
|
328
|
+
timestamp=datetime.fromisoformat(row["timestamp"]),
|
|
329
|
+
record_count=row["record_count"],
|
|
330
|
+
valid_count=row["valid_count"],
|
|
331
|
+
error_count=row["error_count"],
|
|
332
|
+
validity_rate=row["validity_rate"],
|
|
333
|
+
completeness=row["completeness"],
|
|
334
|
+
field_completeness=json.loads(row["field_completeness"]),
|
|
335
|
+
duration_ms=row["duration_ms"],
|
|
336
|
+
errors_by_type=json.loads(row["errors_by_type"]),
|
|
337
|
+
metadata=json.loads(row["metadata"]),
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
def get_summary(
|
|
341
|
+
self, schema_name: str, window_hours: int = 24
|
|
342
|
+
) -> MetricsSummary:
|
|
343
|
+
"""Get aggregated summary for a schema within a time window."""
|
|
344
|
+
cutoff = datetime.utcnow() - timedelta(hours=window_hours)
|
|
345
|
+
|
|
346
|
+
filters = MetricsFilter(
|
|
347
|
+
schema_name=schema_name, since=cutoff, limit=10000 # Get all for summary
|
|
348
|
+
)
|
|
349
|
+
metrics = self.query(filters)
|
|
350
|
+
|
|
351
|
+
if not metrics:
|
|
352
|
+
return MetricsSummary(
|
|
353
|
+
schema_name=schema_name,
|
|
354
|
+
period_start=cutoff,
|
|
355
|
+
period_end=datetime.utcnow(),
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
# Reuse InMemoryMetricsStore's compute logic
|
|
359
|
+
return InMemoryMetricsStore()._compute_summary(
|
|
360
|
+
schema_name, metrics, cutoff, datetime.utcnow()
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
def delete(self, metric_id: str) -> bool:
|
|
364
|
+
"""Delete a metric by ID."""
|
|
365
|
+
with self._lock:
|
|
366
|
+
with self._get_connection() as conn:
|
|
367
|
+
cursor = conn.execute(
|
|
368
|
+
"DELETE FROM validation_metrics WHERE id = ?", (metric_id,)
|
|
369
|
+
)
|
|
370
|
+
conn.commit()
|
|
371
|
+
return cursor.rowcount > 0
|
|
372
|
+
|
|
373
|
+
def clear(self, schema_name: Optional[str] = None) -> int:
|
|
374
|
+
"""Clear metrics."""
|
|
375
|
+
with self._lock:
|
|
376
|
+
with self._get_connection() as conn:
|
|
377
|
+
if schema_name:
|
|
378
|
+
cursor = conn.execute(
|
|
379
|
+
"DELETE FROM validation_metrics WHERE schema_name = ?",
|
|
380
|
+
(schema_name,),
|
|
381
|
+
)
|
|
382
|
+
else:
|
|
383
|
+
cursor = conn.execute("DELETE FROM validation_metrics")
|
|
384
|
+
conn.commit()
|
|
385
|
+
return cursor.rowcount
|
|
@@ -19,23 +19,33 @@ in pycharter. It can be instantiated with contract artifacts from various source
|
|
|
19
19
|
>>> validator = Validator(store=store, schema_id="user_schema")
|
|
20
20
|
>>> result = validator.validate({"name": "Alice", "age": 30})
|
|
21
21
|
>>>
|
|
22
|
-
>>> #
|
|
23
|
-
>>>
|
|
22
|
+
>>> # Using builder pattern
|
|
23
|
+
>>> from pycharter.runtime_validator import ValidatorBuilder
|
|
24
|
+
>>> validator = (
|
|
25
|
+
... ValidatorBuilder()
|
|
26
|
+
... .from_directory("data/contracts/user")
|
|
27
|
+
... .strict()
|
|
28
|
+
... .with_quality_checks()
|
|
29
|
+
... .build()
|
|
30
|
+
... )
|
|
24
31
|
>>> result = validator.validate({"name": "Alice", "age": 30})
|
|
32
|
+
>>> print(result.quality.completeness)
|
|
25
33
|
|
|
26
34
|
Core Components:
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
35
|
+
- Validator: Primary validation class
|
|
36
|
+
- ValidatorBuilder: Fluent API for constructing validators
|
|
37
|
+
- ValidationResult: Validation result with optional quality metrics
|
|
38
|
+
- QualityMetrics: Data quality metrics
|
|
31
39
|
"""
|
|
32
40
|
|
|
41
|
+
from pycharter.runtime_validator.builder import ValidatorBuilder
|
|
33
42
|
from pycharter.runtime_validator.decorators import (
|
|
34
43
|
validate_input,
|
|
35
44
|
validate_output,
|
|
36
45
|
validate_with_contract as validate_with_contract_decorator,
|
|
37
46
|
)
|
|
38
47
|
from pycharter.runtime_validator.validator_core import (
|
|
48
|
+
QualityMetrics,
|
|
39
49
|
ValidationResult,
|
|
40
50
|
validate,
|
|
41
51
|
validate_batch,
|
|
@@ -54,10 +64,13 @@ from pycharter.runtime_validator.validator import (
|
|
|
54
64
|
)
|
|
55
65
|
|
|
56
66
|
__all__ = [
|
|
57
|
-
# PRIMARY INTERFACE: Validator
|
|
67
|
+
# PRIMARY INTERFACE: Validator and Builder
|
|
58
68
|
"Validator",
|
|
69
|
+
"ValidatorBuilder",
|
|
59
70
|
"create_validator",
|
|
71
|
+
# Result classes
|
|
60
72
|
"ValidationResult",
|
|
73
|
+
"QualityMetrics",
|
|
61
74
|
# Low-level validation functions (for direct model validation)
|
|
62
75
|
"validate",
|
|
63
76
|
"validate_batch",
|