etlplus 0.14.3__tar.gz → 0.15.0__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 (230) hide show
  1. {etlplus-0.14.3/etlplus.egg-info → etlplus-0.15.0}/PKG-INFO +1 -1
  2. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/README.md +1 -1
  3. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/api/README.md +2 -2
  4. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/api/utils.py +5 -1
  5. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/cli/README.md +2 -2
  6. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/cli/handlers.py +2 -2
  7. etlplus-0.15.0/etlplus/config/README.md +50 -0
  8. etlplus-0.15.0/etlplus/config/__init__.py +33 -0
  9. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/config/types.py +0 -64
  10. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/database/README.md +2 -2
  11. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/README.md +2 -2
  12. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/ops/run.py +14 -9
  13. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/templates/README.md +2 -2
  14. {etlplus-0.14.3/etlplus/config → etlplus-0.15.0/etlplus/workflow}/README.md +6 -6
  15. {etlplus-0.14.3/etlplus/config → etlplus-0.15.0/etlplus/workflow}/__init__.py +10 -23
  16. {etlplus-0.14.3/etlplus/config → etlplus-0.15.0/etlplus/workflow}/connector.py +17 -16
  17. etlplus-0.15.0/etlplus/workflow/dag.py +105 -0
  18. {etlplus-0.14.3/etlplus/config → etlplus-0.15.0/etlplus/workflow}/jobs.py +17 -11
  19. {etlplus-0.14.3/etlplus/config → etlplus-0.15.0/etlplus/workflow}/pipeline.py +11 -3
  20. {etlplus-0.14.3/etlplus/config → etlplus-0.15.0/etlplus/workflow}/profile.py +8 -5
  21. etlplus-0.15.0/etlplus/workflow/types.py +115 -0
  22. {etlplus-0.14.3 → etlplus-0.15.0/etlplus.egg-info}/PKG-INFO +1 -1
  23. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus.egg-info/SOURCES.txt +13 -9
  24. {etlplus-0.14.3 → etlplus-0.15.0}/tests/integration/conftest.py +6 -6
  25. {etlplus-0.14.3 → etlplus-0.15.0}/tests/integration/test_i_pagination_strategy.py +11 -11
  26. {etlplus-0.14.3 → etlplus-0.15.0}/tests/integration/test_i_pipeline_yaml_load.py +1 -1
  27. {etlplus-0.14.3 → etlplus-0.15.0}/tests/integration/test_i_run.py +1 -1
  28. {etlplus-0.14.3 → etlplus-0.15.0}/tests/integration/test_i_run_profile_pagination_defaults.py +1 -1
  29. {etlplus-0.14.3 → etlplus-0.15.0}/tests/integration/test_i_run_profile_rate_limit_defaults.py +1 -1
  30. {etlplus-0.14.3 → etlplus-0.15.0}/tests/unit/cli/conftest.py +2 -2
  31. {etlplus-0.14.3 → etlplus-0.15.0}/tests/unit/cli/test_u_cli_handlers.py +3 -1
  32. {etlplus-0.14.3 → etlplus-0.15.0}/tests/unit/conftest.py +1 -1
  33. etlplus-0.14.3/tests/unit/config/test_u_connector.py → etlplus-0.15.0/tests/unit/workflow/test_u_workflow_connector.py +6 -6
  34. etlplus-0.14.3/tests/unit/config/test_u_jobs.py → etlplus-0.15.0/tests/unit/workflow/test_u_workflow_jobs.py +3 -3
  35. etlplus-0.14.3/tests/unit/config/test_u_pipeline.py → etlplus-0.15.0/tests/unit/workflow/test_u_workflow_pipeline.py +7 -7
  36. etlplus-0.14.3/tests/unit/config/test_u_config_utils.py → etlplus-0.15.0/tests/unit/workflow/test_u_workflow_utils.py +3 -3
  37. {etlplus-0.14.3 → etlplus-0.15.0}/.coveragerc +0 -0
  38. {etlplus-0.14.3 → etlplus-0.15.0}/.editorconfig +0 -0
  39. {etlplus-0.14.3 → etlplus-0.15.0}/.gitattributes +0 -0
  40. {etlplus-0.14.3 → etlplus-0.15.0}/.github/actions/python-bootstrap/action.yml +0 -0
  41. {etlplus-0.14.3 → etlplus-0.15.0}/.github/workflows/ci.yml +0 -0
  42. {etlplus-0.14.3 → etlplus-0.15.0}/.gitignore +0 -0
  43. {etlplus-0.14.3 → etlplus-0.15.0}/.pre-commit-config.yaml +0 -0
  44. {etlplus-0.14.3 → etlplus-0.15.0}/.ruff.toml +0 -0
  45. {etlplus-0.14.3 → etlplus-0.15.0}/CODE_OF_CONDUCT.md +0 -0
  46. {etlplus-0.14.3 → etlplus-0.15.0}/CONTRIBUTING.md +0 -0
  47. {etlplus-0.14.3 → etlplus-0.15.0}/DEMO.md +0 -0
  48. {etlplus-0.14.3 → etlplus-0.15.0}/LICENSE +0 -0
  49. {etlplus-0.14.3 → etlplus-0.15.0}/MANIFEST.in +0 -0
  50. {etlplus-0.14.3 → etlplus-0.15.0}/Makefile +0 -0
  51. {etlplus-0.14.3 → etlplus-0.15.0}/README.md +0 -0
  52. {etlplus-0.14.3 → etlplus-0.15.0}/REFERENCES.md +0 -0
  53. {etlplus-0.14.3 → etlplus-0.15.0}/SECURITY.md +0 -0
  54. {etlplus-0.14.3 → etlplus-0.15.0}/SUPPORT.md +0 -0
  55. {etlplus-0.14.3 → etlplus-0.15.0}/docs/README.md +0 -0
  56. {etlplus-0.14.3 → etlplus-0.15.0}/docs/pipeline-guide.md +0 -0
  57. {etlplus-0.14.3 → etlplus-0.15.0}/docs/snippets/installation_version.md +0 -0
  58. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/__init__.py +0 -0
  59. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/__main__.py +0 -0
  60. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/__version__.py +0 -0
  61. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/api/__init__.py +0 -0
  62. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/api/auth.py +0 -0
  63. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/api/config.py +0 -0
  64. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/api/endpoint_client.py +0 -0
  65. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/api/enums.py +0 -0
  66. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/api/errors.py +0 -0
  67. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/api/pagination/__init__.py +0 -0
  68. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/api/pagination/client.py +0 -0
  69. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/api/pagination/config.py +0 -0
  70. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/api/pagination/paginator.py +0 -0
  71. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/api/rate_limiting/__init__.py +0 -0
  72. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/api/rate_limiting/config.py +0 -0
  73. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/api/rate_limiting/rate_limiter.py +0 -0
  74. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/api/request_manager.py +0 -0
  75. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/api/retry_manager.py +0 -0
  76. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/api/transport.py +0 -0
  77. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/api/types.py +0 -0
  78. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/cli/__init__.py +0 -0
  79. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/cli/commands.py +0 -0
  80. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/cli/constants.py +0 -0
  81. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/cli/io.py +0 -0
  82. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/cli/main.py +0 -0
  83. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/cli/options.py +0 -0
  84. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/cli/state.py +0 -0
  85. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/cli/types.py +0 -0
  86. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/dag.py +0 -0
  87. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/database/__init__.py +0 -0
  88. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/database/ddl.py +0 -0
  89. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/database/engine.py +0 -0
  90. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/database/orm.py +0 -0
  91. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/database/schema.py +0 -0
  92. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/database/types.py +0 -0
  93. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/enums.py +0 -0
  94. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/__init__.py +0 -0
  95. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/_imports.py +0 -0
  96. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/_io.py +0 -0
  97. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/accdb.py +0 -0
  98. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/arrow.py +0 -0
  99. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/avro.py +0 -0
  100. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/bson.py +0 -0
  101. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/cbor.py +0 -0
  102. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/cfg.py +0 -0
  103. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/conf.py +0 -0
  104. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/core.py +0 -0
  105. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/csv.py +0 -0
  106. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/dat.py +0 -0
  107. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/dta.py +0 -0
  108. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/duckdb.py +0 -0
  109. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/enums.py +0 -0
  110. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/feather.py +0 -0
  111. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/fwf.py +0 -0
  112. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/gz.py +0 -0
  113. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/hbs.py +0 -0
  114. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/hdf5.py +0 -0
  115. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/ini.py +0 -0
  116. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/ion.py +0 -0
  117. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/jinja2.py +0 -0
  118. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/json.py +0 -0
  119. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/log.py +0 -0
  120. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/mat.py +0 -0
  121. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/mdb.py +0 -0
  122. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/msgpack.py +0 -0
  123. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/mustache.py +0 -0
  124. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/nc.py +0 -0
  125. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/ndjson.py +0 -0
  126. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/numbers.py +0 -0
  127. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/ods.py +0 -0
  128. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/orc.py +0 -0
  129. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/parquet.py +0 -0
  130. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/pb.py +0 -0
  131. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/pbf.py +0 -0
  132. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/properties.py +0 -0
  133. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/proto.py +0 -0
  134. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/psv.py +0 -0
  135. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/rda.py +0 -0
  136. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/rds.py +0 -0
  137. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/sas7bdat.py +0 -0
  138. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/sav.py +0 -0
  139. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/sqlite.py +0 -0
  140. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/stub.py +0 -0
  141. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/sylk.py +0 -0
  142. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/tab.py +0 -0
  143. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/toml.py +0 -0
  144. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/tsv.py +0 -0
  145. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/txt.py +0 -0
  146. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/vm.py +0 -0
  147. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/wks.py +0 -0
  148. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/xls.py +0 -0
  149. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/xlsm.py +0 -0
  150. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/xlsx.py +0 -0
  151. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/xml.py +0 -0
  152. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/xpt.py +0 -0
  153. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/yaml.py +0 -0
  154. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/zip.py +0 -0
  155. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/file/zsav.py +0 -0
  156. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/mixins.py +0 -0
  157. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/ops/README.md +0 -0
  158. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/ops/__init__.py +0 -0
  159. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/ops/extract.py +0 -0
  160. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/ops/load.py +0 -0
  161. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/ops/transform.py +0 -0
  162. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/ops/utils.py +0 -0
  163. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/ops/validate.py +0 -0
  164. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/py.typed +0 -0
  165. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/templates/__init__.py +0 -0
  166. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/templates/ddl.sql.j2 +0 -0
  167. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/templates/view.sql.j2 +0 -0
  168. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/types.py +0 -0
  169. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus/utils.py +0 -0
  170. {etlplus-0.14.3/etlplus/config → etlplus-0.15.0/etlplus/workflow}/utils.py +0 -0
  171. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus.egg-info/dependency_links.txt +0 -0
  172. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus.egg-info/entry_points.txt +0 -0
  173. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus.egg-info/requires.txt +0 -0
  174. {etlplus-0.14.3 → etlplus-0.15.0}/etlplus.egg-info/top_level.txt +0 -0
  175. {etlplus-0.14.3 → etlplus-0.15.0}/examples/README.md +0 -0
  176. {etlplus-0.14.3 → etlplus-0.15.0}/examples/configs/ddl_spec.yml +0 -0
  177. {etlplus-0.14.3 → etlplus-0.15.0}/examples/configs/pipeline.yml +0 -0
  178. {etlplus-0.14.3 → etlplus-0.15.0}/examples/data/sample.csv +0 -0
  179. {etlplus-0.14.3 → etlplus-0.15.0}/examples/data/sample.json +0 -0
  180. {etlplus-0.14.3 → etlplus-0.15.0}/examples/data/sample.xml +0 -0
  181. {etlplus-0.14.3 → etlplus-0.15.0}/examples/data/sample.xsd +0 -0
  182. {etlplus-0.14.3 → etlplus-0.15.0}/examples/data/sample.yaml +0 -0
  183. {etlplus-0.14.3 → etlplus-0.15.0}/examples/quickstart_python.py +0 -0
  184. {etlplus-0.14.3 → etlplus-0.15.0}/pyproject.toml +0 -0
  185. {etlplus-0.14.3 → etlplus-0.15.0}/pytest.ini +0 -0
  186. {etlplus-0.14.3 → etlplus-0.15.0}/setup.cfg +0 -0
  187. {etlplus-0.14.3 → etlplus-0.15.0}/setup.py +0 -0
  188. {etlplus-0.14.3 → etlplus-0.15.0}/tests/__init__.py +0 -0
  189. {etlplus-0.14.3 → etlplus-0.15.0}/tests/conftest.py +0 -0
  190. {etlplus-0.14.3 → etlplus-0.15.0}/tests/integration/test_i_cli.py +0 -0
  191. {etlplus-0.14.3 → etlplus-0.15.0}/tests/integration/test_i_examples_data_parity.py +0 -0
  192. {etlplus-0.14.3 → etlplus-0.15.0}/tests/integration/test_i_pipeline_smoke.py +0 -0
  193. {etlplus-0.14.3 → etlplus-0.15.0}/tests/unit/api/conftest.py +0 -0
  194. {etlplus-0.14.3 → etlplus-0.15.0}/tests/unit/api/test_u_api_enums.py +0 -0
  195. {etlplus-0.14.3 → etlplus-0.15.0}/tests/unit/api/test_u_api_utils.py +0 -0
  196. {etlplus-0.14.3 → etlplus-0.15.0}/tests/unit/api/test_u_auth.py +0 -0
  197. {etlplus-0.14.3 → etlplus-0.15.0}/tests/unit/api/test_u_config.py +0 -0
  198. {etlplus-0.14.3 → etlplus-0.15.0}/tests/unit/api/test_u_endpoint_client.py +0 -0
  199. {etlplus-0.14.3 → etlplus-0.15.0}/tests/unit/api/test_u_mocks.py +0 -0
  200. {etlplus-0.14.3 → etlplus-0.15.0}/tests/unit/api/test_u_pagination_client.py +0 -0
  201. {etlplus-0.14.3 → etlplus-0.15.0}/tests/unit/api/test_u_pagination_config.py +0 -0
  202. {etlplus-0.14.3 → etlplus-0.15.0}/tests/unit/api/test_u_paginator.py +0 -0
  203. {etlplus-0.14.3 → etlplus-0.15.0}/tests/unit/api/test_u_rate_limit_config.py +0 -0
  204. {etlplus-0.14.3 → etlplus-0.15.0}/tests/unit/api/test_u_rate_limiter.py +0 -0
  205. {etlplus-0.14.3 → etlplus-0.15.0}/tests/unit/api/test_u_request_manager.py +0 -0
  206. {etlplus-0.14.3 → etlplus-0.15.0}/tests/unit/api/test_u_retry_manager.py +0 -0
  207. {etlplus-0.14.3 → etlplus-0.15.0}/tests/unit/api/test_u_transport.py +0 -0
  208. {etlplus-0.14.3 → etlplus-0.15.0}/tests/unit/api/test_u_types.py +0 -0
  209. {etlplus-0.14.3 → etlplus-0.15.0}/tests/unit/cli/test_u_cli_io.py +0 -0
  210. {etlplus-0.14.3 → etlplus-0.15.0}/tests/unit/cli/test_u_cli_main.py +0 -0
  211. {etlplus-0.14.3 → etlplus-0.15.0}/tests/unit/cli/test_u_cli_state.py +0 -0
  212. {etlplus-0.14.3 → etlplus-0.15.0}/tests/unit/database/test_u_database_ddl.py +0 -0
  213. {etlplus-0.14.3 → etlplus-0.15.0}/tests/unit/database/test_u_database_engine.py +0 -0
  214. {etlplus-0.14.3 → etlplus-0.15.0}/tests/unit/database/test_u_database_orm.py +0 -0
  215. {etlplus-0.14.3 → etlplus-0.15.0}/tests/unit/database/test_u_database_schema.py +0 -0
  216. {etlplus-0.14.3 → etlplus-0.15.0}/tests/unit/file/test_u_file_core.py +0 -0
  217. {etlplus-0.14.3 → etlplus-0.15.0}/tests/unit/file/test_u_file_enums.py +0 -0
  218. {etlplus-0.14.3 → etlplus-0.15.0}/tests/unit/file/test_u_file_yaml.py +0 -0
  219. {etlplus-0.14.3 → etlplus-0.15.0}/tests/unit/ops/test_u_ops_extract.py +0 -0
  220. {etlplus-0.14.3 → etlplus-0.15.0}/tests/unit/ops/test_u_ops_load.py +0 -0
  221. {etlplus-0.14.3 → etlplus-0.15.0}/tests/unit/ops/test_u_ops_run.py +0 -0
  222. {etlplus-0.14.3 → etlplus-0.15.0}/tests/unit/ops/test_u_ops_transform.py +0 -0
  223. {etlplus-0.14.3 → etlplus-0.15.0}/tests/unit/ops/test_u_ops_utils.py +0 -0
  224. {etlplus-0.14.3 → etlplus-0.15.0}/tests/unit/ops/test_u_ops_validate.py +0 -0
  225. {etlplus-0.14.3 → etlplus-0.15.0}/tests/unit/test_u_enums.py +0 -0
  226. {etlplus-0.14.3 → etlplus-0.15.0}/tests/unit/test_u_main.py +0 -0
  227. {etlplus-0.14.3 → etlplus-0.15.0}/tests/unit/test_u_mixins.py +0 -0
  228. {etlplus-0.14.3 → etlplus-0.15.0}/tests/unit/test_u_utils.py +0 -0
  229. {etlplus-0.14.3 → etlplus-0.15.0}/tests/unit/test_u_version.py +0 -0
  230. {etlplus-0.14.3 → etlplus-0.15.0}/tools/update_demo_snippets.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: etlplus
