etlplus 0.9.2__py3-none-any.whl → 0.10.1__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/__init__.py +26 -1
- etlplus/api/README.md +3 -51
- etlplus/api/__init__.py +0 -10
- etlplus/api/config.py +28 -39
- etlplus/api/endpoint_client.py +3 -3
- etlplus/api/pagination/client.py +1 -1
- etlplus/api/rate_limiting/config.py +1 -13
- etlplus/api/rate_limiting/rate_limiter.py +11 -8
- etlplus/api/request_manager.py +6 -11
- etlplus/api/transport.py +2 -14
- etlplus/api/types.py +6 -96
- etlplus/cli/commands.py +43 -76
- etlplus/cli/constants.py +1 -1
- etlplus/cli/handlers.py +12 -40
- etlplus/cli/io.py +2 -2
- etlplus/cli/main.py +1 -1
- etlplus/cli/state.py +7 -4
- etlplus/{workflow → config}/__init__.py +23 -10
- etlplus/{workflow → config}/connector.py +44 -58
- etlplus/{workflow → config}/jobs.py +32 -105
- etlplus/{workflow → config}/pipeline.py +51 -59
- etlplus/{workflow → config}/profile.py +5 -8
- etlplus/config/types.py +204 -0
- etlplus/config/utils.py +120 -0
- etlplus/database/ddl.py +1 -1
- etlplus/database/engine.py +3 -19
- etlplus/database/orm.py +0 -2
- etlplus/database/schema.py +1 -1
- etlplus/enums.py +266 -0
- etlplus/{ops/extract.py → extract.py} +99 -81
- etlplus/file.py +652 -0
- etlplus/{ops/load.py → load.py} +101 -78
- etlplus/{ops/run.py → run.py} +127 -159
- etlplus/{api/utils.py → run_helpers.py} +153 -209
- etlplus/{ops/transform.py → transform.py} +68 -75
- etlplus/types.py +4 -5
- etlplus/utils.py +2 -136
- etlplus/{ops/validate.py → validate.py} +12 -22
- etlplus/validation/__init__.py +44 -0
- etlplus/{ops → validation}/utils.py +17 -53
- {etlplus-0.9.2.dist-info → etlplus-0.10.1.dist-info}/METADATA +17 -210
- etlplus-0.10.1.dist-info/RECORD +65 -0
- {etlplus-0.9.2.dist-info → etlplus-0.10.1.dist-info}/WHEEL +1 -1
- etlplus/README.md +0 -37
- etlplus/api/enums.py +0 -51
- etlplus/cli/README.md +0 -40
- etlplus/database/README.md +0 -48
- etlplus/file/README.md +0 -105
- etlplus/file/__init__.py +0 -25
- etlplus/file/_imports.py +0 -141
- etlplus/file/_io.py +0 -160
- etlplus/file/accdb.py +0 -78
- etlplus/file/arrow.py +0 -78
- etlplus/file/avro.py +0 -176
- etlplus/file/bson.py +0 -77
- etlplus/file/cbor.py +0 -78
- etlplus/file/cfg.py +0 -79
- etlplus/file/conf.py +0 -80
- etlplus/file/core.py +0 -322
- etlplus/file/csv.py +0 -79
- etlplus/file/dat.py +0 -78
- etlplus/file/dta.py +0 -77
- etlplus/file/duckdb.py +0 -78
- etlplus/file/enums.py +0 -343
- etlplus/file/feather.py +0 -111
- etlplus/file/fwf.py +0 -77
- etlplus/file/gz.py +0 -123
- etlplus/file/hbs.py +0 -78
- etlplus/file/hdf5.py +0 -78
- etlplus/file/ini.py +0 -79
- etlplus/file/ion.py +0 -78
- etlplus/file/jinja2.py +0 -78
- etlplus/file/json.py +0 -98
- etlplus/file/log.py +0 -78
- etlplus/file/mat.py +0 -78
- etlplus/file/mdb.py +0 -78
- etlplus/file/msgpack.py +0 -78
- etlplus/file/mustache.py +0 -78
- etlplus/file/nc.py +0 -78
- etlplus/file/ndjson.py +0 -108
- etlplus/file/numbers.py +0 -75
- etlplus/file/ods.py +0 -79
- etlplus/file/orc.py +0 -111
- etlplus/file/parquet.py +0 -113
- etlplus/file/pb.py +0 -78
- etlplus/file/pbf.py +0 -77
- etlplus/file/properties.py +0 -78
- etlplus/file/proto.py +0 -77
- etlplus/file/psv.py +0 -79
- etlplus/file/rda.py +0 -78
- etlplus/file/rds.py +0 -78
- etlplus/file/sas7bdat.py +0 -78
- etlplus/file/sav.py +0 -77
- etlplus/file/sqlite.py +0 -78
- etlplus/file/stub.py +0 -84
- etlplus/file/sylk.py +0 -77
- etlplus/file/tab.py +0 -81
- etlplus/file/toml.py +0 -78
- etlplus/file/tsv.py +0 -80
- etlplus/file/txt.py +0 -102
- etlplus/file/vm.py +0 -78
- etlplus/file/wks.py +0 -77
- etlplus/file/xls.py +0 -88
- etlplus/file/xlsm.py +0 -79
- etlplus/file/xlsx.py +0 -99
- etlplus/file/xml.py +0 -185
- etlplus/file/xpt.py +0 -78
- etlplus/file/yaml.py +0 -95
- etlplus/file/zip.py +0 -175
- etlplus/file/zsav.py +0 -77
- etlplus/ops/README.md +0 -50
- etlplus/ops/__init__.py +0 -61
- etlplus/templates/README.md +0 -46
- etlplus/workflow/README.md +0 -52
- etlplus/workflow/dag.py +0 -105
- etlplus/workflow/types.py +0 -115
- etlplus-0.9.2.dist-info/RECORD +0 -134
- {etlplus-0.9.2.dist-info → etlplus-0.10.1.dist-info}/entry_points.txt +0 -0
- {etlplus-0.9.2.dist-info → etlplus-0.10.1.dist-info}/licenses/LICENSE +0 -0
- {etlplus-0.9.2.dist-info → etlplus-0.10.1.dist-info}/top_level.txt +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"""
|
|
2
|
-
:mod:`etlplus.
|
|
2
|
+
:mod:`etlplus.config.jobs` module.
|
|
3
3
|
|
|
4
4
|
Data classes modeling job orchestration references (extract, validate,
|
|
5
5
|
transform, load).
|
|
6
6
|
|
|
7
7
|
Notes
|
|
8
8
|
-----
|
|
9
|
-
- Lightweight references used inside
|
|
9
|
+
- Lightweight references used inside ``PipelineConfig`` to avoid storing
|
|
10
10
|
large nested structures.
|
|
11
11
|
- All attributes are simple and optional where appropriate, keeping parsing
|
|
12
12
|
tolerant.
|
|
@@ -19,7 +19,6 @@ from dataclasses import field
|
|
|
19
19
|
from typing import Any
|
|
20
20
|
from typing import Self
|
|
21
21
|
|
|
22
|
-
from ..types import StrAnyMap
|
|
23
22
|
from ..utils import coerce_dict
|
|
24
23
|
from ..utils import maybe_mapping
|
|
25
24
|
|
|
@@ -27,7 +26,6 @@ from ..utils import maybe_mapping
|
|
|
27
26
|
|
|
28
27
|
|
|
29
28
|
__all__ = [
|
|
30
|
-
# Data Classes
|
|
31
29
|
'ExtractRef',
|
|
32
30
|
'JobConfig',
|
|
33
31
|
'LoadRef',
|
|
@@ -36,76 +34,10 @@ __all__ = [
|
|
|
36
34
|
]
|
|
37
35
|
|
|
38
36
|
|
|
39
|
-
# SECTION:
|
|
37
|
+
# SECTION: TYPE ALIASES ===================================================== #
|
|
40
38
|
|
|
41
39
|
|
|
42
|
-
|
|
43
|
-
"""
|
|
44
|
-
Normalize optional string values, coercing non-strings when needed.
|
|
45
|
-
|
|
46
|
-
Parameters
|
|
47
|
-
----------
|
|
48
|
-
value : Any
|
|
49
|
-
Optional value to normalize.
|
|
50
|
-
|
|
51
|
-
Returns
|
|
52
|
-
-------
|
|
53
|
-
str | None
|
|
54
|
-
``None`` when ``value`` is ``None``; otherwise a string value.
|
|
55
|
-
"""
|
|
56
|
-
if value is None:
|
|
57
|
-
return None
|
|
58
|
-
return value if isinstance(value, str) else str(value)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def _parse_depends_on(
|
|
62
|
-
value: Any,
|
|
63
|
-
) -> list[str]:
|
|
64
|
-
"""
|
|
65
|
-
Normalize dependency declarations into a string list.
|
|
66
|
-
|
|
67
|
-
Parameters
|
|
68
|
-
----------
|
|
69
|
-
value : Any
|
|
70
|
-
Input dependency specification (string or list of strings).
|
|
71
|
-
|
|
72
|
-
Returns
|
|
73
|
-
-------
|
|
74
|
-
list[str]
|
|
75
|
-
Normalized dependency list.
|
|
76
|
-
"""
|
|
77
|
-
if isinstance(value, str):
|
|
78
|
-
return [value]
|
|
79
|
-
if isinstance(value, list):
|
|
80
|
-
return [entry for entry in value if isinstance(entry, str)]
|
|
81
|
-
return []
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def _require_str(
|
|
85
|
-
# data: dict[str, Any],
|
|
86
|
-
data: StrAnyMap,
|
|
87
|
-
key: str,
|
|
88
|
-
) -> str | None:
|
|
89
|
-
"""
|
|
90
|
-
Extract a required string field from a mapping.
|
|
91
|
-
|
|
92
|
-
Parameters
|
|
93
|
-
----------
|
|
94
|
-
data : StrAnyMap
|
|
95
|
-
Mapping containing the target field.
|
|
96
|
-
key : str
|
|
97
|
-
Field name to extract.
|
|
98
|
-
|
|
99
|
-
Returns
|
|
100
|
-
-------
|
|
101
|
-
str | None
|
|
102
|
-
The string value when present and valid; otherwise ``None``.
|
|
103
|
-
"""
|
|
104
|
-
value = data.get(key)
|
|
105
|
-
return value if isinstance(value, str) else None
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
# SECTION: DATA CLASSES ===================================================== #
|
|
40
|
+
# SECTION: CLASSES ========================================================== #
|
|
109
41
|
|
|
110
42
|
|
|
111
43
|
@dataclass(kw_only=True, slots=True)
|
|
@@ -133,13 +65,12 @@ class ExtractRef:
|
|
|
133
65
|
cls,
|
|
134
66
|
obj: Any,
|
|
135
67
|
) -> Self | None:
|
|
136
|
-
"""
|
|
137
|
-
Parse a mapping into an :class:`ExtractRef` instance.
|
|
68
|
+
"""Parse a mapping into an :class:`ExtractRef` instance.
|
|
138
69
|
|
|
139
70
|
Parameters
|
|
140
71
|
----------
|
|
141
72
|
obj : Any
|
|
142
|
-
Mapping with
|
|
73
|
+
Mapping with ``source`` and optional ``options``.
|
|
143
74
|
|
|
144
75
|
Returns
|
|
145
76
|
-------
|
|
@@ -149,8 +80,8 @@ class ExtractRef:
|
|
|
149
80
|
data = maybe_mapping(obj)
|
|
150
81
|
if not data:
|
|
151
82
|
return None
|
|
152
|
-
source =
|
|
153
|
-
if source
|
|
83
|
+
source = data.get('source')
|
|
84
|
+
if not isinstance(source, str):
|
|
154
85
|
return None
|
|
155
86
|
return cls(
|
|
156
87
|
source=source,
|
|
@@ -169,8 +100,6 @@ class JobConfig:
|
|
|
169
100
|
Unique job name.
|
|
170
101
|
description : str | None
|
|
171
102
|
Optional human-friendly description.
|
|
172
|
-
depends_on : list[str]
|
|
173
|
-
Optional job dependency list. Dependencies must refer to other jobs.
|
|
174
103
|
extract : ExtractRef | None
|
|
175
104
|
Extraction reference.
|
|
176
105
|
validate : ValidationRef | None
|
|
@@ -185,7 +114,6 @@ class JobConfig:
|
|
|
185
114
|
|
|
186
115
|
name: str
|
|
187
116
|
description: str | None = None
|
|
188
|
-
depends_on: list[str] = field(default_factory=list)
|
|
189
117
|
extract: ExtractRef | None = None
|
|
190
118
|
validate: ValidationRef | None = None
|
|
191
119
|
transform: TransformRef | None = None
|
|
@@ -198,8 +126,7 @@ class JobConfig:
|
|
|
198
126
|
cls,
|
|
199
127
|
obj: Any,
|
|
200
128
|
) -> Self | None:
|
|
201
|
-
"""
|
|
202
|
-
Parse a mapping into a :class:`JobConfig` instance.
|
|
129
|
+
"""Parse a mapping into a :class:`JobConfig` instance.
|
|
203
130
|
|
|
204
131
|
Parameters
|
|
205
132
|
----------
|
|
@@ -214,18 +141,17 @@ class JobConfig:
|
|
|
214
141
|
data = maybe_mapping(obj)
|
|
215
142
|
if not data:
|
|
216
143
|
return None
|
|
217
|
-
name =
|
|
218
|
-
if name
|
|
144
|
+
name = data.get('name')
|
|
145
|
+
if not isinstance(name, str):
|
|
219
146
|
return None
|
|
220
147
|
|
|
221
|
-
description =
|
|
222
|
-
|
|
223
|
-
|
|
148
|
+
description = data.get('description')
|
|
149
|
+
if description is not None and not isinstance(description, str):
|
|
150
|
+
description = str(description)
|
|
224
151
|
|
|
225
152
|
return cls(
|
|
226
153
|
name=name,
|
|
227
154
|
description=description,
|
|
228
|
-
depends_on=depends_on,
|
|
229
155
|
extract=ExtractRef.from_obj(data.get('extract')),
|
|
230
156
|
validate=ValidationRef.from_obj(data.get('validate')),
|
|
231
157
|
transform=TransformRef.from_obj(data.get('transform')),
|
|
@@ -258,13 +184,12 @@ class LoadRef:
|
|
|
258
184
|
cls,
|
|
259
185
|
obj: Any,
|
|
260
186
|
) -> Self | None:
|
|
261
|
-
"""
|
|
262
|
-
Parse a mapping into a :class:`LoadRef` instance.
|
|
187
|
+
"""Parse a mapping into a :class:`LoadRef` instance.
|
|
263
188
|
|
|
264
189
|
Parameters
|
|
265
190
|
----------
|
|
266
191
|
obj : Any
|
|
267
|
-
Mapping with
|
|
192
|
+
Mapping with ``target`` and optional ``overrides``.
|
|
268
193
|
|
|
269
194
|
Returns
|
|
270
195
|
-------
|
|
@@ -274,8 +199,8 @@ class LoadRef:
|
|
|
274
199
|
data = maybe_mapping(obj)
|
|
275
200
|
if not data:
|
|
276
201
|
return None
|
|
277
|
-
target =
|
|
278
|
-
if target
|
|
202
|
+
target = data.get('target')
|
|
203
|
+
if not isinstance(target, str):
|
|
279
204
|
return None
|
|
280
205
|
return cls(
|
|
281
206
|
target=target,
|
|
@@ -305,13 +230,12 @@ class TransformRef:
|
|
|
305
230
|
cls,
|
|
306
231
|
obj: Any,
|
|
307
232
|
) -> Self | None:
|
|
308
|
-
"""
|
|
309
|
-
Parse a mapping into a :class:`TransformRef` instance.
|
|
233
|
+
"""Parse a mapping into a :class:`TransformRef` instance.
|
|
310
234
|
|
|
311
235
|
Parameters
|
|
312
236
|
----------
|
|
313
237
|
obj : Any
|
|
314
|
-
Mapping with
|
|
238
|
+
Mapping with ``pipeline``.
|
|
315
239
|
|
|
316
240
|
Returns
|
|
317
241
|
-------
|
|
@@ -321,8 +245,8 @@ class TransformRef:
|
|
|
321
245
|
data = maybe_mapping(obj)
|
|
322
246
|
if not data:
|
|
323
247
|
return None
|
|
324
|
-
pipeline =
|
|
325
|
-
if pipeline
|
|
248
|
+
pipeline = data.get('pipeline')
|
|
249
|
+
if not isinstance(pipeline, str):
|
|
326
250
|
return None
|
|
327
251
|
return cls(pipeline=pipeline)
|
|
328
252
|
|
|
@@ -356,13 +280,12 @@ class ValidationRef:
|
|
|
356
280
|
cls,
|
|
357
281
|
obj: Any,
|
|
358
282
|
) -> Self | None:
|
|
359
|
-
"""
|
|
360
|
-
Parse a mapping into a :class:`ValidationRef` instance.
|
|
283
|
+
"""Parse a mapping into a :class:`ValidationRef` instance.
|
|
361
284
|
|
|
362
285
|
Parameters
|
|
363
286
|
----------
|
|
364
287
|
obj : Any
|
|
365
|
-
Mapping with
|
|
288
|
+
Mapping with ``ruleset`` plus optional metadata.
|
|
366
289
|
|
|
367
290
|
Returns
|
|
368
291
|
-------
|
|
@@ -372,11 +295,15 @@ class ValidationRef:
|
|
|
372
295
|
data = maybe_mapping(obj)
|
|
373
296
|
if not data:
|
|
374
297
|
return None
|
|
375
|
-
ruleset =
|
|
376
|
-
if ruleset
|
|
298
|
+
ruleset = data.get('ruleset')
|
|
299
|
+
if not isinstance(ruleset, str):
|
|
377
300
|
return None
|
|
378
|
-
severity =
|
|
379
|
-
|
|
301
|
+
severity = data.get('severity')
|
|
302
|
+
if severity is not None and not isinstance(severity, str):
|
|
303
|
+
severity = str(severity)
|
|
304
|
+
phase = data.get('phase')
|
|
305
|
+
if phase is not None and not isinstance(phase, str):
|
|
306
|
+
phase = str(phase)
|
|
380
307
|
return cls(
|
|
381
308
|
ruleset=ruleset,
|
|
382
309
|
severity=severity,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
:mod:`etlplus.
|
|
2
|
+
:mod:`etlplus.config.pipeline` module.
|
|
3
3
|
|
|
4
4
|
Pipeline configuration model and helpers for job orchestration.
|
|
5
5
|
|
|
@@ -16,7 +16,6 @@ Notes
|
|
|
16
16
|
from __future__ import annotations
|
|
17
17
|
|
|
18
18
|
import os
|
|
19
|
-
from collections.abc import Callable
|
|
20
19
|
from collections.abc import Mapping
|
|
21
20
|
from dataclasses import dataclass
|
|
22
21
|
from dataclasses import field
|
|
@@ -25,90 +24,72 @@ from typing import Any
|
|
|
25
24
|
from typing import Self
|
|
26
25
|
|
|
27
26
|
from ..api import ApiConfig
|
|
27
|
+
from ..enums import FileFormat
|
|
28
28
|
from ..file import File
|
|
29
|
-
from ..file import FileFormat
|
|
30
29
|
from ..types import StrAnyMap
|
|
31
30
|
from ..utils import coerce_dict
|
|
32
|
-
from ..utils import deep_substitute
|
|
33
31
|
from ..utils import maybe_mapping
|
|
34
32
|
from .connector import Connector
|
|
35
33
|
from .connector import parse_connector
|
|
36
34
|
from .jobs import JobConfig
|
|
37
35
|
from .profile import ProfileConfig
|
|
36
|
+
from .utils import deep_substitute
|
|
38
37
|
|
|
39
38
|
# SECTION: EXPORTS ========================================================== #
|
|
40
39
|
|
|
41
40
|
|
|
42
|
-
__all__ = [
|
|
43
|
-
# Data Classes
|
|
44
|
-
'PipelineConfig',
|
|
45
|
-
# Functions
|
|
46
|
-
'load_pipeline_config',
|
|
47
|
-
]
|
|
41
|
+
__all__ = ['PipelineConfig', 'load_pipeline_config']
|
|
48
42
|
|
|
49
43
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def _collect_parsed[T](
|
|
44
|
+
def _build_jobs(
|
|
54
45
|
raw: StrAnyMap,
|
|
55
|
-
|
|
56
|
-
parser: Callable[[Any], T | None],
|
|
57
|
-
) -> list[T]:
|
|
46
|
+
) -> list[JobConfig]:
|
|
58
47
|
"""
|
|
59
|
-
|
|
48
|
+
Return a list of ``JobConfig`` objects parsed from the mapping.
|
|
60
49
|
|
|
61
50
|
Parameters
|
|
62
51
|
----------
|
|
63
52
|
raw : StrAnyMap
|
|
64
53
|
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.
|
|
69
54
|
|
|
70
55
|
Returns
|
|
71
56
|
-------
|
|
72
|
-
list[
|
|
73
|
-
Parsed
|
|
57
|
+
list[JobConfig]
|
|
58
|
+
Parsed job configurations.
|
|
74
59
|
"""
|
|
75
|
-
|
|
76
|
-
for
|
|
77
|
-
|
|
78
|
-
if
|
|
79
|
-
|
|
80
|
-
|
|
60
|
+
jobs: list[JobConfig] = []
|
|
61
|
+
for job_raw in raw.get('jobs', []) or []:
|
|
62
|
+
job_cfg = JobConfig.from_obj(job_raw)
|
|
63
|
+
if job_cfg is not None:
|
|
64
|
+
jobs.append(job_cfg)
|
|
65
|
+
|
|
66
|
+
return jobs
|
|
81
67
|
|
|
82
68
|
|
|
83
|
-
def
|
|
84
|
-
|
|
85
|
-
) -> Connector
|
|
69
|
+
def _build_sources(
|
|
70
|
+
raw: StrAnyMap,
|
|
71
|
+
) -> list[Connector]:
|
|
86
72
|
"""
|
|
87
|
-
|
|
73
|
+
Return a list of source connectors parsed from the mapping.
|
|
88
74
|
|
|
89
75
|
Parameters
|
|
90
76
|
----------
|
|
91
|
-
|
|
92
|
-
|
|
77
|
+
raw : StrAnyMap
|
|
78
|
+
Raw pipeline mapping.
|
|
93
79
|
|
|
94
80
|
Returns
|
|
95
81
|
-------
|
|
96
|
-
Connector
|
|
97
|
-
Parsed
|
|
82
|
+
list[Connector]
|
|
83
|
+
Parsed source connectors.
|
|
98
84
|
"""
|
|
99
|
-
|
|
100
|
-
return None
|
|
101
|
-
try:
|
|
102
|
-
return parse_connector(entry)
|
|
103
|
-
except TypeError:
|
|
104
|
-
return None
|
|
85
|
+
return _build_connectors(raw, 'sources')
|
|
105
86
|
|
|
106
87
|
|
|
107
|
-
def
|
|
88
|
+
def _build_targets(
|
|
108
89
|
raw: StrAnyMap,
|
|
109
90
|
) -> list[Connector]:
|
|
110
91
|
"""
|
|
111
|
-
Return a list of
|
|
92
|
+
Return a list of target connectors parsed from the mapping.
|
|
112
93
|
|
|
113
94
|
Parameters
|
|
114
95
|
----------
|
|
@@ -118,32 +99,43 @@ def _build_sources(
|
|
|
118
99
|
Returns
|
|
119
100
|
-------
|
|
120
101
|
list[Connector]
|
|
121
|
-
Parsed
|
|
102
|
+
Parsed target connectors.
|
|
122
103
|
"""
|
|
123
|
-
return
|
|
124
|
-
_collect_parsed(raw, 'sources', _parse_connector_entry),
|
|
125
|
-
)
|
|
104
|
+
return _build_connectors(raw, 'targets')
|
|
126
105
|
|
|
127
106
|
|
|
128
|
-
def
|
|
107
|
+
def _build_connectors(
|
|
129
108
|
raw: StrAnyMap,
|
|
109
|
+
key: str,
|
|
130
110
|
) -> list[Connector]:
|
|
131
111
|
"""
|
|
132
|
-
Return
|
|
112
|
+
Return parsed connectors from ``raw[key]`` using tolerant parsing.
|
|
113
|
+
|
|
114
|
+
Unknown or malformed entries are skipped to preserve permissiveness.
|
|
133
115
|
|
|
134
116
|
Parameters
|
|
135
117
|
----------
|
|
136
118
|
raw : StrAnyMap
|
|
137
119
|
Raw pipeline mapping.
|
|
120
|
+
key : str
|
|
121
|
+
List-containing top-level key ("sources" or "targets").
|
|
138
122
|
|
|
139
123
|
Returns
|
|
140
124
|
-------
|
|
141
125
|
list[Connector]
|
|
142
|
-
|
|
126
|
+
Constructed connector instances (malformed entries skipped).
|
|
143
127
|
"""
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
128
|
+
items: list[Connector] = []
|
|
129
|
+
for obj in raw.get(key, []) or []:
|
|
130
|
+
if not (entry := maybe_mapping(obj)):
|
|
131
|
+
continue
|
|
132
|
+
try:
|
|
133
|
+
items.append(parse_connector(entry))
|
|
134
|
+
except TypeError:
|
|
135
|
+
# Skip unsupported types or malformed entries
|
|
136
|
+
continue
|
|
137
|
+
|
|
138
|
+
return items
|
|
147
139
|
|
|
148
140
|
|
|
149
141
|
# SECTION: FUNCTIONS ======================================================== #
|
|
@@ -164,7 +156,7 @@ def load_pipeline_config(
|
|
|
164
156
|
return PipelineConfig.from_yaml(path, substitute=substitute, env=env)
|
|
165
157
|
|
|
166
158
|
|
|
167
|
-
# SECTION:
|
|
159
|
+
# SECTION: CLASSES ========================================================== #
|
|
168
160
|
|
|
169
161
|
|
|
170
162
|
@dataclass(kw_only=True, slots=True)
|
|
@@ -254,7 +246,7 @@ class PipelineConfig:
|
|
|
254
246
|
TypeError
|
|
255
247
|
If the YAML root is not a mapping/object.
|
|
256
248
|
"""
|
|
257
|
-
raw = File(Path(path), FileFormat.YAML).
|
|
249
|
+
raw = File(Path(path), FileFormat.YAML).read_yaml()
|
|
258
250
|
if not isinstance(raw, dict):
|
|
259
251
|
raise TypeError('Pipeline YAML must have a mapping/object root')
|
|
260
252
|
|
|
@@ -321,7 +313,7 @@ class PipelineConfig:
|
|
|
321
313
|
targets = _build_targets(raw)
|
|
322
314
|
|
|
323
315
|
# Jobs
|
|
324
|
-
jobs =
|
|
316
|
+
jobs = _build_jobs(raw)
|
|
325
317
|
|
|
326
318
|
# Table schemas (optional, tolerant pass-through structures).
|
|
327
319
|
table_schemas: list[dict[str, Any]] = []
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
:mod:`etlplus.
|
|
2
|
+
:mod:`etlplus.config.profile` module.
|
|
3
3
|
|
|
4
4
|
Profile model for pipeline-level defaults and environment.
|
|
5
5
|
|
|
@@ -22,13 +22,10 @@ from ..utils import cast_str_dict
|
|
|
22
22
|
# SECTION: EXPORTS ========================================================== #
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
__all__ = [
|
|
26
|
-
# Data Classes
|
|
27
|
-
'ProfileConfig',
|
|
28
|
-
]
|
|
25
|
+
__all__ = ['ProfileConfig']
|
|
29
26
|
|
|
30
27
|
|
|
31
|
-
# SECTION:
|
|
28
|
+
# SECTION: CLASSES ========================================================== #
|
|
32
29
|
|
|
33
30
|
|
|
34
31
|
@dataclass(kw_only=True, slots=True)
|
|
@@ -56,7 +53,7 @@ class ProfileConfig:
|
|
|
56
53
|
cls,
|
|
57
54
|
obj: StrAnyMap | None,
|
|
58
55
|
) -> Self:
|
|
59
|
-
"""Parse a mapping into a
|
|
56
|
+
"""Parse a mapping into a ``ProfileConfig`` instance.
|
|
60
57
|
|
|
61
58
|
Parameters
|
|
62
59
|
----------
|
|
@@ -67,7 +64,7 @@ class ProfileConfig:
|
|
|
67
64
|
-------
|
|
68
65
|
Self
|
|
69
66
|
Parsed profile configuration; non-mapping input yields a default
|
|
70
|
-
instance. All
|
|
67
|
+
instance. All ``env`` values are coerced to strings.
|
|
71
68
|
"""
|
|
72
69
|
if not isinstance(obj, Mapping):
|
|
73
70
|
return cls()
|