etlplus 0.16.2__tar.gz → 0.16.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 (234) hide show
  1. {etlplus-0.16.2 → etlplus-0.16.4}/CONTRIBUTING.md +1 -1
  2. {etlplus-0.16.2/etlplus.egg-info → etlplus-0.16.4}/PKG-INFO +1 -1
  3. {etlplus-0.16.2 → etlplus-0.16.4}/docs/pipeline-guide.md +2 -2
  4. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/README.md +22 -0
  5. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/__init__.py +2 -0
  6. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/cli/commands.py +22 -22
  7. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/cli/handlers.py +8 -9
  8. etlplus-0.16.2/etlplus/workflow/pipeline.py → etlplus-0.16.4/etlplus/config.py +17 -37
  9. etlplus-0.16.4/etlplus/enums.py +144 -0
  10. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/ops/__init__.py +1 -0
  11. {etlplus-0.16.2/etlplus → etlplus-0.16.4/etlplus/ops}/enums.py +5 -108
  12. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/ops/run.py +3 -3
  13. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/ops/transform.py +16 -16
  14. etlplus-0.16.4/etlplus/ops/types.py +147 -0
  15. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/types.py +2 -100
  16. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/workflow/README.md +0 -24
  17. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/workflow/__init__.py +0 -4
  18. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/workflow/jobs.py +0 -2
  19. {etlplus-0.16.2 → etlplus-0.16.4/etlplus.egg-info}/PKG-INFO +1 -1
  20. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus.egg-info/SOURCES.txt +8 -6
  21. {etlplus-0.16.2 → etlplus-0.16.4}/examples/README.md +1 -1
  22. etlplus-0.16.2/examples/quickstart_python.py → etlplus-0.16.4/examples/quickstart.py +22 -1
  23. {etlplus-0.16.2 → etlplus-0.16.4}/tests/integration/conftest.py +13 -13
  24. etlplus-0.16.2/tests/integration/test_i_pipeline_yaml_load.py → etlplus-0.16.4/tests/integration/test_i_config_load.py +9 -9
  25. {etlplus-0.16.2 → etlplus-0.16.4}/tests/integration/test_i_pagination_strategy.py +2 -2
  26. {etlplus-0.16.2 → etlplus-0.16.4}/tests/integration/test_i_run.py +3 -3
  27. {etlplus-0.16.2 → etlplus-0.16.4}/tests/integration/test_i_run_profile_pagination_defaults.py +3 -3
  28. {etlplus-0.16.2 → etlplus-0.16.4}/tests/integration/test_i_run_profile_rate_limit_defaults.py +2 -2
  29. etlplus-0.16.2/tests/unit/api/test_u_config.py → etlplus-0.16.4/tests/unit/api/test_u_api_config.py +1 -1
  30. {etlplus-0.16.2 → etlplus-0.16.4}/tests/unit/cli/conftest.py +3 -3
  31. {etlplus-0.16.2 → etlplus-0.16.4}/tests/unit/cli/test_u_cli_handlers.py +19 -19
  32. {etlplus-0.16.2 → etlplus-0.16.4}/tests/unit/conftest.py +12 -7
  33. etlplus-0.16.2/tests/unit/test_u_enums.py → etlplus-0.16.4/tests/unit/ops/test_u_ops_enums.py +8 -8
  34. {etlplus-0.16.2 → etlplus-0.16.4}/tests/unit/ops/test_u_ops_run.py +24 -24
  35. {etlplus-0.16.2 → etlplus-0.16.4}/tests/unit/ops/test_u_ops_transform.py +3 -3
  36. etlplus-0.16.2/tests/unit/workflow/test_u_workflow_pipeline.py → etlplus-0.16.4/tests/unit/test_u_config.py +25 -25
  37. {etlplus-0.16.2 → etlplus-0.16.4}/.coveragerc +0 -0
  38. {etlplus-0.16.2 → etlplus-0.16.4}/.editorconfig +0 -0
  39. {etlplus-0.16.2 → etlplus-0.16.4}/.gitattributes +0 -0
  40. {etlplus-0.16.2 → etlplus-0.16.4}/.github/actions/python-bootstrap/action.yml +0 -0
  41. {etlplus-0.16.2 → etlplus-0.16.4}/.github/workflows/ci.yml +0 -0
  42. {etlplus-0.16.2 → etlplus-0.16.4}/.gitignore +0 -0
  43. {etlplus-0.16.2 → etlplus-0.16.4}/.pre-commit-config.yaml +0 -0
  44. {etlplus-0.16.2 → etlplus-0.16.4}/.ruff.toml +0 -0
  45. {etlplus-0.16.2 → etlplus-0.16.4}/CODE_OF_CONDUCT.md +0 -0
  46. {etlplus-0.16.2 → etlplus-0.16.4}/DEMO.md +0 -0
  47. {etlplus-0.16.2 → etlplus-0.16.4}/LICENSE +0 -0
  48. {etlplus-0.16.2 → etlplus-0.16.4}/MANIFEST.in +0 -0
  49. {etlplus-0.16.2 → etlplus-0.16.4}/Makefile +0 -0
  50. {etlplus-0.16.2 → etlplus-0.16.4}/README.md +0 -0
  51. {etlplus-0.16.2 → etlplus-0.16.4}/REFERENCES.md +0 -0
  52. {etlplus-0.16.2 → etlplus-0.16.4}/SECURITY.md +0 -0
  53. {etlplus-0.16.2 → etlplus-0.16.4}/SUPPORT.md +0 -0
  54. {etlplus-0.16.2 → etlplus-0.16.4}/docs/README.md +0 -0
  55. {etlplus-0.16.2 → etlplus-0.16.4}/docs/snippets/installation_version.md +0 -0
  56. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/__main__.py +0 -0
  57. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/__version__.py +0 -0
  58. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/api/README.md +0 -0
  59. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/api/__init__.py +0 -0
  60. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/api/auth.py +0 -0
  61. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/api/config.py +0 -0
  62. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/api/endpoint_client.py +0 -0
  63. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/api/enums.py +0 -0
  64. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/api/errors.py +0 -0
  65. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/api/pagination/__init__.py +0 -0
  66. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/api/pagination/client.py +0 -0
  67. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/api/pagination/config.py +0 -0
  68. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/api/pagination/paginator.py +0 -0
  69. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/api/rate_limiting/__init__.py +0 -0
  70. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/api/rate_limiting/config.py +0 -0
  71. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/api/rate_limiting/rate_limiter.py +0 -0
  72. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/api/request_manager.py +0 -0
  73. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/api/retry_manager.py +0 -0
  74. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/api/transport.py +0 -0
  75. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/api/types.py +0 -0
  76. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/api/utils.py +0 -0
  77. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/cli/README.md +0 -0
  78. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/cli/__init__.py +0 -0
  79. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/cli/constants.py +0 -0
  80. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/cli/io.py +0 -0
  81. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/cli/main.py +0 -0
  82. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/cli/options.py +0 -0
  83. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/cli/state.py +0 -0
  84. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/cli/types.py +0 -0
  85. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/connector/__init__.py +0 -0
  86. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/connector/api.py +0 -0
  87. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/connector/connector.py +0 -0
  88. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/connector/core.py +0 -0
  89. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/connector/database.py +0 -0
  90. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/connector/enums.py +0 -0
  91. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/connector/file.py +0 -0
  92. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/connector/types.py +0 -0
  93. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/connector/utils.py +0 -0
  94. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/database/README.md +0 -0
  95. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/database/__init__.py +0 -0
  96. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/database/ddl.py +0 -0
  97. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/database/engine.py +0 -0
  98. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/database/orm.py +0 -0
  99. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/database/schema.py +0 -0
  100. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/database/types.py +0 -0
  101. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/README.md +0 -0
  102. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/__init__.py +0 -0
  103. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/_imports.py +0 -0
  104. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/_io.py +0 -0
  105. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/accdb.py +0 -0
  106. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/arrow.py +0 -0
  107. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/avro.py +0 -0
  108. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/bson.py +0 -0
  109. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/cbor.py +0 -0
  110. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/cfg.py +0 -0
  111. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/conf.py +0 -0
  112. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/core.py +0 -0
  113. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/csv.py +0 -0
  114. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/dat.py +0 -0
  115. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/dta.py +0 -0
  116. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/duckdb.py +0 -0
  117. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/enums.py +0 -0
  118. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/feather.py +0 -0
  119. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/fwf.py +0 -0
  120. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/gz.py +0 -0
  121. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/hbs.py +0 -0
  122. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/hdf5.py +0 -0
  123. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/ini.py +0 -0
  124. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/ion.py +0 -0
  125. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/jinja2.py +0 -0
  126. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/json.py +0 -0
  127. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/log.py +0 -0
  128. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/mat.py +0 -0
  129. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/mdb.py +0 -0
  130. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/msgpack.py +0 -0
  131. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/mustache.py +0 -0
  132. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/nc.py +0 -0
  133. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/ndjson.py +0 -0
  134. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/numbers.py +0 -0
  135. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/ods.py +0 -0
  136. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/orc.py +0 -0
  137. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/parquet.py +0 -0
  138. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/pb.py +0 -0
  139. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/pbf.py +0 -0
  140. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/properties.py +0 -0
  141. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/proto.py +0 -0
  142. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/psv.py +0 -0
  143. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/rda.py +0 -0
  144. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/rds.py +0 -0
  145. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/sas7bdat.py +0 -0
  146. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/sav.py +0 -0
  147. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/sqlite.py +0 -0
  148. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/stub.py +0 -0
  149. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/sylk.py +0 -0
  150. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/tab.py +0 -0
  151. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/toml.py +0 -0
  152. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/tsv.py +0 -0
  153. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/txt.py +0 -0
  154. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/vm.py +0 -0
  155. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/wks.py +0 -0
  156. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/xls.py +0 -0
  157. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/xlsm.py +0 -0
  158. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/xlsx.py +0 -0
  159. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/xml.py +0 -0
  160. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/xpt.py +0 -0
  161. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/yaml.py +0 -0
  162. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/zip.py +0 -0
  163. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/file/zsav.py +0 -0
  164. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/mixins.py +0 -0
  165. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/ops/README.md +0 -0
  166. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/ops/extract.py +0 -0
  167. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/ops/load.py +0 -0
  168. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/ops/utils.py +0 -0
  169. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/ops/validate.py +0 -0
  170. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/py.typed +0 -0
  171. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/templates/README.md +0 -0
  172. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/templates/__init__.py +0 -0
  173. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/templates/ddl.sql.j2 +0 -0
  174. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/templates/view.sql.j2 +0 -0
  175. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/utils.py +0 -0
  176. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/workflow/dag.py +0 -0
  177. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus/workflow/profile.py +0 -0
  178. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus.egg-info/dependency_links.txt +0 -0
  179. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus.egg-info/entry_points.txt +0 -0
  180. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus.egg-info/requires.txt +0 -0
  181. {etlplus-0.16.2 → etlplus-0.16.4}/etlplus.egg-info/top_level.txt +0 -0
  182. {etlplus-0.16.2 → etlplus-0.16.4}/examples/configs/ddl_spec.yml +0 -0
  183. {etlplus-0.16.2 → etlplus-0.16.4}/examples/configs/pipeline.yml +0 -0
  184. {etlplus-0.16.2 → etlplus-0.16.4}/examples/data/sample.csv +0 -0
  185. {etlplus-0.16.2 → etlplus-0.16.4}/examples/data/sample.json +0 -0
  186. {etlplus-0.16.2 → etlplus-0.16.4}/examples/data/sample.xml +0 -0
  187. {etlplus-0.16.2 → etlplus-0.16.4}/examples/data/sample.xsd +0 -0
  188. {etlplus-0.16.2 → etlplus-0.16.4}/examples/data/sample.yaml +0 -0
  189. {etlplus-0.16.2 → etlplus-0.16.4}/pyproject.toml +0 -0
  190. {etlplus-0.16.2 → etlplus-0.16.4}/pytest.ini +0 -0
  191. {etlplus-0.16.2 → etlplus-0.16.4}/setup.cfg +0 -0
  192. {etlplus-0.16.2 → etlplus-0.16.4}/setup.py +0 -0
  193. {etlplus-0.16.2 → etlplus-0.16.4}/tests/__init__.py +0 -0
  194. {etlplus-0.16.2 → etlplus-0.16.4}/tests/conftest.py +0 -0
  195. {etlplus-0.16.2 → etlplus-0.16.4}/tests/integration/test_i_cli.py +0 -0
  196. {etlplus-0.16.2 → etlplus-0.16.4}/tests/integration/test_i_examples_data_parity.py +0 -0
  197. {etlplus-0.16.2 → etlplus-0.16.4}/tests/integration/test_i_pipeline_smoke.py +0 -0
  198. {etlplus-0.16.2 → etlplus-0.16.4}/tests/unit/api/conftest.py +0 -0
  199. {etlplus-0.16.2 → etlplus-0.16.4}/tests/unit/api/test_u_api_enums.py +0 -0
  200. {etlplus-0.16.2 → etlplus-0.16.4}/tests/unit/api/test_u_api_utils.py +0 -0
  201. {etlplus-0.16.2 → etlplus-0.16.4}/tests/unit/api/test_u_auth.py +0 -0
  202. {etlplus-0.16.2 → etlplus-0.16.4}/tests/unit/api/test_u_endpoint_client.py +0 -0
  203. {etlplus-0.16.2 → etlplus-0.16.4}/tests/unit/api/test_u_mocks.py +0 -0
  204. {etlplus-0.16.2 → etlplus-0.16.4}/tests/unit/api/test_u_pagination_client.py +0 -0
  205. {etlplus-0.16.2 → etlplus-0.16.4}/tests/unit/api/test_u_pagination_config.py +0 -0
  206. {etlplus-0.16.2 → etlplus-0.16.4}/tests/unit/api/test_u_paginator.py +0 -0
  207. {etlplus-0.16.2 → etlplus-0.16.4}/tests/unit/api/test_u_rate_limit_config.py +0 -0
  208. {etlplus-0.16.2 → etlplus-0.16.4}/tests/unit/api/test_u_rate_limiter.py +0 -0
  209. {etlplus-0.16.2 → etlplus-0.16.4}/tests/unit/api/test_u_request_manager.py +0 -0
  210. {etlplus-0.16.2 → etlplus-0.16.4}/tests/unit/api/test_u_retry_manager.py +0 -0
  211. {etlplus-0.16.2 → etlplus-0.16.4}/tests/unit/api/test_u_transport.py +0 -0
  212. {etlplus-0.16.2 → etlplus-0.16.4}/tests/unit/api/test_u_types.py +0 -0
  213. {etlplus-0.16.2 → etlplus-0.16.4}/tests/unit/cli/test_u_cli_io.py +0 -0
  214. {etlplus-0.16.2 → etlplus-0.16.4}/tests/unit/cli/test_u_cli_main.py +0 -0
  215. {etlplus-0.16.2 → etlplus-0.16.4}/tests/unit/cli/test_u_cli_state.py +0 -0
  216. {etlplus-0.16.2 → etlplus-0.16.4}/tests/unit/connector/test_u_connector_enums.py +0 -0
  217. {etlplus-0.16.2 → etlplus-0.16.4}/tests/unit/connector/test_u_connector_utils.py +0 -0
  218. {etlplus-0.16.2 → etlplus-0.16.4}/tests/unit/database/test_u_database_ddl.py +0 -0
  219. {etlplus-0.16.2 → etlplus-0.16.4}/tests/unit/database/test_u_database_engine.py +0 -0
  220. {etlplus-0.16.2 → etlplus-0.16.4}/tests/unit/database/test_u_database_orm.py +0 -0
  221. {etlplus-0.16.2 → etlplus-0.16.4}/tests/unit/database/test_u_database_schema.py +0 -0
  222. {etlplus-0.16.2 → etlplus-0.16.4}/tests/unit/file/test_u_file_core.py +0 -0
  223. {etlplus-0.16.2 → etlplus-0.16.4}/tests/unit/file/test_u_file_enums.py +0 -0
  224. {etlplus-0.16.2 → etlplus-0.16.4}/tests/unit/file/test_u_file_yaml.py +0 -0
  225. {etlplus-0.16.2 → etlplus-0.16.4}/tests/unit/ops/test_u_ops_extract.py +0 -0
  226. {etlplus-0.16.2 → etlplus-0.16.4}/tests/unit/ops/test_u_ops_load.py +0 -0
  227. {etlplus-0.16.2 → etlplus-0.16.4}/tests/unit/ops/test_u_ops_utils.py +0 -0
  228. {etlplus-0.16.2 → etlplus-0.16.4}/tests/unit/ops/test_u_ops_validate.py +0 -0
  229. {etlplus-0.16.2 → etlplus-0.16.4}/tests/unit/test_u_main.py +0 -0
  230. {etlplus-0.16.2 → etlplus-0.16.4}/tests/unit/test_u_mixins.py +0 -0
  231. {etlplus-0.16.2 → etlplus-0.16.4}/tests/unit/test_u_utils.py +0 -0
  232. {etlplus-0.16.2 → etlplus-0.16.4}/tests/unit/test_u_version.py +0 -0
  233. {etlplus-0.16.2 → etlplus-0.16.4}/tests/unit/workflow/test_u_workflow_jobs.py +0 -0
  234. {etlplus-0.16.2 → etlplus-0.16.4}/tools/update_demo_snippets.py +0 -0
