dycw-utilities 0.166.30__py3-none-any.whl → 0.185.8__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.
- dycw_utilities-0.185.8.dist-info/METADATA +33 -0
- dycw_utilities-0.185.8.dist-info/RECORD +90 -0
- {dycw_utilities-0.166.30.dist-info → dycw_utilities-0.185.8.dist-info}/WHEEL +1 -1
- {dycw_utilities-0.166.30.dist-info → dycw_utilities-0.185.8.dist-info}/entry_points.txt +1 -0
- utilities/__init__.py +1 -1
- utilities/altair.py +17 -10
- utilities/asyncio.py +50 -72
- utilities/atools.py +9 -11
- utilities/cachetools.py +16 -11
- utilities/click.py +76 -19
- utilities/concurrent.py +1 -1
- utilities/constants.py +492 -0
- utilities/contextlib.py +23 -30
- utilities/contextvars.py +1 -23
- utilities/core.py +2581 -0
- utilities/dataclasses.py +16 -119
- utilities/docker.py +387 -0
- utilities/enum.py +1 -1
- utilities/errors.py +2 -16
- utilities/fastapi.py +5 -5
- utilities/fpdf2.py +2 -1
- utilities/functions.py +34 -265
- utilities/http.py +2 -3
- utilities/hypothesis.py +84 -29
- utilities/importlib.py +17 -1
- utilities/iterables.py +39 -575
- utilities/jinja2.py +145 -0
- utilities/jupyter.py +5 -3
- utilities/libcst.py +1 -1
- utilities/lightweight_charts.py +4 -6
- utilities/logging.py +24 -24
- utilities/math.py +1 -36
- utilities/more_itertools.py +4 -6
- utilities/numpy.py +2 -1
- utilities/operator.py +2 -2
- utilities/orjson.py +42 -43
- utilities/os.py +4 -147
- utilities/packaging.py +129 -0
- utilities/parse.py +35 -15
- utilities/pathlib.py +3 -120
- utilities/platform.py +8 -90
- utilities/polars.py +38 -32
- utilities/postgres.py +37 -33
- utilities/pottery.py +20 -18
- utilities/pqdm.py +3 -4
- utilities/psutil.py +2 -3
- utilities/pydantic.py +25 -0
- utilities/pydantic_settings.py +87 -16
- utilities/pydantic_settings_sops.py +16 -3
- utilities/pyinstrument.py +4 -4
- utilities/pytest.py +96 -125
- utilities/pytest_plugins/pytest_regressions.py +2 -2
- utilities/pytest_regressions.py +32 -11
- utilities/random.py +2 -8
- utilities/redis.py +98 -94
- utilities/reprlib.py +11 -118
- utilities/shellingham.py +66 -0
- utilities/shutil.py +25 -0
- utilities/slack_sdk.py +13 -12
- utilities/sqlalchemy.py +57 -30
- utilities/sqlalchemy_polars.py +16 -25
- utilities/subprocess.py +2590 -0
- utilities/tabulate.py +32 -0
- utilities/testbook.py +8 -8
- utilities/text.py +24 -99
- utilities/throttle.py +159 -0
- utilities/time.py +18 -0
- utilities/timer.py +31 -14
- utilities/traceback.py +16 -23
- utilities/types.py +42 -2
- utilities/typing.py +26 -14
- utilities/uuid.py +1 -1
- utilities/version.py +202 -45
- utilities/whenever.py +53 -150
- dycw_utilities-0.166.30.dist-info/METADATA +0 -41
- dycw_utilities-0.166.30.dist-info/RECORD +0 -98
- dycw_utilities-0.166.30.dist-info/licenses/LICENSE +0 -21
- utilities/aeventkit.py +0 -388
- utilities/atomicwrites.py +0 -182
- utilities/cryptography.py +0 -41
- utilities/getpass.py +0 -8
- utilities/git.py +0 -19
- utilities/gzip.py +0 -31
- utilities/json.py +0 -70
- utilities/pickle.py +0 -25
- utilities/re.py +0 -156
- utilities/sentinel.py +0 -73
- utilities/socket.py +0 -8
- utilities/string.py +0 -20
- utilities/tempfile.py +0 -77
- utilities/typed_settings.py +0 -152
- utilities/tzdata.py +0 -11
- utilities/tzlocal.py +0 -28
- utilities/warnings.py +0 -65
- utilities/zipfile.py +0 -25
- utilities/zoneinfo.py +0 -133
utilities/version.py
CHANGED
|
@@ -2,23 +2,152 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
4
|
from collections.abc import Callable
|
|
5
|
+
from contextlib import suppress
|
|
5
6
|
from dataclasses import dataclass, field, replace
|
|
6
7
|
from functools import total_ordering
|
|
7
8
|
from typing import Any, Self, assert_never, overload, override
|
|
8
9
|
|
|
9
|
-
from utilities.
|
|
10
|
+
from utilities.constants import Sentinel
|
|
10
11
|
from utilities.types import MaybeCallable, MaybeStr
|
|
11
12
|
|
|
12
|
-
type
|
|
13
|
-
type
|
|
13
|
+
type Version2Like = MaybeStr[Version2]
|
|
14
|
+
type Version3Like = MaybeStr[Version3]
|
|
15
|
+
type Version2Or3 = Version2 | Version3
|
|
16
|
+
type MaybeCallableVersion3Like = MaybeCallable[Version3Like]
|
|
14
17
|
|
|
15
18
|
|
|
16
19
|
##
|
|
17
20
|
|
|
18
21
|
|
|
22
|
+
_PARSE_VERSION2_PATTERN = re.compile(r"^(\d+)\.(\d+)(?!\.\d)(?:-(\w+))?")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(repr=False, frozen=True, slots=True)
|
|
26
|
+
@total_ordering
|
|
27
|
+
class Version2:
|
|
28
|
+
"""A version identifier."""
|
|
29
|
+
|
|
30
|
+
major: int = 0
|
|
31
|
+
minor: int = 1
|
|
32
|
+
suffix: str | None = field(default=None, kw_only=True)
|
|
33
|
+
|
|
34
|
+
def __post_init__(self) -> None:
|
|
35
|
+
if (self.major == 0) and (self.minor == 0):
|
|
36
|
+
raise _Version2ZeroError(major=self.major, minor=self.minor)
|
|
37
|
+
if self.major < 0:
|
|
38
|
+
raise _Version2NegativeMajorVersionError(major=self.major)
|
|
39
|
+
if self.minor < 0:
|
|
40
|
+
raise _Version2NegativeMinorVersionError(minor=self.minor)
|
|
41
|
+
if (self.suffix is not None) and (len(self.suffix) == 0):
|
|
42
|
+
raise _Version2EmptySuffixError(suffix=self.suffix)
|
|
43
|
+
|
|
44
|
+
def __le__(self, other: Any, /) -> bool:
|
|
45
|
+
if not isinstance(other, type(self)):
|
|
46
|
+
return NotImplemented
|
|
47
|
+
self_as_tuple = (
|
|
48
|
+
self.major,
|
|
49
|
+
self.minor,
|
|
50
|
+
"" if self.suffix is None else self.suffix,
|
|
51
|
+
)
|
|
52
|
+
other_as_tuple = (
|
|
53
|
+
other.major,
|
|
54
|
+
other.minor,
|
|
55
|
+
"" if other.suffix is None else other.suffix,
|
|
56
|
+
)
|
|
57
|
+
return self_as_tuple <= other_as_tuple
|
|
58
|
+
|
|
59
|
+
@override
|
|
60
|
+
def __repr__(self) -> str:
|
|
61
|
+
version = f"{self.major}.{self.minor}"
|
|
62
|
+
if self.suffix is not None:
|
|
63
|
+
version = f"{version}-{self.suffix}"
|
|
64
|
+
return version
|
|
65
|
+
|
|
66
|
+
@classmethod
|
|
67
|
+
def parse(cls, text: str, /) -> Self:
|
|
68
|
+
"""Parse a string into a Version2 object."""
|
|
69
|
+
try:
|
|
70
|
+
((major, minor, suffix),) = _PARSE_VERSION2_PATTERN.findall(text)
|
|
71
|
+
except ValueError:
|
|
72
|
+
raise _Version2ParseError(text=text) from None
|
|
73
|
+
return cls(int(major), int(minor), suffix=None if suffix == "" else suffix)
|
|
74
|
+
|
|
75
|
+
def bump_major(self) -> Self:
|
|
76
|
+
"""Bump the major component."""
|
|
77
|
+
return type(self)(self.major + 1, 0)
|
|
78
|
+
|
|
79
|
+
def bump_minor(self) -> Self:
|
|
80
|
+
"""Bump the minor component."""
|
|
81
|
+
return type(self)(self.major, self.minor + 1)
|
|
82
|
+
|
|
83
|
+
def version3(self, *, patch: int = 0) -> Version3:
|
|
84
|
+
"""Convert to a Version3 object."""
|
|
85
|
+
return Version3(self.major, self.minor, patch, suffix=self.suffix)
|
|
86
|
+
|
|
87
|
+
def with_suffix(self, *, suffix: str | None = None) -> Self:
|
|
88
|
+
"""Replace the suffix."""
|
|
89
|
+
return replace(self, suffix=suffix)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dataclass(kw_only=True, slots=True)
|
|
93
|
+
class Version2Error(Exception): ...
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@dataclass(kw_only=True, slots=True)
|
|
97
|
+
class _Version2ZeroError(Version2Error):
|
|
98
|
+
major: int
|
|
99
|
+
minor: int
|
|
100
|
+
|
|
101
|
+
@override
|
|
102
|
+
def __str__(self) -> str:
|
|
103
|
+
return f"Version must be greater than zero; got {self.major}.{self.minor}"
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@dataclass(kw_only=True, slots=True)
|
|
107
|
+
class _Version2NegativeMajorVersionError(Version2Error):
|
|
108
|
+
major: int
|
|
109
|
+
|
|
110
|
+
@override
|
|
111
|
+
def __str__(self) -> str:
|
|
112
|
+
return f"Major version must be non-negative; got {self.major}"
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@dataclass(kw_only=True, slots=True)
|
|
116
|
+
class _Version2NegativeMinorVersionError(Version2Error):
|
|
117
|
+
minor: int
|
|
118
|
+
|
|
119
|
+
@override
|
|
120
|
+
def __str__(self) -> str:
|
|
121
|
+
return f"Minor version must be non-negative; got {self.minor}"
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@dataclass(kw_only=True, slots=True)
|
|
125
|
+
class _Version2EmptySuffixError(Version2Error):
|
|
126
|
+
suffix: str
|
|
127
|
+
|
|
128
|
+
@override
|
|
129
|
+
def __str__(self) -> str:
|
|
130
|
+
return f"Suffix must be non-empty; got {self.suffix!r}"
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@dataclass(kw_only=True, slots=True)
|
|
134
|
+
class _Version2ParseError(Version2Error):
|
|
135
|
+
text: str
|
|
136
|
+
|
|
137
|
+
@override
|
|
138
|
+
def __str__(self) -> str:
|
|
139
|
+
return f"Unable to parse version; got {self.text!r}"
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
##
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
_PARSE_VERSION3_PATTERN = re.compile(r"^(\d+)\.(\d+)\.(\d+)(?:-(\w+))?")
|
|
146
|
+
|
|
147
|
+
|
|
19
148
|
@dataclass(repr=False, frozen=True, slots=True)
|
|
20
149
|
@total_ordering
|
|
21
|
-
class
|
|
150
|
+
class Version3:
|
|
22
151
|
"""A version identifier."""
|
|
23
152
|
|
|
24
153
|
major: int = 0
|
|
@@ -28,17 +157,17 @@ class Version:
|
|
|
28
157
|
|
|
29
158
|
def __post_init__(self) -> None:
|
|
30
159
|
if (self.major == 0) and (self.minor == 0) and (self.patch == 0):
|
|
31
|
-
raise
|
|
160
|
+
raise _Version3ZeroError(
|
|
32
161
|
major=self.major, minor=self.minor, patch=self.patch
|
|
33
162
|
)
|
|
34
163
|
if self.major < 0:
|
|
35
|
-
raise
|
|
164
|
+
raise _Version3NegativeMajorVersionError(major=self.major)
|
|
36
165
|
if self.minor < 0:
|
|
37
|
-
raise
|
|
166
|
+
raise _Version3NegativeMinorVersionError(minor=self.minor)
|
|
38
167
|
if self.patch < 0:
|
|
39
|
-
raise
|
|
168
|
+
raise _Version3NegativePatchVersionError(patch=self.patch)
|
|
40
169
|
if (self.suffix is not None) and (len(self.suffix) == 0):
|
|
41
|
-
raise
|
|
170
|
+
raise _Version3EmptySuffixError(suffix=self.suffix)
|
|
42
171
|
|
|
43
172
|
def __le__(self, other: Any, /) -> bool:
|
|
44
173
|
if not isinstance(other, type(self)):
|
|
@@ -64,25 +193,45 @@ class Version:
|
|
|
64
193
|
version = f"{version}-{self.suffix}"
|
|
65
194
|
return version
|
|
66
195
|
|
|
196
|
+
@classmethod
|
|
197
|
+
def parse(cls, text: str, /) -> Self:
|
|
198
|
+
"""Parse a string into a Version3 object."""
|
|
199
|
+
try:
|
|
200
|
+
((major, minor, patch, suffix),) = _PARSE_VERSION3_PATTERN.findall(text)
|
|
201
|
+
except ValueError:
|
|
202
|
+
raise _Version3ParseError(text=text) from None
|
|
203
|
+
return cls(
|
|
204
|
+
int(major), int(minor), int(patch), suffix=None if suffix == "" else suffix
|
|
205
|
+
)
|
|
206
|
+
|
|
67
207
|
def bump_major(self) -> Self:
|
|
208
|
+
"""Bump the major component."""
|
|
68
209
|
return type(self)(self.major + 1, 0, 0)
|
|
69
210
|
|
|
70
211
|
def bump_minor(self) -> Self:
|
|
212
|
+
"""Bump the minor component."""
|
|
71
213
|
return type(self)(self.major, self.minor + 1, 0)
|
|
72
214
|
|
|
73
215
|
def bump_patch(self) -> Self:
|
|
216
|
+
"""Bump the patch component."""
|
|
74
217
|
return type(self)(self.major, self.minor, self.patch + 1)
|
|
75
218
|
|
|
219
|
+
@property
|
|
220
|
+
def version2(self) -> Version2:
|
|
221
|
+
"""Return the major/minor components only."""
|
|
222
|
+
return Version2(self.major, self.minor, suffix=self.suffix)
|
|
223
|
+
|
|
76
224
|
def with_suffix(self, *, suffix: str | None = None) -> Self:
|
|
225
|
+
"""Replace the suffix."""
|
|
77
226
|
return replace(self, suffix=suffix)
|
|
78
227
|
|
|
79
228
|
|
|
80
229
|
@dataclass(kw_only=True, slots=True)
|
|
81
|
-
class
|
|
230
|
+
class Version3Error(Exception): ...
|
|
82
231
|
|
|
83
232
|
|
|
84
233
|
@dataclass(kw_only=True, slots=True)
|
|
85
|
-
class
|
|
234
|
+
class _Version3ZeroError(Version3Error):
|
|
86
235
|
major: int
|
|
87
236
|
minor: int
|
|
88
237
|
patch: int
|
|
@@ -93,7 +242,7 @@ class _VersionZeroError(VersionError):
|
|
|
93
242
|
|
|
94
243
|
|
|
95
244
|
@dataclass(kw_only=True, slots=True)
|
|
96
|
-
class
|
|
245
|
+
class _Version3NegativeMajorVersionError(Version3Error):
|
|
97
246
|
major: int
|
|
98
247
|
|
|
99
248
|
@override
|
|
@@ -102,7 +251,7 @@ class _VersionNegativeMajorVersionError(VersionError):
|
|
|
102
251
|
|
|
103
252
|
|
|
104
253
|
@dataclass(kw_only=True, slots=True)
|
|
105
|
-
class
|
|
254
|
+
class _Version3NegativeMinorVersionError(Version3Error):
|
|
106
255
|
minor: int
|
|
107
256
|
|
|
108
257
|
@override
|
|
@@ -111,7 +260,7 @@ class _VersionNegativeMinorVersionError(VersionError):
|
|
|
111
260
|
|
|
112
261
|
|
|
113
262
|
@dataclass(kw_only=True, slots=True)
|
|
114
|
-
class
|
|
263
|
+
class _Version3NegativePatchVersionError(Version3Error):
|
|
115
264
|
patch: int
|
|
116
265
|
|
|
117
266
|
@override
|
|
@@ -120,7 +269,7 @@ class _VersionNegativePatchVersionError(VersionError):
|
|
|
120
269
|
|
|
121
270
|
|
|
122
271
|
@dataclass(kw_only=True, slots=True)
|
|
123
|
-
class
|
|
272
|
+
class _Version3EmptySuffixError(Version3Error):
|
|
124
273
|
suffix: str
|
|
125
274
|
|
|
126
275
|
@override
|
|
@@ -128,63 +277,71 @@ class _VersionEmptySuffixError(VersionError):
|
|
|
128
277
|
return f"Suffix must be non-empty; got {self.suffix!r}"
|
|
129
278
|
|
|
130
279
|
|
|
131
|
-
|
|
280
|
+
@dataclass(kw_only=True, slots=True)
|
|
281
|
+
class _Version3ParseError(Version3Error):
|
|
282
|
+
text: str
|
|
283
|
+
|
|
284
|
+
@override
|
|
285
|
+
def __str__(self) -> str:
|
|
286
|
+
return f"Unable to parse version; got {self.text!r}"
|
|
132
287
|
|
|
133
288
|
|
|
134
|
-
|
|
135
|
-
"""Parse a string into a version object."""
|
|
136
|
-
try:
|
|
137
|
-
((major, minor, patch, suffix),) = _PARSE_VERSION_PATTERN.findall(version)
|
|
138
|
-
except ValueError:
|
|
139
|
-
raise ParseVersionError(version=version) from None
|
|
140
|
-
return Version(
|
|
141
|
-
int(major), int(minor), int(patch), suffix=None if suffix == "" else suffix
|
|
142
|
-
)
|
|
289
|
+
##
|
|
143
290
|
|
|
144
291
|
|
|
145
|
-
|
|
292
|
+
def parse_version_2_or_3(text: str, /) -> Version2Or3:
|
|
293
|
+
"""Parse a string into a Version2 or Version3 object."""
|
|
294
|
+
with suppress(_Version3ParseError):
|
|
295
|
+
return Version3.parse(text)
|
|
296
|
+
with suppress(_Version2ParseError):
|
|
297
|
+
return Version2.parse(text)
|
|
298
|
+
raise ParseVersion2Or3Error(text=text)
|
|
146
299
|
|
|
147
300
|
|
|
148
301
|
@dataclass(kw_only=True, slots=True)
|
|
149
|
-
class
|
|
150
|
-
|
|
302
|
+
class ParseVersion2Or3Error(Exception):
|
|
303
|
+
text: str
|
|
151
304
|
|
|
152
305
|
@override
|
|
153
306
|
def __str__(self) -> str:
|
|
154
|
-
return f"
|
|
307
|
+
return f"Unable to parse Version2 or Version3; got {self.text!r}"
|
|
155
308
|
|
|
156
309
|
|
|
157
310
|
##
|
|
158
311
|
|
|
159
312
|
|
|
160
313
|
@overload
|
|
161
|
-
def
|
|
314
|
+
def to_version3(version: MaybeCallableVersion3Like, /) -> Version3: ...
|
|
162
315
|
@overload
|
|
163
|
-
def
|
|
316
|
+
def to_version3(version: None, /) -> None: ...
|
|
164
317
|
@overload
|
|
165
|
-
def
|
|
166
|
-
def
|
|
167
|
-
version:
|
|
168
|
-
) ->
|
|
318
|
+
def to_version3(version: Sentinel, /) -> Sentinel: ...
|
|
319
|
+
def to_version3(
|
|
320
|
+
version: MaybeCallableVersion3Like | None | Sentinel, /
|
|
321
|
+
) -> Version3 | None | Sentinel:
|
|
169
322
|
"""Convert to a version."""
|
|
170
323
|
match version:
|
|
171
|
-
case
|
|
324
|
+
case Version3() | None | Sentinel():
|
|
172
325
|
return version
|
|
173
326
|
case str():
|
|
174
|
-
return
|
|
327
|
+
return Version3.parse(version)
|
|
175
328
|
case Callable() as func:
|
|
176
|
-
return
|
|
329
|
+
return to_version3(func())
|
|
177
330
|
case never:
|
|
178
331
|
assert_never(never)
|
|
179
332
|
|
|
180
333
|
|
|
181
334
|
##
|
|
182
335
|
__all__ = [
|
|
183
|
-
"
|
|
184
|
-
"
|
|
185
|
-
"
|
|
186
|
-
"
|
|
187
|
-
"
|
|
188
|
-
"
|
|
189
|
-
"
|
|
336
|
+
"MaybeCallableVersion3Like",
|
|
337
|
+
"ParseVersion2Or3Error",
|
|
338
|
+
"Version2",
|
|
339
|
+
"Version2Error",
|
|
340
|
+
"Version2Like",
|
|
341
|
+
"Version2Or3",
|
|
342
|
+
"Version3",
|
|
343
|
+
"Version3Error",
|
|
344
|
+
"Version3Like",
|
|
345
|
+
"parse_version_2_or_3",
|
|
346
|
+
"to_version3",
|
|
190
347
|
]
|