etlplus 0.16.0__tar.gz → 0.16.3__tar.gz
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.
- {etlplus-0.16.0/etlplus.egg-info → etlplus-0.16.3}/PKG-INFO +1 -1
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/api/types.py +32 -11
- etlplus-0.16.3/etlplus/enums.py +144 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/ops/__init__.py +1 -0
- {etlplus-0.16.0/etlplus → etlplus-0.16.3/etlplus/ops}/enums.py +5 -108
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/ops/extract.py +209 -22
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/ops/load.py +140 -34
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/ops/run.py +86 -101
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/ops/transform.py +46 -27
- etlplus-0.16.3/etlplus/ops/types.py +147 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/types.py +3 -101
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/workflow/__init__.py +2 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/workflow/dag.py +23 -1
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/workflow/jobs.py +15 -26
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/workflow/pipeline.py +37 -54
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/workflow/profile.py +4 -2
- {etlplus-0.16.0 → etlplus-0.16.3/etlplus.egg-info}/PKG-INFO +1 -1
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus.egg-info/SOURCES.txt +5 -3
- {etlplus-0.16.0 → etlplus-0.16.3}/examples/README.md +1 -1
- etlplus-0.16.0/examples/quickstart_python.py → etlplus-0.16.3/examples/quickstart.py +22 -1
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/integration/conftest.py +20 -23
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/connector/test_u_connector_enums.py +1 -1
- etlplus-0.16.0/tests/unit/connector/test_u_connector_connector.py → etlplus-0.16.3/tests/unit/connector/test_u_connector_utils.py +2 -2
- etlplus-0.16.0/tests/unit/test_u_enums.py → etlplus-0.16.3/tests/unit/ops/test_u_ops_enums.py +8 -8
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/ops/test_u_ops_run.py +16 -13
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/ops/test_u_ops_transform.py +3 -3
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/workflow/test_u_workflow_pipeline.py +1 -2
- {etlplus-0.16.0 → etlplus-0.16.3}/.coveragerc +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/.editorconfig +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/.gitattributes +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/.github/actions/python-bootstrap/action.yml +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/.github/workflows/ci.yml +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/.gitignore +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/.pre-commit-config.yaml +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/.ruff.toml +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/CODE_OF_CONDUCT.md +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/CONTRIBUTING.md +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/DEMO.md +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/LICENSE +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/MANIFEST.in +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/Makefile +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/README.md +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/REFERENCES.md +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/SECURITY.md +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/SUPPORT.md +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/docs/README.md +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/docs/pipeline-guide.md +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/docs/snippets/installation_version.md +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/README.md +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/__init__.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/__main__.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/__version__.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/api/README.md +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/api/__init__.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/api/auth.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/api/config.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/api/endpoint_client.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/api/enums.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/api/errors.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/api/pagination/__init__.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/api/pagination/client.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/api/pagination/config.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/api/pagination/paginator.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/api/rate_limiting/__init__.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/api/rate_limiting/config.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/api/rate_limiting/rate_limiter.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/api/request_manager.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/api/retry_manager.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/api/transport.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/api/utils.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/cli/README.md +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/cli/__init__.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/cli/commands.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/cli/constants.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/cli/handlers.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/cli/io.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/cli/main.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/cli/options.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/cli/state.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/cli/types.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/connector/__init__.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/connector/api.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/connector/connector.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/connector/core.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/connector/database.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/connector/enums.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/connector/file.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/connector/types.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/connector/utils.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/database/README.md +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/database/__init__.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/database/ddl.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/database/engine.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/database/orm.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/database/schema.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/database/types.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/README.md +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/__init__.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/_imports.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/_io.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/accdb.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/arrow.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/avro.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/bson.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/cbor.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/cfg.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/conf.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/core.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/csv.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/dat.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/dta.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/duckdb.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/enums.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/feather.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/fwf.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/gz.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/hbs.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/hdf5.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/ini.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/ion.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/jinja2.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/json.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/log.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/mat.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/mdb.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/msgpack.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/mustache.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/nc.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/ndjson.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/numbers.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/ods.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/orc.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/parquet.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/pb.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/pbf.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/properties.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/proto.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/psv.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/rda.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/rds.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/sas7bdat.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/sav.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/sqlite.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/stub.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/sylk.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/tab.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/toml.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/tsv.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/txt.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/vm.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/wks.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/xls.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/xlsm.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/xlsx.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/xml.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/xpt.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/yaml.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/zip.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/zsav.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/mixins.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/ops/README.md +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/ops/utils.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/ops/validate.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/py.typed +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/templates/README.md +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/templates/__init__.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/templates/ddl.sql.j2 +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/templates/view.sql.j2 +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/utils.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/workflow/README.md +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus.egg-info/dependency_links.txt +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus.egg-info/entry_points.txt +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus.egg-info/requires.txt +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/etlplus.egg-info/top_level.txt +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/examples/configs/ddl_spec.yml +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/examples/configs/pipeline.yml +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/examples/data/sample.csv +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/examples/data/sample.json +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/examples/data/sample.xml +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/examples/data/sample.xsd +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/examples/data/sample.yaml +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/pyproject.toml +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/pytest.ini +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/setup.cfg +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/setup.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/__init__.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/conftest.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/integration/test_i_cli.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/integration/test_i_examples_data_parity.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/integration/test_i_pagination_strategy.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/integration/test_i_pipeline_smoke.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/integration/test_i_pipeline_yaml_load.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/integration/test_i_run.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/integration/test_i_run_profile_pagination_defaults.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/integration/test_i_run_profile_rate_limit_defaults.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/api/conftest.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/api/test_u_api_enums.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/api/test_u_api_utils.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/api/test_u_auth.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/api/test_u_config.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/api/test_u_endpoint_client.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/api/test_u_mocks.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/api/test_u_pagination_client.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/api/test_u_pagination_config.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/api/test_u_paginator.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/api/test_u_rate_limit_config.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/api/test_u_rate_limiter.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/api/test_u_request_manager.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/api/test_u_retry_manager.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/api/test_u_transport.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/api/test_u_types.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/cli/conftest.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/cli/test_u_cli_handlers.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/cli/test_u_cli_io.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/cli/test_u_cli_main.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/cli/test_u_cli_state.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/conftest.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/database/test_u_database_ddl.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/database/test_u_database_engine.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/database/test_u_database_orm.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/database/test_u_database_schema.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/file/test_u_file_core.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/file/test_u_file_enums.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/file/test_u_file_yaml.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/ops/test_u_ops_extract.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/ops/test_u_ops_load.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/ops/test_u_ops_utils.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/ops/test_u_ops_validate.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/test_u_main.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/test_u_mixins.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/test_u_utils.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/test_u_version.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/workflow/test_u_workflow_jobs.py +0 -0
- {etlplus-0.16.0 → etlplus-0.16.3}/tools/update_demo_snippets.py +0 -0
|
@@ -53,7 +53,31 @@ __all__ = [
|
|
|
53
53
|
# SECTION: CONSTANTS ======================================================== #
|
|
54
54
|
|
|
55
55
|
|
|
56
|
-
_UNSET = object()
|
|
56
|
+
_UNSET: object = object()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# SECTION: INTERNAL FUNCTIONS =============================================== #
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _to_dict(
|
|
63
|
+
value: Mapping[str, Any] | object | None,
|
|
64
|
+
) -> dict[str, Any] | None:
|
|
65
|
+
"""
|
|
66
|
+
Return a defensive ``dict`` copy for mapping inputs.
|
|
67
|
+
|
|
68
|
+
Parameters
|
|
69
|
+
----------
|
|
70
|
+
value : Mapping[str, Any] | object | None
|
|
71
|
+
Mapping to copy, or ``None``.
|
|
72
|
+
|
|
73
|
+
Returns
|
|
74
|
+
-------
|
|
75
|
+
dict[str, Any] | None
|
|
76
|
+
New ``dict`` instance or ``None`` when the input is ``None``.
|
|
77
|
+
"""
|
|
78
|
+
if value is None:
|
|
79
|
+
return None
|
|
80
|
+
return cast(dict[str, Any], value)
|
|
57
81
|
|
|
58
82
|
|
|
59
83
|
# SECTION: TYPED DICTS ====================================================== #
|
|
@@ -176,9 +200,9 @@ class RequestOptions:
|
|
|
176
200
|
|
|
177
201
|
def __post_init__(self) -> None:
|
|
178
202
|
if self.params is not None:
|
|
179
|
-
object.__setattr__(self, 'params',
|
|
203
|
+
object.__setattr__(self, 'params', _to_dict(self.params))
|
|
180
204
|
if self.headers is not None:
|
|
181
|
-
object.__setattr__(self, 'headers',
|
|
205
|
+
object.__setattr__(self, 'headers', _to_dict(self.headers))
|
|
182
206
|
|
|
183
207
|
# -- Instance Methods -- #
|
|
184
208
|
|
|
@@ -224,23 +248,20 @@ class RequestOptions:
|
|
|
224
248
|
|
|
225
249
|
Returns
|
|
226
250
|
-------
|
|
227
|
-
|
|
251
|
+
Self
|
|
228
252
|
New snapshot reflecting the provided overrides.
|
|
229
253
|
"""
|
|
230
254
|
if params is _UNSET:
|
|
231
255
|
next_params = self.params
|
|
232
|
-
elif params is None:
|
|
233
|
-
next_params = None
|
|
234
256
|
else:
|
|
235
|
-
next_params =
|
|
257
|
+
# next_params = _to_dict(params) if params is not None else None
|
|
258
|
+
next_params = _to_dict(params)
|
|
236
259
|
|
|
237
260
|
if headers is _UNSET:
|
|
238
261
|
next_headers = self.headers
|
|
239
|
-
elif headers is None:
|
|
240
|
-
next_headers = None
|
|
241
262
|
else:
|
|
242
|
-
next_headers =
|
|
243
|
-
|
|
263
|
+
# next_headers = _to_dict(headers) if headers is not None else None
|
|
264
|
+
next_headers = _to_dict(headers)
|
|
244
265
|
if timeout is _UNSET:
|
|
245
266
|
next_timeout = self.timeout
|
|
246
267
|
else:
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""
|
|
2
|
+
:mod:`etlplus.enums` module.
|
|
3
|
+
|
|
4
|
+
Shared enumeration base class.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import enum
|
|
10
|
+
from typing import Self
|
|
11
|
+
|
|
12
|
+
from .types import StrStrMap
|
|
13
|
+
|
|
14
|
+
# SECTION: EXPORTS ========================================================== #
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
# Enums
|
|
19
|
+
'CoercibleStrEnum',
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# SECTION: CLASSES ========================================================== #
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class CoercibleStrEnum(enum.StrEnum):
|
|
27
|
+
"""
|
|
28
|
+
StrEnum with ergonomic helpers.
|
|
29
|
+
|
|
30
|
+
Provides a DRY, class-level :meth:`coerce` that normalizes inputs and
|
|
31
|
+
produces consistent, informative error messages. Also exposes
|
|
32
|
+
:meth:`choices` for UI/validation and :meth:`try_coerce` for soft parsing.
|
|
33
|
+
|
|
34
|
+
Notes
|
|
35
|
+
-----
|
|
36
|
+
- Values are normalized via ``str(value).strip().casefold()``.
|
|
37
|
+
- If value matching fails, the raw string is tried as a member name.
|
|
38
|
+
- Error messages enumerate allowed values for easier debugging.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
# -- Class Methods -- #
|
|
42
|
+
|
|
43
|
+
@classmethod
|
|
44
|
+
def aliases(cls) -> StrStrMap:
|
|
45
|
+
"""
|
|
46
|
+
Return a mapping of common aliases for each enum member.
|
|
47
|
+
|
|
48
|
+
Subclasses may override this method to provide custom aliases.
|
|
49
|
+
|
|
50
|
+
Returns
|
|
51
|
+
-------
|
|
52
|
+
StrStrMap
|
|
53
|
+
A mapping of alias strings to their corresponding enum member
|
|
54
|
+
values or names.
|
|
55
|
+
|
|
56
|
+
Notes
|
|
57
|
+
-----
|
|
58
|
+
- Alias keys are normalized via ``str(key).strip().casefold()``.
|
|
59
|
+
- Alias values should be member values or member names.
|
|
60
|
+
"""
|
|
61
|
+
return {}
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def choices(cls) -> tuple[str, ...]:
|
|
65
|
+
"""
|
|
66
|
+
Return the allowed string values for this enum.
|
|
67
|
+
|
|
68
|
+
Returns
|
|
69
|
+
-------
|
|
70
|
+
tuple[str, ...]
|
|
71
|
+
A tuple of allowed string values for this enum.
|
|
72
|
+
"""
|
|
73
|
+
return tuple(member.value for member in cls)
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def coerce(cls, value: Self | str | object) -> Self:
|
|
77
|
+
"""
|
|
78
|
+
Convert an enum member or string-like input to a member of *cls*.
|
|
79
|
+
|
|
80
|
+
Parameters
|
|
81
|
+
----------
|
|
82
|
+
value : Self | str | object
|
|
83
|
+
An existing enum member or a string-like value to normalize.
|
|
84
|
+
|
|
85
|
+
Returns
|
|
86
|
+
-------
|
|
87
|
+
Self
|
|
88
|
+
The corresponding enum member.
|
|
89
|
+
|
|
90
|
+
Raises
|
|
91
|
+
------
|
|
92
|
+
ValueError
|
|
93
|
+
If the value cannot be coerced into a valid member.
|
|
94
|
+
"""
|
|
95
|
+
if isinstance(value, cls):
|
|
96
|
+
return value
|
|
97
|
+
try:
|
|
98
|
+
raw = str(value).strip()
|
|
99
|
+
normalized = raw.casefold()
|
|
100
|
+
aliases = {
|
|
101
|
+
str(key).strip().casefold(): alias
|
|
102
|
+
for key, alias in cls.aliases().items()
|
|
103
|
+
}
|
|
104
|
+
resolved = aliases.get(normalized)
|
|
105
|
+
if resolved is None:
|
|
106
|
+
try:
|
|
107
|
+
return cls(normalized) # type: ignore[arg-type]
|
|
108
|
+
except (ValueError, TypeError):
|
|
109
|
+
return cls[raw] # type: ignore[index]
|
|
110
|
+
if isinstance(resolved, cls):
|
|
111
|
+
return resolved
|
|
112
|
+
try:
|
|
113
|
+
return cls(resolved) # type: ignore[arg-type]
|
|
114
|
+
except (ValueError, TypeError):
|
|
115
|
+
# Allow aliases to reference member names.
|
|
116
|
+
return cls[resolved] # type: ignore[index]
|
|
117
|
+
except (ValueError, TypeError, KeyError) as e:
|
|
118
|
+
allowed = ', '.join(cls.choices())
|
|
119
|
+
raise ValueError(
|
|
120
|
+
f'Invalid {cls.__name__} value: {value!r}. Allowed: {allowed}',
|
|
121
|
+
) from e
|
|
122
|
+
|
|
123
|
+
@classmethod
|
|
124
|
+
def try_coerce(
|
|
125
|
+
cls,
|
|
126
|
+
value: Self | str | object,
|
|
127
|
+
) -> Self | None:
|
|
128
|
+
"""
|
|
129
|
+
Attempt to coerce a value into the enum; return ``None`` on failure.
|
|
130
|
+
|
|
131
|
+
Parameters
|
|
132
|
+
----------
|
|
133
|
+
value : Self | str | object
|
|
134
|
+
An existing enum member or a string-like value to normalize.
|
|
135
|
+
|
|
136
|
+
Returns
|
|
137
|
+
-------
|
|
138
|
+
Self | None
|
|
139
|
+
The corresponding enum member, or ``None`` if coercion fails.
|
|
140
|
+
"""
|
|
141
|
+
try:
|
|
142
|
+
return cls.coerce(value)
|
|
143
|
+
except (ValueError, TypeError, KeyError):
|
|
144
|
+
return None
|
|
@@ -1,133 +1,30 @@
|
|
|
1
1
|
"""
|
|
2
|
-
:mod:`etlplus.enums` module.
|
|
2
|
+
:mod:`etlplus.ops.enums` module.
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
Operation-specific enums and helpers.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
-
import enum
|
|
10
9
|
import operator as _op
|
|
11
10
|
from statistics import fmean
|
|
12
|
-
from typing import Self
|
|
13
11
|
|
|
12
|
+
from ..enums import CoercibleStrEnum
|
|
13
|
+
from ..types import StrStrMap
|
|
14
14
|
from .types import AggregateFunc
|
|
15
15
|
from .types import OperatorFunc
|
|
16
|
-
from .types import StrStrMap
|
|
17
16
|
|
|
18
|
-
# SECTION: EXPORTS
|
|
17
|
+
# SECTION: EXPORTS ========================================================= #
|
|
19
18
|
|
|
20
19
|
|
|
21
20
|
__all__ = [
|
|
22
21
|
# Enums
|
|
23
22
|
'AggregateName',
|
|
24
|
-
'CoercibleStrEnum',
|
|
25
23
|
'OperatorName',
|
|
26
24
|
'PipelineStep',
|
|
27
25
|
]
|
|
28
26
|
|
|
29
27
|
|
|
30
|
-
# SECTION: CLASSES ========================================================== #
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
class CoercibleStrEnum(enum.StrEnum):
|
|
34
|
-
"""
|
|
35
|
-
StrEnum with ergonomic helpers.
|
|
36
|
-
|
|
37
|
-
Provides a DRY, class-level :meth:`coerce` that normalizes inputs and
|
|
38
|
-
produces consistent, informative error messages. Also exposes
|
|
39
|
-
:meth:`choices` for UI/validation and :meth:`try_coerce` for soft parsing.
|
|
40
|
-
|
|
41
|
-
Notes
|
|
42
|
-
-----
|
|
43
|
-
- Values are normalized via ``str(value).strip().casefold()``.
|
|
44
|
-
- Error messages enumerate allowed values for easier debugging.
|
|
45
|
-
"""
|
|
46
|
-
|
|
47
|
-
# -- Class Methods -- #
|
|
48
|
-
|
|
49
|
-
@classmethod
|
|
50
|
-
def aliases(cls) -> StrStrMap:
|
|
51
|
-
"""
|
|
52
|
-
Return a mapping of common aliases for each enum member.
|
|
53
|
-
|
|
54
|
-
Subclasses may override this method to provide custom aliases.
|
|
55
|
-
|
|
56
|
-
Returns
|
|
57
|
-
-------
|
|
58
|
-
StrStrMap
|
|
59
|
-
A mapping of alias names to their corresponding enum member names.
|
|
60
|
-
"""
|
|
61
|
-
return {}
|
|
62
|
-
|
|
63
|
-
@classmethod
|
|
64
|
-
def choices(cls) -> tuple[str, ...]:
|
|
65
|
-
"""
|
|
66
|
-
Return the allowed string values for this enum.
|
|
67
|
-
|
|
68
|
-
Returns
|
|
69
|
-
-------
|
|
70
|
-
tuple[str, ...]
|
|
71
|
-
A tuple of allowed string values for this enum.
|
|
72
|
-
"""
|
|
73
|
-
return tuple(member.value for member in cls)
|
|
74
|
-
|
|
75
|
-
@classmethod
|
|
76
|
-
def coerce(cls, value: Self | str | object) -> Self:
|
|
77
|
-
"""
|
|
78
|
-
Convert an enum member or string-like input to a member of *cls*.
|
|
79
|
-
|
|
80
|
-
Parameters
|
|
81
|
-
----------
|
|
82
|
-
value : Self | str | object
|
|
83
|
-
An existing enum member or a text value to normalize.
|
|
84
|
-
|
|
85
|
-
Returns
|
|
86
|
-
-------
|
|
87
|
-
Self
|
|
88
|
-
The corresponding enum member.
|
|
89
|
-
|
|
90
|
-
Raises
|
|
91
|
-
------
|
|
92
|
-
ValueError
|
|
93
|
-
If the value cannot be coerced into a valid member.
|
|
94
|
-
"""
|
|
95
|
-
if isinstance(value, cls):
|
|
96
|
-
return value
|
|
97
|
-
try:
|
|
98
|
-
normalized = str(value).strip().casefold()
|
|
99
|
-
resolved = cls.aliases().get(normalized, normalized)
|
|
100
|
-
return cls(resolved) # type: ignore[arg-type]
|
|
101
|
-
except (ValueError, TypeError) as e:
|
|
102
|
-
allowed = ', '.join(cls.choices())
|
|
103
|
-
raise ValueError(
|
|
104
|
-
f'Invalid {cls.__name__} value: {value!r}. Allowed: {allowed}',
|
|
105
|
-
) from e
|
|
106
|
-
|
|
107
|
-
@classmethod
|
|
108
|
-
def try_coerce(
|
|
109
|
-
cls,
|
|
110
|
-
value: object,
|
|
111
|
-
) -> Self | None:
|
|
112
|
-
"""
|
|
113
|
-
Best-effort parse; return ``None`` on failure instead of raising.
|
|
114
|
-
|
|
115
|
-
Parameters
|
|
116
|
-
----------
|
|
117
|
-
value : object
|
|
118
|
-
An existing enum member or a text value to normalize.
|
|
119
|
-
|
|
120
|
-
Returns
|
|
121
|
-
-------
|
|
122
|
-
Self | None
|
|
123
|
-
The corresponding enum member, or ``None`` if coercion fails.
|
|
124
|
-
"""
|
|
125
|
-
try:
|
|
126
|
-
return cls.coerce(value)
|
|
127
|
-
except ValueError:
|
|
128
|
-
return None
|
|
129
|
-
|
|
130
|
-
|
|
131
28
|
# SECTION: ENUMS ============================================================ #
|
|
132
29
|
|
|
133
30
|
|
|
@@ -6,11 +6,19 @@ Helpers to extract data from files, databases, and REST APIs.
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
+
from collections.abc import Mapping
|
|
9
10
|
from pathlib import Path
|
|
10
11
|
from typing import Any
|
|
11
12
|
from typing import cast
|
|
13
|
+
from urllib.parse import urlsplit
|
|
14
|
+
from urllib.parse import urlunsplit
|
|
12
15
|
|
|
16
|
+
from ..api import EndpointClient
|
|
13
17
|
from ..api import HttpMethod
|
|
18
|
+
from ..api import PaginationConfigMap
|
|
19
|
+
from ..api import RequestOptions
|
|
20
|
+
from ..api import compose_api_request_env
|
|
21
|
+
from ..api import paginate_with_client
|
|
14
22
|
from ..api.utils import resolve_request
|
|
15
23
|
from ..connector import DataConnectorType
|
|
16
24
|
from ..file import File
|
|
@@ -19,6 +27,7 @@ from ..types import JSONData
|
|
|
19
27
|
from ..types import JSONDict
|
|
20
28
|
from ..types import JSONList
|
|
21
29
|
from ..types import StrPath
|
|
30
|
+
from ..types import Timeout
|
|
22
31
|
|
|
23
32
|
# SECTION: EXPORTS ========================================================== #
|
|
24
33
|
|
|
@@ -32,50 +41,164 @@ __all__ = [
|
|
|
32
41
|
]
|
|
33
42
|
|
|
34
43
|
|
|
35
|
-
# SECTION: FUNCTIONS
|
|
44
|
+
# SECTION: INTERNAL FUNCTIONS =============================================== #
|
|
36
45
|
|
|
37
46
|
|
|
38
|
-
def
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
47
|
+
def _build_client(
|
|
48
|
+
*,
|
|
49
|
+
base_url: str,
|
|
50
|
+
base_path: str | None,
|
|
51
|
+
endpoints: dict[str, str],
|
|
52
|
+
retry: Any,
|
|
53
|
+
retry_network_errors: bool,
|
|
54
|
+
session: Any,
|
|
55
|
+
) -> EndpointClient:
|
|
56
|
+
"""
|
|
57
|
+
Construct an API client with shared defaults.
|
|
58
|
+
|
|
59
|
+
Parameters
|
|
60
|
+
----------
|
|
61
|
+
base_url : str
|
|
62
|
+
API base URL.
|
|
63
|
+
base_path : str | None
|
|
64
|
+
Base path to prepend for endpoints.
|
|
65
|
+
endpoints : dict[str, str]
|
|
66
|
+
Endpoint name to path mappings.
|
|
67
|
+
retry : Any
|
|
68
|
+
Retry policy configuration.
|
|
69
|
+
retry_network_errors : bool
|
|
70
|
+
Whether to retry on network errors.
|
|
71
|
+
session : Any
|
|
72
|
+
Optional requests session.
|
|
73
|
+
|
|
74
|
+
Returns
|
|
75
|
+
-------
|
|
76
|
+
EndpointClient
|
|
77
|
+
Configured endpoint client instance.
|
|
78
|
+
"""
|
|
79
|
+
ClientClass = EndpointClient # noqa: N806
|
|
80
|
+
return ClientClass(
|
|
81
|
+
base_url=base_url,
|
|
82
|
+
base_path=base_path,
|
|
83
|
+
endpoints=endpoints,
|
|
84
|
+
retry=retry,
|
|
85
|
+
retry_network_errors=retry_network_errors,
|
|
86
|
+
session=session,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _extract_from_api_env(
|
|
91
|
+
env: Mapping[str, Any],
|
|
92
|
+
*,
|
|
93
|
+
use_client: bool,
|
|
42
94
|
) -> JSONData:
|
|
43
95
|
"""
|
|
44
|
-
Extract data from a
|
|
96
|
+
Extract API data from a normalized request environment.
|
|
45
97
|
|
|
46
98
|
Parameters
|
|
47
99
|
----------
|
|
48
|
-
|
|
49
|
-
API
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
**kwargs : Any
|
|
53
|
-
Extra arguments forwarded to the underlying ``requests`` call
|
|
54
|
-
(for example, ``timeout``). To use a pre-configured
|
|
55
|
-
:class:`requests.Session`, provide it via ``session``.
|
|
56
|
-
When omitted, ``timeout`` defaults to 10 seconds.
|
|
100
|
+
env : Mapping[str, Any]
|
|
101
|
+
Normalized environment describing API request parameters.
|
|
102
|
+
use_client : bool
|
|
103
|
+
Whether to use the endpoint client/pagination machinery.
|
|
57
104
|
|
|
58
105
|
Returns
|
|
59
106
|
-------
|
|
60
107
|
JSONData
|
|
61
|
-
|
|
108
|
+
Extracted payload.
|
|
62
109
|
|
|
63
110
|
Raises
|
|
64
111
|
------
|
|
65
|
-
|
|
66
|
-
If
|
|
67
|
-
method (for example, ``get``).
|
|
112
|
+
ValueError
|
|
113
|
+
If required parameters are missing.
|
|
68
114
|
"""
|
|
69
|
-
|
|
70
|
-
|
|
115
|
+
if (
|
|
116
|
+
use_client
|
|
117
|
+
and env.get('use_endpoints')
|
|
118
|
+
and env.get('base_url')
|
|
119
|
+
and env.get('endpoints_map')
|
|
120
|
+
and env.get('endpoint_key')
|
|
121
|
+
):
|
|
122
|
+
client = _build_client(
|
|
123
|
+
base_url=cast(str, env.get('base_url')),
|
|
124
|
+
base_path=cast(str | None, env.get('base_path')),
|
|
125
|
+
endpoints=cast(dict[str, str], env.get('endpoints_map', {})),
|
|
126
|
+
retry=env.get('retry'),
|
|
127
|
+
retry_network_errors=bool(env.get('retry_network_errors', False)),
|
|
128
|
+
session=env.get('session'),
|
|
129
|
+
)
|
|
130
|
+
return paginate_with_client(
|
|
131
|
+
client,
|
|
132
|
+
cast(str, env.get('endpoint_key')),
|
|
133
|
+
env.get('params'),
|
|
134
|
+
env.get('headers'),
|
|
135
|
+
env.get('timeout'),
|
|
136
|
+
env.get('pagination'),
|
|
137
|
+
cast(float | None, env.get('sleep_seconds')),
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
url = env.get('url')
|
|
141
|
+
if not url:
|
|
142
|
+
raise ValueError('API source missing URL')
|
|
143
|
+
|
|
144
|
+
if use_client:
|
|
145
|
+
parts = urlsplit(cast(str, url))
|
|
146
|
+
base = urlunsplit((parts.scheme, parts.netloc, '', '', ''))
|
|
147
|
+
client = _build_client(
|
|
148
|
+
base_url=base,
|
|
149
|
+
base_path=None,
|
|
150
|
+
endpoints={},
|
|
151
|
+
retry=env.get('retry'),
|
|
152
|
+
retry_network_errors=bool(env.get('retry_network_errors', False)),
|
|
153
|
+
session=env.get('session'),
|
|
154
|
+
)
|
|
155
|
+
request_options = RequestOptions(
|
|
156
|
+
params=cast(Mapping[str, Any] | None, env.get('params')),
|
|
157
|
+
headers=cast(Mapping[str, str] | None, env.get('headers')),
|
|
158
|
+
timeout=cast(Timeout | None, env.get('timeout')),
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
return client.paginate_url(
|
|
162
|
+
cast(str, url),
|
|
163
|
+
cast(PaginationConfigMap | None, env.get('pagination')),
|
|
164
|
+
request=request_options,
|
|
165
|
+
sleep_seconds=cast(float, env.get('sleep_seconds', 0.0)),
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
method = env.get('method', HttpMethod.GET)
|
|
169
|
+
timeout = env.get('timeout', None)
|
|
170
|
+
session = env.get('session', None)
|
|
171
|
+
request_kwargs = dict(env.get('request_kwargs') or {})
|
|
71
172
|
request_callable, timeout, _ = resolve_request(
|
|
72
173
|
method,
|
|
73
174
|
session=session,
|
|
74
175
|
timeout=timeout,
|
|
75
176
|
)
|
|
76
|
-
response = request_callable(
|
|
177
|
+
response = request_callable(
|
|
178
|
+
cast(str, url),
|
|
179
|
+
timeout=timeout,
|
|
180
|
+
**request_kwargs,
|
|
181
|
+
)
|
|
77
182
|
response.raise_for_status()
|
|
183
|
+
return _parse_api_response(response)
|
|
184
|
+
|
|
78
185
|
|
|
186
|
+
def _parse_api_response(
|
|
187
|
+
response: Any,
|
|
188
|
+
) -> JSONData:
|
|
189
|
+
"""
|
|
190
|
+
Parse API responses into a consistent JSON payload.
|
|
191
|
+
|
|
192
|
+
Parameters
|
|
193
|
+
----------
|
|
194
|
+
response : Any
|
|
195
|
+
HTTP response object exposing ``headers``, ``json()``, and ``text``.
|
|
196
|
+
|
|
197
|
+
Returns
|
|
198
|
+
-------
|
|
199
|
+
JSONData
|
|
200
|
+
Parsed JSON payload, or a fallback object with raw text.
|
|
201
|
+
"""
|
|
79
202
|
content_type = response.headers.get('content-type', '').lower()
|
|
80
203
|
if 'application/json' in content_type:
|
|
81
204
|
try:
|
|
@@ -99,6 +222,70 @@ def extract_from_api(
|
|
|
99
222
|
return {'content': response.text, 'content_type': content_type}
|
|
100
223
|
|
|
101
224
|
|
|
225
|
+
# SECTION: FUNCTIONS ======================================================== #
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def extract_from_api(
|
|
229
|
+
url: str,
|
|
230
|
+
method: HttpMethod | str = HttpMethod.GET,
|
|
231
|
+
**kwargs: Any,
|
|
232
|
+
) -> JSONData:
|
|
233
|
+
"""
|
|
234
|
+
Extract data from a REST API.
|
|
235
|
+
|
|
236
|
+
Parameters
|
|
237
|
+
----------
|
|
238
|
+
url : str
|
|
239
|
+
API endpoint URL.
|
|
240
|
+
method : HttpMethod | str, optional
|
|
241
|
+
HTTP method to use. Defaults to ``GET``.
|
|
242
|
+
**kwargs : Any
|
|
243
|
+
Extra arguments forwarded to the underlying ``requests`` call
|
|
244
|
+
(for example, ``timeout``). To use a pre-configured
|
|
245
|
+
:class:`requests.Session`, provide it via ``session``.
|
|
246
|
+
When omitted, ``timeout`` defaults to 10 seconds.
|
|
247
|
+
|
|
248
|
+
Returns
|
|
249
|
+
-------
|
|
250
|
+
JSONData
|
|
251
|
+
Parsed JSON payload, or a fallback object with raw text.
|
|
252
|
+
"""
|
|
253
|
+
env = {
|
|
254
|
+
'url': url,
|
|
255
|
+
'method': method,
|
|
256
|
+
'timeout': kwargs.pop('timeout', None),
|
|
257
|
+
'session': kwargs.pop('session', None),
|
|
258
|
+
'request_kwargs': kwargs,
|
|
259
|
+
}
|
|
260
|
+
return _extract_from_api_env(env, use_client=False)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def extract_from_api_source(
|
|
264
|
+
cfg: Any,
|
|
265
|
+
source_obj: Any,
|
|
266
|
+
overrides: dict[str, Any],
|
|
267
|
+
) -> JSONData:
|
|
268
|
+
"""
|
|
269
|
+
Extract data from a REST API source connector.
|
|
270
|
+
|
|
271
|
+
Parameters
|
|
272
|
+
----------
|
|
273
|
+
cfg : Any
|
|
274
|
+
Pipeline configuration.
|
|
275
|
+
source_obj : Any
|
|
276
|
+
Connector configuration.
|
|
277
|
+
overrides : dict[str, Any]
|
|
278
|
+
Extract-time overrides.
|
|
279
|
+
|
|
280
|
+
Returns
|
|
281
|
+
-------
|
|
282
|
+
JSONData
|
|
283
|
+
Extracted payload.
|
|
284
|
+
"""
|
|
285
|
+
env = compose_api_request_env(cfg, source_obj, overrides)
|
|
286
|
+
return _extract_from_api_env(env, use_client=True)
|
|
287
|
+
|
|
288
|
+
|
|
102
289
|
def extract_from_database(
|
|
103
290
|
connection_string: str,
|
|
104
291
|
) -> JSONList:
|