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.
Files changed (159) hide show
  1. {etlplus-0.11.3/etlplus.egg-info → etlplus-0.11.5}/PKG-INFO +1 -1
  2. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/database/ddl.py +1 -1
  3. {etlplus-0.11.3 → etlplus-0.11.5/etlplus.egg-info}/PKG-INFO +1 -1
  4. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus.egg-info/SOURCES.txt +3 -1
  5. etlplus-0.11.3/tests/unit/test_u_file.py → etlplus-0.11.5/tests/unit/file/test_u_file_core.py +6 -154
  6. etlplus-0.11.5/tests/unit/file/test_u_file_enums.py +90 -0
  7. etlplus-0.11.5/tests/unit/file/test_u_file_yaml.py +110 -0
  8. {etlplus-0.11.3 → etlplus-0.11.5}/.coveragerc +0 -0
  9. {etlplus-0.11.3 → etlplus-0.11.5}/.editorconfig +0 -0
  10. {etlplus-0.11.3 → etlplus-0.11.5}/.gitattributes +0 -0
  11. {etlplus-0.11.3 → etlplus-0.11.5}/.github/actions/python-bootstrap/action.yml +0 -0
  12. {etlplus-0.11.3 → etlplus-0.11.5}/.github/workflows/ci.yml +0 -0
  13. {etlplus-0.11.3 → etlplus-0.11.5}/.gitignore +0 -0
  14. {etlplus-0.11.3 → etlplus-0.11.5}/.pre-commit-config.yaml +0 -0
  15. {etlplus-0.11.3 → etlplus-0.11.5}/.ruff.toml +0 -0
  16. {etlplus-0.11.3 → etlplus-0.11.5}/CODE_OF_CONDUCT.md +0 -0
  17. {etlplus-0.11.3 → etlplus-0.11.5}/CONTRIBUTING.md +0 -0
  18. {etlplus-0.11.3 → etlplus-0.11.5}/DEMO.md +0 -0
  19. {etlplus-0.11.3 → etlplus-0.11.5}/LICENSE +0 -0
  20. {etlplus-0.11.3 → etlplus-0.11.5}/MANIFEST.in +0 -0
  21. {etlplus-0.11.3 → etlplus-0.11.5}/Makefile +0 -0
  22. {etlplus-0.11.3 → etlplus-0.11.5}/README.md +0 -0
  23. {etlplus-0.11.3 → etlplus-0.11.5}/REFERENCES.md +0 -0
  24. {etlplus-0.11.3 → etlplus-0.11.5}/docs/README.md +0 -0
  25. {etlplus-0.11.3 → etlplus-0.11.5}/docs/pipeline-guide.md +0 -0
  26. {etlplus-0.11.3 → etlplus-0.11.5}/docs/snippets/installation_version.md +0 -0
  27. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/__init__.py +0 -0
  28. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/__main__.py +0 -0
  29. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/__version__.py +0 -0
  30. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/api/README.md +0 -0
  31. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/api/__init__.py +0 -0
  32. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/api/auth.py +0 -0
  33. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/api/config.py +0 -0
  34. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/api/endpoint_client.py +0 -0
  35. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/api/errors.py +0 -0
  36. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/api/pagination/__init__.py +0 -0
  37. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/api/pagination/client.py +0 -0
  38. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/api/pagination/config.py +0 -0
  39. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/api/pagination/paginator.py +0 -0
  40. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/api/rate_limiting/__init__.py +0 -0
  41. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/api/rate_limiting/config.py +0 -0
  42. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/api/rate_limiting/rate_limiter.py +0 -0
  43. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/api/request_manager.py +0 -0
  44. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/api/retry_manager.py +0 -0
  45. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/api/transport.py +0 -0
  46. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/api/types.py +0 -0
  47. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/cli/__init__.py +0 -0
  48. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/cli/commands.py +0 -0
  49. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/cli/constants.py +0 -0
  50. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/cli/handlers.py +0 -0
  51. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/cli/io.py +0 -0
  52. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/cli/main.py +0 -0
  53. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/cli/options.py +0 -0
  54. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/cli/state.py +0 -0
  55. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/cli/types.py +0 -0
  56. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/config/__init__.py +0 -0
  57. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/config/connector.py +0 -0
  58. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/config/jobs.py +0 -0
  59. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/config/pipeline.py +0 -0
  60. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/config/profile.py +0 -0
  61. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/config/types.py +0 -0
  62. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/config/utils.py +0 -0
  63. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/database/__init__.py +0 -0
  64. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/database/engine.py +0 -0
  65. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/database/orm.py +0 -0
  66. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/database/schema.py +0 -0
  67. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/database/types.py +0 -0
  68. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/enums.py +0 -0
  69. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/extract.py +0 -0
  70. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/file/__init__.py +0 -0
  71. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/file/core.py +0 -0
  72. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/file/csv.py +0 -0
  73. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/file/enums.py +0 -0
  74. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/file/json.py +0 -0
  75. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/file/xml.py +0 -0
  76. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/file/yaml.py +0 -0
  77. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/load.py +0 -0
  78. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/mixins.py +0 -0
  79. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/py.typed +0 -0
  80. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/run.py +0 -0
  81. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/run_helpers.py +0 -0
  82. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/templates/__init__.py +0 -0
  83. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/templates/ddl.sql.j2 +0 -0
  84. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/templates/view.sql.j2 +0 -0
  85. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/transform.py +0 -0
  86. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/types.py +0 -0
  87. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/utils.py +0 -0
  88. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/validate.py +0 -0
  89. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/validation/__init__.py +0 -0
  90. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus/validation/utils.py +0 -0
  91. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus.egg-info/dependency_links.txt +0 -0
  92. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus.egg-info/entry_points.txt +0 -0
  93. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus.egg-info/requires.txt +0 -0
  94. {etlplus-0.11.3 → etlplus-0.11.5}/etlplus.egg-info/top_level.txt +0 -0
  95. {etlplus-0.11.3 → etlplus-0.11.5}/examples/README.md +0 -0
  96. {etlplus-0.11.3 → etlplus-0.11.5}/examples/configs/ddl_spec.yml +0 -0
  97. {etlplus-0.11.3 → etlplus-0.11.5}/examples/configs/pipeline.yml +0 -0
  98. {etlplus-0.11.3 → etlplus-0.11.5}/examples/data/sample.csv +0 -0
  99. {etlplus-0.11.3 → etlplus-0.11.5}/examples/data/sample.json +0 -0
  100. {etlplus-0.11.3 → etlplus-0.11.5}/examples/data/sample.xml +0 -0
  101. {etlplus-0.11.3 → etlplus-0.11.5}/examples/data/sample.xsd +0 -0
  102. {etlplus-0.11.3 → etlplus-0.11.5}/examples/data/sample.yaml +0 -0
  103. {etlplus-0.11.3 → etlplus-0.11.5}/examples/quickstart_python.py +0 -0
  104. {etlplus-0.11.3 → etlplus-0.11.5}/pyproject.toml +0 -0
  105. {etlplus-0.11.3 → etlplus-0.11.5}/pytest.ini +0 -0
  106. {etlplus-0.11.3 → etlplus-0.11.5}/setup.cfg +0 -0
  107. {etlplus-0.11.3 → etlplus-0.11.5}/setup.py +0 -0
  108. {etlplus-0.11.3 → etlplus-0.11.5}/tests/__init__.py +0 -0
  109. {etlplus-0.11.3 → etlplus-0.11.5}/tests/conftest.py +0 -0
  110. {etlplus-0.11.3 → etlplus-0.11.5}/tests/integration/conftest.py +0 -0
  111. {etlplus-0.11.3 → etlplus-0.11.5}/tests/integration/test_i_cli.py +0 -0
  112. {etlplus-0.11.3 → etlplus-0.11.5}/tests/integration/test_i_examples_data_parity.py +0 -0
  113. {etlplus-0.11.3 → etlplus-0.11.5}/tests/integration/test_i_pagination_strategy.py +0 -0
  114. {etlplus-0.11.3 → etlplus-0.11.5}/tests/integration/test_i_pipeline_smoke.py +0 -0
  115. {etlplus-0.11.3 → etlplus-0.11.5}/tests/integration/test_i_pipeline_yaml_load.py +0 -0
  116. {etlplus-0.11.3 → etlplus-0.11.5}/tests/integration/test_i_run.py +0 -0
  117. {etlplus-0.11.3 → etlplus-0.11.5}/tests/integration/test_i_run_profile_pagination_defaults.py +0 -0
  118. {etlplus-0.11.3 → etlplus-0.11.5}/tests/integration/test_i_run_profile_rate_limit_defaults.py +0 -0
  119. {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/api/conftest.py +0 -0
  120. {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/api/test_u_auth.py +0 -0
  121. {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/api/test_u_config.py +0 -0
  122. {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/api/test_u_endpoint_client.py +0 -0
  123. {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/api/test_u_mocks.py +0 -0
  124. {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/api/test_u_pagination_client.py +0 -0
  125. {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/api/test_u_pagination_config.py +0 -0
  126. {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/api/test_u_paginator.py +0 -0
  127. {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/api/test_u_rate_limit_config.py +0 -0
  128. {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/api/test_u_rate_limiter.py +0 -0
  129. {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/api/test_u_request_manager.py +0 -0
  130. {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/api/test_u_retry_manager.py +0 -0
  131. {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/api/test_u_transport.py +0 -0
  132. {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/api/test_u_types.py +0 -0
  133. {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/cli/conftest.py +0 -0
  134. {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/cli/test_u_cli_handlers.py +0 -0
  135. {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/cli/test_u_cli_io.py +0 -0
  136. {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/cli/test_u_cli_main.py +0 -0
  137. {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/cli/test_u_cli_state.py +0 -0
  138. {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/config/test_u_config_utils.py +0 -0
  139. {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/config/test_u_connector.py +0 -0
  140. {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/config/test_u_jobs.py +0 -0
  141. {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/config/test_u_pipeline.py +0 -0
  142. {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/conftest.py +0 -0
  143. {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/database/test_u_database_ddl.py +0 -0
  144. {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/database/test_u_database_engine.py +0 -0
  145. {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/database/test_u_database_orm.py +0 -0
  146. {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/database/test_u_database_schema.py +0 -0
  147. {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/test_u_enums.py +0 -0
  148. {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/test_u_extract.py +0 -0
  149. {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/test_u_load.py +0 -0
  150. {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/test_u_main.py +0 -0
  151. {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/test_u_mixins.py +0 -0
  152. {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/test_u_run.py +0 -0
  153. {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/test_u_run_helpers.py +0 -0
  154. {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/test_u_transform.py +0 -0
  155. {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/test_u_utils.py +0 -0
  156. {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/test_u_validate.py +0 -0
  157. {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/test_u_version.py +0 -0
  158. {etlplus-0.11.3 → etlplus-0.11.5}/tests/unit/validation/test_u_validation_utils.py +0 -0
  159. {etlplus-0.11.3 → etlplus-0.11.5}/tools/update_demo_snippets.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: etlplus
3
- Version: 0.11.3
3
+ Version: 0.11.5
4
4
  Summary: A Swiss Army knife for simple ETL operations
5
5
  Home-page: https://github.com/Dagitali/ETLPlus
6
6
  Author: ETLPlus Team
@@ -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.read_file(spec_path)
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(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: etlplus
3
- Version: 0.11.3
3
+ Version: 0.11.5
4
4
  Summary: A Swiss Army knife for simple ETL operations
5
5
  Home-page: https://github.com/Dagitali/ETLPlus
6
6
  Author: ETLPlus Team
@@ -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
@@ -1,7 +1,7 @@
1
1
  """
2
- :mod:`tests.unit.test_u_file` module.
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 test_classmethods_delegate(
41
+ def test_instance_methods_round_trip(
86
42
  self,
87
43
  tmp_path: Path,
88
44
  ) -> None:
89
45
  """
90
- Test that ``read_file`` and ``write_file`` round-trip via classmethods.
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.write_file(path, data, file_format='json')
96
- result = File.read_file(path, file_format='json')
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