3
- Version: 0.14.3
3
+ Version: 0.15.0
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,4 +1,4 @@
1
- # etlplus package
1
+ # `etlplus` Package
2
2
 
3
3
  The `etlplus` package provides a unified Python API and CLI for ETL operations: extraction,
4
4
  validation, transformation, and loading of data from files, APIs, and databases.
@@ -1,4 +1,4 @@
1
- # etlplus.api subpackage
1
+ # `etlplus.api` Subpackage
2
2
 
3
3
  Documentation for the `etlplus.api` subpackage: a lightweight HTTP client and helpers for paginated
4
4
  REST endpoints.
@@ -12,7 +12,7 @@ REST endpoints.
12
12
 
13
13
  Back to project overview: see the top-level [README](../../README.md).
14
14
 
15
- - [etlplus.api subpackage](#etlplusapi-subpackage)
15
+ - [`etlplus.api` Subpackage](#etlplusapi-subpackage)
16
16
  - [Installation](#installation)
17
17
  - [Quickstart](#quickstart)
18
18
  - [Overriding Rate Limits Per Call](#overriding-rate-limits-per-call)
@@ -892,4 +892,8 @@ def resolve_request(
892
892
  'Session object must supply a callable '
893
893
  f'"{http_method.value}" method',
894
894
  )
895
- return request_callable, request_timeout, http_method
895
+ typed_request_callable = cast(
896
+ Callable[..., requests.Response],
897
+ request_callable,
898
+ )
899
+ return typed_request_callable, request_timeout, http_method
@@ -1,4 +1,4 @@
1
- # etlplus.cli subpackage
1
+ # `etlplus.cli` Subpackage
2
2
 
3
3
  Documentation for the `etlplus.cli` subpackage: command-line interface for ETLPlus workflows.
4
4
 
@@ -9,7 +9,7 @@ Documentation for the `etlplus.cli` subpackage: command-line interface for ETLPl
9
9
 
10
10
  Back to project overview: see the top-level [README](../../README.md).
11
11
 
12
- - [etlplus.cli subpackage](#etlpluscli-subpackage)
12
+ - [`etlplus.cli` Subpackage](#etlpluscli-subpackage)
13
13
  - [Available Commands](#available-commands)
14
14
  - [Command Options](#command-options)
15
15
  - [Example: Running a Pipeline](#example-running-a-pipeline)
@@ -14,8 +14,6 @@ from typing import Any
14
14
  from typing import Literal
15
15
  from typing import cast
16
16
 
17
- from ..config import PipelineConfig
18
- from ..config import load_pipeline_config
19
17
  from ..database import load_table_spec
20
18
  from ..database import render_tables
21
19
  from ..file import File
@@ -28,6 +26,8 @@ from ..ops import validate
28
26
  from ..ops.validate import FieldRules
29
27
  from ..types import JSONData
30
28
  from ..types import TemplateKey
29
+ from ..workflow import PipelineConfig
30
+ from ..workflow import load_pipeline_config
31
31
  from . import io as cli_io
32
32
 
33
33
  # SECTION: EXPORTS ========================================================== #
@@ -0,0 +1,50 @@
1
+ # `etlplus.config` Subpackage
2
+
3
+ Documentation for the `etlplus.config` subpackage: type definitions and config shape helpers for
4
+ ETLPlus.
5
+
6
+ - Exposes TypedDict-based config schemas for API profiles and endpoints
7
+ - Provides exported type aliases for API configuration maps
8
+ - Designed for Python 3.13 typing and editor assistance (runtime parsing lives elsewhere)
9
+
10
+ Back to project overview: see the top-level [README](../../README.md).
11
+
12
+ - [`etlplus.config` Subpackage](#etlplusconfig-subpackage)
13
+ - [Modules](#modules)
14
+ - [Exported Types](#exported-types)
15
+ - [Example: Typing an API Config](#example-typing-an-api-config)
16
+ - [See Also](#see-also)
17
+
18
+ ## Modules
19
+
20
+ - `etlplus.config.__init__`: package exports and high-level package notes
21
+ - `etlplus.config.types`: TypedDict-based config schemas
22
+
23
+ ## Exported Types
24
+
25
+ - `ApiConfigMap`: top-level API config shape
26
+ - `ApiProfileConfigMap`: per-profile API config shape
27
+ - `ApiProfileDefaultsMap`: defaults block within a profile
28
+ - `EndpointMap`: endpoint config shape
29
+
30
+ ## Example: Typing an API Config
31
+
32
+ ```python
33
+ from etlplus.config import ApiConfigMap
34
+
35
+ api_cfg: ApiConfigMap = {
36
+ "base_url": "https://example.test",
37
+ "headers": {"Authorization": "Bearer token"},
38
+ "endpoints": {
39
+ "users": {
40
+ "path": "/users",
41
+ "method": "GET",
42
+ },
43
+ },
44
+ }
45
+ ```
46
+
47
+ ## See Also
48
+
49
+ - Top-level CLI and library usage in the main [README](../../README.md)
50
+ - Config type definitions in [types.py](types.py)
@@ -0,0 +1,33 @@
1
+ """
2
+ :mod:`etlplus.config` package.
3
+
4
+ Configuration models and helpers for ETLPlus.
5
+
6
+ This package defines models for data sources/targets ("connectors"), APIs,
7
+ pagination/rate limits, pipeline orchestration, and related utilities. The
8
+ parsers are permissive (accepting ``Mapping[str, Any]``) and normalize to
9
+ concrete types without raising on unknown/optional fields.
10
+
11
+ Notes
12
+ -----
13
+ - The models use ``@dataclass(slots=True)`` and avoid mutating inputs.
14
+ - TypedDicts are editor/type-checking hints and are not enforced at runtime.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from .types import ApiConfigMap
20
+ from .types import ApiProfileConfigMap
21
+ from .types import ApiProfileDefaultsMap
22
+ from .types import EndpointMap
23
+
24
+ # SECTION: EXPORTS ========================================================== #
25
+
26
+
27
+ __all__ = [
28
+ # Typed Dicts
29
+ 'ApiConfigMap',
30
+ 'ApiProfileConfigMap',
31
+ 'ApiProfileDefaultsMap',
32
+ 'EndpointMap',
33
+ ]
@@ -33,7 +33,6 @@ from __future__ import annotations
33
33
 
34
34
  from collections.abc import Mapping
35
35
  from typing import Any
36
- from typing import Literal
37
36
  from typing import TypedDict
38
37
 
39
38
  from ..api import PaginationConfigMap
@@ -44,26 +43,17 @@ from ..types import StrAnyMap
44
43
 
45
44
 
46
45
  __all__ = [
47
- # Type aliases
48
- 'ConnectorType',
49
- # 'PaginationType',
50
46
  # TypedDicts
51
47
  'ApiProfileDefaultsMap',
52
48
  'ApiProfileConfigMap',
53
49
  'ApiConfigMap',
54
50
  'EndpointMap',
55
- 'ConnectorApiConfigMap',
56
- 'ConnectorDbConfigMap',
57
- 'ConnectorFileConfigMap',
58
51
  ]
59
52
 
60
53
 
61
54
  # SECTION: TYPE ALIASES ===================================================== #
62
55
 
63
56
 
64
- # Literal type for supported connector kinds
65
- type ConnectorType = Literal['api', 'database', 'file']
66
-
67
57
  # Literal type for supported pagination kinds
68
58
  # type PaginationType = Literal['page', 'offset', 'cursor']
69
59
 
@@ -129,60 +119,6 @@ class ApiProfileDefaultsMap(TypedDict, total=False):
129
119
  rate_limit: RateLimitConfigMap | StrAnyMap
130
120
 
131
121
 
132
- class ConnectorApiConfigMap(TypedDict, total=False):
133
- """
134
- Shape accepted by ConnectorApi.from_obj (all keys optional).
135
-
136
- See Also
137
- --------
138
- - etlplus.config.connector.ConnectorApi.from_obj
139
- """
140
-
141
- name: str
142
- type: ConnectorType
143
- url: str
144
- method: str
145
- headers: StrAnyMap
146
- query_params: StrAnyMap
147
- pagination: PaginationConfigMap
148
- rate_limit: RateLimitConfigMap
149
- api: str
150
- endpoint: str
151
-
152
-
153
- class ConnectorDbConfigMap(TypedDict, total=False):
154
- """
155
- Shape accepted by ConnectorDb.from_obj (all keys optional).
156
-
157
- See Also
158
- --------
159
- - etlplus.config.connector.ConnectorDb.from_obj
160
- """
161
-
162
- name: str
163
- type: ConnectorType
164
- connection_string: str
165
- query: str
166
- table: str
167
- mode: str
168
-
169
-
170
- class ConnectorFileConfigMap(TypedDict, total=False):
171
- """
172
- Shape accepted by ConnectorFile.from_obj (all keys optional).
173
-
174
- See Also
175
- --------
176
- - etlplus.config.connector.ConnectorFile.from_obj
177
- """
178
-
179
- name: str
180
- type: ConnectorType
181
- format: str
182
- path: str
183
- options: StrAnyMap
184
-
185
-
186
122
  class EndpointMap(TypedDict, total=False):
187
123
  """
188
124
  Shape accepted by EndpointConfig.from_obj.
@@ -1,4 +1,4 @@
1
- # etlplus.database subpackage
1
+ # `etlplus.database` Subpackage
2
2
 
3
3
  Documentation for the `etlplus.database` subpackage: database engine, schema, and ORM helpers.
4
4
 
@@ -9,7 +9,7 @@ Documentation for the `etlplus.database` subpackage: database engine, schema, an
9
9
 
10
10
  Back to project overview: see the top-level [README](../../README.md).
11
11
 
12
- - [etlplus.database subpackage](#etlplusdatabase-subpackage)
12
+ - [`etlplus.database` Subpackage](#etlplusdatabase-subpackage)
13
13
  - [Database Engine and Connections](#database-engine-and-connections)
14
14
  - [Schema and DDL Helpers](#schema-and-ddl-helpers)
15
15
  - [ORM Utilities](#orm-utilities)
@@ -1,4 +1,4 @@
1
- # etlplus.file subpackage
1
+ # `etlplus.file` Subpackage
2
2
 
3
3
  Documentation for the `etlplus.file` subpackage: unified file format support and helpers for reading
4
4
  and writing data files.
@@ -11,7 +11,7 @@ and writing data files.
11
11
 
12
12
  Back to project overview: see the top-level [README](../../README.md).
13
13
 
14
- - [etlplus.file subpackage](#etlplusfile-subpackage)
14
+ - [`etlplus.file` Subpackage](#etlplusfile-subpackage)
15
15
  - [Supported File Formats](#supported-file-formats)
16
16
  - [Inferring File Format and Compression](#inferring-file-format-and-compression)
17
17
  - [Reading and Writing Files](#reading-and-writing-files)
@@ -20,7 +20,6 @@ from ..api import RequestOptions
20
20
  from ..api import compose_api_request_env
21
21
  from ..api import compose_api_target_env
22
22
  from ..api import paginate_with_client
23
- from ..config import load_pipeline_config
24
23
  from ..enums import DataConnectorType
25
24
  from ..file import FileFormat
26
25
  from ..types import JSONData
@@ -29,6 +28,7 @@ from ..types import PipelineConfig
29
28
  from ..types import StrPath
30
29
  from ..types import Timeout
31
30
  from ..utils import print_json
31
+ from ..workflow import load_pipeline_config
32
32
  from .extract import extract
33
33
  from .load import load
34
34
  from .transform import transform
@@ -162,9 +162,12 @@ def run(
162
162
  # can monkeypatch this class on etlplus.ops.run.
163
163
  ClientClass = EndpointClient # noqa: N806
164
164
  client = ClientClass(
165
- base_url=cast(str, env['base_url']),
165
+ base_url=cast(str, env.get('base_url')),
166
166
  base_path=cast(str | None, env.get('base_path')),
167
- endpoints=cast(dict[str, str], env['endpoints_map']),
167
+ endpoints=cast(
168
+ dict[str, str],
169
+ env.get('endpoints_map', {}),
170
+ ),
168
171
  retry=env.get('retry'),
169
172
  retry_network_errors=bool(
170
173
  env.get('retry_network_errors', False),
@@ -173,7 +176,7 @@ def run(
173
176
  )
174
177
  data = paginate_with_client(
175
178
  client,
176
- cast(str, env['endpoint_key']),
179
+ cast(str, env.get('endpoint_key')),
177
180
  env.get('params'),
178
181
  env.get('headers'),
179
182
  env.get('timeout'),
@@ -276,12 +279,14 @@ def run(
276
279
  if not url_t:
277
280
  raise ValueError('API target missing "url"')
278
281
  kwargs_t: dict[str, Any] = {}
279
- if env_t.get('headers'):
280
- kwargs_t['headers'] = cast(dict[str, str], env_t['headers'])
282
+ headers = env_t.get('headers')
283
+ if headers:
284
+ kwargs_t['headers'] = cast(dict[str, str], headers)
281
285
  if env_t.get('timeout') is not None:
282
- kwargs_t['timeout'] = env_t['timeout']
283
- if env_t.get('session') is not None:
284
- kwargs_t['session'] = env_t['session']
286
+ kwargs_t['timeout'] = env_t.get('timeout')
287
+ session = env_t.get('session')
288
+ if session is not None:
289
+ kwargs_t['session'] = session
285
290
  result = load(
286
291
  data,
287
292
  'api',
@@ -1,4 +1,4 @@
1
- # etlplus.templates subpackage
1
+ # `etlplus.templates` Subpackage
2
2
 
3
3
  Documentation for the `etlplus.templates` subpackage: SQL and DDL template helpers.
4
4
 
@@ -8,7 +8,7 @@ Documentation for the `etlplus.templates` subpackage: SQL and DDL template helpe
8
8
 
9
9
  Back to project overview: see the top-level [README](../../README.md).
10
10
 
11
- - [etlplus.templates subpackage](#etlpustemplates-subpackage)
11
+ - [`etlplus.templates` Subpackage](#etlplus-templates-subpackage)
12
12
  - [Available Templates](#available-templates)
13
13
  - [Rendering Templates](#rendering-templates)
14
14
  - [Example: Rendering a DDL Template](#example-rendering-a-ddl-template)
@@ -1,7 +1,7 @@
1
- # etlplus.config subpackage
1
+ # `etlplus.workflow` Subpackage
2
2
 
3
- Documentation for the `etlplus.config` subpackage: configuration helpers for connectors, pipelines,
4
- jobs, and profiles.
3
+ Documentation for the `etlplus.workflow` subpackage: configuration helpers for connectors,
4
+ pipelines, jobs, and profiles.
5
5
 
6
6
  - Provides classes and utilities for managing ETL pipeline configuration
7
7
  - Supports YAML/JSON config loading and validation
@@ -10,7 +10,7 @@ jobs, and profiles.
10
10
 
11
11
  Back to project overview: see the top-level [README](../../README.md).
12
12
 
13
- - [etlplus.config subpackage](#etlplusconfig-subpackage)
13
+ - [`etlplus.workflow` Subpackage](#etlplusworkflow-subpackage)
14
14
  - [Supported Configuration Types](#supported-configuration-types)
15
15
  - [Loading and Validating Configs](#loading-and-validating-configs)
16
16
  - [Example: Loading a Pipeline Config](#example-loading-a-pipeline-config)
@@ -28,7 +28,7 @@ Back to project overview: see the top-level [README](../../README.md).
28
28
  Use the provided classes to load and validate configuration files:
29
29
 
30
30
  ```python
31
- from etlplus.config import PipelineConfig
31
+ from etlplus.workflow import PipelineConfig
32
32
 
33
33
  cfg = PipelineConfig.from_yaml("pipeline.yml")
34
34
  ```
@@ -39,7 +39,7 @@ cfg = PipelineConfig.from_yaml("pipeline.yml")
39
39
  ## Example: Loading a Pipeline Config
40
40
 
41
41
  ```python
42
- from etlplus.config import PipelineConfig
42
+ from etlplus.workflow import PipelineConfig
43
43
 
44
44
  pipeline = PipelineConfig.from_yaml("configs/pipeline.yml")
45
45
  print(pipeline)
@@ -1,17 +1,7 @@
1
1
  """
2
- :mod:`etlplus.config` package.
2
+ :mod:`etlplus.workflow` package.
3
3
 
4
- Configuration models and helpers for ETLPlus.
5
-
6
- This package defines models for data sources/targets ("connectors"), APIs,
7
- pagination/rate limits, pipeline orchestration, and related utilities. The
8
- parsers are permissive (accepting ``Mapping[str, Any]``) and normalize to
9
- concrete types without raising on unknown/optional fields.
10
-
11
- Notes
12
- -----
13
- - The models use ``@dataclass(slots=True)`` and avoid mutating inputs.
14
- - TypedDicts are editor/type-checking hints and are not enforced at runtime.
4
+ Job workflow helpers.
15
5
  """
16
6
 
17
7
  from __future__ import annotations
@@ -21,6 +11,7 @@ from .connector import ConnectorApi
21
11
  from .connector import ConnectorDb
22
12
  from .connector import ConnectorFile
23
13
  from .connector import parse_connector
14
+ from .dag import topological_sort_jobs
24
15
  from .jobs import ExtractRef
25
16
  from .jobs import JobConfig
26
17
  from .jobs import LoadRef
@@ -28,29 +19,25 @@ from .jobs import TransformRef
28
19
  from .jobs import ValidationRef
29
20
  from .pipeline import PipelineConfig
30
21
  from .pipeline import load_pipeline_config
31
- from .profile import ProfileConfig
32
- from .types import ConnectorType
33
22
 
34
23
  # SECTION: EXPORTS ========================================================== #
35
24
 
36
25
 
37
26
  __all__ = [
38
- # Connectors
39
- 'Connector',
40
- 'ConnectorType',
27
+ # Data Classes
41
28
  'ConnectorApi',
42
29
  'ConnectorDb',
43
30
  'ConnectorFile',
44
- 'parse_connector',
45
- # Jobs / Refs
46
31
  'ExtractRef',
47
32
  'JobConfig',
48
33
  'LoadRef',
34
+ 'PipelineConfig',
49
35
  'TransformRef',
50
36
  'ValidationRef',
51
- # Pipeline
52
- 'PipelineConfig',
37
+ # Functions
53
38
  'load_pipeline_config',
54
- # Profile
55
- 'ProfileConfig',
39
+ 'parse_connector',
40
+ 'topological_sort_jobs',
41
+ # Type Aliases
42
+ 'Connector',
56
43
  ]
@@ -1,5 +1,5 @@
1
1
  """
2
- :mod:`etlplus.config.connector` module.
2
+ :mod:`etlplus.workflow.connector` module.
3
3
 
4
4
  A module defining configuration types for data source/target connectors in ETL
5
5
  pipelines. A "connector" is any I/O endpoint:
@@ -11,18 +11,19 @@ pipelines. A "connector" is any I/O endpoint:
11
11
 
12
12
  Examples
13
13
  --------
14
- - Use ``ConnectorApi``/``ConnectorFile``/``ConnectorDb`` when you want the
15
- concrete dataclasses.
16
- - Use the ``Connector`` union for typing a value that can be any connector.
17
- - Use ``parse_connector(obj)`` to construct a connector instance from a generic
18
- mapping that includes a ``type`` key.
14
+ - Use :class:`ConnectorApi`/:class:`ConnectorFile`/:class:`ConnectorDb` when
15
+ you want the concrete dataclasses.
16
+ - Use the :class:`Connector` union for typing a value that can be any
17
+ connector.
18
+ - Use :func:`parse_connector(obj)` to construct a connector instance from a
19
+ generic mapping that includes a *type* key.
19
20
 
20
21
  Notes
21
22
  -----
22
23
  - TypedDict shapes are editor hints; runtime parsing remains permissive
23
- (from_obj accepts Mapping[str, Any]).
24
+ (from_obj accepts Mapping[str, Any]).
24
25
  - TypedDicts referenced in :mod:`etlplus.config.types` remain editor hints.
25
- Runtime parsing stays permissive and tolerant.
26
+ Runtime parsing stays permissive and tolerant.
26
27
 
27
28
  See Also
28
29
  --------
@@ -59,7 +60,7 @@ if TYPE_CHECKING: # Editor-only typing hints to avoid runtime imports
59
60
 
60
61
 
61
62
  __all__ = [
62
- # Classes
63
+ # Data Classes
63
64
  'ConnectorApi',
64
65
  'ConnectorDb',
65
66
  'ConnectorFile',
@@ -83,12 +84,12 @@ class ConnectorApi:
83
84
  name : str
84
85
  Unique connector name.
85
86
  type : ConnectorType
86
- Connector kind literal, always ``"api"``.
87
+ Connector kind literal, always ``'api'``.
87
88
  url : str | None
88
89
  Direct absolute URL (when not using ``service``/``endpoint`` refs).
89
90
  method : str | None
90
91
  Optional HTTP method; typically omitted for sources (defaults to
91
- GET) and used for targets (e.g., ``"post"``).
92
+ GET) and used for targets (e.g., ``'post'``).
92
93
  headers : dict[str, str]
93
94
  Additional request headers.
94
95
  query_params : dict[str, Any]
@@ -111,7 +112,7 @@ class ConnectorApi:
111
112
 
112
113
  # Direct form
113
114
  url: str | None = None
114
- # Optional HTTP method; typically omitted for sources (defaults to GET
115
+ # Optional HTTP method; typically omitted for sources (defaults to GET)
115
116
  # at runtime) and used for targets (e.g., 'post', 'put').
116
117
  method: str | None = None
117
118
  headers: dict[str, str] = field(default_factory=dict)
@@ -185,7 +186,7 @@ class ConnectorDb:
185
186
  name : str
186
187
  Unique connector name.
187
188
  type : ConnectorType
188
- Connector kind literal, always ``"database"``.
189
+ Connector kind literal, always ``'database'``.
189
190
  connection_string : str | None
190
191
  Connection string/DSN for the database.
191
192
  query : str | None
@@ -193,7 +194,7 @@ class ConnectorDb:
193
194
  table : str | None
194
195
  Target/source table name (optional).
195
196
  mode : str | None
196
- Load mode hint (e.g., ``"append"``, ``"replace"``) — future use.
197
+ Load mode hint (e.g., ``'append'``, ``'replace'``) — future use.
197
198
  """
198
199
 
199
200
  # -- Attributes -- #
@@ -262,9 +263,9 @@ class ConnectorFile:
262
263
  name : str
263
264
  Unique connector name.
264
265
  type : ConnectorType
265
- Connector kind literal, always ``"file"``.
266
+ Connector kind literal, always ``'file'``.
266
267
  format : str | None
267
- File format (e.g., ``"json"``, ``"csv"``).
268
+ File format (e.g., ``'json'``, ``'csv'``).
268
269
  path : str | None
269
270
  File path or URI.
270
271
  options : dict[str, Any]
@@ -0,0 +1,105 @@
1
+ """
2
+ :mod:`etlplus.workflow.dag` module.
3
+
4
+ Lightweight directed acyclic graph (DAG) helpers for ordering jobs based on
5
+ :attr:`depends_on`.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from collections import deque
11
+ from dataclasses import dataclass
12
+
13
+ from .jobs import JobConfig
14
+
15
+ # SECTION: EXPORTS ========================================================== #
16
+
17
+
18
+ __all__ = [
19
+ # Errors
20
+ 'DagError',
21
+ # Functions
22
+ 'topological_sort_jobs',
23
+ ]
24
+
25
+
26
+ # SECTION: ERRORS =========================================================== #
27
+
28
+
29
+ @dataclass(slots=True)
30
+ class DagError(ValueError):
31
+ """
32
+ Raised when the job dependency graph is invalid.
33
+
34
+ Attributes
35
+ ----------
36
+ message : str
37
+ Error message.
38
+ """
39
+
40
+ # -- Attributes -- #
41
+
42
+ message: str
43
+
44
+ # -- Magic Methods (Object Representation) -- #
45
+
46
+ def __str__(self) -> str:
47
+ return self.message
48
+
49
+
50
+ # SECTION: FUNCTIONS ======================================================== #
51
+
52
+
53
+ def topological_sort_jobs(
54
+ jobs: list[JobConfig],
55
+ ) -> list[JobConfig]:
56
+ """
57
+ Return jobs in topological order based on :attr:`depends_on`.
58
+
59
+ Parameters
60
+ ----------
61
+ jobs : list[JobConfig]
62
+ List of job configurations to sort.
63
+
64
+ Returns
65
+ -------
66
+ list[JobConfig]
67
+ Jobs sorted in topological order.
68
+
69
+ Raises
70
+ ------
71
+ DagError
72
+ If a dependency is missing, self-referential, or when a cycle is
73
+ detected.
74
+ """
75
+ index = {job.name: job for job in jobs}
76
+ edges: dict[str, set[str]] = {name: set() for name in index}
77
+ indegree: dict[str, int] = {name: 0 for name in index}
78
+
79
+ for job in jobs:
80
+ for dep in job.depends_on:
81
+ if dep not in index:
82
+ raise DagError(
83
+ f'Unknown dependency "{dep}" in job "{job.name}"',
84
+ )
85
+ if dep == job.name:
86
+ raise DagError(f'Job "{job.name}" depends on itself')
87
+ if job.name not in edges[dep]:
88
+ edges[dep].add(job.name)
89
+ indegree[job.name] += 1
90
+
91
+ queue = deque(sorted(name for name, deg in indegree.items() if deg == 0))
92
+ ordered: list[str] = []
93
+
94
+ while queue:
95
+ name = queue.popleft()
96
+ ordered.append(name)
97
+ for child in sorted(edges[name]):
98
+ indegree[child] -= 1
99
+ if indegree[child] == 0:
100
+ queue.append(child)
101
+
102
+ if len(ordered) != len(jobs):
103
+ raise DagError('Dependency cycle detected')
104
+
105
+ return [index[name] for name in ordered]