etlplus 0.17.3__tar.gz → 0.17.4__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 (246) hide show
  1. {etlplus-0.17.3/etlplus.egg-info → etlplus-0.17.4}/PKG-INFO +1 -1
  2. {etlplus-0.17.3 → etlplus-0.17.4/etlplus.egg-info}/PKG-INFO +1 -1
  3. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus.egg-info/SOURCES.txt +1 -0
  4. {etlplus-0.17.3 → etlplus-0.17.4}/tests/integration/conftest.py +2 -1
  5. {etlplus-0.17.3 → etlplus-0.17.4}/tests/smoke/conftest.py +6 -1
  6. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/api/conftest.py +3 -3
  7. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/cli/conftest.py +2 -2
  8. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/conftest.py +2 -1
  9. etlplus-0.17.4/tests/unit/file/conftest.py +112 -0
  10. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/file/test_u_file_core.py +165 -90
  11. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/file/test_u_file_yaml.py +1 -1
  12. {etlplus-0.17.3 → etlplus-0.17.4}/.coveragerc +0 -0
  13. {etlplus-0.17.3 → etlplus-0.17.4}/.editorconfig +0 -0
  14. {etlplus-0.17.3 → etlplus-0.17.4}/.gitattributes +0 -0
  15. {etlplus-0.17.3 → etlplus-0.17.4}/.github/actions/python-bootstrap/action.yml +0 -0
  16. {etlplus-0.17.3 → etlplus-0.17.4}/.github/workflows/ci.yml +0 -0
  17. {etlplus-0.17.3 → etlplus-0.17.4}/.gitignore +0 -0
  18. {etlplus-0.17.3 → etlplus-0.17.4}/.pre-commit-config.yaml +0 -0
  19. {etlplus-0.17.3 → etlplus-0.17.4}/.ruff.toml +0 -0
  20. {etlplus-0.17.3 → etlplus-0.17.4}/CODE_OF_CONDUCT.md +0 -0
  21. {etlplus-0.17.3 → etlplus-0.17.4}/CONTRIBUTING.md +0 -0
  22. {etlplus-0.17.3 → etlplus-0.17.4}/DEMO.md +0 -0
  23. {etlplus-0.17.3 → etlplus-0.17.4}/LICENSE +0 -0
  24. {etlplus-0.17.3 → etlplus-0.17.4}/MANIFEST.in +0 -0
  25. {etlplus-0.17.3 → etlplus-0.17.4}/Makefile +0 -0
  26. {etlplus-0.17.3 → etlplus-0.17.4}/README.md +0 -0
  27. {etlplus-0.17.3 → etlplus-0.17.4}/REFERENCES.md +0 -0
  28. {etlplus-0.17.3 → etlplus-0.17.4}/SECURITY.md +0 -0
  29. {etlplus-0.17.3 → etlplus-0.17.4}/SUPPORT.md +0 -0
  30. {etlplus-0.17.3 → etlplus-0.17.4}/docs/README.md +0 -0
  31. {etlplus-0.17.3 → etlplus-0.17.4}/docs/pipeline-guide.md +0 -0
  32. {etlplus-0.17.3 → etlplus-0.17.4}/docs/snippets/installation_version.md +0 -0
  33. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/README.md +0 -0
  34. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/__init__.py +0 -0
  35. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/__main__.py +0 -0
  36. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/__version__.py +0 -0
  37. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/api/README.md +0 -0
  38. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/api/__init__.py +0 -0
  39. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/api/auth.py +0 -0
  40. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/api/config.py +0 -0
  41. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/api/endpoint_client.py +0 -0
  42. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/api/enums.py +0 -0
  43. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/api/errors.py +0 -0
  44. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/api/pagination/__init__.py +0 -0
  45. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/api/pagination/client.py +0 -0
  46. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/api/pagination/config.py +0 -0
  47. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/api/pagination/paginator.py +0 -0
  48. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/api/rate_limiting/__init__.py +0 -0
  49. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/api/rate_limiting/config.py +0 -0
  50. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/api/rate_limiting/rate_limiter.py +0 -0
  51. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/api/request_manager.py +0 -0
  52. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/api/retry_manager.py +0 -0
  53. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/api/transport.py +0 -0
  54. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/api/types.py +0 -0
  55. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/api/utils.py +0 -0
  56. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/cli/README.md +0 -0
  57. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/cli/__init__.py +0 -0
  58. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/cli/commands.py +0 -0
  59. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/cli/constants.py +0 -0
  60. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/cli/handlers.py +0 -0
  61. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/cli/io.py +0 -0
  62. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/cli/main.py +0 -0
  63. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/cli/options.py +0 -0
  64. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/cli/state.py +0 -0
  65. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/cli/types.py +0 -0
  66. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/config.py +0 -0
  67. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/connector/__init__.py +0 -0
  68. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/connector/api.py +0 -0
  69. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/connector/connector.py +0 -0
  70. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/connector/core.py +0 -0
  71. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/connector/database.py +0 -0
  72. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/connector/enums.py +0 -0
  73. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/connector/file.py +0 -0
  74. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/connector/types.py +0 -0
  75. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/connector/utils.py +0 -0
  76. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/database/README.md +0 -0
  77. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/database/__init__.py +0 -0
  78. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/database/ddl.py +0 -0
  79. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/database/engine.py +0 -0
  80. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/database/orm.py +0 -0
  81. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/database/schema.py +0 -0
  82. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/database/types.py +0 -0
  83. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/enums.py +0 -0
  84. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/README.md +0 -0
  85. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/__init__.py +0 -0
  86. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/_imports.py +0 -0
  87. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/_io.py +0 -0
  88. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/_r.py +0 -0
  89. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/_sql.py +0 -0
  90. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/accdb.py +0 -0
  91. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/arrow.py +0 -0
  92. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/avro.py +0 -0
  93. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/bson.py +0 -0
  94. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/cbor.py +0 -0
  95. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/cfg.py +0 -0
  96. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/conf.py +0 -0
  97. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/core.py +0 -0
  98. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/csv.py +0 -0
  99. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/dat.py +0 -0
  100. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/dta.py +0 -0
  101. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/duckdb.py +0 -0
  102. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/enums.py +0 -0
  103. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/feather.py +0 -0
  104. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/fwf.py +0 -0
  105. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/gz.py +0 -0
  106. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/hbs.py +0 -0
  107. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/hdf5.py +0 -0
  108. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/ini.py +0 -0
  109. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/ion.py +0 -0
  110. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/jinja2.py +0 -0
  111. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/json.py +0 -0
  112. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/log.py +0 -0
  113. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/mat.py +0 -0
  114. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/mdb.py +0 -0
  115. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/msgpack.py +0 -0
  116. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/mustache.py +0 -0
  117. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/nc.py +0 -0
  118. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/ndjson.py +0 -0
  119. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/numbers.py +0 -0
  120. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/ods.py +0 -0
  121. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/orc.py +0 -0
  122. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/parquet.py +0 -0
  123. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/pb.py +0 -0
  124. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/pbf.py +0 -0
  125. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/properties.py +0 -0
  126. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/proto.py +0 -0
  127. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/psv.py +0 -0
  128. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/rda.py +0 -0
  129. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/rds.py +0 -0
  130. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/sas7bdat.py +0 -0
  131. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/sav.py +0 -0
  132. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/sqlite.py +0 -0
  133. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/stub.py +0 -0
  134. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/sylk.py +0 -0
  135. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/tab.py +0 -0
  136. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/toml.py +0 -0
  137. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/tsv.py +0 -0
  138. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/txt.py +0 -0
  139. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/vm.py +0 -0
  140. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/wks.py +0 -0
  141. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/xls.py +0 -0
  142. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/xlsm.py +0 -0
  143. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/xlsx.py +0 -0
  144. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/xml.py +0 -0
  145. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/xpt.py +0 -0
  146. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/yaml.py +0 -0
  147. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/zip.py +0 -0
  148. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/file/zsav.py +0 -0
  149. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/mixins.py +0 -0
  150. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/ops/README.md +0 -0
  151. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/ops/__init__.py +0 -0
  152. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/ops/enums.py +0 -0
  153. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/ops/extract.py +0 -0
  154. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/ops/load.py +0 -0
  155. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/ops/run.py +0 -0
  156. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/ops/transform.py +0 -0
  157. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/ops/types.py +0 -0
  158. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/ops/utils.py +0 -0
  159. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/ops/validate.py +0 -0
  160. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/py.typed +0 -0
  161. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/templates/README.md +0 -0
  162. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/templates/__init__.py +0 -0
  163. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/templates/ddl.sql.j2 +0 -0
  164. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/templates/view.sql.j2 +0 -0
  165. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/types.py +0 -0
  166. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/utils.py +0 -0
  167. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/workflow/README.md +0 -0
  168. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/workflow/__init__.py +0 -0
  169. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/workflow/dag.py +0 -0
  170. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/workflow/jobs.py +0 -0
  171. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus/workflow/profile.py +0 -0
  172. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus.egg-info/dependency_links.txt +0 -0
  173. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus.egg-info/entry_points.txt +0 -0
  174. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus.egg-info/requires.txt +0 -0
  175. {etlplus-0.17.3 → etlplus-0.17.4}/etlplus.egg-info/top_level.txt +0 -0
  176. {etlplus-0.17.3 → etlplus-0.17.4}/examples/README.md +0 -0
  177. {etlplus-0.17.3 → etlplus-0.17.4}/examples/configs/ddl_spec.yml +0 -0
  178. {etlplus-0.17.3 → etlplus-0.17.4}/examples/configs/pipeline.yml +0 -0
  179. {etlplus-0.17.3 → etlplus-0.17.4}/examples/data/sample.csv +0 -0
  180. {etlplus-0.17.3 → etlplus-0.17.4}/examples/data/sample.json +0 -0
  181. {etlplus-0.17.3 → etlplus-0.17.4}/examples/data/sample.xml +0 -0
  182. {etlplus-0.17.3 → etlplus-0.17.4}/examples/data/sample.xsd +0 -0
  183. {etlplus-0.17.3 → etlplus-0.17.4}/examples/data/sample.yaml +0 -0
  184. {etlplus-0.17.3 → etlplus-0.17.4}/examples/quickstart.py +0 -0
  185. {etlplus-0.17.3 → etlplus-0.17.4}/pyproject.toml +0 -0
  186. {etlplus-0.17.3 → etlplus-0.17.4}/pytest.ini +0 -0
  187. {etlplus-0.17.3 → etlplus-0.17.4}/setup.cfg +0 -0
  188. {etlplus-0.17.3 → etlplus-0.17.4}/setup.py +0 -0
  189. {etlplus-0.17.3 → etlplus-0.17.4}/tests/README.md +0 -0
  190. {etlplus-0.17.3 → etlplus-0.17.4}/tests/__init__.py +0 -0
  191. {etlplus-0.17.3 → etlplus-0.17.4}/tests/conftest.py +0 -0
  192. {etlplus-0.17.3 → etlplus-0.17.4}/tests/integration/test_i_cli.py +0 -0
  193. {etlplus-0.17.3 → etlplus-0.17.4}/tests/integration/test_i_config_load.py +0 -0
  194. {etlplus-0.17.3 → etlplus-0.17.4}/tests/integration/test_i_examples_data_parity.py +0 -0
  195. {etlplus-0.17.3 → etlplus-0.17.4}/tests/integration/test_i_pagination_strategy.py +0 -0
  196. {etlplus-0.17.3 → etlplus-0.17.4}/tests/integration/test_i_run.py +0 -0
  197. {etlplus-0.17.3 → etlplus-0.17.4}/tests/integration/test_i_run_profile_pagination_defaults.py +0 -0
  198. {etlplus-0.17.3 → etlplus-0.17.4}/tests/integration/test_i_run_profile_rate_limit_defaults.py +0 -0
  199. {etlplus-0.17.3 → etlplus-0.17.4}/tests/smoke/__init__.py +0 -0
  200. {etlplus-0.17.3 → etlplus-0.17.4}/tests/smoke/test_s_cli_check.py +0 -0
  201. {etlplus-0.17.3 → etlplus-0.17.4}/tests/smoke/test_s_cli_extract.py +0 -0
  202. {etlplus-0.17.3 → etlplus-0.17.4}/tests/smoke/test_s_cli_load.py +0 -0
  203. {etlplus-0.17.3 → etlplus-0.17.4}/tests/smoke/test_s_cli_render.py +0 -0
  204. {etlplus-0.17.3 → etlplus-0.17.4}/tests/smoke/test_s_cli_transform.py +0 -0
  205. {etlplus-0.17.3 → etlplus-0.17.4}/tests/smoke/test_s_cli_validate.py +0 -0
  206. {etlplus-0.17.3 → etlplus-0.17.4}/tests/smoke/test_s_pipeline.py +0 -0
  207. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/api/test_u_api_auth.py +0 -0
  208. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/api/test_u_api_config.py +0 -0
  209. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/api/test_u_api_endpoint_client.py +0 -0
  210. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/api/test_u_api_enums.py +0 -0
  211. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/api/test_u_api_mocks.py +0 -0
  212. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/api/test_u_api_pagination_client.py +0 -0
  213. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/api/test_u_api_pagination_config.py +0 -0
  214. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/api/test_u_api_paginator.py +0 -0
  215. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/api/test_u_api_rate_limit_config.py +0 -0
  216. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/api/test_u_api_rate_limiter.py +0 -0
  217. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/api/test_u_api_request_manager.py +0 -0
  218. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/api/test_u_api_retry_manager.py +0 -0
  219. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/api/test_u_api_transport.py +0 -0
  220. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/api/test_u_api_types.py +0 -0
  221. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/api/test_u_api_utils.py +0 -0
  222. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/cli/test_u_cli_handlers.py +0 -0
  223. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/cli/test_u_cli_io.py +0 -0
  224. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/cli/test_u_cli_main.py +0 -0
  225. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/cli/test_u_cli_state.py +0 -0
  226. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/connector/test_u_connector_enums.py +0 -0
  227. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/connector/test_u_connector_utils.py +0 -0
  228. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/database/test_u_database_ddl.py +0 -0
  229. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/database/test_u_database_engine.py +0 -0
  230. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/database/test_u_database_orm.py +0 -0
  231. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/database/test_u_database_schema.py +0 -0
  232. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/file/test_u_file_enums.py +0 -0
  233. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/ops/test_u_ops_enums.py +0 -0
  234. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/ops/test_u_ops_extract.py +0 -0
  235. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/ops/test_u_ops_load.py +0 -0
  236. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/ops/test_u_ops_run.py +0 -0
  237. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/ops/test_u_ops_transform.py +0 -0
  238. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/ops/test_u_ops_utils.py +0 -0
  239. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/ops/test_u_ops_validate.py +0 -0
  240. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/test_u_config.py +0 -0
  241. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/test_u_main.py +0 -0
  242. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/test_u_mixins.py +0 -0
  243. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/test_u_utils.py +0 -0
  244. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/test_u_version.py +0 -0
  245. {etlplus-0.17.3 → etlplus-0.17.4}/tests/unit/workflow/test_u_workflow_jobs.py +0 -0
  246. {etlplus-0.17.3 → etlplus-0.17.4}/tools/update_demo_snippets.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: etlplus
