etlplus 0.8.4__tar.gz → 0.8.6__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.8.4/etlplus.egg-info → etlplus-0.8.6}/PKG-INFO +1 -1
- {etlplus-0.8.4 → etlplus-0.8.6/etlplus.egg-info}/PKG-INFO +1 -1
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/unit/cli/test_u_cli_handlers.py +313 -11
- {etlplus-0.8.4 → etlplus-0.8.6}/.coveragerc +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/.editorconfig +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/.gitattributes +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/.github/actions/python-bootstrap/action.yml +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/.github/workflows/ci.yml +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/.gitignore +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/.pre-commit-config.yaml +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/.ruff.toml +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/CODE_OF_CONDUCT.md +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/CONTRIBUTING.md +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/DEMO.md +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/LICENSE +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/MANIFEST.in +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/Makefile +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/README.md +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/REFERENCES.md +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/docs/README.md +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/docs/pipeline-guide.md +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/docs/snippets/installation_version.md +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/__init__.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/__main__.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/__version__.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/api/README.md +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/api/__init__.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/api/auth.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/api/config.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/api/endpoint_client.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/api/errors.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/api/pagination/__init__.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/api/pagination/client.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/api/pagination/config.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/api/pagination/paginator.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/api/rate_limiting/__init__.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/api/rate_limiting/config.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/api/rate_limiting/rate_limiter.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/api/request_manager.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/api/retry_manager.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/api/transport.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/api/types.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/cli/__init__.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/cli/commands.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/cli/constants.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/cli/handlers.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/cli/io.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/cli/main.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/cli/options.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/cli/state.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/cli/types.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/config/__init__.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/config/connector.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/config/jobs.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/config/pipeline.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/config/profile.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/config/types.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/config/utils.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/database/__init__.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/database/ddl.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/database/engine.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/database/orm.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/database/schema.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/database/types.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/enums.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/extract.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/file.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/load.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/mixins.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/py.typed +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/run.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/run_helpers.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/templates/__init__.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/templates/ddl.sql.j2 +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/templates/view.sql.j2 +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/transform.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/types.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/utils.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/validate.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/validation/__init__.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus/validation/utils.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus.egg-info/SOURCES.txt +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus.egg-info/dependency_links.txt +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus.egg-info/entry_points.txt +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus.egg-info/requires.txt +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/etlplus.egg-info/top_level.txt +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/examples/README.md +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/examples/configs/ddl_spec.yml +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/examples/configs/pipeline.yml +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/examples/data/sample.csv +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/examples/data/sample.json +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/examples/data/sample.xml +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/examples/data/sample.xsd +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/examples/data/sample.yaml +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/examples/quickstart_python.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/pyproject.toml +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/pytest.ini +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/setup.cfg +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/setup.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/__init__.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/conftest.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/integration/conftest.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/integration/test_i_cli.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/integration/test_i_examples_data_parity.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/integration/test_i_pagination_strategy.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/integration/test_i_pipeline_smoke.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/integration/test_i_pipeline_yaml_load.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/integration/test_i_run.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/integration/test_i_run_profile_pagination_defaults.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/integration/test_i_run_profile_rate_limit_defaults.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/unit/api/conftest.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/unit/api/test_u_auth.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/unit/api/test_u_config.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/unit/api/test_u_endpoint_client.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/unit/api/test_u_mocks.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/unit/api/test_u_pagination_client.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/unit/api/test_u_pagination_config.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/unit/api/test_u_paginator.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/unit/api/test_u_rate_limit_config.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/unit/api/test_u_rate_limiter.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/unit/api/test_u_request_manager.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/unit/api/test_u_retry_manager.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/unit/api/test_u_transport.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/unit/api/test_u_types.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/unit/cli/conftest.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/unit/cli/test_u_cli_main.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/unit/cli/test_u_cli_state.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/unit/config/test_u_config_utils.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/unit/config/test_u_connector.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/unit/config/test_u_jobs.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/unit/config/test_u_pipeline.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/unit/conftest.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/unit/database/test_u_database_ddl.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/unit/database/test_u_database_engine.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/unit/database/test_u_database_orm.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/unit/database/test_u_database_schema.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/unit/test_u_enums.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/unit/test_u_extract.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/unit/test_u_file.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/unit/test_u_load.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/unit/test_u_main.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/unit/test_u_mixins.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/unit/test_u_run.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/unit/test_u_run_helpers.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/unit/test_u_transform.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/unit/test_u_utils.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/unit/test_u_validate.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/unit/test_u_version.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tests/unit/validation/test_u_validation_utils.py +0 -0
- {etlplus-0.8.4 → etlplus-0.8.6}/tools/update_demo_snippets.py +0 -0
|
@@ -374,7 +374,7 @@ class TestCheckHandler:
|
|
|
374
374
|
dummy_cfg: PipelineConfig,
|
|
375
375
|
capture_io: dict[str, list],
|
|
376
376
|
) -> None:
|
|
377
|
-
"""Test that
|
|
377
|
+
"""Test that :func:`check_handler` prints requested sections."""
|
|
378
378
|
monkeypatch.setattr(
|
|
379
379
|
handlers,
|
|
380
380
|
'load_pipeline_config',
|
|
@@ -399,7 +399,9 @@ class TestExtractHandler:
|
|
|
399
399
|
monkeypatch: pytest.MonkeyPatch,
|
|
400
400
|
capture_io: dict[str, list],
|
|
401
401
|
) -> None:
|
|
402
|
-
"""
|
|
402
|
+
"""
|
|
403
|
+
Test that :func:`extract_handler` uses extract for non-stdin sources.
|
|
404
|
+
"""
|
|
403
405
|
observed: dict[str, object] = {}
|
|
404
406
|
|
|
405
407
|
def fake_extract(
|
|
@@ -439,7 +441,7 @@ class TestExtractHandler:
|
|
|
439
441
|
capture_io: dict[str, list],
|
|
440
442
|
) -> None:
|
|
441
443
|
"""
|
|
442
|
-
Test that explicit format hints
|
|
444
|
+
Test that :func:`extract_handler` forwards explicit file format hints.
|
|
443
445
|
"""
|
|
444
446
|
captured: dict[str, object] = {}
|
|
445
447
|
|
|
@@ -472,7 +474,9 @@ class TestExtractHandler:
|
|
|
472
474
|
monkeypatch: pytest.MonkeyPatch,
|
|
473
475
|
capture_io: dict[str, list],
|
|
474
476
|
) -> None:
|
|
475
|
-
"""
|
|
477
|
+
"""
|
|
478
|
+
Test that :func:`extract_handler` reads stdin and emits parsed data.
|
|
479
|
+
"""
|
|
476
480
|
monkeypatch.setattr(
|
|
477
481
|
handlers.cli_io,
|
|
478
482
|
'read_stdin_text',
|
|
@@ -504,13 +508,14 @@ class TestExtractHandler:
|
|
|
504
508
|
]
|
|
505
509
|
assert capture_io['emit_or_write'] == []
|
|
506
510
|
|
|
507
|
-
def
|
|
511
|
+
def test_writes_output_file_and_skips_emit(
|
|
508
512
|
self,
|
|
509
513
|
monkeypatch: pytest.MonkeyPatch,
|
|
510
514
|
capture_io: dict[str, list],
|
|
511
515
|
) -> None:
|
|
512
516
|
"""
|
|
513
|
-
Test that
|
|
517
|
+
Test that :func:`extract_handler` writes to a file and skips stdout
|
|
518
|
+
emission.
|
|
514
519
|
"""
|
|
515
520
|
observed: dict[str, object] = {}
|
|
516
521
|
|
|
@@ -554,7 +559,7 @@ class TestLoadHandler:
|
|
|
554
559
|
monkeypatch: pytest.MonkeyPatch,
|
|
555
560
|
capture_io: dict[str, list],
|
|
556
561
|
) -> None:
|
|
557
|
-
"""Test that
|
|
562
|
+
"""Test that :func:`load_handler` streams payload for file targets."""
|
|
558
563
|
recorded: dict[str, object] = {}
|
|
559
564
|
|
|
560
565
|
def fake_materialize(
|
|
@@ -601,7 +606,7 @@ class TestLoadHandler:
|
|
|
601
606
|
capture_io: dict[str, list],
|
|
602
607
|
) -> None:
|
|
603
608
|
"""
|
|
604
|
-
Test that
|
|
609
|
+
Test that :func:`load_handler` parses stdin and routes through load.
|
|
605
610
|
"""
|
|
606
611
|
read_calls = {'count': 0}
|
|
607
612
|
|
|
@@ -685,7 +690,8 @@ class TestLoadHandler:
|
|
|
685
690
|
capture_io: dict[str, list],
|
|
686
691
|
) -> None:
|
|
687
692
|
"""
|
|
688
|
-
Test that
|
|
693
|
+
Test that :func:`load_handler` writes to a file and skips stdout
|
|
694
|
+
emission.
|
|
689
695
|
"""
|
|
690
696
|
load_record: dict[str, object] = {}
|
|
691
697
|
|
|
@@ -740,7 +746,7 @@ class TestRenderHandler:
|
|
|
740
746
|
self,
|
|
741
747
|
capsys: pytest.CaptureFixture[str],
|
|
742
748
|
) -> None:
|
|
743
|
-
"""Test that missing
|
|
749
|
+
"""Test that :func:`render_handler` reports missing specs."""
|
|
744
750
|
|
|
745
751
|
assert (
|
|
746
752
|
handlers.render_handler(
|
|
@@ -762,7 +768,7 @@ class TestRenderHandler:
|
|
|
762
768
|
widget_spec_paths: tuple[Path, Path],
|
|
763
769
|
capsys: pytest.CaptureFixture[str],
|
|
764
770
|
) -> None:
|
|
765
|
-
"""Test
|
|
771
|
+
"""Test that :func:`render_handler` writes SQL for standalone specs."""
|
|
766
772
|
spec_path, output_path = widget_spec_paths
|
|
767
773
|
assert (
|
|
768
774
|
handlers.render_handler(
|
|
@@ -788,14 +794,310 @@ class TestRenderHandler:
|
|
|
788
794
|
class TestRunHandler:
|
|
789
795
|
"""Unit test suite for :func:`run_handler`."""
|
|
790
796
|
|
|
797
|
+
def test_emits_pipeline_summary_without_job(
|
|
798
|
+
self,
|
|
799
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
800
|
+
dummy_cfg: PipelineConfig,
|
|
801
|
+
capture_io: dict[str, list],
|
|
802
|
+
) -> None:
|
|
803
|
+
"""Test that :func:`run_handler` emits a summary when no job set."""
|
|
804
|
+
monkeypatch.setattr(
|
|
805
|
+
handlers,
|
|
806
|
+
'load_pipeline_config',
|
|
807
|
+
lambda path, substitute: dummy_cfg,
|
|
808
|
+
)
|
|
809
|
+
|
|
810
|
+
assert (
|
|
811
|
+
handlers.run_handler(
|
|
812
|
+
config='pipeline.yml',
|
|
813
|
+
job=None,
|
|
814
|
+
pipeline=None,
|
|
815
|
+
pretty=True,
|
|
816
|
+
)
|
|
817
|
+
== 0
|
|
818
|
+
)
|
|
819
|
+
|
|
820
|
+
assert capture_io['emit_json'] == [
|
|
821
|
+
(
|
|
822
|
+
(
|
|
823
|
+
{
|
|
824
|
+
'name': dummy_cfg.name,
|
|
825
|
+
'version': dummy_cfg.version,
|
|
826
|
+
'sources': ['s1'],
|
|
827
|
+
'targets': ['t1'],
|
|
828
|
+
'jobs': ['j1'],
|
|
829
|
+
},
|
|
830
|
+
),
|
|
831
|
+
{'pretty': True},
|
|
832
|
+
),
|
|
833
|
+
]
|
|
834
|
+
|
|
835
|
+
def test_runs_job_and_emits_result(
|
|
836
|
+
self,
|
|
837
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
838
|
+
dummy_cfg: PipelineConfig,
|
|
839
|
+
capture_io: dict[str, list],
|
|
840
|
+
) -> None:
|
|
841
|
+
"""
|
|
842
|
+
Test that :func:`run_handler` executes a named job and emits status.
|
|
843
|
+
"""
|
|
844
|
+
monkeypatch.setattr(
|
|
845
|
+
handlers,
|
|
846
|
+
'load_pipeline_config',
|
|
847
|
+
lambda path, substitute: dummy_cfg,
|
|
848
|
+
)
|
|
849
|
+
run_calls: dict[str, object] = {}
|
|
850
|
+
|
|
851
|
+
def fake_run(*, job: str, config_path: str) -> dict[str, object]:
|
|
852
|
+
run_calls['params'] = (job, config_path)
|
|
853
|
+
return {'job': job, 'ok': True}
|
|
854
|
+
|
|
855
|
+
monkeypatch.setattr(handlers, 'run', fake_run)
|
|
856
|
+
|
|
857
|
+
assert (
|
|
858
|
+
handlers.run_handler(
|
|
859
|
+
config='pipeline.yml',
|
|
860
|
+
job='job1',
|
|
861
|
+
pretty=False,
|
|
862
|
+
)
|
|
863
|
+
== 0
|
|
864
|
+
)
|
|
865
|
+
assert run_calls['params'] == ('job1', 'pipeline.yml')
|
|
866
|
+
assert capture_io['emit_json'] == [
|
|
867
|
+
(
|
|
868
|
+
({'status': 'ok', 'result': {'job': 'job1', 'ok': True}},),
|
|
869
|
+
{'pretty': False},
|
|
870
|
+
),
|
|
871
|
+
]
|
|
872
|
+
|
|
791
873
|
|
|
792
874
|
class TestTransformHandler:
|
|
793
875
|
"""Unit test suite for :func:`transform_handler`."""
|
|
794
876
|
|
|
877
|
+
def test_emits_result_without_target(
|
|
878
|
+
self,
|
|
879
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
880
|
+
capture_io: dict[str, list],
|
|
881
|
+
) -> None:
|
|
882
|
+
"""Test that :func:`transform_handler` emits results with no target."""
|
|
883
|
+
resolve_calls: list[tuple[object, str | None, bool]] = []
|
|
884
|
+
|
|
885
|
+
def fake_resolve(
|
|
886
|
+
source: object,
|
|
887
|
+
*,
|
|
888
|
+
format_hint: str | None,
|
|
889
|
+
format_explicit: bool,
|
|
890
|
+
) -> object:
|
|
891
|
+
resolve_calls.append((source, format_hint, format_explicit))
|
|
892
|
+
if source == 'data.json':
|
|
893
|
+
return [{'id': 1}]
|
|
894
|
+
return {'select': ['id']}
|
|
895
|
+
|
|
896
|
+
monkeypatch.setattr(
|
|
897
|
+
handlers.cli_io,
|
|
898
|
+
'resolve_cli_payload',
|
|
899
|
+
fake_resolve,
|
|
900
|
+
)
|
|
901
|
+
monkeypatch.setattr(
|
|
902
|
+
handlers,
|
|
903
|
+
'transform',
|
|
904
|
+
lambda payload, ops: {'rows': payload, 'ops': ops},
|
|
905
|
+
)
|
|
906
|
+
|
|
907
|
+
assert (
|
|
908
|
+
handlers.transform_handler(
|
|
909
|
+
source='data.json',
|
|
910
|
+
operations='ops.json',
|
|
911
|
+
source_format='json',
|
|
912
|
+
target=None,
|
|
913
|
+
pretty=False,
|
|
914
|
+
)
|
|
915
|
+
== 0
|
|
916
|
+
)
|
|
917
|
+
assert resolve_calls == [
|
|
918
|
+
('data.json', 'json', True),
|
|
919
|
+
('ops.json', None, True),
|
|
920
|
+
]
|
|
921
|
+
assert capture_io['emit_json'] == [
|
|
922
|
+
(
|
|
923
|
+
({'rows': [{'id': 1}], 'ops': {'select': ['id']}},),
|
|
924
|
+
{'pretty': False},
|
|
925
|
+
),
|
|
926
|
+
]
|
|
927
|
+
|
|
928
|
+
def test_writes_target_file(
|
|
929
|
+
self,
|
|
930
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
931
|
+
capsys: pytest.CaptureFixture[str],
|
|
932
|
+
) -> None:
|
|
933
|
+
"""Test that :func:`transform_handler` writes data to a target file."""
|
|
934
|
+
monkeypatch.setattr(
|
|
935
|
+
handlers.cli_io,
|
|
936
|
+
'resolve_cli_payload',
|
|
937
|
+
lambda source, **_kwargs: {'source': source}
|
|
938
|
+
if source == 'data.json'
|
|
939
|
+
else {'select': ['id']},
|
|
940
|
+
)
|
|
941
|
+
monkeypatch.setattr(
|
|
942
|
+
handlers,
|
|
943
|
+
'transform',
|
|
944
|
+
lambda payload, ops: {'payload': payload, 'ops': ops},
|
|
945
|
+
)
|
|
946
|
+
write_calls: dict[str, object] = {}
|
|
947
|
+
|
|
948
|
+
def fake_write(
|
|
949
|
+
path: str,
|
|
950
|
+
data: object,
|
|
951
|
+
*,
|
|
952
|
+
file_format: str | None,
|
|
953
|
+
) -> None:
|
|
954
|
+
write_calls['params'] = (path, data, file_format)
|
|
955
|
+
|
|
956
|
+
monkeypatch.setattr(handlers.File, 'write_file', fake_write)
|
|
957
|
+
|
|
958
|
+
assert (
|
|
959
|
+
handlers.transform_handler(
|
|
960
|
+
source='data.json',
|
|
961
|
+
operations='ops.json',
|
|
962
|
+
target='out.json',
|
|
963
|
+
target_format='json',
|
|
964
|
+
pretty=True,
|
|
965
|
+
)
|
|
966
|
+
== 0
|
|
967
|
+
)
|
|
968
|
+
assert write_calls['params'] == (
|
|
969
|
+
'out.json',
|
|
970
|
+
{
|
|
971
|
+
'payload': {'source': 'data.json'},
|
|
972
|
+
'ops': {'select': ['id']},
|
|
973
|
+
},
|
|
974
|
+
'json',
|
|
975
|
+
)
|
|
976
|
+
assert (
|
|
977
|
+
'Data transformed and saved to out.json' in capsys.readouterr().out
|
|
978
|
+
)
|
|
979
|
+
|
|
795
980
|
|
|
796
981
|
class TestValidateHandler:
|
|
797
982
|
"""Unit test suite for :func:`validate_handler`."""
|
|
798
983
|
|
|
984
|
+
def test_emits_result_without_target(
|
|
985
|
+
self,
|
|
986
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
987
|
+
capture_io: dict[str, list],
|
|
988
|
+
) -> None:
|
|
989
|
+
"""Test that :func:`validate_handler` emits results with no target."""
|
|
990
|
+
monkeypatch.setattr(
|
|
991
|
+
handlers.cli_io,
|
|
992
|
+
'resolve_cli_payload',
|
|
993
|
+
lambda source, **_kwargs: {'source': source}
|
|
994
|
+
if source == 'data.json'
|
|
995
|
+
else {'id': {'required': True}},
|
|
996
|
+
)
|
|
997
|
+
monkeypatch.setattr(
|
|
998
|
+
handlers,
|
|
999
|
+
'validate',
|
|
1000
|
+
lambda payload, rules: {'data': payload, 'rules': rules},
|
|
1001
|
+
)
|
|
1002
|
+
|
|
1003
|
+
assert (
|
|
1004
|
+
handlers.validate_handler(
|
|
1005
|
+
source='data.json',
|
|
1006
|
+
rules='rules.json',
|
|
1007
|
+
pretty=False,
|
|
1008
|
+
)
|
|
1009
|
+
== 0
|
|
1010
|
+
)
|
|
1011
|
+
assert capture_io['emit_json'] == [
|
|
1012
|
+
(
|
|
1013
|
+
(
|
|
1014
|
+
{
|
|
1015
|
+
'data': {'source': 'data.json'},
|
|
1016
|
+
'rules': {'id': {'required': True}},
|
|
1017
|
+
},
|
|
1018
|
+
),
|
|
1019
|
+
{'pretty': False},
|
|
1020
|
+
),
|
|
1021
|
+
]
|
|
1022
|
+
|
|
1023
|
+
def test_reports_missing_data_for_target(
|
|
1024
|
+
self,
|
|
1025
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
1026
|
+
capsys: pytest.CaptureFixture[str],
|
|
1027
|
+
) -> None:
|
|
1028
|
+
"""Test that :func:`validate_handler` reports missing output data."""
|
|
1029
|
+
monkeypatch.setattr(
|
|
1030
|
+
handlers.cli_io,
|
|
1031
|
+
'resolve_cli_payload',
|
|
1032
|
+
lambda source, **_kwargs: {'source': source}
|
|
1033
|
+
if source == 'data.json'
|
|
1034
|
+
else {'id': {'required': True}},
|
|
1035
|
+
)
|
|
1036
|
+
monkeypatch.setattr(
|
|
1037
|
+
handlers,
|
|
1038
|
+
'validate',
|
|
1039
|
+
lambda *_args, **_kwargs: {'data': None},
|
|
1040
|
+
)
|
|
1041
|
+
|
|
1042
|
+
assert (
|
|
1043
|
+
handlers.validate_handler(
|
|
1044
|
+
source='data.json',
|
|
1045
|
+
rules='rules.json',
|
|
1046
|
+
target='out.json',
|
|
1047
|
+
pretty=True,
|
|
1048
|
+
)
|
|
1049
|
+
== 0
|
|
1050
|
+
)
|
|
1051
|
+
assert (
|
|
1052
|
+
'Validation failed, no data to save for out.json'
|
|
1053
|
+
in capsys.readouterr().err
|
|
1054
|
+
)
|
|
1055
|
+
|
|
1056
|
+
def test_writes_target_file(
|
|
1057
|
+
self,
|
|
1058
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
1059
|
+
) -> None:
|
|
1060
|
+
"""Test that: func: `validate_handler` writes data to a target file."""
|
|
1061
|
+
monkeypatch.setattr(
|
|
1062
|
+
handlers.cli_io,
|
|
1063
|
+
'resolve_cli_payload',
|
|
1064
|
+
lambda source, **_kwargs: {'source': source}
|
|
1065
|
+
if source == 'data.json'
|
|
1066
|
+
else {'id': {'required': True}},
|
|
1067
|
+
)
|
|
1068
|
+
monkeypatch.setattr(
|
|
1069
|
+
handlers,
|
|
1070
|
+
'validate',
|
|
1071
|
+
lambda *_args, **_kwargs: {'data': {'id': 1}},
|
|
1072
|
+
)
|
|
1073
|
+
write_calls: dict[str, object] = {}
|
|
1074
|
+
|
|
1075
|
+
def fake_write(
|
|
1076
|
+
data: object,
|
|
1077
|
+
path: str | None,
|
|
1078
|
+
*,
|
|
1079
|
+
success_message: str,
|
|
1080
|
+
) -> bool:
|
|
1081
|
+
write_calls['params'] = (data, path, success_message)
|
|
1082
|
+
return True
|
|
1083
|
+
|
|
1084
|
+
monkeypatch.setattr(handlers.cli_io, 'write_json_output', fake_write)
|
|
1085
|
+
|
|
1086
|
+
assert (
|
|
1087
|
+
handlers.validate_handler(
|
|
1088
|
+
source='data.json',
|
|
1089
|
+
rules='rules.json',
|
|
1090
|
+
target='out.json',
|
|
1091
|
+
pretty=True,
|
|
1092
|
+
)
|
|
1093
|
+
== 0
|
|
1094
|
+
)
|
|
1095
|
+
assert write_calls['params'] == (
|
|
1096
|
+
{'id': 1},
|
|
1097
|
+
'out.json',
|
|
1098
|
+
'Validation result saved to',
|
|
1099
|
+
)
|
|
1100
|
+
|
|
799
1101
|
|
|
800
1102
|
@pytest.mark.parametrize(
|
|
801
1103
|
'kwargs,expected_keys',
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|