pythonwrench 0.6.4__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.
- pythonwrench/__init__.py +490 -0
- pythonwrench/__main__.py +7 -0
- pythonwrench/_core.py +192 -0
- pythonwrench/abc.py +30 -0
- pythonwrench/argparse/__init__.py +81 -0
- pythonwrench/argparse/dataclass_.py +284 -0
- pythonwrench/argparse/parsers.py +619 -0
- pythonwrench/cast.py +247 -0
- pythonwrench/checksum.py +427 -0
- pythonwrench/collections/__init__.py +104 -0
- pythonwrench/collections/collections.py +900 -0
- pythonwrench/collections/prop.py +104 -0
- pythonwrench/collections/reducers.py +330 -0
- pythonwrench/concurrent.py +73 -0
- pythonwrench/csv.py +12 -0
- pythonwrench/dataclasses.py +117 -0
- pythonwrench/datetime.py +17 -0
- pythonwrench/difflib.py +39 -0
- pythonwrench/disk_cache.py +615 -0
- pythonwrench/entrypoints/info.py +44 -0
- pythonwrench/entrypoints/safe_rmdir.py +98 -0
- pythonwrench/entrypoints/tree.py +113 -0
- pythonwrench/enum.py +55 -0
- pythonwrench/functools.py +234 -0
- pythonwrench/hashlib.py +95 -0
- pythonwrench/importlib.py +243 -0
- pythonwrench/inspect.py +69 -0
- pythonwrench/json.py +12 -0
- pythonwrench/jsonl.py +12 -0
- pythonwrench/logging.py +252 -0
- pythonwrench/math.py +107 -0
- pythonwrench/os.py +226 -0
- pythonwrench/pickle.py +12 -0
- pythonwrench/random.py +60 -0
- pythonwrench/re.py +139 -0
- pythonwrench/semver.py +406 -0
- pythonwrench/serialization/__init__.py +70 -0
- pythonwrench/serialization/_core.py +70 -0
- pythonwrench/serialization/csv.py +493 -0
- pythonwrench/serialization/json.py +178 -0
- pythonwrench/serialization/jsonl.py +215 -0
- pythonwrench/serialization/pickle.py +186 -0
- pythonwrench/time.py +34 -0
- pythonwrench/typing/__init__.py +125 -0
- pythonwrench/typing/checks.py +551 -0
- pythonwrench/typing/classes.py +251 -0
- pythonwrench/warnings.py +118 -0
- pythonwrench-0.6.4.dist-info/METADATA +242 -0
- pythonwrench-0.6.4.dist-info/RECORD +52 -0
- pythonwrench-0.6.4.dist-info/WHEEL +4 -0
- pythonwrench-0.6.4.dist-info/entry_points.txt +10 -0
- pythonwrench-0.6.4.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
import csv
|
|
5
|
+
import io
|
|
6
|
+
from csv import DictReader, DictWriter
|
|
7
|
+
from io import TextIOBase
|
|
8
|
+
from os import PathLike
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import (
|
|
11
|
+
Any,
|
|
12
|
+
Dict,
|
|
13
|
+
Iterable,
|
|
14
|
+
List,
|
|
15
|
+
Literal,
|
|
16
|
+
Mapping,
|
|
17
|
+
Optional,
|
|
18
|
+
TypeVar,
|
|
19
|
+
Union,
|
|
20
|
+
get_args,
|
|
21
|
+
overload,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
from pythonwrench.cast import as_builtin
|
|
25
|
+
from pythonwrench.collections import dict_list_to_list_dict, list_dict_to_dict_list
|
|
26
|
+
from pythonwrench.functools import function_alias
|
|
27
|
+
from pythonwrench.serialization._core import _setup_output_fpath
|
|
28
|
+
from pythonwrench.typing import isinstance_generic
|
|
29
|
+
|
|
30
|
+
T = TypeVar("T")
|
|
31
|
+
|
|
32
|
+
Orient = Literal["list", "dict"]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# -- Dump / Save / Serialize content to CSV --
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def dump_csv(
|
|
39
|
+
data: Union[Iterable[Mapping[str, Any]], Mapping[str, Iterable[Any]], Iterable],
|
|
40
|
+
file: Union[str, Path, PathLike, TextIOBase, None] = None,
|
|
41
|
+
/,
|
|
42
|
+
*,
|
|
43
|
+
overwrite: bool = True,
|
|
44
|
+
make_parents: bool = True,
|
|
45
|
+
to_builtins: bool = False,
|
|
46
|
+
header: Union[bool, Literal["auto"]] = "auto",
|
|
47
|
+
align_content: bool = False,
|
|
48
|
+
replace_newline_by: Optional[str] = "\\n",
|
|
49
|
+
**csv_writer_kwds,
|
|
50
|
+
) -> str:
|
|
51
|
+
r"""Dump content to CSV format into string and/or file.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
data: Data to serialize. Can be a list of dicts, dicts of lists or list of lists.
|
|
55
|
+
file: File path or buffer to write serialized data.
|
|
56
|
+
overwrite: If True, overwrite target filepath. defaults to True.
|
|
57
|
+
make_parents: Build intermediate directories to filepath. defaults to True.
|
|
58
|
+
to_builtins: If True, converts data to builtin equivalent before saving. defaults to False.
|
|
59
|
+
header: Indicates if CSV must have header. If "auto", an header is added when a dict of list or list of dicts is passed. defaults to "auto".
|
|
60
|
+
align_content: If True, center content at the middle of each row for better visualization. defaults to False.
|
|
61
|
+
replace_newline_by: Replace newline character to avoid newline in CSV content. defaults to "\\n".
|
|
62
|
+
\*\*csv_writer_kwds: Others optional arguments passed to CSV writer object.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
Dumped content as string.
|
|
66
|
+
"""
|
|
67
|
+
content = dumps_csv(
|
|
68
|
+
data,
|
|
69
|
+
to_builtins=to_builtins,
|
|
70
|
+
header=header,
|
|
71
|
+
align_content=align_content,
|
|
72
|
+
replace_newline_by=replace_newline_by,
|
|
73
|
+
**csv_writer_kwds,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
if isinstance(file, (str, Path, PathLike)):
|
|
77
|
+
file = _setup_output_fpath(file, overwrite=overwrite, make_parents=make_parents)
|
|
78
|
+
with open(file, "w") as opened_file:
|
|
79
|
+
opened_file.write(content)
|
|
80
|
+
elif isinstance(file, TextIOBase):
|
|
81
|
+
file.write(content)
|
|
82
|
+
elif file is None:
|
|
83
|
+
pass
|
|
84
|
+
else:
|
|
85
|
+
msg = f"Invalid argument type {type(file)}. (expected one of str, Path, TextIOBase, None)"
|
|
86
|
+
raise TypeError(msg)
|
|
87
|
+
|
|
88
|
+
return content
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def dumps_csv(
|
|
92
|
+
data: Union[Iterable[Mapping[str, Any]], Mapping[str, Iterable[Any]], Iterable],
|
|
93
|
+
/,
|
|
94
|
+
*,
|
|
95
|
+
to_builtins: bool = False,
|
|
96
|
+
header: Union[bool, Literal["auto"]] = "auto",
|
|
97
|
+
align_content: bool = False,
|
|
98
|
+
replace_newline_by: Optional[str] = "\\n",
|
|
99
|
+
**csv_writer_kwds,
|
|
100
|
+
) -> str:
|
|
101
|
+
r"""Dump content to CSV format into string.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
data: Data to serialize. Can be a list of dicts, dicts of lists or list of lists.
|
|
105
|
+
overwrite: If True, overwrite target filepath. defaults to True.
|
|
106
|
+
make_parents: Build intermediate directories to filepath. defaults to True.
|
|
107
|
+
to_builtins: If True, converts data to builtin equivalent before saving. defaults to False.
|
|
108
|
+
header: Indicates if CSV must have header. If "auto", an header is added when a dict of list or list of dicts is passed. defaults to "auto".
|
|
109
|
+
align_content: If True, center content at the middle of each row for better visualization. defaults to False.
|
|
110
|
+
replace_newline_by: Replace newline character to avoid newline in CSV content. defaults to "\\n".
|
|
111
|
+
\*\*csv_writer_kwds: Others optional arguments passed to CSV writer object.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Dumped content as string.
|
|
115
|
+
"""
|
|
116
|
+
with io.StringIO() as buffer:
|
|
117
|
+
_serialize_csv(
|
|
118
|
+
data,
|
|
119
|
+
buffer,
|
|
120
|
+
to_builtins=to_builtins,
|
|
121
|
+
header=header,
|
|
122
|
+
align_content=align_content,
|
|
123
|
+
replace_newline_by=replace_newline_by,
|
|
124
|
+
**csv_writer_kwds,
|
|
125
|
+
)
|
|
126
|
+
content = buffer.getvalue()
|
|
127
|
+
return content
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def save_csv(
|
|
131
|
+
data: Union[Iterable[Mapping[str, Any]], Mapping[str, Iterable[Any]], Iterable],
|
|
132
|
+
file: Union[str, Path, PathLike, TextIOBase],
|
|
133
|
+
/,
|
|
134
|
+
*,
|
|
135
|
+
overwrite: bool = True,
|
|
136
|
+
make_parents: bool = True,
|
|
137
|
+
to_builtins: bool = False,
|
|
138
|
+
header: Union[bool, Literal["auto"]] = "auto",
|
|
139
|
+
align_content: bool = False,
|
|
140
|
+
replace_newline_by: Optional[str] = "\\n",
|
|
141
|
+
**csv_writer_kwds,
|
|
142
|
+
) -> None:
|
|
143
|
+
r"""Save content to CSV format into a file or buffer.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
data: Data to serialize. Can be a list of dicts, dicts of lists or list of lists.
|
|
147
|
+
overwrite: If True, overwrite target filepath. defaults to True.
|
|
148
|
+
make_parents: Build intermediate directories to filepath. defaults to True.
|
|
149
|
+
to_builtins: If True, converts data to builtin equivalent before saving. defaults to False.
|
|
150
|
+
header: Indicates if CSV must have header. If "auto", an header is added when a dict of list or list of dicts is passed. defaults to "auto".
|
|
151
|
+
align_content: If True, center content at the middle of each row for better visualization. defaults to False.
|
|
152
|
+
replace_newline_by: Replace newline character to avoid newline in CSV content. defaults to "\\n".
|
|
153
|
+
\*\*csv_writer_kwds: Others optional arguments passed to CSV writer object.
|
|
154
|
+
"""
|
|
155
|
+
if isinstance(file, (str, Path, PathLike)):
|
|
156
|
+
file = _setup_output_fpath(file, overwrite=overwrite, make_parents=make_parents)
|
|
157
|
+
file = open(file, "w")
|
|
158
|
+
close = True
|
|
159
|
+
elif isinstance(file, TextIOBase):
|
|
160
|
+
close = False
|
|
161
|
+
else:
|
|
162
|
+
msg = f"Invalid argument type {type(file)}. (expected one of str, Path, PathLike, TextIOBase)"
|
|
163
|
+
raise TypeError(msg)
|
|
164
|
+
|
|
165
|
+
_serialize_csv(
|
|
166
|
+
data,
|
|
167
|
+
file,
|
|
168
|
+
to_builtins=to_builtins,
|
|
169
|
+
header=header,
|
|
170
|
+
align_content=align_content,
|
|
171
|
+
replace_newline_by=replace_newline_by,
|
|
172
|
+
**csv_writer_kwds,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
if close:
|
|
176
|
+
file.close()
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _serialize_csv(
|
|
180
|
+
data: Union[Iterable[Mapping[str, Any]], Mapping[str, Iterable[Any]], Iterable],
|
|
181
|
+
buffer: TextIOBase,
|
|
182
|
+
*,
|
|
183
|
+
to_builtins: bool = False,
|
|
184
|
+
header: Union[bool, Literal["auto"]] = "auto",
|
|
185
|
+
align_content: bool = False,
|
|
186
|
+
replace_newline_by: Optional[str] = "\\n",
|
|
187
|
+
**csv_writer_kwds,
|
|
188
|
+
) -> None:
|
|
189
|
+
"""Perform the serialize csv operation."""
|
|
190
|
+
if to_builtins:
|
|
191
|
+
data = as_builtin(data)
|
|
192
|
+
|
|
193
|
+
is_mapping_iterable = isinstance_generic(data, Mapping[str, Iterable])
|
|
194
|
+
if is_mapping_iterable:
|
|
195
|
+
is_iterable_mapping = False
|
|
196
|
+
else:
|
|
197
|
+
is_iterable_mapping = isinstance_generic(data, Iterable[Mapping[str, Any]])
|
|
198
|
+
|
|
199
|
+
if header == "auto":
|
|
200
|
+
header = is_mapping_iterable or is_iterable_mapping
|
|
201
|
+
|
|
202
|
+
if is_mapping_iterable:
|
|
203
|
+
data_lst = dict_list_to_list_dict(data, "same")
|
|
204
|
+
elif is_iterable_mapping:
|
|
205
|
+
data_lst = [dict(data_i.items()) for data_i in data]
|
|
206
|
+
elif isinstance(data, str):
|
|
207
|
+
msg = f"Invalid argument type {type(data)}."
|
|
208
|
+
raise TypeError(msg)
|
|
209
|
+
elif not header and isinstance_generic(data, Iterable[str]):
|
|
210
|
+
data_lst = [{"0": data_i} for data_i in data]
|
|
211
|
+
elif not header and isinstance_generic(data, Iterable[Iterable]):
|
|
212
|
+
data_lst = [dict(zip(map(str, range(len(data_i))), data)) for data_i in data]
|
|
213
|
+
elif not header and isinstance(data, Iterable):
|
|
214
|
+
data_lst = [{"0": data_i} for data_i in data]
|
|
215
|
+
else:
|
|
216
|
+
msg = f"Invalid argument type {type(data)} with {header=}."
|
|
217
|
+
raise TypeError(msg)
|
|
218
|
+
del data
|
|
219
|
+
|
|
220
|
+
if header:
|
|
221
|
+
writer_cls = DictWriter
|
|
222
|
+
else:
|
|
223
|
+
writer_cls = csv.writer
|
|
224
|
+
|
|
225
|
+
if len(data_lst) == 0:
|
|
226
|
+
fieldnames = []
|
|
227
|
+
else:
|
|
228
|
+
fieldnames = [str(k) for k in data_lst[0].keys()]
|
|
229
|
+
|
|
230
|
+
if align_content:
|
|
231
|
+
old_fieldnames = fieldnames
|
|
232
|
+
data_lst = _stringify(data_lst)
|
|
233
|
+
fieldnames = _stringify(fieldnames)
|
|
234
|
+
max_num_chars = {
|
|
235
|
+
k: max(max(len(data_i[k]) for data_i in data_lst), len(k)) + 1
|
|
236
|
+
for k in fieldnames
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
fieldnames = [f"{{:^{max_num_chars[k]}s}}".format(k) for k in fieldnames]
|
|
240
|
+
old_to_new_fieldnames = dict(zip(old_fieldnames, fieldnames))
|
|
241
|
+
|
|
242
|
+
data_lst = [
|
|
243
|
+
{
|
|
244
|
+
old_to_new_fieldnames[k]: f"{{:^{max_num_chars[k]}s}}".format(v)
|
|
245
|
+
for k, v in data_i.items()
|
|
246
|
+
}
|
|
247
|
+
for data_i in data_lst
|
|
248
|
+
]
|
|
249
|
+
|
|
250
|
+
if replace_newline_by is not None:
|
|
251
|
+
|
|
252
|
+
def _replace_newline(s):
|
|
253
|
+
"""Perform the replace newline operation."""
|
|
254
|
+
if not isinstance(s, str):
|
|
255
|
+
return s
|
|
256
|
+
else:
|
|
257
|
+
return s.replace("\n", replace_newline_by)
|
|
258
|
+
|
|
259
|
+
data_lst = [
|
|
260
|
+
{_replace_newline(k): _replace_newline(v) for k, v in data_i.items()}
|
|
261
|
+
for data_i in data_lst
|
|
262
|
+
]
|
|
263
|
+
|
|
264
|
+
if header:
|
|
265
|
+
csv_writer_kwds["fieldnames"] = fieldnames
|
|
266
|
+
|
|
267
|
+
writer = writer_cls(buffer, **csv_writer_kwds)
|
|
268
|
+
if isinstance(writer, DictWriter):
|
|
269
|
+
writer.writeheader()
|
|
270
|
+
writer.writerows(data_lst)
|
|
271
|
+
else:
|
|
272
|
+
data_lst = [tuple(data_i.values()) for data_i in data_lst]
|
|
273
|
+
writer.writerows(data_lst)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def _stringify(x: Any) -> Any:
|
|
277
|
+
"""Perform the stringify operation."""
|
|
278
|
+
if isinstance(x, str):
|
|
279
|
+
return x
|
|
280
|
+
elif isinstance(x, dict):
|
|
281
|
+
return {_stringify(k): _stringify(v) for k, v in x.items()} # type: ignore
|
|
282
|
+
elif isinstance(x, (list, tuple, set, frozenset)):
|
|
283
|
+
return type(x)(_stringify(xi) for xi in x)
|
|
284
|
+
else:
|
|
285
|
+
return str(x)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
# -- Load / Read / Parse CSV content --
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
@overload
|
|
292
|
+
def load_csv(
|
|
293
|
+
file: Union[str, Path, TextIOBase],
|
|
294
|
+
/,
|
|
295
|
+
*,
|
|
296
|
+
orient: Literal["list"] = "list",
|
|
297
|
+
header: bool = True,
|
|
298
|
+
comment_start: Optional[str] = None,
|
|
299
|
+
strip_content: bool = False,
|
|
300
|
+
# CSV reader kwargs
|
|
301
|
+
delimiter: Optional[str] = None,
|
|
302
|
+
**csv_reader_kwds,
|
|
303
|
+
) -> List[Dict[str, Any]]:
|
|
304
|
+
"""Load csv."""
|
|
305
|
+
...
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
@overload
|
|
309
|
+
def load_csv(
|
|
310
|
+
file: Union[str, Path, TextIOBase],
|
|
311
|
+
/,
|
|
312
|
+
*,
|
|
313
|
+
orient: Literal["dict"],
|
|
314
|
+
header: bool = True,
|
|
315
|
+
comment_start: Optional[str] = None,
|
|
316
|
+
strip_content: bool = False,
|
|
317
|
+
# CSV reader kwargs
|
|
318
|
+
delimiter: Optional[str] = None,
|
|
319
|
+
**csv_reader_kwds,
|
|
320
|
+
) -> Dict[str, List[Any]]:
|
|
321
|
+
"""Load csv."""
|
|
322
|
+
...
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def load_csv(
|
|
326
|
+
file: Union[str, Path, TextIOBase],
|
|
327
|
+
/,
|
|
328
|
+
*,
|
|
329
|
+
orient: Orient = "list",
|
|
330
|
+
header: bool = True,
|
|
331
|
+
comment_start: Optional[str] = None,
|
|
332
|
+
strip_content: bool = False,
|
|
333
|
+
# CSV reader kwargs
|
|
334
|
+
delimiter: Optional[str] = ",",
|
|
335
|
+
**csv_reader_kwds,
|
|
336
|
+
) -> Union[List[Dict[str, Any]], Dict[str, List[Any]]]:
|
|
337
|
+
r"""Load content from csv filepath.
|
|
338
|
+
|
|
339
|
+
Args:
|
|
340
|
+
orient: Orientation of the output value. Can be "list" or "dict". defaults to "list".
|
|
341
|
+
header: Specify if CSV has header column. defaults to True.
|
|
342
|
+
comment_start: If this string is not None and a line starts with this string, the line will be ignored. defaults to None.
|
|
343
|
+
delimiter: Value delimiter. defaults to ",".
|
|
344
|
+
\*\*csv_reader_kwds: Other optional csv arguments.
|
|
345
|
+
|
|
346
|
+
Returns:
|
|
347
|
+
The loaded values as dict of lists, list of dicts or list of lists.
|
|
348
|
+
"""
|
|
349
|
+
if isinstance(file, (str, Path, PathLike)):
|
|
350
|
+
file = Path(file)
|
|
351
|
+
if delimiter is None:
|
|
352
|
+
delimiter = "\t" if file.suffix == ".tsv" else ","
|
|
353
|
+
|
|
354
|
+
file = file.open("r")
|
|
355
|
+
close = True
|
|
356
|
+
else:
|
|
357
|
+
close = False
|
|
358
|
+
|
|
359
|
+
data = _parse_csv(
|
|
360
|
+
file,
|
|
361
|
+
orient=orient,
|
|
362
|
+
header=header,
|
|
363
|
+
comment_start=comment_start,
|
|
364
|
+
strip_content=strip_content,
|
|
365
|
+
delimiter=delimiter,
|
|
366
|
+
**csv_reader_kwds,
|
|
367
|
+
)
|
|
368
|
+
if close:
|
|
369
|
+
file.close()
|
|
370
|
+
return data
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
@overload
|
|
374
|
+
def loads_csv(
|
|
375
|
+
content: str,
|
|
376
|
+
/,
|
|
377
|
+
*,
|
|
378
|
+
orient: Literal["list"] = "list",
|
|
379
|
+
header: bool = True,
|
|
380
|
+
comment_start: Optional[str] = None,
|
|
381
|
+
strip_content: bool = False,
|
|
382
|
+
# CSV reader kwargs
|
|
383
|
+
delimiter: Optional[str] = ",",
|
|
384
|
+
**csv_reader_kwds,
|
|
385
|
+
) -> List[Dict[str, Any]]:
|
|
386
|
+
"""Load s csv."""
|
|
387
|
+
...
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
@overload
|
|
391
|
+
def loads_csv(
|
|
392
|
+
content: str,
|
|
393
|
+
/,
|
|
394
|
+
*,
|
|
395
|
+
orient: Literal["dict"],
|
|
396
|
+
header: bool = True,
|
|
397
|
+
comment_start: Optional[str] = None,
|
|
398
|
+
strip_content: bool = False,
|
|
399
|
+
# CSV reader kwargs
|
|
400
|
+
delimiter: Optional[str] = ",",
|
|
401
|
+
**csv_reader_kwds,
|
|
402
|
+
) -> Dict[str, List[Any]]:
|
|
403
|
+
"""Load s csv."""
|
|
404
|
+
...
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def loads_csv(
|
|
408
|
+
content: str,
|
|
409
|
+
/,
|
|
410
|
+
*,
|
|
411
|
+
orient: Orient = "list",
|
|
412
|
+
header: bool = True,
|
|
413
|
+
comment_start: Optional[str] = None,
|
|
414
|
+
strip_content: bool = False,
|
|
415
|
+
# CSV reader kwargs
|
|
416
|
+
delimiter: Optional[str] = ",",
|
|
417
|
+
**csv_reader_kwds,
|
|
418
|
+
) -> Union[List[Dict[str, Any]], Dict[str, List[Any]]]:
|
|
419
|
+
"""Load s csv."""
|
|
420
|
+
with io.StringIO(content) as buffer:
|
|
421
|
+
return _parse_csv(
|
|
422
|
+
buffer,
|
|
423
|
+
orient=orient,
|
|
424
|
+
header=header,
|
|
425
|
+
comment_start=comment_start,
|
|
426
|
+
strip_content=strip_content,
|
|
427
|
+
delimiter=delimiter,
|
|
428
|
+
**csv_reader_kwds,
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
@function_alias(load_csv)
|
|
433
|
+
def read_csv(*args, **kwargs):
|
|
434
|
+
"""Read csv."""
|
|
435
|
+
...
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def _parse_csv(
|
|
439
|
+
file: TextIOBase,
|
|
440
|
+
/,
|
|
441
|
+
*,
|
|
442
|
+
orient: Orient = "list",
|
|
443
|
+
header: bool = True,
|
|
444
|
+
comment_start: Optional[str] = None,
|
|
445
|
+
strip_content: bool = False,
|
|
446
|
+
# CSV reader kwargs
|
|
447
|
+
delimiter: Optional[str] = ",",
|
|
448
|
+
**csv_reader_kwds,
|
|
449
|
+
) -> Union[List[Dict[str, Any]], Dict[str, List[Any]]]:
|
|
450
|
+
"""Parse csv."""
|
|
451
|
+
if delimiter is None:
|
|
452
|
+
msg = f"Invalid argument {delimiter=}. (expected not None when {type(file)=})"
|
|
453
|
+
raise ValueError(msg)
|
|
454
|
+
|
|
455
|
+
if header:
|
|
456
|
+
reader_cls = DictReader
|
|
457
|
+
else:
|
|
458
|
+
reader_cls = csv.reader
|
|
459
|
+
|
|
460
|
+
reader = reader_cls(file, delimiter=delimiter, **csv_reader_kwds)
|
|
461
|
+
raw_data_lst = list(reader)
|
|
462
|
+
|
|
463
|
+
data_lst: List[Dict[str, Any]]
|
|
464
|
+
if header:
|
|
465
|
+
data_lst = raw_data_lst # type: ignore
|
|
466
|
+
else:
|
|
467
|
+
data_lst = [
|
|
468
|
+
{str(j): data_ij for j, data_ij in enumerate(data_i)}
|
|
469
|
+
for data_i in raw_data_lst
|
|
470
|
+
]
|
|
471
|
+
del raw_data_lst
|
|
472
|
+
|
|
473
|
+
if comment_start is not None:
|
|
474
|
+
data_lst = [
|
|
475
|
+
line
|
|
476
|
+
for line in data_lst
|
|
477
|
+
if not next(iter(line.values())).startswith(comment_start)
|
|
478
|
+
]
|
|
479
|
+
|
|
480
|
+
if strip_content:
|
|
481
|
+
data_lst = [
|
|
482
|
+
{k.strip(): v.strip() for k, v in data_i.items()} for data_i in data_lst
|
|
483
|
+
]
|
|
484
|
+
|
|
485
|
+
if orient == "dict":
|
|
486
|
+
result = list_dict_to_dict_list(data_lst, key_mode="same") # type: ignore
|
|
487
|
+
elif orient == "list":
|
|
488
|
+
result = data_lst
|
|
489
|
+
else:
|
|
490
|
+
msg = f"Invalid argument {orient=}. (expected one of {get_args(Orient)})"
|
|
491
|
+
raise ValueError(msg)
|
|
492
|
+
|
|
493
|
+
return result # type: ignore
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
from io import StringIO, TextIOBase
|
|
6
|
+
from os import PathLike
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Optional, Union
|
|
9
|
+
|
|
10
|
+
from pythonwrench.cast import as_builtin
|
|
11
|
+
from pythonwrench.functools import function_alias
|
|
12
|
+
from pythonwrench.serialization._core import _setup_output_fpath
|
|
13
|
+
|
|
14
|
+
# -- Dump / Save / Serialize content to JSON --
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def dump_json(
|
|
18
|
+
data: Any,
|
|
19
|
+
file: Union[str, Path, None, TextIOBase] = None,
|
|
20
|
+
/,
|
|
21
|
+
*,
|
|
22
|
+
overwrite: bool = True,
|
|
23
|
+
make_parents: bool = True,
|
|
24
|
+
to_builtins: bool = False,
|
|
25
|
+
# JSON dump kwargs
|
|
26
|
+
indent: Optional[int] = 4,
|
|
27
|
+
ensure_ascii: bool = False,
|
|
28
|
+
**json_dumps_kwds,
|
|
29
|
+
) -> str:
|
|
30
|
+
r"""Dump content to JSON format into a string and/or file.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
data: Data to dump to JSON.
|
|
34
|
+
file: Optional filepath to save dumped data. Not used if None. defaults to None.
|
|
35
|
+
overwrite: If True, overwrite target filepath. defaults to True.
|
|
36
|
+
make_parents: Build intermediate directories to filepath. defaults to True.
|
|
37
|
+
to_builtins: If True, converts data to builtin equivalent before saving. defaults to False.
|
|
38
|
+
indent: JSON indentation size in spaces. defaults to 4.
|
|
39
|
+
ensure_ascii: Ensure only ASCII characters. defaults to False.
|
|
40
|
+
\*\*json_dump_kwds: Other args passed to `json.dumps`.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Dumped content as string.
|
|
44
|
+
"""
|
|
45
|
+
content = dumps_json(
|
|
46
|
+
data,
|
|
47
|
+
to_builtins=to_builtins,
|
|
48
|
+
indent=indent,
|
|
49
|
+
ensure_ascii=ensure_ascii,
|
|
50
|
+
**json_dumps_kwds,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
if isinstance(file, (str, Path, PathLike)):
|
|
54
|
+
file = _setup_output_fpath(file, overwrite=overwrite, make_parents=make_parents)
|
|
55
|
+
with open(file, "w") as opened_file:
|
|
56
|
+
opened_file.write(content)
|
|
57
|
+
elif isinstance(file, TextIOBase):
|
|
58
|
+
file.write(content)
|
|
59
|
+
elif file is None:
|
|
60
|
+
pass
|
|
61
|
+
else:
|
|
62
|
+
msg = f"Invalid argument type {type(file)}. (expected one of str, Path, TextIOBase, None)"
|
|
63
|
+
raise TypeError(msg)
|
|
64
|
+
|
|
65
|
+
return content
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def dumps_json(
|
|
69
|
+
data: Any,
|
|
70
|
+
/,
|
|
71
|
+
*,
|
|
72
|
+
to_builtins: bool = False,
|
|
73
|
+
# JSON dump kwargs
|
|
74
|
+
indent: Optional[int] = 4,
|
|
75
|
+
ensure_ascii: bool = False,
|
|
76
|
+
**json_dumps_kwds,
|
|
77
|
+
) -> str:
|
|
78
|
+
"""Perform the dumps json operation."""
|
|
79
|
+
with StringIO() as buffer:
|
|
80
|
+
_serialize_json(
|
|
81
|
+
data,
|
|
82
|
+
buffer,
|
|
83
|
+
to_builtins=to_builtins,
|
|
84
|
+
indent=indent,
|
|
85
|
+
ensure_ascii=ensure_ascii,
|
|
86
|
+
**json_dumps_kwds,
|
|
87
|
+
)
|
|
88
|
+
content = buffer.getvalue()
|
|
89
|
+
return content
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def save_json(
|
|
93
|
+
data: Any,
|
|
94
|
+
file: Union[str, Path, PathLike, TextIOBase],
|
|
95
|
+
/,
|
|
96
|
+
*,
|
|
97
|
+
overwrite: bool = True,
|
|
98
|
+
make_parents: bool = True,
|
|
99
|
+
to_builtins: bool = False,
|
|
100
|
+
# JSON dump kwargs
|
|
101
|
+
indent: Optional[int] = 4,
|
|
102
|
+
ensure_ascii: bool = False,
|
|
103
|
+
**json_dumps_kwds,
|
|
104
|
+
) -> None:
|
|
105
|
+
"""Save json."""
|
|
106
|
+
if isinstance(file, (str, Path, PathLike)):
|
|
107
|
+
file = _setup_output_fpath(file, overwrite=overwrite, make_parents=make_parents)
|
|
108
|
+
file = open(file, "w")
|
|
109
|
+
close = True
|
|
110
|
+
elif isinstance(file, TextIOBase):
|
|
111
|
+
close = False
|
|
112
|
+
else:
|
|
113
|
+
msg = f"Invalid argument type {type(file)}. (expected one of str, Path, PathLike, TextIOBase)"
|
|
114
|
+
raise TypeError(msg)
|
|
115
|
+
|
|
116
|
+
_serialize_json(
|
|
117
|
+
data,
|
|
118
|
+
file,
|
|
119
|
+
to_builtins=to_builtins,
|
|
120
|
+
indent=indent,
|
|
121
|
+
ensure_ascii=ensure_ascii,
|
|
122
|
+
**json_dumps_kwds,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
if close:
|
|
126
|
+
file.close()
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _serialize_json(
|
|
130
|
+
data: Any,
|
|
131
|
+
buffer: TextIOBase,
|
|
132
|
+
/,
|
|
133
|
+
*,
|
|
134
|
+
to_builtins: bool = False,
|
|
135
|
+
**json_dumps_kwds,
|
|
136
|
+
) -> None:
|
|
137
|
+
"""Perform the serialize json operation."""
|
|
138
|
+
if to_builtins:
|
|
139
|
+
data = as_builtin(data)
|
|
140
|
+
return json.dump(data, buffer, **json_dumps_kwds)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# -- Load / Read / Parse JSON content --
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def load_json(
|
|
147
|
+
file: Union[str, Path, PathLike, TextIOBase],
|
|
148
|
+
/,
|
|
149
|
+
**json_loads_kwds,
|
|
150
|
+
) -> Any:
|
|
151
|
+
"""Load json."""
|
|
152
|
+
if isinstance(file, (str, Path, PathLike)):
|
|
153
|
+
file = open(file, "r")
|
|
154
|
+
close = True
|
|
155
|
+
else:
|
|
156
|
+
close = False
|
|
157
|
+
|
|
158
|
+
data = _parse_json(file, **json_loads_kwds)
|
|
159
|
+
if close:
|
|
160
|
+
file.close()
|
|
161
|
+
return data
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def loads_json(content: str, /, **json_loads_kwds) -> Any:
|
|
165
|
+
"""Load s json."""
|
|
166
|
+
with StringIO(content) as buffer:
|
|
167
|
+
return _parse_json(buffer, **json_loads_kwds)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@function_alias(load_json)
|
|
171
|
+
def read_json(*args, **kwargs):
|
|
172
|
+
"""Read json."""
|
|
173
|
+
...
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _parse_json(buffer: TextIOBase, **json_loads_kwds) -> Any:
|
|
177
|
+
"""Parse json."""
|
|
178
|
+
return json.load(buffer, **json_loads_kwds)
|