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/print.py ADDED
@@ -0,0 +1,482 @@
1
+ """
2
+ This module provides utility functions for printing messages with different levels of importance.
3
+
4
+ If a message is printed multiple times, it will be displayed as "(xN) message"
5
+ where N is the number of times the message has been printed.
6
+
7
+ .. image:: https://raw.githubusercontent.com/Stoupy51/stouputils/refs/heads/main/assets/print_module.gif
8
+ :alt: stouputils print examples
9
+ """
10
+
11
+ # Imports
12
+ import os
13
+ import sys
14
+ import time
15
+ from collections.abc import Callable, Iterable, Iterator
16
+ from typing import IO, Any, TextIO, TypeVar, cast
17
+
18
+ # Colors constants
19
+ RESET: str = "\033[0m"
20
+ RED: str = "\033[91m"
21
+ GREEN: str = "\033[92m"
22
+ YELLOW: str = "\033[93m"
23
+ BLUE: str = "\033[94m"
24
+ MAGENTA: str = "\033[95m"
25
+ CYAN: str = "\033[96m"
26
+ LINE_UP: str = "\033[1A"
27
+
28
+ # Constants
29
+ BAR_FORMAT: str = "{l_bar}{bar}" + MAGENTA + "| {n_fmt}/{total_fmt} [{rate_fmt}{postfix}, {elapsed}<{remaining}]" + RESET
30
+ T = TypeVar("T")
31
+
32
+ # Enable colors on Windows 10 terminal if applicable
33
+ if os.name == "nt":
34
+ os.system("color")
35
+
36
+ # Print functions
37
+ previous_args_kwards: tuple[Any, Any] = ((), {})
38
+ nb_values: int = 1
39
+ import_time: float = time.time()
40
+
41
+ # Colored for loop function
42
+ def colored_for_loop[T](
43
+ iterable: Iterable[T],
44
+ desc: str = "Processing",
45
+ color: str = MAGENTA,
46
+ bar_format: str = BAR_FORMAT,
47
+ ascii: bool = False,
48
+ smooth_tqdm: bool = True,
49
+ **kwargs: Any
50
+ ) -> Iterator[T]:
51
+ """ Function to iterate over a list with a colored TQDM progress bar like the other functions in this module.
52
+
53
+ Args:
54
+ iterable (Iterable): List to iterate over
55
+ desc (str): Description of the function execution displayed in the progress bar
56
+ color (str): Color of the progress bar (Defaults to MAGENTA)
57
+ bar_format (str): Format of the progress bar (Defaults to BAR_FORMAT)
58
+ ascii (bool): Whether to use ASCII or Unicode characters for the progress bar (Defaults to False)
59
+ smooth_tqdm (bool): Whether to enable smooth progress bar updates by setting miniters=1 and mininterval=0.0 (Defaults to True)
60
+ **kwargs: Additional arguments to pass to the TQDM progress bar
61
+
62
+ Yields:
63
+ T: Each item of the iterable
64
+
65
+ Examples:
66
+ >>> for i in colored_for_loop(range(10), desc="Time sleeping loop"):
67
+ ... time.sleep(0.01)
68
+ >>> # Time sleeping loop: 100%|██████████████████| 10/10 [ 95.72it/s, 00:00<00:00]
69
+ """
70
+ if bar_format == BAR_FORMAT:
71
+ bar_format = bar_format.replace(MAGENTA, color)
72
+ desc = color + desc
73
+
74
+ if smooth_tqdm:
75
+ kwargs.setdefault("mininterval", 0.0)
76
+ try:
77
+ total = len(iterable) # type: ignore
78
+ import shutil
79
+ width = shutil.get_terminal_size().columns
80
+ kwargs.setdefault("miniters", max(1, total // width))
81
+ except (TypeError, OSError):
82
+ kwargs.setdefault("miniters", 1)
83
+
84
+ from tqdm.auto import tqdm
85
+ yield from tqdm(iterable, desc=desc, bar_format=bar_format, ascii=ascii, **kwargs)
86
+
87
+ def info(
88
+ *values: Any,
89
+ color: str = GREEN,
90
+ text: str = "INFO ",
91
+ prefix: str = "",
92
+ file: TextIO | list[TextIO] | None = None,
93
+ **print_kwargs: Any,
94
+ ) -> None:
95
+ """ Print an information message looking like "[INFO HH:MM:SS] message" in green by default.
96
+
97
+ Args:
98
+ values (Any): Values to print (like the print function)
99
+ color (str): Color of the message (default: GREEN)
100
+ text (str): Text of the message (default: "INFO ")
101
+ prefix (str): Prefix to add to the values
102
+ file (TextIO|list[TextIO]): File(s) to write the message to (default: sys.stdout)
103
+ print_kwargs (dict): Keyword arguments to pass to the print function
104
+ """
105
+ # Use stdout if no file is specified
106
+ if file is None:
107
+ file = sys.stdout
108
+
109
+ # If file is a list, recursively call info() for each file
110
+ if isinstance(file, list):
111
+ for f in file:
112
+ info(*values, color=color, text=text, prefix=prefix, file=f, **print_kwargs)
113
+ else:
114
+ # Build the message with prefix, color, text and timestamp
115
+ message: str = f"{prefix}{color}[{text} {current_time()}]"
116
+
117
+ # If this is a repeated print, add a line up and counter
118
+ if is_same_print(*values, color=color, text=text, prefix=prefix, **print_kwargs):
119
+ message = f"{LINE_UP}{message} (x{nb_values})"
120
+
121
+ # Print the message with the values and reset color
122
+ print(message, *values, RESET, file=file, **print_kwargs)
123
+
124
+ def debug(*values: Any, **print_kwargs: Any) -> None:
125
+ """ Print a debug message looking like "[DEBUG HH:MM:SS] message" in cyan by default. """
126
+ if "text" not in print_kwargs:
127
+ print_kwargs["text"] = "DEBUG"
128
+ if "color" not in print_kwargs:
129
+ print_kwargs["color"] = CYAN
130
+ info(*values, **print_kwargs)
131
+
132
+ def alt_debug(*values: Any, **print_kwargs: Any) -> None:
133
+ """ Print a debug message looking like "[DEBUG HH:MM:SS] message" in blue by default. """
134
+ if "text" not in print_kwargs:
135
+ print_kwargs["text"] = "DEBUG"
136
+ if "color" not in print_kwargs:
137
+ print_kwargs["color"] = BLUE
138
+ info(*values, **print_kwargs)
139
+
140
+ def suggestion(*values: Any, **print_kwargs: Any) -> None:
141
+ """ Print a suggestion message looking like "[SUGGESTION HH:MM:SS] message" in cyan by default. """
142
+ if "text" not in print_kwargs:
143
+ print_kwargs["text"] = "SUGGESTION"
144
+ if "color" not in print_kwargs:
145
+ print_kwargs["color"] = CYAN
146
+ info(*values, **print_kwargs)
147
+
148
+ def progress(*values: Any, **print_kwargs: Any) -> None:
149
+ """ Print a progress message looking like "[PROGRESS HH:MM:SS] message" in magenta by default. """
150
+ if "text" not in print_kwargs:
151
+ print_kwargs["text"] = "PROGRESS"
152
+ if "color" not in print_kwargs:
153
+ print_kwargs["color"] = MAGENTA
154
+ info(*values, **print_kwargs)
155
+
156
+ def warning(*values: Any, **print_kwargs: Any) -> None:
157
+ """ Print a warning message looking like "[WARNING HH:MM:SS] message" in yellow by default and in sys.stderr. """
158
+ if "file" not in print_kwargs:
159
+ print_kwargs["file"] = sys.stderr
160
+ if "text" not in print_kwargs:
161
+ print_kwargs["text"] = "WARNING"
162
+ if "color" not in print_kwargs:
163
+ print_kwargs["color"] = YELLOW
164
+ info(*values, **print_kwargs)
165
+
166
+ def error(*values: Any, exit: bool = False, **print_kwargs: Any) -> None:
167
+ """ Print an error message (in sys.stderr and in red by default)
168
+ and optionally ask the user to continue or stop the program.
169
+
170
+ Args:
171
+ values (Any): Values to print (like the print function)
172
+ exit (bool): Whether to ask the user to continue or stop the program,
173
+ false to ignore the error automatically and continue
174
+ print_kwargs (dict): Keyword arguments to pass to the print function
175
+ """
176
+ file: TextIO = sys.stderr
177
+ if "file" in print_kwargs:
178
+ if isinstance(print_kwargs["file"], list):
179
+ file = cast(TextIO, print_kwargs["file"][0])
180
+ else:
181
+ file = print_kwargs["file"]
182
+ if "text" not in print_kwargs:
183
+ print_kwargs["text"] = "ERROR"
184
+ if "color" not in print_kwargs:
185
+ print_kwargs["color"] = RED
186
+ info(*values, **print_kwargs)
187
+ if exit:
188
+ try:
189
+ print("Press enter to ignore error and continue, or 'CTRL+C' to stop the program... ", file=file)
190
+ input()
191
+ except (KeyboardInterrupt, EOFError):
192
+ print(file=file)
193
+ sys.exit(1)
194
+
195
+ def whatisit(
196
+ *values: Any,
197
+ print_function: Callable[..., None] = debug,
198
+ max_length: int = 250,
199
+ color: str = CYAN,
200
+ **print_kwargs: Any,
201
+ ) -> None:
202
+ """ Print the type of each value and the value itself, with its id and length/shape.
203
+
204
+ The output format is: "type, <id id_number>: (length/shape) value"
205
+
206
+ Args:
207
+ values (Any): Values to print
208
+ print_function (Callable): Function to use to print the values (default: debug())
209
+ max_length (int): Maximum length of the value string to print (default: 250)
210
+ color (str): Color of the message (default: CYAN)
211
+ print_kwargs (dict): Keyword arguments to pass to the print function
212
+ """
213
+ def _internal(value: Any) -> str:
214
+ """ Get the string representation of the value, with length or shape instead of length if shape is available """
215
+
216
+ # Build metadata parts list
217
+ metadata_parts: list[str] = []
218
+
219
+ # Get the dtype if available
220
+ try:
221
+ metadata_parts.append(f"dtype: {value.dtype}")
222
+ except (AttributeError, TypeError):
223
+ pass
224
+
225
+ # Get the shape or length of the value
226
+ try:
227
+ metadata_parts.append(f"shape: {value.shape}")
228
+ except (AttributeError, TypeError):
229
+ try:
230
+ metadata_parts.append(f"length: {len(value)}")
231
+ except (AttributeError, TypeError):
232
+ pass
233
+
234
+ # Get the min and max if available (Iterable of numbers)
235
+ try:
236
+ if not isinstance(value, str | bytes | bytearray | dict | int | float):
237
+ import numpy as np
238
+ mini, maxi = np.min(value), np.max(value)
239
+ if mini != maxi:
240
+ metadata_parts.append(f"min: {mini}")
241
+ metadata_parts.append(f"max: {maxi}")
242
+ except (Exception):
243
+ pass
244
+
245
+ # Combine metadata into a single parenthesized string
246
+ metadata_str: str = f"({', '.join(metadata_parts)}) " if metadata_parts else ""
247
+
248
+ # Get the string representation of the value
249
+ value = cast(Any, value)
250
+ value_str: str = str(value)
251
+ if len(value_str) > max_length:
252
+ value_str = value_str[:max_length] + "..."
253
+ if "\n" in value_str:
254
+ value_str = "\n" + value_str # Add a newline before the value if there is a newline in it.
255
+
256
+ # Return the formatted string
257
+ return f"{type(value)}, <id {id(value)}>: {metadata_str}{value_str}"
258
+
259
+ # Add the color to the message
260
+ if "color" not in print_kwargs:
261
+ print_kwargs["color"] = color
262
+
263
+ # Set text to "What is it?" if not already set
264
+ if "text" not in print_kwargs:
265
+ print_kwargs["text"] = "What is it?"
266
+
267
+ # Print the values
268
+ if len(values) > 1:
269
+ print_function("".join(f"\n {_internal(value)}" for value in values), **print_kwargs)
270
+ elif len(values) == 1:
271
+ print_function(_internal(values[0]), **print_kwargs)
272
+
273
+ def breakpoint(*values: Any, print_function: Callable[..., None] = warning, **print_kwargs: Any) -> None:
274
+ """ Breakpoint function, pause the program and print the values.
275
+
276
+ Args:
277
+ values (Any): Values to print
278
+ print_function (Callable): Function to use to print the values (default: warning())
279
+ print_kwargs (dict): Keyword arguments to pass to the print function
280
+ """
281
+ if "text" not in print_kwargs:
282
+ print_kwargs["text"] = "BREAKPOINT (press Enter)"
283
+ file: TextIO = sys.stderr
284
+ if "file" in print_kwargs:
285
+ if isinstance(print_kwargs["file"], list):
286
+ file = cast(TextIO, print_kwargs["file"][0])
287
+ else:
288
+ file = print_kwargs["file"]
289
+ whatisit(*values, print_function=print_function, **print_kwargs)
290
+ try:
291
+ input()
292
+ except (KeyboardInterrupt, EOFError):
293
+ print(file=file)
294
+ sys.exit(1)
295
+
296
+
297
+ # TeeMultiOutput class to duplicate output to multiple file-like objects
298
+ class TeeMultiOutput:
299
+ """ File-like object that duplicates output to multiple file-like objects.
300
+
301
+ Args:
302
+ *files (IO[Any]): One or more file-like objects that have write and flush methods
303
+ strip_colors (bool): Strip ANSI color codes from output sent to non-stdout/stderr files
304
+ ascii_only (bool): Replace non-ASCII characters with their ASCII equivalents for non-stdout/stderr files
305
+ ignore_lineup (bool): Ignore lines containing LINE_UP escape sequence in non-terminal outputs
306
+
307
+ Examples:
308
+ >>> f = open("logfile.txt", "w")
309
+ >>> sys.stdout = TeeMultiOutput(sys.stdout, f)
310
+ >>> print("Hello World") # Output goes to both console and file
311
+ Hello World
312
+ >>> f.close() # TeeMultiOutput will handle any future writes to closed files gracefully
313
+ """
314
+ def __init__(
315
+ self, *files: IO[Any], strip_colors: bool = True, ascii_only: bool = True, ignore_lineup: bool = True
316
+ ) -> None:
317
+ # Flatten any TeeMultiOutput instances in files
318
+ flattened_files: list[IO[Any]] = []
319
+ for file in files:
320
+ if isinstance(file, TeeMultiOutput):
321
+ flattened_files.extend(file.files)
322
+ else:
323
+ flattened_files.append(file)
324
+
325
+ self.files: tuple[IO[Any], ...] = tuple(flattened_files)
326
+ """ File-like objects to write to """
327
+ self.strip_colors: bool = strip_colors
328
+ """ Whether to strip ANSI color codes from output sent to non-stdout/stderr files """
329
+ self.ascii_only: bool = ascii_only
330
+ """ Whether to replace non-ASCII characters with their ASCII equivalents for non-stdout/stderr files """
331
+ self.ignore_lineup: bool = ignore_lineup
332
+ """ Whether to ignore lines containing LINE_UP escape sequence in non-terminal outputs """
333
+
334
+ @property
335
+ def encoding(self) -> str:
336
+ """ Get the encoding of the first file, or "utf-8" as fallback.
337
+
338
+ Returns:
339
+ str: The encoding, ex: "utf-8", "ascii", "latin1", etc.
340
+ """
341
+ try:
342
+ return self.files[0].encoding # type: ignore
343
+ except (IndexError, AttributeError):
344
+ return "utf-8"
345
+
346
+ def write(self, obj: str) -> int:
347
+ """ Write the object to all files while stripping colors if needed.
348
+
349
+ Args:
350
+ obj (str): String to write
351
+ Returns:
352
+ int: Number of characters written to the first file
353
+ """
354
+ files_to_remove: list[IO[Any]] = []
355
+ num_chars_written: int = 0
356
+ for i, f in enumerate(self.files):
357
+ try:
358
+ # Check if file is closed
359
+ if hasattr(f, "closed") and f.closed:
360
+ files_to_remove.append(f)
361
+ continue
362
+
363
+ # Check if this file is a terminal/console or a regular file
364
+ content: str = obj
365
+ if not (hasattr(f, "isatty") and f.isatty()):
366
+ # Non-terminal files get processed content (stripped colors, ASCII-only, etc.)
367
+
368
+ # Skip content if it contains LINE_UP and ignore_lineup is True
369
+ if self.ignore_lineup and (LINE_UP in content or "\r" in content):
370
+ continue
371
+
372
+ # Strip colors if needed
373
+ if self.strip_colors:
374
+ content = remove_colors(content)
375
+
376
+ # Replace Unicode block characters with ASCII equivalents
377
+ # Replace other problematic Unicode characters as needed
378
+ if self.ascii_only:
379
+ content = content.replace('█', '#')
380
+ content = ''.join(c if ord(c) < 128 else '?' for c in content)
381
+
382
+ # Write content to file
383
+ if i == 0:
384
+ num_chars_written = f.write(content)
385
+ else:
386
+ f.write(content)
387
+
388
+ except ValueError:
389
+ # ValueError is raised when writing to a closed file
390
+ files_to_remove.append(f)
391
+ except Exception:
392
+ pass
393
+
394
+ # Remove closed files from the list
395
+ if files_to_remove:
396
+ self.files = tuple(f for f in self.files if f not in files_to_remove)
397
+ return num_chars_written
398
+
399
+ def flush(self) -> None:
400
+ """ Flush all files. """
401
+ for f in self.files:
402
+ try:
403
+ f.flush()
404
+ except Exception:
405
+ pass
406
+
407
+ def fileno(self) -> int:
408
+ """ Return the file descriptor of the first file. """
409
+ return self.files[0].fileno() if hasattr(self.files[0], "fileno") else 0
410
+
411
+
412
+ # Utility functions
413
+ def remove_colors(text: str) -> str:
414
+ """ Remove the colors from a text """
415
+ for color in [RESET, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, LINE_UP]:
416
+ text = text.replace(color, "")
417
+ return text
418
+
419
+ def is_same_print(*args: Any, **kwargs: Any) -> bool:
420
+ """ Checks if the current print call is the same as the previous one. """
421
+ global previous_args_kwards, nb_values
422
+ try:
423
+ if previous_args_kwards == (args, kwargs):
424
+ nb_values += 1
425
+ return True
426
+ except Exception:
427
+ # Comparison failed (e.g., comparing DataFrames or other complex objects)
428
+ # Use str() for comparison instead
429
+ current_str: str = str((args, kwargs))
430
+ previous_str: str = str(previous_args_kwards)
431
+ if previous_str == current_str:
432
+ nb_values += 1
433
+ return True
434
+ # Else, update previous args and reset counter
435
+ previous_args_kwards = (args, kwargs)
436
+ nb_values = 1
437
+ return False
438
+
439
+ def current_time() -> str:
440
+ """ Get the current time as "HH:MM:SS" if less than 24 hours since import, else "YYYY-MM-DD HH:MM:SS" """
441
+ # If the import time is more than 24 hours, return the full datetime
442
+ if (time.time() - import_time) > (24 * 60 * 60):
443
+ return time.strftime("%Y-%m-%d %H:%M:%S")
444
+ else:
445
+ return time.strftime("%H:%M:%S")
446
+
447
+
448
+ # Test the print functions
449
+ if __name__ == "__main__":
450
+ info("Hello", "World")
451
+ time.sleep(0.5)
452
+ info("Hello", "World")
453
+ time.sleep(0.5)
454
+ info("Hello", "World")
455
+ time.sleep(0.5)
456
+ info("Not Hello World !")
457
+ time.sleep(0.5)
458
+ info("Hello", "World")
459
+ time.sleep(0.5)
460
+ info("Hello", "World")
461
+
462
+ # All remaining print functions
463
+ alt_debug("Hello", "World")
464
+ debug("Hello", "World")
465
+ suggestion("Hello", "World")
466
+ progress("Hello", "World")
467
+ warning("Hello", "World")
468
+ error("Hello", "World", exit=False)
469
+ whatisit("Hello")
470
+ whatisit("Hello", "World")
471
+
472
+ # Test whatisit with different types
473
+ import numpy as np
474
+ print()
475
+ whatisit(
476
+ 123,
477
+ "Hello World",
478
+ [1, 2, 3, 4, 5],
479
+ np.array([[1, 2, 3], [4, 5, 6]]),
480
+ {"a": 1, "b": 2},
481
+ )
482
+
stouputils/py.typed ADDED
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,15 @@
1
+ from ._deprecated import *
2
+ from .all_doctests import *
3
+ from .archive import *
4
+ from .backup import *
5
+ from .collections import *
6
+ from .continuous_delivery import *
7
+ from .ctx import *
8
+ from .decorators import *
9
+ from .image import *
10
+ from .io import *
11
+ from .parallel import *
12
+ from .print import *
13
+ from .version_pkg import *
14
+
15
+ __version__: str
@@ -0,0 +1,12 @@
1
+ from .decorators import LogLevels as LogLevels, deprecated as deprecated
2
+ from .io import csv_dump as csv_dump, csv_load as csv_load, json_dump as json_dump, json_load as json_load
3
+ from typing import Any
4
+
5
+ def super_csv_dump(*args: Any, **kwargs: Any) -> Any:
6
+ ''' Deprecated function, use "csv_dump" instead. '''
7
+ def super_csv_load(*args: Any, **kwargs: Any) -> Any:
8
+ ''' Deprecated function, use "csv_load" instead. '''
9
+ def super_json_dump(*args: Any, **kwargs: Any) -> Any:
10
+ ''' Deprecated function, use "json_dump" instead. '''
11
+ def super_json_load(*args: Any, **kwargs: Any) -> Any:
12
+ ''' Deprecated function, use "json_load" instead. '''
@@ -0,0 +1,46 @@
1
+ from . import decorators as decorators
2
+ from .decorators import measure_time as measure_time
3
+ from .io import clean_path as clean_path, relative_path as relative_path
4
+ from .print import error as error, info as info, progress as progress, warning as warning
5
+ from doctest import TestResults as TestResults
6
+ from types import ModuleType
7
+
8
+ def launch_tests(root_dir: str, strict: bool = True) -> int:
9
+ ''' Main function to launch tests for all modules in the given directory.
10
+
11
+ \tArgs:
12
+ \t\troot_dir\t\t\t\t(str):\t\t\tRoot directory to search for modules
13
+ \t\tstrict\t\t\t\t\t(bool):\t\t\tModify the force_raise_exception variable to True in the decorators module
14
+
15
+ \tReturns:
16
+ \t\tint: The number of failed tests
17
+
18
+ \tExamples:
19
+ \t\t>>> launch_tests("unknown_dir")
20
+ \t\tTraceback (most recent call last):
21
+ \t\t\t...
22
+ \t\tValueError: No modules found in \'unknown_dir\'
23
+
24
+ \t.. code-block:: python
25
+
26
+ \t\t> if launch_tests("/path/to/source") > 0:
27
+ \t\t\tsys.exit(1)
28
+ \t\t[PROGRESS HH:MM:SS] Importing module \'module1\'\ttook 0.001s
29
+ \t\t[PROGRESS HH:MM:SS] Importing module \'module2\'\ttook 0.002s
30
+ \t\t[PROGRESS HH:MM:SS] Importing module \'module3\'\ttook 0.003s
31
+ \t\t[PROGRESS HH:MM:SS] Importing module \'module4\'\ttook 0.004s
32
+ \t\t[INFO HH:MM:SS] Testing 4 modules...
33
+ \t\t[PROGRESS HH:MM:SS] Testing module \'module1\'\ttook 0.005s
34
+ \t\t[PROGRESS HH:MM:SS] Testing module \'module2\'\ttook 0.006s
35
+ \t\t[PROGRESS HH:MM:SS] Testing module \'module3\'\ttook 0.007s
36
+ \t\t[PROGRESS HH:MM:SS] Testing module \'module4\'\ttook 0.008s
37
+ \t'''
38
+ def test_module_with_progress(module: ModuleType, separator: str) -> TestResults:
39
+ """ Test a module with testmod and measure the time taken with progress printing.
40
+
41
+ \tArgs:
42
+ \t\tmodule\t\t(ModuleType):\tModule to test
43
+ \t\tseparator\t(str):\t\t\tSeparator string for alignment in output
44
+ \tReturns:
45
+ \t\tTestResults: The results of the tests
46
+ \t"""
@@ -0,0 +1,2 @@
1
+ from .automatic_docs import *
2
+ from .upscaler import *
@@ -0,0 +1,106 @@
1
+ from ..continuous_delivery import version_to_float as version_to_float
2
+ from ..decorators import LogLevels as LogLevels, handle_error as handle_error, simple_cache as simple_cache
3
+ from ..io import clean_path as clean_path, json_dump as json_dump, super_open as super_open
4
+ from ..print import info as info
5
+ from collections.abc import Callable as Callable
6
+
7
+ REQUIREMENTS: list[str]
8
+
9
+ def check_dependencies(html_theme: str) -> None:
10
+ ''' Check for each requirement if it is installed.
11
+
12
+ \tArgs:
13
+ \t\thtml_theme (str): HTML theme to use for the documentation, to check if it is installed (e.g. "breeze", "pydata_sphinx_theme", "furo", etc.)
14
+ \t'''
15
+ def get_sphinx_conf_content(project: str, project_dir: str, author: str, current_version: str, copyright: str, html_logo: str, html_favicon: str, html_theme: str = 'breeze', github_user: str = '', github_repo: str = '', version_list: list[str] | None = None, skip_undocumented: bool = True) -> str:
16
+ """ Get the content of the Sphinx configuration file.
17
+
18
+ \tArgs:
19
+ \t\tproject (str): Name of the project
20
+ \t\tproject_dir (str): Path to the project directory
21
+ \t\tauthor (str): Author of the project
22
+ \t\tcurrent_version (str): Current version
23
+ \t\tcopyright (str): Copyright information
24
+ \t\thtml_logo (str): URL to the logo
25
+ \t\thtml_favicon (str): URL to the favicon
26
+ \t\tgithub_user (str): GitHub username
27
+ \t\tgithub_repo (str): GitHub repository name
28
+ \t\tversion_list (list[str] | None): List of versions. Defaults to None
29
+ \t\tskip_undocumented (bool): Whether to skip undocumented members. Defaults to True
30
+
31
+ \tReturns:
32
+ \t\tstr: Content of the Sphinx configuration file
33
+ \t"""
34
+ def get_versions_from_github(github_user: str, github_repo: str, recent_minor_versions: int = 2) -> list[str]:
35
+ """ Get list of versions from GitHub gh-pages branch.
36
+ \tOnly shows detailed versions for the last N minor versions, and keeps only
37
+ \tthe latest patch version for older minor versions.
38
+
39
+ \tArgs:
40
+ \t\tgithub_user (str): GitHub username
41
+ \t\tgithub_repo (str): GitHub repository name
42
+ \t\trecent_minor_versions (int): Number of recent minor versions to show all patches for (-1 for all).
43
+
44
+ \tReturns:
45
+ \t\tlist[str]: List of versions, with 'latest' as first element
46
+ \t"""
47
+ def markdown_to_rst(markdown_content: str) -> str:
48
+ """ Convert markdown content to RST format.
49
+
50
+ \tArgs:
51
+ \t\tmarkdown_content (str): Markdown content
52
+
53
+ \tReturns:
54
+ \t\tstr: RST content
55
+ \t"""
56
+ def generate_index_rst(readme_path: str, index_path: str, project: str, github_user: str, github_repo: str, get_versions_function: Callable[[str, str, int], list[str]] = ..., recent_minor_versions: int = 2) -> None:
57
+ """ Generate index.rst from README.md content.
58
+
59
+ \tArgs:
60
+ \t\treadme_path (str): Path to the README.md file
61
+ \t\tindex_path (str): Path where index.rst should be created
62
+ \t\tproject (str): Name of the project
63
+ \t\tgithub_user (str): GitHub username
64
+ \t\tgithub_repo (str): GitHub repository name
65
+ \t\tget_versions_function (Callable[[str, str, int], list[str]]): Function to get versions from GitHub
66
+ \t\trecent_minor_versions (int): Number of recent minor versions to show all patches for. Defaults to 2
67
+ \t"""
68
+ def generate_documentation(source_dir: str, modules_dir: str, project_dir: str, build_dir: str) -> None:
69
+ """ Generate documentation using Sphinx.
70
+
71
+ \tArgs:
72
+ \t\tsource_dir (str): Source directory
73
+ \t\tmodules_dir (str): Modules directory
74
+ \t\tproject_dir (str): Project directory
75
+ \t\tbuild_dir (str): Build directory
76
+ \t"""
77
+ def generate_redirect_html(filepath: str) -> None:
78
+ """ Generate HTML content for redirect page.
79
+
80
+ \tArgs:
81
+ \t\tfilepath (str): Path to the file where the HTML content should be written
82
+ \t"""
83
+ def update_documentation(root_path: str, project: str, project_dir: str = '', author: str = 'Author', copyright: str = '2025, Author', html_logo: str = '', html_favicon: str = '', html_theme: str = 'breeze', github_user: str = '', github_repo: str = '', version: str | None = None, skip_undocumented: bool = True, recent_minor_versions: int = 2, get_versions_function: Callable[[str, str, int], list[str]] = ..., generate_index_function: Callable[..., None] = ..., generate_docs_function: Callable[..., None] = ..., generate_redirect_function: Callable[[str], None] = ..., get_conf_content_function: Callable[..., str] = ...) -> None:
84
+ ''' Update the Sphinx documentation.
85
+
86
+ \tArgs:
87
+ \t\troot_path (str): Root path of the project
88
+ \t\tproject (str): Name of the project
89
+ \t\tproject_dir (str): Path to the project directory (to be used with generate_docs_function)
90
+ \t\tauthor (str): Author of the project
91
+ \t\tcopyright (str): Copyright information
92
+ \t\thtml_logo (str): URL to the logo
93
+ \t\thtml_favicon (str): URL to the favicon
94
+ \t\thtml_theme (str): Theme to use for the documentation. Defaults to "breeze"
95
+ \t\tgithub_user (str): GitHub username
96
+ \t\tgithub_repo (str): GitHub repository name
97
+ \t\tversion (str | None): Version to build documentation for (e.g. "1.0.0", defaults to "latest")
98
+ \t\tskip_undocumented (bool): Whether to skip undocumented members. Defaults to True
99
+ \t\trecent_minor_versions (int): Number of recent minor versions to show all patches for. Defaults to 2
100
+
101
+ \t\tget_versions_function (Callable[[str, str, int], list[str]]): Function to get versions from GitHub
102
+ \t\tgenerate_index_function (Callable[..., None]): Function to generate index.rst
103
+ \t\tgenerate_docs_function (Callable[..., None]): Function to generate documentation
104
+ \t\tgenerate_redirect_function (Callable[[str], None]): Function to create redirect file
105
+ \t\tget_conf_content_function (Callable[..., str]): Function to get Sphinx conf.py content
106
+ \t'''
@@ -0,0 +1,3 @@
1
+ from .config import *
2
+ from .image import *
3
+ from .video import *