etlplus 0.16.0__tar.gz → 0.16.3__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.0/etlplus.egg-info → etlplus-0.16.3}/PKG-INFO +1 -1
  2. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/api/types.py +32 -11
  3. etlplus-0.16.3/etlplus/enums.py +144 -0
  4. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/ops/__init__.py +1 -0
  5. {etlplus-0.16.0/etlplus → etlplus-0.16.3/etlplus/ops}/enums.py +5 -108
  6. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/ops/extract.py +209 -22
  7. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/ops/load.py +140 -34
  8. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/ops/run.py +86 -101
  9. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/ops/transform.py +46 -27
  10. etlplus-0.16.3/etlplus/ops/types.py +147 -0
  11. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/types.py +3 -101
  12. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/workflow/__init__.py +2 -0
  13. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/workflow/dag.py +23 -1
  14. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/workflow/jobs.py +15 -26
  15. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/workflow/pipeline.py +37 -54
  16. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/workflow/profile.py +4 -2
  17. {etlplus-0.16.0 → etlplus-0.16.3/etlplus.egg-info}/PKG-INFO +1 -1
  18. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus.egg-info/SOURCES.txt +5 -3
  19. {etlplus-0.16.0 → etlplus-0.16.3}/examples/README.md +1 -1
  20. etlplus-0.16.0/examples/quickstart_python.py → etlplus-0.16.3/examples/quickstart.py +22 -1
  21. {etlplus-0.16.0 → etlplus-0.16.3}/tests/integration/conftest.py +20 -23
  22. {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/connector/test_u_connector_enums.py +1 -1
  23. etlplus-0.16.0/tests/unit/connector/test_u_connector_connector.py → etlplus-0.16.3/tests/unit/connector/test_u_connector_utils.py +2 -2
  24. etlplus-0.16.0/tests/unit/test_u_enums.py → etlplus-0.16.3/tests/unit/ops/test_u_ops_enums.py +8 -8
  25. {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/ops/test_u_ops_run.py +16 -13
  26. {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/ops/test_u_ops_transform.py +3 -3
  27. {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/workflow/test_u_workflow_pipeline.py +1 -2
  28. {etlplus-0.16.0 → etlplus-0.16.3}/.coveragerc +0 -0
  29. {etlplus-0.16.0 → etlplus-0.16.3}/.editorconfig +0 -0
  30. {etlplus-0.16.0 → etlplus-0.16.3}/.gitattributes +0 -0
  31. {etlplus-0.16.0 → etlplus-0.16.3}/.github/actions/python-bootstrap/action.yml +0 -0
  32. {etlplus-0.16.0 → etlplus-0.16.3}/.github/workflows/ci.yml +0 -0
  33. {etlplus-0.16.0 → etlplus-0.16.3}/.gitignore +0 -0
  34. {etlplus-0.16.0 → etlplus-0.16.3}/.pre-commit-config.yaml +0 -0
  35. {etlplus-0.16.0 → etlplus-0.16.3}/.ruff.toml +0 -0
  36. {etlplus-0.16.0 → etlplus-0.16.3}/CODE_OF_CONDUCT.md +0 -0
  37. {etlplus-0.16.0 → etlplus-0.16.3}/CONTRIBUTING.md +0 -0
  38. {etlplus-0.16.0 → etlplus-0.16.3}/DEMO.md +0 -0
  39. {etlplus-0.16.0 → etlplus-0.16.3}/LICENSE +0 -0
  40. {etlplus-0.16.0 → etlplus-0.16.3}/MANIFEST.in +0 -0
  41. {etlplus-0.16.0 → etlplus-0.16.3}/Makefile +0 -0
  42. {etlplus-0.16.0 → etlplus-0.16.3}/README.md +0 -0
  43. {etlplus-0.16.0 → etlplus-0.16.3}/REFERENCES.md +0 -0
  44. {etlplus-0.16.0 → etlplus-0.16.3}/SECURITY.md +0 -0
  45. {etlplus-0.16.0 → etlplus-0.16.3}/SUPPORT.md +0 -0
  46. {etlplus-0.16.0 → etlplus-0.16.3}/docs/README.md +0 -0
  47. {etlplus-0.16.0 → etlplus-0.16.3}/docs/pipeline-guide.md +0 -0
  48. {etlplus-0.16.0 → etlplus-0.16.3}/docs/snippets/installation_version.md +0 -0
  49. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/README.md +0 -0
  50. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/__init__.py +0 -0
  51. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/__main__.py +0 -0
  52. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/__version__.py +0 -0
  53. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/api/README.md +0 -0
  54. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/api/__init__.py +0 -0
  55. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/api/auth.py +0 -0
  56. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/api/config.py +0 -0
  57. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/api/endpoint_client.py +0 -0
  58. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/api/enums.py +0 -0
  59. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/api/errors.py +0 -0
  60. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/api/pagination/__init__.py +0 -0
  61. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/api/pagination/client.py +0 -0
  62. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/api/pagination/config.py +0 -0
  63. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/api/pagination/paginator.py +0 -0
  64. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/api/rate_limiting/__init__.py +0 -0
  65. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/api/rate_limiting/config.py +0 -0
  66. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/api/rate_limiting/rate_limiter.py +0 -0
  67. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/api/request_manager.py +0 -0
  68. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/api/retry_manager.py +0 -0
  69. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/api/transport.py +0 -0
  70. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/api/utils.py +0 -0
  71. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/cli/README.md +0 -0
  72. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/cli/__init__.py +0 -0
  73. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/cli/commands.py +0 -0
  74. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/cli/constants.py +0 -0
  75. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/cli/handlers.py +0 -0
  76. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/cli/io.py +0 -0
  77. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/cli/main.py +0 -0
  78. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/cli/options.py +0 -0
  79. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/cli/state.py +0 -0
  80. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/cli/types.py +0 -0
  81. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/connector/__init__.py +0 -0
  82. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/connector/api.py +0 -0
  83. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/connector/connector.py +0 -0
  84. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/connector/core.py +0 -0
  85. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/connector/database.py +0 -0
  86. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/connector/enums.py +0 -0
  87. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/connector/file.py +0 -0
  88. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/connector/types.py +0 -0
  89. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/connector/utils.py +0 -0
  90. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/database/README.md +0 -0
  91. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/database/__init__.py +0 -0
  92. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/database/ddl.py +0 -0
  93. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/database/engine.py +0 -0
  94. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/database/orm.py +0 -0
  95. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/database/schema.py +0 -0
  96. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/database/types.py +0 -0
  97. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/README.md +0 -0
  98. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/__init__.py +0 -0
  99. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/_imports.py +0 -0
  100. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/_io.py +0 -0
  101. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/accdb.py +0 -0
  102. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/arrow.py +0 -0
  103. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/avro.py +0 -0
  104. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/bson.py +0 -0
  105. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/cbor.py +0 -0
  106. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/cfg.py +0 -0
  107. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/conf.py +0 -0
  108. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/core.py +0 -0
  109. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/csv.py +0 -0
  110. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/dat.py +0 -0
  111. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/dta.py +0 -0
  112. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/duckdb.py +0 -0
  113. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/enums.py +0 -0
  114. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/feather.py +0 -0
  115. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/fwf.py +0 -0
  116. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/gz.py +0 -0
  117. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/hbs.py +0 -0
  118. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/hdf5.py +0 -0
  119. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/ini.py +0 -0
  120. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/ion.py +0 -0
  121. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/jinja2.py +0 -0
  122. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/json.py +0 -0
  123. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/log.py +0 -0
  124. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/mat.py +0 -0
  125. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/mdb.py +0 -0
  126. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/msgpack.py +0 -0
  127. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/mustache.py +0 -0
  128. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/nc.py +0 -0
  129. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/ndjson.py +0 -0
  130. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/numbers.py +0 -0
  131. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/ods.py +0 -0
  132. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/orc.py +0 -0
  133. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/parquet.py +0 -0
  134. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/pb.py +0 -0
  135. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/pbf.py +0 -0
  136. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/properties.py +0 -0
  137. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/proto.py +0 -0
  138. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/psv.py +0 -0
  139. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/rda.py +0 -0
  140. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/rds.py +0 -0
  141. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/sas7bdat.py +0 -0
  142. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/sav.py +0 -0
  143. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/sqlite.py +0 -0
  144. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/stub.py +0 -0
  145. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/sylk.py +0 -0
  146. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/tab.py +0 -0
  147. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/toml.py +0 -0
  148. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/tsv.py +0 -0
  149. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/txt.py +0 -0
  150. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/vm.py +0 -0
  151. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/wks.py +0 -0
  152. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/xls.py +0 -0
  153. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/xlsm.py +0 -0
  154. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/xlsx.py +0 -0
  155. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/xml.py +0 -0
  156. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/xpt.py +0 -0
  157. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/yaml.py +0 -0
  158. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/zip.py +0 -0
  159. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/file/zsav.py +0 -0
  160. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/mixins.py +0 -0
  161. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/ops/README.md +0 -0
  162. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/ops/utils.py +0 -0
  163. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/ops/validate.py +0 -0
  164. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/py.typed +0 -0
  165. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/templates/README.md +0 -0
  166. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/templates/__init__.py +0 -0
  167. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/templates/ddl.sql.j2 +0 -0
  168. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/templates/view.sql.j2 +0 -0
  169. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/utils.py +0 -0
  170. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus/workflow/README.md +0 -0
  171. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus.egg-info/dependency_links.txt +0 -0
  172. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus.egg-info/entry_points.txt +0 -0
  173. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus.egg-info/requires.txt +0 -0
  174. {etlplus-0.16.0 → etlplus-0.16.3}/etlplus.egg-info/top_level.txt +0 -0
  175. {etlplus-0.16.0 → etlplus-0.16.3}/examples/configs/ddl_spec.yml +0 -0
  176. {etlplus-0.16.0 → etlplus-0.16.3}/examples/configs/pipeline.yml +0 -0
  177. {etlplus-0.16.0 → etlplus-0.16.3}/examples/data/sample.csv +0 -0
  178. {etlplus-0.16.0 → etlplus-0.16.3}/examples/data/sample.json +0 -0
  179. {etlplus-0.16.0 → etlplus-0.16.3}/examples/data/sample.xml +0 -0
  180. {etlplus-0.16.0 → etlplus-0.16.3}/examples/data/sample.xsd +0 -0
  181. {etlplus-0.16.0 → etlplus-0.16.3}/examples/data/sample.yaml +0 -0
  182. {etlplus-0.16.0 → etlplus-0.16.3}/pyproject.toml +0 -0
  183. {etlplus-0.16.0 → etlplus-0.16.3}/pytest.ini +0 -0
  184. {etlplus-0.16.0 → etlplus-0.16.3}/setup.cfg +0 -0
  185. {etlplus-0.16.0 → etlplus-0.16.3}/setup.py +0 -0
  186. {etlplus-0.16.0 → etlplus-0.16.3}/tests/__init__.py +0 -0
  187. {etlplus-0.16.0 → etlplus-0.16.3}/tests/conftest.py +0 -0
  188. {etlplus-0.16.0 → etlplus-0.16.3}/tests/integration/test_i_cli.py +0 -0
  189. {etlplus-0.16.0 → etlplus-0.16.3}/tests/integration/test_i_examples_data_parity.py +0 -0
  190. {etlplus-0.16.0 → etlplus-0.16.3}/tests/integration/test_i_pagination_strategy.py +0 -0
  191. {etlplus-0.16.0 → etlplus-0.16.3}/tests/integration/test_i_pipeline_smoke.py +0 -0
  192. {etlplus-0.16.0 → etlplus-0.16.3}/tests/integration/test_i_pipeline_yaml_load.py +0 -0
  193. {etlplus-0.16.0 → etlplus-0.16.3}/tests/integration/test_i_run.py +0 -0
  194. {etlplus-0.16.0 → etlplus-0.16.3}/tests/integration/test_i_run_profile_pagination_defaults.py +0 -0
  195. {etlplus-0.16.0 → etlplus-0.16.3}/tests/integration/test_i_run_profile_rate_limit_defaults.py +0 -0
  196. {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/api/conftest.py +0 -0
  197. {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/api/test_u_api_enums.py +0 -0
  198. {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/api/test_u_api_utils.py +0 -0
  199. {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/api/test_u_auth.py +0 -0
  200. {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/api/test_u_config.py +0 -0
  201. {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/api/test_u_endpoint_client.py +0 -0
  202. {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/api/test_u_mocks.py +0 -0
  203. {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/api/test_u_pagination_client.py +0 -0
  204. {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/api/test_u_pagination_config.py +0 -0
  205. {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/api/test_u_paginator.py +0 -0
  206. {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/api/test_u_rate_limit_config.py +0 -0
  207. {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/api/test_u_rate_limiter.py +0 -0
  208. {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/api/test_u_request_manager.py +0 -0
  209. {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/api/test_u_retry_manager.py +0 -0
  210. {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/api/test_u_transport.py +0 -0
  211. {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/api/test_u_types.py +0 -0
  212. {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/cli/conftest.py +0 -0
  213. {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/cli/test_u_cli_handlers.py +0 -0
  214. {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/cli/test_u_cli_io.py +0 -0
  215. {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/cli/test_u_cli_main.py +0 -0
  216. {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/cli/test_u_cli_state.py +0 -0
  217. {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/conftest.py +0 -0
  218. {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/database/test_u_database_ddl.py +0 -0
  219. {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/database/test_u_database_engine.py +0 -0
  220. {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/database/test_u_database_orm.py +0 -0
  221. {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/database/test_u_database_schema.py +0 -0
  222. {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/file/test_u_file_core.py +0 -0
  223. {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/file/test_u_file_enums.py +0 -0
  224. {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/file/test_u_file_yaml.py +0 -0
  225. {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/ops/test_u_ops_extract.py +0 -0
  226. {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/ops/test_u_ops_load.py +0 -0
  227. {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/ops/test_u_ops_utils.py +0 -0
  228. {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/ops/test_u_ops_validate.py +0 -0
  229. {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/test_u_main.py +0 -0
  230. {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/test_u_mixins.py +0 -0
  231. {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/test_u_utils.py +0 -0
  232. {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/test_u_version.py +0 -0
  233. {etlplus-0.16.0 → etlplus-0.16.3}/tests/unit/workflow/test_u_workflow_jobs.py +0 -0
  234. {etlplus-0.16.0 → etlplus-0.16.3}/tools/update_demo_snippets.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: etlplus
3
- Version: 0.16.0
3
+ Version: 0.16.3
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
@@ -53,7 +53,31 @@ __all__ = [
53
53
  # SECTION: CONSTANTS ======================================================== #
54
54
 
55
55
 
56
- _UNSET = object()
56
+ _UNSET: object = object()
57
+
58
+
59
+ # SECTION: INTERNAL FUNCTIONS =============================================== #
60
+
61
+
62
+ def _to_dict(
63
+ value: Mapping[str, Any] | object | None,
64
+ ) -> dict[str, Any] | None:
65
+ """
66
+ Return a defensive ``dict`` copy for mapping inputs.
67
+
68
+ Parameters
69
+ ----------
70
+ value : Mapping[str, Any] | object | None
71
+ Mapping to copy, or ``None``.
72
+
73
+ Returns
74
+ -------
75
+ dict[str, Any] | None
76
+ New ``dict`` instance or ``None`` when the input is ``None``.
77
+ """
78
+ if value is None:
79
+ return None
80
+ return cast(dict[str, Any], value)
57
81
 
58
82
 
59
83
  # SECTION: TYPED DICTS ====================================================== #
@@ -176,9 +200,9 @@ class RequestOptions:
176
200
 
177
201
  def __post_init__(self) -> None:
178
202
  if self.params is not None:
179
- object.__setattr__(self, 'params', dict(self.params))
203
+ object.__setattr__(self, 'params', _to_dict(self.params))
180
204
  if self.headers is not None:
181
- object.__setattr__(self, 'headers', dict(self.headers))
205
+ object.__setattr__(self, 'headers', _to_dict(self.headers))
182
206
 
183
207
  # -- Instance Methods -- #
184
208
 
@@ -224,23 +248,20 @@ class RequestOptions:
224
248
 
225
249
  Returns
226
250
  -------
227
- RequestOptions
251
+ Self
228
252
  New snapshot reflecting the provided overrides.
229
253
  """
230
254
  if params is _UNSET:
231
255
  next_params = self.params
232
- elif params is None:
233
- next_params = None
234
256
  else:
235
- next_params = cast(dict, params)
257
+ # next_params = _to_dict(params) if params is not None else None
258
+ next_params = _to_dict(params)
236
259
 
237
260
  if headers is _UNSET:
238
261
  next_headers = self.headers
239
- elif headers is None:
240
- next_headers = None
241
262
  else:
242
- next_headers = cast(dict, headers)
243
-
263
+ # next_headers = _to_dict(headers) if headers is not None else None
264
+ next_headers = _to_dict(headers)
244
265
  if timeout is _UNSET:
245
266
  next_timeout = self.timeout
246
267
  else:
@@ -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
 
@@ -6,11 +6,19 @@ Helpers to extract data from files, databases, and REST APIs.
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
+ from collections.abc import Mapping
9
10
  from pathlib import Path
10
11
  from typing import Any
11
12
  from typing import cast
13
+ from urllib.parse import urlsplit
14
+ from urllib.parse import urlunsplit
12
15
 
16
+ from ..api import EndpointClient
13
17
  from ..api import HttpMethod
18
+ from ..api import PaginationConfigMap
19
+ from ..api import RequestOptions
20
+ from ..api import compose_api_request_env
21
+ from ..api import paginate_with_client
14
22
  from ..api.utils import resolve_request
15
23
  from ..connector import DataConnectorType
16
24
  from ..file import File
@@ -19,6 +27,7 @@ from ..types import JSONData
19
27
  from ..types import JSONDict
20
28
  from ..types import JSONList
21
29
  from ..types import StrPath
30
+ from ..types import Timeout
22
31
 
23
32
  # SECTION: EXPORTS ========================================================== #
24
33
 
@@ -32,50 +41,164 @@ __all__ = [
32
41
  ]
33
42
 
34
43
 
35
- # SECTION: FUNCTIONS ======================================================== #
44
+ # SECTION: INTERNAL FUNCTIONS =============================================== #
36
45
 
37
46
 
38
- def extract_from_api(
39
- url: str,
40
- method: HttpMethod | str = HttpMethod.GET,
41
- **kwargs: Any,
47
+ def _build_client(
48
+ *,
49
+ base_url: str,
50
+ base_path: str | None,
51
+ endpoints: dict[str, str],
52
+ retry: Any,
53
+ retry_network_errors: bool,
54
+ session: Any,
55
+ ) -> EndpointClient:
56
+ """
57
+ Construct an API client with shared defaults.
58
+
59
+ Parameters
60
+ ----------
61
+ base_url : str
62
+ API base URL.
63
+ base_path : str | None
64
+ Base path to prepend for endpoints.
65
+ endpoints : dict[str, str]
66
+ Endpoint name to path mappings.
67
+ retry : Any
68
+ Retry policy configuration.
69
+ retry_network_errors : bool
70
+ Whether to retry on network errors.
71
+ session : Any
72
+ Optional requests session.
73
+
74
+ Returns
75
+ -------
76
+ EndpointClient
77
+ Configured endpoint client instance.
78
+ """
79
+ ClientClass = EndpointClient # noqa: N806
80
+ return ClientClass(
81
+ base_url=base_url,
82
+ base_path=base_path,
83
+ endpoints=endpoints,
84
+ retry=retry,
85
+ retry_network_errors=retry_network_errors,
86
+ session=session,
87
+ )
88
+
89
+
90
+ def _extract_from_api_env(
91
+ env: Mapping[str, Any],
92
+ *,
93
+ use_client: bool,
42
94
  ) -> JSONData:
43
95
  """
44
- Extract data from a REST API.
96
+ Extract API data from a normalized request environment.
45
97
 
46
98
  Parameters
47
99
  ----------
48
- url : str
49
- API endpoint URL.
50
- method : HttpMethod | str, optional
51
- HTTP method to use. Defaults to ``GET``.
52
- **kwargs : Any
53
- Extra arguments forwarded to the underlying ``requests`` call
54
- (for example, ``timeout``). To use a pre-configured
55
- :class:`requests.Session`, provide it via ``session``.
56
- When omitted, ``timeout`` defaults to 10 seconds.
100
+ env : Mapping[str, Any]
101
+ Normalized environment describing API request parameters.
102
+ use_client : bool
103
+ Whether to use the endpoint client/pagination machinery.
57
104
 
58
105
  Returns
59
106
  -------
60
107
  JSONData
61
- Parsed JSON payload, or a fallback object with raw text.
108
+ Extracted payload.
62
109
 
63
110
  Raises
64
111
  ------
65
- TypeError
66
- If a provided ``session`` does not expose the required HTTP
67
- method (for example, ``get``).
112
+ ValueError
113
+ If required parameters are missing.
68
114
  """
69
- timeout = kwargs.pop('timeout', None)
70
- session = kwargs.pop('session', None)
115
+ if (
116
+ use_client
117
+ and env.get('use_endpoints')
118
+ and env.get('base_url')
119
+ and env.get('endpoints_map')
120
+ and env.get('endpoint_key')
121
+ ):
122
+ client = _build_client(
123
+ base_url=cast(str, env.get('base_url')),
124
+ base_path=cast(str | None, env.get('base_path')),
125
+ endpoints=cast(dict[str, str], env.get('endpoints_map', {})),
126
+ retry=env.get('retry'),
127
+ retry_network_errors=bool(env.get('retry_network_errors', False)),
128
+ session=env.get('session'),
129
+ )
130
+ return paginate_with_client(
131
+ client,
132
+ cast(str, env.get('endpoint_key')),
133
+ env.get('params'),
134
+ env.get('headers'),
135
+ env.get('timeout'),
136
+ env.get('pagination'),
137
+ cast(float | None, env.get('sleep_seconds')),
138
+ )
139
+
140
+ url = env.get('url')
141
+ if not url:
142
+ raise ValueError('API source missing URL')
143
+
144
+ if use_client:
145
+ parts = urlsplit(cast(str, url))
146
+ base = urlunsplit((parts.scheme, parts.netloc, '', '', ''))
147
+ client = _build_client(
148
+ base_url=base,
149
+ base_path=None,
150
+ endpoints={},
151
+ retry=env.get('retry'),
152
+ retry_network_errors=bool(env.get('retry_network_errors', False)),
153
+ session=env.get('session'),
154
+ )
155
+ request_options = RequestOptions(
156
+ params=cast(Mapping[str, Any] | None, env.get('params')),
157
+ headers=cast(Mapping[str, str] | None, env.get('headers')),
158
+ timeout=cast(Timeout | None, env.get('timeout')),
159
+ )
160
+
161
+ return client.paginate_url(
162
+ cast(str, url),
163
+ cast(PaginationConfigMap | None, env.get('pagination')),
164
+ request=request_options,
165
+ sleep_seconds=cast(float, env.get('sleep_seconds', 0.0)),
166
+ )
167
+
168
+ method = env.get('method', HttpMethod.GET)
169
+ timeout = env.get('timeout', None)
170
+ session = env.get('session', None)
171
+ request_kwargs = dict(env.get('request_kwargs') or {})
71
172
  request_callable, timeout, _ = resolve_request(
72
173
  method,
73
174
  session=session,
74
175
  timeout=timeout,
75
176
  )
76
- response = request_callable(url, timeout=timeout, **kwargs)
177
+ response = request_callable(
178
+ cast(str, url),
179
+ timeout=timeout,
180
+ **request_kwargs,
181
+ )
77
182
  response.raise_for_status()
183
+ return _parse_api_response(response)
184
+
78
185
 
186
+ def _parse_api_response(
187
+ response: Any,
188
+ ) -> JSONData:
189
+ """
190
+ Parse API responses into a consistent JSON payload.
191
+
192
+ Parameters
193
+ ----------
194
+ response : Any
195
+ HTTP response object exposing ``headers``, ``json()``, and ``text``.
196
+
197
+ Returns
198
+ -------
199
+ JSONData
200
+ Parsed JSON payload, or a fallback object with raw text.
201
+ """
79
202
  content_type = response.headers.get('content-type', '').lower()
80
203
  if 'application/json' in content_type:
81
204
  try:
@@ -99,6 +222,70 @@ def extract_from_api(
99
222
  return {'content': response.text, 'content_type': content_type}
100
223
 
101
224
 
225
+ # SECTION: FUNCTIONS ======================================================== #
226
+
227
+
228
+ def extract_from_api(
229
+ url: str,
230
+ method: HttpMethod | str = HttpMethod.GET,
231
+ **kwargs: Any,
232
+ ) -> JSONData:
233
+ """
234
+ Extract data from a REST API.
235
+
236
+ Parameters
237
+ ----------
238
+ url : str
239
+ API endpoint URL.
240
+ method : HttpMethod | str, optional
241
+ HTTP method to use. Defaults to ``GET``.
242
+ **kwargs : Any
243
+ Extra arguments forwarded to the underlying ``requests`` call
244
+ (for example, ``timeout``). To use a pre-configured
245
+ :class:`requests.Session`, provide it via ``session``.
246
+ When omitted, ``timeout`` defaults to 10 seconds.
247
+
248
+ Returns
249
+ -------
250
+ JSONData
251
+ Parsed JSON payload, or a fallback object with raw text.
252
+ """
253
+ env = {
254
+ 'url': url,
255
+ 'method': method,
256
+ 'timeout': kwargs.pop('timeout', None),
257
+ 'session': kwargs.pop('session', None),
258
+ 'request_kwargs': kwargs,
259
+ }
260
+ return _extract_from_api_env(env, use_client=False)
261
+
262
+
263
+ def extract_from_api_source(
264
+ cfg: Any,
265
+ source_obj: Any,
266
+ overrides: dict[str, Any],
267
+ ) -> JSONData:
268
+ """
269
+ Extract data from a REST API source connector.
270
+
271
+ Parameters
272
+ ----------
273
+ cfg : Any
274
+ Pipeline configuration.
275
+ source_obj : Any
276
+ Connector configuration.
277
+ overrides : dict[str, Any]
278
+ Extract-time overrides.
279
+
280
+ Returns
281
+ -------
282
+ JSONData
283
+ Extracted payload.
284
+ """
285
+ env = compose_api_request_env(cfg, source_obj, overrides)
286
+ return _extract_from_api_env(env, use_client=True)
287
+
288
+
102
289
  def extract_from_database(
103
290
  connection_string: str,
104
291
  ) -> JSONList: