dycw-utilities 0.131.15__py3-none-any.whl → 0.131.17__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.15.dist-info → dycw_utilities-0.131.17.dist-info}/METADATA +1 -1
- {dycw_utilities-0.131.15.dist-info → dycw_utilities-0.131.17.dist-info}/RECORD +16 -17
- utilities/__init__.py +1 -1
- utilities/asyncio.py +47 -60
- utilities/click.py +128 -179
- utilities/hypothesis.py +57 -19
- utilities/pottery.py +16 -19
- utilities/psutil.py +8 -7
- utilities/redis.py +108 -116
- utilities/slack_sdk.py +17 -18
- utilities/sqlalchemy.py +36 -30
- utilities/sqlalchemy_polars.py +12 -27
- utilities/whenever.py +1 -192
- utilities/whenever2.py +209 -8
- utilities/tenacity.py +0 -145
- {dycw_utilities-0.131.15.dist-info → dycw_utilities-0.131.17.dist-info}/WHEEL +0 -0
- {dycw_utilities-0.131.15.dist-info → dycw_utilities-0.131.17.dist-info}/licenses/LICENSE +0 -0
utilities/whenever2.py
CHANGED
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
import datetime as dt
|
4
4
|
from collections.abc import Callable
|
5
|
+
from dataclasses import dataclass
|
5
6
|
from functools import cache
|
6
7
|
from logging import LogRecord
|
7
8
|
from typing import TYPE_CHECKING, Any, assert_never, overload, override
|
@@ -17,6 +18,7 @@ from whenever import (
|
|
17
18
|
)
|
18
19
|
|
19
20
|
from utilities.datetime import maybe_sub_pct_y
|
21
|
+
from utilities.math import sign
|
20
22
|
from utilities.sentinel import Sentinel, sentinel
|
21
23
|
from utilities.tzlocal import LOCAL_TIME_ZONE, LOCAL_TIME_ZONE_NAME
|
22
24
|
from utilities.zoneinfo import UTC, get_time_zone_name
|
@@ -44,16 +46,52 @@ PLAIN_DATE_TIME_MIN = PlainDateTime.from_py_datetime(dt.datetime.min) # noqa: D
|
|
44
46
|
PLAIN_DATE_TIME_MAX = PlainDateTime.from_py_datetime(dt.datetime.max) # noqa: DTZ901
|
45
47
|
ZONED_DATE_TIME_MIN = PLAIN_DATE_TIME_MIN.assume_tz(UTC.key)
|
46
48
|
ZONED_DATE_TIME_MAX = PLAIN_DATE_TIME_MAX.assume_tz(UTC.key)
|
47
|
-
DATE_TIME_DELTA_MIN = DateTimeDelta(days=-3652059, seconds=-316192377600)
|
48
|
-
DATE_TIME_DELTA_MAX = DateTimeDelta(days=3652059, seconds=316192377600)
|
49
|
-
DATE_DELTA_MIN = DATE_TIME_DELTA_MIN.date_part()
|
50
|
-
DATE_DELTA_MAX = DATE_TIME_DELTA_MAX.date_part()
|
51
|
-
TIME_DELTA_MIN = DATE_TIME_DELTA_MIN.time_part()
|
52
|
-
TIME_DELTA_MAX = DATE_TIME_DELTA_MAX.time_part()
|
53
49
|
|
54
50
|
|
55
|
-
|
56
|
-
|
51
|
+
DATE_TIME_DELTA_MIN = DateTimeDelta(
|
52
|
+
weeks=-521722,
|
53
|
+
days=-5,
|
54
|
+
hours=-23,
|
55
|
+
minutes=-59,
|
56
|
+
seconds=-59,
|
57
|
+
milliseconds=-999,
|
58
|
+
microseconds=-999,
|
59
|
+
nanoseconds=-999,
|
60
|
+
)
|
61
|
+
DATE_TIME_DELTA_MAX = DateTimeDelta(
|
62
|
+
weeks=521722,
|
63
|
+
days=5,
|
64
|
+
hours=23,
|
65
|
+
minutes=59,
|
66
|
+
seconds=59,
|
67
|
+
milliseconds=999,
|
68
|
+
microseconds=999,
|
69
|
+
nanoseconds=999,
|
70
|
+
)
|
71
|
+
DATE_DELTA_MIN = DATE_TIME_DELTA_MIN.date_part()
|
72
|
+
DATE_DELTA_MAX = DATE_TIME_DELTA_MAX.date_part()
|
73
|
+
TIME_DELTA_MIN = TimeDelta(hours=-87831216)
|
74
|
+
TIME_DELTA_MAX = TimeDelta(hours=87831216)
|
75
|
+
|
76
|
+
|
77
|
+
DATE_TIME_DELTA_PARSABLE_MIN = DateTimeDelta(
|
78
|
+
weeks=-142857,
|
79
|
+
hours=-23,
|
80
|
+
minutes=-59,
|
81
|
+
seconds=-59,
|
82
|
+
milliseconds=-999,
|
83
|
+
microseconds=-999,
|
84
|
+
nanoseconds=-999,
|
85
|
+
)
|
86
|
+
DATE_TIME_DELTA_PARSABLE_MAX = DateTimeDelta(
|
87
|
+
weeks=142857,
|
88
|
+
hours=23,
|
89
|
+
minutes=59,
|
90
|
+
seconds=59,
|
91
|
+
milliseconds=999,
|
92
|
+
microseconds=999,
|
93
|
+
nanoseconds=999,
|
94
|
+
)
|
57
95
|
DATE_DELTA_PARSABLE_MIN = DateDelta(days=-999999)
|
58
96
|
DATE_DELTA_PARSABLE_MAX = DateDelta(days=999999)
|
59
97
|
|
@@ -163,6 +201,164 @@ def to_date(
|
|
163
201
|
assert_never(never)
|
164
202
|
|
165
203
|
|
204
|
+
##
|
205
|
+
|
206
|
+
|
207
|
+
def to_days(delta: DateDelta, /) -> int:
|
208
|
+
"""Compute the number of days in a date delta."""
|
209
|
+
months, days = delta.in_months_days()
|
210
|
+
if months != 0:
|
211
|
+
raise ToDaysError(months=months)
|
212
|
+
return days
|
213
|
+
|
214
|
+
|
215
|
+
@dataclass(kw_only=True, slots=True)
|
216
|
+
class ToDaysError(Exception):
|
217
|
+
months: int
|
218
|
+
|
219
|
+
@override
|
220
|
+
def __str__(self) -> str:
|
221
|
+
return f"Date delta must not contain months; got {self.months}"
|
222
|
+
|
223
|
+
|
224
|
+
##
|
225
|
+
|
226
|
+
|
227
|
+
def to_date_time_delta(nanos: int, /) -> DateTimeDelta:
|
228
|
+
"""Construct a date-time delta."""
|
229
|
+
components = _to_time_delta_components(nanos)
|
230
|
+
days, hours = divmod(components.hours, 24)
|
231
|
+
weeks, days = divmod(days, 7)
|
232
|
+
match sign(nanos): # pragma: no cover
|
233
|
+
case 1:
|
234
|
+
if hours < 0:
|
235
|
+
hours += 24
|
236
|
+
days -= 1
|
237
|
+
if days < 0:
|
238
|
+
days += 7
|
239
|
+
weeks -= 1
|
240
|
+
case -1:
|
241
|
+
if hours > 0:
|
242
|
+
hours -= 24
|
243
|
+
days += 1
|
244
|
+
if days > 0:
|
245
|
+
days -= 7
|
246
|
+
weeks += 1
|
247
|
+
case 0:
|
248
|
+
...
|
249
|
+
return DateTimeDelta(
|
250
|
+
weeks=weeks,
|
251
|
+
days=days,
|
252
|
+
hours=hours,
|
253
|
+
minutes=components.minutes,
|
254
|
+
seconds=components.seconds,
|
255
|
+
microseconds=components.microseconds,
|
256
|
+
milliseconds=components.milliseconds,
|
257
|
+
nanoseconds=components.nanoseconds,
|
258
|
+
)
|
259
|
+
|
260
|
+
|
261
|
+
##
|
262
|
+
|
263
|
+
|
264
|
+
def to_nanos(delta: DateTimeDelta, /) -> int:
|
265
|
+
"""Compute the number of nanoseconds in a date-time delta."""
|
266
|
+
months, days, _, _ = delta.in_months_days_secs_nanos()
|
267
|
+
if months != 0:
|
268
|
+
raise ToNanosError(months=months)
|
269
|
+
return 24 * 60 * 60 * int(1e9) * days + delta.time_part().in_nanoseconds()
|
270
|
+
|
271
|
+
|
272
|
+
@dataclass(kw_only=True, slots=True)
|
273
|
+
class ToNanosError(Exception):
|
274
|
+
months: int
|
275
|
+
|
276
|
+
@override
|
277
|
+
def __str__(self) -> str:
|
278
|
+
return f"Date-time delta must not contain months; got {self.months}"
|
279
|
+
|
280
|
+
|
281
|
+
##
|
282
|
+
|
283
|
+
|
284
|
+
def to_time_delta(nanos: int, /) -> TimeDelta:
|
285
|
+
"""Construct a time delta."""
|
286
|
+
components = _to_time_delta_components(nanos)
|
287
|
+
return TimeDelta(
|
288
|
+
hours=components.hours,
|
289
|
+
minutes=components.minutes,
|
290
|
+
seconds=components.seconds,
|
291
|
+
microseconds=components.microseconds,
|
292
|
+
milliseconds=components.milliseconds,
|
293
|
+
nanoseconds=components.nanoseconds,
|
294
|
+
)
|
295
|
+
|
296
|
+
|
297
|
+
@dataclass(kw_only=True, slots=True)
|
298
|
+
class _TimeDeltaComponents:
|
299
|
+
hours: int
|
300
|
+
minutes: int
|
301
|
+
seconds: int
|
302
|
+
microseconds: int
|
303
|
+
milliseconds: int
|
304
|
+
nanoseconds: int
|
305
|
+
|
306
|
+
|
307
|
+
def _to_time_delta_components(nanos: int, /) -> _TimeDeltaComponents:
|
308
|
+
sign_use = sign(nanos)
|
309
|
+
micros, nanos = divmod(nanos, int(1e3))
|
310
|
+
millis, micros = divmod(micros, int(1e3))
|
311
|
+
secs, millis = divmod(millis, int(1e3))
|
312
|
+
mins, secs = divmod(secs, 60)
|
313
|
+
hours, mins = divmod(mins, 60)
|
314
|
+
match sign_use: # pragma: no cover
|
315
|
+
case 1:
|
316
|
+
if nanos < 0:
|
317
|
+
nanos += int(1e3)
|
318
|
+
micros -= 1
|
319
|
+
if micros < 0:
|
320
|
+
micros += int(1e3)
|
321
|
+
millis -= 1
|
322
|
+
if millis < 0:
|
323
|
+
millis += int(1e3)
|
324
|
+
secs -= 1
|
325
|
+
if secs < 0:
|
326
|
+
secs += 60
|
327
|
+
mins -= 1
|
328
|
+
if mins < 0:
|
329
|
+
mins += 60
|
330
|
+
hours -= 1
|
331
|
+
case -1:
|
332
|
+
if nanos > 0:
|
333
|
+
nanos -= int(1e3)
|
334
|
+
micros += 1
|
335
|
+
if micros > 0:
|
336
|
+
micros -= int(1e3)
|
337
|
+
millis += 1
|
338
|
+
if millis > 0:
|
339
|
+
millis -= int(1e3)
|
340
|
+
secs += 1
|
341
|
+
if secs > 0:
|
342
|
+
secs -= 60
|
343
|
+
mins += 1
|
344
|
+
if mins > 0:
|
345
|
+
mins -= 60
|
346
|
+
hours += 1
|
347
|
+
case 0:
|
348
|
+
...
|
349
|
+
return _TimeDeltaComponents(
|
350
|
+
hours=hours,
|
351
|
+
minutes=mins,
|
352
|
+
seconds=secs,
|
353
|
+
microseconds=micros,
|
354
|
+
milliseconds=millis,
|
355
|
+
nanoseconds=nanos,
|
356
|
+
)
|
357
|
+
|
358
|
+
|
359
|
+
##
|
360
|
+
|
361
|
+
|
166
362
|
@overload
|
167
363
|
def to_zoned_date_time(*, date_time: MaybeCallableZonedDateTime) -> ZonedDateTime: ...
|
168
364
|
@overload
|
@@ -265,6 +461,8 @@ __all__ = [
|
|
265
461
|
"ZERO_TIME",
|
266
462
|
"ZONED_DATE_TIME_MAX",
|
267
463
|
"ZONED_DATE_TIME_MIN",
|
464
|
+
"ToDaysError",
|
465
|
+
"ToNanosError",
|
268
466
|
"WheneverLogRecord",
|
269
467
|
"format_compact",
|
270
468
|
"format_compact",
|
@@ -276,5 +474,8 @@ __all__ = [
|
|
276
474
|
"get_today",
|
277
475
|
"get_today_local",
|
278
476
|
"to_date",
|
477
|
+
"to_date_time_delta",
|
478
|
+
"to_days",
|
479
|
+
"to_nanos",
|
279
480
|
"to_zoned_date_time",
|
280
481
|
]
|
utilities/tenacity.py
DELETED
@@ -1,145 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
from contextlib import AbstractAsyncContextManager, asynccontextmanager
|
4
|
-
from typing import TYPE_CHECKING, Any, override
|
5
|
-
|
6
|
-
from tenacity import (
|
7
|
-
AsyncRetrying,
|
8
|
-
AttemptManager,
|
9
|
-
RetryCallState,
|
10
|
-
RetryError,
|
11
|
-
after_nothing,
|
12
|
-
before_nothing,
|
13
|
-
retry_if_exception_type,
|
14
|
-
stop_never,
|
15
|
-
wait_none,
|
16
|
-
)
|
17
|
-
from tenacity import wait_exponential_jitter as _wait_exponential_jitter
|
18
|
-
from tenacity._utils import MAX_WAIT
|
19
|
-
from tenacity.asyncio import _portable_async_sleep
|
20
|
-
|
21
|
-
from utilities.asyncio import timeout_dur
|
22
|
-
from utilities.contextlib import NoOpContextManager
|
23
|
-
from utilities.datetime import datetime_duration_to_float
|
24
|
-
|
25
|
-
if TYPE_CHECKING:
|
26
|
-
from collections.abc import AsyncIterator, Callable
|
27
|
-
|
28
|
-
from tenacity.retry import RetryBaseT
|
29
|
-
from tenacity.stop import StopBaseT
|
30
|
-
from tenacity.wait import WaitBaseT
|
31
|
-
|
32
|
-
from utilities.types import Duration, MaybeAwaitable
|
33
|
-
|
34
|
-
|
35
|
-
type MaybeAttemptManager = NoOpContextManager | AttemptManager
|
36
|
-
type MaybeAttemptContextManager = AbstractAsyncContextManager[MaybeAttemptManager]
|
37
|
-
|
38
|
-
|
39
|
-
class wait_exponential_jitter(_wait_exponential_jitter): # noqa: N801
|
40
|
-
"""Subclass of `wait_exponential_jitter` accepting durations."""
|
41
|
-
|
42
|
-
@override
|
43
|
-
def __init__(
|
44
|
-
self,
|
45
|
-
initial: Duration = 1,
|
46
|
-
max: Duration = MAX_WAIT,
|
47
|
-
exp_base: float = 2,
|
48
|
-
jitter: Duration = 1,
|
49
|
-
) -> None:
|
50
|
-
super().__init__(
|
51
|
-
initial=datetime_duration_to_float(initial),
|
52
|
-
max=datetime_duration_to_float(max),
|
53
|
-
exp_base=exp_base,
|
54
|
-
jitter=datetime_duration_to_float(jitter),
|
55
|
-
)
|
56
|
-
|
57
|
-
|
58
|
-
async def yield_attempts(
|
59
|
-
*,
|
60
|
-
sleep: Callable[[int | float], MaybeAwaitable[None]] | None = None,
|
61
|
-
stop: StopBaseT | None = None,
|
62
|
-
wait: WaitBaseT | None = None,
|
63
|
-
retry: RetryBaseT | None = None,
|
64
|
-
before: Callable[[RetryCallState], MaybeAwaitable[None]] | None = None,
|
65
|
-
after: Callable[[RetryCallState], MaybeAwaitable[None]] | None = None,
|
66
|
-
before_sleep: Callable[[RetryCallState], MaybeAwaitable[None]] | None = None,
|
67
|
-
reraise: bool | None = None,
|
68
|
-
retry_error_cls: type[RetryError] | None = None,
|
69
|
-
retry_error_callback: Callable[[RetryCallState], MaybeAwaitable[Any]] | None = None,
|
70
|
-
) -> AsyncIterator[MaybeAttemptManager]:
|
71
|
-
"""Yield the attempts."""
|
72
|
-
if (
|
73
|
-
(sleep is None)
|
74
|
-
and (stop is None)
|
75
|
-
and (wait is None)
|
76
|
-
and (retry is None)
|
77
|
-
and (before is None)
|
78
|
-
and (after is None)
|
79
|
-
and (before_sleep is None)
|
80
|
-
and (reraise is None)
|
81
|
-
and (retry_error_cls is None)
|
82
|
-
):
|
83
|
-
yield NoOpContextManager()
|
84
|
-
else:
|
85
|
-
retrying = AsyncRetrying(
|
86
|
-
sleep=_portable_async_sleep if sleep is None else sleep,
|
87
|
-
stop=stop_never if stop is None else stop,
|
88
|
-
wait=wait_none() if wait is None else wait,
|
89
|
-
retry=retry_if_exception_type() if retry is None else retry,
|
90
|
-
before=before_nothing if before is None else before,
|
91
|
-
after=after_nothing if after is None else after,
|
92
|
-
before_sleep=None if before_sleep is None else before_sleep,
|
93
|
-
reraise=False if reraise is None else reraise,
|
94
|
-
retry_error_cls=RetryError if retry_error_cls is None else retry_error_cls,
|
95
|
-
retry_error_callback=retry_error_callback,
|
96
|
-
)
|
97
|
-
async for attempt in retrying:
|
98
|
-
yield attempt
|
99
|
-
|
100
|
-
|
101
|
-
async def yield_timeout_attempts(
|
102
|
-
*,
|
103
|
-
sleep: Callable[[int | float], MaybeAwaitable[None]] | None = None,
|
104
|
-
stop: StopBaseT | None = None,
|
105
|
-
wait: WaitBaseT | None = None,
|
106
|
-
retry: RetryBaseT | None = None,
|
107
|
-
before: Callable[[RetryCallState], MaybeAwaitable[None]] | None = None,
|
108
|
-
after: Callable[[RetryCallState], MaybeAwaitable[None]] | None = None,
|
109
|
-
before_sleep: Callable[[RetryCallState], MaybeAwaitable[None]] | None = None,
|
110
|
-
reraise: bool | None = None,
|
111
|
-
retry_error_cls: type[RetryError] | None = None,
|
112
|
-
retry_error_callback: Callable[[RetryCallState], MaybeAwaitable[Any]] | None = None,
|
113
|
-
timeout: Duration | None = None,
|
114
|
-
) -> AsyncIterator[MaybeAttemptContextManager]:
|
115
|
-
"""Yield the attempts, with timeout."""
|
116
|
-
async for attempt in yield_attempts(
|
117
|
-
sleep=sleep,
|
118
|
-
stop=stop,
|
119
|
-
wait=wait,
|
120
|
-
retry=retry,
|
121
|
-
before=before,
|
122
|
-
after=after,
|
123
|
-
before_sleep=before_sleep,
|
124
|
-
reraise=reraise,
|
125
|
-
retry_error_cls=retry_error_cls,
|
126
|
-
retry_error_callback=retry_error_callback,
|
127
|
-
):
|
128
|
-
|
129
|
-
@asynccontextmanager
|
130
|
-
async def new(
|
131
|
-
attempt: MaybeAttemptManager, /
|
132
|
-
) -> AsyncIterator[MaybeAttemptManager]:
|
133
|
-
with attempt:
|
134
|
-
async with timeout_dur(duration=timeout):
|
135
|
-
yield attempt
|
136
|
-
|
137
|
-
yield new(attempt)
|
138
|
-
|
139
|
-
|
140
|
-
__all__ = [
|
141
|
-
"MaybeAttemptManager",
|
142
|
-
"wait_exponential_jitter",
|
143
|
-
"yield_attempts",
|
144
|
-
"yield_timeout_attempts",
|
145
|
-
]
|
File without changes
|
File without changes
|