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.
- stouputils/__init__.py +40 -0
- stouputils/__init__.pyi +14 -0
- stouputils/__main__.py +81 -0
- stouputils/_deprecated.py +37 -0
- stouputils/_deprecated.pyi +12 -0
- stouputils/all_doctests.py +160 -0
- stouputils/all_doctests.pyi +46 -0
- stouputils/applications/__init__.py +22 -0
- stouputils/applications/__init__.pyi +2 -0
- stouputils/applications/automatic_docs.py +634 -0
- stouputils/applications/automatic_docs.pyi +106 -0
- stouputils/applications/upscaler/__init__.py +39 -0
- stouputils/applications/upscaler/__init__.pyi +3 -0
- stouputils/applications/upscaler/config.py +128 -0
- stouputils/applications/upscaler/config.pyi +18 -0
- stouputils/applications/upscaler/image.py +247 -0
- stouputils/applications/upscaler/image.pyi +109 -0
- stouputils/applications/upscaler/video.py +287 -0
- stouputils/applications/upscaler/video.pyi +60 -0
- stouputils/archive.py +344 -0
- stouputils/archive.pyi +67 -0
- stouputils/backup.py +488 -0
- stouputils/backup.pyi +109 -0
- stouputils/collections.py +244 -0
- stouputils/collections.pyi +86 -0
- stouputils/continuous_delivery/__init__.py +27 -0
- stouputils/continuous_delivery/__init__.pyi +5 -0
- stouputils/continuous_delivery/cd_utils.py +243 -0
- stouputils/continuous_delivery/cd_utils.pyi +129 -0
- stouputils/continuous_delivery/github.py +522 -0
- stouputils/continuous_delivery/github.pyi +162 -0
- stouputils/continuous_delivery/pypi.py +91 -0
- stouputils/continuous_delivery/pypi.pyi +43 -0
- stouputils/continuous_delivery/pyproject.py +147 -0
- stouputils/continuous_delivery/pyproject.pyi +67 -0
- stouputils/continuous_delivery/stubs.py +86 -0
- stouputils/continuous_delivery/stubs.pyi +39 -0
- stouputils/ctx.py +408 -0
- stouputils/ctx.pyi +211 -0
- stouputils/data_science/config/get.py +51 -0
- stouputils/data_science/config/set.py +125 -0
- stouputils/data_science/data_processing/image/__init__.py +66 -0
- stouputils/data_science/data_processing/image/auto_contrast.py +79 -0
- stouputils/data_science/data_processing/image/axis_flip.py +58 -0
- stouputils/data_science/data_processing/image/bias_field_correction.py +74 -0
- stouputils/data_science/data_processing/image/binary_threshold.py +73 -0
- stouputils/data_science/data_processing/image/blur.py +59 -0
- stouputils/data_science/data_processing/image/brightness.py +54 -0
- stouputils/data_science/data_processing/image/canny.py +110 -0
- stouputils/data_science/data_processing/image/clahe.py +92 -0
- stouputils/data_science/data_processing/image/common.py +30 -0
- stouputils/data_science/data_processing/image/contrast.py +53 -0
- stouputils/data_science/data_processing/image/curvature_flow_filter.py +74 -0
- stouputils/data_science/data_processing/image/denoise.py +378 -0
- stouputils/data_science/data_processing/image/histogram_equalization.py +123 -0
- stouputils/data_science/data_processing/image/invert.py +64 -0
- stouputils/data_science/data_processing/image/laplacian.py +60 -0
- stouputils/data_science/data_processing/image/median_blur.py +52 -0
- stouputils/data_science/data_processing/image/noise.py +59 -0
- stouputils/data_science/data_processing/image/normalize.py +65 -0
- stouputils/data_science/data_processing/image/random_erase.py +66 -0
- stouputils/data_science/data_processing/image/resize.py +69 -0
- stouputils/data_science/data_processing/image/rotation.py +80 -0
- stouputils/data_science/data_processing/image/salt_pepper.py +68 -0
- stouputils/data_science/data_processing/image/sharpening.py +55 -0
- stouputils/data_science/data_processing/image/shearing.py +64 -0
- stouputils/data_science/data_processing/image/threshold.py +64 -0
- stouputils/data_science/data_processing/image/translation.py +71 -0
- stouputils/data_science/data_processing/image/zoom.py +83 -0
- stouputils/data_science/data_processing/image_augmentation.py +118 -0
- stouputils/data_science/data_processing/image_preprocess.py +183 -0
- stouputils/data_science/data_processing/prosthesis_detection.py +359 -0
- stouputils/data_science/data_processing/technique.py +481 -0
- stouputils/data_science/dataset/__init__.py +45 -0
- stouputils/data_science/dataset/dataset.py +292 -0
- stouputils/data_science/dataset/dataset_loader.py +135 -0
- stouputils/data_science/dataset/grouping_strategy.py +296 -0
- stouputils/data_science/dataset/image_loader.py +100 -0
- stouputils/data_science/dataset/xy_tuple.py +696 -0
- stouputils/data_science/metric_dictionnary.py +106 -0
- stouputils/data_science/metric_utils.py +847 -0
- stouputils/data_science/mlflow_utils.py +206 -0
- stouputils/data_science/models/abstract_model.py +149 -0
- stouputils/data_science/models/all.py +85 -0
- stouputils/data_science/models/base_keras.py +765 -0
- stouputils/data_science/models/keras/all.py +38 -0
- stouputils/data_science/models/keras/convnext.py +62 -0
- stouputils/data_science/models/keras/densenet.py +50 -0
- stouputils/data_science/models/keras/efficientnet.py +60 -0
- stouputils/data_science/models/keras/mobilenet.py +56 -0
- stouputils/data_science/models/keras/resnet.py +52 -0
- stouputils/data_science/models/keras/squeezenet.py +233 -0
- stouputils/data_science/models/keras/vgg.py +42 -0
- stouputils/data_science/models/keras/xception.py +38 -0
- stouputils/data_science/models/keras_utils/callbacks/__init__.py +20 -0
- stouputils/data_science/models/keras_utils/callbacks/colored_progress_bar.py +219 -0
- stouputils/data_science/models/keras_utils/callbacks/learning_rate_finder.py +148 -0
- stouputils/data_science/models/keras_utils/callbacks/model_checkpoint_v2.py +31 -0
- stouputils/data_science/models/keras_utils/callbacks/progressive_unfreezing.py +249 -0
- stouputils/data_science/models/keras_utils/callbacks/warmup_scheduler.py +66 -0
- stouputils/data_science/models/keras_utils/losses/__init__.py +12 -0
- stouputils/data_science/models/keras_utils/losses/next_generation_loss.py +56 -0
- stouputils/data_science/models/keras_utils/visualizations.py +416 -0
- stouputils/data_science/models/model_interface.py +939 -0
- stouputils/data_science/models/sandbox.py +116 -0
- stouputils/data_science/range_tuple.py +234 -0
- stouputils/data_science/scripts/augment_dataset.py +77 -0
- stouputils/data_science/scripts/exhaustive_process.py +133 -0
- stouputils/data_science/scripts/preprocess_dataset.py +70 -0
- stouputils/data_science/scripts/routine.py +168 -0
- stouputils/data_science/utils.py +285 -0
- stouputils/decorators.py +595 -0
- stouputils/decorators.pyi +242 -0
- stouputils/image.py +441 -0
- stouputils/image.pyi +172 -0
- stouputils/installer/__init__.py +18 -0
- stouputils/installer/__init__.pyi +5 -0
- stouputils/installer/common.py +67 -0
- stouputils/installer/common.pyi +39 -0
- stouputils/installer/downloader.py +101 -0
- stouputils/installer/downloader.pyi +24 -0
- stouputils/installer/linux.py +144 -0
- stouputils/installer/linux.pyi +39 -0
- stouputils/installer/main.py +223 -0
- stouputils/installer/main.pyi +57 -0
- stouputils/installer/windows.py +136 -0
- stouputils/installer/windows.pyi +31 -0
- stouputils/io.py +486 -0
- stouputils/io.pyi +213 -0
- stouputils/parallel.py +453 -0
- stouputils/parallel.pyi +211 -0
- stouputils/print.py +527 -0
- stouputils/print.pyi +146 -0
- stouputils/py.typed +1 -0
- stouputils-1.12.1.dist-info/METADATA +179 -0
- stouputils-1.12.1.dist-info/RECORD +138 -0
- stouputils-1.12.1.dist-info/WHEEL +4 -0
- 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
|
+
|