3
- Version: 0.17.3
3
+ Version: 0.17.4
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: etlplus
3
- Version: 0.17.3
3
+ Version: 0.17.4
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
@@ -229,6 +229,7 @@ tests/unit/database/test_u_database_ddl.py
229
229
  tests/unit/database/test_u_database_engine.py
230
230
  tests/unit/database/test_u_database_orm.py
231
231
  tests/unit/database/test_u_database_schema.py
232
+ tests/unit/file/conftest.py
232
233
  tests/unit/file/test_u_file_core.py
233
234
  tests/unit/file/test_u_file_enums.py
234
235
  tests/unit/file/test_u_file_yaml.py
@@ -1,7 +1,8 @@
1
1
  """
2
2
  :mod:`tests.integration.conftest` module.
3
3
 
4
- Configures pytest-based integration tests and provides shared fixtures.
4
+ Define shared fixtures and helpers for pytest-based integration tests of
5
+ :mod:`etlplus`.
5
6
 
6
7
  Notes
7
8
  -----
@@ -1,7 +1,12 @@
1
1
  """
2
2
  :mod:`tests.smoke.conftest` module.
3
3
 
4
- Shared fixtures for smoke tests.
4
+ Define shared fixtures and helpers for pytest-based smoke tests of
5
+ :mod:`etlplus`.
6
+
7
+ Notes
8
+ -----
9
+ - Fixtures are designed for reuse and DRY test setup.
5
10
  """
6
11
 
7
12
  from __future__ import annotations
@@ -1,13 +1,13 @@
1
1
  """
