stouputils 1.12.1__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 (138) hide show
  1. stouputils/__init__.py +40 -0
  2. stouputils/__init__.pyi +14 -0
  3. stouputils/__main__.py +81 -0
  4. stouputils/_deprecated.py +37 -0
  5. stouputils/_deprecated.pyi +12 -0
  6. stouputils/all_doctests.py +160 -0
  7. stouputils/all_doctests.pyi +46 -0
  8. stouputils/applications/__init__.py +22 -0
  9. stouputils/applications/__init__.pyi +2 -0
  10. stouputils/applications/automatic_docs.py +634 -0
  11. stouputils/applications/automatic_docs.pyi +106 -0
  12. stouputils/applications/upscaler/__init__.py +39 -0
  13. stouputils/applications/upscaler/__init__.pyi +3 -0
  14. stouputils/applications/upscaler/config.py +128 -0
  15. stouputils/applications/upscaler/config.pyi +18 -0
  16. stouputils/applications/upscaler/image.py +247 -0
  17. stouputils/applications/upscaler/image.pyi +109 -0
  18. stouputils/applications/upscaler/video.py +287 -0
  19. stouputils/applications/upscaler/video.pyi +60 -0
  20. stouputils/archive.py +344 -0
  21. stouputils/archive.pyi +67 -0
  22. stouputils/backup.py +488 -0
  23. stouputils/backup.pyi +109 -0
  24. stouputils/collections.py +244 -0
  25. stouputils/collections.pyi +86 -0
  26. stouputils/continuous_delivery/__init__.py +27 -0
  27. stouputils/continuous_delivery/__init__.pyi +5 -0
  28. stouputils/continuous_delivery/cd_utils.py +243 -0
  29. stouputils/continuous_delivery/cd_utils.pyi +129 -0
  30. stouputils/continuous_delivery/github.py +522 -0
  31. stouputils/continuous_delivery/github.pyi +162 -0
  32. stouputils/continuous_delivery/pypi.py +91 -0
  33. stouputils/continuous_delivery/pypi.pyi +43 -0
  34. stouputils/continuous_delivery/pyproject.py +147 -0
  35. stouputils/continuous_delivery/pyproject.pyi +67 -0
  36. stouputils/continuous_delivery/stubs.py +86 -0
  37. stouputils/continuous_delivery/stubs.pyi +39 -0
  38. stouputils/ctx.py +408 -0
  39. stouputils/ctx.pyi +211 -0
  40. stouputils/data_science/config/get.py +51 -0
  41. stouputils/data_science/config/set.py +125 -0
  42. stouputils/data_science/data_processing/image/__init__.py +66 -0
  43. stouputils/data_science/data_processing/image/auto_contrast.py +79 -0
  44. stouputils/data_science/data_processing/image/axis_flip.py +58 -0
  45. stouputils/data_science/data_processing/image/bias_field_correction.py +74 -0
  46. stouputils/data_science/data_processing/image/binary_threshold.py +73 -0
  47. stouputils/data_science/data_processing/image/blur.py +59 -0
  48. stouputils/data_science/data_processing/image/brightness.py +54 -0
  49. stouputils/data_science/data_processing/image/canny.py +110 -0
  50. stouputils/data_science/data_processing/image/clahe.py +92 -0
  51. stouputils/data_science/data_processing/image/common.py +30 -0
  52. stouputils/data_science/data_processing/image/contrast.py +53 -0
  53. stouputils/data_science/data_processing/image/curvature_flow_filter.py +74 -0
  54. stouputils/data_science/data_processing/image/denoise.py +378 -0
  55. stouputils/data_science/data_processing/image/histogram_equalization.py +123 -0
  56. stouputils/data_science/data_processing/image/invert.py +64 -0
  57. stouputils/data_science/data_processing/image/laplacian.py +60 -0
  58. stouputils/data_science/data_processing/image/median_blur.py +52 -0
  59. stouputils/data_science/data_processing/image/noise.py +59 -0
  60. stouputils/data_science/data_processing/image/normalize.py +65 -0
  61. stouputils/data_science/data_processing/image/random_erase.py +66 -0
  62. stouputils/data_science/data_processing/image/resize.py +69 -0
  63. stouputils/data_science/data_processing/image/rotation.py +80 -0
  64. stouputils/data_science/data_processing/image/salt_pepper.py +68 -0
  65. stouputils/data_science/data_processing/image/sharpening.py +55 -0
  66. stouputils/data_science/data_processing/image/shearing.py +64 -0
  67. stouputils/data_science/data_processing/image/threshold.py +64 -0
  68. stouputils/data_science/data_processing/image/translation.py +71 -0
  69. stouputils/data_science/data_processing/image/zoom.py +83 -0
  70. stouputils/data_science/data_processing/image_augmentation.py +118 -0
  71. stouputils/data_science/data_processing/image_preprocess.py +183 -0
  72. stouputils/data_science/data_processing/prosthesis_detection.py +359 -0
  73. stouputils/data_science/data_processing/technique.py +481 -0
  74. stouputils/data_science/dataset/__init__.py +45 -0
  75. stouputils/data_science/dataset/dataset.py +292 -0
  76. stouputils/data_science/dataset/dataset_loader.py +135 -0
  77. stouputils/data_science/dataset/grouping_strategy.py +296 -0
  78. stouputils/data_science/dataset/image_loader.py +100 -0
  79. stouputils/data_science/dataset/xy_tuple.py +696 -0
  80. stouputils/data_science/metric_dictionnary.py +106 -0
  81. stouputils/data_science/metric_utils.py +847 -0
  82. stouputils/data_science/mlflow_utils.py +206 -0
  83. stouputils/data_science/models/abstract_model.py +149 -0
  84. stouputils/data_science/models/all.py +85 -0
  85. stouputils/data_science/models/base_keras.py +765 -0
  86. stouputils/data_science/models/keras/all.py +38 -0
  87. stouputils/data_science/models/keras/convnext.py +62 -0
  88. stouputils/data_science/models/keras/densenet.py +50 -0
  89. stouputils/data_science/models/keras/efficientnet.py +60 -0
  90. stouputils/data_science/models/keras/mobilenet.py +56 -0
  91. stouputils/data_science/models/keras/resnet.py +52 -0
  92. stouputils/data_science/models/keras/squeezenet.py +233 -0
  93. stouputils/data_science/models/keras/vgg.py +42 -0
  94. stouputils/data_science/models/keras/xception.py +38 -0
  95. stouputils/data_science/models/keras_utils/callbacks/__init__.py +20 -0
  96. stouputils/data_science/models/keras_utils/callbacks/colored_progress_bar.py +219 -0
  97. stouputils/data_science/models/keras_utils/callbacks/learning_rate_finder.py +148 -0
  98. stouputils/data_science/models/keras_utils/callbacks/model_checkpoint_v2.py +31 -0
  99. stouputils/data_science/models/keras_utils/callbacks/progressive_unfreezing.py +249 -0
  100. stouputils/data_science/models/keras_utils/callbacks/warmup_scheduler.py +66 -0
  101. stouputils/data_science/models/keras_utils/losses/__init__.py +12 -0
  102. stouputils/data_science/models/keras_utils/losses/next_generation_loss.py +56 -0
  103. stouputils/data_science/models/keras_utils/visualizations.py +416 -0
  104. stouputils/data_science/models/model_interface.py +939 -0
  105. stouputils/data_science/models/sandbox.py +116 -0
  106. stouputils/data_science/range_tuple.py +234 -0
  107. stouputils/data_science/scripts/augment_dataset.py +77 -0
  108. stouputils/data_science/scripts/exhaustive_process.py +133 -0
  109. stouputils/data_science/scripts/preprocess_dataset.py +70 -0
  110. stouputils/data_science/scripts/routine.py +168 -0
  111. stouputils/data_science/utils.py +285 -0
  112. stouputils/decorators.py +595 -0
  113. stouputils/decorators.pyi +242 -0
  114. stouputils/image.py +441 -0
  115. stouputils/image.pyi +172 -0
  116. stouputils/installer/__init__.py +18 -0
  117. stouputils/installer/__init__.pyi +5 -0
  118. stouputils/installer/common.py +67 -0
  119. stouputils/installer/common.pyi +39 -0
  120. stouputils/installer/downloader.py +101 -0
  121. stouputils/installer/downloader.pyi +24 -0
  122. stouputils/installer/linux.py +144 -0
  123. stouputils/installer/linux.pyi +39 -0
  124. stouputils/installer/main.py +223 -0
  125. stouputils/installer/main.pyi +57 -0
  126. stouputils/installer/windows.py +136 -0
  127. stouputils/installer/windows.pyi +31 -0
  128. stouputils/io.py +486 -0
  129. stouputils/io.pyi +213 -0
  130. stouputils/parallel.py +453 -0
  131. stouputils/parallel.pyi +211 -0
  132. stouputils/print.py +527 -0
  133. stouputils/print.pyi +146 -0
  134. stouputils/py.typed +1 -0
  135. stouputils-1.12.1.dist-info/METADATA +179 -0
  136. stouputils-1.12.1.dist-info/RECORD +138 -0
  137. stouputils-1.12.1.dist-info/WHEEL +4 -0
  138. stouputils-1.12.1.dist-info/entry_points.txt +3 -0
