dycw-utilities 0.131.7__py3-none-any.whl → 0.131.9__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.131.7
3
+ Version: 0.131.9
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -1,7 +1,7 @@
1
- utilities/__init__.py,sha256=8vYsYB_QS775vHWdWRbA5PoQsxv9xMVXwDnzye5-mJc,60
1
+ utilities/__init__.py,sha256=OQKoBd8NcREzug1yqBz40CfEPg5mv9S_IlwEwGMga6c,60
2
2
  utilities/aiolimiter.py,sha256=mD0wEiqMgwpty4XTbawFpnkkmJS6R4JRsVXFUaoitSU,628
3
3
  utilities/altair.py,sha256=HeZBVUocjkrTNwwKrClppsIqgNFF-ykv05HfZSoHYno,9104
4
- utilities/asyncio.py,sha256=lvdgBhuMtxq0dpiwF9g2WMMrit3kqXibN1V5NZ4xdbo,38046
4
+ utilities/asyncio.py,sha256=yfKvAIDCRrWdyQMVZMo4DJQx4nVrXoAcqwhNuF95Ryo,38186
5
5
  utilities/atomicwrites.py,sha256=geFjn9Pwn-tTrtoGjDDxWli9NqbYfy3gGL6ZBctiqSo,5393
6
6
  utilities/atools.py,sha256=IYMuFSFGSKyuQmqD6v5IUtDlz8PPw0Sr87Cub_gRU3M,1168
7
7
  utilities/cachetools.py,sha256=C1zqOg7BYz0IfQFK8e3qaDDgEZxDpo47F15RTfJM37Q,2910
@@ -61,7 +61,7 @@ utilities/pytest.py,sha256=zP4CWKXpRVk4aRDRxolUAvqQwX7wgDO8lzmkQfuZaZo,7832
61
61
  utilities/pytest_regressions.py,sha256=YI55B7EtLjhz7zPJZ6NK9bWrxrKCKabWZJe1cwcbA5o,5082
62
62
  utilities/python_dotenv.py,sha256=edXsvHZhZnYeqfMfrsRRpj7_9eJI6uizh3xLx8Q9B3w,3228
63
63
  utilities/random.py,sha256=lYdjgxB7GCfU_fwFVl5U-BIM_HV3q6_urL9byjrwDM8,4157
64
- utilities/re.py,sha256=5J4d8VwIPFVrX2Eb8zfoxImDv7IwiN_U7mJ07wR2Wvs,3958
64
+ utilities/re.py,sha256=J7pJOn-Zn5nJ6rvWwMeJWP86EtMk_klhBexnoSaQiZA,4610
65
65
  utilities/redis.py,sha256=IceT5EjgrebVkGL8X3M35xlqjI2c7zFbyV1P4dExN4M,36037
66
66
  utilities/reprlib.py,sha256=ssYTcBW-TeRh3fhCJv57sopTZHF5FrPyyUg9yp5XBlo,3953
67
67
  utilities/scipy.py,sha256=wZJM7fEgBAkLSYYvSmsg5ac-QuwAI0BGqHVetw1_Hb0,947
@@ -78,9 +78,9 @@ utilities/tempfile.py,sha256=VqmZJAhTJ1OaVywFzk5eqROV8iJbW9XQ_QYAV0bpdRo,1384
78
78
  utilities/tenacity.py,sha256=1PUvODiBVgeqIh7G5TRt5WWMSqjLYkEqP53itT97WQc,4914
79
79
  utilities/text.py,sha256=ymBFlP_cA8OgNnZRVNs7FAh7OG8HxE6YkiLEMZv5g_A,11297
80
80
  utilities/threading.py,sha256=GvBOp4CyhHfN90wGXZuA2VKe9fGzMaEa7oCl4f3nnPU,1009
81
- utilities/timer.py,sha256=Rkc49KSpHuC8s7vUxGO9DU55U9I6yDKnchsQqrUCVBs,4075
81
+ utilities/timer.py,sha256=VeSl3ot8-f4D1d3HjjSsgKvjxHJGXd_sW4KcTExOR64,2475
82
82
  utilities/traceback.py,sha256=l9onlqDdW5GCSIFbU_-htBE7KlsvsJdNtGrK6-k0RCQ,8759