@@ -133,5 +133,5 @@ If a test calls `etlplus.cli.main()` or `etlplus.ops.run.run()`, it is integrati
133
133
  - CLI tests: monkeypatch `sys.argv` and call `etlplus.cli.main()`; capture output with `capsys`.
134
134
  - File I/O: use `tmp_path` / `TemporaryDirectory()`; never write to the repo tree.
135
135
  - API flows: stub `EndpointClient` or transport layer via `monkeypatch` to avoid real HTTP.
136
- - Runner tests: monkeypatch `load_pipeline_config` to inject an in-memory `PipelineConfig`.
136
+ - Runner tests: monkeypatch `load_config` to inject an in-memory `Config`.
137
137
  - Keep tests small and focused; prefer one behavior per test with clear assertions.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: etlplus
3
- Version: 0.16.2
3
+ Version: 0.16.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
@@ -497,8 +497,8 @@ For the HTTP client and pagination API, see `etlplus/api/README.md`.
497
497
 
498
498
  ## Design notes: Mapping inputs, dict outputs
499
499
 
500
- ETLPlus config constructors (e.g., `ApiConfig.from_obj`, `PipelineConfig.from_dict`) accept
501
- `Mapping[str, Any]` rather than `dict[str, Any]` for inputs. Why?
500
+ ETLPlus config constructors (e.g., `ApiConfig.from_obj`, `Config.from_dict`) accept `Mapping[str,
501
+ Any]` rather than `dict[str, Any]` for inputs. Why?
502
502
 
