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.
Files changed (96) hide show
  1. dycw_utilities-0.185.8.dist-info/METADATA +33 -0
  2. dycw_utilities-0.185.8.dist-info/RECORD +90 -0
  3. {dycw_utilities-0.166.30.dist-info → dycw_utilities-0.185.8.dist-info}/WHEEL +1 -1
  4. {dycw_utilities-0.166.30.dist-info → dycw_utilities-0.185.8.dist-info}/entry_points.txt +1 -0
  5. utilities/__init__.py +1 -1
  6. utilities/altair.py +17 -10
  7. utilities/asyncio.py +50 -72
  8. utilities/atools.py +9 -11
  9. utilities/cachetools.py +16 -11
  10. utilities/click.py +76 -19
  11. utilities/concurrent.py +1 -1
  12. utilities/constants.py +492 -0
  13. utilities/contextlib.py +23 -30
  14. utilities/contextvars.py +1 -23
  15. utilities/core.py +2581 -0
  16. utilities/dataclasses.py +16 -119
  17. utilities/docker.py +387 -0
  18. utilities/enum.py +1 -1
  19. utilities/errors.py +2 -16
  20. utilities/fastapi.py +5 -5
  21. utilities/fpdf2.py +2 -1
  22. utilities/functions.py +34 -265
  23. utilities/http.py +2 -3
  24. utilities/hypothesis.py +84 -29
  25. utilities/importlib.py +17 -1
  26. utilities/iterables.py +39 -575
  27. utilities/jinja2.py +145 -0
  28. utilities/jupyter.py +5 -3
  29. utilities/libcst.py +1 -1
  30. utilities/lightweight_charts.py +4 -6
  31. utilities/logging.py +24 -24
  32. utilities/math.py +1 -36
  33. utilities/more_itertools.py +4 -6
  34. utilities/numpy.py +2 -1
  35. utilities/operator.py +2 -2
  36. utilities/orjson.py +42 -43
  37. utilities/os.py +4 -147
  38. utilities/packaging.py +129 -0
  39. utilities/parse.py +35 -15
  40. utilities/pathlib.py +3 -120
  41. utilities/platform.py +8 -90
  42. utilities/polars.py +38 -32
  43. utilities/postgres.py +37 -33
  44. utilities/pottery.py +20 -18
  45. utilities/pqdm.py +3 -4
  46. utilities/psutil.py +2 -3
  47. utilities/pydantic.py +25 -0
  48. utilities/pydantic_settings.py +87 -16
  49. utilities/pydantic_settings_sops.py +16 -3
  50. utilities/pyinstrument.py +4 -4
  51. utilities/pytest.py +96 -125
  52. utilities/pytest_plugins/pytest_regressions.py +2 -2
  53. utilities/pytest_regressions.py +32 -11
  54. utilities/random.py +2 -8
  55. utilities/redis.py +98 -94
  56. utilities/reprlib.py +11 -118
  57. utilities/shellingham.py +66 -0
  58. utilities/shutil.py +25 -0
  59. utilities/slack_sdk.py +13 -12
  60. utilities/sqlalchemy.py +57 -30
  61. utilities/sqlalchemy_polars.py +16 -25
  62. utilities/subprocess.py +2590 -0
  63. utilities/tabulate.py +32 -0
  64. utilities/testbook.py +8 -8
  65. utilities/text.py +24 -99
  66. utilities/throttle.py +159 -0
  67. utilities/time.py +18 -0
  68. utilities/timer.py +31 -14
  69. utilities/traceback.py +16 -23
  70. utilities/types.py +42 -2
  71. utilities/typing.py +26 -14
  72. utilities/uuid.py +1 -1
  73. utilities/version.py +202 -45
  74. utilities/whenever.py +53 -150
  75. dycw_utilities-0.166.30.dist-info/METADATA +0 -41
  76. dycw_utilities-0.166.30.dist-info/RECORD +0 -98
  77. dycw_utilities-0.166.30.dist-info/licenses/LICENSE +0 -21
  78. utilities/aeventkit.py +0 -388
  79. utilities/atomicwrites.py +0 -182
  80. utilities/cryptography.py +0 -41
  81. utilities/getpass.py +0 -8
  82. utilities/git.py +0 -19
  83. utilities/gzip.py +0 -31
  84. utilities/json.py +0 -70
  85. utilities/pickle.py +0 -25
  86. utilities/re.py +0 -156
  87. utilities/sentinel.py +0 -73
  88. utilities/socket.py +0 -8
  89. utilities/string.py +0 -20
  90. utilities/tempfile.py +0 -77
  91. utilities/typed_settings.py +0 -152
  92. utilities/tzdata.py +0 -11
  93. utilities/tzlocal.py +0 -28
  94. utilities/warnings.py +0 -65
  95. utilities/zipfile.py +0 -25
  96. 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.sentinel import Sentinel