2
2
  :mod:`tests.unit.api.conftest` module.
3
3
 
4
- Configures pytest-based unit tests for and provides shared fixtures for
4
+ Define shared fixtures and helpers for pytest-based unit tests of
5
5
  :mod:`etlplus.api`.
6
6
 
7
7
  Notes
8
8
  -----
9
- - Fixtures are designed for reuse and DRY test setup across API-focused
10
- unit tests.
9
+ - Fixtures are designed for reuse and DRY test setup across API-focused unit
10
+ tests.
11
11
  """
12
12
 
13
13
  from __future__ import annotations
@@ -1,8 +1,8 @@
1
1
  """
2
2
  :mod:`tests.unit.cli.conftest` module.
3
3
 
4
- Configures pytest-based unit tests and provides shared fixtures for
5
- :mod:`etlplus.cli` unit tests.
4
+ Define shared fixtures and helpers for pytest-based unit tests of
5
+ :mod:`etlplus.cli`.
6
6
 
7
7
  Notes
8
8
  -----
@@ -1,7 +1,8 @@
1
1
  """
2
2
  :mod:`tests.unit.conftest` module.
3
3
 
4
- Configures pytest-based unit tests and provides shared fixtures.
4
+ Define shared fixtures and helpers for pytest-based unit tests of
5
+ :mod:`etlplus`.
5
6
 
6
7
  Notes
7
8
  -----
@@ -0,0 +1,112 @@
1
+ """
2
+ :mod:`tests.unit.file.conftest` module.
3
+
4
+ Define shared fixtures and helpers for pytest-based unit tests of
5
+ :mod:`etlplus.file`.
6
+
7
+ Notes
8
+ -----
9
+ - Fixtures are designed for reuse and DRY test setup across file-focused unit
10
+ tests.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import math
16
+ import numbers
17
+
18
+ import pytest
19
+
20
+ from etlplus.types import JSONData
21
+ from etlplus.types import JSONDict
22
+
23
+ # SECTION: MARKERS ========================================================== #
24
+
25
+
26
+ # Directory-level marker for unit tests.
27
+ pytestmark = pytest.mark.unit
28
+
29
+
30
+ # SECTION: HELPERS ========================================================== #
31
+
32
+
33
+ def require_optional_modules(*modules: str) -> None:
34
+ """
35
+ Skip the test when optional dependencies are missing.
36
+
37
+ Parameters
38
+ ----------
39
+ *modules : str
40
+ Module names to verify via ``pytest.importorskip``.
41
+ """
42
+ for module in modules:
43
+ pytest.importorskip(module)
44
+
45
+
46
+ def _coerce_numeric_value(value: object) -> object:
47
+ """Coerce numeric scalars into stable Python numeric types."""
48
+ if isinstance(value, numbers.Real):
49
+ try:
50
+ numeric = float(value)
51
+ if math.isnan(numeric):
52
+ return None
53
+ except (TypeError, ValueError):
54
+ return value
55
+ if numeric.is_integer():
56
+ return int(numeric)
57
+ return float(numeric)
58
+ return value
59
+
60
+
61
+ def normalize_numeric_records(records: JSONData) -> JSONData:
62
+ """
63
+ Normalize numeric record values for deterministic comparisons.
64
+
65
+ Parameters
66
+ ----------
67
+ records : JSONData
68
+ Record payloads to normalize.
69
+
70
+ Returns
71
+ -------
72
+ JSONData
73
+ Normalized record payloads.
74
+ """
75
+ if isinstance(records, list):
76
+ normalized: list[JSONDict] = []
77
+ for row in records:
78
+ if not isinstance(row, dict):
79
+ normalized.append(row)
80
+ continue
81
+ cleaned: JSONDict = {}
82
+ for key, value in row.items():
83
+ cleaned[key] = _coerce_numeric_value(value)
84
+ normalized.append(cleaned)
85
+ return normalized
86
+ return records
87
+
88
+
89
+ def normalize_xml_payload(payload: JSONData) -> JSONData:
90
+ """
91
+ Normalize XML payloads to list-based item structures when possible.
92
+
93
+ Parameters
94
+ ----------
95
+ payload : JSONData
96
+ XML payload to normalize.
97
+
98
+ Returns
99
+ -------
100
+ JSONData
101
+ Normalized XML payload.
102
+ """
103
+ if not isinstance(payload, dict):
104
+ return payload
105
+ root = payload.get('root')
106
+ if not isinstance(root, dict):
107
+ return payload
108
+ items = root.get('items')
109
+ if isinstance(items, dict):
110
+ root = {**root, 'items': [items]}
111
+ return {**payload, 'root': root}
112
+ return payload
@@ -11,17 +11,24 @@ Notes
11
11
 
