etlplus 0.16.10__py3-none-any.whl → 0.17.3__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/file/README.md +33 -0
- etlplus/file/_imports.py +35 -20
- etlplus/file/_io.py +138 -15
- etlplus/file/_r.py +48 -0
- etlplus/file/_sql.py +224 -0
- etlplus/file/accdb.py +7 -6
- etlplus/file/arrow.py +29 -10
- etlplus/file/avro.py +13 -10
- etlplus/file/bson.py +94 -10
- etlplus/file/cbor.py +29 -17
- etlplus/file/cfg.py +7 -6
- etlplus/file/conf.py +7 -6
- etlplus/file/core.py +1 -1
- etlplus/file/csv.py +8 -7
- etlplus/file/dat.py +52 -11
- etlplus/file/dta.py +36 -16
- etlplus/file/duckdb.py +72 -11
- etlplus/file/enums.py +29 -0
- etlplus/file/feather.py +15 -30
- etlplus/file/fwf.py +44 -10
- etlplus/file/gz.py +12 -7
- etlplus/file/hbs.py +7 -6
- etlplus/file/hdf5.py +71 -8
- etlplus/file/ini.py +60 -17
- etlplus/file/ion.py +7 -6
- etlplus/file/jinja2.py +7 -6
- etlplus/file/json.py +10 -11
- etlplus/file/log.py +7 -6
- etlplus/file/mat.py +7 -6
- etlplus/file/mdb.py +7 -6
- etlplus/file/msgpack.py +27 -15
- etlplus/file/mustache.py +7 -6
- etlplus/file/nc.py +69 -11
- etlplus/file/ndjson.py +10 -6
- etlplus/file/numbers.py +7 -6
- etlplus/file/ods.py +48 -11
- etlplus/file/orc.py +15 -30
- etlplus/file/parquet.py +10 -6
- etlplus/file/pb.py +36 -24
- etlplus/file/pbf.py +7 -6
- etlplus/file/properties.py +44 -18
- etlplus/file/proto.py +24 -18
- etlplus/file/psv.py +12 -11
- etlplus/file/rda.py +57 -15
- etlplus/file/rds.py +50 -14
- etlplus/file/sas7bdat.py +26 -16
- etlplus/file/sav.py +34 -16
- etlplus/file/sqlite.py +70 -10
- etlplus/file/stub.py +8 -6
- etlplus/file/sylk.py +7 -6
- etlplus/file/tab.py +13 -13
- etlplus/file/toml.py +56 -17
- etlplus/file/tsv.py +8 -7
- etlplus/file/txt.py +10 -7
- etlplus/file/vm.py +7 -6
- etlplus/file/wks.py +7 -6
- etlplus/file/xls.py +8 -5
- etlplus/file/xlsm.py +48 -10
- etlplus/file/xlsx.py +10 -6
- etlplus/file/xml.py +11 -9
- etlplus/file/xpt.py +46 -10
- etlplus/file/yaml.py +10 -11
- etlplus/file/zip.py +10 -5
- etlplus/file/zsav.py +7 -6
- {etlplus-0.16.10.dist-info → etlplus-0.17.3.dist-info}/METADATA +44 -26
- {etlplus-0.16.10.dist-info → etlplus-0.17.3.dist-info}/RECORD +70 -68
- {etlplus-0.16.10.dist-info → etlplus-0.17.3.dist-info}/WHEEL +0 -0
- {etlplus-0.16.10.dist-info → etlplus-0.17.3.dist-info}/entry_points.txt +0 -0
- {etlplus-0.16.10.dist-info → etlplus-0.17.3.dist-info}/licenses/LICENSE +0 -0
- {etlplus-0.16.10.dist-info → etlplus-0.17.3.dist-info}/top_level.txt +0 -0
etlplus/file/txt.py
CHANGED
|
@@ -16,11 +16,12 @@ Notes
|
|
|
16
16
|
|
|
17
17
|
from __future__ import annotations
|
|
18
18
|
|
|
19
|
-
from pathlib import Path
|
|
20
|
-
|
|
21
19
|
from ..types import JSONData
|
|
22
20
|
from ..types import JSONList
|
|
21
|
+
from ..types import StrPath
|
|
23
22
|
from ..utils import count_records
|
|
23
|
+
from ._io import coerce_path
|
|
24
|
+
from ._io import ensure_parent_dir
|
|
24
25
|
from ._io import normalize_records
|
|
25
26
|
|
|
26
27
|
# SECTION: EXPORTS ========================================================== #
|
|
@@ -37,14 +38,14 @@ __all__ = [
|
|
|
37
38
|
|
|
38
39
|
|
|
39
40
|
def read(
|
|
40
|
-
path:
|
|
41
|
+
path: StrPath,
|
|
41
42
|
) -> JSONList:
|
|
42
43
|
"""
|
|
43
44
|
Read TXT content from *path*.
|
|
44
45
|
|
|
45
46
|
Parameters
|
|
46
47
|
----------
|
|
47
|
-
path :
|
|
48
|
+
path : StrPath
|
|
48
49
|
Path to the TXT file on disk.
|
|
49
50
|
|
|
50
51
|
Returns
|
|
@@ -52,6 +53,7 @@ def read(
|
|
|
52
53
|
JSONList
|
|
53
54
|
The list of dictionaries read from the TXT file.
|
|
54
55
|
"""
|
|
56
|
+
path = coerce_path(path)
|
|
55
57
|
rows: JSONList = []
|
|
56
58
|
with path.open('r', encoding='utf-8') as handle:
|
|
57
59
|
for line in handle:
|
|
@@ -63,7 +65,7 @@ def read(
|
|
|
63
65
|
|
|
64
66
|
|
|
65
67
|
def write(
|
|
66
|
-
path:
|
|
68
|
+
path: StrPath,
|
|
67
69
|
data: JSONData,
|
|
68
70
|
) -> int:
|
|
69
71
|
"""
|
|
@@ -71,7 +73,7 @@ def write(
|
|
|
71
73
|
|
|
72
74
|
Parameters
|
|
73
75
|
----------
|
|
74
|
-
path :
|
|
76
|
+
path : StrPath
|
|
75
77
|
Path to the TXT file on disk.
|
|
76
78
|
data : JSONData
|
|
77
79
|
Data to write. Expects ``{'text': '...'} `` or a list of those.
|
|
@@ -87,12 +89,13 @@ def write(
|
|
|
87
89
|
If any item in *data* is not a dictionary or if any dictionary
|
|
88
90
|
does not contain a ``'text'`` key.
|
|
89
91
|
"""
|
|
92
|
+
path = coerce_path(path)
|
|
90
93
|
rows = normalize_records(data, 'TXT')
|
|
91
94
|
|
|
92
95
|
if not rows:
|
|
93
96
|
return 0
|
|
94
97
|
|
|
95
|
-
path
|
|
98
|
+
ensure_parent_dir(path)
|
|
96
99
|
with path.open('w', encoding='utf-8') as handle:
|
|
97
100
|
for row in rows:
|
|
98
101
|
if 'text' not in row:
|
etlplus/file/vm.py
CHANGED
|
@@ -19,11 +19,11 @@ Notes
|
|
|
19
19
|
|
|
20
20
|
from __future__ import annotations
|
|
21
21
|
|
|
22
|
-
from pathlib import Path
|
|
23
|
-
|
|
24
22
|
from ..types import JSONData
|
|
25
23
|
from ..types import JSONList
|
|
24
|
+
from ..types import StrPath
|
|
26
25
|
from . import stub
|
|
26
|
+
from ._io import coerce_path
|
|
27
27
|
|
|
28
28
|
# SECTION: EXPORTS ========================================================== #
|
|
29
29
|
|
|
@@ -39,14 +39,14 @@ __all__ = [
|
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
def read(
|
|
42
|
-
path:
|
|
42
|
+
path: StrPath,
|
|
43
43
|
) -> JSONList:
|
|
44
44
|
"""
|
|
45
45
|
Read VM content from *path*.
|
|
46
46
|
|
|
47
47
|
Parameters
|
|
48
48
|
----------
|
|
49
|
-
path :
|
|
49
|
+
path : StrPath
|
|
50
50
|
Path to the VM file on disk.
|
|
51
51
|
|
|
52
52
|
Returns
|
|
@@ -58,7 +58,7 @@ def read(
|
|
|
58
58
|
|
|
59
59
|
|
|
60
60
|
def write(
|
|
61
|
-
path:
|
|
61
|
+
path: StrPath,
|
|
62
62
|
data: JSONData,
|
|
63
63
|
) -> int:
|
|
64
64
|
"""
|
|
@@ -66,7 +66,7 @@ def write(
|
|
|
66
66
|
|
|
67
67
|
Parameters
|
|
68
68
|
----------
|
|
69
|
-
path :
|
|
69
|
+
path : StrPath
|
|
70
70
|
Path to the VM file on disk.
|
|
71
71
|
data : JSONData
|
|
72
72
|
Data to write as VM file. Should be a list of dictionaries or a single
|
|
@@ -77,4 +77,5 @@ def write(
|
|
|
77
77
|
int
|
|
78
78
|
The number of rows written to the VM file.
|
|
79
79
|
"""
|
|
80
|
+
path = coerce_path(path)
|
|
80
81
|
return stub.write(path, data, format_name='VM')
|
etlplus/file/wks.py
CHANGED
|
@@ -18,11 +18,11 @@ Notes
|
|
|
18
18
|
|
|
19
19
|
from __future__ import annotations
|
|
20
20
|
|
|
21
|
-
from pathlib import Path
|
|
22
|
-
|
|
23
21
|
from ..types import JSONData
|
|
24
22
|
from ..types import JSONList
|
|
23
|
+
from ..types import StrPath
|
|
25
24
|
from . import stub
|
|
25
|
+
from ._io import coerce_path
|
|
26
26
|
|
|
27
27
|
# SECTION: EXPORTS ========================================================== #
|
|
28
28
|
|
|
@@ -38,14 +38,14 @@ __all__ = [
|
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
def read(
|
|
41
|
-
path:
|
|
41
|
+
path: StrPath,
|
|
42
42
|
) -> JSONList:
|
|
43
43
|
"""
|
|
44
44
|
Read WKS content from *path*.
|
|
45
45
|
|
|
46
46
|
Parameters
|
|
47
47
|
----------
|
|
48
|
-
path :
|
|
48
|
+
path : StrPath
|
|
49
49
|
Path to the WKS file on disk.
|
|
50
50
|
|
|
51
51
|
Returns
|
|
@@ -57,7 +57,7 @@ def read(
|
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
def write(
|
|
60
|
-
path:
|
|
60
|
+
path: StrPath,
|
|
61
61
|
data: JSONData,
|
|
62
62
|
) -> int:
|
|
63
63
|
"""
|
|
@@ -65,7 +65,7 @@ def write(
|
|
|
65
65
|
|
|
66
66
|
Parameters
|
|
67
67
|
----------
|
|
68
|
-
path :
|
|
68
|
+
path : StrPath
|
|
69
69
|
Path to the WKS file on disk.
|
|
70
70
|
data : JSONData
|
|
71
71
|
Data to write as WKS file. Should be a list of dictionaries or a
|
|
@@ -76,4 +76,5 @@ def write(
|
|
|
76
76
|
int
|
|
77
77
|
The number of rows written to the WKS file.
|
|
78
78
|
"""
|
|
79
|
+
path = coerce_path(path)
|
|
79
80
|
return stub.write(path, data, format_name='WKS')
|
etlplus/file/xls.py
CHANGED
|
@@ -6,12 +6,13 @@ Helpers for reading Excel XLS files (write is not supported).
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
-
from pathlib import Path
|
|
10
9
|
from typing import cast
|
|
11
10
|
|
|
12
11
|
from ..types import JSONData
|
|
13
12
|
from ..types import JSONList
|
|
13
|
+
from ..types import StrPath
|
|
14
14
|
from ._imports import get_pandas
|
|
15
|
+
from ._io import coerce_path
|
|
15
16
|
|
|
16
17
|
# SECTION: EXPORTS ========================================================== #
|
|
17
18
|
|
|
@@ -27,14 +28,14 @@ __all__ = [
|
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
def read(
|
|
30
|
-
path:
|
|
31
|
+
path: StrPath,
|
|
31
32
|
) -> JSONList:
|
|
32
33
|
"""
|
|
33
34
|
Read XLS content from *path*.
|
|
34
35
|
|
|
35
36
|
Parameters
|
|
36
37
|
----------
|
|
37
|
-
path :
|
|
38
|
+
path : StrPath
|
|
38
39
|
Path to the XLS file on disk.
|
|
39
40
|
|
|
40
41
|
Returns
|
|
@@ -47,6 +48,7 @@ def read(
|
|
|
47
48
|
ImportError
|
|
48
49
|
If the optional dependency "xlrd" is not installed.
|
|
49
50
|
"""
|
|
51
|
+
path = coerce_path(path)
|
|
50
52
|
pandas = get_pandas('XLS')
|
|
51
53
|
try:
|
|
52
54
|
frame = pandas.read_excel(path, engine='xlrd')
|
|
@@ -59,7 +61,7 @@ def read(
|
|
|
59
61
|
|
|
60
62
|
|
|
61
63
|
def write(
|
|
62
|
-
path:
|
|
64
|
+
path: StrPath,
|
|
63
65
|
data: JSONData,
|
|
64
66
|
) -> int:
|
|
65
67
|
"""
|
|
@@ -71,7 +73,7 @@ def write(
|
|
|
71
73
|
|
|
72
74
|
Parameters
|
|
73
75
|
----------
|
|
74
|
-
path :
|
|
76
|
+
path : StrPath
|
|
75
77
|
Path to the XLS file on disk.
|
|
76
78
|
data : JSONData
|
|
77
79
|
Data to write.
|
|
@@ -86,4 +88,5 @@ def write(
|
|
|
86
88
|
RuntimeError
|
|
87
89
|
If XLS writing is attempted.
|
|
88
90
|
"""
|
|
91
|
+
path = coerce_path(path)
|
|
89
92
|
raise RuntimeError('XLS write is not supported; use XLSX instead')
|
etlplus/file/xlsm.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"""
|
|
2
2
|
:mod:`etlplus.file.xlsm` module.
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
spreadsheet files
|
|
4
|
+
Helpers for reading/writing Microsoft Excel Macro-Enabled (XLSM)
|
|
5
|
+
spreadsheet files.
|
|
6
6
|
|
|
7
7
|
Notes
|
|
8
8
|
-----
|
|
@@ -19,11 +19,15 @@ Notes
|
|
|
19
19
|
|
|
20
20
|
from __future__ import annotations
|
|
21
21
|
|
|
22
|
-
from
|
|
22
|
+
from typing import cast
|
|
23
23
|
|
|
24
24
|
from ..types import JSONData
|
|
25
25
|
from ..types import JSONList
|
|
26
|
-
from
|
|
26
|
+
from ..types import StrPath
|
|
27
|
+
from ._imports import get_pandas
|
|
28
|
+
from ._io import coerce_path
|
|
29
|
+
from ._io import ensure_parent_dir
|
|
30
|
+
from ._io import normalize_records
|
|
27
31
|
|
|
28
32
|
# SECTION: EXPORTS ========================================================== #
|
|
29
33
|
|
|
@@ -39,26 +43,40 @@ __all__ = [
|
|
|
39
43
|
|
|
40
44
|
|
|
41
45
|
def read(
|
|
42
|
-
path:
|
|
46
|
+
path: StrPath,
|
|
43
47
|
) -> JSONList:
|
|
44
48
|
"""
|
|
45
49
|
Read XLSM content from *path*.
|
|
46
50
|
|
|
47
51
|
Parameters
|
|
48
52
|
----------
|
|
49
|
-
path :
|
|
53
|
+
path : StrPath
|
|
50
54
|
Path to the XLSM file on disk.
|
|
51
55
|
|
|
52
56
|
Returns
|
|
53
57
|
-------
|
|
54
58
|
JSONList
|
|
55
59
|
The list of dictionaries read from the XLSM file.
|
|
60
|
+
|
|
61
|
+
Raises
|
|
62
|
+
------
|
|
63
|
+
ImportError
|
|
64
|
+
If optional dependencies for XLSM support are missing.
|
|
56
65
|
"""
|
|
57
|
-
|
|
66
|
+
path = coerce_path(path)
|
|
67
|
+
pandas = get_pandas('XLSM')
|
|
68
|
+
try:
|
|
69
|
+
frame = pandas.read_excel(path)
|
|
70
|
+
except ImportError as e: # pragma: no cover
|
|
71
|
+
raise ImportError(
|
|
72
|
+
'XLSM support requires optional dependency "openpyxl".\n'
|
|
73
|
+
'Install with: pip install openpyxl',
|
|
74
|
+
) from e
|
|
75
|
+
return cast(JSONList, frame.to_dict(orient='records'))
|
|
58
76
|
|
|
59
77
|
|
|
60
78
|
def write(
|
|
61
|
-
path:
|
|
79
|
+
path: StrPath,
|
|
62
80
|
data: JSONData,
|
|
63
81
|
) -> int:
|
|
64
82
|
"""
|
|
@@ -66,7 +84,7 @@ def write(
|
|
|
66
84
|
|
|
67
85
|
Parameters
|
|
68
86
|
----------
|
|
69
|
-
path :
|
|
87
|
+
path : StrPath
|
|
70
88
|
Path to the XLSM file on disk.
|
|
71
89
|
data : JSONData
|
|
72
90
|
Data to write as XLSM file. Should be a list of dictionaries or a
|
|
@@ -76,5 +94,25 @@ def write(
|
|
|
76
94
|
-------
|
|
77
95
|
int
|
|
78
96
|
The number of rows written to the XLSM file.
|
|
97
|
+
|
|
98
|
+
Raises
|
|
99
|
+
------
|
|
100
|
+
ImportError
|
|
101
|
+
If optional dependencies for XLSM support are missing.
|
|
79
102
|
"""
|
|
80
|
-
|
|
103
|
+
path = coerce_path(path)
|
|
104
|
+
records = normalize_records(data, 'XLSM')
|
|
105
|
+
if not records:
|
|
106
|
+
return 0
|
|
107
|
+
|
|
108
|
+
pandas = get_pandas('XLSM')
|
|
109
|
+
ensure_parent_dir(path)
|
|
110
|
+
frame = pandas.DataFrame.from_records(records)
|
|
111
|
+
try:
|
|
112
|
+
frame.to_excel(path, index=False)
|
|
113
|
+
except ImportError as e: # pragma: no cover
|
|
114
|
+
raise ImportError(
|
|
115
|
+
'XLSM support requires optional dependency "openpyxl".\n'
|
|
116
|
+
'Install with: pip install openpyxl',
|
|
117
|
+
) from e
|
|
118
|
+
return len(records)
|
etlplus/file/xlsx.py
CHANGED
|
@@ -6,12 +6,14 @@ Helpers for reading/writing Excel XLSX files.
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
-
from pathlib import Path
|
|
10
9
|
from typing import cast
|
|
11
10
|
|
|
12
11
|
from ..types import JSONData
|
|
13
12
|
from ..types import JSONList
|
|
13
|
+
from ..types import StrPath
|
|
14
14
|
from ._imports import get_pandas
|
|
15
|
+
from ._io import coerce_path
|
|
16
|
+
from ._io import ensure_parent_dir
|
|
15
17
|
from ._io import normalize_records
|
|
16
18
|
|
|
17
19
|
# SECTION: EXPORTS ========================================================== #
|
|
@@ -28,14 +30,14 @@ __all__ = [
|
|
|
28
30
|
|
|
29
31
|
|
|
30
32
|
def read(
|
|
31
|
-
path:
|
|
33
|
+
path: StrPath,
|
|
32
34
|
) -> JSONList:
|
|
33
35
|
"""
|
|
34
36
|
Read XLSX content from *path*.
|
|
35
37
|
|
|
36
38
|
Parameters
|
|
37
39
|
----------
|
|
38
|
-
path :
|
|
40
|
+
path : StrPath
|
|
39
41
|
Path to the XLSX file on disk.
|
|
40
42
|
|
|
41
43
|
Returns
|
|
@@ -48,6 +50,7 @@ def read(
|
|
|
48
50
|
ImportError
|
|
49
51
|
If optional dependencies for XLSX support are missing.
|
|
50
52
|
"""
|
|
53
|
+
path = coerce_path(path)
|
|
51
54
|
pandas = get_pandas('XLSX')
|
|
52
55
|
try:
|
|
53
56
|
frame = pandas.read_excel(path)
|
|
@@ -60,7 +63,7 @@ def read(
|
|
|
60
63
|
|
|
61
64
|
|
|
62
65
|
def write(
|
|
63
|
-
path:
|
|
66
|
+
path: StrPath,
|
|
64
67
|
data: JSONData,
|
|
65
68
|
) -> int:
|
|
66
69
|
"""
|
|
@@ -68,7 +71,7 @@ def write(
|
|
|
68
71
|
|
|
69
72
|
Parameters
|
|
70
73
|
----------
|
|
71
|
-
path :
|
|
74
|
+
path : StrPath
|
|
72
75
|
Path to the XLSX file on disk.
|
|
73
76
|
data : JSONData
|
|
74
77
|
Data to write.
|
|
@@ -83,12 +86,13 @@ def write(
|
|
|
83
86
|
ImportError
|
|
84
87
|
If optional dependencies for XLSX support are missing.
|
|
85
88
|
"""
|
|
89
|
+
path = coerce_path(path)
|
|
86
90
|
records = normalize_records(data, 'XLSX')
|
|
87
91
|
if not records:
|
|
88
92
|
return 0
|
|
89
93
|
|
|
90
94
|
pandas = get_pandas('XLSX')
|
|
91
|
-
path
|
|
95
|
+
ensure_parent_dir(path)
|
|
92
96
|
frame = pandas.DataFrame.from_records(records)
|
|
93
97
|
try:
|
|
94
98
|
frame.to_excel(path, index=False)
|
etlplus/file/xml.py
CHANGED
|
@@ -18,12 +18,14 @@ Notes
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
20
|
import xml.etree.ElementTree as ET
|
|
21
|
-
from pathlib import Path
|
|
22
21
|
from typing import Any
|
|
23
22
|
|
|
24
23
|
from ..types import JSONData
|
|
25
24
|
from ..types import JSONDict
|
|
25
|
+
from ..types import StrPath
|
|
26
26
|
from ..utils import count_records
|
|
27
|
+
from ._io import coerce_path
|
|
28
|
+
from ._io import ensure_parent_dir
|
|
27
29
|
|
|
28
30
|
# SECTION: EXPORTS ========================================================== #
|
|
29
31
|
|
|
@@ -124,10 +126,7 @@ def _element_to_dict(
|
|
|
124
126
|
result[tag] = child_data
|
|
125
127
|
|
|
126
128
|
for key, value in element.attrib.items():
|
|
127
|
-
|
|
128
|
-
result[f'@{key}'] = value
|
|
129
|
-
else:
|
|
130
|
-
result[key] = value
|
|
129
|
+
result[f'@{key}'] = value
|
|
131
130
|
return result
|
|
132
131
|
|
|
133
132
|
|
|
@@ -135,14 +134,14 @@ def _element_to_dict(
|
|
|
135
134
|
|
|
136
135
|
|
|
137
136
|
def read(
|
|
138
|
-
path:
|
|
137
|
+
path: StrPath,
|
|
139
138
|
) -> JSONDict:
|
|
140
139
|
"""
|
|
141
140
|
Read XML content from *path*.
|
|
142
141
|
|
|
143
142
|
Parameters
|
|
144
143
|
----------
|
|
145
|
-
path :
|
|
144
|
+
path : StrPath
|
|
146
145
|
Path to the XML file on disk.
|
|
147
146
|
|
|
148
147
|
Returns
|
|
@@ -150,6 +149,7 @@ def read(
|
|
|
150
149
|
JSONDict
|
|
151
150
|
Nested dictionary representation of the XML file.
|
|
152
151
|
"""
|
|
152
|
+
path = coerce_path(path)
|
|
153
153
|
tree = ET.parse(path)
|
|
154
154
|
root = tree.getroot()
|
|
155
155
|
|
|
@@ -157,7 +157,7 @@ def read(
|
|
|
157
157
|
|
|
158
158
|
|
|
159
159
|
def write(
|
|
160
|
-
path:
|
|
160
|
+
path: StrPath,
|
|
161
161
|
data: JSONData,
|
|
162
162
|
*,
|
|
163
163
|
root_tag: str,
|
|
@@ -167,7 +167,7 @@ def write(
|
|
|
167
167
|
|
|
168
168
|
Parameters
|
|
169
169
|
----------
|
|
170
|
-
path :
|
|
170
|
+
path : StrPath
|
|
171
171
|
Path to the XML file on disk.
|
|
172
172
|
data : JSONData
|
|
173
173
|
Data to write as XML.
|
|
@@ -179,6 +179,7 @@ def write(
|
|
|
179
179
|
int
|
|
180
180
|
The number of records written to the XML file.
|
|
181
181
|
"""
|
|
182
|
+
path = coerce_path(path)
|
|
182
183
|
if isinstance(data, dict) and len(data) == 1:
|
|
183
184
|
root_name, payload = next(iter(data.items()))
|
|
184
185
|
root_element = _dict_to_element(str(root_name), payload)
|
|
@@ -186,6 +187,7 @@ def write(
|
|
|
186
187
|
root_element = _dict_to_element(root_tag, data)
|
|
187
188
|
|
|
188
189
|
tree = ET.ElementTree(root_element)
|
|
190
|
+
ensure_parent_dir(path)
|
|
189
191
|
tree.write(path, encoding='utf-8', xml_declaration=True)
|
|
190
192
|
|
|
191
193
|
return count_records(data)
|
etlplus/file/xpt.py
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
:mod:`etlplus.file.xpt` module.
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
yet).
|
|
4
|
+
Helpers for reading/writing SAS Transport (XPT) files.
|
|
6
5
|
|
|
7
6
|
Notes
|
|
8
7
|
-----
|
|
@@ -19,11 +18,16 @@ Notes
|
|
|
19
18
|
|
|
20
19
|
from __future__ import annotations
|
|
21
20
|
|
|
22
|
-
from
|
|
21
|
+
from typing import cast
|
|
23
22
|
|
|
24
23
|
from ..types import JSONData
|
|
25
24
|
from ..types import JSONList
|
|
26
|
-
from
|
|
25
|
+
from ..types import StrPath
|
|
26
|
+
from ._imports import get_dependency
|
|
27
|
+
from ._imports import get_pandas
|
|
28
|
+
from ._io import coerce_path
|
|
29
|
+
from ._io import ensure_parent_dir
|
|
30
|
+
from ._io import normalize_records
|
|
27
31
|
|
|
28
32
|
# SECTION: EXPORTS ========================================================== #
|
|
29
33
|
|
|
@@ -39,14 +43,14 @@ __all__ = [
|
|
|
39
43
|
|
|
40
44
|
|
|
41
45
|
def read(
|
|
42
|
-
path:
|
|
46
|
+
path: StrPath,
|
|
43
47
|
) -> JSONList:
|
|
44
48
|
"""
|
|
45
49
|
Read XPT content from *path*.
|
|
46
50
|
|
|
47
51
|
Parameters
|
|
48
52
|
----------
|
|
49
|
-
path :
|
|
53
|
+
path : StrPath
|
|
50
54
|
Path to the XPT file on disk.
|
|
51
55
|
|
|
52
56
|
Returns
|
|
@@ -54,11 +58,22 @@ def read(
|
|
|
54
58
|
JSONList
|
|
55
59
|
The list of dictionaries read from the XPT file.
|
|
56
60
|
"""
|
|
57
|
-
|
|
61
|
+
path = coerce_path(path)
|
|
62
|
+
pandas = get_pandas('XPT')
|
|
63
|
+
pyreadstat = get_dependency('pyreadstat', format_name='XPT')
|
|
64
|
+
reader = getattr(pyreadstat, 'read_xport', None)
|
|
65
|
+
if reader is not None:
|
|
66
|
+
frame, _meta = reader(str(path))
|
|
67
|
+
return cast(JSONList, frame.to_dict(orient='records'))
|
|
68
|
+
try:
|
|
69
|
+
frame = pandas.read_sas(path, format='xport')
|
|
70
|
+
except TypeError:
|
|
71
|
+
frame = pandas.read_sas(path)
|
|
72
|
+
return cast(JSONList, frame.to_dict(orient='records'))
|
|
58
73
|
|
|
59
74
|
|
|
60
75
|
def write(
|
|
61
|
-
path:
|
|
76
|
+
path: StrPath,
|
|
62
77
|
data: JSONData,
|
|
63
78
|
) -> int:
|
|
64
79
|
"""
|
|
@@ -66,7 +81,7 @@ def write(
|
|
|
66
81
|
|
|
67
82
|
Parameters
|
|
68
83
|
----------
|
|
69
|
-
path :
|
|
84
|
+
path : StrPath
|
|
70
85
|
Path to the XPT file on disk.
|
|
71
86
|
data : JSONData
|
|
72
87
|
Data to write as XPT file. Should be a list of dictionaries or a
|
|
@@ -76,5 +91,26 @@ def write(
|
|
|
76
91
|
-------
|
|
77
92
|
int
|
|
78
93
|
The number of rows written to the XPT file.
|
|
94
|
+
|
|
95
|
+
Raises
|
|
96
|
+
------
|
|
97
|
+
ImportError
|
|
98
|
+
If "pyreadstat" is not installed with write support.
|
|
79
99
|
"""
|
|
80
|
-
|
|
100
|
+
path = coerce_path(path)
|
|
101
|
+
records = normalize_records(data, 'XPT')
|
|
102
|
+
if not records:
|
|
103
|
+
return 0
|
|
104
|
+
|
|
105
|
+
pandas = get_pandas('XPT')
|
|
106
|
+
pyreadstat = get_dependency('pyreadstat', format_name='XPT')
|
|
107
|
+
writer = getattr(pyreadstat, 'write_xport', None)
|
|
108
|
+
if writer is None:
|
|
109
|
+
raise ImportError(
|
|
110
|
+
'XPT write support requires "pyreadstat" with write_xport().',
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
ensure_parent_dir(path)
|
|
114
|
+
frame = pandas.DataFrame.from_records(records)
|
|
115
|
+
writer(frame, str(path))
|
|
116
|
+
return len(records)
|
etlplus/file/yaml.py
CHANGED
|
@@ -17,12 +17,13 @@ Notes
|
|
|
17
17
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
|
-
from pathlib import Path
|
|
21
|
-
|
|
22
20
|
from ..types import JSONData
|
|
21
|
+
from ..types import StrPath
|
|
23
22
|
from ..utils import count_records
|
|
24
23
|
from ._imports import get_yaml
|
|
24
|
+
from ._io import coerce_path
|
|
25
25
|
from ._io import coerce_record_payload
|
|
26
|
+
from ._io import ensure_parent_dir
|
|
26
27
|
|
|
27
28
|
# SECTION: EXPORTS ========================================================== #
|
|
28
29
|
|
|
@@ -38,7 +39,7 @@ __all__ = [
|
|
|
38
39
|
|
|
39
40
|
|
|
40
41
|
def read(
|
|
41
|
-
path:
|
|
42
|
+
path: StrPath,
|
|
42
43
|
) -> JSONData:
|
|
43
44
|
"""
|
|
44
45
|
Read YAML content from *path*.
|
|
@@ -47,19 +48,15 @@ def read(
|
|
|
47
48
|
|
|
48
49
|
Parameters
|
|
49
50
|
----------
|
|
50
|
-
path :
|
|
51
|
+
path : StrPath
|
|
51
52
|
Path to the YAML file on disk.
|
|
52
53
|
|
|
53
54
|
Returns
|
|
54
55
|
-------
|
|
55
56
|
JSONData
|
|
56
57
|
The structured data read from the YAML file.
|
|
57
|
-
|
|
58
|
-
Raises
|
|
59
|
-
------
|
|
60
|
-
TypeError
|
|
61
|
-
If the YAML root is not an object or an array of objects.
|
|
62
58
|
"""
|
|
59
|
+
path = coerce_path(path)
|
|
63
60
|
with path.open('r', encoding='utf-8') as handle:
|
|
64
61
|
loaded = get_yaml().safe_load(handle)
|
|
65
62
|
|
|
@@ -67,7 +64,7 @@ def read(
|
|
|
67
64
|
|
|
68
65
|
|
|
69
66
|
def write(
|
|
70
|
-
path:
|
|
67
|
+
path: StrPath,
|
|
71
68
|
data: JSONData,
|
|
72
69
|
) -> int:
|
|
73
70
|
"""
|
|
@@ -75,7 +72,7 @@ def write(
|
|
|
75
72
|
|
|
76
73
|
Parameters
|
|
77
74
|
----------
|
|
78
|
-
path :
|
|
75
|
+
path : StrPath
|
|
79
76
|
Path to the YAML file on disk.
|
|
80
77
|
data : JSONData
|
|
81
78
|
Data to write as YAML.
|
|
@@ -85,6 +82,8 @@ def write(
|
|
|
85
82
|
int
|
|
86
83
|
The number of records written.
|
|
87
84
|
"""
|
|
85
|
+
path = coerce_path(path)
|
|
86
|
+
ensure_parent_dir(path)
|
|
88
87
|
with path.open('w', encoding='utf-8') as handle:
|
|
89
88
|
get_yaml().safe_dump(
|
|
90
89
|
data,
|