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.
Files changed (52) hide show
  1. pythonwrench/__init__.py +490 -0
  2. pythonwrench/__main__.py +7 -0
  3. pythonwrench/_core.py +192 -0
  4. pythonwrench/abc.py +30 -0
  5. pythonwrench/argparse/__init__.py +81 -0
  6. pythonwrench/argparse/dataclass_.py +284 -0
  7. pythonwrench/argparse/parsers.py +619 -0
  8. pythonwrench/cast.py +247 -0
  9. pythonwrench/checksum.py +427 -0
  10. pythonwrench/collections/__init__.py +104 -0
  11. pythonwrench/collections/collections.py +900 -0
  12. pythonwrench/collections/prop.py +104 -0
  13. pythonwrench/collections/reducers.py +330 -0
  14. pythonwrench/concurrent.py +73 -0
  15. pythonwrench/csv.py +12 -0
  16. pythonwrench/dataclasses.py +117 -0
  17. pythonwrench/datetime.py +17 -0
  18. pythonwrench/difflib.py +39 -0
  19. pythonwrench/disk_cache.py +615 -0
  20. pythonwrench/entrypoints/info.py +44 -0
  21. pythonwrench/entrypoints/safe_rmdir.py +98 -0
  22. pythonwrench/entrypoints/tree.py +113 -0
  23. pythonwrench/enum.py +55 -0
  24. pythonwrench/functools.py +234 -0
  25. pythonwrench/hashlib.py +95 -0
  26. pythonwrench/importlib.py +243 -0
  27. pythonwrench/inspect.py +69 -0
  28. pythonwrench/json.py +12 -0
  29. pythonwrench/jsonl.py +12 -0
  30. pythonwrench/logging.py +252 -0
  31. pythonwrench/math.py +107 -0
  32. pythonwrench/os.py +226 -0
  33. pythonwrench/pickle.py +12 -0
  34. pythonwrench/random.py +60 -0
  35. pythonwrench/re.py +139 -0
  36. pythonwrench/semver.py +406 -0
  37. pythonwrench/serialization/__init__.py +70 -0
  38. pythonwrench/serialization/_core.py +70 -0
  39. pythonwrench/serialization/csv.py +493 -0
  40. pythonwrench/serialization/json.py +178 -0
  41. pythonwrench/serialization/jsonl.py +215 -0
  42. pythonwrench/serialization/pickle.py +186 -0
  43. pythonwrench/time.py +34 -0
  44. pythonwrench/typing/__init__.py +125 -0
  45. pythonwrench/typing/checks.py +551 -0
  46. pythonwrench/typing/classes.py +251 -0
  47. pythonwrench/warnings.py +118 -0
  48. pythonwrench-0.6.4.dist-info/METADATA +242 -0
  49. pythonwrench-0.6.4.dist-info/RECORD +52 -0
  50. pythonwrench-0.6.4.dist-info/WHEEL +4 -0
  51. pythonwrench-0.6.4.dist-info/entry_points.txt +10 -0
  52. 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)