pyutilkit 0.8.0__tar.gz → 0.9.0__tar.gz
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.
Potentially problematic release.
This version of pyutilkit might be problematic. Click here for more details.
- {pyutilkit-0.8.0 → pyutilkit-0.9.0}/PKG-INFO +1 -1
- {pyutilkit-0.8.0 → pyutilkit-0.9.0}/pyproject.toml +4 -3
- {pyutilkit-0.8.0 → pyutilkit-0.9.0}/src/pyutilkit/subprocess.py +3 -3
- {pyutilkit-0.8.0 → pyutilkit-0.9.0}/src/pyutilkit/term.py +128 -38
- {pyutilkit-0.8.0 → pyutilkit-0.9.0}/src/pyutilkit/timing.py +1 -1
- {pyutilkit-0.8.0 → pyutilkit-0.9.0}/LICENSE.md +0 -0
- {pyutilkit-0.8.0 → pyutilkit-0.9.0}/docs/README.md +0 -0
- {pyutilkit-0.8.0 → pyutilkit-0.9.0}/src/pyutilkit/__init__.py +0 -0
- {pyutilkit-0.8.0 → pyutilkit-0.9.0}/src/pyutilkit/classes.py +0 -0
- {pyutilkit-0.8.0 → pyutilkit-0.9.0}/src/pyutilkit/data/__init__.py +0 -0
- {pyutilkit-0.8.0 → pyutilkit-0.9.0}/src/pyutilkit/data/timezones.py +0 -0
- {pyutilkit-0.8.0 → pyutilkit-0.9.0}/src/pyutilkit/date_utils.py +0 -0
- {pyutilkit-0.8.0 → pyutilkit-0.9.0}/src/pyutilkit/files.py +0 -0
- {pyutilkit-0.8.0 → pyutilkit-0.9.0}/src/pyutilkit/py.typed +0 -0
|
@@ -6,7 +6,7 @@ build-backend = "phosphorus.construction.api"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "pyutilkit"
|
|
9
|
-
version = "0.
|
|
9
|
+
version = "0.9.0"
|
|
10
10
|
|
|
11
11
|
authors = [
|
|
12
12
|
{ name = "Stephanos Kuma", email = "stephanos@kuma.ai" },
|
|
@@ -67,7 +67,7 @@ target-version = [
|
|
|
67
67
|
check_untyped_defs = true
|
|
68
68
|
disallow_any_decorated = true
|
|
69
69
|
disallow_any_explicit = true
|
|
70
|
-
disallow_any_expr =
|
|
70
|
+
disallow_any_expr = false # many builtins are Any
|
|
71
71
|
disallow_any_generics = true
|
|
72
72
|
disallow_any_unimported = true
|
|
73
73
|
disallow_incomplete_defs = true
|
|
@@ -102,6 +102,7 @@ select = [
|
|
|
102
102
|
"ALL",
|
|
103
103
|
]
|
|
104
104
|
ignore = [
|
|
105
|
+
"C901", # Adding a limit to complexity is too arbitrary
|
|
105
106
|
"COM812", # Avoid magic trailing commas
|
|
106
107
|
"D10", # Not everything needs a docstring
|
|
107
108
|
"D203", # Prefer `no-blank-line-before-class` (D211)
|
|
@@ -146,7 +147,7 @@ data_file = ".cov_cache/coverage.dat"
|
|
|
146
147
|
exclude_also = [
|
|
147
148
|
"if TYPE_CHECKING:",
|
|
148
149
|
]
|
|
149
|
-
fail_under =
|
|
150
|
+
fail_under = 90
|
|
150
151
|
precision = 2
|
|
151
152
|
show_missing = true
|
|
152
153
|
skip_covered = true
|
|
@@ -34,12 +34,12 @@ def run_command(
|
|
|
34
34
|
)
|
|
35
35
|
|
|
36
36
|
for line in process.stdout or []:
|
|
37
|
-
sys.stdout.buffer.write(line)
|
|
37
|
+
sys.stdout.buffer.write(line)
|
|
38
38
|
sys.stdout.flush()
|
|
39
39
|
stdout.append(line)
|
|
40
40
|
|
|
41
41
|
for line in process.stderr or []:
|
|
42
|
-
sys.stderr.buffer.write(line)
|
|
42
|
+
sys.stderr.buffer.write(line)
|
|
43
43
|
sys.stderr.flush()
|
|
44
44
|
stderr.append(line)
|
|
45
45
|
|
|
@@ -50,6 +50,6 @@ def run_command(
|
|
|
50
50
|
stdout=b"".join(stdout),
|
|
51
51
|
stderr=b"".join(stderr),
|
|
52
52
|
pid=process.pid,
|
|
53
|
-
returncode=process.returncode,
|
|
53
|
+
returncode=process.returncode,
|
|
54
54
|
elapsed=stopwatch.elapsed,
|
|
55
55
|
)
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
import sys
|
|
5
|
+
from dataclasses import dataclass
|
|
5
6
|
from enum import IntEnum, unique
|
|
6
7
|
from math import ceil, floor
|
|
7
8
|
from typing import TYPE_CHECKING
|
|
@@ -66,36 +67,50 @@ class SGRCodes(IntEnum):
|
|
|
66
67
|
return f"\033[{self.value}m"
|
|
67
68
|
|
|
68
69
|
|
|
69
|
-
|
|
70
|
-
|
|
70
|
+
@dataclass(frozen=True, order=True)
|
|
71
|
+
class SGRString:
|
|
72
|
+
__slots__ = ( # py3.9: remove this line
|
|
73
|
+
"_force_prefix",
|
|
74
|
+
"_force_sgr",
|
|
75
|
+
"_is_error",
|
|
76
|
+
"_prefix",
|
|
77
|
+
"_sgr",
|
|
78
|
+
"_string",
|
|
79
|
+
"_suffix",
|
|
80
|
+
)
|
|
81
|
+
|
|
71
82
|
_string: str
|
|
83
|
+
_sgr: tuple[SGRCodes, ...]
|
|
72
84
|
_prefix: str
|
|
73
85
|
_suffix: str
|
|
74
|
-
|
|
86
|
+
_force_prefix: bool
|
|
87
|
+
_force_sgr: bool
|
|
88
|
+
_is_error: bool
|
|
75
89
|
|
|
76
|
-
def
|
|
77
|
-
|
|
90
|
+
def __init__(
|
|
91
|
+
self,
|
|
78
92
|
obj: object,
|
|
79
93
|
*,
|
|
80
94
|
prefix: str = "",
|
|
81
95
|
suffix: str = "",
|
|
82
96
|
params: Iterable[SGRCodes] = (),
|
|
83
|
-
|
|
84
|
-
|
|
97
|
+
force_prefix: bool = False,
|
|
98
|
+
force_sgr: bool = False,
|
|
99
|
+
is_error: bool = False,
|
|
100
|
+
) -> None:
|
|
85
101
|
params = tuple(params)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
return string
|
|
91
|
-
|
|
92
|
-
def __setattr__(self, name: str, value: object) -> None:
|
|
93
|
-
msg = "SGRString is immutable"
|
|
94
|
-
raise AttributeError(msg)
|
|
102
|
+
force_prefix = (
|
|
103
|
+
force_prefix or os.getenv("PY_UTIL_FORCE_PREFIX", "").lower() in TRUE_VAR
|
|
104
|
+
)
|
|
105
|
+
force_sgr = force_sgr or os.getenv("PY_UTIL_FORCE_SGR", "").lower() in TRUE_VAR
|
|
95
106
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
107
|
+
object.__setattr__(self, "_prefix", prefix)
|
|
108
|
+
object.__setattr__(self, "_string", str(obj))
|
|
109
|
+
object.__setattr__(self, "_sgr", params)
|
|
110
|
+
object.__setattr__(self, "_suffix", suffix)
|
|
111
|
+
object.__setattr__(self, "_force_prefix", force_prefix)
|
|
112
|
+
object.__setattr__(self, "_force_sgr", force_sgr)
|
|
113
|
+
object.__setattr__(self, "_is_error", is_error)
|
|
99
114
|
|
|
100
115
|
def __str__(self) -> str:
|
|
101
116
|
sgr_prefix = "".join(code.sequence for code in self._sgr)
|
|
@@ -113,6 +128,9 @@ class SGRString(str):
|
|
|
113
128
|
prefix=self._prefix,
|
|
114
129
|
suffix=self._suffix,
|
|
115
130
|
params=self._sgr,
|
|
131
|
+
force_prefix=self._force_prefix,
|
|
132
|
+
force_sgr=self._force_sgr,
|
|
133
|
+
is_error=self._is_error,
|
|
116
134
|
)
|
|
117
135
|
|
|
118
136
|
def __rmul__(self, other: object) -> Self:
|
|
@@ -123,16 +141,12 @@ class SGRString(str):
|
|
|
123
141
|
prefix=self._prefix,
|
|
124
142
|
suffix=self._suffix,
|
|
125
143
|
params=self._sgr,
|
|
144
|
+
force_prefix=self._force_prefix,
|
|
145
|
+
force_sgr=self._force_sgr,
|
|
146
|
+
is_error=self._is_error,
|
|
126
147
|
)
|
|
127
148
|
|
|
128
|
-
def print(
|
|
129
|
-
self,
|
|
130
|
-
end: str = "\n",
|
|
131
|
-
*,
|
|
132
|
-
force_prefix: bool = False,
|
|
133
|
-
force_sgr: bool = False,
|
|
134
|
-
is_error: bool = False,
|
|
135
|
-
) -> None:
|
|
149
|
+
def print(self, end: str = "\n", *, full_color: bool = False) -> None:
|
|
136
150
|
"""Print the command output.
|
|
137
151
|
|
|
138
152
|
The command will be printed to stdout if it's not the output of an error,
|
|
@@ -141,8 +155,8 @@ class SGRString(str):
|
|
|
141
155
|
If the output stream isn't a tty, it will strip the SGR codes and the prefix,
|
|
142
156
|
unless forced to keep them.
|
|
143
157
|
"""
|
|
144
|
-
file = sys.stderr if
|
|
145
|
-
if file.isatty():
|
|
158
|
+
file = sys.stderr if self._is_error else sys.stdout
|
|
159
|
+
if file.isatty():
|
|
146
160
|
prefix = self._prefix
|
|
147
161
|
sgr_prefix = "".join(code.sequence for code in self._sgr)
|
|
148
162
|
sgr_suffix = SGRCodes.RESET.sequence
|
|
@@ -152,16 +166,17 @@ class SGRString(str):
|
|
|
152
166
|
sgr_prefix = ""
|
|
153
167
|
sgr_suffix = ""
|
|
154
168
|
suffix = ""
|
|
155
|
-
if
|
|
169
|
+
if self._force_sgr:
|
|
156
170
|
sgr_prefix = "".join(code.sequence for code in self._sgr)
|
|
157
171
|
sgr_suffix = SGRCodes.RESET.sequence if self._sgr else ""
|
|
158
|
-
if
|
|
159
|
-
force_prefix
|
|
160
|
-
or os.getenv("PY_UTIL_FORCE_OUTFIX", "").lower() in TRUE_VAR
|
|
161
|
-
):
|
|
172
|
+
if self._force_prefix:
|
|
162
173
|
prefix = self._prefix
|
|
163
174
|
suffix = self._suffix
|
|
164
175
|
|
|
176
|
+
if full_color:
|
|
177
|
+
prefix, sgr_prefix = sgr_prefix, prefix
|
|
178
|
+
suffix, sgr_suffix = sgr_suffix, suffix
|
|
179
|
+
|
|
165
180
|
print(
|
|
166
181
|
prefix,
|
|
167
182
|
sgr_prefix,
|
|
@@ -180,23 +195,98 @@ class SGRString(str):
|
|
|
180
195
|
left_spaces: int = 1,
|
|
181
196
|
right_spaces: int = 1,
|
|
182
197
|
space: str = " ",
|
|
183
|
-
force_sgr: bool = False,
|
|
184
|
-
is_error: bool = False,
|
|
185
198
|
) -> None:
|
|
186
199
|
try:
|
|
187
200
|
terminal_size = os.get_terminal_size()
|
|
188
201
|
except OSError:
|
|
189
202
|
# in pseudo-terminals an OSError is thrown
|
|
190
|
-
self.print(
|
|
203
|
+
self.print()
|
|
191
204
|
return
|
|
192
205
|
|
|
193
206
|
columns = terminal_size.columns
|
|
194
207
|
title_length = left_spaces + len(self) + right_spaces
|
|
195
208
|
if title_length >= columns:
|
|
196
|
-
self.print(
|
|
209
|
+
self.print()
|
|
197
210
|
return
|
|
198
211
|
|
|
199
212
|
half = (columns - title_length) / 2
|
|
200
213
|
prefix = f"{padding * ceil(half)}{space * left_spaces}{self._prefix}"
|
|
201
214
|
suffix = f"{self._suffix}{space * right_spaces}{padding * floor(half)}"
|
|
202
215
|
type(self)(self._string, prefix=prefix, suffix=suffix, params=self._sgr).print()
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
@dataclass(frozen=True, order=True)
|
|
219
|
+
class SGROutput:
|
|
220
|
+
__slots__ = ("_strings",) # py3.9: remove this line
|
|
221
|
+
_strings: tuple[SGRString, ...]
|
|
222
|
+
|
|
223
|
+
def __init__(
|
|
224
|
+
self,
|
|
225
|
+
strings: Iterable[SGRString],
|
|
226
|
+
force_prefix: bool | None = None,
|
|
227
|
+
force_sgr: bool | None = None,
|
|
228
|
+
is_error: bool | None = None,
|
|
229
|
+
) -> None:
|
|
230
|
+
strings = tuple(
|
|
231
|
+
self._clean_string(
|
|
232
|
+
string,
|
|
233
|
+
force_prefix=force_prefix,
|
|
234
|
+
force_sgr=force_sgr,
|
|
235
|
+
is_error=is_error,
|
|
236
|
+
)
|
|
237
|
+
for string in strings
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
object.__setattr__(self, "_strings", strings)
|
|
241
|
+
|
|
242
|
+
@staticmethod
|
|
243
|
+
def _clean_string(
|
|
244
|
+
string: SGRString,
|
|
245
|
+
force_prefix: bool | None,
|
|
246
|
+
force_sgr: bool | None,
|
|
247
|
+
is_error: bool | None,
|
|
248
|
+
) -> SGRString:
|
|
249
|
+
force_prefix = (
|
|
250
|
+
string._force_prefix # noqa: SLF001
|
|
251
|
+
if force_prefix is None
|
|
252
|
+
else force_prefix
|
|
253
|
+
)
|
|
254
|
+
force_sgr = (
|
|
255
|
+
string._force_sgr if force_sgr is None else force_sgr # noqa: SLF001
|
|
256
|
+
)
|
|
257
|
+
is_error = string._is_error if is_error is None else is_error # noqa: SLF001
|
|
258
|
+
return SGRString(
|
|
259
|
+
string._string, # noqa: SLF001
|
|
260
|
+
prefix=string._prefix, # noqa: SLF001
|
|
261
|
+
suffix=string._suffix, # noqa: SLF001
|
|
262
|
+
params=string._sgr, # noqa: SLF001
|
|
263
|
+
force_prefix=force_prefix,
|
|
264
|
+
force_sgr=force_sgr,
|
|
265
|
+
is_error=is_error,
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
def print(self, sep: str = "", end: str = "\n") -> None:
|
|
269
|
+
n = len(self._strings)
|
|
270
|
+
for index, string in enumerate(self._strings, start=1):
|
|
271
|
+
current_end = end if index == n else sep
|
|
272
|
+
string.print(end=current_end)
|
|
273
|
+
|
|
274
|
+
def header(
|
|
275
|
+
self,
|
|
276
|
+
*,
|
|
277
|
+
padding: str = " ",
|
|
278
|
+
left_spaces: int = 1,
|
|
279
|
+
right_spaces: int = 1,
|
|
280
|
+
space: str = " ",
|
|
281
|
+
) -> None:
|
|
282
|
+
n = len(self._strings)
|
|
283
|
+
if n > 1:
|
|
284
|
+
msg = "Only one string is allowed for the header"
|
|
285
|
+
raise ValueError(msg)
|
|
286
|
+
|
|
287
|
+
self._strings[0].header(
|
|
288
|
+
padding=padding,
|
|
289
|
+
left_spaces=left_spaces,
|
|
290
|
+
right_spaces=right_spaces,
|
|
291
|
+
space=space,
|
|
292
|
+
)
|
|
@@ -91,7 +91,7 @@ class Timing:
|
|
|
91
91
|
def __truediv__(self, other: object) -> Timing:
|
|
92
92
|
if not isinstance(other, int):
|
|
93
93
|
return NotImplemented
|
|
94
|
-
return Timing(nanoseconds=self.nanoseconds
|
|
94
|
+
return Timing(nanoseconds=round(self.nanoseconds / other))
|
|
95
95
|
|
|
96
96
|
|
|
97
97
|
class Stopwatch:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|