dycw-utilities 0.175.17__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 (94) 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.175.17.dist-info → dycw_utilities-0.185.8.dist-info}/WHEEL +2 -2
  4. utilities/__init__.py +1 -1
  5. utilities/altair.py +8 -6
  6. utilities/asyncio.py +40 -56
  7. utilities/atools.py +9 -11
  8. utilities/cachetools.py +8 -6
  9. utilities/click.py +4 -3
  10. utilities/concurrent.py +1 -1
  11. utilities/constants.py +492 -0
  12. utilities/contextlib.py +23 -30
  13. utilities/contextvars.py +1 -23
  14. utilities/core.py +2581 -0
  15. utilities/dataclasses.py +16 -119
  16. utilities/docker.py +139 -45
  17. utilities/enum.py +1 -1
  18. utilities/errors.py +2 -16
  19. utilities/fastapi.py +5 -5
  20. utilities/fpdf2.py +2 -1
  21. utilities/functions.py +33 -264
  22. utilities/http.py +2 -3
  23. utilities/hypothesis.py +48 -25
  24. utilities/iterables.py +39 -575
  25. utilities/jinja2.py +3 -6
  26. utilities/jupyter.py +5 -3
  27. utilities/libcst.py +1 -1
  28. utilities/lightweight_charts.py +4 -6
  29. utilities/logging.py +17 -15
  30. utilities/math.py +1 -36
  31. utilities/more_itertools.py +4 -6
  32. utilities/numpy.py +2 -1
  33. utilities/operator.py +2 -2
  34. utilities/orjson.py +24 -25
  35. utilities/os.py +4 -185
  36. utilities/packaging.py +129 -0
  37. utilities/parse.py +33 -13
  38. utilities/pathlib.py +2 -136
  39. utilities/platform.py +8 -90
  40. utilities/polars.py +34 -31
  41. utilities/postgres.py +9 -4
  42. utilities/pottery.py +20 -18
  43. utilities/pqdm.py +3 -4
  44. utilities/psutil.py +2 -3
  45. utilities/pydantic.py +18 -4
  46. utilities/pydantic_settings.py +7 -9
  47. utilities/pydantic_settings_sops.py +3 -3
  48. utilities/pyinstrument.py +4 -4
  49. utilities/pytest.py +49 -108
  50. utilities/pytest_plugins/pytest_regressions.py +2 -2
  51. utilities/pytest_regressions.py +8 -6
  52. utilities/random.py +2 -8
  53. utilities/redis.py +98 -94
  54. utilities/reprlib.py +11 -118
  55. utilities/shellingham.py +66 -0
  56. utilities/slack_sdk.py +13 -12
  57. utilities/sqlalchemy.py +42 -30
  58. utilities/sqlalchemy_polars.py +16 -25
  59. utilities/subprocess.py +1166 -148
  60. utilities/tabulate.py +32 -0
  61. utilities/testbook.py +8 -8
  62. utilities/text.py +24 -115
  63. utilities/throttle.py +159 -0
  64. utilities/time.py +18 -0
  65. utilities/timer.py +29 -12
  66. utilities/traceback.py +15 -22
  67. utilities/types.py +38 -3
  68. utilities/typing.py +18 -12
  69. utilities/uuid.py +1 -1
  70. utilities/version.py +202 -45
  71. utilities/whenever.py +22 -150
  72. dycw_utilities-0.175.17.dist-info/METADATA +0 -34
  73. dycw_utilities-0.175.17.dist-info/RECORD +0 -103
  74. utilities/atomicwrites.py +0 -182
  75. utilities/cryptography.py +0 -41
  76. utilities/getpass.py +0 -8
  77. utilities/git.py +0 -19
  78. utilities/grp.py +0 -28
  79. utilities/gzip.py +0 -31
  80. utilities/json.py +0 -70
  81. utilities/permissions.py +0 -298
  82. utilities/pickle.py +0 -25
  83. utilities/pwd.py +0 -28
  84. utilities/re.py +0 -156
  85. utilities/sentinel.py +0 -73
  86. utilities/socket.py +0 -8
  87. utilities/string.py +0 -20
  88. utilities/tempfile.py +0 -136
  89. utilities/tzdata.py +0 -11
  90. utilities/tzlocal.py +0 -28
  91. utilities/warnings.py +0 -65
  92. utilities/zipfile.py +0 -25
  93. utilities/zoneinfo.py +0 -133
  94. {dycw_utilities-0.175.17.dist-info → dycw_utilities-0.185.8.dist-info}/entry_points.txt +0 -0
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
  ]