12
12
  from __future__ import annotations
13
13
 
14
- import math
15
- import numbers
14
+ import sqlite3
15
+ import zipfile
16
+ from os import PathLike
16
17
  from pathlib import Path
18
+ from pathlib import PurePath
17
19
  from typing import cast
18
20
 
19
21
  import pytest
20
22
 
21
23
  from etlplus.file import File
22
24
  from etlplus.file import FileFormat
25
+ from etlplus.file import csv as csv_file
26
+ from etlplus.file import json as json_file
27
+ from etlplus.file import xml as xml_file
23
28
  from etlplus.types import JSONData
24
- from etlplus.types import JSONDict
29
+ from tests.unit.file.conftest import normalize_numeric_records
30
+ from tests.unit.file.conftest import normalize_xml_payload
31
+ from tests.unit.file.conftest import require_optional_modules
25
32
 
26
33
  # SECTION: MARKERS ========================================================== #
27
34
 
@@ -299,64 +306,41 @@ FORMAT_CASES: list[FormatCase] = [
299
306
  ),
300
307
  ]
301
308
 
302
-
303
- def _coerce_numeric_value(
304
- value: object,
305
- ) -> object:
306
- """Coerce numeric scalars into stable Python numeric types."""
307
- if isinstance(value, numbers.Real):
308
- try:
309
- numeric = float(value)
310
- if math.isnan(numeric):
311
- return None
312
- except (TypeError, ValueError):
313
- return value
314
- if numeric.is_integer():
315
- return int(numeric)
316
- return float(numeric)
317
- return value
318
-
319
-
320
- def _normalize_numeric_records(
321
- records: JSONData,
322
- ) -> JSONData:
323
- """Normalize numeric record values (e.g., floats to ints when integral)."""
324
- if isinstance(records, list):
325
- normalized: list[JSONDict] = []
326
- for row in records:
327
- if not isinstance(row, dict):
328
- normalized.append(row)
329
- continue
330
- cleaned: JSONDict = {}
331
- for key, value in row.items():
332
- cleaned[key] = _coerce_numeric_value(value)
333
- normalized.append(cleaned)
334
- return normalized
335
- return records
336
-
337
-
338
- def _normalize_xml_payload(
339
- payload: JSONData,
340
- ) -> JSONData:
341
- """Normalize XML payloads to list-based item structures when possible."""
342
- if not isinstance(payload, dict):
343
- return payload
344
- root = payload.get('root')
345
- if not isinstance(root, dict):
346
- return payload
347
- items = root.get('items')
348
- if isinstance(items, dict):
349
- root = {**root, 'items': [items]}
350
- return {**payload, 'root': root}
351
- return payload
352
-
353
-
354
- def _require_modules(
355
- modules: tuple[str, ...],
356
- ) -> None:
357
- """Skip the test when optional dependencies are missing."""
358
- for module in modules:
359
- pytest.importorskip(module)
309
+ STUBBED_FORMATS: tuple[tuple[FileFormat, str], ...] = (
310
+ # Permanent stub as formality
311
+ (FileFormat.STUB, 'data.stub'),
312
+ # Temporary stubs until implemented
313
+ (FileFormat.ACCDB, 'data.accdb'),
314
+ (FileFormat.CFG, 'data.cfg'),
315
+ (FileFormat.CONF, 'data.conf'),
316
+ (FileFormat.ION, 'data.ion'),
317
+ (FileFormat.JINJA2, 'data.jinja2'),
318
+ (FileFormat.LOG, 'data.log'),
319
+ (FileFormat.MAT, 'data.mat'),
320
+ (FileFormat.MDB, 'data.mdb'),
321
+ # (FileFormat.MDF, 'data.mdf'),
322
+ (FileFormat.MUSTACHE, 'data.mustache'),
323
+ (FileFormat.PBF, 'data.pbf'),
324
+ # (FileFormat.RAW, 'data.raw'),
325
+ # (FileFormat.RTF, 'data.rtf'),
326
+ # (FileFormat.SDF, 'data.sdf'),
327
+ # (FileFormat.SLV, 'data.slv'),
328
+ (FileFormat.SYLK, 'data.sylk'),
329
+ # (FileFormat.VCF, 'data.vcf'),
330
+ (FileFormat.VM, 'data.vm'),
331
+ # (FileFormat.WSV, 'data.wsv'),
332
+ (FileFormat.ZSAV, 'data.zsav'),
333
+ )
334
+
335
+
336
+ class _DummyPath(PathLike[str]):
337
+ """Simple PathLike wrapper for StrPath tests."""
338
+
339
+ def __init__(self, path: str) -> None:
340
+ self._path = path
341
+
342
+ def __fspath__(self) -> str:
343
+ return self._path
360
344
 
