etlplus 0.15.0__py3-none-any.whl → 0.16.6__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 +25 -3
- etlplus/__init__.py +2 -0
- etlplus/api/README.md +31 -0
- etlplus/api/__init__.py +14 -14
- etlplus/api/auth.py +10 -7
- etlplus/api/config.py +8 -13
- etlplus/api/endpoint_client.py +20 -20
- etlplus/api/errors.py +4 -4
- etlplus/api/pagination/__init__.py +6 -6
- etlplus/api/pagination/config.py +12 -10
- etlplus/api/pagination/paginator.py +6 -7
- etlplus/api/rate_limiting/__init__.py +2 -2
- etlplus/api/rate_limiting/config.py +14 -14
- etlplus/api/rate_limiting/rate_limiter.py +3 -3
- etlplus/api/request_manager.py +4 -4
- etlplus/api/retry_manager.py +8 -8
- etlplus/api/transport.py +11 -11
- etlplus/api/types.py +131 -11
- etlplus/api/utils.py +50 -50
- etlplus/cli/commands.py +93 -60
- etlplus/cli/constants.py +1 -1
- etlplus/cli/handlers.py +43 -26
- etlplus/cli/io.py +2 -2
- etlplus/cli/main.py +2 -2
- etlplus/cli/state.py +4 -7
- etlplus/{workflow/pipeline.py → config.py} +62 -99
- etlplus/connector/__init__.py +43 -0
- etlplus/connector/api.py +161 -0
- etlplus/connector/connector.py +26 -0
- etlplus/connector/core.py +132 -0
- etlplus/connector/database.py +122 -0
- etlplus/connector/enums.py +52 -0
- etlplus/connector/file.py +120 -0
- etlplus/connector/types.py +40 -0
- etlplus/connector/utils.py +122 -0
- etlplus/database/ddl.py +2 -2
- etlplus/database/engine.py +19 -3
- etlplus/database/orm.py +2 -0
- etlplus/enums.py +36 -200
- etlplus/file/_imports.py +1 -0
- etlplus/file/_io.py +52 -4
- etlplus/file/accdb.py +3 -2
- etlplus/file/arrow.py +3 -2
- etlplus/file/avro.py +3 -2
- etlplus/file/bson.py +3 -2
- etlplus/file/cbor.py +3 -2
- etlplus/file/cfg.py +3 -2
- etlplus/file/conf.py +3 -2
- etlplus/file/core.py +11 -8
- etlplus/file/csv.py +3 -2
- etlplus/file/dat.py +3 -2
- etlplus/file/dta.py +3 -2
- etlplus/file/duckdb.py +3 -2
- etlplus/file/enums.py +1 -1
- etlplus/file/feather.py +3 -2
- etlplus/file/fwf.py +3 -2
- etlplus/file/gz.py +3 -2
- etlplus/file/hbs.py +3 -2
- etlplus/file/hdf5.py +3 -2
- etlplus/file/ini.py +3 -2
- etlplus/file/ion.py +3 -2
- etlplus/file/jinja2.py +3 -2
- etlplus/file/json.py +5 -16
- etlplus/file/log.py +3 -2
- etlplus/file/mat.py +3 -2
- etlplus/file/mdb.py +3 -2
- etlplus/file/msgpack.py +3 -2
- etlplus/file/mustache.py +3 -2
- etlplus/file/nc.py +3 -2
- etlplus/file/ndjson.py +3 -2
- etlplus/file/numbers.py +3 -2
- etlplus/file/ods.py +3 -2
- etlplus/file/orc.py +3 -2
- etlplus/file/parquet.py +3 -2
- etlplus/file/pb.py +3 -2
- etlplus/file/pbf.py +3 -2
- etlplus/file/properties.py +3 -2
- etlplus/file/proto.py +3 -2
- etlplus/file/psv.py +3 -2
- etlplus/file/rda.py +3 -2
- etlplus/file/rds.py +3 -2
- etlplus/file/sas7bdat.py +3 -2
- etlplus/file/sav.py +3 -2
- etlplus/file/sqlite.py +3 -2
- etlplus/file/stub.py +1 -0
- etlplus/file/sylk.py +3 -2
- etlplus/file/tab.py +3 -2
- etlplus/file/toml.py +3 -2
- etlplus/file/tsv.py +3 -2
- etlplus/file/txt.py +4 -3
- etlplus/file/vm.py +3 -2
- etlplus/file/wks.py +3 -2
- etlplus/file/xls.py +3 -2
- etlplus/file/xlsm.py +3 -2
- etlplus/file/xlsx.py +3 -2
- etlplus/file/xml.py +9 -3
- etlplus/file/xpt.py +3 -2
- etlplus/file/yaml.py +5 -16
- etlplus/file/zip.py +3 -2
- etlplus/file/zsav.py +3 -2
- etlplus/ops/__init__.py +1 -0
- etlplus/ops/enums.py +173 -0
- etlplus/ops/extract.py +222 -23
- etlplus/ops/load.py +155 -36
- etlplus/ops/run.py +92 -107
- etlplus/ops/transform.py +48 -29
- etlplus/ops/types.py +147 -0
- etlplus/ops/utils.py +11 -40
- etlplus/ops/validate.py +16 -16
- etlplus/types.py +6 -102
- etlplus/utils.py +163 -29
- etlplus/workflow/README.md +0 -24
- etlplus/workflow/__init__.py +2 -15
- etlplus/workflow/dag.py +23 -1
- etlplus/workflow/jobs.py +83 -39
- etlplus/workflow/profile.py +4 -2
- {etlplus-0.15.0.dist-info → etlplus-0.16.6.dist-info}/METADATA +4 -4
- etlplus-0.16.6.dist-info/RECORD +143 -0
- {etlplus-0.15.0.dist-info → etlplus-0.16.6.dist-info}/WHEEL +1 -1
- 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/connector.py +0 -373
- etlplus/workflow/types.py +0 -115
- etlplus/workflow/utils.py +0 -120
- etlplus-0.15.0.dist-info/RECORD +0 -139
- {etlplus-0.15.0.dist-info → etlplus-0.16.6.dist-info}/entry_points.txt +0 -0
- {etlplus-0.15.0.dist-info → etlplus-0.16.6.dist-info}/licenses/LICENSE +0 -0
- {etlplus-0.15.0.dist-info → etlplus-0.16.6.dist-info}/top_level.txt +0 -0
etlplus/enums.py
CHANGED
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
"""
|
|
2
2
|
:mod:`etlplus.enums` module.
|
|
3
3
|
|
|
4
|
-
Shared enumeration
|
|
4
|
+
Shared enumeration base class.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
9
|
import enum
|
|
10
|
-
import operator as _op
|
|
11
|
-
from statistics import fmean
|
|
12
10
|
from typing import Self
|
|
13
11
|
|
|
14
|
-
from .types import AggregateFunc
|
|
15
|
-
from .types import OperatorFunc
|
|
16
12
|
from .types import StrStrMap
|
|
17
13
|
|
|
18
14
|
# SECTION: EXPORTS ========================================================== #
|
|
@@ -20,11 +16,7 @@ from .types import StrStrMap
|
|
|
20
16
|
|
|
21
17
|
__all__ = [
|
|
22
18
|
# Enums
|
|
23
|
-
'AggregateName',
|
|
24
19
|
'CoercibleStrEnum',
|
|
25
|
-
'DataConnectorType',
|
|
26
|
-
'OperatorName',
|
|
27
|
-
'PipelineStep',
|
|
28
20
|
]
|
|
29
21
|
|
|
30
22
|
|
|
@@ -42,6 +34,7 @@ class CoercibleStrEnum(enum.StrEnum):
|
|
|
42
34
|
Notes
|
|
43
35
|
-----
|
|
44
36
|
- Values are normalized via ``str(value).strip().casefold()``.
|
|
37
|
+
- If value matching fails, the raw string is tried as a member name.
|
|
45
38
|
- Error messages enumerate allowed values for easier debugging.
|
|
46
39
|
"""
|
|
47
40
|
|
|
@@ -57,7 +50,13 @@ class CoercibleStrEnum(enum.StrEnum):
|
|
|
57
50
|
Returns
|
|
58
51
|
-------
|
|
59
52
|
StrStrMap
|
|
60
|
-
A mapping of alias
|
|
53
|
+
A mapping of alias strings to their corresponding enum member
|
|
54
|
+
values or names.
|
|
55
|
+
|
|
56
|
+
Notes
|
|
57
|
+
-----
|
|
58
|
+
- Alias keys are normalized via ``str(key).strip().casefold()``.
|
|
59
|
+
- Alias values should be member values or member names.
|
|
61
60
|
"""
|
|
62
61
|
return {}
|
|
63
62
|
|
|
@@ -76,12 +75,12 @@ class CoercibleStrEnum(enum.StrEnum):
|
|
|
76
75
|
@classmethod
|
|
77
76
|
def coerce(cls, value: Self | str | object) -> Self:
|
|
78
77
|
"""
|
|
79
|
-
Convert an enum member or string-like input to a member of
|
|
78
|
+
Convert an enum member or string-like input to a member of *cls*.
|
|
80
79
|
|
|
81
80
|
Parameters
|
|
82
81
|
----------
|
|
83
82
|
value : Self | str | object
|
|
84
|
-
An existing enum member or a
|
|
83
|
+
An existing enum member or a string-like value to normalize.
|
|
85
84
|
|
|
86
85
|
Returns
|
|
87
86
|
-------
|
|
@@ -96,10 +95,26 @@ class CoercibleStrEnum(enum.StrEnum):
|
|
|
96
95
|
if isinstance(value, cls):
|
|
97
96
|
return value
|
|
98
97
|
try:
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
98
|
+
raw = str(value).strip()
|
|
99
|
+
normalized = raw.casefold()
|
|
100
|
+
aliases = {
|
|
101
|
+
str(key).strip().casefold(): alias
|
|
102
|
+
for key, alias in cls.aliases().items()
|
|
103
|
+
}
|
|
104
|
+
resolved = aliases.get(normalized)
|
|
105
|
+
if resolved is None:
|
|
106
|
+
try:
|
|
107
|
+
return cls(normalized) # type: ignore[arg-type]
|
|
108
|
+
except (ValueError, TypeError):
|
|
109
|
+
return cls[raw] # type: ignore[index]
|
|
110
|
+
if isinstance(resolved, cls):
|
|
111
|
+
return resolved
|
|
112
|
+
try:
|
|
113
|
+
return cls(resolved) # type: ignore[arg-type]
|
|
114
|
+
except (ValueError, TypeError):
|
|
115
|
+
# Allow aliases to reference member names.
|
|
116
|
+
return cls[resolved] # type: ignore[index]
|
|
117
|
+
except (ValueError, TypeError, KeyError) as e:
|
|
103
118
|
allowed = ', '.join(cls.choices())
|
|
104
119
|
raise ValueError(
|
|
105
120
|
f'Invalid {cls.__name__} value: {value!r}. Allowed: {allowed}',
|
|
@@ -108,15 +123,15 @@ class CoercibleStrEnum(enum.StrEnum):
|
|
|
108
123
|
@classmethod
|
|
109
124
|
def try_coerce(
|
|
110
125
|
cls,
|
|
111
|
-
value: object,
|
|
126
|
+
value: Self | str | object,
|
|
112
127
|
) -> Self | None:
|
|
113
128
|
"""
|
|
114
|
-
|
|
129
|
+
Attempt to coerce a value into the enum; return ``None`` on failure.
|
|
115
130
|
|
|
116
131
|
Parameters
|
|
117
132
|
----------
|
|
118
|
-
value : object
|
|
119
|
-
An existing enum member or a
|
|
133
|
+
value : Self | str | object
|
|
134
|
+
An existing enum member or a string-like value to normalize.
|
|
120
135
|
|
|
121
136
|
Returns
|
|
122
137
|
-------
|
|
@@ -125,184 +140,5 @@ class CoercibleStrEnum(enum.StrEnum):
|
|
|
125
140
|
"""
|
|
126
141
|
try:
|
|
127
142
|
return cls.coerce(value)
|
|
128
|
-
except ValueError:
|
|
143
|
+
except (ValueError, TypeError, KeyError):
|
|
129
144
|
return None
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
# SECTION: ENUMS ============================================================ #
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
class AggregateName(CoercibleStrEnum):
|
|
136
|
-
"""Supported aggregations with helpers."""
|
|
137
|
-
|
|
138
|
-
# -- Constants -- #
|
|
139
|
-
|
|
140
|
-
AVG = 'avg'
|
|
141
|
-
COUNT = 'count'
|
|
142
|
-
MAX = 'max'
|
|
143
|
-
MIN = 'min'
|
|
144
|
-
SUM = 'sum'
|
|
145
|
-
|
|
146
|
-
# -- Class Methods -- #
|
|
147
|
-
|
|
148
|
-
@property
|
|
149
|
-
def func(self) -> AggregateFunc:
|
|
150
|
-
"""
|
|
151
|
-
Get the aggregation function for this aggregation type.
|
|
152
|
-
|
|
153
|
-
Returns
|
|
154
|
-
-------
|
|
155
|
-
AggregateFunc
|
|
156
|
-
The aggregation function corresponding to this aggregation type.
|
|
157
|
-
"""
|
|
158
|
-
if self is AggregateName.COUNT:
|
|
159
|
-
return lambda xs, n: n
|
|
160
|
-
if self is AggregateName.MAX:
|
|
161
|
-
return lambda xs, n: (max(xs) if xs else None)
|
|
162
|
-
if self is AggregateName.MIN:
|
|
163
|
-
return lambda xs, n: (min(xs) if xs else None)
|
|
164
|
-
if self is AggregateName.SUM:
|
|
165
|
-
return lambda xs, n: sum(xs)
|
|
166
|
-
|
|
167
|
-
# AVG
|
|
168
|
-
return lambda xs, n: (fmean(xs) if xs else 0.0)
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
class DataConnectorType(CoercibleStrEnum):
|
|
172
|
-
"""Supported data connector types."""
|
|
173
|
-
|
|
174
|
-
# -- Constants -- #
|
|
175
|
-
|
|
176
|
-
API = 'api'
|
|
177
|
-
DATABASE = 'database'
|
|
178
|
-
FILE = 'file'
|
|
179
|
-
|
|
180
|
-
# -- Class Methods -- #
|
|
181
|
-
|
|
182
|
-
@classmethod
|
|
183
|
-
def aliases(cls) -> StrStrMap:
|
|
184
|
-
"""
|
|
185
|
-
Return a mapping of common aliases for each enum member.
|
|
186
|
-
|
|
187
|
-
Returns
|
|
188
|
-
-------
|
|
189
|
-
StrStrMap
|
|
190
|
-
A mapping of alias names to their corresponding enum member names.
|
|
191
|
-
"""
|
|
192
|
-
return {
|
|
193
|
-
'http': 'api',
|
|
194
|
-
'https': 'api',
|
|
195
|
-
'rest': 'api',
|
|
196
|
-
'db': 'database',
|
|
197
|
-
'filesystem': 'file',
|
|
198
|
-
'fs': 'file',
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
class OperatorName(CoercibleStrEnum):
|
|
203
|
-
"""Supported comparison operators with helpers."""
|
|
204
|
-
|
|
205
|
-
# -- Constants -- #
|
|
206
|
-
|
|
207
|
-
EQ = 'eq'
|
|
208
|
-
NE = 'ne'
|
|
209
|
-
GT = 'gt'
|
|
210
|
-
GTE = 'gte'
|
|
211
|
-
LT = 'lt'
|
|
212
|
-
LTE = 'lte'
|
|
213
|
-
IN = 'in'
|
|
214
|
-
CONTAINS = 'contains'
|
|
215
|
-
|
|
216
|
-
# -- Getters -- #
|
|
217
|
-
|
|
218
|
-
@property
|
|
219
|
-
def func(self) -> OperatorFunc:
|
|
220
|
-
"""
|
|
221
|
-
Get the comparison function for this operator.
|
|
222
|
-
|
|
223
|
-
Returns
|
|
224
|
-
-------
|
|
225
|
-
OperatorFunc
|
|
226
|
-
The comparison function corresponding to this operator.
|
|
227
|
-
"""
|
|
228
|
-
match self:
|
|
229
|
-
case OperatorName.EQ:
|
|
230
|
-
return _op.eq
|
|
231
|
-
case OperatorName.NE:
|
|
232
|
-
return _op.ne
|
|
233
|
-
case OperatorName.GT:
|
|
234
|
-
return _op.gt
|
|
235
|
-
case OperatorName.GTE:
|
|
236
|
-
return _op.ge
|
|
237
|
-
case OperatorName.LT:
|
|
238
|
-
return _op.lt
|
|
239
|
-
case OperatorName.LTE:
|
|
240
|
-
return _op.le
|
|
241
|
-
case OperatorName.IN:
|
|
242
|
-
return lambda a, b: a in b
|
|
243
|
-
case OperatorName.CONTAINS:
|
|
244
|
-
return lambda a, b: b in a
|
|
245
|
-
|
|
246
|
-
# -- Class Methods -- #
|
|
247
|
-
|
|
248
|
-
@classmethod
|
|
249
|
-
def aliases(cls) -> StrStrMap:
|
|
250
|
-
"""
|
|
251
|
-
Return a mapping of common aliases for each enum member.
|
|
252
|
-
|
|
253
|
-
Returns
|
|
254
|
-
-------
|
|
255
|
-
StrStrMap
|
|
256
|
-
A mapping of alias names to their corresponding enum member names.
|
|
257
|
-
"""
|
|
258
|
-
return {
|
|
259
|
-
'==': 'eq',
|
|
260
|
-
'=': 'eq',
|
|
261
|
-
'!=': 'ne',
|
|
262
|
-
'<>': 'ne',
|
|
263
|
-
'>=': 'gte',
|
|
264
|
-
'≥': 'gte',
|
|
265
|
-
'<=': 'lte',
|
|
266
|
-
'≤': 'lte',
|
|
267
|
-
'>': 'gt',
|
|
268
|
-
'<': 'lt',
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
class PipelineStep(CoercibleStrEnum):
|
|
273
|
-
"""Pipeline step names as an enum for internal orchestration."""
|
|
274
|
-
|
|
275
|
-
# -- Constants -- #
|
|
276
|
-
|
|
277
|
-
FILTER = 'filter'
|
|
278
|
-
MAP = 'map'
|
|
279
|
-
SELECT = 'select'
|
|
280
|
-
SORT = 'sort'
|
|
281
|
-
AGGREGATE = 'aggregate'
|
|
282
|
-
|
|
283
|
-
# -- Getters -- #
|
|
284
|
-
|
|
285
|
-
@property
|
|
286
|
-
def order(self) -> int:
|
|
287
|
-
"""
|
|
288
|
-
Get the execution order of this pipeline step.
|
|
289
|
-
|
|
290
|
-
Returns
|
|
291
|
-
-------
|
|
292
|
-
int
|
|
293
|
-
The execution order of this pipeline step.
|
|
294
|
-
"""
|
|
295
|
-
return _PIPELINE_ORDER_INDEX[self]
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
# SECTION: INTERNAL CONSTANTS ============================================== #
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
# Precomputed order index for PipelineStep; avoids recomputing on each access.
|
|
302
|
-
_PIPELINE_ORDER_INDEX: dict[PipelineStep, int] = {
|
|
303
|
-
PipelineStep.FILTER: 0,
|
|
304
|
-
PipelineStep.MAP: 1,
|
|
305
|
-
PipelineStep.SELECT: 2,
|
|
306
|
-
PipelineStep.SORT: 3,
|
|
307
|
-
PipelineStep.AGGREGATE: 4,
|
|
308
|
-
}
|
etlplus/file/_imports.py
CHANGED
etlplus/file/_io.py
CHANGED
|
@@ -8,6 +8,7 @@ from __future__ import annotations
|
|
|
8
8
|
|
|
9
9
|
import csv
|
|
10
10
|
from pathlib import Path
|
|
11
|
+
from typing import Any
|
|
11
12
|
from typing import cast
|
|
12
13
|
|
|
13
14
|
from ..types import JSONData
|
|
@@ -17,6 +18,44 @@ from ..types import JSONList
|
|
|
17
18
|
# SECTION: FUNCTIONS ======================================================== #
|
|
18
19
|
|
|
19
20
|
|
|
21
|
+
def coerce_record_payload(
|
|
22
|
+
payload: Any,
|
|
23
|
+
*,
|
|
24
|
+
format_name: str,
|
|
25
|
+
) -> JSONData:
|
|
26
|
+
"""
|
|
27
|
+
Validate that *payload* is an object or list of objects.
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
payload : Any
|
|
32
|
+
Parsed payload to validate.
|
|
33
|
+
format_name : str
|
|
34
|
+
Human-readable format name for error messages.
|
|
35
|
+
|
|
36
|
+
Returns
|
|
37
|
+
-------
|
|
38
|
+
JSONData
|
|
39
|
+
*payload* when it is a dict or a list of dicts.
|
|
40
|
+
|
|
41
|
+
Raises
|
|
42
|
+
------
|
|
43
|
+
TypeError
|
|
44
|
+
If the payload is not a dict or list of dicts.
|
|
45
|
+
"""
|
|
46
|
+
if isinstance(payload, dict):
|
|
47
|
+
return cast(JSONDict, payload)
|
|
48
|
+
if isinstance(payload, list):
|
|
49
|
+
if all(isinstance(item, dict) for item in payload):
|
|
50
|
+
return cast(JSONList, payload)
|
|
51
|
+
raise TypeError(
|
|
52
|
+
f'{format_name} array must contain only objects (dicts)',
|
|
53
|
+
)
|
|
54
|
+
raise TypeError(
|
|
55
|
+
f'{format_name} root must be an object or an array of objects',
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
20
59
|
def normalize_records(
|
|
21
60
|
data: JSONData,
|
|
22
61
|
format_name: str,
|
|
@@ -50,9 +89,13 @@ def normalize_records(
|
|
|
50
89
|
return [cast(JSONDict, data)]
|
|
51
90
|
|
|
52
91
|
|
|
53
|
-
def read_delimited(
|
|
92
|
+
def read_delimited(
|
|
93
|
+
path: Path,
|
|
94
|
+
*,
|
|
95
|
+
delimiter: str,
|
|
96
|
+
) -> JSONList:
|
|
54
97
|
"""
|
|
55
|
-
Read delimited content from
|
|
98
|
+
Read delimited content from *path*.
|
|
56
99
|
|
|
57
100
|
Parameters
|
|
58
101
|
----------
|
|
@@ -79,9 +122,14 @@ def read_delimited(path: Path, *, delimiter: str) -> JSONList:
|
|
|
79
122
|
return rows
|
|
80
123
|
|
|
81
124
|
|
|
82
|
-
def write_delimited(
|
|
125
|
+
def write_delimited(
|
|
126
|
+
path: Path,
|
|
127
|
+
data: JSONData,
|
|
128
|
+
*,
|
|
129
|
+
delimiter: str,
|
|
130
|
+
) -> int:
|
|
83
131
|
"""
|
|
84
|
-
Write
|
|
132
|
+
Write *data* to a delimited file and return record count.
|
|
85
133
|
|
|
86
134
|
Parameters
|
|
87
135
|
----------
|
etlplus/file/accdb.py
CHANGED
|
@@ -28,6 +28,7 @@ from . import stub
|
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
__all__ = [
|
|
31
|
+
# Functions
|
|
31
32
|
'read',
|
|
32
33
|
'write',
|
|
33
34
|
]
|
|
@@ -40,7 +41,7 @@ def read(
|
|
|
40
41
|
path: Path,
|
|
41
42
|
) -> JSONList:
|
|
42
43
|
"""
|
|
43
|
-
Read ACCDB content from
|
|
44
|
+
Read ACCDB content from *path*.
|
|
44
45
|
|
|
45
46
|
Parameters
|
|
46
47
|
----------
|
|
@@ -60,7 +61,7 @@ def write(
|
|
|
60
61
|
data: JSONData,
|
|
61
62
|
) -> int:
|
|
62
63
|
"""
|
|
63
|
-
Write
|
|
64
|
+
Write *data* to ACCDB at *path* and return record count.
|
|
64
65
|
|
|
65
66
|
Parameters
|
|
66
67
|
----------
|
etlplus/file/arrow.py
CHANGED
|
@@ -28,6 +28,7 @@ from . import stub
|
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
__all__ = [
|
|
31
|
+
# Functions
|
|
31
32
|
'read',
|
|
32
33
|
'write',
|
|
33
34
|
]
|
|
@@ -40,7 +41,7 @@ def read(
|
|
|
40
41
|
path: Path,
|
|
41
42
|
) -> JSONList:
|
|
42
43
|
"""
|
|
43
|
-
Read ARROW content from
|
|
44
|
+
Read ARROW content from *path*.
|
|
44
45
|
|
|
45
46
|
Parameters
|
|
46
47
|
----------
|
|
@@ -60,7 +61,7 @@ def write(
|
|
|
60
61
|
data: JSONData,
|
|
61
62
|
) -> int:
|
|
62
63
|
"""
|
|
63
|
-
Write
|
|
64
|
+
Write *data* to ARROW at *path* and return record count.
|
|
64
65
|
|
|
65
66
|
Parameters
|
|
66
67
|
----------
|
etlplus/file/avro.py
CHANGED
|
@@ -33,6 +33,7 @@ from ._io import normalize_records
|
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
__all__ = [
|
|
36
|
+
# Functions
|
|
36
37
|
'read',
|
|
37
38
|
'write',
|
|
38
39
|
]
|
|
@@ -124,7 +125,7 @@ def read(
|
|
|
124
125
|
path: Path,
|
|
125
126
|
) -> JSONList:
|
|
126
127
|
"""
|
|
127
|
-
Read AVRO content from
|
|
128
|
+
Read AVRO content from *path*.
|
|
128
129
|
|
|
129
130
|
Parameters
|
|
130
131
|
----------
|
|
@@ -147,7 +148,7 @@ def write(
|
|
|
147
148
|
data: JSONData,
|
|
148
149
|
) -> int:
|
|
149
150
|
"""
|
|
150
|
-
Write
|
|
151
|
+
Write *data* to AVRO at *path* and return record count.
|
|
151
152
|
|
|
152
153
|
Parameters
|
|
153
154
|
----------
|
etlplus/file/bson.py
CHANGED
|
@@ -27,6 +27,7 @@ from . import stub
|
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
__all__ = [
|
|
30
|
+
# Functions
|
|
30
31
|
'read',
|
|
31
32
|
'write',
|
|
32
33
|
]
|
|
@@ -39,7 +40,7 @@ def read(
|
|
|
39
40
|
path: Path,
|
|
40
41
|
) -> JSONList:
|
|
41
42
|
"""
|
|
42
|
-
Read BSON content from
|
|
43
|
+
Read BSON content from *path*.
|
|
43
44
|
|
|
44
45
|
Parameters
|
|
45
46
|
----------
|
|
@@ -59,7 +60,7 @@ def write(
|
|
|
59
60
|
data: JSONData,
|
|
60
61
|
) -> int:
|
|
61
62
|
"""
|
|
62
|
-
Write
|
|
63
|
+
Write *data* to BSON at *path* and return record count.
|
|
63
64
|
|
|
64
65
|
Parameters
|
|
65
66
|
----------
|
etlplus/file/cbor.py
CHANGED
|
@@ -28,6 +28,7 @@ from . import stub
|
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
__all__ = [
|
|
31
|
+
# Functions
|
|
31
32
|
'read',
|
|
32
33
|
'write',
|
|
33
34
|
]
|
|
@@ -40,7 +41,7 @@ def read(
|
|
|
40
41
|
path: Path,
|
|
41
42
|
) -> JSONList:
|
|
42
43
|
"""
|
|
43
|
-
Read CBOR content from
|
|
44
|
+
Read CBOR content from *path*.
|
|
44
45
|
|
|
45
46
|
Parameters
|
|
46
47
|
----------
|
|
@@ -60,7 +61,7 @@ def write(
|
|
|
60
61
|
data: JSONData,
|
|
61
62
|
) -> int:
|
|
62
63
|
"""
|
|
63
|
-
Write
|
|
64
|
+
Write *data* to CBOR at *path* and return record count.
|
|
64
65
|
|
|
65
66
|
Parameters
|
|
66
67
|
----------
|
etlplus/file/cfg.py
CHANGED
|
@@ -29,6 +29,7 @@ from . import stub
|
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
__all__ = [
|
|
32
|
+
# Functions
|
|
32
33
|
'read',
|
|
33
34
|
'write',
|
|
34
35
|
]
|
|
@@ -41,7 +42,7 @@ def read(
|
|
|
41
42
|
path: Path,
|
|
42
43
|
) -> JSONList:
|
|
43
44
|
"""
|
|
44
|
-
Read CFG content from
|
|
45
|
+
Read CFG content from *path*.
|
|
45
46
|
|
|
46
47
|
Parameters
|
|
47
48
|
----------
|
|
@@ -61,7 +62,7 @@ def write(
|
|
|
61
62
|
data: JSONData,
|
|
62
63
|
) -> int:
|
|
63
64
|
"""
|
|
64
|
-
Write
|
|
65
|
+
Write *data* to CFG file at *path* and return record count.
|
|
65
66
|
|
|
66
67
|
Parameters
|
|
67
68
|
----------
|
etlplus/file/conf.py
CHANGED
|
@@ -30,6 +30,7 @@ from . import stub
|
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
__all__ = [
|
|
33
|
+
# Functions
|
|
33
34
|
'read',
|
|
34
35
|
'write',
|
|
35
36
|
]
|
|
@@ -42,7 +43,7 @@ def read(
|
|
|
42
43
|
path: Path,
|
|
43
44
|
) -> JSONList:
|
|
44
45
|
"""
|
|
45
|
-
Read CONF content from
|
|
46
|
+
Read CONF content from *path*.
|
|
46
47
|
|
|
47
48
|
Parameters
|
|
48
49
|
----------
|
|
@@ -62,7 +63,7 @@ def write(
|
|
|
62
63
|
data: JSONData,
|
|
63
64
|
) -> int:
|
|
64
65
|
"""
|
|
65
|
-
Write
|
|
66
|
+
Write *data* to CONF at *path* and return record count.
|
|
66
67
|
|
|
67
68
|
Parameters
|
|
68
69
|
----------
|
etlplus/file/core.py
CHANGED
|
@@ -22,7 +22,10 @@ from .enums import infer_file_format_and_compression
|
|
|
22
22
|
# SECTION: EXPORTS ========================================================== #
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
__all__ = [
|
|
25
|
+
__all__ = [
|
|
26
|
+
# Classes
|
|
27
|
+
'File',
|
|
28
|
+
]
|
|
26
29
|
|
|
27
30
|
|
|
28
31
|
# SECTION: INTERNAL FUNCTIONS =============================================== #
|
|
@@ -30,7 +33,7 @@ __all__ = ['File']
|
|
|
30
33
|
|
|
31
34
|
def _accepts_root_tag(handler: object) -> bool:
|
|
32
35
|
"""
|
|
33
|
-
Return True when
|
|
36
|
+
Return True when *handler* supports a ``root_tag`` argument.
|
|
34
37
|
|
|
35
38
|
Parameters
|
|
36
39
|
----------
|
|
@@ -57,7 +60,7 @@ def _accepts_root_tag(handler: object) -> bool:
|
|
|
57
60
|
@cache
|
|
58
61
|
def _module_for_format(file_format: FileFormat) -> ModuleType:
|
|
59
62
|
"""
|
|
60
|
-
Import and return the module for
|
|
63
|
+
Import and return the module for *file_format*.
|
|
61
64
|
|
|
62
65
|
Parameters
|
|
63
66
|
----------
|
|
@@ -112,8 +115,8 @@ class File:
|
|
|
112
115
|
"""
|
|
113
116
|
Auto-detect and set the file format on initialization.
|
|
114
117
|
|
|
115
|
-
If no explicit
|
|
116
|
-
the file path's extension and update :attr:`file_format`. If the
|
|
118
|
+
If no explicit :attr:`file_format` is provided, attempt to infer it
|
|
119
|
+
from the file path's extension and update :attr:`file_format`. If the
|
|
117
120
|
extension is unknown, the attribute is left as ``None`` and will be
|
|
118
121
|
validated later by :meth:`_ensure_format`.
|
|
119
122
|
"""
|
|
@@ -262,7 +265,7 @@ class File:
|
|
|
262
265
|
|
|
263
266
|
def read(self) -> JSONData:
|
|
264
267
|
"""
|
|
265
|
-
Read structured data from :attr
|
|
268
|
+
Read structured data from :attr:path` using :attr:`file_format`.
|
|
266
269
|
|
|
267
270
|
Returns
|
|
268
271
|
-------
|
|
@@ -291,7 +294,7 @@ class File:
|
|
|
291
294
|
root_tag: str = xml.DEFAULT_XML_ROOT,
|
|
292
295
|
) -> int:
|
|
293
296
|
"""
|
|
294
|
-
Write
|
|
297
|
+
Write *data* to *path* using :attr:`file_format`.
|
|
295
298
|
|
|
296
299
|
Parameters
|
|
297
300
|
----------
|
|
@@ -299,7 +302,7 @@ class File:
|
|
|
299
302
|
Data to write to the file.
|
|
300
303
|
root_tag : str, optional
|
|
301
304
|
Root tag name to use when writing XML files. Defaults to
|
|
302
|
-
``
|
|
305
|
+
``xml.DEFAULT_XML_ROOT``.
|
|
303
306
|
|
|
304
307
|
Returns
|
|
305
308
|
-------
|
etlplus/file/csv.py
CHANGED
|
@@ -29,6 +29,7 @@ from ._io import write_delimited
|
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
__all__ = [
|
|
32
|
+
# Functions
|
|
32
33
|
'read',
|
|
33
34
|
'write',
|
|
34
35
|
]
|
|
@@ -41,7 +42,7 @@ def read(
|
|
|
41
42
|
path: Path,
|
|
42
43
|
) -> JSONList:
|
|
43
44
|
"""
|
|
44
|
-
Read CSV content from
|
|
45
|
+
Read CSV content from *path*.
|
|
45
46
|
|
|
46
47
|
Parameters
|
|
47
48
|
----------
|
|
@@ -61,7 +62,7 @@ def write(
|
|
|
61
62
|
data: JSONData,
|
|
62
63
|
) -> int:
|
|
63
64
|
"""
|
|
64
|
-
Write
|
|
65
|
+
Write *data* to CSV at *path* and return record count.
|
|
65
66
|
|
|
66
67
|
Parameters
|
|
67
68
|
----------
|
etlplus/file/dat.py
CHANGED
|
@@ -28,6 +28,7 @@ from . import stub
|
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
__all__ = [
|
|
31
|
+
# Functions
|
|
31
32
|
'read',
|
|
32
33
|
'write',
|
|
33
34
|
]
|
|
@@ -40,7 +41,7 @@ def read(
|
|
|
40
41
|
path: Path,
|
|
41
42
|
) -> JSONList:
|
|
42
43
|
"""
|
|
43
|
-
Read DAT content from
|
|
44
|
+
Read DAT content from *path*.
|
|
44
45
|
|
|
45
46
|
Parameters
|
|
46
47
|
----------
|
|
@@ -60,7 +61,7 @@ def write(
|
|
|
60
61
|
data: JSONData,
|
|
61
62
|
) -> int:
|
|
62
63
|
"""
|
|
63
|
-
Write
|
|
64
|
+
Write *data* to DAT file at *path* and return record count.
|
|
64
65
|
|
|
65
66
|
Parameters
|
|
66
67
|
----------
|