83
- utilities/types.py,sha256=gP04CcCOyFrG7BgblVCsrrChiuO2x842NDVW-GF7odo,18370
83
+ utilities/types.py,sha256=Ubd3VBiqZ71eS71WSemHEM4oRF-5fnwd3PnMAm8IZaI,18461
84
84
  utilities/typing.py,sha256=kQWywPcRbFBKmvQBELmgbiqSHsnlo_D0ru53vl6KDeY,13846
85
85
  utilities/tzdata.py,sha256=yCf70NICwAeazN3_JcXhWvRqCy06XJNQ42j7r6gw3HY,1217
86
86
  utilities/tzlocal.py,sha256=P5BjqTiYskeCwjE7i9zycCFXO4MWdZgYCh4jut-LpzA,1042
@@ -88,10 +88,10 @@ utilities/uuid.py,sha256=jJTFxz-CWgltqNuzmythB7iEQ-Q1mCwPevUfKthZT3c,611
88
88
  utilities/version.py,sha256=ufhJMmI6KPs1-3wBI71aj5wCukd3sP_m11usLe88DNA,5117
89
89
  utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
90
90
  utilities/whenever.py,sha256=jS31ZAY5OMxFxLja_Yo5Fidi87Pd-GoVZ7Vi_teqVDA,16743
91
- utilities/whenever2.py,sha256=XxiAEp4onJNhIi6G7L2HuzkMJZGA3j-I_fuXxmb5vtY,5432
91
+ utilities/whenever2.py,sha256=JHixGl6KibK8GUF13GeBjvWMYsFHRDSXixSo0xMSJFM,5437
92
92
  utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
93
93
  utilities/zoneinfo.py,sha256=tvcgu3QzDxe2suTexi2QzRGpin7VK1TjHa0JYYxT69I,1862
94
- dycw_utilities-0.131.7.dist-info/METADATA,sha256=LY97kj9T58_Fg3RF7GpooTy1HfTHyI0XV3PMZnOEw9E,1584
95
- dycw_utilities-0.131.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
96
- dycw_utilities-0.131.7.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
97
- dycw_utilities-0.131.7.dist-info/RECORD,,
94
+ dycw_utilities-0.131.9.dist-info/METADATA,sha256=Ot-6Wm0MCFNJh_vmaEvMVM89IHtiCDBrKiq267xA9bQ,1584
95
+ dycw_utilities-0.131.9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
96
+ dycw_utilities-0.131.9.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
97
+ dycw_utilities-0.131.9.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.131.7"
3
+ __version__ = "0.131.9"
utilities/asyncio.py CHANGED
@@ -43,6 +43,7 @@ from typing import (
43
43
  )
44
44
 
45
45
  from typing_extensions import deprecated
46
+ from whenever import TimeDelta
46
47
 
47
48
  from utilities.dataclasses import replace_non_sentinel
