dycw-utilities 0.147.3__py3-none-any.whl → 0.148.0__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.147.3.dist-info → dycw_utilities-0.148.0.dist-info}/METADATA +1 -1
- {dycw_utilities-0.147.3.dist-info → dycw_utilities-0.148.0.dist-info}/RECORD +8 -8
- utilities/__init__.py +1 -1
- utilities/asyncio.py +36 -7
- utilities/pottery.py +6 -35
- {dycw_utilities-0.147.3.dist-info → dycw_utilities-0.148.0.dist-info}/WHEEL +0 -0
- {dycw_utilities-0.147.3.dist-info → dycw_utilities-0.148.0.dist-info}/entry_points.txt +0 -0
- {dycw_utilities-0.147.3.dist-info → dycw_utilities-0.148.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,6 @@
|
|
1
|
-
utilities/__init__.py,sha256=
|
1
|
+
utilities/__init__.py,sha256=2TRLfWl0dK_2ThOu9hSeTmoG0iZ_XFR_8beebjQMWqg,60
|
2
2
|
utilities/altair.py,sha256=92E2lCdyHY4Zb-vCw6rEJIsWdKipuu-Tu2ab1ufUfAk,9079
|
3
|
-
utilities/asyncio.py,sha256=
|
3
|
+
utilities/asyncio.py,sha256=J40KmMxTfpgj-GaTNikN0uQNpJnKqqViTD9IRYUvlvM,15985
|
4
4
|
utilities/atomicwrites.py,sha256=xcOWenTBRS0oat3kg7Sqe51AohNThMQ2ixPL7QCG8hw,5795
|
5
5
|
utilities/atools.py,sha256=9im2g8OCf-Iynqa8bAv8N0Ycj9QvrJmGO7yLCZEdgII,986
|
6
6
|
utilities/cachetools.py,sha256=v1-9sXHLdOLiwmkq6NB0OUbxeKBuVVN6wmAWefWoaHI,2744
|
@@ -49,7 +49,7 @@ utilities/platform.py,sha256=Ue9LSxYvg9yUXGKuz5aZoy_qkUEXde-v6B09exgSctU,2813
|
|
49
49
|
utilities/polars.py,sha256=BgiDryAVOapi41ddfJqN0wYh_sDj8BNEYtPB36LaHdo,71824
|
50
50
|
utilities/polars_ols.py,sha256=Uc9V5kvlWZ5cU93lKZ-cfAKdVFFw81tqwLW9PxtUvMs,5618
|
51
51
|
utilities/postgres.py,sha256=dbQrUFOoV6huNeuYTnSpNhSNq-QhXkBfrJPMtoAGJtY,7877
|
52
|
-
utilities/pottery.py,sha256=
|
52
|
+
utilities/pottery.py,sha256=w2X80PXWwzdHdqSYJP6ESrPNNDP3xzpyuJn-fp-Vt3M,5969
|
53
53
|
utilities/pqdm.py,sha256=BTsYPtbKQWwX-iXF4qCkfPG7DPxIB54J989n83bXrIo,3092
|
54
54
|
utilities/psutil.py,sha256=KUlu4lrUw9Zg1V7ZGetpWpGb9DB8l_SSDWGbANFNCPU,2104
|
55
55
|
utilities/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -88,8 +88,8 @@ utilities/zoneinfo.py,sha256=oEH-nL3t4h9uawyZqWDtNtDAl6M-CLpLYGI_nI6DulM,1971
|
|
88
88
|
utilities/pytest_plugins/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
|
89
89
|
utilities/pytest_plugins/pytest_randomly.py,sha256=NXzCcGKbpgYouz5yehKb4jmxmi2SexKKpgF4M65bi10,414
|
90
90
|
utilities/pytest_plugins/pytest_regressions.py,sha256=Iwhfv_OJH7UCPZCfoh7ugZ2Xjqjil-BBBsOb8sDwiGI,1471
|
91
|
-
dycw_utilities-0.
|
92
|
-
dycw_utilities-0.
|
93
|
-
dycw_utilities-0.
|
94
|
-
dycw_utilities-0.
|
95
|
-
dycw_utilities-0.
|
91
|
+
dycw_utilities-0.148.0.dist-info/METADATA,sha256=xk8TDabhrZWpV7yjJMe8lvY3v4ut2CWbfjcRHyIdRVg,1697
|
92
|
+
dycw_utilities-0.148.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
93
|
+
dycw_utilities-0.148.0.dist-info/entry_points.txt,sha256=BOD_SoDxwsfJYOLxhrSXhHP_T7iw-HXI9f2WVkzYxvQ,135
|
94
|
+
dycw_utilities-0.148.0.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
|
95
|
+
dycw_utilities-0.148.0.dist-info/RECORD,,
|
utilities/__init__.py
CHANGED
utilities/asyncio.py
CHANGED
@@ -37,8 +37,10 @@ from typing import (
|
|
37
37
|
|
38
38
|
from utilities.errors import ImpossibleCaseError
|
39
39
|
from utilities.functions import ensure_int, ensure_not_none, to_bool
|
40
|
+
from utilities.logging import get_logger
|
40
41
|
from utilities.random import SYSTEM_RANDOM
|
41
42
|
from utilities.sentinel import Sentinel, sentinel
|
43
|
+
from utilities.warnings import suppress_warnings
|
42
44
|
from utilities.whenever import get_now, round_date_or_date_time, to_nanoseconds
|
43
45
|
|
44
46
|
if TYPE_CHECKING:
|
@@ -63,6 +65,7 @@ if TYPE_CHECKING:
|
|
63
65
|
from utilities.types import (
|
64
66
|
Coro,
|
65
67
|
Delta,
|
68
|
+
LoggerOrName,
|
66
69
|
MaybeCallableBool,
|
67
70
|
MaybeType,
|
68
71
|
SupportsKeysAndGetItem,
|
@@ -340,6 +343,20 @@ class EnhancedTaskGroup(TaskGroup):
|
|
340
343
|
##
|
341
344
|
|
342
345
|
|
346
|
+
def get_coroutine_name(func: Callable[[], Coro[Any]], /) -> str:
|
347
|
+
"""Get the name of a coroutine, and then dispose of it gracefully."""
|
348
|
+
coro = func()
|
349
|
+
name = coro.__name__
|
350
|
+
with suppress_warnings(
|
351
|
+
message="coroutine '.*' was never awaited", category=RuntimeWarning
|
352
|
+
):
|
353
|
+
del coro
|
354
|
+
return name
|
355
|
+
|
356
|
+
|
357
|
+
##
|
358
|
+
|
359
|
+
|
343
360
|
async def get_items[T](queue: Queue[T], /, *, max_size: int | None = None) -> list[T]:
|
344
361
|
"""Get items from a queue; if empty then wait."""
|
345
362
|
try:
|
@@ -380,23 +397,34 @@ async def loop_until_succeed(
|
|
380
397
|
func: Callable[[], Coro[None]],
|
381
398
|
/,
|
382
399
|
*,
|
383
|
-
|
400
|
+
logger: LoggerOrName | None = None,
|
401
|
+
errors: type[Exception] | tuple[type[Exception], ...] | None = None,
|
384
402
|
sleep: Delta | None = None,
|
385
|
-
) ->
|
403
|
+
) -> bool:
|
386
404
|
"""Repeatedly call a coroutine until it succeeds."""
|
405
|
+
name = get_coroutine_name(func)
|
387
406
|
while True:
|
388
407
|
try:
|
389
|
-
|
390
|
-
except Exception as
|
391
|
-
if
|
392
|
-
error(
|
408
|
+
await func()
|
409
|
+
except Exception as error: # noqa: BLE001
|
410
|
+
if logger is not None:
|
411
|
+
get_logger(logger=logger).error("Error running %r", name, exc_info=True)
|
393
412
|
exc_type, exc_value, traceback = sys.exc_info()
|
394
413
|
if (exc_type is None) or (exc_value is None): # pragma: no cover
|
395
414
|
raise ImpossibleCaseError(
|
396
415
|
case=[f"{exc_type=}", f"{exc_value=}"]
|
397
416
|
) from None
|
398
417
|
sys.excepthook(exc_type, exc_value, traceback)
|
399
|
-
|
418
|
+
if (errors is not None) and isinstance(error, errors):
|
419
|
+
return False
|
420
|
+
if sleep is not None:
|
421
|
+
if logger is not None:
|
422
|
+
get_logger(logger=logger).info("Sleeping for %s...", sleep)
|
423
|
+
await sleep_td(sleep)
|
424
|
+
if logger is not None:
|
425
|
+
get_logger(logger=logger).info("Retrying %r...", name)
|
426
|
+
else:
|
427
|
+
return True
|
400
428
|
|
401
429
|
|
402
430
|
##
|
@@ -522,6 +550,7 @@ __all__ = [
|
|
522
550
|
"AsyncDict",
|
523
551
|
"EnhancedTaskGroup",
|
524
552
|
"StreamCommandOutput",
|
553
|
+
"get_coroutine_name",
|
525
554
|
"get_items",
|
526
555
|
"get_items_nowait",
|
527
556
|
"loop_until_succeed",
|
utilities/pottery.py
CHANGED
@@ -5,16 +5,14 @@ from dataclasses import dataclass
|
|
5
5
|
from sys import maxsize
|
6
6
|
from typing import TYPE_CHECKING, override
|
7
7
|
|
8
|
-
from pottery import AIORedlock
|
8
|
+
from pottery import AIORedlock, ExtendUnlockedLock
|
9
9
|
from pottery.exceptions import ReleaseUnlockedLock
|
10
10
|
from redis.asyncio import Redis
|
11
11
|
|
12
12
|
from utilities.asyncio import loop_until_succeed, sleep_td, timeout_td
|
13
13
|
from utilities.contextlib import enhanced_async_context_manager
|
14
|
-
from utilities.functools import partial
|
15
14
|
from utilities.iterables import always_iterable
|
16
15
|
from utilities.logging import get_logger
|
17
|
-
from utilities.warnings import suppress_warnings
|
18
16
|
from utilities.whenever import MILLISECOND, SECOND, to_seconds
|
19
17
|
|
20
18
|
if TYPE_CHECKING:
|
@@ -72,10 +70,7 @@ async def try_yield_coroutine_looper(
|
|
72
70
|
throttle=throttle,
|
73
71
|
) as lock:
|
74
72
|
yield CoroutineLooper(lock=lock, logger=logger, sleep=sleep_error)
|
75
|
-
except
|
76
|
-
_YieldAccessUnableToAcquireLockError,
|
77
|
-
_YieldAccessAcquiredUnlockedLockError,
|
78
|
-
) as error:
|
73
|
+
except _YieldAccessUnableToAcquireLockError as error: # skipif-ci-and-not-linux
|
79
74
|
if logger is not None:
|
80
75
|
get_logger(logger=logger).info("%s", error)
|
81
76
|
async with nullcontext():
|
@@ -92,27 +87,14 @@ class CoroutineLooper:
|
|
92
87
|
|
93
88
|
async def __call__[**P](
|
94
89
|
self, func: Callable[P, Coro[None]], *args: P.args, **kwargs: P.kwargs
|
95
|
-
) ->
|
90
|
+
) -> bool:
|
96
91
|
def make_coro() -> Coro[None]:
|
97
92
|
return func(*args, **kwargs)
|
98
93
|
|
99
|
-
await loop_until_succeed(
|
100
|
-
make_coro,
|
94
|
+
return await loop_until_succeed(
|
95
|
+
make_coro, logger=self.logger, errors=ExtendUnlockedLock, sleep=self.sleep
|
101
96
|
)
|
102
97
|
|
103
|
-
def _error(self, error: Exception, /, *, func: Callable[[], Coro[None]]) -> None:
|
104
|
-
_ = error
|
105
|
-
if self.logger is not None:
|
106
|
-
coro = func()
|
107
|
-
name = coro.__name__ # skipif-ci-and-not-linux
|
108
|
-
with suppress_warnings(
|
109
|
-
message="coroutine '.*' was never awaited", category=RuntimeWarning
|
110
|
-
):
|
111
|
-
del coro
|
112
|
-
get_logger(logger=self.logger).error(
|
113
|
-
"Error running %r", name, exc_info=True
|
114
|
-
)
|
115
|
-
|
116
98
|
|
117
99
|
##
|
118
100
|
|
@@ -150,8 +132,6 @@ async def yield_access(
|
|
150
132
|
lock = await _get_first_available_lock(
|
151
133
|
key, locks, num=num, timeout=timeout_acquire, sleep=sleep
|
152
134
|
)
|
153
|
-
if (await lock.locked()) == 0.0: # pragma: no cover
|
154
|
-
raise _YieldAccessAcquiredUnlockedLockError(key=lock.key)
|
155
135
|
yield lock
|
156
136
|
finally: # skipif-ci-and-not-linux
|
157
137
|
await sleep_td(throttle)
|
@@ -175,9 +155,7 @@ async def _get_first_available_lock(
|
|
175
155
|
)
|
176
156
|
async with timeout_td(timeout, error=error): # skipif-ci-and-not-linux
|
177
157
|
while True:
|
178
|
-
if (
|
179
|
-
(result := await _get_first_available_lock_if_any(locks)) is not None
|
180
|
-
) and (await result.locked() > 0.0):
|
158
|
+
if (result := await _get_first_available_lock_if_any(locks)) is not None:
|
181
159
|
return result
|
182
160
|
await sleep_td(sleep)
|
183
161
|
|
@@ -215,13 +193,6 @@ class _YieldAccessUnableToAcquireLockError(YieldAccessError):
|
|
215
193
|
return f"Unable to acquire any 1 of {self.num} locks for {self.key!r} after {self.timeout}" # skipif-ci-and-not-linux
|
216
194
|
|
217
195
|
|
218
|
-
@dataclass(kw_only=True, slots=True)
|
219
|
-
class _YieldAccessAcquiredUnlockedLockError(YieldAccessError):
|
220
|
-
@override
|
221
|
-
def __str__(self) -> str:
|
222
|
-
return f"Acquired an unlocked lock {self.key!r}" # pragma: no cover
|
223
|
-
|
224
|
-
|
225
196
|
__all__ = [
|
226
197
|
"CoroutineLooper",
|
227
198
|
"YieldAccessError",
|
File without changes
|
File without changes
|
File without changes
|