etlplus 0.11.3__tar.gz → 0.11.5__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.11.3/etlplus.egg-info → etlplus-0.11.5}/PKG-INFO +1 -1
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/database/ddl.py +1 -1
- {etlplus-0.11.3 → etlplus-0.11.5/etlplus.egg-info}/PKG-INFO +1 -1
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus.egg-info/SOURCES.txt +3 -1
- etlplus-0.11.3/tests/unit/test_u_file.py → etlplus-0.11.5/tests/unit/file/test_u_file_core.py +6 -154
- etlplus-0.11.5/tests/unit/file/test_u_file_enums.py +90 -0
- etlplus-0.11.5/tests/unit/file/test_u_file_yaml.py +110 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/.coveragerc +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/.editorconfig +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/.gitattributes +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/.github/actions/python-bootstrap/action.yml +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/.github/workflows/ci.yml +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/.gitignore +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/.pre-commit-config.yaml +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/.ruff.toml +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/CODE_OF_CONDUCT.md +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/CONTRIBUTING.md +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/DEMO.md +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/LICENSE +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/MANIFEST.in +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/Makefile +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/README.md +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/REFERENCES.md +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/docs/README.md +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/docs/pipeline-guide.md +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/docs/snippets/installation_version.md +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/__init__.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/__main__.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/__version__.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/api/README.md +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/api/__init__.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/api/auth.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/api/config.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/api/endpoint_client.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/api/errors.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/api/pagination/__init__.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/api/pagination/client.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/api/pagination/config.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/api/pagination/paginator.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/api/rate_limiting/__init__.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/api/rate_limiting/config.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/api/rate_limiting/rate_limiter.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/api/request_manager.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/api/retry_manager.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/api/transport.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/api/types.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/cli/__init__.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/cli/commands.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/cli/constants.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/cli/handlers.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/cli/io.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/cli/main.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/cli/options.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/cli/state.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/cli/types.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/config/__init__.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/config/connector.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/config/jobs.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/config/pipeline.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/config/profile.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/config/types.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/config/utils.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/database/__init__.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/database/engine.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/database/orm.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/database/schema.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/database/types.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/enums.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/extract.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/file/__init__.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/file/core.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/file/csv.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/file/enums.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/file/json.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/file/xml.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/file/yaml.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/load.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/mixins.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/py.typed +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/run.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/run_helpers.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/templates/__init__.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/templates/ddl.sql.j2 +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/templates/view.sql.j2 +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/transform.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/types.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/utils.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/validate.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/validation/__init__.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/validation/utils.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus.egg-info/dependency_links.txt +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus.egg-info/entry_points.txt +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus.egg-info/requires.txt +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/etlplus.egg-info/top_level.txt +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/examples/README.md +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/examples/configs/ddl_spec.yml +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/examples/configs/pipeline.yml +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/examples/data/sample.csv +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/examples/data/sample.json +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/examples/data/sample.xml +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/examples/data/sample.xsd +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/examples/data/sample.yaml +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/examples/quickstart_python.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/pyproject.toml +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/pytest.ini +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/setup.cfg +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/setup.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/__init__.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/conftest.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/integration/conftest.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/integration/test_i_cli.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/integration/test_i_examples_data_parity.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/integration/test_i_pagination_strategy.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/integration/test_i_pipeline_smoke.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/integration/test_i_pipeline_yaml_load.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/integration/test_i_run.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/integration/test_i_run_profile_pagination_defaults.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/integration/test_i_run_profile_rate_limit_defaults.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/api/conftest.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/api/test_u_auth.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/api/test_u_config.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/api/test_u_endpoint_client.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/api/test_u_mocks.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/api/test_u_pagination_client.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/api/test_u_pagination_config.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/api/test_u_paginator.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/api/test_u_rate_limit_config.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/api/test_u_rate_limiter.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/api/test_u_request_manager.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/api/test_u_retry_manager.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/api/test_u_transport.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/api/test_u_types.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/cli/conftest.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/cli/test_u_cli_handlers.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/cli/test_u_cli_io.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/cli/test_u_cli_main.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/cli/test_u_cli_state.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/config/test_u_config_utils.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/config/test_u_connector.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/config/test_u_jobs.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/config/test_u_pipeline.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/conftest.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/database/test_u_database_ddl.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/database/test_u_database_engine.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/database/test_u_database_orm.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/database/test_u_database_schema.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/test_u_enums.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/test_u_extract.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/test_u_load.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/test_u_main.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/test_u_mixins.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/test_u_run.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/test_u_run_helpers.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/test_u_transform.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/test_u_utils.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/test_u_validate.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/test_u_version.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/validation/test_u_validation_utils.py +0 -0
- {etlplus-0.11.3 → etlplus-0.11.5}/tools/update_demo_snippets.py +0 -0
|
@@ -203,7 +203,7 @@ def load_table_spec(
|
|
|
203
203
|
raise ValueError('Spec must be .json, .yml, or .yaml')
|
|
204
204
|
|
|
205
205
|
try:
|
|
206
|
-
spec = File.
|
|
206
|
+
spec = File.from_path(spec_path).read()
|
|
207
207
|
except ImportError as e:
|
|
208
208
|
if suffix in {'.yml', '.yaml'}:
|
|
209
209
|
raise RuntimeError(
|
|
@@ -114,7 +114,6 @@ tests/integration/test_i_run_profile_rate_limit_defaults.py
|
|
|
114
114
|
tests/unit/conftest.py
|
|
115
115
|
tests/unit/test_u_enums.py
|
|
116
116
|
tests/unit/test_u_extract.py
|
|
117
|
-
tests/unit/test_u_file.py
|
|
118
117
|
tests/unit/test_u_load.py
|
|
119
118
|
tests/unit/test_u_main.py
|
|
120
119
|
tests/unit/test_u_mixins.py
|
|
@@ -151,5 +150,8 @@ tests/unit/database/test_u_database_ddl.py
|
|
|
151
150
|
tests/unit/database/test_u_database_engine.py
|
|
152
151
|
tests/unit/database/test_u_database_orm.py
|
|
153
152
|
tests/unit/database/test_u_database_schema.py
|
|
153
|
+
tests/unit/file/test_u_file_core.py
|
|
154
|
+
tests/unit/file/test_u_file_enums.py
|
|
155
|
+
tests/unit/file/test_u_file_yaml.py
|
|
154
156
|
tests/unit/validation/test_u_validation_utils.py
|
|
155
157
|
tools/update_demo_snippets.py
|
etlplus-0.11.3/tests/unit/test_u_file.py → etlplus-0.11.5/tests/unit/file/test_u_file_core.py
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
|
-
:mod:`tests.unit.
|
|
2
|
+
:mod:`tests.unit.test_u_file_core` module.
|
|
3
3
|
|
|
4
|
-
Unit tests for :mod:`etlplus.file`.
|
|
4
|
+
Unit tests for :mod:`etlplus.file.core`.
|
|
5
5
|
|
|
6
6
|
Notes
|
|
7
7
|
-----
|
|
@@ -11,17 +11,13 @@ Notes
|
|
|
11
11
|
|
|
12
12
|
from __future__ import annotations
|
|
13
13
|
|
|
14
|
-
from collections.abc import Generator
|
|
15
14
|
from pathlib import Path
|
|
16
15
|
from typing import cast
|
|
17
16
|
|
|
18
17
|
import pytest
|
|
19
18
|
|
|
20
|
-
import etlplus.file.yaml as yaml_module
|
|
21
|
-
from etlplus.file import CompressionFormat
|
|
22
19
|
from etlplus.file import File
|
|
23
20
|
from etlplus.file import FileFormat
|
|
24
|
-
from etlplus.file import infer_file_format_and_compression
|
|
25
21
|
from etlplus.types import JSONDict
|
|
26
22
|
|
|
27
23
|
# SECTION: HELPERS ========================================================== #
|
|
@@ -30,46 +26,6 @@ from etlplus.types import JSONDict
|
|
|
30
26
|
pytestmark = pytest.mark.unit
|
|
31
27
|
|
|
32
28
|
|
|
33
|
-
class _StubYaml:
|
|
34
|
-
"""Minimal PyYAML substitute to avoid optional dependency in tests."""
|
|
35
|
-
|
|
36
|
-
def __init__(self) -> None:
|
|
37
|
-
self.dump_calls: list[dict[str, object]] = []
|
|
38
|
-
|
|
39
|
-
def safe_load(
|
|
40
|
-
self,
|
|
41
|
-
handle: object,
|
|
42
|
-
) -> dict[str, str]:
|
|
43
|
-
"""Stub for PyYAML's ``safe_load`` function."""
|
|
44
|
-
text = ''
|
|
45
|
-
if hasattr(handle, 'read'): # type: ignore[call-arg]
|
|
46
|
-
text = handle.read()
|
|
47
|
-
return {'loaded': str(text).strip()}
|
|
48
|
-
|
|
49
|
-
def safe_dump(
|
|
50
|
-
self,
|
|
51
|
-
data: object,
|
|
52
|
-
handle: object,
|
|
53
|
-
**kwargs: object,
|
|
54
|
-
) -> None:
|
|
55
|
-
"""Stub for PyYAML's ``safe_dump`` function."""
|
|
56
|
-
self.dump_calls.append({'data': data, 'kwargs': kwargs})
|
|
57
|
-
if hasattr(handle, 'write'):
|
|
58
|
-
handle.write('yaml') # type: ignore[call-arg]
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
@pytest.fixture(name='yaml_stub')
|
|
62
|
-
def yaml_stub_fixture() -> Generator[_StubYaml]:
|
|
63
|
-
"""Install a stub PyYAML module for YAML tests."""
|
|
64
|
-
# pylint: disable=protected-access
|
|
65
|
-
|
|
66
|
-
stub = _StubYaml()
|
|
67
|
-
yaml_module._YAML_CACHE.clear()
|
|
68
|
-
yaml_module._YAML_CACHE['mod'] = stub
|
|
69
|
-
yield stub
|
|
70
|
-
yaml_module._YAML_CACHE.clear()
|
|
71
|
-
|
|
72
|
-
|
|
73
29
|
# SECTION: TESTS ============================================================ #
|
|
74
30
|
|
|
75
31
|
|
|
@@ -82,18 +38,18 @@ class TestFile:
|
|
|
82
38
|
- Exercises JSON detection and defers errors for unknown extensions.
|
|
83
39
|
"""
|
|
84
40
|
|
|
85
|
-
def
|
|
41
|
+
def test_instance_methods_round_trip(
|
|
86
42
|
self,
|
|
87
43
|
tmp_path: Path,
|
|
88
44
|
) -> None:
|
|
89
45
|
"""
|
|
90
|
-
Test
|
|
46
|
+
Test :meth:`read` and :meth:`write` round-tripping data.
|
|
91
47
|
"""
|
|
92
48
|
path = tmp_path / 'delegated.json'
|
|
93
49
|
data = {'name': 'delegated'}
|
|
94
50
|
|
|
95
|
-
File
|
|
96
|
-
result = File
|
|
51
|
+
File(path, file_format=FileFormat.JSON).write(data)
|
|
52
|
+
result = File(path, file_format=FileFormat.JSON).read()
|
|
97
53
|
|
|
98
54
|
assert isinstance(result, dict)
|
|
99
55
|
assert result['name'] == 'delegated'
|
|
@@ -303,107 +259,3 @@ class TestFile:
|
|
|
303
259
|
text = path.read_text(encoding='utf-8')
|
|
304
260
|
assert text.startswith('<?xml')
|
|
305
261
|
assert '<records>' in text
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
class TestFileFormat:
|
|
309
|
-
"""Unit test suite for :class:`etlplus.enums.FileFormat`."""
|
|
310
|
-
|
|
311
|
-
@pytest.mark.parametrize(
|
|
312
|
-
'value,expected',
|
|
313
|
-
[
|
|
314
|
-
('JSON', FileFormat.JSON),
|
|
315
|
-
('application/xml', FileFormat.XML),
|
|
316
|
-
('yml', FileFormat.YAML),
|
|
317
|
-
],
|
|
318
|
-
)
|
|
319
|
-
def test_aliases(
|
|
320
|
-
self,
|
|
321
|
-
value: str,
|
|
322
|
-
expected: FileFormat,
|
|
323
|
-
) -> None:
|
|
324
|
-
"""Test alias coercions."""
|
|
325
|
-
assert FileFormat.coerce(value) is expected
|
|
326
|
-
|
|
327
|
-
def test_coerce(self) -> None:
|
|
328
|
-
"""Test :meth:`coerce`."""
|
|
329
|
-
assert FileFormat.coerce('csv') is FileFormat.CSV
|
|
330
|
-
|
|
331
|
-
def test_invalid_value(self) -> None:
|
|
332
|
-
"""Test that invalid values raise ValueError."""
|
|
333
|
-
with pytest.raises(ValueError, match='Invalid FileFormat'):
|
|
334
|
-
FileFormat.coerce('ini')
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
class TestInferFileFormatAndCompression:
|
|
338
|
-
"""Unit test suite for :func:`infer_file_format_and_compression`."""
|
|
339
|
-
|
|
340
|
-
@pytest.mark.parametrize(
|
|
341
|
-
'value,filename,expected_format,expected_compression',
|
|
342
|
-
[
|
|
343
|
-
('data.csv.gz', None, FileFormat.CSV, CompressionFormat.GZ),
|
|
344
|
-
('data.jsonl.gz', None, FileFormat.NDJSON, CompressionFormat.GZ),
|
|
345
|
-
('data.zip', None, None, CompressionFormat.ZIP),
|
|
346
|
-
('application/json; charset=utf-8', None, FileFormat.JSON, None),
|
|
347
|
-
('application/gzip', None, None, CompressionFormat.GZ),
|
|
348
|
-
(
|
|
349
|
-
'application/octet-stream',
|
|
350
|
-
'payload.csv.gz',
|
|
351
|
-
FileFormat.CSV,
|
|
352
|
-
CompressionFormat.GZ,
|
|
353
|
-
),
|
|
354
|
-
('application/octet-stream', None, None, None),
|
|
355
|
-
(FileFormat.GZ, None, None, CompressionFormat.GZ),
|
|
356
|
-
(CompressionFormat.ZIP, None, None, CompressionFormat.ZIP),
|
|
357
|
-
],
|
|
358
|
-
)
|
|
359
|
-
def test_infers_format_and_compression(
|
|
360
|
-
self,
|
|
361
|
-
value: object,
|
|
362
|
-
filename: object | None,
|
|
363
|
-
expected_format: FileFormat | None,
|
|
364
|
-
expected_compression: CompressionFormat | None,
|
|
365
|
-
) -> None:
|
|
366
|
-
"""Test mixed inputs for format and compression inference."""
|
|
367
|
-
fmt, compression = infer_file_format_and_compression(value, filename)
|
|
368
|
-
assert fmt is expected_format
|
|
369
|
-
assert compression is expected_compression
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
@pytest.mark.unit
|
|
373
|
-
class TestYamlSupport:
|
|
374
|
-
"""Unit tests exercising YAML read/write helpers without PyYAML."""
|
|
375
|
-
|
|
376
|
-
def test_read_yaml_uses_stub(
|
|
377
|
-
self,
|
|
378
|
-
tmp_path: Path,
|
|
379
|
-
yaml_stub: _StubYaml,
|
|
380
|
-
) -> None:
|
|
381
|
-
"""
|
|
382
|
-
Test reading YAML should invoke stub ``safe_load``.
|
|
383
|
-
"""
|
|
384
|
-
# pylint: disable=protected-access
|
|
385
|
-
|
|
386
|
-
assert yaml_module._YAML_CACHE['mod'] is yaml_stub
|
|
387
|
-
path = tmp_path / 'data.yaml'
|
|
388
|
-
path.write_text('name: etl', encoding='utf-8')
|
|
389
|
-
|
|
390
|
-
result = File(path, FileFormat.YAML).read()
|
|
391
|
-
|
|
392
|
-
assert result == {'loaded': 'name: etl'}
|
|
393
|
-
|
|
394
|
-
def test_write_yaml_uses_stub(
|
|
395
|
-
self,
|
|
396
|
-
tmp_path: Path,
|
|
397
|
-
yaml_stub: _StubYaml,
|
|
398
|
-
) -> None:
|
|
399
|
-
"""
|
|
400
|
-
Test writing YAML should invoke stub ``safe_dump``.
|
|
401
|
-
"""
|
|
402
|
-
path = tmp_path / 'data.yaml'
|
|
403
|
-
payload = [{'name': 'etl'}]
|
|
404
|
-
|
|
405
|
-
written = File(path, FileFormat.YAML).write(payload)
|
|
406
|
-
|
|
407
|
-
assert written == 1
|
|
408
|
-
assert yaml_stub.dump_calls
|
|
409
|
-
assert yaml_stub.dump_calls[0]['data'] == payload
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""
|
|
2
|
+
:mod:`tests.unit.test_u_file_enums` module.
|
|
3
|
+
|
|
4
|
+
Unit tests for :mod:`etlplus.file.enums`.
|
|
5
|
+
|
|
6
|
+
Notes
|
|
7
|
+
-----
|
|
8
|
+
- Uses ``tmp_path`` for filesystem isolation.
|
|
9
|
+
- Exercises JSON detection and defers errors for unknown extensions.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import pytest
|
|
15
|
+
|
|
16
|
+
from etlplus.file import CompressionFormat
|
|
17
|
+
from etlplus.file import FileFormat
|
|
18
|
+
from etlplus.file import infer_file_format_and_compression
|
|
19
|
+
|
|
20
|
+
# SECTION: HELPERS ========================================================== #
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
pytestmark = pytest.mark.unit
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# SECTION: TESTS ============================================================ #
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class TestFileFormat:
|
|
30
|
+
"""Unit test suite for :class:`etlplus.enums.FileFormat`."""
|
|
31
|
+
|
|
32
|
+
@pytest.mark.parametrize(
|
|
33
|
+
'value,expected',
|
|
34
|
+
[
|
|
35
|
+
('JSON', FileFormat.JSON),
|
|
36
|
+
('application/xml', FileFormat.XML),
|
|
37
|
+
('yml', FileFormat.YAML),
|
|
38
|
+
],
|
|
39
|
+
)
|
|
40
|
+
def test_aliases(
|
|
41
|
+
self,
|
|
42
|
+
value: str,
|
|
43
|
+
expected: FileFormat,
|
|
44
|
+
) -> None:
|
|
45
|
+
"""Test alias coercions."""
|
|
46
|
+
assert FileFormat.coerce(value) is expected
|
|
47
|
+
|
|
48
|
+
def test_coerce(self) -> None:
|
|
49
|
+
"""Test :meth:`coerce`."""
|
|
50
|
+
assert FileFormat.coerce('csv') is FileFormat.CSV
|
|
51
|
+
|
|
52
|
+
def test_invalid_value(self) -> None:
|
|
53
|
+
"""Test that invalid values raise ValueError."""
|
|
54
|
+
with pytest.raises(ValueError, match='Invalid FileFormat'):
|
|
55
|
+
FileFormat.coerce('ini')
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class TestInferFileFormatAndCompression:
|
|
59
|
+
"""Unit test suite for :func:`infer_file_format_and_compression`."""
|
|
60
|
+
|
|
61
|
+
@pytest.mark.parametrize(
|
|
62
|
+
'value,filename,expected_format,expected_compression',
|
|
63
|
+
[
|
|
64
|
+
('data.csv.gz', None, FileFormat.CSV, CompressionFormat.GZ),
|
|
65
|
+
('data.jsonl.gz', None, FileFormat.NDJSON, CompressionFormat.GZ),
|
|
66
|
+
('data.zip', None, None, CompressionFormat.ZIP),
|
|
67
|
+
('application/json; charset=utf-8', None, FileFormat.JSON, None),
|
|
68
|
+
('application/gzip', None, None, CompressionFormat.GZ),
|
|
69
|
+
(
|
|
70
|
+
'application/octet-stream',
|
|
71
|
+
'payload.csv.gz',
|
|
72
|
+
FileFormat.CSV,
|
|
73
|
+
CompressionFormat.GZ,
|
|
74
|
+
),
|
|
75
|
+
('application/octet-stream', None, None, None),
|
|
76
|
+
(FileFormat.GZ, None, None, CompressionFormat.GZ),
|
|
77
|
+
(CompressionFormat.ZIP, None, None, CompressionFormat.ZIP),
|
|
78
|
+
],
|
|
79
|
+
)
|
|
80
|
+
def test_infers_format_and_compression(
|
|
81
|
+
self,
|
|
82
|
+
value: object,
|
|
83
|
+
filename: object | None,
|
|
84
|
+
expected_format: FileFormat | None,
|
|
85
|
+
expected_compression: CompressionFormat | None,
|
|
86
|
+
) -> None:
|
|
87
|
+
"""Test mixed inputs for format and compression inference."""
|
|
88
|
+
fmt, compression = infer_file_format_and_compression(value, filename)
|
|
89
|
+
assert fmt is expected_format
|
|
90
|
+
assert compression is expected_compression
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""
|
|
2
|
+
:mod:`tests.unit.test_u_file_yaml` module.
|
|
3
|
+
|
|
4
|
+
Unit tests for :mod:`etlplus.file.yaml`.
|
|
5
|
+
|
|
6
|
+
Notes
|
|
7
|
+
-----
|
|
8
|
+
- Uses ``tmp_path`` for filesystem isolation.
|
|
9
|
+
- Exercises JSON detection and defers errors for unknown extensions.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from collections.abc import Generator
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
import pytest
|
|
18
|
+
|
|
19
|
+
import etlplus.file.yaml as yaml_module
|
|
20
|
+
from etlplus.file import File
|
|
21
|
+
from etlplus.file import FileFormat
|
|
22
|
+
|
|
23
|
+
# SECTION: HELPERS ========================================================== #
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
pytestmark = pytest.mark.unit
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class _StubYaml:
|
|
30
|
+
"""Minimal PyYAML substitute to avoid optional dependency in tests."""
|
|
31
|
+
|
|
32
|
+
def __init__(self) -> None:
|
|
33
|
+
self.dump_calls: list[dict[str, object]] = []
|
|
34
|
+
|
|
35
|
+
def safe_load(
|
|
36
|
+
self,
|
|
37
|
+
handle: object,
|
|
38
|
+
) -> dict[str, str]:
|
|
39
|
+
"""Stub for PyYAML's ``safe_load`` function."""
|
|
40
|
+
text = ''
|
|
41
|
+
if hasattr(handle, 'read'): # type: ignore[call-arg]
|
|
42
|
+
text = handle.read()
|
|
43
|
+
return {'loaded': str(text).strip()}
|
|
44
|
+
|
|
45
|
+
def safe_dump(
|
|
46
|
+
self,
|
|
47
|
+
data: object,
|
|
48
|
+
handle: object,
|
|
49
|
+
**kwargs: object,
|
|
50
|
+
) -> None:
|
|
51
|
+
"""Stub for PyYAML's ``safe_dump`` function."""
|
|
52
|
+
self.dump_calls.append({'data': data, 'kwargs': kwargs})
|
|
53
|
+
if hasattr(handle, 'write'):
|
|
54
|
+
handle.write('yaml') # type: ignore[call-arg]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@pytest.fixture(name='yaml_stub')
|
|
58
|
+
def yaml_stub_fixture() -> Generator[_StubYaml]:
|
|
59
|
+
"""Install a stub PyYAML module for YAML tests."""
|
|
60
|
+
# pylint: disable=protected-access
|
|
61
|
+
|
|
62
|
+
stub = _StubYaml()
|
|
63
|
+
yaml_module._YAML_CACHE.clear()
|
|
64
|
+
yaml_module._YAML_CACHE['mod'] = stub
|
|
65
|
+
yield stub
|
|
66
|
+
yaml_module._YAML_CACHE.clear()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# SECTION: TESTS ============================================================ #
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# TODO: Can File read/write YAML without PyYAML installed?
|
|
73
|
+
# TODO: If not, remove this unit test suite.
|
|
74
|
+
class TestYamlSupport:
|
|
75
|
+
"""Unit tests exercising YAML read/write helpers without PyYAML."""
|
|
76
|
+
|
|
77
|
+
def test_read_yaml_uses_stub(
|
|
78
|
+
self,
|
|
79
|
+
tmp_path: Path,
|
|
80
|
+
yaml_stub: _StubYaml,
|
|
81
|
+
) -> None:
|
|
82
|
+
"""
|
|
83
|
+
Test reading YAML should invoke stub ``safe_load``.
|
|
84
|
+
"""
|
|
85
|
+
# pylint: disable=protected-access
|
|
86
|
+
|
|
87
|
+
assert yaml_module._YAML_CACHE['mod'] is yaml_stub
|
|
88
|
+
path = tmp_path / 'data.yaml'
|
|
89
|
+
path.write_text('name: etl', encoding='utf-8')
|
|
90
|
+
|
|
91
|
+
result = File(path, FileFormat.YAML).read()
|
|
92
|
+
|
|
93
|
+
assert result == {'loaded': 'name: etl'}
|
|
94
|
+
|
|
95
|
+
def test_write_yaml_uses_stub(
|
|
96
|
+
self,
|
|
97
|
+
tmp_path: Path,
|
|
98
|
+
yaml_stub: _StubYaml,
|
|
99
|
+
) -> None:
|
|
100
|
+
"""
|
|
101
|
+
Test writing YAML should invoke stub ``safe_dump``.
|
|
102
|
+
"""
|
|
103
|
+
path = tmp_path / 'data.yaml'
|
|
104
|
+
payload = [{'name': 'etl'}]
|
|
105
|
+
|
|
106
|
+
written = File(path, FileFormat.YAML).write(payload)
|
|
107
|
+
|
|
108
|
+
assert written == 1
|
|
109
|
+
assert yaml_stub.dump_calls
|
|
110
|
+
assert yaml_stub.dump_calls[0]['data'] == payload
|
|
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
|
{etlplus-0.11.3 → etlplus-0.11.5}/tests/integration/test_i_run_profile_pagination_defaults.py
RENAMED
|
File without changes
|
{etlplus-0.11.3 → etlplus-0.11.5}/tests/integration/test_i_run_profile_rate_limit_defaults.py
RENAMED
|
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
|