10
+ from utilities.constants import Sentinel
10
11
  from utilities.types import MaybeCallable, MaybeStr
11
12
 
12
- type VersionLike = MaybeStr[Version]
13
- type MaybeCallableVersionLike = MaybeCallable[VersionLike]
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 Version:
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 _VersionZeroError(
160
+ raise _Version3ZeroError(
32
161
  major=self.major, minor=self.minor, patch=self.patch
33
162
  )
34
163
  if self.major < 0:
35
- raise _VersionNegativeMajorVersionError(major=self.major)
164
+ raise _Version3NegativeMajorVersionError(major=self.major)
36
165
  if self.minor < 0:
37
- raise _VersionNegativeMinorVersionError(minor=self.minor)
166
+ raise _Version3NegativeMinorVersionError(minor=self.minor)
38
167
  if self.patch < 0:
39
- raise _VersionNegativePatchVersionError(patch=self.patch)
168
+ raise _Version3NegativePatchVersionError(patch=self.patch)
40
169
  if (self.suffix is not None) and (len(self.suffix) == 0):
41
- raise _VersionEmptySuffixError(suffix=self.suffix)
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 VersionError(Exception): ...
230
+ class Version3Error(Exception): ...
82
231
 
83
232
 
84
233
  @dataclass(kw_only=True, slots=True)
85
- class _VersionZeroError(VersionError):
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 _VersionNegativeMajorVersionError(VersionError):
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 _VersionNegativeMinorVersionError(VersionError):
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 _VersionNegativePatchVersionError(VersionError):
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 _VersionEmptySuffixError(VersionError):
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
- def parse_version(version: str, /) -> Version:
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
- _PARSE_VERSION_PATTERN = re.compile(r"^(\d+)\.(\d+)\.(\d+)(?:-(\w+))?")
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 ParseVersionError(Exception):
150
- version: str
302
+ class ParseVersion2Or3Error(Exception):
303
+ text: str
151
304
 
152
305
  @override
153
306
  def __str__(self) -> str:
154
- return f"Invalid version string: {self.version!r}"
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 to_version(version: MaybeCallableVersionLike, /) -> Version: ...
314
+ def to_version3(version: MaybeCallableVersion3Like, /) -> Version3: ...
162
315
  @overload
163
- def to_version(version: None, /) -> None: ...
316
+ def to_version3(version: None, /) -> None: ...
164
317
  @overload
165
- def to_version(version: Sentinel, /) -> Sentinel: ...
166
- def to_version(
167
- version: MaybeCallableVersionLike | None | Sentinel, /
168
- ) -> Version | None | Sentinel:
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 Version() | None | Sentinel():
324
+ case Version3() | None | Sentinel():
172
325
  return version
173
326
  case str():
174
- return parse_version(version)
327
+ return Version3.parse(version)
175
328
  case Callable() as func:
176
- return to_version(func())
329
+ return to_version3(func())
177
330
  case never:
178
331
  assert_never(never)
179
332
 
180
333
 
181
334
  ##
182
335
  __all__ = [
183
- "MaybeCallableVersionLike",
184
- "ParseVersionError",
185
- "Version",
186
- "VersionError",
187
- "VersionLike",
188
- "parse_version",
189
- "to_version",
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
  ]