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.
- {dycw_utilities-0.131.7.dist-info → dycw_utilities-0.131.9.dist-info}/METADATA +1 -1
- {dycw_utilities-0.131.7.dist-info → dycw_utilities-0.131.9.dist-info}/RECORD +10 -10
- utilities/__init__.py +1 -1
- utilities/asyncio.py +6 -2
- utilities/re.py +51 -20
- utilities/timer.py +31 -65
- utilities/types.py +6 -0
- utilities/whenever2.py +1 -1
- {dycw_utilities-0.131.7.dist-info → dycw_utilities-0.131.9.dist-info}/WHEEL +0 -0
- {dycw_utilities-0.131.7.dist-info → dycw_utilities-0.131.9.dist-info}/licenses/LICENSE +0 -0
@@ -1,7 +1,7 @@
|
|
1
|
-
utilities/__init__.py,sha256=
|
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=
|
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=
|
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=
|
81
|
+
utilities/timer.py,sha256=VeSl3ot8-f4D1d3HjjSsgKvjxHJGXd_sW4KcTExOR64,2475
|
82
82
|
utilities/traceback.py,sha256=l9onlqDdW5GCSIFbU_-htBE7KlsvsJdNtGrK6-k0RCQ,8759
|
83
|
-
utilities/types.py,sha256=
|
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=
|
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.
|
95
|
-
dycw_utilities-0.131.
|
96
|
-
dycw_utilities-0.131.
|
97
|
-
dycw_utilities-0.131.
|
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
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
|
-
|
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
|
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
|
-
|
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
|
-
|
16
|
-
match
|
33
|
+
pattern_use = ensure_pattern(pattern, flags=flags)
|
34
|
+
match pattern_use.groups:
|
17
35
|
case 0:
|
18
|
-
raise _ExtractGroupNoCaptureGroupsError(pattern=
|
36
|
+
raise _ExtractGroupNoCaptureGroupsError(pattern=pattern_use, text=text)
|
19
37
|
case 1:
|
20
|
-
matches: list[str] =
|
38
|
+
matches: list[str] = pattern_use.findall(text)
|
21
39
|
try:
|
22
40
|
return one(matches)
|
23
41
|
except OneEmptyError:
|
24
|
-
raise _ExtractGroupNoMatchesError(
|
42
|
+
raise _ExtractGroupNoMatchesError(
|
43
|
+
pattern=pattern_use, text=text
|
44
|
+
) from None
|
25
45
|
except OneNonUniqueError:
|
26
46
|
raise _ExtractGroupMultipleMatchesError(
|
27
|
-
pattern=
|
47
|
+
pattern=pattern_use, text=text, matches=matches
|
28
48
|
) from None
|
29
49
|
case _:
|
30
|
-
raise _ExtractGroupMultipleCaptureGroupsError(
|
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
|
-
|
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
|
-
|
78
|
-
if (n_groups :=
|
79
|
-
raise _ExtractGroupsNoCaptureGroupsError(pattern=
|
80
|
-
matches: list[str] =
|
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=
|
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=
|
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__ = [
|
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
|
6
|
-
|
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
|
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 =
|
20
|
-
self._end:
|
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) ->
|
25
|
-
|
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
|
-
|
33
|
-
|
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
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
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 =
|
42
|
+
self._start = get_now_local()
|
62
43
|
return self
|
63
44
|
|
64
45
|
def __exit__(self, *_: object) -> bool:
|
65
|
-
self._end =
|
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
|
53
|
+
return self.timedelta.format_common_iso()
|
73
54
|
|
74
55
|
@override
|
75
56
|
def __str__(self) -> str:
|
76
|
-
return
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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) ->
|
84
|
+
def timedelta(self) -> TimeDelta:
|
116
85
|
"""The elapsed time, as a `timedelta` object."""
|
117
|
-
|
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
|
-
|
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
File without changes
|
File without changes
|