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.
- etlplus/README.md +3 -3
- etlplus/api/README.md +31 -0
- etlplus/api/config.py +3 -8
- etlplus/api/types.py +89 -0
- etlplus/cli/commands.py +75 -42
- etlplus/cli/handlers.py +30 -12
- etlplus/cli/main.py +1 -1
- etlplus/cli/state.py +4 -7
- etlplus/database/engine.py +18 -2
- etlplus/database/orm.py +2 -0
- etlplus/file/_io.py +39 -0
- etlplus/file/json.py +2 -14
- etlplus/file/yaml.py +2 -14
- etlplus/ops/utils.py +4 -33
- etlplus/ops/validate.py +3 -3
- etlplus/types.py +3 -2
- etlplus/utils.py +136 -2
- etlplus/workflow/connector.py +41 -28
- etlplus/workflow/jobs.py +84 -27
- etlplus/workflow/pipeline.py +46 -46
- etlplus/workflow/types.py +1 -1
- {etlplus-0.15.0.dist-info → etlplus-0.15.4.dist-info}/METADATA +4 -4
- {etlplus-0.15.0.dist-info → etlplus-0.15.4.dist-info}/RECORD +27 -32
- etlplus/config/README.md +0 -50
- etlplus/config/__init__.py +0 -33
- etlplus/config/types.py +0 -140
- etlplus/dag.py +0 -103
- etlplus/workflow/utils.py +0 -120
- {etlplus-0.15.0.dist-info → etlplus-0.15.4.dist-info}/WHEEL +0 -0
- {etlplus-0.15.0.dist-info → etlplus-0.15.4.dist-info}/entry_points.txt +0 -0
- {etlplus-0.15.0.dist-info → etlplus-0.15.4.dist-info}/licenses/LICENSE +0 -0
- {etlplus-0.15.0.dist-info → etlplus-0.15.4.dist-info}/top_level.txt +0 -0
etlplus/workflow/pipeline.py
CHANGED
|
@@ -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
|
|
53
|
+
def _collect_parsed[T](
|
|
53
54
|
raw: StrAnyMap,
|
|
54
|
-
|
|
55
|
+
key: str,
|
|
56
|
+
parser: Callable[[Any], T | None],
|
|
57
|
+
) -> list[T]:
|
|
55
58
|
"""
|
|
56
|
-
|
|
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[
|
|
66
|
-
Parsed
|
|
72
|
+
list[T]
|
|
73
|
+
Parsed items, excluding invalid entries.
|
|
67
74
|
"""
|
|
68
|
-
|
|
69
|
-
for
|
|
70
|
-
|
|
71
|
-
if
|
|
72
|
-
|
|
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
|
|
78
|
-
|
|
79
|
-
) ->
|
|
83
|
+
def _parse_connector_entry(
|
|
84
|
+
obj: Any,
|
|
85
|
+
) -> Connector | None:
|
|
80
86
|
"""
|
|
81
|
-
|
|
87
|
+
Parse a connector mapping into a concrete connector instance.
|
|
82
88
|
|
|
83
89
|
Parameters
|
|
84
90
|
----------
|
|
85
|
-
|
|
86
|
-
|
|
91
|
+
obj : Any
|
|
92
|
+
Candidate connector mapping.
|
|
87
93
|
|
|
88
94
|
Returns
|
|
89
95
|
-------
|
|
90
|
-
|
|
91
|
-
Parsed
|
|
96
|
+
Connector | None
|
|
97
|
+
Parsed connector instance or ``None`` when invalid.
|
|
92
98
|
"""
|
|
93
|
-
|
|
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
|
|
107
|
+
def _build_sources(
|
|
97
108
|
raw: StrAnyMap,
|
|
98
109
|
) -> list[Connector]:
|
|
99
110
|
"""
|
|
100
|
-
Return a list of
|
|
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
|
|
121
|
+
Parsed source connectors.
|
|
111
122
|
"""
|
|
112
|
-
return
|
|
123
|
+
return list(
|
|
124
|
+
_collect_parsed(raw, 'sources', _parse_connector_entry),
|
|
125
|
+
)
|
|
113
126
|
|
|
114
127
|
|
|
115
|
-
def
|
|
128
|
+
def _build_targets(
|
|
116
129
|
raw: StrAnyMap,
|
|
117
|
-
key: str,
|
|
118
130
|
) -> list[Connector]:
|
|
119
131
|
"""
|
|
120
|
-
Return
|
|
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
|
-
|
|
142
|
+
Parsed target connectors.
|
|
135
143
|
"""
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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 =
|
|
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.
|
|
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.
|
|
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.
|
|
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=
|
|
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
|
|
10
|
-
etlplus/utils.py,sha256=
|
|
11
|
-
etlplus/api/README.md,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
33
|
+
etlplus/cli/handlers.py,sha256=hXsgG3c6n8JDN4U3Svq6i2r21vetw5NcmXE1bGSwb9w,18560
|
|
35
34
|
etlplus/cli/io.py,sha256=EFaBPYaBOyOllfMQWXgTjy-MPiGfNejicpD7ROrPyAE,7840
|
|
36
|
-
etlplus/cli/main.py,sha256=
|
|
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=
|
|
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=
|
|
47
|
-
etlplus/database/orm.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
120
|
-
etlplus/ops/validate.py,sha256
|
|
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=
|
|
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=
|
|
130
|
-
etlplus/workflow/pipeline.py,sha256=
|
|
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=
|
|
133
|
-
etlplus/
|
|
134
|
-
etlplus-0.15.
|
|
135
|
-
etlplus-0.15.
|
|
136
|
-
etlplus-0.15.
|
|
137
|
-
etlplus-0.15.
|
|
138
|
-
etlplus-0.15.
|
|
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)
|
etlplus/config/__init__.py
DELETED
|
@@ -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]
|