stouputils 1.18.5__py3-none-any.whl → 1.18.6__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/all_doctests.py +10 -3
- stouputils/print.py +91 -44
- stouputils/print.pyi +39 -17
- {stouputils-1.18.5.dist-info → stouputils-1.18.6.dist-info}/METADATA +1 -1
- {stouputils-1.18.5.dist-info → stouputils-1.18.6.dist-info}/RECORD +7 -7
- {stouputils-1.18.5.dist-info → stouputils-1.18.6.dist-info}/WHEEL +0 -0
- {stouputils-1.18.5.dist-info → stouputils-1.18.6.dist-info}/entry_points.txt +0 -0
stouputils/all_doctests.py
CHANGED
|
@@ -94,15 +94,22 @@ def launch_tests(root_dir: str, strict: bool = True, pattern: str = "*") -> int:
|
|
|
94
94
|
if not modules_file_paths:
|
|
95
95
|
raise ValueError(f"No modules found in '{relative_path(root_dir)}'")
|
|
96
96
|
|
|
97
|
+
# Sort module by number of submodules and alphabetically
|
|
98
|
+
modules_file_paths.sort(key=lambda x: (x.count('.'), x))
|
|
99
|
+
|
|
97
100
|
# Filter modules based on pattern
|
|
98
101
|
if pattern != "*":
|
|
99
102
|
import fnmatch
|
|
100
|
-
|
|
103
|
+
new_paths: list[str] = [
|
|
101
104
|
path for path in modules_file_paths
|
|
102
105
|
if fnmatch.fnmatch(path, pattern)
|
|
103
106
|
]
|
|
104
|
-
if not
|
|
105
|
-
raise ValueError(
|
|
107
|
+
if not new_paths:
|
|
108
|
+
raise ValueError(
|
|
109
|
+
f"No modules matching pattern '{pattern}' found in '{relative_path(root_dir)}'.\n"
|
|
110
|
+
f"Candidates were: {', '.join(relative_path(p) for p in modules_file_paths)[:500]}..."
|
|
111
|
+
)
|
|
112
|
+
modules_file_paths = new_paths
|
|
106
113
|
|
|
107
114
|
# Find longest module path for alignment
|
|
108
115
|
max_length: int = max(len(path) for path in modules_file_paths)
|
stouputils/print.py
CHANGED
|
@@ -107,45 +107,67 @@ def format_colored(*values: Any) -> str:
|
|
|
107
107
|
Examples:
|
|
108
108
|
>>> # Test function names with parentheses
|
|
109
109
|
>>> result = format_colored("Call print() with 42 items")
|
|
110
|
-
>>> result.count(MAGENTA)
|
|
111
|
-
|
|
110
|
+
>>> result.count(MAGENTA) # print and 42
|
|
111
|
+
2
|
|
112
112
|
|
|
113
113
|
>>> # Test function names without parentheses
|
|
114
114
|
>>> result = format_colored("Use len and sum functions")
|
|
115
|
-
>>> result.count(MAGENTA)
|
|
116
|
-
|
|
115
|
+
>>> result.count(MAGENTA) # len and sum
|
|
116
|
+
2
|
|
117
117
|
|
|
118
118
|
>>> # Test exceptions (bold magenta)
|
|
119
119
|
>>> result = format_colored("Got ValueError when parsing")
|
|
120
|
-
>>> result.count(MAGENTA)
|
|
121
|
-
|
|
120
|
+
>>> result.count(MAGENTA), result.count(BOLD) # ValueError in bold magenta
|
|
121
|
+
(1, 1)
|
|
122
122
|
|
|
123
123
|
>>> # Test file paths
|
|
124
124
|
>>> result = format_colored("Processing ./data.csv file")
|
|
125
|
-
>>> result.count(MAGENTA)
|
|
126
|
-
|
|
125
|
+
>>> result.count(MAGENTA) # ./data.csv
|
|
126
|
+
1
|
|
127
127
|
|
|
128
128
|
>>> # Test file paths with quotes
|
|
129
129
|
>>> result = format_colored('File "/path/to/script.py" line 42')
|
|
130
|
-
>>> result.count(MAGENTA)
|
|
131
|
-
|
|
130
|
+
>>> result.count(MAGENTA) # /path/to/script.py and 42
|
|
131
|
+
2
|
|
132
132
|
|
|
133
133
|
>>> # Test numbers
|
|
134
|
-
>>> result = format_colored("Found 100 items and 3.14 value")
|
|
135
|
-
>>> result.count(MAGENTA)
|
|
136
|
-
|
|
134
|
+
>>> result = format_colored("Found 100 items and 3.14 value, 3.0e+10 is big")
|
|
135
|
+
>>> result.count(MAGENTA) # 100 and 3.14
|
|
136
|
+
3
|
|
137
137
|
|
|
138
138
|
>>> # Test mixed content
|
|
139
139
|
>>> result = format_colored("Call sum() got IndexError at line 256 in utils.py")
|
|
140
|
-
>>> result.count(MAGENTA)
|
|
141
|
-
|
|
142
|
-
>>> result.count(BOLD)
|
|
143
|
-
|
|
140
|
+
>>> result.count(MAGENTA) # sum, IndexError (bold), and 256
|
|
141
|
+
3
|
|
142
|
+
>>> result.count(BOLD) # IndexError is bold
|
|
143
|
+
1
|
|
144
|
+
|
|
145
|
+
>>> # Test keywords always colored
|
|
146
|
+
>>> result = format_colored("Check class dtype type")
|
|
147
|
+
>>> result.count(MAGENTA) # class, dtype, type
|
|
148
|
+
3
|
|
144
149
|
|
|
145
150
|
>>> # Test plain text (no coloring)
|
|
146
151
|
>>> result = format_colored("This is plain text")
|
|
147
152
|
>>> result.count(MAGENTA) == 0 and result == "This is plain text"
|
|
148
153
|
True
|
|
154
|
+
|
|
155
|
+
>>> # Affix punctuation should not be colored (assert exact coloring, punctuation uncolored)
|
|
156
|
+
>>> result = format_colored("<class")
|
|
157
|
+
>>> result == "<" + MAGENTA + "class" + RESET
|
|
158
|
+
True
|
|
159
|
+
>>> result = format_colored("(dtype:")
|
|
160
|
+
>>> result == "(" + MAGENTA + "dtype" + RESET + ":"
|
|
161
|
+
True
|
|
162
|
+
>>> result = format_colored("[1.")
|
|
163
|
+
>>> result == "[" + MAGENTA + "1" + RESET + "."
|
|
164
|
+
True
|
|
165
|
+
|
|
166
|
+
>>> # Test complex
|
|
167
|
+
>>> text = "<class 'numpy.ndarray'>, <id 140357548266896>: (dtype: float32, shape: (6,), min: 0.0, max: 1.0) [1. 0. 0. 0. 1. 0.]"
|
|
168
|
+
>>> result = format_colored(text)
|
|
169
|
+
>>> result.count(MAGENTA) # class, numpy, ndarray, float32, 6, 0.0, 1.0, 1. 0.
|
|
170
|
+
16
|
|
149
171
|
"""
|
|
150
172
|
import builtins
|
|
151
173
|
import re
|
|
@@ -163,6 +185,9 @@ def format_colored(*values: Any) -> str:
|
|
|
163
185
|
and issubclass(getattr(builtins, name), BaseException))
|
|
164
186
|
}
|
|
165
187
|
|
|
188
|
+
# Additional keywords always colored (case-insensitive on stripped words)
|
|
189
|
+
KEYWORDS: set[str] = {"class", "dtype", "type"}
|
|
190
|
+
|
|
166
191
|
def is_filepath(word: str) -> bool:
|
|
167
192
|
""" Check if a word looks like a file path """
|
|
168
193
|
# Remove quotes if present
|
|
@@ -187,7 +212,7 @@ def format_colored(*values: Any) -> str:
|
|
|
187
212
|
|
|
188
213
|
def is_number(word: str) -> bool:
|
|
189
214
|
try:
|
|
190
|
-
float(word.
|
|
215
|
+
float(''.join(c for c in word if c.isdigit() or c in '.-+e'))
|
|
191
216
|
return True
|
|
192
217
|
except ValueError:
|
|
193
218
|
return False
|
|
@@ -201,7 +226,22 @@ def format_colored(*values: Any) -> str:
|
|
|
201
226
|
|
|
202
227
|
def is_exception(word: str) -> bool:
|
|
203
228
|
""" Check if a word is a known exception name """
|
|
204
|
-
return word.
|
|
229
|
+
return ''.join(c for c in word if c.isalnum()) in EXCEPTION_NAMES
|
|
230
|
+
|
|
231
|
+
def is_keyword(word: str) -> bool:
|
|
232
|
+
""" Check if a word is one of the always-colored keywords """
|
|
233
|
+
clean_alnum = ''.join(c for c in word if c.isalnum())
|
|
234
|
+
return clean_alnum in KEYWORDS
|
|
235
|
+
|
|
236
|
+
def split_affixes(w: str) -> tuple[str, str, str]:
|
|
237
|
+
""" Split leading/trailing non-word characters and return (prefix, core, suffix).
|
|
238
|
+
|
|
239
|
+
This preserves punctuation like '<', '(', '[', '"', etc., while operating on the core text.
|
|
240
|
+
"""
|
|
241
|
+
m = re.match(r'^(\W*)(.*?)(\W*)$', w, re.ASCII)
|
|
242
|
+
if m:
|
|
243
|
+
return m.group(1), m.group(2), m.group(3)
|
|
244
|
+
return "", w, ""
|
|
205
245
|
|
|
206
246
|
# Convert all values to strings and join them and split into words while preserving separators
|
|
207
247
|
text: str = " ".join(str(v) for v in values)
|
|
@@ -219,36 +259,43 @@ def format_colored(*values: Any) -> str:
|
|
|
219
259
|
i += 1
|
|
220
260
|
continue
|
|
221
261
|
|
|
222
|
-
#
|
|
262
|
+
# If the whole token looks like a filepath (e.g. './data.csv' or '/path/to/file'), color it as-is
|
|
223
263
|
colored: bool = False
|
|
224
264
|
if is_filepath(word):
|
|
225
265
|
colored_words.append(f"{MAGENTA}{word}{RESET}")
|
|
226
266
|
colored = True
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
#
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
prefix
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
267
|
+
else:
|
|
268
|
+
# Split affixes to preserve punctuation like '<', '(', '[' etc.
|
|
269
|
+
prefix, core, suffix = split_affixes(word)
|
|
270
|
+
|
|
271
|
+
# Try to identify and color the word (operate on core where applicable)
|
|
272
|
+
if is_filepath(core):
|
|
273
|
+
colored_words.append(f"{prefix}{MAGENTA}{core}{RESET}{suffix}")
|
|
274
|
+
colored = True
|
|
275
|
+
elif is_exception(core):
|
|
276
|
+
colored_words.append(f"{prefix}{BOLD}{MAGENTA}{core}{RESET}{suffix}")
|
|
277
|
+
colored = True
|
|
278
|
+
elif is_number(core):
|
|
279
|
+
colored_words.append(f"{prefix}{MAGENTA}{core}{RESET}{suffix}")
|
|
280
|
+
colored = True
|
|
281
|
+
elif is_keyword(core):
|
|
282
|
+
colored_words.append(f"{prefix}{MAGENTA}{core}{RESET}{suffix}")
|
|
283
|
+
colored = True
|
|
284
|
+
elif is_function_name(core)[0]:
|
|
285
|
+
func_name = is_function_name(core)[1]
|
|
286
|
+
# Find where the function name ends in the core
|
|
287
|
+
func_start = core.find(func_name)
|
|
288
|
+
if func_start != -1:
|
|
289
|
+
pre_core = core[:func_start]
|
|
290
|
+
func_end = func_start + len(func_name)
|
|
291
|
+
post_core = core[func_end:]
|
|
292
|
+
colored_words.append(f"{prefix}{pre_core}{MAGENTA}{func_name}{RESET}{post_core}{suffix}")
|
|
293
|
+
else:
|
|
294
|
+
# Fallback if we can't find it (shouldn't happen)
|
|
295
|
+
colored_words.append(f"{prefix}{MAGENTA}{core}{RESET}{suffix}")
|
|
296
|
+
colored = True
|
|
250
297
|
|
|
251
|
-
# If nothing matched, keep the word
|
|
298
|
+
# If nothing matched, keep the original word
|
|
252
299
|
if not colored:
|
|
253
300
|
colored_words.append(word)
|
|
254
301
|
i += 1
|
stouputils/print.pyi
CHANGED
|
@@ -53,45 +53,67 @@ def format_colored(*values: Any) -> str:
|
|
|
53
53
|
\tExamples:
|
|
54
54
|
\t\t>>> # Test function names with parentheses
|
|
55
55
|
\t\t>>> result = format_colored("Call print() with 42 items")
|
|
56
|
-
\t\t>>> result.count(MAGENTA)
|
|
57
|
-
\t\
|
|
56
|
+
\t\t>>> result.count(MAGENTA) # print and 42
|
|
57
|
+
\t\t2
|
|
58
58
|
|
|
59
59
|
\t\t>>> # Test function names without parentheses
|
|
60
60
|
\t\t>>> result = format_colored("Use len and sum functions")
|
|
61
|
-
\t\t>>> result.count(MAGENTA)
|
|
62
|
-
\t\
|
|
61
|
+
\t\t>>> result.count(MAGENTA) # len and sum
|
|
62
|
+
\t\t2
|
|
63
63
|
|
|
64
64
|
\t\t>>> # Test exceptions (bold magenta)
|
|
65
65
|
\t\t>>> result = format_colored("Got ValueError when parsing")
|
|
66
|
-
\t\t>>> result.count(MAGENTA)
|
|
67
|
-
\t\
|
|
66
|
+
\t\t>>> result.count(MAGENTA), result.count(BOLD) # ValueError in bold magenta
|
|
67
|
+
\t\t(1, 1)
|
|
68
68
|
|
|
69
69
|
\t\t>>> # Test file paths
|
|
70
70
|
\t\t>>> result = format_colored("Processing ./data.csv file")
|
|
71
|
-
\t\t>>> result.count(MAGENTA)
|
|
72
|
-
\t\
|
|
71
|
+
\t\t>>> result.count(MAGENTA) # ./data.csv
|
|
72
|
+
\t\t1
|
|
73
73
|
|
|
74
74
|
\t\t>>> # Test file paths with quotes
|
|
75
75
|
\t\t>>> result = format_colored(\'File "/path/to/script.py" line 42\')
|
|
76
|
-
\t\t>>> result.count(MAGENTA)
|
|
77
|
-
\t\
|
|
76
|
+
\t\t>>> result.count(MAGENTA) # /path/to/script.py and 42
|
|
77
|
+
\t\t2
|
|
78
78
|
|
|
79
79
|
\t\t>>> # Test numbers
|
|
80
|
-
\t\t>>> result = format_colored("Found 100 items and 3.14 value")
|
|
81
|
-
\t\t>>> result.count(MAGENTA)
|
|
82
|
-
\t\
|
|
80
|
+
\t\t>>> result = format_colored("Found 100 items and 3.14 value, 3.0e+10 is big")
|
|
81
|
+
\t\t>>> result.count(MAGENTA) # 100 and 3.14
|
|
82
|
+
\t\t3
|
|
83
83
|
|
|
84
84
|
\t\t>>> # Test mixed content
|
|
85
85
|
\t\t>>> result = format_colored("Call sum() got IndexError at line 256 in utils.py")
|
|
86
|
-
\t\t>>> result.count(MAGENTA)
|
|
87
|
-
\t\
|
|
88
|
-
\t\t>>> result.count(BOLD)
|
|
89
|
-
\t\
|
|
86
|
+
\t\t>>> result.count(MAGENTA) # sum, IndexError (bold), and 256
|
|
87
|
+
\t\t3
|
|
88
|
+
\t\t>>> result.count(BOLD) # IndexError is bold
|
|
89
|
+
\t\t1
|
|
90
|
+
|
|
91
|
+
\t\t>>> # Test keywords always colored
|
|
92
|
+
\t\t>>> result = format_colored("Check class dtype type")
|
|
93
|
+
\t\t>>> result.count(MAGENTA) # class, dtype, type
|
|
94
|
+
\t\t3
|
|
90
95
|
|
|
91
96
|
\t\t>>> # Test plain text (no coloring)
|
|
92
97
|
\t\t>>> result = format_colored("This is plain text")
|
|
93
98
|
\t\t>>> result.count(MAGENTA) == 0 and result == "This is plain text"
|
|
94
99
|
\t\tTrue
|
|
100
|
+
|
|
101
|
+
\t\t>>> # Affix punctuation should not be colored (assert exact coloring, punctuation uncolored)
|
|
102
|
+
\t\t>>> result = format_colored("<class")
|
|
103
|
+
\t\t>>> result == "<" + MAGENTA + "class" + RESET
|
|
104
|
+
\t\tTrue
|
|
105
|
+
\t\t>>> result = format_colored("(dtype:")
|
|
106
|
+
\t\t>>> result == "(" + MAGENTA + "dtype" + RESET + ":"
|
|
107
|
+
\t\tTrue
|
|
108
|
+
\t\t>>> result = format_colored("[1.")
|
|
109
|
+
\t\t>>> result == "[" + MAGENTA + "1" + RESET + "."
|
|
110
|
+
\t\tTrue
|
|
111
|
+
|
|
112
|
+
\t\t>>> # Test complex
|
|
113
|
+
\t\t>>> text = "<class \'numpy.ndarray\'>, <id 140357548266896>: (dtype: float32, shape: (6,), min: 0.0, max: 1.0) [1. 0. 0. 0. 1. 0.]"
|
|
114
|
+
\t\t>>> result = format_colored(text)
|
|
115
|
+
\t\t>>> result.count(MAGENTA) # class, numpy, ndarray, float32, 6, 0.0, 1.0, 1. 0.
|
|
116
|
+
\t\t16
|
|
95
117
|
\t'''
|
|
96
118
|
def colored(*values: Any, file: TextIO | None = None, **print_kwargs: Any) -> None:
|
|
97
119
|
''' Print with Python 3.14 style colored formatting.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: stouputils
|
|
3
|
-
Version: 1.18.
|
|
3
|
+
Version: 1.18.6
|
|
4
4
|
Summary: Stouputils is a collection of utility modules designed to simplify and enhance the development process. It includes a range of tools for tasks such as execution of doctests, display utilities, decorators, as well as context managers, and many more.
|
|
5
5
|
Keywords: utilities,tools,helpers,development,python
|
|
6
6
|
Author: Stoupy51
|
|
@@ -3,7 +3,7 @@ stouputils/__init__.pyi,sha256=T8-ovn3Kl5CxYtBjJJ5AzR2qujECMI1Biwahz0nooBo,395
|
|
|
3
3
|
stouputils/__main__.py,sha256=MA3jjc1yL8_0Z9oB55BMqrFq1ot_-e5rNqSxFQGsMzs,2910
|
|
4
4
|
stouputils/_deprecated.py,sha256=Bcq6YjdM9Rk9Vq-WMhc_tuEbPORX6U8HAJ9Vh-VIWTA,1478
|
|
5
5
|
stouputils/_deprecated.pyi,sha256=6-8YsftJd2fRAdBLsysc6jf-uA8V2wiqkiFAbdfWfJQ,664
|
|
6
|
-
stouputils/all_doctests.py,sha256=
|
|
6
|
+
stouputils/all_doctests.py,sha256=jQ7HXWoj2VTUbsu8eBq7EiW9mbx-_yXycdpS1fD8Tro,6566
|
|
7
7
|
stouputils/all_doctests.pyi,sha256=R3FRKaQv3sTZbxLvvsChHZZKygVMhmL6pqrYYLqvZCg,2017
|
|
8
8
|
stouputils/applications/__init__.py,sha256=dbjwZt8PZF043KoJSItqCpH32FtRxN5sgV-8Q2b1l10,457
|
|
9
9
|
stouputils/applications/__init__.pyi,sha256=DTYq2Uqq1uLzCMkFByjRqdtREA-9SaQnp4QpgmCEPFg,56
|
|
@@ -149,14 +149,14 @@ stouputils/parallel/multi.py,sha256=tHJgcQJwsI6QeKEHoGJC4tsVK_6t1Fazkb06i1u-W_8,
|
|
|
149
149
|
stouputils/parallel/multi.pyi,sha256=DWolZn1UoXxOfuw7LqEJcU8aQJsN-_DRhPGJlJCA5pQ,8021
|
|
150
150
|
stouputils/parallel/subprocess.py,sha256=LWbwwAmnz54dCz9TAcKNg1TOMCVSP0C-0GIXaS5nVx0,6728
|
|
151
151
|
stouputils/parallel/subprocess.pyi,sha256=gzRtpTslvoENLtSNk79fe3Xz8lV3IwuopT9uMHW9BTU,3680
|
|
152
|
-
stouputils/print.py,sha256=
|
|
153
|
-
stouputils/print.pyi,sha256=
|
|
152
|
+
stouputils/print.py,sha256=86Qjyyj_riU7w3RQdYIHTlPVICUzKsfEBF6NBwZc20g,26745
|
|
153
|
+
stouputils/print.pyi,sha256=qu7Pr1c6let2fLcBvbfrrcfCg0s3rf_1jD8FDhR1bgk,11188
|
|
154
154
|
stouputils/py.typed,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
|
|
155
155
|
stouputils/typing.py,sha256=TwvxrvxhBRkyHkoOpfyXebN13M3xJb8MAjKXiNIWjew,2205
|
|
156
156
|
stouputils/typing.pyi,sha256=U2UmFZausMYpnsUQROQE2JOwHcjx2hKV0rJuOdR57Ew,1341
|
|
157
157
|
stouputils/version_pkg.py,sha256=Jsp-s03L14DkiZ94vQgrlQmaxApfn9DC8M_nzT1SJLk,7014
|
|
158
158
|
stouputils/version_pkg.pyi,sha256=QPvqp1U3QA-9C_CC1dT9Vahv1hXEhstbM7x5uzMZSsQ,755
|
|
159
|
-
stouputils-1.18.
|
|
160
|
-
stouputils-1.18.
|
|
161
|
-
stouputils-1.18.
|
|
162
|
-
stouputils-1.18.
|
|
159
|
+
stouputils-1.18.6.dist-info/WHEEL,sha256=e_m4S054HL0hyR3CpOk-b7Q7fDX6BuFkgL5OjAExXas,80
|
|
160
|
+
stouputils-1.18.6.dist-info/entry_points.txt,sha256=tx0z9VOnE-sfkmbFbA93zaBMzV3XSsKEJa_BWIqUzxw,57
|
|
161
|
+
stouputils-1.18.6.dist-info/METADATA,sha256=w_R6exClXWoaJI171uO0hX5rZ3oh3hTvCtCua1di9RM,14011
|
|
162
|
+
stouputils-1.18.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|