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.
Files changed (140) hide show
  1. stouputils/__init__.py +40 -0
  2. stouputils/__main__.py +86 -0
  3. stouputils/_deprecated.py +37 -0
  4. stouputils/all_doctests.py +160 -0
  5. stouputils/applications/__init__.py +22 -0
  6. stouputils/applications/automatic_docs.py +634 -0
  7. stouputils/applications/upscaler/__init__.py +39 -0
  8. stouputils/applications/upscaler/config.py +128 -0
  9. stouputils/applications/upscaler/image.py +247 -0
  10. stouputils/applications/upscaler/video.py +287 -0
  11. stouputils/archive.py +344 -0
  12. stouputils/backup.py +488 -0
  13. stouputils/collections.py +244 -0
  14. stouputils/continuous_delivery/__init__.py +27 -0
  15. stouputils/continuous_delivery/cd_utils.py +243 -0
  16. stouputils/continuous_delivery/github.py +522 -0
  17. stouputils/continuous_delivery/pypi.py +130 -0
  18. stouputils/continuous_delivery/pyproject.py +147 -0
  19. stouputils/continuous_delivery/stubs.py +86 -0
  20. stouputils/ctx.py +408 -0
  21. stouputils/data_science/config/get.py +51 -0
  22. stouputils/data_science/config/set.py +125 -0
  23. stouputils/data_science/data_processing/image/__init__.py +66 -0
  24. stouputils/data_science/data_processing/image/auto_contrast.py +79 -0
  25. stouputils/data_science/data_processing/image/axis_flip.py +58 -0
  26. stouputils/data_science/data_processing/image/bias_field_correction.py +74 -0
  27. stouputils/data_science/data_processing/image/binary_threshold.py +73 -0
  28. stouputils/data_science/data_processing/image/blur.py +59 -0
  29. stouputils/data_science/data_processing/image/brightness.py +54 -0
  30. stouputils/data_science/data_processing/image/canny.py +110 -0
  31. stouputils/data_science/data_processing/image/clahe.py +92 -0
  32. stouputils/data_science/data_processing/image/common.py +30 -0
  33. stouputils/data_science/data_processing/image/contrast.py +53 -0
  34. stouputils/data_science/data_processing/image/curvature_flow_filter.py +74 -0
  35. stouputils/data_science/data_processing/image/denoise.py +378 -0
  36. stouputils/data_science/data_processing/image/histogram_equalization.py +123 -0
  37. stouputils/data_science/data_processing/image/invert.py +64 -0
  38. stouputils/data_science/data_processing/image/laplacian.py +60 -0
  39. stouputils/data_science/data_processing/image/median_blur.py +52 -0
  40. stouputils/data_science/data_processing/image/noise.py +59 -0
  41. stouputils/data_science/data_processing/image/normalize.py +65 -0
  42. stouputils/data_science/data_processing/image/random_erase.py +66 -0
  43. stouputils/data_science/data_processing/image/resize.py +69 -0
  44. stouputils/data_science/data_processing/image/rotation.py +80 -0
  45. stouputils/data_science/data_processing/image/salt_pepper.py +68 -0
  46. stouputils/data_science/data_processing/image/sharpening.py +55 -0
  47. stouputils/data_science/data_processing/image/shearing.py +64 -0
  48. stouputils/data_science/data_processing/image/threshold.py +64 -0
  49. stouputils/data_science/data_processing/image/translation.py +71 -0
  50. stouputils/data_science/data_processing/image/zoom.py +83 -0
  51. stouputils/data_science/data_processing/image_augmentation.py +118 -0
  52. stouputils/data_science/data_processing/image_preprocess.py +183 -0
  53. stouputils/data_science/data_processing/prosthesis_detection.py +359 -0
  54. stouputils/data_science/data_processing/technique.py +481 -0
  55. stouputils/data_science/dataset/__init__.py +45 -0
  56. stouputils/data_science/dataset/dataset.py +292 -0
  57. stouputils/data_science/dataset/dataset_loader.py +135 -0
  58. stouputils/data_science/dataset/grouping_strategy.py +296 -0
  59. stouputils/data_science/dataset/image_loader.py +100 -0
  60. stouputils/data_science/dataset/xy_tuple.py +696 -0
  61. stouputils/data_science/metric_dictionnary.py +106 -0
  62. stouputils/data_science/metric_utils.py +847 -0
  63. stouputils/data_science/mlflow_utils.py +206 -0
  64. stouputils/data_science/models/abstract_model.py +149 -0
  65. stouputils/data_science/models/all.py +85 -0
  66. stouputils/data_science/models/base_keras.py +765 -0
  67. stouputils/data_science/models/keras/all.py +38 -0
  68. stouputils/data_science/models/keras/convnext.py +62 -0
  69. stouputils/data_science/models/keras/densenet.py +50 -0
  70. stouputils/data_science/models/keras/efficientnet.py +60 -0
  71. stouputils/data_science/models/keras/mobilenet.py +56 -0
  72. stouputils/data_science/models/keras/resnet.py +52 -0
  73. stouputils/data_science/models/keras/squeezenet.py +233 -0
  74. stouputils/data_science/models/keras/vgg.py +42 -0
  75. stouputils/data_science/models/keras/xception.py +38 -0
  76. stouputils/data_science/models/keras_utils/callbacks/__init__.py +20 -0
  77. stouputils/data_science/models/keras_utils/callbacks/colored_progress_bar.py +219 -0
  78. stouputils/data_science/models/keras_utils/callbacks/learning_rate_finder.py +148 -0
  79. stouputils/data_science/models/keras_utils/callbacks/model_checkpoint_v2.py +31 -0
  80. stouputils/data_science/models/keras_utils/callbacks/progressive_unfreezing.py +249 -0
  81. stouputils/data_science/models/keras_utils/callbacks/warmup_scheduler.py +66 -0
  82. stouputils/data_science/models/keras_utils/losses/__init__.py +12 -0
  83. stouputils/data_science/models/keras_utils/losses/next_generation_loss.py +56 -0
  84. stouputils/data_science/models/keras_utils/visualizations.py +416 -0
  85. stouputils/data_science/models/model_interface.py +939 -0
  86. stouputils/data_science/models/sandbox.py +116 -0
  87. stouputils/data_science/range_tuple.py +234 -0
  88. stouputils/data_science/scripts/augment_dataset.py +77 -0
  89. stouputils/data_science/scripts/exhaustive_process.py +133 -0
  90. stouputils/data_science/scripts/preprocess_dataset.py +70 -0
  91. stouputils/data_science/scripts/routine.py +168 -0
  92. stouputils/data_science/utils.py +285 -0
  93. stouputils/decorators.py +605 -0
  94. stouputils/image.py +441 -0
  95. stouputils/installer/__init__.py +18 -0
  96. stouputils/installer/common.py +67 -0
  97. stouputils/installer/downloader.py +101 -0
  98. stouputils/installer/linux.py +144 -0
  99. stouputils/installer/main.py +223 -0
  100. stouputils/installer/windows.py +136 -0
  101. stouputils/io.py +486 -0
  102. stouputils/parallel.py +483 -0
  103. stouputils/print.py +482 -0
  104. stouputils/py.typed +1 -0
  105. stouputils/stouputils/__init__.pyi +15 -0
  106. stouputils/stouputils/_deprecated.pyi +12 -0
  107. stouputils/stouputils/all_doctests.pyi +46 -0
  108. stouputils/stouputils/applications/__init__.pyi +2 -0
  109. stouputils/stouputils/applications/automatic_docs.pyi +106 -0
  110. stouputils/stouputils/applications/upscaler/__init__.pyi +3 -0
  111. stouputils/stouputils/applications/upscaler/config.pyi +18 -0
  112. stouputils/stouputils/applications/upscaler/image.pyi +109 -0
  113. stouputils/stouputils/applications/upscaler/video.pyi +60 -0
  114. stouputils/stouputils/archive.pyi +67 -0
  115. stouputils/stouputils/backup.pyi +109 -0
  116. stouputils/stouputils/collections.pyi +86 -0
  117. stouputils/stouputils/continuous_delivery/__init__.pyi +5 -0
  118. stouputils/stouputils/continuous_delivery/cd_utils.pyi +129 -0
  119. stouputils/stouputils/continuous_delivery/github.pyi +162 -0
  120. stouputils/stouputils/continuous_delivery/pypi.pyi +53 -0
  121. stouputils/stouputils/continuous_delivery/pyproject.pyi +67 -0
  122. stouputils/stouputils/continuous_delivery/stubs.pyi +39 -0
  123. stouputils/stouputils/ctx.pyi +211 -0
  124. stouputils/stouputils/decorators.pyi +252 -0
  125. stouputils/stouputils/image.pyi +172 -0
  126. stouputils/stouputils/installer/__init__.pyi +5 -0
  127. stouputils/stouputils/installer/common.pyi +39 -0
  128. stouputils/stouputils/installer/downloader.pyi +24 -0
  129. stouputils/stouputils/installer/linux.pyi +39 -0
  130. stouputils/stouputils/installer/main.pyi +57 -0
  131. stouputils/stouputils/installer/windows.pyi +31 -0
  132. stouputils/stouputils/io.pyi +213 -0
  133. stouputils/stouputils/parallel.pyi +216 -0
  134. stouputils/stouputils/print.pyi +136 -0
  135. stouputils/stouputils/version_pkg.pyi +15 -0
  136. stouputils/version_pkg.py +189 -0
  137. stouputils-1.14.0.dist-info/METADATA +178 -0
  138. stouputils-1.14.0.dist-info/RECORD +140 -0
  139. stouputils-1.14.0.dist-info/WHEEL +4 -0
  140. stouputils-1.14.0.dist-info/entry_points.txt +3 -0