48
49
  from utilities.datetime import (
@@ -961,11 +962,14 @@ def put_items_nowait(items: Iterable[_T], queue: Queue[_T], /) -> None:
961
962
  ##
962
963
 
963
964
 
964
- async def sleep_dur(*, duration: Duration | None = None) -> None:
965
+ async def sleep_dur(*, duration: Duration | TimeDelta | None = None) -> None:
965
966
  """Sleep which accepts durations."""
966
967
  if duration is None:
967
968
  return
968
- await sleep(datetime_duration_to_float(duration))
969
+ if isinstance(duration, TimeDelta):
970
+ await sleep(duration.in_seconds())
971
+ else:
972
+ await sleep(datetime_duration_to_float(duration))
969
973
 
970
974
 
971
975
  ##
utilities/re.py CHANGED
@@ -2,37 +2,59 @@ from __future__ import annotations
2
2
 
3
3
  import re
4
4
  from dataclasses import dataclass
5
- from typing import override
5
+ from re import Pattern
6
+ from typing import TYPE_CHECKING, assert_never, override
6
7
 
7
8
  from utilities.iterables import OneEmptyError, OneNonUniqueError, one
8
9
 
10
+ if TYPE_CHECKING:
11
+ from utilities.types import PatternLike
9
12
 
10
- def extract_group(pattern: str, text: str, /, *, flags: int = 0) -> str:
13
+
14
+ def ensure_pattern(pattern: PatternLike, /, *, flags: int = 0) -> Pattern[str]:
15
+ """Ensure a pattern is returned."""
16
+ match pattern:
17
+ case Pattern():
18
+ return pattern
19
+ case str():
20
+ return re.compile(pattern, flags=flags)
21
+ case _ as never:
22
+ assert_never(never)
23
+
24
+
25
+ ##
26
+
27
+
28
+ def extract_group(pattern: PatternLike, text: str, /, *, flags: int = 0) -> str:
11
29
  """Extract a group.
12
30
 
13
31
  The regex must have 1 capture group, and this must match exactly once.
14
32
  """
15
- compiled = re.compile(pattern, flags=flags)
16
- match compiled.groups:
33
+ pattern_use = ensure_pattern(pattern, flags=flags)
34
+ match pattern_use.groups:
17
35
  case 0:
18
- raise _ExtractGroupNoCaptureGroupsError(pattern=pattern, text=text)
36
+ raise _ExtractGroupNoCaptureGroupsError(pattern=pattern_use, text=text)
19
37
  case 1:
20
- matches: list[str] = compiled.findall(text)
38
+ matches: list[str] = pattern_use.findall(text)
21
39
  try:
22
40
  return one(matches)
23
41
  except OneEmptyError:
24
- raise _ExtractGroupNoMatchesError(pattern=pattern, text=text) from None
42
+ raise _ExtractGroupNoMatchesError(
43
+ pattern=pattern_use, text=text
44
+ ) from None
25
45
  except OneNonUniqueError:
26
46
  raise _ExtractGroupMultipleMatchesError(
27
- pattern=pattern, text=text, matches=matches
47
+ pattern=pattern_use, text=text, matches=matches
28
48
  ) from None
29
49
  case _:
30
- raise _ExtractGroupMultipleCaptureGroupsError(pattern=pattern, text=text)
50
+ raise _ExtractGroupMultipleCaptureGroupsError(
51
+ pattern=pattern_use, text=text
52
+ )
31
53
 
32
54
 
33
55
  @dataclass(kw_only=True, slots=True)
34
56
  class ExtractGroupError(Exception):
35
- pattern: str
57
+ pattern: Pattern[str]
36
58
  text: str
37
59
 
38
60
 
@@ -68,32 +90,35 @@ class _ExtractGroupNoMatchesError(ExtractGroupError):
68
90
  return f"Pattern {self.pattern} must match against {self.text}"
69
91
 
70
92
 
71
- def extract_groups(pattern: str, text: str, /, *, flags: int = 0) -> list[str]:
93
+ ##
94
+
95
+
96
+ def extract_groups(pattern: PatternLike, text: str, /, *, flags: int = 0) -> list[str]:
72
97
  """Extract multiple groups.
73
98
 
74
99
  The regex may have any number of capture groups, and they must collectively
75
100
  match exactly once.
76
101
  """
77
- compiled = re.compile(pattern, flags=flags)
78
- if (n_groups := compiled.groups) == 0:
79
- raise _ExtractGroupsNoCaptureGroupsError(pattern=pattern, text=text)
80
- matches: list[str] = compiled.findall(text)
102
+ pattern_use = ensure_pattern(pattern, flags=flags)
103
+ if (n_groups := pattern_use.groups) == 0:
104
+ raise _ExtractGroupsNoCaptureGroupsError(pattern=pattern_use, text=text)
105
+ matches: list[str] = pattern_use.findall(text)
81
106
  match len(matches), n_groups:
82
107
  case 0, _:
83
- raise _ExtractGroupsNoMatchesError(pattern=pattern, text=text)
108
+ raise _ExtractGroupsNoMatchesError(pattern=pattern_use, text=text)
84
109
  case 1, 1:
85
110
  return matches
86
111
  case 1, _:
87
112
  return list(one(matches))
88
113
  case _:
89
114
  raise _ExtractGroupsMultipleMatchesError(
90
- pattern=pattern, text=text, matches=matches
115
+ pattern=pattern_use, text=text, matches=matches
91
116
  )
92
117
 
93
118
 
94
119
  @dataclass(kw_only=True, slots=True)
95
120
  class ExtractGroupsError(Exception):
96
- pattern: str
121
+ pattern: Pattern[str]
97
122
  text: str
98
123
 
99
124
 
@@ -108,7 +133,7 @@ class _ExtractGroupsMultipleMatchesError(ExtractGroupsError):
108
133
 
109
134
  @dataclass(kw_only=True, slots=True)
110
135
  class _ExtractGroupsNoCaptureGroupsError(ExtractGroupsError):
111
- pattern: str
136
+ pattern: Pattern[str]
112
137
  text: str
113
138
 
114
139
  @override
@@ -123,4 +148,10 @@ class _ExtractGroupsNoMatchesError(ExtractGroupsError):
123
148
  return f"Pattern {self.pattern} must match against {self.text}"
124
149
 
125
150
 
126
- __all__ = ["ExtractGroupError", "ExtractGroupsError", "extract_group", "extract_groups"]
151
+ __all__ = [
152
+ "ExtractGroupError",
153
+ "ExtractGroupsError",
154
+ "ensure_pattern",
155
+ "extract_group",
156
+ "extract_groups",
157
+ ]
utilities/timer.py CHANGED
@@ -1,14 +1,14 @@
1
1
  from __future__ import annotations
2
2
 
3
- import datetime as dt
4
3
  from operator import add, eq, ge, gt, le, lt, mul, ne, sub, truediv
5
- from timeit import default_timer
6
- from typing import TYPE_CHECKING, Any, Self, overload, override
4
+ from typing import TYPE_CHECKING, Any, Self, override
5
+
6
+ from utilities.whenever2 import get_now_local
7
7
 
8
8
  if TYPE_CHECKING:
9
9
  from collections.abc import Callable
10
10
 
11
- from utilities.types import Number
11
+ from whenever import TimeDelta, ZonedDateTime
12
12
 
13
13
 
14
14
  class Timer:
@@ -16,116 +16,82 @@ class Timer:
16
16
 
17
17
  def __init__(self) -> None:
18
18
  super().__init__()
19
- self._start = default_timer()
20
- self._end: float | None = None
19
+ self._start: ZonedDateTime = get_now_local()
20
+ self._end: ZonedDateTime | None = None
21
21
 
22
22
  # arithmetic
23
23
 
24
- def __add__(self, other: Any) -> dt.timedelta:
25
- if isinstance(other, int | float):
26
- return dt.timedelta(seconds=self._apply_op(add, other))
27
- if isinstance(other, dt.timedelta | Timer):
28
- return self._apply_op(add, other)
29
- return NotImplemented
24
+ def __add__(self, other: Any) -> TimeDelta:
25
+ return self._apply_op(add, other)
30
26
 
31
27
  def __float__(self) -> float:
32
- end_use = default_timer() if (end := self._end) is None else end
33
- return end_use - self._start
28
+ return self.timedelta.in_seconds()
29
+
30
+ def __sub__(self, other: Any) -> TimeDelta:
31
+ return self._apply_op(sub, other)
34
32
 
35
- def __sub__(self, other: Any) -> dt.timedelta:
36
- if isinstance(other, int | float):
37
- return dt.timedelta(seconds=self._apply_op(sub, other))
38
- if isinstance(other, dt.timedelta | Timer):
39
- return self._apply_op(sub, other)
40
- return NotImplemented
41
-
42
- def __mul__(self, other: Any) -> dt.timedelta:
43
- if isinstance(other, int | float):
44
- return dt.timedelta(seconds=self._apply_op(mul, other))
45
- return NotImplemented
46
-
47
- @overload
48
- def __truediv__(self, other: Number) -> dt.timedelta: ...
49
- @overload
50
- def __truediv__(self, other: dt.timedelta | Timer) -> float: ...
51
- def __truediv__(self, other: Any) -> dt.timedelta | float:
52
- if isinstance(other, int | float):
53
- return dt.timedelta(seconds=self._apply_op(truediv, other))
54
- if isinstance(other, dt.timedelta | Timer):
55
- return self._apply_op(truediv, other)
56
- return NotImplemented
33
+ def __mul__(self, other: Any) -> TimeDelta:
34
+ return self._apply_op(mul, other)
35
+
36
+ def __truediv__(self, other: Any) -> TimeDelta:
37
+ return self._apply_op(truediv, other)
57
38
 
58
39
  # context manager
59
40
 
60
41
  def __enter__(self) -> Self:
61
- self._start = default_timer()
42
+ self._start = get_now_local()
62
43
  return self
63
44
 
64
45
  def __exit__(self, *_: object) -> bool:
65
- self._end = default_timer()
46
+ self._end = get_now_local()
66
47
  return False
67
48
 
68
49
  # repr
69
50
 
70
51
  @override
71
52
  def __repr__(self) -> str:
72
- return str(self.timedelta)
53
+ return self.timedelta.format_common_iso()
73
54
 
74
55
  @override
75
56
  def __str__(self) -> str:
76
- return str(self.timedelta)
57
+ return self.timedelta.format_common_iso()
77
58
 
78
59
  # comparison
79
60
 
80
61
  @override
81
62
  def __eq__(self, other: object) -> bool:
82
- if isinstance(other, int | float | dt.timedelta | Timer):
83
- return self._apply_op(eq, other)
84
- return False
63
+ return self._apply_op(eq, other)
85
64
 
86
65
  def __ge__(self, other: Any) -> bool:
87
- if isinstance(other, int | float | dt.timedelta | Timer):
88
- return self._apply_op(ge, other)
89
- return NotImplemented
66
+ return self._apply_op(ge, other)
90
67
 
91
68
  def __gt__(self, other: Any) -> bool:
92
- if isinstance(other, int | float | dt.timedelta | Timer):
93
- return self._apply_op(gt, other)
94
- return NotImplemented
69
+ return self._apply_op(gt, other)
95
70
 
96
71
  def __le__(self, other: Any) -> bool:
97
- if isinstance(other, int | float | dt.timedelta | Timer):
98
- return self._apply_op(le, other)
99
- return NotImplemented
72
+ return self._apply_op(le, other)
100
73
 
101
74
  def __lt__(self, other: Any) -> bool:
102
- if isinstance(other, int | float | dt.timedelta | Timer):
103
- return self._apply_op(lt, other)
104
- return NotImplemented
75
+ return self._apply_op(lt, other)
105
76
 
106
77
  @override
107
78
  def __ne__(self, other: object) -> bool:
108
- if isinstance(other, int | float | dt.timedelta | Timer):
109
- return self._apply_op(ne, other)
110
- return True
79
+ return self._apply_op(ne, other)
111
80
 
112
81
  # properties
113
82
 
114
83
  @property
115
- def timedelta(self) -> dt.timedelta:
84
+ def timedelta(self) -> TimeDelta:
116
85
  """The elapsed time, as a `timedelta` object."""
117
- return dt.timedelta(seconds=float(self))
86
+ end_use = get_now_local() if (end := self._end) is None else end
87
+ return end_use - self._start
118
88
 
119
89
  # private
120
90
 
121
91
  def _apply_op(self, op: Callable[[Any, Any], Any], other: Any, /) -> Any:
122
- if isinstance(other, int | float):
123
- return op(float(self), other)
124
92
  if isinstance(other, Timer):
125
93
  return op(self.timedelta, other.timedelta)
126
- if isinstance(other, dt.timedelta):
127
- return op(self.timedelta, other)
128
- return NotImplemented # pragma: no cover
94
+ return op(self.timedelta, other)
129
95
 
130
96
 
131
97
  __all__ = ["Timer"]
utilities/types.py CHANGED
@@ -7,6 +7,7 @@ from enum import Enum
7
7
  from logging import Logger
8
8
  from pathlib import Path
9
9
  from random import Random
10
+ from re import Pattern
10
11
  from types import TracebackType
11
12
  from typing import (
12
13
  Any,
@@ -249,6 +250,10 @@ type PathLike = MaybeStr[Path]
249
250
  type Seed = int | float | str | bytes | bytearray | Random
250
251
 
251
252
 
253
+ # re
254
+ type PatternLike = MaybeStr[Pattern[str]]
255
+
256
+
252
257
  # traceback
253
258
  type ExcInfo = tuple[type[BaseException], BaseException, TracebackType]
254
259
  type OptExcInfo = ExcInfo | tuple[None, None, None]
@@ -294,6 +299,7 @@ __all__ = [
294
299
  "Parallelism",
295
300
  "ParseObjectExtra",
296
301
  "PathLike",
302
+ "PatternLike",
297
303
  "RoundMode",
298
304
  "Seed",
299
305
  "SerializeObjectExtra",
utilities/whenever2.py CHANGED
@@ -53,7 +53,7 @@ DATE_DELTA_PARSABLE_MAX = DateDelta(days=999999)
53
53
  ## common constants
54
54
 
55
55
 
56
- ZERO_TIME = Time()
56
+ ZERO_TIME = TimeDelta()
57
57
  MICROSECOND = TimeDelta(microseconds=1)
58
58
  MILLISECOND = TimeDelta(milliseconds=1)
59
59
  SECOND = TimeDelta(seconds=1)