dycw-utilities 0.115.0__py3-none-any.whl → 0.116.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.115.0.dist-info → dycw_utilities-0.116.0.dist-info}/METADATA +1 -1
- {dycw_utilities-0.115.0.dist-info → dycw_utilities-0.116.0.dist-info}/RECORD +8 -8
- utilities/__init__.py +1 -1
- utilities/asyncio.py +66 -21
- utilities/errors.py +19 -2
- utilities/redis.py +3 -3
- {dycw_utilities-0.115.0.dist-info → dycw_utilities-0.116.0.dist-info}/WHEEL +0 -0
- {dycw_utilities-0.115.0.dist-info → dycw_utilities-0.116.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,7 +1,7 @@
|
|
1
|
-
utilities/__init__.py,sha256=
|
1
|
+
utilities/__init__.py,sha256=pLugaU63qZmC8Jq_ToL0O5damDhX5lVpukCAQ0DN0UA,60
|
2
2
|
utilities/altair.py,sha256=Gpja-flOo-Db0PIPJLJsgzAlXWoKUjPU1qY-DQ829ek,9156
|
3
3
|
utilities/astor.py,sha256=xuDUkjq0-b6fhtwjhbnebzbqQZAjMSHR1IIS5uOodVg,777
|
4
|
-
utilities/asyncio.py,sha256=
|
4
|
+
utilities/asyncio.py,sha256=NmAyhbOabek5LRagPcgTcSjjAZGK1yu5tLErWDdv5V0,23353
|
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
|
@@ -14,7 +14,7 @@ utilities/cvxpy.py,sha256=Rv1-fD-XYerosCavRF8Pohop2DBkU3AlFaGTfD8AEAA,13776
|
|
14
14
|
utilities/dataclasses.py,sha256=iiC1wpGXWhaocIikzwBt8bbLWyImoUlOlcDZJGejaIg,33011
|
15
15
|
utilities/datetime.py,sha256=PcN-4_sSPX1zbpdzBQRdo08pubCuGHyigxkV6SUnvlo,38733
|
16
16
|
utilities/enum.py,sha256=HoRwVCWzsnH0vpO9ZEcAAIZLMv0Sn2vJxxA4sYMQgDs,5793
|
17
|
-
utilities/errors.py,sha256=
|
17
|
+
utilities/errors.py,sha256=gxsaa7eq7jbYl41Of40-ivjXqJB5gt4QAcJ0smZZMJE,829
|
18
18
|
utilities/eventkit.py,sha256=6M5Xu1SzN-juk9PqBHwy5dS-ta7T0qA6SMpDsakOJ0E,13039
|
19
19
|
utilities/fastapi.py,sha256=y-35at3005jzlNx2wJoiSvB1Ch5bMo30wgU_so3IDdI,2467
|
20
20
|
utilities/fpdf2.py,sha256=y1NGXR5chWqLXWpewGV3hlRGMr_5yV1lVRkPBhPEgJI,1843
|
@@ -58,7 +58,7 @@ utilities/pytest_regressions.py,sha256=-SVT9647Dg6-JcdsiaDKXe3NdOmmrvGevLKWwGjxq
|
|
58
58
|
utilities/python_dotenv.py,sha256=iWcnpXbH7S6RoXHiLlGgyuH6udCupAcPd_gQ0eAenQ0,3190
|
59
59
|
utilities/random.py,sha256=lYdjgxB7GCfU_fwFVl5U-BIM_HV3q6_urL9byjrwDM8,4157
|
60
60
|
utilities/re.py,sha256=5J4d8VwIPFVrX2Eb8zfoxImDv7IwiN_U7mJ07wR2Wvs,3958
|
61
|
-
utilities/redis.py,sha256=
|
61
|
+
utilities/redis.py,sha256=0LAmbQFkwbJ3w6teTqGw1fNCSs1tDivL3DVAqob0S5c,26732
|
62
62
|
utilities/reprlib.py,sha256=Re9bk3n-kC__9DxQmRlevqFA86pE6TtVfWjUgpbVOv0,1849
|
63
63
|
utilities/rich.py,sha256=t50MwwVBsoOLxzmeVFSVpjno4OW6Ufum32skXbV8-Bs,1911
|
64
64
|
utilities/scipy.py,sha256=X6ROnHwiUhAmPhM0jkfEh0-Fd9iRvwiqtCQMOLmOQF8,945
|
@@ -87,7 +87,7 @@ utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
|
|
87
87
|
utilities/whenever.py,sha256=iLRP_-8CZtBpHKbGZGu-kjSMg1ZubJ-VSmgSy7Eudxw,17787
|
88
88
|
utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
|
89
89
|
utilities/zoneinfo.py,sha256=-Xm57PMMwDTYpxJdkiJG13wnbwK--I7XItBh5WVhD-o,1874
|
90
|
-
dycw_utilities-0.
|
91
|
-
dycw_utilities-0.
|
92
|
-
dycw_utilities-0.
|
93
|
-
dycw_utilities-0.
|
90
|
+
dycw_utilities-0.116.0.dist-info/METADATA,sha256=yAhNM7tqxJvQ36peel-0LjBpoJ-0nS9jT20DPZ9yBn4,12943
|
91
|
+
dycw_utilities-0.116.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
92
|
+
dycw_utilities-0.116.0.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
|
93
|
+
dycw_utilities-0.116.0.dist-info/RECORD,,
|
utilities/__init__.py
CHANGED
utilities/asyncio.py
CHANGED
@@ -30,6 +30,7 @@ from subprocess import PIPE
|
|
30
30
|
from sys import stderr, stdout
|
31
31
|
from typing import (
|
32
32
|
TYPE_CHECKING,
|
33
|
+
Any,
|
33
34
|
Generic,
|
34
35
|
NoReturn,
|
35
36
|
Self,
|
@@ -41,8 +42,9 @@ from typing import (
|
|
41
42
|
)
|
42
43
|
|
43
44
|
from utilities.datetime import MILLISECOND, MINUTE, SECOND, datetime_duration_to_float
|
44
|
-
from utilities.errors import ImpossibleCaseError
|
45
|
+
from utilities.errors import ImpossibleCaseError, repr_error
|
45
46
|
from utilities.functions import ensure_int, ensure_not_none, get_class_name
|
47
|
+
from utilities.reprlib import get_repr
|
46
48
|
from utilities.sentinel import Sentinel, sentinel
|
47
49
|
from utilities.types import (
|
48
50
|
Coroutine1,
|
@@ -379,17 +381,19 @@ class InfiniteLooper(ABC, Generic[THashable]):
|
|
379
381
|
self._error_upon_core(error)
|
380
382
|
await sleep_dur(duration=self.sleep_restart)
|
381
383
|
|
382
|
-
async def _run_looper_with_coroutines(
|
384
|
+
async def _run_looper_with_coroutines(
|
385
|
+
self, *coroutines: Callable[[], Coroutine1[None]]
|
386
|
+
) -> None:
|
383
387
|
"""Run multiple loopers."""
|
384
388
|
while True:
|
385
389
|
self._reset_events()
|
386
390
|
try:
|
387
391
|
async with TaskGroup() as tg:
|
388
392
|
_ = tg.create_task(self._run_looper())
|
389
|
-
_ =
|
390
|
-
except
|
391
|
-
self.
|
392
|
-
await sleep_dur(duration=self.sleep_restart)
|
393
|
+
_ = [tg.create_task(c()) for c in coroutines]
|
394
|
+
except ExceptionGroup as error:
|
395
|
+
self._error_group_upon_coroutines(error)
|
396
|
+
await sleep_dur(duration=self.sleep_restart)
|
393
397
|
|
394
398
|
async def _initialize(self) -> None:
|
395
399
|
"""Initialize the loop."""
|
@@ -401,9 +405,9 @@ class InfiniteLooper(ABC, Generic[THashable]):
|
|
401
405
|
"""Handle any errors upon initializing the looper."""
|
402
406
|
if self.logger is not None:
|
403
407
|
getLogger(name=self.logger).error(
|
404
|
-
"
|
408
|
+
"%r encountered %r whilst initializing; sleeping for %s...",
|
405
409
|
get_class_name(self),
|
406
|
-
|
410
|
+
repr_error(error),
|
407
411
|
self.sleep_restart,
|
408
412
|
)
|
409
413
|
|
@@ -411,12 +415,25 @@ class InfiniteLooper(ABC, Generic[THashable]):
|
|
411
415
|
"""Handle any errors upon running the core function."""
|
412
416
|
if self.logger is not None:
|
413
417
|
getLogger(name=self.logger).error(
|
414
|
-
"
|
418
|
+
"%r encountered %r; sleeping for %s...",
|
415
419
|
get_class_name(self),
|
416
|
-
|
420
|
+
repr_error(error),
|
417
421
|
self.sleep_restart,
|
418
422
|
)
|
419
423
|
|
424
|
+
def _error_group_upon_coroutines(self, group: ExceptionGroup, /) -> None:
|
425
|
+
"""Handle any errors upon running the core function."""
|
426
|
+
if self.logger is not None:
|
427
|
+
errors = group.exceptions
|
428
|
+
n = len(errors)
|
429
|
+
msgs = [f"{get_class_name(self)!r} encountered {n} error(s):"]
|
430
|
+
msgs.extend(
|
431
|
+
f"- Error #{i}/{n}: {repr_error(e)}"
|
432
|
+
for i, e in enumerate(errors, start=1)
|
433
|
+
)
|
434
|
+
msgs.append(f"Sleeping for {self.sleep_restart}...")
|
435
|
+
getLogger(name=self.logger).error("\n".join(msgs))
|
436
|
+
|
420
437
|
def _raise_error(self, event: THashable, /) -> NoReturn:
|
421
438
|
"""Raise the error corresponding to given event."""
|
422
439
|
mapping = dict(self._yield_events_and_exceptions())
|
@@ -434,10 +451,10 @@ class InfiniteLooper(ABC, Generic[THashable]):
|
|
434
451
|
try:
|
435
452
|
event_obj = self._events[event]
|
436
453
|
except KeyError:
|
437
|
-
raise InfiniteLooperError(event=event) from None
|
454
|
+
raise InfiniteLooperError(looper=self, event=event) from None
|
438
455
|
event_obj.set()
|
439
456
|
|
440
|
-
def _yield_coroutines(self) -> Iterator[Coroutine1[None]]:
|
457
|
+
def _yield_coroutines(self) -> Iterator[Callable[[], Coroutine1[None]]]:
|
441
458
|
"""Yield any other coroutines which must also be run."""
|
442
459
|
yield from []
|
443
460
|
|
@@ -450,11 +467,12 @@ class InfiniteLooper(ABC, Generic[THashable]):
|
|
450
467
|
|
451
468
|
@dataclass(kw_only=True, slots=True)
|
452
469
|
class InfiniteLooperError(Exception):
|
470
|
+
looper: InfiniteLooper[Any]
|
453
471
|
event: Hashable
|
454
472
|
|
455
473
|
@override
|
456
474
|
def __str__(self) -> str:
|
457
|
-
return f"
|
475
|
+
return f"{get_class_name(self.looper)!r} does not have an event {self.event!r}"
|
458
476
|
|
459
477
|
|
460
478
|
##
|
@@ -466,25 +484,24 @@ class InfiniteQueueLooper(InfiniteLooper[THashable], Generic[THashable, _T]):
|
|
466
484
|
|
467
485
|
queue_type: type[Queue[_T]] = field(default=Queue, repr=False)
|
468
486
|
_queue: Queue[_T] = field(init=False)
|
469
|
-
_current: Queue[_T] = field(init=False)
|
470
487
|
|
471
488
|
@override
|
472
489
|
def __post_init__(self) -> None:
|
473
490
|
super().__post_init__()
|
474
491
|
self._queue = self.queue_type()
|
475
|
-
self._current = self.queue_type()
|
476
492
|
|
477
493
|
@override
|
478
494
|
async def _core(self) -> None:
|
479
495
|
"""Run the core part of the loop."""
|
480
|
-
items =
|
481
|
-
|
482
|
-
|
496
|
+
items = get_items_nowait(self._queue)
|
497
|
+
if len(items) == 0:
|
498
|
+
return
|
483
499
|
try:
|
484
500
|
await self._process_items(*items)
|
485
|
-
except Exception:
|
486
|
-
|
487
|
-
|
501
|
+
except Exception as error: # noqa: BLE001
|
502
|
+
raise InfiniteQueueLooperError(
|
503
|
+
looper=self, items=items, error=error
|
504
|
+
) from None
|
488
505
|
|
489
506
|
@abstractmethod
|
490
507
|
async def _process_items(self, *items: _T) -> None:
|
@@ -494,6 +511,33 @@ class InfiniteQueueLooper(InfiniteLooper[THashable], Generic[THashable, _T]):
|
|
494
511
|
"""Put items into the queue."""
|
495
512
|
put_items_nowait(items, self._queue)
|
496
513
|
|
514
|
+
@override
|
515
|
+
def _error_upon_core(self, error: Exception, /) -> None:
|
516
|
+
"""Handle any errors upon running the core function."""
|
517
|
+
if self.logger is not None:
|
518
|
+
if isinstance(error, InfiniteQueueLooperError):
|
519
|
+
getLogger(name=self.logger).error(
|
520
|
+
"%r encountered %s whilst processing %d item(s) %s; sleeping for %s...",
|
521
|
+
get_class_name(self),
|
522
|
+
repr_error(error.error),
|
523
|
+
len(error.items),
|
524
|
+
get_repr(error.items),
|
525
|
+
self.sleep_restart,
|
526
|
+
)
|
527
|
+
else:
|
528
|
+
super()._error_upon_core(error) # pragma: no cover
|
529
|
+
|
530
|
+
|
531
|
+
@dataclass(kw_only=True, slots=True)
|
532
|
+
class InfiniteQueueLooperError(Exception, Generic[_T]):
|
533
|
+
looper: InfiniteQueueLooper[Any, Any]
|
534
|
+
items: Sequence[_T]
|
535
|
+
error: Exception
|
536
|
+
|
537
|
+
@override
|
538
|
+
def __str__(self) -> str:
|
539
|
+
return f"{get_class_name(self.looper)!r} encountered {repr_error(self.error)} whilst processing {len(self.items)} item(s): {get_repr(self.items)}"
|
540
|
+
|
497
541
|
|
498
542
|
##
|
499
543
|
|
@@ -703,6 +747,7 @@ __all__ = [
|
|
703
747
|
"InfiniteLooper",
|
704
748
|
"InfiniteLooperError",
|
705
749
|
"InfiniteQueueLooper",
|
750
|
+
"InfiniteQueueLooperError",
|
706
751
|
"QueueProcessor",
|
707
752
|
"StreamCommandOutput",
|
708
753
|
"UniquePriorityQueue",
|
utilities/errors.py
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
from dataclasses import dataclass
|
4
|
-
from typing import override
|
4
|
+
from typing import TYPE_CHECKING, assert_never, override
|
5
|
+
|
6
|
+
if TYPE_CHECKING:
|
7
|
+
from utilities.types import MaybeType
|
5
8
|
|
6
9
|
|
7
10
|
@dataclass(kw_only=True, slots=True)
|
@@ -14,4 +17,18 @@ class ImpossibleCaseError(Exception):
|
|
14
17
|
return f"Case must be possible: {desc}."
|
15
18
|
|
16
19
|
|
17
|
-
|
20
|
+
##
|
21
|
+
|
22
|
+
|
23
|
+
def repr_error(error: MaybeType[Exception], /) -> str:
|
24
|
+
"""Get a string representation of an error."""
|
25
|
+
match error:
|
26
|
+
case Exception() as error_obj:
|
27
|
+
return f"{error_obj.__class__.__name__}({error_obj})"
|
28
|
+
case type() as error_cls:
|
29
|
+
return error_cls.__name__
|
30
|
+
case _ as never:
|
31
|
+
assert_never(never)
|
32
|
+
|
33
|
+
|
34
|
+
__all__ = ["ImpossibleCaseError", "repr_error"]
|
utilities/redis.py
CHANGED
@@ -648,7 +648,7 @@ _SUBSCRIBE_SLEEP: Duration = MILLISECOND
|
|
648
648
|
|
649
649
|
|
650
650
|
@overload
|
651
|
-
|
651
|
+
def subscribe(
|
652
652
|
pubsub: PubSub,
|
653
653
|
channels: MaybeIterable[str],
|
654
654
|
/,
|
@@ -658,7 +658,7 @@ async def subscribe(
|
|
658
658
|
sleep: Duration = _SUBSCRIBE_SLEEP,
|
659
659
|
) -> AsyncIterator[_T]: ...
|
660
660
|
@overload
|
661
|
-
|
661
|
+
def subscribe(
|
662
662
|
pubsub: PubSub,
|
663
663
|
channels: MaybeIterable[str],
|
664
664
|
/,
|
@@ -667,7 +667,7 @@ async def subscribe(
|
|
667
667
|
timeout: Duration | None = _SUBSCRIBE_TIMEOUT,
|
668
668
|
sleep: Duration = _SUBSCRIBE_SLEEP,
|
669
669
|
) -> AsyncIterator[bytes]: ...
|
670
|
-
async def subscribe(
|
670
|
+
async def subscribe( # pyright: ignore[reportInconsistentOverload]
|
671
671
|
pubsub: PubSub,
|
672
672
|
channels: MaybeIterable[str],
|
673
673
|
/,
|
File without changes
|
File without changes
|