stouputils/print.py ADDED
@@ -0,0 +1,527 @@
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
+ **kwargs: Any
49
+ ) -> Iterator[T]:
50
+ """ Function to iterate over a list with a colored TQDM progress bar like the other functions in this module.
51
+
52
+ Args:
53
+ iterable (Iterable): List to iterate over
54
+ desc (str): Description of the function execution displayed in the progress bar
55
+ color (str): Color of the progress bar (Defaults to MAGENTA)
56
+ bar_format (str): Format of the progress bar (Defaults to BAR_FORMAT)
57
+ ascii (bool): Whether to use ASCII or Unicode characters for the progress bar (Defaults to False)
58
+ verbose (int): Level of verbosity, decrease by 1 for each depth (Defaults to 1)
59
+ **kwargs: Additional arguments to pass to the TQDM progress bar
60
+
61
+ Yields:
62
+ T: Each item of the iterable
63
+
64
+ Examples:
65
+ >>> for i in colored_for_loop(range(10), desc="Time sleeping loop"):
66
+ ... time.sleep(0.01)
67
+ >>> # Time sleeping loop: 100%|██████████████████| 10/10 [ 95.72it/s, 00:00<00:00]
68
+ """
69
+ if bar_format == BAR_FORMAT:
70
+ bar_format = bar_format.replace(MAGENTA, color)
71
+ desc = color + desc
72
+
73
+ from tqdm.auto import tqdm
74
+ yield from tqdm(iterable, desc=desc, bar_format=bar_format, ascii=ascii, **kwargs)
75
+
76
+ def info(
77
+ *values: Any,
78
+ color: str = GREEN,
79
+ text: str = "INFO ",
80
+ prefix: str = "",
81
+ file: TextIO | list[TextIO] | None = None,
82
+ **print_kwargs: Any,
83
+ ) -> None:
84
+ """ Print an information message looking like "[INFO HH:MM:SS] message" in green by default.
85
+
86
+ Args:
87
+ values (Any): Values to print (like the print function)
88
+ color (str): Color of the message (default: GREEN)
89
+ text (str): Text of the message (default: "INFO ")
90
+ prefix (str): Prefix to add to the values
91
+ file (TextIO|list[TextIO]): File(s) to write the message to (default: sys.stdout)
92
+ print_kwargs (dict): Keyword arguments to pass to the print function
93
+ """
94
+ # Use stdout if no file is specified
95
+ if file is None:
96
+ file = sys.stdout
97
+
98
+ # If file is a list, recursively call info() for each file
99
+ if isinstance(file, list):
100
+ for f in file:
101
+ info(*values, color=color, text=text, prefix=prefix, file=f, **print_kwargs)
102
+ else:
103
+ # Build the message with prefix, color, text and timestamp
104
+ message: str = f"{prefix}{color}[{text} {current_time()}]"
105
+
106
+ # If this is a repeated print, add a line up and counter
107
+ if is_same_print(*values, color=color, text=text, prefix=prefix, **print_kwargs):
108
+ message = f"{LINE_UP}{message} (x{nb_values})"
109
+
110
+ # Print the message with the values and reset color
111
+ print(message, *values, RESET, file=file, **print_kwargs)
112
+
113
+ def debug(*values: Any, **print_kwargs: Any) -> None:
114
+ """ Print a debug message looking like "[DEBUG HH:MM:SS] message" in cyan by default. """
115
+ if "text" not in print_kwargs:
116
+ print_kwargs["text"] = "DEBUG"
117
+ if "color" not in print_kwargs:
118
+ print_kwargs["color"] = CYAN
119
+ info(*values, **print_kwargs)
120
+
121
+ def alt_debug(*values: Any, **print_kwargs: Any) -> None:
122
+ """ Print a debug message looking like "[DEBUG HH:MM:SS] message" in blue by default. """
123
+ if "text" not in print_kwargs:
124
+ print_kwargs["text"] = "DEBUG"
125
+ if "color" not in print_kwargs:
126
+ print_kwargs["color"] = BLUE
127
+ info(*values, **print_kwargs)
128
+
129
+ def suggestion(*values: Any, **print_kwargs: Any) -> None:
130
+ """ Print a suggestion message looking like "[SUGGESTION HH:MM:SS] message" in cyan by default. """
131
+ if "text" not in print_kwargs:
132
+ print_kwargs["text"] = "SUGGESTION"
133
+ if "color" not in print_kwargs:
134
+ print_kwargs["color"] = CYAN
135
+ info(*values, **print_kwargs)
136
+
137
+ def progress(*values: Any, **print_kwargs: Any) -> None:
138
+ """ Print a progress message looking like "[PROGRESS HH:MM:SS] message" in magenta by default. """
139
+ if "text" not in print_kwargs:
140
+ print_kwargs["text"] = "PROGRESS"
141
+ if "color" not in print_kwargs:
142
+ print_kwargs["color"] = MAGENTA
143
+ info(*values, **print_kwargs)
144
+
145
+ def warning(*values: Any, **print_kwargs: Any) -> None:
146
+ """ Print a warning message looking like "[WARNING HH:MM:SS] message" in yellow by default and in sys.stderr. """
147
+ if "file" not in print_kwargs:
148
+ print_kwargs["file"] = sys.stderr
149
+ if "text" not in print_kwargs:
150
+ print_kwargs["text"] = "WARNING"
151
+ if "color" not in print_kwargs:
152
+ print_kwargs["color"] = YELLOW
153
+ info(*values, **print_kwargs)
154
+
155
+ def error(*values: Any, exit: bool = False, **print_kwargs: Any) -> None:
156
+ """ Print an error message (in sys.stderr and in red by default)
157
+ and optionally ask the user to continue or stop the program.
158
+
159
+ Args:
160
+ values (Any): Values to print (like the print function)
161
+ exit (bool): Whether to ask the user to continue or stop the program,
162
+ false to ignore the error automatically and continue
163
+ print_kwargs (dict): Keyword arguments to pass to the print function
164
+ """
165
+ file: TextIO = sys.stderr
166
+ if "file" in print_kwargs:
167
+ if isinstance(print_kwargs["file"], list):
168
+ file = cast(TextIO, print_kwargs["file"][0])
169
+ else:
170
+ file = print_kwargs["file"]
171
+ if "text" not in print_kwargs:
172
+ print_kwargs["text"] = "ERROR"
173
+ if "color" not in print_kwargs:
174
+ print_kwargs["color"] = RED
175
+ info(*values, **print_kwargs)
176
+ if exit:
177
+ try:
178
+ print("Press enter to ignore error and continue, or 'CTRL+C' to stop the program... ", file=file)
179
+ input()
180
+ except (KeyboardInterrupt, EOFError):
181
+ print(file=file)
182
+ sys.exit(1)
183
+
184
+ def whatisit(
185
+ *values: Any,
186
+ print_function: Callable[..., None] = debug,
187
+ max_length: int = 250,
188
+ color: str = CYAN,
189
+ **print_kwargs: Any,
190
+ ) -> None:
191
+ """ Print the type of each value and the value itself, with its id and length/shape.
192
+
193
+ The output format is: "type, <id id_number>: (length/shape) value"
194
+
195
+ Args:
196
+ values (Any): Values to print
197
+ print_function (Callable): Function to use to print the values (default: debug())
198
+ max_length (int): Maximum length of the value string to print (default: 250)
199
+ color (str): Color of the message (default: CYAN)
200
+ print_kwargs (dict): Keyword arguments to pass to the print function
201
+ """
202
+ def _internal(value: Any) -> str:
203
+ """ Get the string representation of the value, with length or shape instead of length if shape is available """
204
+
205
+ # Build metadata parts list
206
+ metadata_parts: list[str] = []
207
+
208
+ # Get the dtype if available
209
+ try:
210
+ metadata_parts.append(f"dtype: {value.dtype}")
211
+ except (AttributeError, TypeError):
212
+ pass
213
+
214
+ # Get the shape or length of the value
215
+ try:
216
+ metadata_parts.append(f"shape: {value.shape}")
217
+ except (AttributeError, TypeError):
218
+ try:
219
+ metadata_parts.append(f"length: {len(value)}")
220
+ except (AttributeError, TypeError):
221
+ pass
222
+
223
+ # Get the min and max if available (Iterable of numbers)
224
+ try:
225
+ if not isinstance(value, str | bytes | bytearray | dict | int | float):
226
+ import numpy as np
227
+ metadata_parts.append(f"min: {np.min(value)}")
228
+ metadata_parts.append(f"max: {np.max(value)}")
229
+ except (Exception):
230
+ pass
231
+
232
+ # Combine metadata into a single parenthesized string
233
+ metadata_str: str = f"({', '.join(metadata_parts)}) " if metadata_parts else ""
234
+
235
+ # Get the string representation of the value
236
+ value = cast(Any, value)
237
+ value_str: str = str(value)
238
+ if len(value_str) > max_length:
239
+ value_str = value_str[:max_length] + "..."
240
+ if "\n" in value_str:
241
+ value_str = "\n" + value_str # Add a newline before the value if there is a newline in it.
242
+
243
+ # Return the formatted string
244
+ return f"{type(value)}, <id {id(value)}>: {metadata_str}{value_str}"
245
+
246
+ # Add the color to the message
247
+ if "color" not in print_kwargs:
248
+ print_kwargs["color"] = color
249
+
250
+ # Set text to "What is it?" if not already set
251
+ if "text" not in print_kwargs:
252
+ print_kwargs["text"] = "What is it?"
253
+
254
+ # Print the values
255
+ if len(values) > 1:
256
+ print_function("".join(f"\n {_internal(value)}" for value in values), **print_kwargs)
257
+ elif len(values) == 1:
258
+ print_function(_internal(values[0]), **print_kwargs)
259
+
260
+ def breakpoint(*values: Any, print_function: Callable[..., None] = warning, **print_kwargs: Any) -> None:
261
+ """ Breakpoint function, pause the program and print the values.
262
+
263
+ Args:
264
+ values (Any): Values to print
265
+ print_function (Callable): Function to use to print the values (default: warning())
266
+ print_kwargs (dict): Keyword arguments to pass to the print function
267
+ """
268
+ if "text" not in print_kwargs:
269
+ print_kwargs["text"] = "BREAKPOINT (press Enter)"
270
+ file: TextIO = sys.stderr
271
+ if "file" in print_kwargs:
272
+ if isinstance(print_kwargs["file"], list):
273
+ file = cast(TextIO, print_kwargs["file"][0])
274
+ else:
275
+ file = print_kwargs["file"]
276
+ whatisit(*values, print_function=print_function, **print_kwargs)
277
+ try:
278
+ input()
279
+ except (KeyboardInterrupt, EOFError):
280
+ print(file=file)
281
+ sys.exit(1)
282
+
283
+
284
+ # TeeMultiOutput class to duplicate output to multiple file-like objects
285
+ class TeeMultiOutput:
286
+ """ File-like object that duplicates output to multiple file-like objects.
287
+
288
+ Args:
289
+ *files (IO[Any]): One or more file-like objects that have write and flush methods
290
+ strip_colors (bool): Strip ANSI color codes from output sent to non-stdout/stderr files
291
+ ascii_only (bool): Replace non-ASCII characters with their ASCII equivalents for non-stdout/stderr files
292
+ ignore_lineup (bool): Ignore lines containing LINE_UP escape sequence in non-terminal outputs
293
+
294
+ Examples:
295
+ >>> f = open("logfile.txt", "w")
296
+ >>> sys.stdout = TeeMultiOutput(sys.stdout, f)
297
+ >>> print("Hello World") # Output goes to both console and file
298
+ Hello World
299
+ >>> f.close() # TeeMultiOutput will handle any future writes to closed files gracefully
300
+ """
301
+ def __init__(
302
+ self, *files: IO[Any], strip_colors: bool = True, ascii_only: bool = True, ignore_lineup: bool = True
303
+ ) -> None:
304
+ # Flatten any TeeMultiOutput instances in files
305
+ flattened_files: list[IO[Any]] = []
306
+ for file in files:
307
+ if isinstance(file, TeeMultiOutput):
308
+ flattened_files.extend(file.files)
309
+ else:
310
+ flattened_files.append(file)
311
+
312
+ self.files: tuple[IO[Any], ...] = tuple(flattened_files)
313
+ """ File-like objects to write to """
314
+ self.strip_colors: bool = strip_colors
315
+ """ Whether to strip ANSI color codes from output sent to non-stdout/stderr files """
316
+ self.ascii_only: bool = ascii_only
317
+ """ Whether to replace non-ASCII characters with their ASCII equivalents for non-stdout/stderr files """
318
+ self.ignore_lineup: bool = ignore_lineup
319
+ """ Whether to ignore lines containing LINE_UP escape sequence in non-terminal outputs """
320
+
321
+ @property
322
+ def encoding(self) -> str:
323
+ """ Get the encoding of the first file, or "utf-8" as fallback.
324
+
325
+ Returns:
326
+ str: The encoding, ex: "utf-8", "ascii", "latin1", etc.
327
+ """
328
+ try:
329
+ return self.files[0].encoding # type: ignore
330
+ except (IndexError, AttributeError):
331
+ return "utf-8"
332
+
333
+ def write(self, obj: str) -> int:
334
+ """ Write the object to all files while stripping colors if needed.
335
+
336
+ Args:
337
+ obj (str): String to write
338
+ Returns:
339
+ int: Number of characters written to the first file
340
+ """
341
+ files_to_remove: list[IO[Any]] = []
342
+ num_chars_written: int = 0
343
+ for i, f in enumerate(self.files):
344
+ try:
345
+ # Check if file is closed
346
+ if hasattr(f, "closed") and f.closed:
347
+ files_to_remove.append(f)
348
+ continue
349
+
350
+ # Check if this file is a terminal/console or a regular file
351
+ content: str = obj
352
+ if not (hasattr(f, "isatty") and f.isatty()):
353
+ # Non-terminal files get processed content (stripped colors, ASCII-only, etc.)
354
+
355
+ # Skip content if it contains LINE_UP and ignore_lineup is True
356
+ if self.ignore_lineup and (LINE_UP in content or "\r" in content):
357
+ continue
358
+
359
+ # Strip colors if needed
360
+ if self.strip_colors:
361
+ content = remove_colors(content)
362
+
363
+ # Replace Unicode block characters with ASCII equivalents
364
+ # Replace other problematic Unicode characters as needed
365
+ if self.ascii_only:
366
+ content = content.replace('█', '#')
367
+ content = ''.join(c if ord(c) < 128 else '?' for c in content)
368
+
369
+ # Write content to file
370
+ if i == 0:
371
+ num_chars_written = f.write(content)
372
+ else:
373
+ f.write(content)
374
+
375
+ except ValueError:
376
+ # ValueError is raised when writing to a closed file
377
+ files_to_remove.append(f)
378
+ except Exception:
379
+ pass
380
+
381
+ # Remove closed files from the list
382
+ if files_to_remove:
383
+ self.files = tuple(f for f in self.files if f not in files_to_remove)
384
+ return num_chars_written
385
+
386
+ def flush(self) -> None:
387
+ """ Flush all files. """
388
+ for f in self.files:
389
+ try:
390
+ f.flush()
391
+ except Exception:
392
+ pass
393
+
394
+ def fileno(self) -> int:
395
+ """ Return the file descriptor of the first file. """
396
+ return self.files[0].fileno() if hasattr(self.files[0], "fileno") else 0
397
+
398
+
399
+ # Utility functions
400
+ def remove_colors(text: str) -> str:
401
+ """ Remove the colors from a text """
402
+ for color in [RESET, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, LINE_UP]:
403
+ text = text.replace(color, "")
404
+ return text
405
+
406
+ def is_same_print(*args: Any, **kwargs: Any) -> bool:
407
+ """ Checks if the current print call is the same as the previous one. """
408
+ global previous_args_kwards, nb_values
409
+ try:
410
+ if previous_args_kwards == (args, kwargs):
411
+ nb_values += 1
412
+ return True
413
+ except Exception:
414
+ # Comparison failed (e.g., comparing DataFrames or other complex objects)
415
+ # Use str() for comparison instead
416
+ current_str: str = str((args, kwargs))
417
+ previous_str: str = str(previous_args_kwards)
418
+ if previous_str == current_str:
419
+ nb_values += 1
420
+ return True
421
+ # Else, update previous args and reset counter
422
+ previous_args_kwards = (args, kwargs)
423
+ nb_values = 1
424
+ return False
425
+
426
+ def current_time() -> str:
427
+ """ Get the current time as "HH:MM:SS" if less than 24 hours since import, else "YYYY-MM-DD HH:MM:SS" """
428
+ # If the import time is more than 24 hours, return the full datetime
429
+ if (time.time() - import_time) > (24 * 60 * 60):
430
+ return time.strftime("%Y-%m-%d %H:%M:%S")
431
+ else:
432
+ return time.strftime("%H:%M:%S")
433
+
434
+
435
+ def show_version(main_package: str = "stouputils", primary_color: str = CYAN, secondary_color: str = GREEN) -> None:
436
+ """ Print the version of the main package and its dependencies.
437
+
438
+ Used by the "stouputils --version" command.
439
+
440
+ Args:
441
+ main_package (str): Name of the main package to show version for
442
+ primary_color (str): Color to use for the primary package name
443
+ secondary_color (str): Color to use for the secondary package names
444
+ """
445
+ # Imports
446
+ from importlib.metadata import requires, version
447
+ def ver(package_name: str) -> str:
448
+ try:
449
+ return version(package_name)
450
+ except Exception:
451
+ return ""
452
+
453
+ # Get dependencies dynamically and extract package names from requirements (e.g., "tqdm>=4.0.0" -> "tqdm")
454
+ deps: list[str] = requires(main_package) or []
455
+ dep_names: list[str] = sorted([
456
+ dep
457
+ .split(">")[0]
458
+ .split("<")[0]
459
+ .split("=")[0]
460
+ .split("[")[0]
461
+ for dep in deps
462
+ ])
463
+ all_deps: list[tuple[str, str]] = [
464
+ (x, ver(x).split("version: ")[-1])
465
+ for x in (main_package, *dep_names)
466
+ ]
467
+ all_deps = [pair for pair in all_deps if pair[1]] # Filter out packages with no version found
468
+ longest_name_length: int = max(len(name) for name, _ in all_deps)
469
+ longest_version_length: int = max(len(ver) for _, ver in all_deps)
470
+
471
+ # Get Python version
472
+ python_version: str = f" Python {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro} "
473
+ minimum_separator_length: int = len(python_version) + 10 # Always at least 5 dashes on each side
474
+ separator_length: int = max(minimum_separator_length, longest_name_length + longest_version_length + 4)
475
+ python_text_length: int = len(python_version)
476
+ left_dashes: int = (separator_length - python_text_length) // 2
477
+ right_dashes: int = separator_length - python_text_length - left_dashes
478
+ separator_with_python: str = "─" * left_dashes + python_version + "─" * right_dashes
479
+ separator: str = "─" * separator_length
480
+
481
+ for pkg, v in all_deps:
482
+ pkg_spacing: str = " " * (longest_name_length - len(pkg))
483
+
484
+ # Highlight the main package with a different style
485
+ if pkg == main_package:
486
+ print(f"{primary_color}{separator_with_python}{RESET}")
487
+ print(f"{primary_color}{pkg}{pkg_spacing} {secondary_color}v{v}{RESET}")
488
+ print(f"{primary_color}{separator}{RESET}")
489
+ else:
490
+ print(f"{primary_color}{pkg}{pkg_spacing} {secondary_color}v{v}{RESET}")
491
+ return
492
+
493
+ # Test the print functions
494
+ if __name__ == "__main__":
495
+ info("Hello", "World")
496
+ time.sleep(0.5)
497
+ info("Hello", "World")
498
+ time.sleep(0.5)
499
+ info("Hello", "World")
500
+ time.sleep(0.5)
501
+ info("Not Hello World !")
502
+ time.sleep(0.5)
503
+ info("Hello", "World")
504
+ time.sleep(0.5)
505
+ info("Hello", "World")
506
+
507
+ # All remaining print functions
508
+ alt_debug("Hello", "World")
509
+ debug("Hello", "World")
510
+ suggestion("Hello", "World")
511
+ progress("Hello", "World")
512
+ warning("Hello", "World")
513
+ error("Hello", "World", exit=False)
514
+ whatisit("Hello")
515
+ whatisit("Hello", "World")
516
+
517
+ # Test whatisit with different types
518
+ import numpy as np
519
+ print()
520
+ whatisit(
521
+ 123,
522
+ "Hello World",
523
+ [1, 2, 3, 4, 5],
524
+ np.array([[1, 2, 3], [4, 5, 6]]),
525
+ {"a": 1, "b": 2},
526
+ )
527
+
stouputils/print.pyi ADDED
@@ -0,0 +1,146 @@
1
+ from collections.abc import Callable as Callable, Iterable, Iterator
2
+ from typing import Any, IO, TextIO, TypeVar
3
+
4
+ RESET: str
5
+ RED: str
6
+ GREEN: str
7
+ YELLOW: str
8
+ BLUE: str
9
+ MAGENTA: str
10
+ CYAN: str
11
+ LINE_UP: str
12
+ BAR_FORMAT: str
13
+ T = TypeVar('T')
14
+ previous_args_kwards: tuple[Any, Any]
15
+ nb_values: int
16
+ import_time: float
17
+
18
+ def colored_for_loop[T](iterable: Iterable[T], desc: str = 'Processing', color: str = ..., bar_format: str = ..., ascii: bool = False, **kwargs: Any) -> Iterator[T]:
19
+ ''' Function to iterate over a list with a colored TQDM progress bar like the other functions in this module.
20
+
21
+ \tArgs:
22
+ \t\titerable\t(Iterable):\t\t\tList to iterate over
23
+ \t\tdesc\t\t(str):\t\t\t\tDescription of the function execution displayed in the progress bar
24
+ \t\tcolor\t\t(str):\t\t\t\tColor of the progress bar (Defaults to MAGENTA)
25
+ \t\tbar_format\t(str):\t\t\t\tFormat of the progress bar (Defaults to BAR_FORMAT)
26
+ \t\tascii\t\t(bool):\t\t\t\tWhether to use ASCII or Unicode characters for the progress bar (Defaults to False)
27
+ \t\tverbose\t\t(int):\t\t\t\tLevel of verbosity, decrease by 1 for each depth (Defaults to 1)
28
+ \t\t**kwargs:\t\t\t\t\t\tAdditional arguments to pass to the TQDM progress bar
29
+
30
+ \tYields:
31
+ \t\tT: Each item of the iterable
32
+
33
+ \tExamples:
34
+ \t\t>>> for i in colored_for_loop(range(10), desc="Time sleeping loop"):
35
+ \t\t... time.sleep(0.01)
36
+ \t\t>>> # Time sleeping loop: 100%|██████████████████| 10/10 [ 95.72it/s, 00:00<00:00]
37
+ \t'''
38
+ def info(*values: Any, color: str = ..., text: str = 'INFO ', prefix: str = '', file: TextIO | list[TextIO] | None = None, **print_kwargs: Any) -> None:
39
+ ''' Print an information message looking like "[INFO HH:MM:SS] message" in green by default.
40
+
41
+ \tArgs:
42
+ \t\tvalues\t\t\t(Any):\t\t\t\t\tValues to print (like the print function)
43
+ \t\tcolor\t\t\t(str):\t\t\t\t\tColor of the message (default: GREEN)
44
+ \t\ttext\t\t\t(str):\t\t\t\t\tText of the message (default: "INFO ")
45
+ \t\tprefix\t\t\t(str):\t\t\t\t\tPrefix to add to the values
46
+ \t\tfile\t\t\t(TextIO|list[TextIO]):\tFile(s) to write the message to (default: sys.stdout)
47
+ \t\tprint_kwargs\t(dict):\t\t\t\t\tKeyword arguments to pass to the print function
48
+ \t'''
49
+ def debug(*values: Any, **print_kwargs: Any) -> None:
50
+ ''' Print a debug message looking like "[DEBUG HH:MM:SS] message" in cyan by default. '''
51
+ def alt_debug(*values: Any, **print_kwargs: Any) -> None:
52
+ ''' Print a debug message looking like "[DEBUG HH:MM:SS] message" in blue by default. '''
53
+ def suggestion(*values: Any, **print_kwargs: Any) -> None:
54
+ ''' Print a suggestion message looking like "[SUGGESTION HH:MM:SS] message" in cyan by default. '''
55
+ def progress(*values: Any, **print_kwargs: Any) -> None:
56
+ ''' Print a progress message looking like "[PROGRESS HH:MM:SS] message" in magenta by default. '''
57
+ def warning(*values: Any, **print_kwargs: Any) -> None:
58
+ ''' Print a warning message looking like "[WARNING HH:MM:SS] message" in yellow by default and in sys.stderr. '''
59
+ def error(*values: Any, exit: bool = False, **print_kwargs: Any) -> None:
60
+ """ Print an error message (in sys.stderr and in red by default)
61
+ \tand optionally ask the user to continue or stop the program.
62
+
63
+ \tArgs:
64
+ \t\tvalues\t\t\t(Any):\t\tValues to print (like the print function)
65
+ \t\texit\t\t\t(bool):\t\tWhether to ask the user to continue or stop the program,
66
+ \t\t\tfalse to ignore the error automatically and continue
67
+ \t\tprint_kwargs\t(dict):\t\tKeyword arguments to pass to the print function
68
+ \t"""
69
+ def whatisit(*values: Any, print_function: Callable[..., None] = ..., max_length: int = 250, color: str = ..., **print_kwargs: Any) -> None:
70
+ ''' Print the type of each value and the value itself, with its id and length/shape.
71
+
72
+ \tThe output format is: "type, <id id_number>:\t(length/shape) value"
73
+
74
+ \tArgs:
75
+ \t\tvalues\t\t\t(Any):\t\tValues to print
76
+ \t\tprint_function\t(Callable):\tFunction to use to print the values (default: debug())
77
+ \t\tmax_length\t\t(int):\t\tMaximum length of the value string to print (default: 250)
78
+ \t\tcolor\t\t\t(str):\t\tColor of the message (default: CYAN)
79
+ \t\tprint_kwargs\t(dict):\t\tKeyword arguments to pass to the print function
80
+ \t'''
81
+ def breakpoint(*values: Any, print_function: Callable[..., None] = ..., **print_kwargs: Any) -> None:
82
+ """ Breakpoint function, pause the program and print the values.
83
+
84
+ \tArgs:
85
+ \t\tvalues\t\t\t(Any):\t\tValues to print
86
+ \t\tprint_function\t(Callable):\tFunction to use to print the values (default: warning())
87
+ \t\tprint_kwargs\t(dict):\t\tKeyword arguments to pass to the print function
88
+ \t"""
89
+
90
+ class TeeMultiOutput:
91
+ ''' File-like object that duplicates output to multiple file-like objects.
92
+
93
+ \tArgs:
94
+ \t\t*files (IO[Any]): One or more file-like objects that have write and flush methods
95
+ \t\tstrip_colors (bool): Strip ANSI color codes from output sent to non-stdout/stderr files
96
+ \t\tascii_only (bool): Replace non-ASCII characters with their ASCII equivalents for non-stdout/stderr files
97
+ \t\tignore_lineup (bool): Ignore lines containing LINE_UP escape sequence in non-terminal outputs
98
+
99
+ \tExamples:
100
+ \t\t>>> f = open("logfile.txt", "w")
101
+ \t\t>>> sys.stdout = TeeMultiOutput(sys.stdout, f)
102
+ \t\t>>> print("Hello World") # Output goes to both console and file
103
+ \t\tHello World
104
+ \t\t>>> f.close()\t# TeeMultiOutput will handle any future writes to closed files gracefully
105
+ \t'''
106
+ files: tuple[IO[Any], ...]
107
+ strip_colors: bool
108
+ ascii_only: bool
109
+ ignore_lineup: bool
110
+ def __init__(self, *files: IO[Any], strip_colors: bool = True, ascii_only: bool = True, ignore_lineup: bool = True) -> None: ...
111
+ @property
112
+ def encoding(self) -> str:
113
+ ''' Get the encoding of the first file, or "utf-8" as fallback.
114
+
115
+ \t\tReturns:
116
+ \t\t\tstr: The encoding, ex: "utf-8", "ascii", "latin1", etc.
117
+ \t\t'''
118
+ def write(self, obj: str) -> int:
119
+ """ Write the object to all files while stripping colors if needed.
120
+
121
+ \t\tArgs:
122
+ \t\t\tobj (str): String to write
123
+ \t\tReturns:
124
+ \t\t\tint: Number of characters written to the first file
125
+ \t\t"""
126
+ def flush(self) -> None:
127
+ """ Flush all files. """
128
+ def fileno(self) -> int:
129
+ """ Return the file descriptor of the first file. """
130
+
131
+ def remove_colors(text: str) -> str:
132
+ """ Remove the colors from a text """
133
+ def is_same_print(*args: Any, **kwargs: Any) -> bool:
134
+ """ Checks if the current print call is the same as the previous one. """
135
+ def current_time() -> str:
136
+ ''' Get the current time as "HH:MM:SS" if less than 24 hours since import, else "YYYY-MM-DD HH:MM:SS" '''
137
+ def show_version(main_package: str = 'stouputils', primary_color: str = ..., secondary_color: str = ...) -> None:
138
+ ''' Print the version of the main package and its dependencies.
139
+
140
+ \tUsed by the "stouputils --version" command.
141
+
142
+ \tArgs:
143
+ \t\tmain_package\t\t\t(str):\t\t\tName of the main package to show version for
144
+ \t\tprimary_color\t\t\t(str):\t\t\tColor to use for the primary package name
145
+ \t\tsecondary_color\t\t\t(str):\t\t\tColor to use for the secondary package names
146
+ \t'''
stouputils/py.typed ADDED
@@ -0,0 +1 @@
1
+