etlplus 0.10.4__py3-none-any.whl → 0.11.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/cli/commands.py +1 -1
- etlplus/cli/constants.py +1 -1
- etlplus/cli/io.py +2 -2
- etlplus/config/pipeline.py +2 -2
- etlplus/enums.py +2 -240
- etlplus/extract.py +2 -2
- etlplus/file/__init__.py +27 -0
- etlplus/file/core.py +287 -0
- etlplus/file/csv.py +82 -0
- etlplus/file/enums.py +266 -0
- etlplus/file/json.py +87 -0
- etlplus/file/xml.py +165 -0
- etlplus/file/yaml.py +125 -0
- etlplus/load.py +4 -4
- {etlplus-0.10.4.dist-info → etlplus-0.11.1.dist-info}/METADATA +1 -1
- {etlplus-0.10.4.dist-info → etlplus-0.11.1.dist-info}/RECORD +20 -14
- etlplus/file.py +0 -652
- {etlplus-0.10.4.dist-info → etlplus-0.11.1.dist-info}/WHEEL +0 -0
- {etlplus-0.10.4.dist-info → etlplus-0.11.1.dist-info}/entry_points.txt +0 -0
- {etlplus-0.10.4.dist-info → etlplus-0.11.1.dist-info}/licenses/LICENSE +0 -0
- {etlplus-0.10.4.dist-info → etlplus-0.11.1.dist-info}/top_level.txt +0 -0
etlplus/file/yaml.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""
|
|
2
|
+
:mod:`etlplus.file.yaml` module.
|
|
3
|
+
|
|
4
|
+
Optional YAML read/write helpers.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
11
|
+
from typing import cast
|
|
12
|
+
|
|
13
|
+
from ..types import JSONData
|
|
14
|
+
from ..types import JSONDict
|
|
15
|
+
from ..types import JSONList
|
|
16
|
+
from ..utils import count_records
|
|
17
|
+
|
|
18
|
+
# SECTION: INTERNAL CONSTANTS =============================================== #
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Optional YAML support (lazy-loaded to avoid hard dependency)
|
|
22
|
+
# Cached access function to avoid global statements.
|
|
23
|
+
_YAML_CACHE: dict[str, Any] = {}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# SECTION: INTERNAL FUNCTIONS =============================================== #
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _get_yaml() -> Any:
|
|
30
|
+
"""
|
|
31
|
+
Return the PyYAML module, importing it on first use.
|
|
32
|
+
|
|
33
|
+
Raises an informative ImportError if the optional dependency is missing.
|
|
34
|
+
"""
|
|
35
|
+
mod = _YAML_CACHE.get('mod')
|
|
36
|
+
if mod is not None: # pragma: no cover - tiny branch
|
|
37
|
+
return mod
|
|
38
|
+
try:
|
|
39
|
+
_yaml_mod = __import__('yaml') # type: ignore[assignment]
|
|
40
|
+
except ImportError as e: # pragma: no cover
|
|
41
|
+
raise ImportError(
|
|
42
|
+
'YAML support requires optional dependency "PyYAML".\n'
|
|
43
|
+
'Install with: pip install PyYAML',
|
|
44
|
+
) from e
|
|
45
|
+
_YAML_CACHE['mod'] = _yaml_mod
|
|
46
|
+
|
|
47
|
+
return _yaml_mod
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _require_yaml() -> None:
|
|
51
|
+
"""Ensure PyYAML is available or raise an informative error."""
|
|
52
|
+
_get_yaml()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# SECTION: FUNCTIONS ======================================================== #
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def read(
|
|
59
|
+
path: Path,
|
|
60
|
+
) -> JSONData:
|
|
61
|
+
"""
|
|
62
|
+
Load and validate YAML payloads from ``path``.
|
|
63
|
+
|
|
64
|
+
Parameters
|
|
65
|
+
----------
|
|
66
|
+
path : Path
|
|
67
|
+
Path to the YAML file on disk.
|
|
68
|
+
|
|
69
|
+
Returns
|
|
70
|
+
-------
|
|
71
|
+
JSONData
|
|
72
|
+
The structured data read from the YAML file.
|
|
73
|
+
|
|
74
|
+
Raises
|
|
75
|
+
------
|
|
76
|
+
TypeError
|
|
77
|
+
If the YAML root is not an object or an array of objects.
|
|
78
|
+
"""
|
|
79
|
+
_require_yaml()
|
|
80
|
+
|
|
81
|
+
with path.open('r', encoding='utf-8') as handle:
|
|
82
|
+
loaded = _get_yaml().safe_load(handle)
|
|
83
|
+
|
|
84
|
+
if isinstance(loaded, dict):
|
|
85
|
+
return cast(JSONDict, loaded)
|
|
86
|
+
if isinstance(loaded, list):
|
|
87
|
+
if all(isinstance(item, dict) for item in loaded):
|
|
88
|
+
return cast(JSONList, loaded)
|
|
89
|
+
raise TypeError(
|
|
90
|
+
'YAML array must contain only objects (dicts) when loading',
|
|
91
|
+
)
|
|
92
|
+
raise TypeError(
|
|
93
|
+
'YAML root must be an object or an array of objects when loading',
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def write(
|
|
98
|
+
path: Path,
|
|
99
|
+
data: JSONData,
|
|
100
|
+
) -> int:
|
|
101
|
+
"""
|
|
102
|
+
Write ``data`` as YAML to ``path`` and return record count.
|
|
103
|
+
|
|
104
|
+
Parameters
|
|
105
|
+
----------
|
|
106
|
+
path : Path
|
|
107
|
+
Path to the YAML file on disk.
|
|
108
|
+
data : JSONData
|
|
109
|
+
Data to write as YAML.
|
|
110
|
+
|
|
111
|
+
Returns
|
|
112
|
+
-------
|
|
113
|
+
int
|
|
114
|
+
The number of records written.
|
|
115
|
+
"""
|
|
116
|
+
_require_yaml()
|
|
117
|
+
with path.open('w', encoding='utf-8') as handle:
|
|
118
|
+
_get_yaml().safe_dump(
|
|
119
|
+
data,
|
|
120
|
+
handle,
|
|
121
|
+
sort_keys=False,
|
|
122
|
+
allow_unicode=True,
|
|
123
|
+
default_flow_style=False,
|
|
124
|
+
)
|
|
125
|
+
return count_records(data)
|
etlplus/load.py
CHANGED
|
@@ -15,12 +15,12 @@ from typing import cast
|
|
|
15
15
|
import requests # type: ignore[import]
|
|
16
16
|
|
|
17
17
|
from .enums import DataConnectorType
|
|
18
|
-
from .enums import FileFormat
|
|
19
18
|
from .enums import HttpMethod
|
|
20
19
|
from .enums import coerce_data_connector_type
|
|
21
|
-
from .enums import coerce_file_format
|
|
22
20
|
from .enums import coerce_http_method
|
|
23
21
|
from .file import File
|
|
22
|
+
from .file import FileFormat
|
|
23
|
+
from .file import coerce_file_format
|
|
24
24
|
from .types import JSONData
|
|
25
25
|
from .types import JSONDict
|
|
26
26
|
from .types import JSONList
|
|
@@ -101,7 +101,7 @@ def load_data(
|
|
|
101
101
|
return cast(JSONData, source)
|
|
102
102
|
|
|
103
103
|
if isinstance(source, Path):
|
|
104
|
-
return File(source, FileFormat.JSON).
|
|
104
|
+
return File(source, FileFormat.JSON).read()
|
|
105
105
|
|
|
106
106
|
if isinstance(source, str):
|
|
107
107
|
# Special case: '-' means read JSON from STDIN (Unix convention).
|
|
@@ -111,7 +111,7 @@ def load_data(
|
|
|
111
111
|
candidate = Path(source)
|
|
112
112
|
if candidate.exists():
|
|
113
113
|
try:
|
|
114
|
-
return File(candidate, FileFormat.JSON).
|
|
114
|
+
return File(candidate, FileFormat.JSON).read()
|
|
115
115
|
except (OSError, json.JSONDecodeError, ValueError):
|
|
116
116
|
# Fall back to treating the string as raw JSON content.
|
|
117
117
|
pass
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
etlplus/__init__.py,sha256=M2gScnyir6WOMAh_EuoQIiAzdcTls0_5hbd_Q6of8I0,1021
|
|
2
2
|
etlplus/__main__.py,sha256=btoROneNiigyfBU7BSzPKZ1R9gzBMpxcpsbPwmuHwTM,479
|
|
3
3
|
etlplus/__version__.py,sha256=1E0GMK_yUWCMQFKxXjTvyMwofi0qT2k4CDNiHWiymWE,327
|
|
4
|
-
etlplus/enums.py,sha256=
|
|
5
|
-
etlplus/extract.py,sha256=
|
|
6
|
-
etlplus/
|
|
7
|
-
etlplus/load.py,sha256=R_y0_vtsEo1bwxWVQu2bfhB5ZIJoIoWu2ycCdvY4RnE,8737
|
|
4
|
+
etlplus/enums.py,sha256=r_KhmzIY-PSjfKfkz8uQtb_B5clfeTokRwqXreSWlNI,9150
|
|
5
|
+
etlplus/extract.py,sha256=V1cifAktXW4BWyoaaJPgR6R_pAG6TPKq-XOzxb49_ic,6171
|
|
6
|
+
etlplus/load.py,sha256=t9VfEuWyVq7MukuaLWrjj7oGyi6VDnXkiBXGPNAh1zE,8725
|
|
8
7
|
etlplus/mixins.py,sha256=ifGpHwWv7U00yqGf-kN93vJax2IiK4jaGtTsPsO3Oak,1350
|
|
9
8
|
etlplus/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
9
|
etlplus/run.py,sha256=X4kp5FQlIWVf1_d9oSrchKau7BFDCE1Zkscvu7WPaWw,12340
|
|
@@ -31,10 +30,10 @@ etlplus/api/rate_limiting/__init__.py,sha256=ZySB1dZettEDnWvI1EHf_TZ9L08M_kKsNR-
|
|
|
31
30
|
etlplus/api/rate_limiting/config.py,sha256=2b4wIynblN-1EyMqI4aXa71SljzSjXYh5N1Nngr3jOg,9406
|
|
32
31
|
etlplus/api/rate_limiting/rate_limiter.py,sha256=Uxozqd_Ej5Lsj-M-mLT2WexChgWh7x35_YP10yqYPQA,7159
|
|
33
32
|
etlplus/cli/__init__.py,sha256=J97-Rv931IL1_b4AXnB7Fbbd7HKnHBpx18NQfC_kE6c,299
|
|
34
|
-
etlplus/cli/commands.py,sha256=
|
|
35
|
-
etlplus/cli/constants.py,sha256=
|
|
33
|
+
etlplus/cli/commands.py,sha256=g8_m3A8HEMyTRu2HctNiRoi2gtB5oSZCUEcyq-PIXos,24669
|
|
34
|
+
etlplus/cli/constants.py,sha256=E6Uy4WauLa_0zkzxqImXh-bb1gKdb9sBZQVc8QOzr2Q,1943
|
|
36
35
|
etlplus/cli/handlers.py,sha256=K0GazvrPgocJ-63HZqF0xhyJk8TB1Gcj-eIbWltXKRU,17759
|
|
37
|
-
etlplus/cli/io.py,sha256=
|
|
36
|
+
etlplus/cli/io.py,sha256=EFaBPYaBOyOllfMQWXgTjy-MPiGfNejicpD7ROrPyAE,7840
|
|
38
37
|
etlplus/cli/main.py,sha256=IgeqxypixfwLHR-QcpgVMQ7vMZ865bXOh2oO9v-BWeM,5234
|
|
39
38
|
etlplus/cli/options.py,sha256=vfXT3YLh7wG1iC-aTdSg6ItMC8l6n0Lozmy53XjqLbA,1199
|
|
40
39
|
etlplus/cli/state.py,sha256=Pfd8ru0wYIN7eGp1_A0tioqs1LiCDZCuJ6AnjZb6yYQ,8027
|
|
@@ -42,7 +41,7 @@ etlplus/cli/types.py,sha256=tclhKVJXDqHzlTQBYKARfqMgDOcuBJ-Zej2pvFy96WM,652
|
|
|
42
41
|
etlplus/config/__init__.py,sha256=VZWzOg7d2YR9NT6UwKTv44yf2FRUMjTHynkm1Dl5Qzo,1486
|
|
43
42
|
etlplus/config/connector.py,sha256=0-TIwevHbKRHVmucvyGpPd-3tB1dKHB-dj0yJ6kq5eY,9809
|
|
44
43
|
etlplus/config/jobs.py,sha256=hmzRCqt0OvCEZZR4ONKrd3lvSv0OmayjLc4yOBk3ug8,7399
|
|
45
|
-
etlplus/config/pipeline.py,sha256=
|
|
44
|
+
etlplus/config/pipeline.py,sha256=m4Jh0ctFcKrIx6zR7LEC0sYY5wq0o8NqOruWPlz6qmA,9494
|
|
46
45
|
etlplus/config/profile.py,sha256=Ss2zedQGjkaGSpvBLTD4SZaWViMJ7TJPLB8Q2_BTpPg,1898
|
|
47
46
|
etlplus/config/types.py,sha256=a0epJ3z16HQ5bY3Ctf8s_cQPa3f0HHcwdOcjCP2xoG4,4954
|
|
48
47
|
etlplus/config/utils.py,sha256=4SUHMkt5bKBhMhiJm-DrnmE2Q4TfOgdNCKz8PJDS27o,3443
|
|
@@ -52,14 +51,21 @@ etlplus/database/engine.py,sha256=7rr7YndA8LwyWJL8k1YhQbqxxmW4gWEUQjp0NwQcYtc,40
|
|
|
52
51
|
etlplus/database/orm.py,sha256=gCSqH-CjQz6tV9133-VqgiwokK5ylun0BwXaIWfImAo,10008
|
|
53
52
|
etlplus/database/schema.py,sha256=HNTgglI8qvQLInr7gq--2lLmLKHzAZTL2MJUOIw9DlY,7025
|
|
54
53
|
etlplus/database/types.py,sha256=_pkQyC14TzAlgyeIqZG4F5LWYknZbHw3TW68Auk7Ya0,795
|
|
54
|
+
etlplus/file/__init__.py,sha256=xd_Tvtzx7_PrGVb4Cjqp-v8p3P2qTPA3cZ14VzA1-0g,539
|
|
55
|
+
etlplus/file/core.py,sha256=NXTGSIKIo7HvLDlMtme37_d4NUhsf4RUNKp5mTj-wqU,8131
|
|
56
|
+
etlplus/file/csv.py,sha256=VbMW_NaqCw03HlfvYzb9MoAgCXI3cl9qc4dASkTHoyw,1880
|
|
57
|
+
etlplus/file/enums.py,sha256=NjgXQ0f53Xa1eyGKHvYkk58udjQI5TQfLVaoawfdXY0,7520
|
|
58
|
+
etlplus/file/json.py,sha256=xSV5PkZ_tZQuZNdLr1FQUwuCQXyL7Ch3WRJ3hkw0p68,1911
|
|
59
|
+
etlplus/file/xml.py,sha256=vjate5u9Z26LPlpvZsdzpqXsIUZRgen7oHa3ly-aIhs,3905
|
|
60
|
+
etlplus/file/yaml.py,sha256=6KaWoG7oYB26EHX2TZ7LOgigO11Hoq3MH--adFq_Eck,3004
|
|
55
61
|
etlplus/templates/__init__.py,sha256=tsniN7XJYs3NwYxJ6c2HD5upHP3CDkLx-bQCMt97UOM,106
|
|
56
62
|
etlplus/templates/ddl.sql.j2,sha256=s8fMWvcb4eaJVXkifuib1aQPljtZ8buuyB_uA-ZdU3Q,4734
|
|
57
63
|
etlplus/templates/view.sql.j2,sha256=Iy8DHfhq5yyvrUKDxqp_aHIEXY4Tm6j4wT7YDEFWAhk,2180
|
|
58
64
|
etlplus/validation/__init__.py,sha256=Pe5Xg1_EA4uiNZGYu5WTF3j7odjmyxnAJ8rcioaplSQ,1254
|
|
59
65
|
etlplus/validation/utils.py,sha256=Mtqg449VIke0ziy_wd2r6yrwJzQkA1iulZC87FzXMjo,10201
|
|
60
|
-
etlplus-0.
|
|
61
|
-
etlplus-0.
|
|
62
|
-
etlplus-0.
|
|
63
|
-
etlplus-0.
|
|
64
|
-
etlplus-0.
|
|
65
|
-
etlplus-0.
|
|
66
|
+
etlplus-0.11.1.dist-info/licenses/LICENSE,sha256=MuNO63i6kWmgnV2pbP2SLqP54mk1BGmu7CmbtxMmT-U,1069
|
|
67
|
+
etlplus-0.11.1.dist-info/METADATA,sha256=zIJEvlxTNB13talthWkdGrbTWBNQZB8rkEn3Zwc9888,21036
|
|
68
|
+
etlplus-0.11.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
69
|
+
etlplus-0.11.1.dist-info/entry_points.txt,sha256=6w-2-jzuPa55spzK34h-UKh2JTEShh38adFRONNP9QE,45
|
|
70
|
+
etlplus-0.11.1.dist-info/top_level.txt,sha256=aWWF-udn_sLGuHTM6W6MLh99ArS9ROkUWO8Mi8y1_2U,8
|
|
71
|
+
etlplus-0.11.1.dist-info/RECORD,,
|