stouputils/archive.py ADDED
@@ -0,0 +1,344 @@
1
+ """
2
+ This module provides functions for creating and managing archives.
3
+
4
+ - repair_zip_file: Try to repair a corrupted zip file by ignoring some of the errors
5
+ - make_archive: Create a zip archive from a source directory with consistent file timestamps.
6
+ - archive_cli: Main entry point for command line usage
7
+
8
+ .. image:: https://raw.githubusercontent.com/Stoupy51/stouputils/refs/heads/main/assets/archive_module.gif
9
+ :alt: stouputils archive examples
10
+ """
11
+
12
+ # pyright: reportUnusedVariable=false
13
+ # Imports
14
+ import fnmatch
15
+ import os
16
+ from zipfile import ZIP_DEFLATED, ZipFile, ZipInfo
17
+
18
+ from .decorators import LogLevels, handle_error
19
+ from .io import clean_path, super_copy
20
+ from .print import CYAN, GREEN, RESET, debug, error, info
21
+
22
+
23
+ # Function that repair a corrupted zip file (ignoring some of the errors)
24
+ @handle_error()
25
+ def repair_zip_file(file_path: str, destination: str) -> bool:
26
+ """ Try to repair a corrupted zip file by ignoring some of the errors
27
+
28
+ This function manually parses the ZIP file structure to extract files
29
+ even when the ZIP file is corrupted. It reads the central directory
30
+ entries and attempts to decompress each file individually.
31
+
32
+ Args:
33
+ file_path (str): Path of the zip file to repair
34
+ destination (str): Destination of the new file
35
+ Returns:
36
+ bool: Always returns True unless any strong error
37
+
38
+ Examples:
39
+
40
+ .. code-block:: python
41
+
42
+ > repair_zip_file("/path/to/source.zip", "/path/to/destination.zip")
43
+ """
44
+ # Check
45
+ if not os.path.exists(file_path):
46
+ raise FileNotFoundError(f"File '{file_path}' not found")
47
+ dirname: str = os.path.dirname(destination)
48
+ if dirname and not os.path.exists(dirname):
49
+ raise FileNotFoundError(f"Directory '{dirname}' not found")
50
+
51
+ import struct
52
+ import zlib
53
+
54
+ # Read the entire ZIP file into memory
55
+ with open(file_path, 'rb') as f:
56
+ data = f.read()
57
+
58
+ # Find central directory entries
59
+ CENTRAL_SIG = b'PK\x01\x02'
60
+ entries: list[dict[str, int | str]] = []
61
+ idx = 0
62
+
63
+ while True:
64
+ idx = data.find(CENTRAL_SIG, idx)
65
+ if idx == -1:
66
+ break
67
+ # Ensure enough length for central directory header
68
+ if idx + 46 > len(data):
69
+ break
70
+
71
+ header = data[idx:idx+46]
72
+ try:
73
+ (
74
+ sig,
75
+ ver_made, ver_needed, flags, comp_method, mtime, mdate,
76
+ crc, csize, usize,
77
+ name_len, extra_len, comm_len,
78
+ disk_start, int_attr,
79
+ ext_attr, local_off
80
+ ) = struct.unpack('<4s6H3L3H2H2L', header)
81
+
82
+ name_start = idx + 46
83
+ if name_start + name_len > len(data):
84
+ idx += 4
85
+ continue
86
+
87
+ name = data[name_start:name_start+name_len].decode('utf-8', errors='replace')
88
+ entries.append({
89
+ 'name': name,
90
+ 'comp_method': comp_method,
91
+ 'csize': csize,
92
+ 'usize': usize,
93
+ 'local_offset': local_off,
94
+ 'crc': crc
95
+ })
96
+ except (struct.error, UnicodeDecodeError):
97
+ # Skip corrupted entries
98
+ pass
99
+
100
+ idx += 4
101
+
102
+ # Create a new ZIP file with recovered entries
103
+ with ZipFile(destination, "w", compression=ZIP_DEFLATED) as new_zip_file:
104
+ for entry in entries:
105
+ try:
106
+ # Get the local header to find data start
107
+ lo: int = int(entry['local_offset'])
108
+ if lo + 30 > len(data):
109
+ continue
110
+
111
+ lh = data[lo:lo+30]
112
+ try:
113
+ _, _, _, _, _, _, _, _, _, name_len, extra_len = struct.unpack('<4sHHHHHLLLHH', lh)
114
+ except struct.error:
115
+ continue
116
+
117
+ data_start: int = lo + 30 + name_len + extra_len
118
+ if data_start + int(entry['csize']) > len(data):
119
+ continue
120
+
121
+ comp_data = data[data_start:data_start+int(entry['csize'])]
122
+
123
+ # Decompress the data
124
+ try:
125
+ if int(entry['comp_method']) == 0: # No compression
126
+ content = comp_data[:int(entry['usize'])]
127
+ elif int(entry['comp_method']) == 8: # Deflate compression
128
+ content = zlib.decompress(comp_data, -zlib.MAX_WBITS)
129
+ else:
130
+ # Unsupported compression method, skip
131
+ continue
132
+
133
+ # Write to new ZIP file
134
+ new_zip_file.writestr(str(entry['name']), content)
135
+
136
+ except (zlib.error, Exception):
137
+ # If decompression fails, try to write raw data as a fallback
138
+ try:
139
+ new_zip_file.writestr(f"{entry['name']!s}.corrupted", comp_data)
140
+ except Exception:
141
+ # Skip completely corrupted entries
142
+ continue
143
+
144
+ except Exception:
145
+ # Skip any entries that cause errors
146
+ continue
147
+
148
+ return True
149
+
150
+ # Function that makes an archive with consistency (same zip file each time)
151
+ @handle_error()
152
+ def make_archive(
153
+ source: str,
154
+ destinations: list[str] | str | None = None,
155
+ override_time: None | tuple[int, int, int, int, int, int] = None,
156
+ create_dir: bool = False,
157
+ ignore_patterns: str | None = None,
158
+ ) -> bool:
159
+ """ Create a zip archive from a source directory with consistent file timestamps.
160
+ (Meaning deterministic zip file each time)
161
+
162
+ Creates a zip archive from the source directory and copies it to one or more destinations.
163
+ The archive will have consistent file timestamps across runs if override_time is specified.
164
+ Uses maximum compression level (9) with ZIP_DEFLATED algorithm.
165
+
166
+ Args:
167
+ source (str): The source folder to archive
168
+ destinations (list[str]|str): The destination folder(s) or file(s) to copy the archive to
169
+ override_time (None | tuple[int, ...]): The constant time to use for the archive
170
+ (e.g. (2024, 1, 1, 0, 0, 0) for 2024-01-01 00:00:00)
171
+ create_dir (bool): Whether to create the destination directory if it doesn't exist
172
+ ignore_patterns (str | None): Glob pattern(s) to ignore files. Can be a single pattern or comma-separated patterns (e.g. "*.pyc" or "*.pyc,__pycache__,*.log")
173
+ Returns:
174
+ bool: Always returns True unless any strong error
175
+ Examples:
176
+
177
+ .. code-block:: python
178
+
179
+ > make_archive("/path/to/source", "/path/to/destination.zip")
180
+ > make_archive("/path/to/source", ["/path/to/destination.zip", "/path/to/destination2.zip"])
181
+ > make_archive("src", "hello_from_year_2085.zip", override_time=(2085,1,1,0,0,0))
182
+ > make_archive("src", "output.zip", ignore_patterns="*.pyc")
183
+ > make_archive("src", "output.zip", ignore_patterns="__pycache__")
184
+ > make_archive("src", "output.zip", ignore_patterns="*.pyc,__pycache__,*.log")
185
+ """
186
+ # Fix copy_destinations type if needed
187
+ if destinations is None:
188
+ destinations = []
189
+ if destinations and isinstance(destinations, str):
190
+ destinations = [destinations]
191
+ if not destinations:
192
+ raise ValueError("destinations must be a list of at least one destination")
193
+
194
+ # Create directories if needed
195
+ if create_dir:
196
+ for dest_file in destinations:
197
+ dest_file = clean_path(dest_file)
198
+ parent_dir = os.path.dirname(dest_file)
199
+ if parent_dir and not os.path.exists(parent_dir):
200
+ os.makedirs(parent_dir, exist_ok=True)
201
+
202
+ # Create the archive
203
+ destination: str = clean_path(destinations[0])
204
+ destination = destination if ".zip" in destination else destination + ".zip"
205
+
206
+ # Parse ignore patterns (can be a single pattern or comma-separated patterns)
207
+ ignore_pattern_list: list[str] = []
208
+ if ignore_patterns:
209
+ ignore_pattern_list = [pattern.strip() for pattern in ignore_patterns.split(',')]
210
+
211
+ def should_ignore(path: str) -> bool:
212
+ """Check if a file or directory path should be ignored based on patterns."""
213
+ if not ignore_pattern_list:
214
+ return False
215
+ for pattern in ignore_pattern_list:
216
+ if fnmatch.fnmatch(os.path.basename(path), pattern) or fnmatch.fnmatch(path, pattern):
217
+ return True
218
+ return False
219
+
220
+ with ZipFile(destination, "w", compression=ZIP_DEFLATED, compresslevel=9) as zip:
221
+ for root, dirs, files in os.walk(source):
222
+ # Filter out ignored directories in-place to prevent walking into them
223
+ dirs[:] = [d for d in dirs if not should_ignore(d)]
224
+
225
+ for file in files:
226
+ file_path: str = clean_path(os.path.join(root, file))
227
+ rel_path = os.path.relpath(file_path, source)
228
+
229
+ # Skip files that match any ignore pattern
230
+ if should_ignore(file) or should_ignore(rel_path):
231
+ continue
232
+
233
+ info: ZipInfo = ZipInfo(rel_path)
234
+ info.compress_type = ZIP_DEFLATED
235
+ if override_time:
236
+ info.date_time = override_time
237
+ with open(file_path, "rb") as f:
238
+ zip.writestr(info, f.read())
239
+
240
+ # Copy the archive to the destination(s)
241
+ for dest_file in destinations[1:]:
242
+ @handle_error(Exception, message=f"Unable to copy '{destination}' to '{dest_file}'", error_log=LogLevels.WARNING)
243
+ def internal(src: str, dest: str) -> None:
244
+ super_copy(src, dest, create_dir=create_dir)
245
+ internal(destination, clean_path(dest_file))
246
+
247
+ return True
248
+
249
+
250
+ # Main entry point for command line usage
251
+ def archive_cli() -> None:
252
+ """ Main entry point for command line usage.
253
+
254
+ Examples:
255
+
256
+ .. code-block:: bash
257
+
258
+ # Repair a corrupted zip file
259
+ python -m stouputils.archive repair /path/to/corrupted.zip /path/to/repaired.zip
260
+
261
+ # Create a zip archive
262
+ python -m stouputils.archive make /path/to/source /path/to/destination.zip
263
+
264
+ # Create a zip archive with ignore patterns
265
+ python -m stouputils.archive make /path/to/source /path/to/destination.zip --ignore "*.pyc,__pycache__"
266
+ """
267
+ import argparse
268
+ import sys
269
+
270
+ # Check for help or no command
271
+ if len(sys.argv) == 1 or (len(sys.argv) == 2 and sys.argv[1] in ("--help", "-h", "help")):
272
+ separator: str = "─" * 60
273
+ print(f"{CYAN}{separator}{RESET}")
274
+ print(f"{CYAN}stouputils {GREEN}archive {CYAN}utilities{RESET}")
275
+ print(f"{CYAN}{separator}{RESET}")
276
+ print(f"\n{CYAN}Usage:{RESET} stouputils archive <command> [options]")
277
+ print(f"\n{CYAN}Available commands:{RESET}")
278
+ print(f" {GREEN}make{RESET} <source> <destination> [--ignore PATTERNS] [--create-dir]")
279
+ print(" Create a zip archive from source directory")
280
+ print(f" {CYAN}--ignore{RESET} Glob patterns to ignore (comma-separated)")
281
+ print(f" {CYAN}--create-dir{RESET} Create destination directory if needed")
282
+ print(f"\n {GREEN}repair{RESET} <input_file> [output_file]")
283
+ print(" Repair a corrupted zip file")
284
+ print(" If output_file is omitted, adds '_repaired' suffix")
285
+ print(f"{CYAN}{separator}{RESET}")
286
+ return
287
+
288
+ parser = argparse.ArgumentParser(description="Archive utilities")
289
+ subparsers = parser.add_subparsers(dest="command", help="Available commands")
290
+
291
+ # Repair command
292
+ repair_parser = subparsers.add_parser("repair", help="Repair a corrupted zip file")
293
+ repair_parser.add_argument("input_file", help="Path to the corrupted zip file")
294
+ repair_parser.add_argument("output_file", nargs="?", help="Path to the repaired zip file (optional, defaults to input_file with '_repaired' suffix)")
295
+
296
+ # Make archive command
297
+ archive_parser = subparsers.add_parser("make", help="Create a zip archive")
298
+ archive_parser.add_argument("source", help="Source directory to archive")
299
+ archive_parser.add_argument("destination", help="Destination zip file")
300
+ archive_parser.add_argument("--ignore", help="Glob patterns to ignore (comma-separated)")
301
+ archive_parser.add_argument("--create-dir", action="store_true", help="Create destination directory if it doesn't exist")
302
+
303
+ args = parser.parse_args()
304
+
305
+ if args.command == "repair":
306
+ input_file = args.input_file
307
+ if args.output_file:
308
+ output_file = args.output_file
309
+ else:
310
+ # Generate default output filename
311
+ base, ext = os.path.splitext(input_file)
312
+ output_file = f"{base}_repaired{ext}"
313
+
314
+ debug(f"Repairing '{input_file}' to '{output_file}'...")
315
+ try:
316
+ repair_zip_file(input_file, output_file)
317
+ info(f"Successfully repaired zip file: {output_file}")
318
+ except Exception as e:
319
+ error(f"Error repairing zip file: {e}", exit=False)
320
+ sys.exit(1)
321
+
322
+ elif args.command == "make":
323
+ debug(f"Creating archive from '{args.source}' to '{args.destination}'...")
324
+ try:
325
+ make_archive(
326
+ source=args.source,
327
+ destinations=args.destination,
328
+ create_dir=args.create_dir,
329
+ ignore_patterns=args.ignore
330
+ )
331
+ info(f"Successfully created archive: {args.destination}")
332
+ except Exception as e:
333
+ error(f"Error creating archive: {e}", exit=False)
334
+ sys.exit(1)
335
+
336
+ else:
337
+ parser.print_help()
338
+ sys.exit(1)
339
+
340
+
341
+ if __name__ == "__main__":
342
+ archive_cli()
343
+
344
+