361
345
 
362
346
  # SECTION: FIXTURES ========================================================= #
@@ -365,31 +349,7 @@ def _require_modules(
365
349
  @pytest.fixture(name='stubbed_formats')
366
350
  def stubbed_formats_fixture() -> list[tuple[FileFormat, str]]:
367
351
  """Return a list of stubbed file formats for testing."""
368
- return [
369
- # Permanent stub as formality
370
- (FileFormat.STUB, 'data.stub'),
371
- # Temporary stubs until implemented
372
- (FileFormat.ACCDB, 'data.accdb'),
373
- (FileFormat.CFG, 'data.cfg'),
374
- (FileFormat.CONF, 'data.conf'),
375
- (FileFormat.ION, 'data.ion'),
376
- (FileFormat.JINJA2, 'data.jinja2'),
377
- (FileFormat.LOG, 'data.log'),
378
- (FileFormat.MAT, 'data.mat'),
379
- (FileFormat.MDB, 'data.mdb'),
380
- # (FileFormat.MDF, 'data.mdf'),
381
- (FileFormat.MUSTACHE, 'data.mustache'),
382
- (FileFormat.PBF, 'data.pbf'),
383
- # (FileFormat.RAW, 'data.raw'),
384
- # (FileFormat.RTF, 'data.rtf'),
385
- # (FileFormat.SDF, 'data.sdf'),
386
- # (FileFormat.SLV, 'data.slv'),
387
- (FileFormat.SYLK, 'data.sylk'),
388
- # (FileFormat.VCF, 'data.vcf'),
389
- (FileFormat.VM, 'data.vm'),
390
- # (FileFormat.WSV, 'data.wsv'),
391
- (FileFormat.ZSAV, 'data.zsav'),
392
- ]
352
+ return list(STUBBED_FORMATS)
393
353
 
394
354
 
395
355
  # SECTION: TESTS ============================================================ #
@@ -421,6 +381,40 @@ class TestFile:
421
381
  f.read()
422
382
  assert 'compressed file' in str(e.value)
423
383
 
384
+ def test_duckdb_read_fails_with_multiple_tables(
385
+ self,
386
+ tmp_path: Path,
387
+ ) -> None:
388
+ """
389
+ Test DuckDB reader rejects multiple table databases.
390
+ """
391
+ duckdb = pytest.importorskip('duckdb')
392
+ path = tmp_path / 'multi.duckdb'
393
+ conn = duckdb.connect(str(path))
394
+ try:
395
+ conn.execute('CREATE TABLE one (id INTEGER)')
396
+ conn.execute('CREATE TABLE two (id INTEGER)')
397
+ finally:
398
+ conn.close()
399
+
400
+ with pytest.raises(ValueError, match='Multiple tables'):
401
+ File(path, FileFormat.DUCKDB).read()
402
+
403
+ def test_gz_round_trip_json(
404
+ self,
405
+ tmp_path: Path,
406
+ ) -> None:
407
+ """
408
+ Test JSON round-trip inside a gzip archive.
409
+ """
410
+ path = tmp_path / 'data.json.gz'
411
+ payload = [{'name': 'Ada'}]
412
+
413
+ File(path, FileFormat.GZ).write(payload)
414
+ result = File(path, FileFormat.GZ).read()
415
+
416
+ assert result == payload
417
+
424
418
  @pytest.mark.parametrize(
425
419
  'filename,expected_format',
426
420
  [
@@ -541,7 +535,7 @@ class TestFile:
541
535
  requires: tuple[str, ...],
542
536
  ) -> None:
543
537
  """Test round-trip reads and writes across file formats."""
544
- _require_modules(requires)
538
+ require_optional_modules(*requires)
545
539
  path = tmp_path / filename
546
540
 
547
541
  File(path, file_format).write(payload)
@@ -553,12 +547,57 @@ class TestFile:
553
547
  raise
554
548
 
555
549
  if file_format is FileFormat.XML:
556
- result = _normalize_xml_payload(result)
557
- expected = _normalize_xml_payload(expected)
550
+ result = normalize_xml_payload(result)
551
+ expected = normalize_xml_payload(expected)
558
552
  if file_format is FileFormat.XLS:
559
- result = _normalize_numeric_records(result)
553
+ result = normalize_numeric_records(result)
560
554
  assert result == expected
561
555
 
556
+ def test_sqlite_read_fails_with_multiple_tables(
557
+ self,
558
+ tmp_path: Path,
559
+ ) -> None:
560
+ """
561
+ Test SQLite reader rejects multiple table databases.
562
+ """
563
+ path = tmp_path / 'multi.sqlite'
564
+ conn = sqlite3.connect(path)
565
+ try:
566
+ conn.execute('CREATE TABLE one (id INTEGER)')
567
+ conn.execute('CREATE TABLE two (id INTEGER)')
568
+ conn.commit()
569
+ finally:
570
+ conn.close()
571
+
572
+ with pytest.raises(ValueError, match='Multiple tables'):
573
+ File(path, FileFormat.SQLITE).read()
574
+
575
+ def test_strpath_support_for_module_helpers(
576
+ self,
577
+ tmp_path: Path,
578
+ ) -> None:
579
+ """
580
+ Test module helpers accept ``StrPath`` inputs.
581
+
582
+ Uses ``str`` and ``PathLike`` inputs to validate normalization.
583
+ """
584
+ csv_path = tmp_path / 'data.csv'
585
+ json_path = tmp_path / 'data.json'
586
+ xml_path = tmp_path / 'data.xml'
587
+
588
+ csv_file.write(str(csv_path), [{'name': 'Ada'}])
589
+ assert csv_file.read(str(csv_path)) == [{'name': 'Ada'}]
590
+
591
+ json_file.write(_DummyPath(str(json_path)), {'name': 'Ada'})
592
+ assert json_file.read(_DummyPath(str(json_path))) == {'name': 'Ada'}
593
+
594
+ xml_file.write(
595
+ PurePath(xml_path),
596
+ {'root': {'text': 'hello'}},
597
+ root_tag='root',
598
+ )
599
+ assert xml_file.read(PurePath(xml_path)) == {'root': {'text': 'hello'}}
600
+
562
601
  def test_stub_formats_raise_on_read(
563
602
  self,
564
603
  tmp_path: Path,
@@ -680,3 +719,39 @@ class TestFile:
680
719
  text = path.read_text(encoding='utf-8')
681
720
  assert text.startswith('<?xml')
682
721
  assert '<records>' in text
722
+
723
+ def test_xml_round_trip_with_attributes(
724
+ self,
725
+ tmp_path: Path,
726
+ ) -> None:
727
+ """
728
+ Test XML read/write preserves attribute fields.
729
+ """
730
+ path = tmp_path / 'attrs.xml'
731
+ payload: JSONData = {
732
+ 'root': {
733
+ '@id': '42',
734
+ 'item': {'@lang': 'en', 'text': 'Hello'},
735
+ },
736
+ }
737
+
738
+ File(path, FileFormat.XML).write(payload)
739
+ result = File(path, FileFormat.XML).read()
740
+
741
+ assert result == payload
742
+
743
+ def test_zip_multi_file_read(
744
+ self,
745
+ tmp_path: Path,
746
+ ) -> None:
747
+ """
748
+ Test ZIP files with multiple entries return a dict payload.
749
+ """
750
+ path = tmp_path / 'bundle.zip'
751
+ with zipfile.ZipFile(path, 'w') as archive:
752
+ archive.writestr('a.json', '{"a": 1}')
753
+ archive.writestr('b.json', '{"b": 2}')
754
+
755
+ result = File(path, FileFormat.ZIP).read()
756
+
757
+ assert result == {'a.json': {'a': 1}, 'b.json': {'b': 2}}
@@ -6,7 +6,7 @@ Unit tests for :mod:`etlplus.file.yaml`.
6
6
  Notes
7
7
  -----
8
8
  - Uses ``tmp_path`` for filesystem isolation.
9
- - Exercises JSON detection and defers errors for unknown extensions.
9
+ - Exercises YAML read/write helpers with a stubbed PyYAML module.
10
10
  """
11
11
 
12
12
  from __future__ import annotations
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes