stouputils 1.14.0__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.
- stouputils/__init__.py +40 -0
- stouputils/__main__.py +86 -0
- stouputils/_deprecated.py +37 -0
- stouputils/all_doctests.py +160 -0
- stouputils/applications/__init__.py +22 -0
- stouputils/applications/automatic_docs.py +634 -0
- stouputils/applications/upscaler/__init__.py +39 -0
- stouputils/applications/upscaler/config.py +128 -0
- stouputils/applications/upscaler/image.py +247 -0
- stouputils/applications/upscaler/video.py +287 -0
- stouputils/archive.py +344 -0
- stouputils/backup.py +488 -0
- stouputils/collections.py +244 -0
- stouputils/continuous_delivery/__init__.py +27 -0
- stouputils/continuous_delivery/cd_utils.py +243 -0
- stouputils/continuous_delivery/github.py +522 -0
- stouputils/continuous_delivery/pypi.py +130 -0
- stouputils/continuous_delivery/pyproject.py +147 -0
- stouputils/continuous_delivery/stubs.py +86 -0
- stouputils/ctx.py +408 -0
- stouputils/data_science/config/get.py +51 -0
- stouputils/data_science/config/set.py +125 -0
- stouputils/data_science/data_processing/image/__init__.py +66 -0
- stouputils/data_science/data_processing/image/auto_contrast.py +79 -0
- stouputils/data_science/data_processing/image/axis_flip.py +58 -0
- stouputils/data_science/data_processing/image/bias_field_correction.py +74 -0
- stouputils/data_science/data_processing/image/binary_threshold.py +73 -0
- stouputils/data_science/data_processing/image/blur.py +59 -0
- stouputils/data_science/data_processing/image/brightness.py +54 -0
- stouputils/data_science/data_processing/image/canny.py +110 -0
- stouputils/data_science/data_processing/image/clahe.py +92 -0
- stouputils/data_science/data_processing/image/common.py +30 -0
- stouputils/data_science/data_processing/image/contrast.py +53 -0
- stouputils/data_science/data_processing/image/curvature_flow_filter.py +74 -0
- stouputils/data_science/data_processing/image/denoise.py +378 -0
- stouputils/data_science/data_processing/image/histogram_equalization.py +123 -0
- stouputils/data_science/data_processing/image/invert.py +64 -0
- stouputils/data_science/data_processing/image/laplacian.py +60 -0
- stouputils/data_science/data_processing/image/median_blur.py +52 -0
- stouputils/data_science/data_processing/image/noise.py +59 -0
- stouputils/data_science/data_processing/image/normalize.py +65 -0
- stouputils/data_science/data_processing/image/random_erase.py +66 -0
- stouputils/data_science/data_processing/image/resize.py +69 -0
- stouputils/data_science/data_processing/image/rotation.py +80 -0
- stouputils/data_science/data_processing/image/salt_pepper.py +68 -0
- stouputils/data_science/data_processing/image/sharpening.py +55 -0
- stouputils/data_science/data_processing/image/shearing.py +64 -0
- stouputils/data_science/data_processing/image/threshold.py +64 -0
- stouputils/data_science/data_processing/image/translation.py +71 -0
- stouputils/data_science/data_processing/image/zoom.py +83 -0
- stouputils/data_science/data_processing/image_augmentation.py +118 -0
- stouputils/data_science/data_processing/image_preprocess.py +183 -0
- stouputils/data_science/data_processing/prosthesis_detection.py +359 -0
- stouputils/data_science/data_processing/technique.py +481 -0
- stouputils/data_science/dataset/__init__.py +45 -0
- stouputils/data_science/dataset/dataset.py +292 -0
- stouputils/data_science/dataset/dataset_loader.py +135 -0
- stouputils/data_science/dataset/grouping_strategy.py +296 -0
- stouputils/data_science/dataset/image_loader.py +100 -0
- stouputils/data_science/dataset/xy_tuple.py +696 -0
- stouputils/data_science/metric_dictionnary.py +106 -0
- stouputils/data_science/metric_utils.py +847 -0
- stouputils/data_science/mlflow_utils.py +206 -0
- stouputils/data_science/models/abstract_model.py +149 -0
- stouputils/data_science/models/all.py +85 -0
- stouputils/data_science/models/base_keras.py +765 -0
- stouputils/data_science/models/keras/all.py +38 -0
- stouputils/data_science/models/keras/convnext.py +62 -0
- stouputils/data_science/models/keras/densenet.py +50 -0
- stouputils/data_science/models/keras/efficientnet.py +60 -0
- stouputils/data_science/models/keras/mobilenet.py +56 -0
- stouputils/data_science/models/keras/resnet.py +52 -0
- stouputils/data_science/models/keras/squeezenet.py +233 -0
- stouputils/data_science/models/keras/vgg.py +42 -0
- stouputils/data_science/models/keras/xception.py +38 -0
- stouputils/data_science/models/keras_utils/callbacks/__init__.py +20 -0
- stouputils/data_science/models/keras_utils/callbacks/colored_progress_bar.py +219 -0
- stouputils/data_science/models/keras_utils/callbacks/learning_rate_finder.py +148 -0
- stouputils/data_science/models/keras_utils/callbacks/model_checkpoint_v2.py +31 -0
- stouputils/data_science/models/keras_utils/callbacks/progressive_unfreezing.py +249 -0
- stouputils/data_science/models/keras_utils/callbacks/warmup_scheduler.py +66 -0
- stouputils/data_science/models/keras_utils/losses/__init__.py +12 -0
- stouputils/data_science/models/keras_utils/losses/next_generation_loss.py +56 -0
- stouputils/data_science/models/keras_utils/visualizations.py +416 -0
- stouputils/data_science/models/model_interface.py +939 -0
- stouputils/data_science/models/sandbox.py +116 -0
- stouputils/data_science/range_tuple.py +234 -0
- stouputils/data_science/scripts/augment_dataset.py +77 -0
- stouputils/data_science/scripts/exhaustive_process.py +133 -0
- stouputils/data_science/scripts/preprocess_dataset.py +70 -0
- stouputils/data_science/scripts/routine.py +168 -0
- stouputils/data_science/utils.py +285 -0
- stouputils/decorators.py +605 -0
- stouputils/image.py +441 -0
- stouputils/installer/__init__.py +18 -0
- stouputils/installer/common.py +67 -0
- stouputils/installer/downloader.py +101 -0
- stouputils/installer/linux.py +144 -0
- stouputils/installer/main.py +223 -0
- stouputils/installer/windows.py +136 -0
- stouputils/io.py +486 -0
- stouputils/parallel.py +483 -0
- stouputils/print.py +482 -0
- stouputils/py.typed +1 -0
- stouputils/stouputils/__init__.pyi +15 -0
- stouputils/stouputils/_deprecated.pyi +12 -0
- stouputils/stouputils/all_doctests.pyi +46 -0
- stouputils/stouputils/applications/__init__.pyi +2 -0
- stouputils/stouputils/applications/automatic_docs.pyi +106 -0
- stouputils/stouputils/applications/upscaler/__init__.pyi +3 -0
- stouputils/stouputils/applications/upscaler/config.pyi +18 -0
- stouputils/stouputils/applications/upscaler/image.pyi +109 -0
- stouputils/stouputils/applications/upscaler/video.pyi +60 -0
- stouputils/stouputils/archive.pyi +67 -0
- stouputils/stouputils/backup.pyi +109 -0
- stouputils/stouputils/collections.pyi +86 -0
- stouputils/stouputils/continuous_delivery/__init__.pyi +5 -0
- stouputils/stouputils/continuous_delivery/cd_utils.pyi +129 -0
- stouputils/stouputils/continuous_delivery/github.pyi +162 -0
- stouputils/stouputils/continuous_delivery/pypi.pyi +53 -0
- stouputils/stouputils/continuous_delivery/pyproject.pyi +67 -0
- stouputils/stouputils/continuous_delivery/stubs.pyi +39 -0
- stouputils/stouputils/ctx.pyi +211 -0
- stouputils/stouputils/decorators.pyi +252 -0
- stouputils/stouputils/image.pyi +172 -0
- stouputils/stouputils/installer/__init__.pyi +5 -0
- stouputils/stouputils/installer/common.pyi +39 -0
- stouputils/stouputils/installer/downloader.pyi +24 -0
- stouputils/stouputils/installer/linux.pyi +39 -0
- stouputils/stouputils/installer/main.pyi +57 -0
- stouputils/stouputils/installer/windows.pyi +31 -0
- stouputils/stouputils/io.pyi +213 -0
- stouputils/stouputils/parallel.pyi +216 -0
- stouputils/stouputils/print.pyi +136 -0
- stouputils/stouputils/version_pkg.pyi +15 -0
- stouputils/version_pkg.py +189 -0
- stouputils-1.14.0.dist-info/METADATA +178 -0
- stouputils-1.14.0.dist-info/RECORD +140 -0
- stouputils-1.14.0.dist-info/WHEEL +4 -0
- stouputils-1.14.0.dist-info/entry_points.txt +3 -0
stouputils/io.py
ADDED
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module provides utilities for file management.
|
|
3
|
+
|
|
4
|
+
- get_root_path: Get the absolute path of the directory
|
|
5
|
+
- relative_path: Get the relative path of a file relative to a given directory
|
|
6
|
+
- json_dump: Writes the provided data to a JSON file with a specified indentation depth.
|
|
7
|
+
- json_load: Load a JSON file from the given path
|
|
8
|
+
- csv_dump: Writes data to a CSV file with customizable options
|
|
9
|
+
- csv_load: Load a CSV file from the given path
|
|
10
|
+
- super_copy: Copy a file (or a folder) from the source to the destination (always create the directory)
|
|
11
|
+
- super_open: Open a file with the given mode, creating the directory if it doesn't exist (only if writing)
|
|
12
|
+
- replace_tilde: Replace the "~" by the user's home directory
|
|
13
|
+
- clean_path: Clean the path by replacing backslashes with forward slashes and simplifying the path
|
|
14
|
+
|
|
15
|
+
.. image:: https://raw.githubusercontent.com/Stoupy51/stouputils/refs/heads/main/assets/io_module.gif
|
|
16
|
+
:alt: stouputils io examples
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
# Imports
|
|
20
|
+
import csv
|
|
21
|
+
import json
|
|
22
|
+
import os
|
|
23
|
+
import re
|
|
24
|
+
import shutil
|
|
25
|
+
from io import StringIO
|
|
26
|
+
from typing import IO, Any
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# Function that takes a relative path and returns the absolute path of the directory
|
|
30
|
+
def get_root_path(relative_path: str, go_up: int = 0) -> str:
|
|
31
|
+
""" Get the absolute path of the directory.
|
|
32
|
+
Usually used to get the root path of the project using the __file__ variable.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
relative_path (str): The path to get the absolute directory path from
|
|
36
|
+
go_up (int): Number of parent directories to go up (default: 0)
|
|
37
|
+
Returns:
|
|
38
|
+
str: The absolute path of the directory
|
|
39
|
+
|
|
40
|
+
Examples:
|
|
41
|
+
|
|
42
|
+
.. code-block:: python
|
|
43
|
+
|
|
44
|
+
> get_root_path(__file__)
|
|
45
|
+
'C:/Users/Alexandre-PC/AppData/Local/Programs/Python/Python310/lib/site-packages/stouputils'
|
|
46
|
+
|
|
47
|
+
> get_root_path(__file__, 3)
|
|
48
|
+
'C:/Users/Alexandre-PC/AppData/Local/Programs/Python/Python310'
|
|
49
|
+
"""
|
|
50
|
+
return clean_path(
|
|
51
|
+
os.path.dirname(os.path.abspath(relative_path))
|
|
52
|
+
+ "/.." * go_up
|
|
53
|
+
) or "."
|
|
54
|
+
|
|
55
|
+
# Function that returns the relative path of a file
|
|
56
|
+
def relative_path(file_path: str, relative_to: str = "") -> str:
|
|
57
|
+
""" Get the relative path of a file relative to a given directory.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
file_path (str): The path to get the relative path from
|
|
61
|
+
relative_to (str): The path to get the relative path to (default: current working directory -> os.getcwd())
|
|
62
|
+
Returns:
|
|
63
|
+
str: The relative path of the file
|
|
64
|
+
Examples:
|
|
65
|
+
|
|
66
|
+
>>> relative_path("D:/some/random/path/stouputils/io.py", "D:\\\\some")
|
|
67
|
+
'random/path/stouputils/io.py'
|
|
68
|
+
>>> relative_path("D:/some/random/path/stouputils/io.py", "D:\\\\some\\\\")
|
|
69
|
+
'random/path/stouputils/io.py'
|
|
70
|
+
"""
|
|
71
|
+
if not relative_to:
|
|
72
|
+
relative_to = os.getcwd()
|
|
73
|
+
file_path = clean_path(file_path)
|
|
74
|
+
relative_to = clean_path(relative_to)
|
|
75
|
+
if file_path.startswith(relative_to):
|
|
76
|
+
return clean_path(os.path.relpath(file_path, relative_to)) or "."
|
|
77
|
+
else:
|
|
78
|
+
return file_path or "."
|
|
79
|
+
|
|
80
|
+
# JSON dump with indentation for levels
|
|
81
|
+
def json_dump(
|
|
82
|
+
data: Any,
|
|
83
|
+
file: IO[Any] | str | None = None,
|
|
84
|
+
max_level: int | None = 2,
|
|
85
|
+
indent: str | int = '\t',
|
|
86
|
+
suffix: str = "\n",
|
|
87
|
+
ensure_ascii: bool = False
|
|
88
|
+
) -> str:
|
|
89
|
+
r""" Writes the provided data to a JSON file with a specified indentation depth.
|
|
90
|
+
For instance, setting max_level to 2 will limit the indentation to 2 levels.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
data (Any): The data to dump (usually a dict or a list)
|
|
94
|
+
file (IO[Any] | str): The file object or path to dump the data to
|
|
95
|
+
max_level (int | None): The depth of indentation to stop at (-1 for infinite), None will default to 2
|
|
96
|
+
indent (str | int): The indentation character (default: '\t')
|
|
97
|
+
suffix (str): The suffix to add at the end of the string (default: '\n')
|
|
98
|
+
ensure_ascii (bool): Whether to escape non-ASCII characters (default: False)
|
|
99
|
+
Returns:
|
|
100
|
+
str: The content of the file in every case
|
|
101
|
+
|
|
102
|
+
>>> json_dump({"a": [[1,2,3]], "b": 2}, max_level = 0)
|
|
103
|
+
'{"a": [[1,2,3]],"b": 2}\n'
|
|
104
|
+
>>> json_dump({"a": [[1,2,3]], "b": 2}, max_level = 1)
|
|
105
|
+
'{\n\t"a": [[1,2,3]],\n\t"b": 2\n}\n'
|
|
106
|
+
>>> json_dump({"a": [[1,2,3]], "b": 2}, max_level = 2)
|
|
107
|
+
'{\n\t"a": [\n\t\t[1,2,3]\n\t],\n\t"b": 2\n}\n'
|
|
108
|
+
>>> json_dump({"a": [[1,2,3]], "b": 2}, max_level = 3)
|
|
109
|
+
'{\n\t"a": [\n\t\t[\n\t\t\t1,\n\t\t\t2,\n\t\t\t3\n\t\t]\n\t],\n\t"b": 2\n}\n'
|
|
110
|
+
>>> json_dump({"éà": "üñ"}, ensure_ascii = True, max_level = 0)
|
|
111
|
+
'{"\\u00e9\\u00e0": "\\u00fc\\u00f1"}\n'
|
|
112
|
+
>>> json_dump({"éà": "üñ"}, ensure_ascii = False, max_level = 0)
|
|
113
|
+
'{"éà": "üñ"}\n'
|
|
114
|
+
"""
|
|
115
|
+
# Handle None values for max_level
|
|
116
|
+
if max_level is None:
|
|
117
|
+
max_level = 2
|
|
118
|
+
|
|
119
|
+
# Dump content with 2-space indent and replace it with the desired indent
|
|
120
|
+
content: str = json.dumps(data, indent=indent, ensure_ascii=ensure_ascii)
|
|
121
|
+
|
|
122
|
+
# Limit max depth of indentation
|
|
123
|
+
if max_level > -1:
|
|
124
|
+
escape: str = re.escape(indent if isinstance(indent, str) else ' '*indent)
|
|
125
|
+
pattern: re.Pattern[str] = re.compile(
|
|
126
|
+
r"\n" + escape + "{" + str(max_level + 1) + r",}(.*)"
|
|
127
|
+
r"|\n" + escape + "{" + str(max_level) + r"}([}\]])"
|
|
128
|
+
)
|
|
129
|
+
content = pattern.sub(r"\1\2", content)
|
|
130
|
+
|
|
131
|
+
# Final newline and write
|
|
132
|
+
content += suffix
|
|
133
|
+
if file:
|
|
134
|
+
if isinstance(file, str):
|
|
135
|
+
with super_open(file, "w") as f:
|
|
136
|
+
f.write(content)
|
|
137
|
+
else:
|
|
138
|
+
file.write(content)
|
|
139
|
+
return content
|
|
140
|
+
|
|
141
|
+
# JSON load from file path
|
|
142
|
+
def json_load(file_path: str) -> Any:
|
|
143
|
+
""" Load a JSON file from the given path
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
file_path (str): The path to the JSON file
|
|
147
|
+
Returns:
|
|
148
|
+
Any: The content of the JSON file
|
|
149
|
+
"""
|
|
150
|
+
with open(file_path) as f:
|
|
151
|
+
return json.loads(f.read())
|
|
152
|
+
|
|
153
|
+
# CSV dump to file
|
|
154
|
+
def csv_dump(
|
|
155
|
+
data: Any,
|
|
156
|
+
file: IO[Any] | str | None = None,
|
|
157
|
+
delimiter: str = ',',
|
|
158
|
+
has_header: bool = True,
|
|
159
|
+
index: bool = False,
|
|
160
|
+
*args: Any,
|
|
161
|
+
**kwargs: Any
|
|
162
|
+
) -> str:
|
|
163
|
+
""" Writes data to a CSV file with customizable options and returns the CSV content as a string.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
data (list[list[Any]] | list[dict[str, Any]] | pd.DataFrame | pl.DataFrame):
|
|
167
|
+
The data to write, either a list of lists, list of dicts, pandas DataFrame, or Polars DataFrame
|
|
168
|
+
file (IO[Any] | str): The file object or path to dump the data to
|
|
169
|
+
delimiter (str): The delimiter to use (default: ',')
|
|
170
|
+
has_header (bool): Whether to include headers (default: True, applies to dict and DataFrame data)
|
|
171
|
+
index (bool): Whether to include the index (default: False, only applies to pandas DataFrame)
|
|
172
|
+
*args (Any): Additional positional arguments to pass to the underlying CSV writer or DataFrame method
|
|
173
|
+
**kwargs (Any): Additional keyword arguments to pass to the underlying CSV writer or DataFrame method
|
|
174
|
+
Returns:
|
|
175
|
+
str: The CSV content as a string
|
|
176
|
+
|
|
177
|
+
Examples:
|
|
178
|
+
|
|
179
|
+
>>> csv_dump([["a", "b", "c"], [1, 2, 3], [4, 5, 6]])
|
|
180
|
+
'a,b,c\\r\\n1,2,3\\r\\n4,5,6\\r\\n'
|
|
181
|
+
|
|
182
|
+
>>> csv_dump([{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}])
|
|
183
|
+
'name,age\\r\\nAlice,30\\r\\nBob,25\\r\\n'
|
|
184
|
+
"""
|
|
185
|
+
if isinstance(data, str | bytes | dict):
|
|
186
|
+
raise ValueError("Data must be a list of lists, list of dicts, pandas DataFrame, or Polars DataFrame")
|
|
187
|
+
output = StringIO()
|
|
188
|
+
done: bool = False
|
|
189
|
+
|
|
190
|
+
# Handle Polars DataFrame
|
|
191
|
+
try:
|
|
192
|
+
import polars as pl # type: ignore
|
|
193
|
+
if isinstance(data, pl.DataFrame):
|
|
194
|
+
copy_kwargs = kwargs.copy()
|
|
195
|
+
copy_kwargs.setdefault("separator", delimiter)
|
|
196
|
+
copy_kwargs.setdefault("include_header", has_header)
|
|
197
|
+
data.write_csv(output, *args, **copy_kwargs)
|
|
198
|
+
done = True
|
|
199
|
+
except Exception:
|
|
200
|
+
pass
|
|
201
|
+
|
|
202
|
+
# Handle pandas DataFrame
|
|
203
|
+
if not done:
|
|
204
|
+
try:
|
|
205
|
+
import pandas as pd # type: ignore
|
|
206
|
+
if isinstance(data, pd.DataFrame):
|
|
207
|
+
copy_kwargs = kwargs.copy()
|
|
208
|
+
copy_kwargs.setdefault("index", index)
|
|
209
|
+
copy_kwargs.setdefault("sep", delimiter)
|
|
210
|
+
copy_kwargs.setdefault("header", has_header)
|
|
211
|
+
data.to_csv(output, *args, **copy_kwargs)
|
|
212
|
+
except Exception:
|
|
213
|
+
pass
|
|
214
|
+
|
|
215
|
+
if not done:
|
|
216
|
+
# Handle list of dicts
|
|
217
|
+
data = list(data) # Ensure list and not other iterable
|
|
218
|
+
if isinstance(data[0], dict):
|
|
219
|
+
fieldnames = list(data[0].keys()) # type: ignore
|
|
220
|
+
kwargs.setdefault("fieldnames", fieldnames)
|
|
221
|
+
kwargs.setdefault("delimiter", delimiter)
|
|
222
|
+
dict_writer = csv.DictWriter(output, *args, **kwargs)
|
|
223
|
+
if has_header:
|
|
224
|
+
dict_writer.writeheader()
|
|
225
|
+
dict_writer.writerows(data) # type: ignore
|
|
226
|
+
done = True
|
|
227
|
+
|
|
228
|
+
# Handle list of lists
|
|
229
|
+
else:
|
|
230
|
+
kwargs.setdefault("delimiter", delimiter)
|
|
231
|
+
list_writer = csv.writer(output, *args, **kwargs)
|
|
232
|
+
list_writer.writerows(data) # type: ignore
|
|
233
|
+
done = True
|
|
234
|
+
|
|
235
|
+
# If still not done, raise error
|
|
236
|
+
if not done:
|
|
237
|
+
output.close()
|
|
238
|
+
raise ValueError(f"Data must be a list of lists, list of dicts, pandas DataFrame, or Polars DataFrame, got {type(data)} instead")
|
|
239
|
+
|
|
240
|
+
# Get content and write to file if needed
|
|
241
|
+
content: str = output.getvalue()
|
|
242
|
+
if file:
|
|
243
|
+
if isinstance(file, str):
|
|
244
|
+
with super_open(file, "w") as f:
|
|
245
|
+
f.write(content)
|
|
246
|
+
else:
|
|
247
|
+
file.write(content)
|
|
248
|
+
output.close()
|
|
249
|
+
return content
|
|
250
|
+
|
|
251
|
+
# CSV load from file path
|
|
252
|
+
def csv_load(file_path: str, delimiter: str = ',', has_header: bool = True, as_dict: bool = False, as_dataframe: bool = False, use_polars: bool = False, *args: Any, **kwargs: Any) -> Any:
|
|
253
|
+
""" Load a CSV file from the given path
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
file_path (str): The path to the CSV file
|
|
257
|
+
delimiter (str): The delimiter used in the CSV (default: ',')
|
|
258
|
+
has_header (bool): Whether the CSV has a header row (default: True)
|
|
259
|
+
as_dict (bool): Whether to return data as list of dicts (default: False)
|
|
260
|
+
as_dataframe (bool): Whether to return data as a DataFrame (default: False)
|
|
261
|
+
use_polars (bool): Whether to use Polars instead of pandas for DataFrame (default: False, requires polars)
|
|
262
|
+
*args: Additional positional arguments to pass to the underlying CSV reader or DataFrame method
|
|
263
|
+
**kwargs: Additional keyword arguments to pass to the underlying CSV reader or DataFrame method
|
|
264
|
+
Returns:
|
|
265
|
+
list[list[str]] | list[dict[str, str]] | pd.DataFrame | pl.DataFrame: The content of the CSV file
|
|
266
|
+
|
|
267
|
+
Examples:
|
|
268
|
+
|
|
269
|
+
.. code-block:: python
|
|
270
|
+
|
|
271
|
+
> Assuming "test.csv" contains: a,b,c\\n1,2,3\\n4,5,6
|
|
272
|
+
> csv_load("test.csv")
|
|
273
|
+
[['1', '2', '3'], ['4', '5', '6']]
|
|
274
|
+
|
|
275
|
+
> csv_load("test.csv", as_dict=True)
|
|
276
|
+
[{'a': '1', 'b': '2', 'c': '3'}, {'a': '4', 'b': '5', 'c': '6'}]
|
|
277
|
+
|
|
278
|
+
> csv_load("test.csv", as_dataframe=True)
|
|
279
|
+
a b c
|
|
280
|
+
0 1 2 3
|
|
281
|
+
1 4 5 6
|
|
282
|
+
|
|
283
|
+
.. code-block:: console
|
|
284
|
+
|
|
285
|
+
> csv_load("test.csv", as_dataframe=True, use_polars=True)
|
|
286
|
+
shape: (2, 3)
|
|
287
|
+
┌─────┬─────┬─────┐
|
|
288
|
+
│ a ┆ b ┆ c │
|
|
289
|
+
│ --- ┆ --- ┆ --- │
|
|
290
|
+
│ i64 ┆ i64 ┆ i64 │
|
|
291
|
+
╞═════╪═════╪═════╡
|
|
292
|
+
│ 1 ┆ 2 ┆ 3 │
|
|
293
|
+
│ 4 ┆ 5 ┆ 6 │
|
|
294
|
+
└─────┴─────┴─────┘
|
|
295
|
+
""" # noqa: E101
|
|
296
|
+
# Handle DataFrame loading
|
|
297
|
+
if as_dataframe:
|
|
298
|
+
if use_polars:
|
|
299
|
+
import polars as pl # type: ignore
|
|
300
|
+
if not os.path.exists(file_path):
|
|
301
|
+
return pl.DataFrame() # type: ignore
|
|
302
|
+
kwargs.setdefault("separator", delimiter)
|
|
303
|
+
kwargs.setdefault("has_header", has_header)
|
|
304
|
+
return pl.read_csv(file_path, *args, **kwargs) # type: ignore
|
|
305
|
+
else:
|
|
306
|
+
import pandas as pd # type: ignore
|
|
307
|
+
if not os.path.exists(file_path):
|
|
308
|
+
return pd.DataFrame() # type: ignore
|
|
309
|
+
kwargs.setdefault("sep", delimiter)
|
|
310
|
+
kwargs.setdefault("header", 0 if has_header else None)
|
|
311
|
+
return pd.read_csv(file_path, *args, **kwargs) # type: ignore
|
|
312
|
+
|
|
313
|
+
# Handle dict or list
|
|
314
|
+
if not os.path.exists(file_path):
|
|
315
|
+
return []
|
|
316
|
+
with super_open(file_path, "r") as f:
|
|
317
|
+
if as_dict or has_header:
|
|
318
|
+
kwargs.setdefault("delimiter", delimiter)
|
|
319
|
+
reader = csv.DictReader(f, *args, **kwargs)
|
|
320
|
+
return list(reader)
|
|
321
|
+
else:
|
|
322
|
+
kwargs.setdefault("delimiter", delimiter)
|
|
323
|
+
reader = csv.reader(f, *args, **kwargs)
|
|
324
|
+
return list(reader)
|
|
325
|
+
|
|
326
|
+
# For easy file copy
|
|
327
|
+
def super_copy(src: str, dst: str, create_dir: bool = True, symlink: bool = False) -> str:
|
|
328
|
+
""" Copy a file (or a folder) from the source to the destination
|
|
329
|
+
|
|
330
|
+
Args:
|
|
331
|
+
src (str): The source path
|
|
332
|
+
dst (str): The destination path
|
|
333
|
+
create_dir (bool): Whether to create the directory if it doesn't exist (default: True)
|
|
334
|
+
symlink (bool): Whether to create a symlink instead of copying (Linux only, default: True)
|
|
335
|
+
Returns:
|
|
336
|
+
str: The destination path
|
|
337
|
+
"""
|
|
338
|
+
# Disable symlink functionality on Windows as it uses shortcuts instead of proper symlinks
|
|
339
|
+
if os.name == "nt":
|
|
340
|
+
symlink = False
|
|
341
|
+
|
|
342
|
+
# Create destination directory if needed
|
|
343
|
+
if create_dir:
|
|
344
|
+
os.makedirs(os.path.dirname(dst), exist_ok=True)
|
|
345
|
+
|
|
346
|
+
# Handle directory copying
|
|
347
|
+
if os.path.isdir(src):
|
|
348
|
+
if symlink:
|
|
349
|
+
|
|
350
|
+
# Remove existing destination if it's different from source
|
|
351
|
+
if os.path.exists(dst):
|
|
352
|
+
if os.path.samefile(src, dst) is False:
|
|
353
|
+
if os.path.isdir(dst):
|
|
354
|
+
shutil.rmtree(dst)
|
|
355
|
+
else:
|
|
356
|
+
os.remove(dst)
|
|
357
|
+
return os.symlink(src.rstrip('/'), dst.rstrip('/'), target_is_directory=True) or dst
|
|
358
|
+
else:
|
|
359
|
+
return os.symlink(src.rstrip('/'), dst.rstrip('/'), target_is_directory=True) or dst
|
|
360
|
+
|
|
361
|
+
# Regular directory copy
|
|
362
|
+
else:
|
|
363
|
+
return shutil.copytree(src, dst, dirs_exist_ok = True)
|
|
364
|
+
|
|
365
|
+
# Handle file copying
|
|
366
|
+
else:
|
|
367
|
+
if symlink:
|
|
368
|
+
|
|
369
|
+
# Remove existing destination if it's different from source
|
|
370
|
+
if os.path.exists(dst):
|
|
371
|
+
if os.path.samefile(src, dst) is False:
|
|
372
|
+
os.remove(dst)
|
|
373
|
+
return os.symlink(src, dst, target_is_directory=False) or dst
|
|
374
|
+
else:
|
|
375
|
+
return os.symlink(src, dst, target_is_directory=False) or dst
|
|
376
|
+
|
|
377
|
+
# Regular file copy
|
|
378
|
+
else:
|
|
379
|
+
return shutil.copy(src, dst)
|
|
380
|
+
return ""
|
|
381
|
+
|
|
382
|
+
# For easy file management
|
|
383
|
+
def super_open(file_path: str, mode: str, encoding: str = "utf-8") -> IO[Any]:
|
|
384
|
+
""" Open a file with the given mode, creating the directory if it doesn't exist (only if writing)
|
|
385
|
+
|
|
386
|
+
Args:
|
|
387
|
+
file_path (str): The path to the file
|
|
388
|
+
mode (str): The mode to open the file with, ex: "w", "r", "a", "wb", "rb", "ab"
|
|
389
|
+
encoding (str): The encoding to use when opening the file (default: "utf-8")
|
|
390
|
+
Returns:
|
|
391
|
+
open: The file object, ready to be used
|
|
392
|
+
"""
|
|
393
|
+
# Make directory
|
|
394
|
+
file_path = clean_path(file_path)
|
|
395
|
+
if "/" in file_path and ("w" in mode or "a" in mode):
|
|
396
|
+
os.makedirs(os.path.dirname(file_path), exist_ok=True)
|
|
397
|
+
|
|
398
|
+
# Open file and return
|
|
399
|
+
if "b" in mode:
|
|
400
|
+
return open(file_path, mode)
|
|
401
|
+
else:
|
|
402
|
+
return open(file_path, mode, encoding = encoding) # Always use utf-8 encoding to avoid issues
|
|
403
|
+
|
|
404
|
+
def read_file(file_path: str, encoding: str = "utf-8") -> str:
|
|
405
|
+
""" Read the content of a file and return it as a string
|
|
406
|
+
|
|
407
|
+
Args:
|
|
408
|
+
file_path (str): The path to the file
|
|
409
|
+
encoding (str): The encoding to use when opening the file (default: "utf-8")
|
|
410
|
+
Returns:
|
|
411
|
+
str: The content of the file
|
|
412
|
+
"""
|
|
413
|
+
with super_open(file_path, "r", encoding=encoding) as f:
|
|
414
|
+
return f.read()
|
|
415
|
+
|
|
416
|
+
# Function that replace the "~" by the user's home directory
|
|
417
|
+
def replace_tilde(path: str) -> str:
|
|
418
|
+
""" Replace the "~" by the user's home directory
|
|
419
|
+
|
|
420
|
+
Args:
|
|
421
|
+
path (str): The path to replace the "~" by the user's home directory
|
|
422
|
+
Returns:
|
|
423
|
+
str: The path with the "~" replaced by the user's home directory
|
|
424
|
+
Examples:
|
|
425
|
+
|
|
426
|
+
.. code-block:: python
|
|
427
|
+
|
|
428
|
+
> replace_tilde("~/Documents/test.txt")
|
|
429
|
+
'/home/user/Documents/test.txt'
|
|
430
|
+
"""
|
|
431
|
+
return path.replace("~", os.path.expanduser("~")).replace("\\", "/")
|
|
432
|
+
|
|
433
|
+
# Utility function to clean the path
|
|
434
|
+
def clean_path(file_path: str, trailing_slash: bool = True) -> str:
|
|
435
|
+
""" Clean the path by replacing backslashes with forward slashes and simplifying the path
|
|
436
|
+
|
|
437
|
+
Args:
|
|
438
|
+
file_path (str): The path to clean
|
|
439
|
+
trailing_slash (bool): Whether to keep the trailing slash, ex: "test/" -> "test/"
|
|
440
|
+
Returns:
|
|
441
|
+
str: The cleaned path
|
|
442
|
+
Examples:
|
|
443
|
+
>>> clean_path("C:\\\\Users\\\\Stoupy\\\\Documents\\\\test.txt")
|
|
444
|
+
'C:/Users/Stoupy/Documents/test.txt'
|
|
445
|
+
|
|
446
|
+
>>> clean_path("Some Folder////")
|
|
447
|
+
'Some Folder/'
|
|
448
|
+
|
|
449
|
+
>>> clean_path("test/uwu/1/../../")
|
|
450
|
+
'test/'
|
|
451
|
+
|
|
452
|
+
>>> clean_path("some/./folder/../")
|
|
453
|
+
'some/'
|
|
454
|
+
|
|
455
|
+
>>> clean_path("folder1/folder2/../../folder3")
|
|
456
|
+
'folder3'
|
|
457
|
+
|
|
458
|
+
>>> clean_path("./test/./folder/")
|
|
459
|
+
'test/folder/'
|
|
460
|
+
|
|
461
|
+
>>> clean_path("C:/folder1\\\\folder2")
|
|
462
|
+
'C:/folder1/folder2'
|
|
463
|
+
"""
|
|
464
|
+
# Replace tilde
|
|
465
|
+
file_path = replace_tilde(str(file_path))
|
|
466
|
+
|
|
467
|
+
# Check if original path ends with slash
|
|
468
|
+
ends_with_slash: bool = file_path.endswith('/') or file_path.endswith('\\')
|
|
469
|
+
|
|
470
|
+
# Use os.path.normpath to clean up the path
|
|
471
|
+
file_path = os.path.normpath(file_path)
|
|
472
|
+
|
|
473
|
+
# Convert backslashes to forward slashes
|
|
474
|
+
file_path = file_path.replace(os.sep, '/')
|
|
475
|
+
|
|
476
|
+
# Add trailing slash back if original had one
|
|
477
|
+
if ends_with_slash and not file_path.endswith('/'):
|
|
478
|
+
file_path += '/'
|
|
479
|
+
|
|
480
|
+
# Remove trailing slash if requested
|
|
481
|
+
if not trailing_slash and file_path.endswith('/'):
|
|
482
|
+
file_path = file_path[:-1]
|
|
483
|
+
|
|
484
|
+
# Return the cleaned path
|
|
485
|
+
return file_path if file_path != "." else ""
|
|
486
|
+
|