etlplus 0.11.7__py3-none-any.whl → 0.11.9__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/handlers.py +1 -1
- etlplus/database/ddl.py +1 -1
- etlplus/file/core.py +49 -104
- {etlplus-0.11.7.dist-info → etlplus-0.11.9.dist-info}/METADATA +1 -1
- {etlplus-0.11.7.dist-info → etlplus-0.11.9.dist-info}/RECORD +9 -9
- {etlplus-0.11.7.dist-info → etlplus-0.11.9.dist-info}/WHEEL +0 -0
- {etlplus-0.11.7.dist-info → etlplus-0.11.9.dist-info}/entry_points.txt +0 -0
- {etlplus-0.11.7.dist-info → etlplus-0.11.9.dist-info}/licenses/LICENSE +0 -0
- {etlplus-0.11.7.dist-info → etlplus-0.11.9.dist-info}/top_level.txt +0 -0
etlplus/cli/handlers.py
CHANGED
|
@@ -570,7 +570,7 @@ def transform_handler(
|
|
|
570
570
|
data = transform(payload, cast(TransformOperations, operations_payload))
|
|
571
571
|
|
|
572
572
|
if target and target != '-':
|
|
573
|
-
File
|
|
573
|
+
File(target, file_format=target_format).write(data)
|
|
574
574
|
print(f'Data transformed and saved to {target}')
|
|
575
575
|
return 0
|
|
576
576
|
|
etlplus/database/ddl.py
CHANGED
|
@@ -203,7 +203,7 @@ def load_table_spec(
|
|
|
203
203
|
raise ValueError('Spec must be .json, .yml, or .yaml')
|
|
204
204
|
|
|
205
205
|
try:
|
|
206
|
-
spec = File
|
|
206
|
+
spec = File(spec_path).read()
|
|
207
207
|
except ImportError as e:
|
|
208
208
|
if suffix in {'.yml', '.yaml'}:
|
|
209
209
|
raise RuntimeError(
|
etlplus/file/core.py
CHANGED
|
@@ -11,7 +11,6 @@ from dataclasses import dataclass
|
|
|
11
11
|
from pathlib import Path
|
|
12
12
|
|
|
13
13
|
from ..types import JSONData
|
|
14
|
-
from ..types import StrPath
|
|
15
14
|
from . import csv
|
|
16
15
|
from . import json
|
|
17
16
|
from . import xml
|
|
@@ -43,7 +42,15 @@ class File:
|
|
|
43
42
|
Path to the file on disk.
|
|
44
43
|
file_format : FileFormat | None, optional
|
|
45
44
|
Explicit format. If omitted, the format is inferred from the file
|
|
46
|
-
extension (``.csv``, ``.json``,
|
|
45
|
+
extension (``.csv``, ``.json``, etc.).
|
|
46
|
+
|
|
47
|
+
Parameters
|
|
48
|
+
----------
|
|
49
|
+
path : StrPath
|
|
50
|
+
Path to the file on disk.
|
|
51
|
+
file_format : FileFormat | str | None, optional
|
|
52
|
+
Explicit format. If omitted, the format is inferred from the file
|
|
53
|
+
extension (``.csv``, ``.json``, etc.).
|
|
47
54
|
"""
|
|
48
55
|
|
|
49
56
|
# -- Attributes -- #
|
|
@@ -62,16 +69,10 @@ class File:
|
|
|
62
69
|
extension is unknown, the attribute is left as ``None`` and will be
|
|
63
70
|
validated later by :meth:`_ensure_format`.
|
|
64
71
|
"""
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
self.path = Path(self.path)
|
|
68
|
-
|
|
72
|
+
self.path = Path(self.path)
|
|
73
|
+
self.file_format = self._coerce_format(self.file_format)
|
|
69
74
|
if self.file_format is None:
|
|
70
|
-
|
|
71
|
-
self.file_format = self._guess_format()
|
|
72
|
-
except ValueError:
|
|
73
|
-
# Leave as None; _ensure_format() will raise on use if needed.
|
|
74
|
-
pass
|
|
75
|
+
self.file_format = self._maybe_guess_format()
|
|
75
76
|
|
|
76
77
|
# -- Internal Instance Methods -- #
|
|
77
78
|
|
|
@@ -84,6 +85,28 @@ class File:
|
|
|
84
85
|
if not self.path.exists():
|
|
85
86
|
raise FileNotFoundError(f'File not found: {self.path}')
|
|
86
87
|
|
|
88
|
+
def _coerce_format(
|
|
89
|
+
self,
|
|
90
|
+
file_format: FileFormat | str | None,
|
|
91
|
+
) -> FileFormat | None:
|
|
92
|
+
"""
|
|
93
|
+
Normalize the file format input.
|
|
94
|
+
|
|
95
|
+
Parameters
|
|
96
|
+
----------
|
|
97
|
+
file_format : FileFormat | str | None
|
|
98
|
+
File format specifier. Strings are coerced into
|
|
99
|
+
:class:`FileFormat`.
|
|
100
|
+
|
|
101
|
+
Returns
|
|
102
|
+
-------
|
|
103
|
+
FileFormat | None
|
|
104
|
+
A normalized file format, or ``None`` when unspecified.
|
|
105
|
+
"""
|
|
106
|
+
if file_format is None or isinstance(file_format, FileFormat):
|
|
107
|
+
return file_format
|
|
108
|
+
return FileFormat.coerce(file_format)
|
|
109
|
+
|
|
87
110
|
def _ensure_format(self) -> FileFormat:
|
|
88
111
|
"""
|
|
89
112
|
Resolve the active format, guessing from extension if needed.
|
|
@@ -125,6 +148,21 @@ class File:
|
|
|
125
148
|
f'Cannot infer file format from extension {self.path.suffix!r}',
|
|
126
149
|
)
|
|
127
150
|
|
|
151
|
+
def _maybe_guess_format(self) -> FileFormat | None:
|
|
152
|
+
"""
|
|
153
|
+
Try to infer the format, returning ``None`` if it cannot be inferred.
|
|
154
|
+
|
|
155
|
+
Returns
|
|
156
|
+
-------
|
|
157
|
+
FileFormat | None
|
|
158
|
+
The inferred format, or ``None`` if inference fails.
|
|
159
|
+
"""
|
|
160
|
+
try:
|
|
161
|
+
return self._guess_format()
|
|
162
|
+
except ValueError:
|
|
163
|
+
# Leave as None; _ensure_format() will raise on use if needed.
|
|
164
|
+
return None
|
|
165
|
+
|
|
128
166
|
# -- Instance Methods -- #
|
|
129
167
|
|
|
130
168
|
def read(self) -> JSONData:
|
|
@@ -192,96 +230,3 @@ class File:
|
|
|
192
230
|
case FileFormat.YAML:
|
|
193
231
|
return yaml.write(self.path, data)
|
|
194
232
|
raise ValueError(f'Unsupported format: {fmt}')
|
|
195
|
-
|
|
196
|
-
# -- Class Methods -- #
|
|
197
|
-
|
|
198
|
-
@classmethod
|
|
199
|
-
def from_path(
|
|
200
|
-
cls,
|
|
201
|
-
path: StrPath,
|
|
202
|
-
*,
|
|
203
|
-
file_format: FileFormat | str | None = None,
|
|
204
|
-
) -> File:
|
|
205
|
-
"""
|
|
206
|
-
Create a :class:`File` from any path-like and optional format.
|
|
207
|
-
|
|
208
|
-
Parameters
|
|
209
|
-
----------
|
|
210
|
-
path : StrPath
|
|
211
|
-
Path to the file on disk.
|
|
212
|
-
file_format : FileFormat | str | None, optional
|
|
213
|
-
Explicit format. If omitted, the format is inferred from the file
|
|
214
|
-
extension (``.csv``, ``.json``, or ``.xml``).
|
|
215
|
-
|
|
216
|
-
Returns
|
|
217
|
-
-------
|
|
218
|
-
File
|
|
219
|
-
The constructed :class:`File` instance.
|
|
220
|
-
"""
|
|
221
|
-
resolved = Path(path)
|
|
222
|
-
ff: FileFormat | None
|
|
223
|
-
if isinstance(file_format, str):
|
|
224
|
-
ff = FileFormat.coerce(file_format)
|
|
225
|
-
else:
|
|
226
|
-
ff = file_format
|
|
227
|
-
|
|
228
|
-
return cls(resolved, ff)
|
|
229
|
-
|
|
230
|
-
@classmethod
|
|
231
|
-
def read_file(
|
|
232
|
-
cls,
|
|
233
|
-
path: StrPath,
|
|
234
|
-
file_format: FileFormat | str | None = None,
|
|
235
|
-
) -> JSONData:
|
|
236
|
-
"""
|
|
237
|
-
Read structured data.
|
|
238
|
-
|
|
239
|
-
Parameters
|
|
240
|
-
----------
|
|
241
|
-
path : StrPath
|
|
242
|
-
Path to the file on disk.
|
|
243
|
-
file_format : FileFormat | str | None, optional
|
|
244
|
-
Explicit format. If omitted, the format is inferred from the file
|
|
245
|
-
extension (``.csv``, ``.json``, or ``.xml``).
|
|
246
|
-
|
|
247
|
-
Returns
|
|
248
|
-
-------
|
|
249
|
-
JSONData
|
|
250
|
-
The structured data read from the file.
|
|
251
|
-
"""
|
|
252
|
-
return cls.from_path(path, file_format=file_format).read()
|
|
253
|
-
|
|
254
|
-
@classmethod
|
|
255
|
-
def write_file(
|
|
256
|
-
cls,
|
|
257
|
-
path: StrPath,
|
|
258
|
-
data: JSONData,
|
|
259
|
-
file_format: FileFormat | str | None = None,
|
|
260
|
-
*,
|
|
261
|
-
root_tag: str = xml.DEFAULT_XML_ROOT,
|
|
262
|
-
) -> int:
|
|
263
|
-
"""
|
|
264
|
-
Write structured data and count written records.
|
|
265
|
-
|
|
266
|
-
Parameters
|
|
267
|
-
----------
|
|
268
|
-
path : StrPath
|
|
269
|
-
Path to the file on disk.
|
|
270
|
-
data : JSONData
|
|
271
|
-
Data to write to the file.
|
|
272
|
-
file_format : FileFormat | str | None, optional
|
|
273
|
-
Explicit format. If omitted, the format is inferred from the file
|
|
274
|
-
extension (``.csv``, ``.json``, or ``.xml``).
|
|
275
|
-
root_tag : str, optional
|
|
276
|
-
Root tag name to use when writing XML files. Defaults to
|
|
277
|
-
``'root'``.
|
|
278
|
-
|
|
279
|
-
Returns
|
|
280
|
-
-------
|
|
281
|
-
int
|
|
282
|
-
The number of records written to the file.
|
|
283
|
-
"""
|
|
284
|
-
return cls.from_path(path, file_format=file_format).write(
|
|
285
|
-
data,
|
|
286
|
-
root_tag=root_tag,
|
|
287
|
-
)
|
|
@@ -32,7 +32,7 @@ etlplus/api/rate_limiting/rate_limiter.py,sha256=Uxozqd_Ej5Lsj-M-mLT2WexChgWh7x3
|
|
|
32
32
|
etlplus/cli/__init__.py,sha256=J97-Rv931IL1_b4AXnB7Fbbd7HKnHBpx18NQfC_kE6c,299
|
|
33
33
|
etlplus/cli/commands.py,sha256=g8_m3A8HEMyTRu2HctNiRoi2gtB5oSZCUEcyq-PIXos,24669
|
|
34
34
|
etlplus/cli/constants.py,sha256=E6Uy4WauLa_0zkzxqImXh-bb1gKdb9sBZQVc8QOzr2Q,1943
|
|
35
|
-
etlplus/cli/handlers.py,sha256=
|
|
35
|
+
etlplus/cli/handlers.py,sha256=FvnYWKRg4u7iCcIvAJtpjDEbqw2DHj3G34NXJbMwnHk,17754
|
|
36
36
|
etlplus/cli/io.py,sha256=EFaBPYaBOyOllfMQWXgTjy-MPiGfNejicpD7ROrPyAE,7840
|
|
37
37
|
etlplus/cli/main.py,sha256=IgeqxypixfwLHR-QcpgVMQ7vMZ865bXOh2oO9v-BWeM,5234
|
|
38
38
|
etlplus/cli/options.py,sha256=vfXT3YLh7wG1iC-aTdSg6ItMC8l6n0Lozmy53XjqLbA,1199
|
|
@@ -46,13 +46,13 @@ etlplus/config/profile.py,sha256=Ss2zedQGjkaGSpvBLTD4SZaWViMJ7TJPLB8Q2_BTpPg,189
|
|
|
46
46
|
etlplus/config/types.py,sha256=a0epJ3z16HQ5bY3Ctf8s_cQPa3f0HHcwdOcjCP2xoG4,4954
|
|
47
47
|
etlplus/config/utils.py,sha256=4SUHMkt5bKBhMhiJm-DrnmE2Q4TfOgdNCKz8PJDS27o,3443
|
|
48
48
|
etlplus/database/__init__.py,sha256=AKJsDl2RHuRGPS-eXgNJeh4aSncJP5Y0yLApBF6i7i8,1052
|
|
49
|
-
etlplus/database/ddl.py,sha256=
|
|
49
|
+
etlplus/database/ddl.py,sha256=0dEM9SJMMabkhI_h-Fc0j9a1Sl5lSyZdI0bIeBVGm10,7913
|
|
50
50
|
etlplus/database/engine.py,sha256=PUxXLvLPyc-KaxuW7fXe12wYci7EvUp-Ad1H3bGhUog,4058
|
|
51
51
|
etlplus/database/orm.py,sha256=gCSqH-CjQz6tV9133-VqgiwokK5ylun0BwXaIWfImAo,10008
|
|
52
52
|
etlplus/database/schema.py,sha256=813C0Dd3WE53KTYot4dgjAxctgKXLXx-8_Rk_4r2e28,7022
|
|
53
53
|
etlplus/database/types.py,sha256=_pkQyC14TzAlgyeIqZG4F5LWYknZbHw3TW68Auk7Ya0,795
|
|
54
54
|
etlplus/file/__init__.py,sha256=X03bosSM-uSd6dh3ur0un6_ozFRw2Tm4PE6kVUjtXK8,475
|
|
55
|
-
etlplus/file/core.py,sha256=
|
|
55
|
+
etlplus/file/core.py,sha256=oMEzvs2RKtE95TX-HWz__PavjHwlhcTk-D9Jti99AXU,6675
|
|
56
56
|
etlplus/file/csv.py,sha256=VbMW_NaqCw03HlfvYzb9MoAgCXI3cl9qc4dASkTHoyw,1880
|
|
57
57
|
etlplus/file/enums.py,sha256=rwrbwj6PejG0c5v6jzcsmeNu9cSqDyWB1foIuM5UyJo,6648
|
|
58
58
|
etlplus/file/json.py,sha256=xSV5PkZ_tZQuZNdLr1FQUwuCQXyL7Ch3WRJ3hkw0p68,1911
|
|
@@ -63,9 +63,9 @@ etlplus/templates/ddl.sql.j2,sha256=s8fMWvcb4eaJVXkifuib1aQPljtZ8buuyB_uA-ZdU3Q,
|
|
|
63
63
|
etlplus/templates/view.sql.j2,sha256=Iy8DHfhq5yyvrUKDxqp_aHIEXY4Tm6j4wT7YDEFWAhk,2180
|
|
64
64
|
etlplus/validation/__init__.py,sha256=Pe5Xg1_EA4uiNZGYu5WTF3j7odjmyxnAJ8rcioaplSQ,1254
|
|
65
65
|
etlplus/validation/utils.py,sha256=Mtqg449VIke0ziy_wd2r6yrwJzQkA1iulZC87FzXMjo,10201
|
|
66
|
-
etlplus-0.11.
|
|
67
|
-
etlplus-0.11.
|
|
68
|
-
etlplus-0.11.
|
|
69
|
-
etlplus-0.11.
|
|
70
|
-
etlplus-0.11.
|
|
71
|
-
etlplus-0.11.
|
|
66
|
+
etlplus-0.11.9.dist-info/licenses/LICENSE,sha256=MuNO63i6kWmgnV2pbP2SLqP54mk1BGmu7CmbtxMmT-U,1069
|
|
67
|
+
etlplus-0.11.9.dist-info/METADATA,sha256=G8vVH8Z8HJYymyRoBpcpm8SY4o7PkGkgjQsu2q_d_8M,21036
|
|
68
|
+
etlplus-0.11.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
69
|
+
etlplus-0.11.9.dist-info/entry_points.txt,sha256=6w-2-jzuPa55spzK34h-UKh2JTEShh38adFRONNP9QE,45
|
|
70
|
+
etlplus-0.11.9.dist-info/top_level.txt,sha256=aWWF-udn_sLGuHTM6W6MLh99ArS9ROkUWO8Mi8y1_2U,8
|
|
71
|
+
etlplus-0.11.9.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|