503
503
  - Flexibility: callers can pass any mapping-like object (e.g., YAML loaders that return custom
504
504
  mappings) without copying into a `dict` first.
@@ -31,6 +31,28 @@ assert validate(filtered, {"age": {"type": "number", "min": 0}})["valid"]
31
31
  load(filtered, "file", "output.json", file_format="json")
32
32
  ```
33
33
 
34
+ ## Loading and Validating Configs
35
+
36
+ Use the provided classes to load and validate configuration files:
37
+
38
+ ```python
39
+ from etlplus.workflow import Config
40
+
41
+ cfg = Config.from_yaml("pipeline.yml")
42
+ ```
43
+
44
+ - Supports YAML and JSON formats
45
+ - Validates against expected schema
46
+
47
+ ## Example: Loading a Pipeline Config
48
+
49
+ ```python
50
+ from etlplus.workflow import Config
51
+
52
+ config = Config.from_yaml("configs/pipeline.yml")
53
+ print(config)
54
+ ```
55
+
34
56
  ## See Also
35
57
 
36
58
  - [Top-level project README](../README.md)
@@ -5,6 +5,7 @@ Top-level facade for the ETLPlus toolkit.
5
5
  """
6
6
 
7
7
  from .__version__ import __version__
8
+ from .config import Config
8
9
 
9
10
  __author__ = 'ETLPlus Team'
10
11
 
@@ -15,4 +16,5 @@ __author__ = 'ETLPlus Team'
15
16
  __all__ = [
16
17
  '__author__',
17
18
  '__version__',
19
+ 'Config',
18
20
  ]
@@ -62,6 +62,16 @@ __all__ = ['app']
62
62
  # SECTION: TYPE ALIASES ==================================================== #
63
63
 
64
64
 
65
+ ConfigOption = Annotated[
66
+ str,
67
+ typer.Option(
68
+ ...,
69
+ '--config',
70
+ metavar='PATH',
71
+ help='Path to YAML-formatted configuration file.',
72
+ ),
73
+ ]
74
+
65
75
  JobOption = Annotated[
66
76
  str | None,
67
77
  typer.Option(
@@ -97,16 +107,6 @@ OutputOption = Annotated[
97
107
  ),
98
108
  ]
99
109
 
100
- PipelineConfigOption = Annotated[
101
- str,
102
- typer.Option(
103
- ...,
104
- '--config',
105
- metavar='PATH',
106
- help='Path to pipeline YAML configuration file.',
107
- ),
108
- ]
109
-
110
110
  PipelineOption = Annotated[
111
111
  str | None,
112
112
  typer.Option(
@@ -407,7 +407,7 @@ def _root(
407
407
  @app.command('check')
408
408
  def check_cmd(
409
409
  ctx: typer.Context,
410
- config: PipelineConfigOption,
410
+ config: ConfigOption,
411
411
  jobs: JobsOption = False,
412
412
  pipelines: PipelinesOption = False,
413
413
  sources: SourcesOption = False,
@@ -422,20 +422,20 @@ def check_cmd(
422
422
  ----------
423
423
  ctx : typer.Context
424
424
  The Typer context.
425
- config : PipelineConfigOption
425
+ config : ConfigOption
426
426
  Path to pipeline YAML configuration file.
427
- jobs : bool, optional
427
+ jobs : JobsOption, optional
428
428
  List available job names and exit. Default is ``False``.
429
- pipelines : bool, optional
429
+ pipelines : PipelinesOption, optional
430
430
  List ETL pipelines. Default is ``False``.
431
- sources : bool, optional
431
+ sources : SourcesOption, optional
432
432
  List data sources. Default is ``False``.
433
- summary : bool, optional
433
+ summary : SummaryOption, optional
434
434
  Show pipeline summary (name, version, sources, targets, jobs). Default
435
435
  is ``False``.
436
- targets : bool, optional
436
+ targets : TargetsOption, optional
437
437
  List data targets. Default is ``False``.
438
- transforms : bool, optional
438
+ transforms : TransformsOption, optional
439
439
  List data transforms. Default is ``False``.
440
440
 
441
441
  Returns
@@ -725,7 +725,7 @@ def render_cmd(
725
725
  @app.command('run')
726
726
  def run_cmd(
727
727
  ctx: typer.Context,
728
- config: PipelineConfigOption,
728
+ config: ConfigOption,
729
729
  job: JobOption = None,
730
730
  pipeline: PipelineOption = None,
731
731
  ) -> int:
@@ -736,11 +736,11 @@ def run_cmd(
736
736
  ----------
737
737
  ctx : typer.Context
738
738
  The Typer context.
739
- config : PipelineConfigOption
739
+ config : ConfigOption
740
740
  Path to pipeline YAML configuration file.
741
- job : str | None, optional
741
+ job : JobOption, optional
742
742
  Name of the job to run. Default is ``None``.
743
- pipeline : str | None, optional
743
+ pipeline : PipelineOption, optional
744
744
  Name of the pipeline to run. Default is ``None``.
745
745
 
746
746
  Returns
@@ -14,6 +14,7 @@ from typing import Any
14
14
  from typing import Literal
15
15
  from typing import cast
16
16
 
17
+ from .. import Config
17
18
  from ..database import load_table_spec
18
19
  from ..database import render_tables
19
20
  from ..file import File
@@ -26,8 +27,6 @@ from ..ops import validate
26
27
  from ..ops.validate import FieldRules
27
28
  from ..types import JSONData
28
29
  from ..types import TemplateKey
29
- from ..workflow import PipelineConfig
30
- from ..workflow import load_pipeline_config
31
30
  from . import io as cli_io
32
31
 
33
32
  # SECTION: EXPORTS ========================================================== #
@@ -73,14 +72,14 @@ def _collect_table_specs(
73
72
  specs.append(dict(load_table_spec(Path(spec_path))))
74
73
 
75
74
  if config_path:
76
- cfg = load_pipeline_config(config_path, substitute=True)
75
+ cfg = Config.from_yaml(config_path, substitute=True)
77
76
  specs.extend(getattr(cfg, 'table_schemas', []))
78
77
 
79
78
  return specs
80
79
 
81
80
 
82
81
  def _check_sections(
83
- cfg: PipelineConfig,
82
+ cfg: Config,
84
83
  *,
85
84
  jobs: bool,
86
85
  pipelines: bool,
@@ -93,7 +92,7 @@ def _check_sections(
93
92
 
94
93
  Parameters
95
94
  ----------
96
- cfg : PipelineConfig
95
+ cfg : Config
97
96
  The loaded pipeline configuration.
98
97
  jobs : bool
99
98
  Whether to include job metadata.
@@ -133,14 +132,14 @@ def _check_sections(
133
132
 
134
133
 
135
134
  def _pipeline_summary(
136
- cfg: PipelineConfig,
135
+ cfg: Config,
137
136
  ) -> dict[str, Any]:
138
137
  """
139
138
  Return a human-friendly snapshot of a pipeline config.
140
139
 
141
140
  Parameters
142
141
  ----------
143
- cfg : PipelineConfig
142
+ cfg : Config
144
143
  The loaded pipeline configuration.
145
144
 
146
145
  Returns
@@ -229,7 +228,7 @@ def check_handler(
229
228
  Zero on success.
230
229
 
231
230
  """
232
- cfg = load_pipeline_config(config, substitute=substitute)
231
+ cfg = Config.from_yaml(config, substitute=substitute)
233
232
  if summary:
234
233
  cli_io.emit_json(_pipeline_summary(cfg), pretty=True)
235
234
  return 0
@@ -514,7 +513,7 @@ def run_handler(
514
513
  int
515
514
  Zero on success.
516
515
  """
517
- cfg = load_pipeline_config(config, substitute=True)
516
+ cfg = Config.from_yaml(config, substitute=True)
518
517
 
519
518
  job_name = job or pipeline
520
519
  if job_name:
@@ -1,7 +1,7 @@
1
1
  """
2
- :mod:`etlplus.workflow.pipeline` module.
2
+ :mod:`etlplus.config` module.
3
3
 
4
- Pipeline configuration model and helpers for job orchestration.
4
+ Configuration model and helpers for job pipeline orchestration.
5
5
 
6
6
  Notes
7
7
  -----
@@ -24,26 +24,24 @@ from pathlib import Path
24
24
  from typing import Any
25
25
  from typing import Self
26
26
 
27
- from ..api import ApiConfig
28
- from ..connector import Connector
29
- from ..connector import parse_connector
30
- from ..file import File
31
- from ..file import FileFormat
32
- from ..types import StrAnyMap
33
- from ..utils import coerce_dict
34
- from ..utils import deep_substitute
35
- from ..utils import maybe_mapping
36
- from .jobs import JobConfig
37
- from .profile import ProfileConfig
27
+ from .api import ApiConfig
28
+ from .connector import Connector
29
+ from .connector import parse_connector
30
+ from .file import File
31
+ from .file import FileFormat
32
+ from .types import StrAnyMap
33
+ from .utils import coerce_dict
34
+ from .utils import deep_substitute
35
+ from .utils import maybe_mapping
36
+ from .workflow.jobs import JobConfig
37
+ from .workflow.profile import ProfileConfig
38
38
 
39
39
  # SECTION: EXPORTS ========================================================== #
40
40
 
41
41
 
42
42
  __all__ = [
43
43
  # Data Classes
44
- 'PipelineConfig',
45
- # Functions
46
- 'load_pipeline_config',
44
+ 'Config',
47
45
  ]
48
46
 
49
47
 
@@ -126,29 +124,11 @@ def _parse_connector_entry(
126
124
  return None
127
125
 
128
126
 
129
- # SECTION: FUNCTIONS ======================================================== #
130
-
131
-
132
- def load_pipeline_config(
133
- path: Path | str,
134
- *,
135
- substitute: bool = False,
136
- env: Mapping[str, str] | None = None,
137
- ) -> PipelineConfig:
138
- """
139
- Load a pipeline YAML file into a ``PipelineConfig`` instance.
140
-
141
- Delegates to ``PipelineConfig.from_yaml`` for construction and optional
142
- variable substitution.
143
- """
144
- return PipelineConfig.from_yaml(path, substitute=substitute, env=env)
145
-
146
-
147
127
  # SECTION: DATA CLASSES ===================================================== #
148
128
 
149
129
 
150
130
  @dataclass(kw_only=True, slots=True)
151
- class PipelineConfig:
131
+ class Config:
152
132
  """
153
133
  Configuration for the data processing pipeline.
154
134
 
@@ -211,7 +191,7 @@ class PipelineConfig:
211
191
  env: Mapping[str, str] | None = None,
212
192
  ) -> Self:
213
193
  """
214
- Parse a YAML file into a ``PipelineConfig`` instance.
194
+ Parse a YAML file into a ``Config`` instance.
215
195
 
216
196
  Parameters
217
197
  ----------
@@ -259,7 +239,7 @@ class PipelineConfig:
259
239
  raw: StrAnyMap,
260
240
  ) -> Self:
261
241
  """
262
- Parse a mapping into a ``PipelineConfig`` instance.
242
+ Parse a mapping into a ``Config`` instance.
263
243
 
264
244
  Parameters
265
245
  ----------
@@ -0,0 +1,144 @@
1
+ """
2
+ :mod:`etlplus.enums` module.
3
+
4
+ Shared enumeration base class.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import enum
10
+ from typing import Self
11
+
12
+ from .types import StrStrMap
13
+
14
+ # SECTION: EXPORTS ========================================================== #
15
+
16
+
17
+ __all__ = [
18
+ # Enums
19
+ 'CoercibleStrEnum',
20
+ ]
21
+
22
+
23
+ # SECTION: CLASSES ========================================================== #
24
+
25
+
26
+ class CoercibleStrEnum(enum.StrEnum):
27
+ """
28
+ StrEnum with ergonomic helpers.
29
+
30
+ Provides a DRY, class-level :meth:`coerce` that normalizes inputs and
31
+ produces consistent, informative error messages. Also exposes
32
+ :meth:`choices` for UI/validation and :meth:`try_coerce` for soft parsing.
33
+
34
+ Notes
35
+ -----
36
+ - Values are normalized via ``str(value).strip().casefold()``.
37
+ - If value matching fails, the raw string is tried as a member name.
38
+ - Error messages enumerate allowed values for easier debugging.
39
+ """
40
+
41
+ # -- Class Methods -- #
42
+
43
+ @classmethod
44
+ def aliases(cls) -> StrStrMap:
45
+ """
46
+ Return a mapping of common aliases for each enum member.
47
+
48
+ Subclasses may override this method to provide custom aliases.
49
+
50
+ Returns
51
+ -------
52
+ StrStrMap
53
+ A mapping of alias strings to their corresponding enum member
54
+ values or names.
55
+
56
+ Notes
57
+ -----
58
+ - Alias keys are normalized via ``str(key).strip().casefold()``.
59
+ - Alias values should be member values or member names.
60
+ """
61
+ return {}
62
+
63
+ @classmethod
64
+ def choices(cls) -> tuple[str, ...]:
65
+ """
66
+ Return the allowed string values for this enum.
67
+
68
+ Returns
69
+ -------
70
+ tuple[str, ...]
71
+ A tuple of allowed string values for this enum.
72
+ """
73
+ return tuple(member.value for member in cls)
74
+
75
+ @classmethod
76
+ def coerce(cls, value: Self | str | object) -> Self:
77
+ """
78
+ Convert an enum member or string-like input to a member of *cls*.
79
+
80
+ Parameters
81
+ ----------
82
+ value : Self | str | object
83
+ An existing enum member or a string-like value to normalize.
84
+
85
+ Returns
86
+ -------
87
+ Self
88
+ The corresponding enum member.
89
+
90
+ Raises
91
+ ------
92
+ ValueError
93
+ If the value cannot be coerced into a valid member.
94
+ """
95
+ if isinstance(value, cls):
96
+ return value
97
+ try:
98
+ raw = str(value).strip()
99
+ normalized = raw.casefold()
100
+ aliases = {
101
+ str(key).strip().casefold(): alias
102
+ for key, alias in cls.aliases().items()
103
+ }
104
+ resolved = aliases.get(normalized)
105
+ if resolved is None:
106
+ try:
107
+ return cls(normalized) # type: ignore[arg-type]
108
+ except (ValueError, TypeError):
109
+ return cls[raw] # type: ignore[index]
110
+ if isinstance(resolved, cls):
111
+ return resolved
112
+ try:
113
+ return cls(resolved) # type: ignore[arg-type]
114
+ except (ValueError, TypeError):
115
+ # Allow aliases to reference member names.
116
+ return cls[resolved] # type: ignore[index]
117
+ except (ValueError, TypeError, KeyError) as e:
118
+ allowed = ', '.join(cls.choices())
119
+ raise ValueError(
120
+ f'Invalid {cls.__name__} value: {value!r}. Allowed: {allowed}',
121
+ ) from e
122
+
123
+ @classmethod
124
+ def try_coerce(
125
+ cls,
126
+ value: Self | str | object,
127
+ ) -> Self | None:
128
+ """
129
+ Attempt to coerce a value into the enum; return ``None`` on failure.
130
+
131
+ Parameters
132
+ ----------
133
+ value : Self | str | object
134
+ An existing enum member or a string-like value to normalize.
135
+
136
+ Returns
137
+ -------
138
+ Self | None
139
+ The corresponding enum member, or ``None`` if coercion fails.
140
+ """
141
+ try:
142
+ return cls.coerce(value)
143
+ except (ValueError, TypeError, KeyError):
144
+ return None
@@ -52,6 +52,7 @@ from .validate import validate
52
52
 
53
53
 
54
54
  __all__ = [
55
+ # Functions
55
56
  'extract',
56
57
  'load',
57
58
  'run',
@@ -1,133 +1,30 @@
1
1
  """
2
- :mod:`etlplus.enums` module.
2
+ :mod:`etlplus.ops.enums` module.
3
3
 
4
- Shared enumeration types used across ETLPlus modules.
4
+ Operation-specific enums and helpers.
5
5
  """
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
- import enum
10
9
  import operator as _op
11
10
  from statistics import fmean
12
- from typing import Self
13
11
 
12
+ from ..enums import CoercibleStrEnum
13
+ from ..types import StrStrMap
14
14
  from .types import AggregateFunc
15
15
  from .types import OperatorFunc
16
- from .types import StrStrMap
17
16
 
18
- # SECTION: EXPORTS ========================================================== #
17
+ # SECTION: EXPORTS ========================================================= #
19
18
 
20
19
 
21
20
  __all__ = [
22
21
  # Enums
23
22
  'AggregateName',
24
- 'CoercibleStrEnum',
25
23
  'OperatorName',
26
24
  'PipelineStep',
27
25
  ]
28
26
 
29
27
 
30
- # SECTION: CLASSES ========================================================== #
31
-
32
-
33
- class CoercibleStrEnum(enum.StrEnum):
34
- """
35
- StrEnum with ergonomic helpers.
36
-
37
- Provides a DRY, class-level :meth:`coerce` that normalizes inputs and
38
- produces consistent, informative error messages. Also exposes
39
- :meth:`choices` for UI/validation and :meth:`try_coerce` for soft parsing.
40
-
41
- Notes
42
- -----
43
- - Values are normalized via ``str(value).strip().casefold()``.
44
- - Error messages enumerate allowed values for easier debugging.
45
- """
46
-
47
- # -- Class Methods -- #
48
-
49
- @classmethod
50
- def aliases(cls) -> StrStrMap:
51
- """
52
- Return a mapping of common aliases for each enum member.
53
-
54
- Subclasses may override this method to provide custom aliases.
55
-
56
- Returns
57
- -------
58
- StrStrMap
59
- A mapping of alias names to their corresponding enum member names.
60
- """
61
- return {}
62
-
63
- @classmethod
64
- def choices(cls) -> tuple[str, ...]:
65
- """
66
- Return the allowed string values for this enum.
67
-
68
- Returns
69
- -------
70
- tuple[str, ...]
71
- A tuple of allowed string values for this enum.
72
- """
73
- return tuple(member.value for member in cls)
74
-
75
- @classmethod
76
- def coerce(cls, value: Self | str | object) -> Self:
77
- """
78
- Convert an enum member or string-like input to a member of *cls*.
79
-
80
- Parameters
81
- ----------
82
- value : Self | str | object
83
- An existing enum member or a text value to normalize.
84
-
85
- Returns
86
- -------
87
- Self
88
- The corresponding enum member.
89
-
90
- Raises
91
- ------
92
- ValueError
93
- If the value cannot be coerced into a valid member.
94
- """
95
- if isinstance(value, cls):
96
- return value
97
- try:
98
- normalized = str(value).strip().casefold()
99
- resolved = cls.aliases().get(normalized, normalized)
100
- return cls(resolved) # type: ignore[arg-type]
101
- except (ValueError, TypeError) as e:
102
- allowed = ', '.join(cls.choices())
103
- raise ValueError(
104
- f'Invalid {cls.__name__} value: {value!r}. Allowed: {allowed}',
105
- ) from e
106
-
107
- @classmethod
108
- def try_coerce(
109
- cls,
110
- value: object,
111
- ) -> Self | None:
112
- """
113
- Best-effort parse; return ``None`` on failure instead of raising.
114
-
115
- Parameters
116
- ----------
117
- value : object
118
- An existing enum member or a text value to normalize.
119
-
120
- Returns
121
- -------
122
- Self | None
123
- The corresponding enum member, or ``None`` if coercion fails.
124
- """
125
- try:
126
- return cls.coerce(value)
127
- except ValueError:
128
- return None
129
-
130
-
131
28
  # SECTION: ENUMS ============================================================ #
132
29
 
133
30
 
@@ -11,14 +11,14 @@ from typing import Final
11
11
  from typing import cast
12
12
 
13
13
  from ..api import HttpMethod
14
+ from ..config import Config
14
15
  from ..connector import DataConnectorType
15
16
  from ..file import FileFormat
17
+ from ..ops.types import PipelineConfig
16
18
  from ..types import JSONData
17
19
  from ..types import JSONDict
18
- from ..types import PipelineConfig
19
20
  from ..types import StrPath
20
21
  from ..utils import print_json
21
- from ..workflow import load_pipeline_config
22
22
  from .extract import extract
23
23
  from .extract import extract_from_api_source
24
24
  from .load import load
@@ -176,7 +176,7 @@ def run(
176
176
  If the job is not found or if there are configuration issues.
177
177
  """
178
178
  cfg_path = config_path or DEFAULT_CONFIG_PATH
179
- cfg = load_pipeline_config(cfg_path, substitute=True)
179
+ cfg = Config.from_yaml(cfg_path, substitute=True)
180
180
 
181
181
  # Lookup job by name
182
182
  if not (job_obj := next((j for j in cfg.jobs if j.name == job), None)):