utilities/whenever.py CHANGED
@@ -32,13 +32,18 @@ from whenever import (
32
32
  ZonedDateTime,
33
33
  )
34
34
 
35
- from utilities.dataclasses import replace_non_sentinel
35
+ from utilities.constants import LOCAL_TIME_ZONE_NAME, UTC, Sentinel, sentinel
36
+ from utilities.core import (
37
+ get_now,
38
+ get_now_local,
39
+ get_time,
40
+ get_today,
41
+ replace_non_sentinel,
42
+ to_time_zone_name,
43
+ )
36
44
  from utilities.functions import get_class_name
37
45
  from utilities.math import sign
38
46
  from utilities.platform import get_strftime
39
- from utilities.sentinel import Sentinel, sentinel
40
- from utilities.tzlocal import LOCAL_TIME_ZONE, LOCAL_TIME_ZONE_NAME
41
- from utilities.zoneinfo import UTC, to_time_zone_name
42
47
 
43
48
  if TYPE_CHECKING:
44
49
  from utilities.types import (
@@ -53,13 +58,6 @@ if TYPE_CHECKING:
53
58
  )
54
59
 
55
60
 
56
- # bounds
57
-
58
-
59
- ZONED_DATE_TIME_MIN = PlainDateTime.MIN.assume_tz(UTC.key)
60
- ZONED_DATE_TIME_MAX = PlainDateTime.MAX.assume_tz(UTC.key)
61
-
62
-
63
61
  DATE_TIME_DELTA_MIN = DateTimeDelta(
64
62
  weeks=-521722,
65
63
  days=-5,
@@ -80,10 +78,6 @@ DATE_TIME_DELTA_MAX = DateTimeDelta(
80
78
  microseconds=999,
81
79
  nanoseconds=999,
82
80
  )
83
- DATE_DELTA_MIN = DATE_TIME_DELTA_MIN.date_part()
84
- DATE_DELTA_MAX = DATE_TIME_DELTA_MAX.date_part()
85
- TIME_DELTA_MIN = TimeDelta(hours=-87831216)
86
- TIME_DELTA_MAX = TimeDelta(hours=87831216)
87
81
 
88
82
 
89
83
  DATE_TIME_DELTA_PARSABLE_MIN = DateTimeDelta(
@@ -115,22 +109,6 @@ DATE_TWO_DIGIT_YEAR_MAX = Date(DATE_TWO_DIGIT_YEAR_MIN.year + 99, 12, 31)
115
109
  ## common constants
116
110
 
117
111
 
118
- ZERO_DAYS = DateDelta()
119
- ZERO_TIME = TimeDelta()
120
- MICROSECOND = TimeDelta(microseconds=1)
121
- MILLISECOND = TimeDelta(milliseconds=1)
122
- SECOND = TimeDelta(seconds=1)
123
- MINUTE = TimeDelta(minutes=1)
124
- HOUR = TimeDelta(hours=1)
125
- DAY = DateDelta(days=1)
126
- WEEK = DateDelta(weeks=1)
127
- MONTH = DateDelta(months=1)
128
- YEAR = DateDelta(years=1)
129
-
130
-
131
- ##
132
-
133
-
134
112
  def add_year_month(x: YearMonth, /, *, years: int = 0, months: int = 0) -> YearMonth:
135
113
  """Add to a year-month."""
136
114
  y = x.on_day(1) + DateDelta(years=years, months=months)
@@ -258,6 +236,7 @@ def datetime_utc(
258
236
  month: int,
259
237
  day: int,
260
238
  /,
239
+ *,
261
240
  hour: int = 0,
262
241
  minute: int = 0,
263
242
  second: int = 0,
@@ -360,79 +339,6 @@ def from_timestamp_nanos(i: int, /, *, time_zone: TimeZoneLike = UTC) -> ZonedDa
360
339
  ##
361
340
 
362
341
 
363
- def get_now(time_zone: TimeZoneLike = UTC, /) -> ZonedDateTime:
364
- """Get the current zoned date-time."""
365
- return ZonedDateTime.now(to_time_zone_name(time_zone))
366
-
367
-
368
- NOW_UTC = get_now(UTC)
369
-
370
-
371
- def get_now_local() -> ZonedDateTime:
372
- """Get the current zoned date-time in the local time-zone."""
373
- return get_now(LOCAL_TIME_ZONE)
374
-
375
-
376
- NOW_LOCAL = get_now_local()
377
-
378
-
379
- def get_now_plain(time_zone: TimeZoneLike = UTC, /) -> PlainDateTime:
380
- """Get the current plain date-time."""
381
- return get_now(time_zone).to_plain()
382
-
383
-
384
- NOW_PLAIN = get_now_plain()
385
-
386
-
387
- def get_now_local_plain() -> PlainDateTime:
388
- """Get the current plain date-time in the local time-zone."""
389
- return get_now_local().to_plain()
390
-
391
-
392
- NOW_LOCAL_PLAIN = get_now_local_plain()
393
-
394
-
395
- ##
396
-
397
-
398
- def get_time(time_zone: TimeZoneLike = UTC, /) -> Time:
399
- """Get the current time."""
400
- return get_now(time_zone).time()
401
-
402
-
403
- TIME_UTC = get_time(UTC)
404
-
405
-
406
- def get_time_local() -> Time:
407
- """Get the current time in the local time-zone."""
408
- return get_time(LOCAL_TIME_ZONE)
409
-
410
-
411
- TIME_LOCAL = get_time_local()
412
-
413
-
414
- ##
415
-
416
-
417
- def get_today(time_zone: TimeZoneLike = UTC, /) -> Date:
418
- """Get the current, timezone-aware local date."""
419
- return get_now(time_zone).date()
420
-
421
-
422
- TODAY_UTC = get_today(UTC)
423
-
424
-
425
- def get_today_local() -> Date:
426
- """Get the current, timezone-aware local date."""
427
- return get_today(LOCAL_TIME_ZONE)
428
-
429
-
430
- TODAY_LOCAL = get_today_local()
431
-
432
-
433
- ##
434
-
435
-
436
342
  def is_weekend(
437
343
  date_time: ZonedDateTime,
438
344
  /,
@@ -618,7 +524,7 @@ def _round_datetime_decompose(delta: Delta, /) -> tuple[int, _RoundDateOrDateTim
618
524
  if (0 < hours < 24) and (24 % hours == 0):
619
525
  return hours, "H"
620
526
  raise _RoundDateOrDateTimeIncrementError(
621
- duration=delta, increment=hours, divisor=24
527
+ delta=delta, increment=hours, divisor=24
622
528
  )
623
529
  try:
624
530
  minutes = to_minutes(delta)
@@ -628,7 +534,7 @@ def _round_datetime_decompose(delta: Delta, /) -> tuple[int, _RoundDateOrDateTim
628
534
  if (0 < minutes < 60) and (60 % minutes == 0):
629
535
  return minutes, "M"
630
536
  raise _RoundDateOrDateTimeIncrementError(
631
- duration=delta, increment=minutes, divisor=60
537
+ delta=delta, increment=minutes, divisor=60
632
538
  )
633
539
  try:
634
540
  seconds = to_seconds(delta)
@@ -638,7 +544,7 @@ def _round_datetime_decompose(delta: Delta, /) -> tuple[int, _RoundDateOrDateTim
638
544
  if (0 < seconds < 60) and (60 % seconds == 0):
639
545
  return seconds, "S"
640
546
  raise _RoundDateOrDateTimeIncrementError(
641
- duration=delta, increment=seconds, divisor=60
547
+ delta=delta, increment=seconds, divisor=60
642
548
  )
643
549
  try:
644
550
  milliseconds = to_milliseconds(delta)
@@ -648,7 +554,7 @@ def _round_datetime_decompose(delta: Delta, /) -> tuple[int, _RoundDateOrDateTim
648
554
  if (0 < milliseconds < 1000) and (1000 % milliseconds == 0):
649
555
  return milliseconds, "ms"
650
556
  raise _RoundDateOrDateTimeIncrementError(
651
- duration=delta, increment=milliseconds, divisor=1000
557
+ delta=delta, increment=milliseconds, divisor=1000
652
558
  )
653
559
  try:
654
560
  microseconds = to_microseconds(delta)
@@ -658,16 +564,16 @@ def _round_datetime_decompose(delta: Delta, /) -> tuple[int, _RoundDateOrDateTim
658
564
  if (0 < microseconds < 1000) and (1000 % microseconds == 0):
659
565
  return microseconds, "us"
660
566
  raise _RoundDateOrDateTimeIncrementError(
661
- duration=delta, increment=microseconds, divisor=1000
567
+ delta=delta, increment=microseconds, divisor=1000
662
568
  )
663
569
  try:
664
570
  nanoseconds = to_nanoseconds(delta)
665
571
  except ToNanosecondsError:
666
- raise _RoundDateOrDateTimeInvalidDurationError(duration=delta) from None
572
+ raise _RoundDateOrDateTimeInvalidDeltaError(delta=delta) from None
667
573
  if (0 < nanoseconds < 1000) and (1000 % nanoseconds == 0):
668
574
  return nanoseconds, "ns"
669
575
  raise _RoundDateOrDateTimeIncrementError(
670
- duration=delta, increment=nanoseconds, divisor=1000
576
+ delta=delta, increment=nanoseconds, divisor=1000
671
577
  )
672
578
 
673
579
 
@@ -787,22 +693,22 @@ class RoundDateOrDateTimeError(Exception): ...
787
693
 
788
694
  @dataclass(kw_only=True, slots=True)
789
695
  class _RoundDateOrDateTimeIncrementError(RoundDateOrDateTimeError):
790
- duration: Delta
696
+ delta: Delta
791
697
  increment: int
792
698
  divisor: int
793
699
 
794
700
  @override
795
701
  def __str__(self) -> str:
796
- return f"Duration {self.duration} increment must be a proper divisor of {self.divisor}; got {self.increment}"
702
+ return f"Delta {self.delta} increment must be a proper divisor of {self.divisor}; got {self.increment}"
797
703
 
798
704
 
799
705
  @dataclass(kw_only=True, slots=True)
800
- class _RoundDateOrDateTimeInvalidDurationError(RoundDateOrDateTimeError):
801
- duration: Delta
706
+ class _RoundDateOrDateTimeInvalidDeltaError(RoundDateOrDateTimeError):
707
+ delta: Delta
802
708
 
803
709
  @override
804
710
  def __str__(self) -> str:
805
- return f"Duration must be valid; got {self.duration}"
711
+ return f"Delta must be valid; got {self.delta}"
806
712
 
807
713
 
808
714
  @dataclass(kw_only=True, slots=True)
@@ -1997,38 +1903,12 @@ class _ZonedDateTimePeriodExactEqError(ZonedDateTimePeriodError):
1997
1903
 
1998
1904
 
1999
1905
  __all__ = [
2000
- "DATE_DELTA_MAX",
2001
- "DATE_DELTA_MIN",
2002
1906
  "DATE_DELTA_PARSABLE_MAX",
2003
1907
  "DATE_DELTA_PARSABLE_MIN",
2004
- "DATE_TIME_DELTA_MAX",
2005
- "DATE_TIME_DELTA_MIN",
2006
1908
  "DATE_TIME_DELTA_PARSABLE_MAX",
2007
1909
  "DATE_TIME_DELTA_PARSABLE_MIN",
2008
1910
  "DATE_TWO_DIGIT_YEAR_MAX",
2009
1911
  "DATE_TWO_DIGIT_YEAR_MIN",
2010
- "DAY",
2011
- "HOUR",
2012
- "MICROSECOND",
2013
- "MILLISECOND",
2014
- "MINUTE",
2015
- "MONTH",
2016
- "NOW_LOCAL",
2017
- "NOW_LOCAL_PLAIN",
2018
- "NOW_PLAIN",
2019
- "SECOND",
2020
- "TIME_DELTA_MAX",
2021
- "TIME_DELTA_MIN",
2022
- "TIME_LOCAL",
2023
- "TIME_UTC",
2024
- "TODAY_LOCAL",
2025
- "TODAY_UTC",
2026
- "WEEK",
2027
- "YEAR",
2028
- "ZERO_DAYS",
2029
- "ZERO_TIME",
2030
- "ZONED_DATE_TIME_MAX",
2031
- "ZONED_DATE_TIME_MIN",
2032
1912
  "DatePeriod",
2033
1913
  "DatePeriodError",
2034
1914
  "MeanDateTimeError",
@@ -2055,14 +1935,6 @@ __all__ = [
2055
1935
  "from_timestamp",
2056
1936
  "from_timestamp_millis",
2057
1937
  "from_timestamp_nanos",
2058
- "get_now",
2059
- "get_now_local",
2060
- "get_now_local_plain",
2061
- "get_now_plain",
2062
- "get_time",
2063
- "get_time_local",
2064
- "get_today",
2065
- "get_today_local",
2066
1938
  "is_weekend",
2067
1939
  "mean_datetime",
2068
1940
  "min_max_date",
@@ -1,34 +0,0 @@
1
- Metadata-Version: 2.3
2
- Name: dycw-utilities
3
- Version: 0.175.17
4
- Summary: Miscellaneous Python utilities
5
- Author: Derek Wan
6
- Author-email: Derek Wan <d.wan@icloud.com>
7
- Requires-Dist: atomicwrites>=1.4.1,<1.5
8
- Requires-Dist: typing-extensions>=4.15.0,<4.16
9
- Requires-Dist: tzlocal>=5.3.1,<5.4
10
- Requires-Dist: whenever>=0.9.4,<0.10
11
- Requires-Dist: coloredlogs>=15.0.1,<15.1 ; extra == 'logging'
12
- Requires-Dist: dycw-pytest-only>=2.1.1,<2.2 ; extra == 'test'
13
- Requires-Dist: hypothesis>=6.148.8,<6.149 ; extra == 'test'
14
- Requires-Dist: pytest>=9.0.2,<9.1 ; extra == 'test'
15
- Requires-Dist: pytest-asyncio>=1.3.0,<1.4 ; extra == 'test'
16
- Requires-Dist: pytest-cov>=7.0.0,<7.1 ; extra == 'test'
17
- Requires-Dist: pytest-instafail>=0.5.0,<0.6 ; extra == 'test'
18
- Requires-Dist: pytest-lazy-fixtures>=1.4.0,<1.5 ; extra == 'test'
19
- Requires-Dist: pytest-randomly>=4.0.1,<4.1 ; extra == 'test'
20
- Requires-Dist: pytest-regressions>=2.8.3,<2.9 ; extra == 'test'
21
- Requires-Dist: pytest-repeat>=0.9.4,<0.10 ; extra == 'test'
22
- Requires-Dist: pytest-rerunfailures>=16.1,<16.2 ; extra == 'test'
23
- Requires-Dist: pytest-rng>=1.0.0,<1.1 ; extra == 'test'
24
- Requires-Dist: pytest-timeout>=2.4.0,<2.5 ; extra == 'test'
25
- Requires-Dist: pytest-xdist>=3.8.0,<3.9 ; extra == 'test'
26
- Requires-Dist: testbook>=0.4.2,<0.5 ; extra == 'test'
27
- Requires-Python: >=3.12
28
- Provides-Extra: logging
29
- Provides-Extra: test
30
- Description-Content-Type: text/markdown
31
-
32
- # `python-utilities`
33
-
34
- Miscellaneous Python utilities