etlplus 0.15.0__py3-none-any.whl → 0.15.4__py3-none-any.whl

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.
@@ -16,6 +16,7 @@ Notes
16
16
  from __future__ import annotations
17
17
 
18
18
  import os
19
+ from collections.abc import Callable
19
20
  from collections.abc import Mapping
20
21
  from dataclasses import dataclass
21
22
  from dataclasses import field
@@ -28,12 +29,12 @@ from ..file import File
28
29
  from ..file import FileFormat
29
30
  from ..types import StrAnyMap
30
31
  from ..utils import coerce_dict
32
+ from ..utils import deep_substitute
31
33
  from ..utils import maybe_mapping
32
34
  from .connector import Connector
33
35
  from .connector import parse_connector
34
36
  from .jobs import JobConfig
35
37
  from .profile import ProfileConfig
36
- from .utils import deep_substitute
37
38
 
38
39
  # SECTION: EXPORTS ========================================================== #
39
40
 
@@ -49,55 +50,65 @@ __all__ = [
49
50
  # SECTION: INTERNAL FUNCTIONS =============================================== #
50
51
 
51
52
 
52
- def _build_jobs(
53
+ def _collect_parsed[T](
53
54
  raw: StrAnyMap,
54
- ) -> list[JobConfig]:
55
+ key: str,
56
+ parser: Callable[[Any], T | None],
57
+ ) -> list[T]:
55
58
  """
56
- Return a list of ``JobConfig`` objects parsed from the mapping.
59
+ Collect parsed items from ``raw[key]`` using a tolerant parser.
57
60
 
58
61
  Parameters
59
62
  ----------
60
63
  raw : StrAnyMap
61
64
  Raw pipeline mapping.
65
+ key : str
66
+ Key pointing to a list-like payload.
67
+ parser : Callable[[Any], T | None]
68
+ Parser that returns an instance or ``None`` for invalid entries.
62
69
 
63
70
  Returns
64
71
  -------
65
- list[JobConfig]
66
- Parsed job configurations.
72
+ list[T]
73
+ Parsed items, excluding invalid entries.
67
74
  """
68
- jobs: list[JobConfig] = []
69
- for job_raw in raw.get('jobs', []) or []:
70
- job_cfg = JobConfig.from_obj(job_raw)
71
- if job_cfg is not None:
72
- jobs.append(job_cfg)
73
-
74
- return jobs
75
+ items: list[T] = []
76
+ for entry in raw.get(key, []) or []:
77
+ parsed = parser(entry)
78
+ if parsed is not None:
79
+ items.append(parsed)
80
+ return items
75
81
 
76
82
 
77
- def _build_sources(
78
- raw: StrAnyMap,
79
- ) -> list[Connector]:
83
+ def _parse_connector_entry(
84
+ obj: Any,
85
+ ) -> Connector | None:
80
86
  """
81
- Return a list of source connectors parsed from the mapping.
87
+ Parse a connector mapping into a concrete connector instance.
82
88
 
83
89
  Parameters
84
90
  ----------
85
- raw : StrAnyMap
86
- Raw pipeline mapping.
91
+ obj : Any
92
+ Candidate connector mapping.
87
93
 
88
94
  Returns
89
95
  -------
90
- list[Connector]
91
- Parsed source connectors.
96
+ Connector | None
97
+ Parsed connector instance or ``None`` when invalid.
92
98
  """
93
- return _build_connectors(raw, 'sources')
99
+ if not (entry := maybe_mapping(obj)):
100
+ return None
101
+ try:
102
+ return parse_connector(entry)
103
+ except TypeError:
104
+ return None
94
105
 
95
106
 
96
- def _build_targets(
107
+ def _build_sources(
97
108
  raw: StrAnyMap,
98
109
  ) -> list[Connector]:
99
110
  """
100
- Return a list of target connectors parsed from the mapping.
111
+ Return a list of source connectors parsed from the mapping.
101
112
 
102
113
  Parameters
103
114
  ----------
@@ -107,43 +118,32 @@ def _build_targets(
107
118
  Returns
108
119
  -------
109
120
  list[Connector]
110
- Parsed target connectors.
121
+ Parsed source connectors.
111
122
  """
112
- return _build_connectors(raw, 'targets')
123
+ return list(
124
+ _collect_parsed(raw, 'sources', _parse_connector_entry),
125
+ )
113
126
 
114
127
 
115
- def _build_connectors(
128
+ def _build_targets(
116
129
  raw: StrAnyMap,
117
- key: str,
118
130
  ) -> list[Connector]:
119
131
  """
120
- Return parsed connectors from ``raw[key]`` using tolerant parsing.
121
-
122
- Unknown or malformed entries are skipped to preserve permissiveness.
132
+ Return a list of target connectors parsed from the mapping.
123
133
 
124
134
  Parameters
125
135
  ----------
126
136
  raw : StrAnyMap
127
137
  Raw pipeline mapping.
128
- key : str
129
- List-containing top-level key ("sources" or "targets").
130
138
 
131
139
  Returns
132
140
  -------
133
141
  list[Connector]
134
- Constructed connector instances (malformed entries skipped).
142
+ Parsed target connectors.
135
143
  """
136
- items: list[Connector] = []
137
- for obj in raw.get(key, []) or []:
138
- if not (entry := maybe_mapping(obj)):
139
- continue
140
- try:
141
- items.append(parse_connector(entry))
142
- except TypeError:
143
- # Skip unsupported types or malformed entries
144
- continue
145
-
146
- return items
144
+ return list(
145
+ _collect_parsed(raw, 'targets', _parse_connector_entry),
146
+ )
147
147
 
148
148
 
149
149
  # SECTION: FUNCTIONS ======================================================== #
@@ -321,7 +321,7 @@ class PipelineConfig:
321
321
  targets = _build_targets(raw)
322
322
 
323
323
  # Jobs
324
- jobs = _build_jobs(raw)
324
+ jobs = _collect_parsed(raw, 'jobs', JobConfig.from_obj)
325
325
 
326
326
  # Table schemas (optional, tolerant pass-through structures).
327
327
  table_schemas: list[dict[str, Any]] = []
etlplus/workflow/types.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """
2
2
  :mod:`etlplus.workflow.types` module.
3
3
 
4
- Type aliases and editor-only :class:`TypedDict`s for :mod:`etlplus.config`.
4
+ Type aliases and editor-only :class:`TypedDict`s for :mod:`etlplus.workflow`.
5
5
 
6
6
  These types improve IDE autocomplete and static analysis while the runtime
7
7
  parsers remain permissive.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: etlplus
3
- Version: 0.15.0
3
+ Version: 0.15.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
@@ -805,12 +805,12 @@ Navigate to detailed documentation for each subpackage:
805
805
 
806
806
  - [etlplus.api](etlplus/api/README.md): Lightweight HTTP client and paginated REST helpers
807
807
  - [etlplus.file](etlplus/file/README.md): Unified file format support and helpers
808
- - [etlplus.config](etlplus/config/README.md): Configuration helpers for connectors, pipelines, jobs,
809
- and profiles
810
- - [etlplus.cli](etlplus/cli/README.md): Command-line interface for ETLPlus workflows
808
+ - [etlplus.cli](etlplus/cli/README.md): Command-line interface definitions for `etlplus`
811
809
  - [etlplus.database](etlplus/database/README.md): Database engine, schema, and ORM helpers
812
810
  - [etlplus.templates](etlplus/templates/README.md): SQL and DDL template helpers
813
811
  - [etlplus.validation](etlplus/validation/README.md): Data validation utilities and helpers
812
+ - [etlplus.workflow](etlplus/workflow/README.md): Helpers for data connectors, pipelines, jobs, and
813
+ profiles
814
814
 
815
815
  ### Community Health
816
816
 
@@ -1,24 +1,23 @@
1
- etlplus/README.md,sha256=NwnsMajz2_afAXgYDnTIwVU6uBoOKQn8f6UJHUY-Xe8,1461
1
+ etlplus/README.md,sha256=JaMSomnMsHrTruDnonHqe83Rv4K0-e7Wy46tMeVoleU,1468
2
2
  etlplus/__init__.py,sha256=mgTP4PJmRmsEjTCAizzzdtzAmhuHtarmPzphzdjvLgM,277
3
3
  etlplus/__main__.py,sha256=btoROneNiigyfBU7BSzPKZ1R9gzBMpxcpsbPwmuHwTM,479
4
4
  etlplus/__version__.py,sha256=1E0GMK_yUWCMQFKxXjTvyMwofi0qT2k4CDNiHWiymWE,327
5
- etlplus/dag.py,sha256=4EYmBsJax3y4clHv10jjdp3GrBBD_WblvtxUb_JxGCQ,2464
6
5
  etlplus/enums.py,sha256=ZxObavNilITJNT4Mf2hBy1s4kKtTFUTmrc8_whkxKNg,7541
7
6
  etlplus/mixins.py,sha256=ifGpHwWv7U00yqGf-kN93vJax2IiK4jaGtTsPsO3Oak,1350
8
7
  etlplus/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- etlplus/types.py,sha256=LhyKU67CW7CcLEshE_1OIvEqWr-QVMJVYByf1221miY,6161
10
- etlplus/utils.py,sha256=BMLTWAvCJj3zLEcffBgURYnu0UGhhXsfH2WWpAt7fV8,13363
11
- etlplus/api/README.md,sha256=Glxz9Y5RagqJomCPc8q3Nagm-23Owj9aAd8fM0H-Ph8,7917
8
+ etlplus/types.py,sha256=-knM5bWRq49k4o-KlW-GRsENcj2P-h6oPnEmuz3EJso,6214
9
+ etlplus/utils.py,sha256=JVOBPyjIv1C9EZWZuYdqCISS-Dpa-PHrFvvruu1R0HA,17171
10
+ etlplus/api/README.md,sha256=amxS_eIcsnNuVvD0x_w8nkyfedOTYbhlY0gGhaFg0DE,8705
12
11
  etlplus/api/__init__.py,sha256=PK2lQv1FbsE7ZZS_ejevFZQSuOUHGApBc22YfHAzMqA,4615
13
12
  etlplus/api/auth.py,sha256=GOO5on-LoMS1GXTAhtK9rFcfpjbBcNeA6NE5UZwIq0g,12158
14
- etlplus/api/config.py,sha256=Xi1_FUaogQxAAx7LUTGwYr5vEB_SIFvjU_xgMGF2Fsc,17913
13
+ etlplus/api/config.py,sha256=_8_iXpaHXQN6RQ1z0e3q0LTY936IYsc1SVcE6_sPT6E,17639
15
14
  etlplus/api/endpoint_client.py,sha256=OPAvw3NkpSzeITqgRP1reyqX9Nc_XC8lAj6Yp7nV4Tw,30705
16
15
  etlplus/api/enums.py,sha256=Tvkru6V8fzQh2JM2FDLcA_yaPENOKz5JgzxLhieqEvc,1141
17
16
  etlplus/api/errors.py,sha256=XjI2xW-sypMUNUbqfc2S57-IGyWnH3oCDFhCmKYYI_Q,4648
18
17
  etlplus/api/request_manager.py,sha256=fhzPV5x7DqpKqoLvfDR8GKhBX_QBMtvZsRXxVnQQElY,18674
19
18
  etlplus/api/retry_manager.py,sha256=0GDhJVyIlb1Ww35JUWlYoa8QYUPjKLBtxQeZj3TdLbY,11306
20
19
  etlplus/api/transport.py,sha256=DVWzWFZLfQQXaTJ60w-SzdXRxiOE8llPprpUB5CtSyg,9410
21
- etlplus/api/types.py,sha256=ijHgREN5akEFF3th2yNdWbhJhXvAxh_24qQgg_sMB4w,4653
20
+ etlplus/api/types.py,sha256=geuKmnf6C2P9_tpRpOF90Uu7J160OL3RQpQ_CgJsSrM,6695
22
21
  etlplus/api/utils.py,sha256=U39OXcrPiLD9m0Y03pACXIIOyuuDPO6RRKuTcwEayBE,26249
23
22
  etlplus/api/pagination/__init__.py,sha256=a4UX2J0AG8RMvmHt_CCofUm5vSmFo6GAfkb8XnSXypM,1395
24
23
  etlplus/api/pagination/client.py,sha256=yMEpWqRxTCD4zRc9OYtEyUtShpGH5atiHFEAt95v2FE,5394
@@ -29,28 +28,25 @@ etlplus/api/rate_limiting/config.py,sha256=eB-dSOG-c5awYeEpnDsQJ5pO8BJtEpGYxOunB
29
28
  etlplus/api/rate_limiting/rate_limiter.py,sha256=heAasm1KLeBFmoJKPz6W-eQbNuahMgG1E9-z8zGEUro,7035
30
29
  etlplus/cli/README.md,sha256=8H_G2d3HteYIU6ReX9K9DM485QjWDT5vHMQbGD_vv20,1237
31
30
  etlplus/cli/__init__.py,sha256=J97-Rv931IL1_b4AXnB7Fbbd7HKnHBpx18NQfC_kE6c,299
32
- etlplus/cli/commands.py,sha256=g8_m3A8HEMyTRu2HctNiRoi2gtB5oSZCUEcyq-PIXos,24669
31
+ etlplus/cli/commands.py,sha256=HFlg29tO6Jwv1NXWAHmvniLCyRSlboL55Arn9B8nZAM,25028
33
32
  etlplus/cli/constants.py,sha256=E6Uy4WauLa_0zkzxqImXh-bb1gKdb9sBZQVc8QOzr2Q,1943
34
- etlplus/cli/handlers.py,sha256=mILI386pf2RzfI7WJfXJ13huIGwsqFfcYx4sNIf1aWU,18174
33
+ etlplus/cli/handlers.py,sha256=hXsgG3c6n8JDN4U3Svq6i2r21vetw5NcmXE1bGSwb9w,18560
35
34
  etlplus/cli/io.py,sha256=EFaBPYaBOyOllfMQWXgTjy-MPiGfNejicpD7ROrPyAE,7840
36
- etlplus/cli/main.py,sha256=IgeqxypixfwLHR-QcpgVMQ7vMZ865bXOh2oO9v-BWeM,5234
35
+ etlplus/cli/main.py,sha256=d5yhCS6aNo_TKMvhdTS7qeJm4yb1S58gGBrUoBV7SCs,5241
37
36
  etlplus/cli/options.py,sha256=vfXT3YLh7wG1iC-aTdSg6ItMC8l6n0Lozmy53XjqLbA,1199
38
- etlplus/cli/state.py,sha256=Pfd8ru0wYIN7eGp1_A0tioqs1LiCDZCuJ6AnjZb6yYQ,8027
37
+ etlplus/cli/state.py,sha256=3Dq5BKct0uAvRajtc2yHbsX7wqepZOwlAMKsyvQcnqk,7918
39
38
  etlplus/cli/types.py,sha256=tclhKVJXDqHzlTQBYKARfqMgDOcuBJ-Zej2pvFy96WM,652
40
- etlplus/config/README.md,sha256=CBeUW6IBfS8_U1iW4LzMQiAshcaTr2hgMDW3bos3BUY,1467
41
- etlplus/config/__init__.py,sha256=rjUC-OUH_dXqG11B5r0hlJuss1J_nSinkvoygfuqId8,921
42
- etlplus/config/types.py,sha256=3EyibjzqjK3GQu0fsvbmkfHERDtubEmtOU_lXsgXQuQ,3603
43
39
  etlplus/database/README.md,sha256=3Af5BEGLkBmMoGOLtS1GQuj4wKPh_CwSp5NEPMf2uaY,1435
44
40
  etlplus/database/__init__.py,sha256=AKJsDl2RHuRGPS-eXgNJeh4aSncJP5Y0yLApBF6i7i8,1052
45
41
  etlplus/database/ddl.py,sha256=0dEM9SJMMabkhI_h-Fc0j9a1Sl5lSyZdI0bIeBVGm10,7913
46
- etlplus/database/engine.py,sha256=PUxXLvLPyc-KaxuW7fXe12wYci7EvUp-Ad1H3bGhUog,4058
47
- etlplus/database/orm.py,sha256=gCSqH-CjQz6tV9133-VqgiwokK5ylun0BwXaIWfImAo,10008
42
+ etlplus/database/engine.py,sha256=gWgTjGoo1HYHrBuYX5l2P8avAR3XithzAiqUOpLHP-8,4386
43
+ etlplus/database/orm.py,sha256=ZCHkeVEUns2eievlFzmLyVKA3YVPea1xs6vrcUBZ7Jw,10010
48
44
  etlplus/database/schema.py,sha256=813C0Dd3WE53KTYot4dgjAxctgKXLXx-8_Rk_4r2e28,7022
49
45
  etlplus/database/types.py,sha256=_pkQyC14TzAlgyeIqZG4F5LWYknZbHw3TW68Auk7Ya0,795
50
46
  etlplus/file/README.md,sha256=ivU8svVs1fktQiW5ozvh1N-IOSLCAQ3oM9bW8DUFwIw,3630
51
47
  etlplus/file/__init__.py,sha256=X03bosSM-uSd6dh3ur0un6_ozFRw2Tm4PE6kVUjtXK8,475
52
48
  etlplus/file/_imports.py,sha256=9e8CWjyNIRcmiULEPuwtnJELUOXd4EvVv_vDnDYiB9c,3121
53
- etlplus/file/_io.py,sha256=GXTcvjfXQX4rSdyu1JNhFmqQJlirDqd8dEGCN3dHvNg,2968
49
+ etlplus/file/_io.py,sha256=2Dj7MH6r-yp_SsCe5GmuNN7Ta70qYzF_jKoMgnD3rME,3925
54
50
  etlplus/file/accdb.py,sha256=xdBLrXHDonVJ1Z6-qZRrLBXpUwqw5nZYxDuxYMJHzVs,1681
55
51
  etlplus/file/arrow.py,sha256=rYPqoCAI3cBHETYN3c0Xi7R5Nq7_prIdyESm3ll3Zos,1694
56
52
  etlplus/file/avro.py,sha256=yFQMnWPcvC0CbDCyagoB9SHRIuvl2SXIoQJTBIlw4dA,4437
@@ -72,7 +68,7 @@ etlplus/file/hdf5.py,sha256=SZ-UbXTJGOYA82hdma7AFulWo9emH5Kih_RXC7f-Bfk,1624
72
68
  etlplus/file/ini.py,sha256=HlvQyQC00oBD8KFBfznPjBx9wF1ZwXH7Yo1JaXqCq8I,1701
73
69
  etlplus/file/ion.py,sha256=9q938jROTAcuUT5sRYvS1qIeoz9p8KkVWYDDS2-2A_4,1710
74
70
  etlplus/file/jinja2.py,sha256=FFqGQjBnRgPQ-oJ3WqiKspJMj6g_J0fDKGwsoOJAEzk,1630
75
- etlplus/file/json.py,sha256=6EWsOI4RZo366FYsnPp-VkHdgAVBRVUwix-hAJZQM10,2537
71
+ etlplus/file/json.py,sha256=m4eM8cPGHYAEppwLqBdq-t9gC2GtKPLptRTBeBfRimY,2130
76
72
  etlplus/file/log.py,sha256=OmsGijXZn1VhN7BUklF8saoktxkmHh1MPLI_BbGDLyc,1681
77
73
  etlplus/file/mat.py,sha256=u3jWMK8c4k9L0uVRiQSd7JgVQF-efoJj3QiKYtt1arA,1654
78
74
  etlplus/file/mdb.py,sha256=hSCaxBbc_b9dGTBpR-Gb0UTY2r3zYUxoEiKuwpnx0kI,1657
@@ -107,7 +103,7 @@ etlplus/file/xlsm.py,sha256=6meoukAvpEIm_mvVomIo2oefPYfmHHXfE1XmFfeFpvY,1738
107
103
  etlplus/file/xlsx.py,sha256=vtiAS8Ng9FV1vCWYTd1YO2ORKIJG3HDfmqy3NkVpt0A,2182
108
104
  etlplus/file/xml.py,sha256=p5P60DiV6hyiz-t970d0d0ZXB41gVvAm3MT7dTMULa8,4360
109
105
  etlplus/file/xpt.py,sha256=JLqOkZ60awNsPXSqLKcPUwqZLPhPR05zk4EVRdEfvoU,1702
110
- etlplus/file/yaml.py,sha256=8BEqCELaTQ_nRu1iksLBHVj19P6JmcUf5__fe0yVigw,2449
106
+ etlplus/file/yaml.py,sha256=P9xzginPVVyvIPbFAp6MYWwARhK50GzgMbjZB7HwOrs,2052
111
107
  etlplus/file/zip.py,sha256=nd26V3S0edklriKnKOGDTLlO8RBXTda_zLLEQrJgKL4,4185
112
108
  etlplus/file/zsav.py,sha256=2WxjXamvzj0adDbWGMWM3-cj_LsCRjyZz4J907lNkPk,1664
113
109
  etlplus/ops/README.md,sha256=8omi7DYZhelc26JKk8Cm8QR8I3OGwziysPj1ivx41iQ,1380
@@ -116,24 +112,23 @@ etlplus/ops/extract.py,sha256=OJozX25qTjME7m9aTdVPJScT3GHHgopGM8uHo_rHm1Y,5783
116
112
  etlplus/ops/load.py,sha256=RgEKnVygRN2cPDpzihU8UsIhd4eVoj0cPe0jBuNC0u4,8328
117
113
  etlplus/ops/run.py,sha256=JOPQxrciRUQ67FJhUcZ-pW1aiYGZUdxHLwhzWLNCaDo,13528
118
114
  etlplus/ops/transform.py,sha256=1P43WYUaw872JDU86FhbbbkP8xnBWpgEPn2Q-z4ywls,25421
119
- etlplus/ops/utils.py,sha256=tbV-s3NjIGO-GfdyJFxPYIYihPibNo-4TbdRq9vWVWs,11480
120
- etlplus/ops/validate.py,sha256=gLa5zcwhxGbaIhH-OqTwDsQAgAYZSajLVGwwyeT2OZk,13257
115
+ etlplus/ops/utils.py,sha256=mNUcbnLl3M6rA_BPoUqG-IFPqT1zrp_EkQTs7uvzBIQ,10839
116
+ etlplus/ops/validate.py,sha256=-OLAwQNNCmmDbmj0SB7zzYXDkJfcyBP_z9nTpqImLP0,13271
121
117
  etlplus/templates/README.md,sha256=IfPXlj1TGVA-uFWosHJhE2rabFW-znxOlOMazO9Z5cE,1361
122
118
  etlplus/templates/__init__.py,sha256=tsniN7XJYs3NwYxJ6c2HD5upHP3CDkLx-bQCMt97UOM,106
123
119
  etlplus/templates/ddl.sql.j2,sha256=s8fMWvcb4eaJVXkifuib1aQPljtZ8buuyB_uA-ZdU3Q,4734
124
120
  etlplus/templates/view.sql.j2,sha256=Iy8DHfhq5yyvrUKDxqp_aHIEXY4Tm6j4wT7YDEFWAhk,2180
125
121
  etlplus/workflow/README.md,sha256=D1oloiJCOHiqpqgv3m3qpRSIUOMIQcWtIsOPv7KkNI0,1652
126
122
  etlplus/workflow/__init__.py,sha256=LxI9VGlDBUc9ADoK8JNn3oVsGeaz1Uhjonn4Y5KIJdM,967
127
- etlplus/workflow/connector.py,sha256=9b4KF2uNKaJtsQadwT7Id8OujfJM8GLJHqI-n4kFsGA,9851
123
+ etlplus/workflow/connector.py,sha256=vnzq9-qcU4ThWeI3ZJcL8iCoaKWrSEtZ9Jixn4il2KE,9995
128
124
  etlplus/workflow/dag.py,sha256=kp31dORgk0GHbct_bipU5hu_0elwBtwLsXGjMWuhFHI,2503
129
- etlplus/workflow/jobs.py,sha256=yFNj4S7ORMtpYSA0nCAUgb4E5msW7dyvtQVPBb-CAK8,7938
130
- etlplus/workflow/pipeline.py,sha256=DL6XsX47gGPiURH8Qj1Ixm0RctI3taqo3e6Asg8fpto,9624
125
+ etlplus/workflow/jobs.py,sha256=5_jhPoT2hCGTgsbK_lIPKrB94f7tzOEUJxtU3tpvTjg,8947
126
+ etlplus/workflow/pipeline.py,sha256=eF6OYMXZ_YdDPZ5FJC6OTCAM-zsb8HxhX3IHpbb_N8w,9631
131
127
  etlplus/workflow/profile.py,sha256=dZ6P50k_ZqXnrbgrbODUqgVkymbchcEqfZR-ExjTd3M,1935
132
- etlplus/workflow/types.py,sha256=kVdOMT8qwnWlD7C4qWv-uF7r6-Kk-RlMZmoY8tnIo7A,2769
133
- etlplus/workflow/utils.py,sha256=4SUHMkt5bKBhMhiJm-DrnmE2Q4TfOgdNCKz8PJDS27o,3443
134
- etlplus-0.15.0.dist-info/licenses/LICENSE,sha256=MuNO63i6kWmgnV2pbP2SLqP54mk1BGmu7CmbtxMmT-U,1069
135
- etlplus-0.15.0.dist-info/METADATA,sha256=YdRPU6L9SNAYpfB6fypG3-KewetI_QoyDlE2WnS2wPQ,28115
136
- etlplus-0.15.0.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
137
- etlplus-0.15.0.dist-info/entry_points.txt,sha256=6w-2-jzuPa55spzK34h-UKh2JTEShh38adFRONNP9QE,45
138
- etlplus-0.15.0.dist-info/top_level.txt,sha256=aWWF-udn_sLGuHTM6W6MLh99ArS9ROkUWO8Mi8y1_2U,8
139
- etlplus-0.15.0.dist-info/RECORD,,
128
+ etlplus/workflow/types.py,sha256=Lv1MLyM45Ob5AsMQQBqzbUaD8g7HMVxu_HoQGPeiugU,2771
129
+ etlplus-0.15.4.dist-info/licenses/LICENSE,sha256=MuNO63i6kWmgnV2pbP2SLqP54mk1BGmu7CmbtxMmT-U,1069
130
+ etlplus-0.15.4.dist-info/METADATA,sha256=B7LuY1d_eg8Dm4d3s2fAV9US4PiE66qBmwS7VH7MZOo,28114
131
+ etlplus-0.15.4.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
132
+ etlplus-0.15.4.dist-info/entry_points.txt,sha256=6w-2-jzuPa55spzK34h-UKh2JTEShh38adFRONNP9QE,45
133
+ etlplus-0.15.4.dist-info/top_level.txt,sha256=aWWF-udn_sLGuHTM6W6MLh99ArS9ROkUWO8Mi8y1_2U,8
134
+ etlplus-0.15.4.dist-info/RECORD,,
etlplus/config/README.md DELETED
@@ -1,50 +0,0 @@
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)
@@ -1,33 +0,0 @@
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
- ]
etlplus/config/types.py DELETED
@@ -1,140 +0,0 @@
1
- """
2
- :mod:`etlplus.config.types` module.
3
-
4
- Type aliases and editor-only TypedDicts for :mod:`etlplus.config`.
5
-
6
- These types improve IDE autocomplete and static analysis while the runtime
7
- parsers remain permissive.
8
-
9
- Notes
10
- -----
11
- - TypedDicts in this module are intentionally ``total=False`` and are not
12
- enforced at runtime.
13
- - ``*.from_obj`` constructors accept ``Mapping[str, Any]`` and perform
14
- tolerant parsing and light casting. This keeps the runtime permissive while
15
- improving autocomplete and static analysis for contributors.
16
-
17
- Examples
18
- --------
19
- >>> from etlplus.config import Connector
20
- >>> src: Connector = {
21
- >>> "type": "file",
22
- >>> "path": "/data/input.csv",
23
- >>> }
24
- >>> tgt: Connector = {
25
- >>> "type": "database",
26
- >>> "connection_string": "postgresql://user:pass@localhost/db",
27
- >>> }
28
- >>> from etlplus.api import RetryPolicy
29
- >>> rp: RetryPolicy = {"max_attempts": 3, "backoff": 0.5}
30
- """
31
-
32
- from __future__ import annotations
33
-
34
- from collections.abc import Mapping
35
- from typing import Any
36
- from typing import TypedDict
37
-
38
- from ..api import PaginationConfigMap
39
- from ..api import RateLimitConfigMap
40
- from ..types import StrAnyMap
41
-
42
- # SECTION: EXPORTS ========================================================= #
43
-
44
-
45
- __all__ = [
46
- # TypedDicts
47
- 'ApiProfileDefaultsMap',
48
- 'ApiProfileConfigMap',
49
- 'ApiConfigMap',
50
- 'EndpointMap',
51
- ]
52
-
53
-
54
- # SECTION: TYPE ALIASES ===================================================== #
55
-
56
-
57
- # Literal type for supported pagination kinds
58
- # type PaginationType = Literal['page', 'offset', 'cursor']
59
-
60
-
61
- # SECTION: TYPED DICTS ====================================================== #
62
-
63
-
64
- class ApiConfigMap(TypedDict, total=False):
65
- """
66
- Top-level API config shape parsed by ApiConfig.from_obj.
67
-
68
- Either provide a 'base_url' with optional 'headers' and 'endpoints', or
69
- provide 'profiles' with at least one profile having a 'base_url'.
70
-
71
- See Also
72
- --------
73
- - etlplus.config.api.ApiConfig.from_obj: parses this mapping
74
- """
75
-
76
- base_url: str
77
- headers: StrAnyMap
78
- endpoints: Mapping[str, EndpointMap | str]
79
- profiles: Mapping[str, ApiProfileConfigMap]
80
-
81
-
82
- class ApiProfileConfigMap(TypedDict, total=False):
83
- """
84
- Shape accepted for a profile entry under ApiConfigMap.profiles.
85
-
86
- Notes
87
- -----
88
- `base_url` is required at runtime when profiles are provided.
89
-
90
- See Also
91
- --------
92
- - etlplus.config.api.ApiProfileConfig.from_obj: parses this mapping
93
- """
94
-
95
- base_url: str
96
- headers: StrAnyMap
97
- base_path: str
98
- auth: StrAnyMap
99
- defaults: ApiProfileDefaultsMap
100
-
101
-
102
- class ApiProfileDefaultsMap(TypedDict, total=False):
103
- """
104
- Defaults block available under a profile (all keys optional).
105
-
106
- Notes
107
- -----
108
- Runtime expects header values to be str; typing remains permissive.
109
-
110
- See Also
111
- --------
112
- - etlplus.config.api.ApiProfileConfig.from_obj: consumes this block
113
- - etlplus.config.pagination.PaginationConfig.from_obj: parses pagination
114
- - etlplus.api.rate_limiting.RateLimitConfig.from_obj: parses rate_limit
115
- """
116
-
117
- headers: StrAnyMap
118
- pagination: PaginationConfigMap | StrAnyMap
119
- rate_limit: RateLimitConfigMap | StrAnyMap
120
-
121
-
122
- class EndpointMap(TypedDict, total=False):
123
- """
124
- Shape accepted by EndpointConfig.from_obj.
125
-
126
- One of 'path' or 'url' should be provided.
127
-
128
- See Also
129
- --------
130
- - etlplus.config.api.EndpointConfig.from_obj: parses this mapping
131
- """
132
-
133
- path: str
134
- url: str
135
- method: str
136
- path_params: StrAnyMap
137
- query_params: StrAnyMap
138
- body: Any
139
- pagination: PaginationConfigMap
140
- rate_limit: RateLimitConfigMap
etlplus/dag.py DELETED
@@ -1,103 +0,0 @@
1
- """
2
- :mod:`etlplus.dag` module.
3
-
4
- Lightweight directed acyclic graph (DAG) helpers for ordering jobs based on
5
- ``depends_on``.
6
- """
7
-
8
- from __future__ import annotations
9
-
10
- from collections import deque
11
- from dataclasses import dataclass
12
-
13
- from .config.jobs import JobConfig
14
-
15
- # SECTION: EXPORTS ========================================================== #
16
-
17
-
18
- __all__ = [
19
- 'DagError',
20
- 'topological_sort_jobs',
21
- ]
22
-
23
-
24
- # SECTION: ERRORS =========================================================== #
25
-
26
-
27
- @dataclass(slots=True)
28
- class DagError(ValueError):
29
- """
30
- Raised when the job dependency graph is invalid.
31
-
32
- Attributes
33
- ----------
34
- message : str
35
- Error message.
36
- """
37
-
38
- # -- Attributes -- #
39
-
40
- message: str
41
-
42
- # -- Magic Methods (Object Representation) -- #
43
-
44
- def __str__(self) -> str:
45
- return self.message
46
-
47
-
48
- # SECTION: FUNCTIONS ======================================================== #
49
-
50
-
51
- def topological_sort_jobs(
52
- jobs: list[JobConfig],
53
- ) -> list[JobConfig]:
54
- """
55
- Return jobs in topological order based on ``depends_on``.
56
-
57
- Parameters
58
- ----------
59
- jobs : list[JobConfig]
60
- List of job configurations to sort.
61
-
62
- Returns
63
- -------
64
- list[JobConfig]
65
- Jobs sorted in topological order.
66
-
67
- Raises
68
- ------
69
- DagError
70
- If a dependency is missing, self-referential, or when a cycle is
71
- detected.
72
- """
73
- index = {job.name: job for job in jobs}
74
- edges: dict[str, set[str]] = {name: set() for name in index}
75
- indegree: dict[str, int] = {name: 0 for name in index}
76
-
77
- for job in jobs:
78
- for dep in job.depends_on:
79
- if dep not in index:
80
- raise DagError(
81
- f'Unknown dependency "{dep}" in job "{job.name}"',
82
- )
83
- if dep == job.name:
84
- raise DagError(f'Job "{job.name}" depends on itself')
85
- if job.name not in edges[dep]:
86
- edges[dep].add(job.name)
87
- indegree[job.name] += 1
88
-
89
- queue = deque(sorted(name for name, deg in indegree.items() if deg == 0))
90
- ordered: list[str] = []
91
-
92
- while queue:
93
- name = queue.popleft()
94
- ordered.append(name)
95
- for child in sorted(edges[name]):
96
- indegree[child] -= 1
97
- if indegree[child] == 0:
98
- queue.append(child)
99
-
100
- if len(ordered) != len(jobs):
101
- raise DagError('Dependency cycle detected')
102
-
103
- return [index[name] for name in ordered]