dycw-utilities 0.113.5__py3-none-any.whl → 0.114.1__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.113.5.dist-info → dycw_utilities-0.114.1.dist-info}/METADATA +1 -1
- {dycw_utilities-0.113.5.dist-info → dycw_utilities-0.114.1.dist-info}/RECORD +8 -8
- utilities/__init__.py +1 -1
- utilities/asyncio.py +140 -48
- utilities/slack_sdk.py +1 -1
- utilities/sqlalchemy.py +1 -1
- {dycw_utilities-0.113.5.dist-info → dycw_utilities-0.114.1.dist-info}/WHEEL +0 -0
- {dycw_utilities-0.113.5.dist-info → dycw_utilities-0.114.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,7 +1,7 @@
|
|
1
|
-
utilities/__init__.py,sha256=
|
1
|
+
utilities/__init__.py,sha256=cCgankjsOm9nkKXgXTLbYLmBYYnt73fexYfZXeoBRUc,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=XVvmVNXKhP246JawvQCpFh4bMOl_NYm7QM60e-zrSFQ,20850
|
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
|
@@ -64,9 +64,9 @@ utilities/rich.py,sha256=t50MwwVBsoOLxzmeVFSVpjno4OW6Ufum32skXbV8-Bs,1911
|
|
64
64
|
utilities/scipy.py,sha256=X6ROnHwiUhAmPhM0jkfEh0-Fd9iRvwiqtCQMOLmOQF8,945
|
65
65
|
utilities/sentinel.py,sha256=3jIwgpMekWgDAxPDA_hXMP2St43cPhciKN3LWiZ7kv0,1248
|
66
66
|
utilities/shelve.py,sha256=HZsMwK4tcIfg3sh0gApx4-yjQnrY4o3V3ZRimvRhoW0,738
|
67
|
-
utilities/slack_sdk.py,sha256=
|
67
|
+
utilities/slack_sdk.py,sha256=Gbla983KulSSXnNyzaXgYQLKoq84KvLH8SdhxU-jQ0Q,4126
|
68
68
|
utilities/socket.py,sha256=K77vfREvzoVTrpYKo6MZakol0EYu2q1sWJnnZqL0So0,118
|
69
|
-
utilities/sqlalchemy.py,sha256=
|
69
|
+
utilities/sqlalchemy.py,sha256=bs7rD1f8yB0uaFMYgmjo8wEoGow0x6aiELSYTPY_Img,35447
|
70
70
|
utilities/sqlalchemy_polars.py,sha256=wjJpoUo-yO9E2ujpG_06vV5r2OdvBiQ4yvV6wKCa2Tk,15605
|
71
71
|
utilities/statsmodels.py,sha256=koyiBHvpMcSiBfh99wFUfSggLNx7cuAw3rwyfAhoKpQ,3410
|
72
72
|
utilities/streamlit.py,sha256=U9PJBaKP1IdSykKhPZhIzSPTZsmLsnwbEPZWzNhJPKk,2955
|
@@ -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.114.1.dist-info/METADATA,sha256=Cw2lgBuLscK2IEbNCzzQltYWeAGsvTcdpsxT5CyWNJw,12943
|
91
|
+
dycw_utilities-0.114.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
92
|
+
dycw_utilities-0.114.1.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
|
93
|
+
dycw_utilities-0.114.1.dist-info/RECORD,,
|
utilities/__init__.py
CHANGED
utilities/asyncio.py
CHANGED
@@ -4,7 +4,6 @@ from abc import ABC, abstractmethod
|
|
4
4
|
from asyncio import (
|
5
5
|
CancelledError,
|
6
6
|
Event,
|
7
|
-
Lock,
|
8
7
|
PriorityQueue,
|
9
8
|
Queue,
|
10
9
|
QueueEmpty,
|
@@ -17,7 +16,7 @@ from asyncio import (
|
|
17
16
|
sleep,
|
18
17
|
timeout,
|
19
18
|
)
|
20
|
-
from collections.abc import Callable, Mapping
|
19
|
+
from collections.abc import Callable, Hashable, Iterable, Iterator, Mapping
|
21
20
|
from contextlib import (
|
22
21
|
AsyncExitStack,
|
23
22
|
_AsyncGeneratorContextManager,
|
@@ -31,6 +30,7 @@ from sys import stderr, stdout
|
|
31
30
|
from typing import (
|
32
31
|
TYPE_CHECKING,
|
33
32
|
Generic,
|
33
|
+
NoReturn,
|
34
34
|
Self,
|
35
35
|
TextIO,
|
36
36
|
TypeVar,
|
@@ -44,7 +44,6 @@ from utilities.errors import ImpossibleCaseError
|
|
44
44
|
from utilities.functions import ensure_int, ensure_not_none
|
45
45
|
from utilities.sentinel import Sentinel, sentinel
|
46
46
|
from utilities.types import (
|
47
|
-
Coroutine1,
|
48
47
|
MaybeCallableEvent,
|
49
48
|
MaybeType,
|
50
49
|
THashable,
|
@@ -240,7 +239,6 @@ class QueueProcessor(AsyncService, Generic[_T]):
|
|
240
239
|
sleep: Duration = MILLISECOND
|
241
240
|
_await_upon_aenter: bool = field(default=False, init=False, repr=False)
|
242
241
|
_queue: Queue[_T] = field(init=False, repr=False)
|
243
|
-
_lock: Lock = field(default_factory=Lock, init=False, repr=False)
|
244
242
|
|
245
243
|
def __post_init__(self) -> None:
|
246
244
|
self._queue = self.queue_type(
|
@@ -265,9 +263,9 @@ class QueueProcessor(AsyncService, Generic[_T]):
|
|
265
263
|
await self._run()
|
266
264
|
await sleep_dur(duration=self.sleep)
|
267
265
|
|
268
|
-
|
266
|
+
def _get_items_nowait(self, *, max_size: int | None = None) -> Sequence[_T]:
|
269
267
|
"""Get items from the queue; no waiting."""
|
270
|
-
return
|
268
|
+
return get_items_nowait(self._queue, max_size=max_size)
|
271
269
|
|
272
270
|
@abstractmethod
|
273
271
|
async def _process_item(self, item: _T, /) -> None:
|
@@ -282,7 +280,7 @@ class QueueProcessor(AsyncService, Generic[_T]):
|
|
282
280
|
async def _run(self) -> None:
|
283
281
|
"""Run the processer."""
|
284
282
|
try:
|
285
|
-
(item,) =
|
283
|
+
(item,) = self._get_items_nowait(max_size=1)
|
286
284
|
except ValueError:
|
287
285
|
raise QueueEmpty from None
|
288
286
|
try:
|
@@ -330,61 +328,155 @@ class ExceptionProcessor(QueueProcessor[Exception | type[Exception]]):
|
|
330
328
|
class InfiniteLooper(ABC, Generic[THashable]):
|
331
329
|
"""An infinite loop which can throw exceptions by setting events."""
|
332
330
|
|
333
|
-
|
331
|
+
_events: Mapping[THashable, Event] = field(
|
334
332
|
default_factory=dict, init=False, repr=False
|
335
333
|
)
|
336
334
|
sleep_core: Duration = SECOND
|
337
335
|
sleep_restart: Duration = MINUTE
|
338
336
|
|
339
337
|
def __post_init__(self) -> None:
|
340
|
-
self.
|
341
|
-
|
342
|
-
|
338
|
+
self._events = {
|
339
|
+
event: Event() for event, _ in self._yield_events_and_exceptions()
|
340
|
+
}
|
341
|
+
|
342
|
+
async def __call__(self) -> None:
|
343
|
+
"""Create a coroutine to run the looper."""
|
344
|
+
loopers = list(self._yield_loopers())
|
345
|
+
if len(loopers) == 0:
|
346
|
+
return await self._run_looper()
|
347
|
+
return await self._run_multiple_loopers(*loopers)
|
348
|
+
|
349
|
+
async def _run_looper(self) -> None:
|
350
|
+
"""Run the looper by itself."""
|
343
351
|
while True:
|
344
352
|
try:
|
345
353
|
self._reset_events()
|
346
354
|
try:
|
347
|
-
await self.
|
355
|
+
await self._initialize()
|
348
356
|
except Exception as error: # noqa: BLE001
|
349
|
-
self.
|
357
|
+
self._error_upon_initialize(error)
|
350
358
|
await sleep_dur(duration=self.sleep_restart)
|
351
359
|
else:
|
352
360
|
while True:
|
353
361
|
try:
|
354
362
|
event = next(
|
355
363
|
key
|
356
|
-
for (key, value) in self.
|
364
|
+
for (key, value) in self._events.items()
|
357
365
|
if value.is_set()
|
358
366
|
)
|
359
367
|
except StopIteration:
|
360
|
-
await self.
|
368
|
+
await self._core()
|
361
369
|
await sleep_dur(duration=self.sleep_core)
|
362
370
|
else:
|
363
|
-
|
371
|
+
self._raise_error(event)
|
372
|
+
except InfiniteLooperError:
|
373
|
+
raise
|
364
374
|
except Exception as error: # noqa: BLE001
|
365
|
-
self.
|
375
|
+
self._error_upon_core(error)
|
366
376
|
await sleep_dur(duration=self.sleep_restart)
|
367
377
|
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
378
|
+
async def _run_multiple_loopers(self, *loopers: InfiniteLooper) -> None:
|
379
|
+
"""Run multiple loopers."""
|
380
|
+
while True:
|
381
|
+
self._reset_events()
|
382
|
+
try:
|
383
|
+
async with TaskGroup() as tg:
|
384
|
+
_ = tg.create_task(self._run_looper())
|
385
|
+
_ = [tg.create_task(looper()) for looper in loopers]
|
386
|
+
except Exception as error: # noqa: BLE001
|
387
|
+
self._error_upon_core(error) # pragma: no cover
|
388
|
+
await sleep_dur(duration=self.sleep_restart) # pragma: no cover
|
372
389
|
|
373
|
-
async def
|
390
|
+
async def _initialize(self) -> None:
|
374
391
|
"""Initialize the loop."""
|
375
392
|
|
376
|
-
async def
|
377
|
-
"""Run the core."""
|
393
|
+
async def _core(self) -> None:
|
394
|
+
"""Run the core part of the loop."""
|
378
395
|
|
379
|
-
def
|
396
|
+
def _error_upon_initialize(self, error: Exception, /) -> None:
|
397
|
+
"""Handle any errors upon initializing the looper."""
|
380
398
|
_ = error
|
381
399
|
|
382
|
-
def
|
400
|
+
def _error_upon_core(self, error: Exception, /) -> None:
|
401
|
+
"""Handle any errors upon running the core function."""
|
383
402
|
_ = error
|
384
403
|
|
404
|
+
def _raise_error(self, event: THashable, /) -> NoReturn:
|
405
|
+
"""Raise the error corresponding to given event."""
|
406
|
+
mapping = dict(self._yield_events_and_exceptions())
|
407
|
+
error = mapping.get(event, InfiniteLooperError)
|
408
|
+
raise error
|
409
|
+
|
385
410
|
def _reset_events(self) -> None:
|
386
411
|
"""Reset the events."""
|
387
|
-
self.
|
412
|
+
self._events = {
|
413
|
+
event: Event() for event, _ in self._yield_events_and_exceptions()
|
414
|
+
}
|
415
|
+
|
416
|
+
def _set_event(self, event: THashable, /) -> None:
|
417
|
+
"""Set the given event."""
|
418
|
+
try:
|
419
|
+
event_obj = self._events[event]
|
420
|
+
except KeyError:
|
421
|
+
raise InfiniteLooperError(event=event) from None
|
422
|
+
event_obj.set()
|
423
|
+
|
424
|
+
def _yield_events_and_exceptions(
|
425
|
+
self,
|
426
|
+
) -> Iterator[tuple[THashable, MaybeType[BaseException]]]:
|
427
|
+
"""Yield the events & exceptions."""
|
428
|
+
yield from []
|
429
|
+
|
430
|
+
def _yield_loopers(self) -> Iterator[InfiniteLooper]:
|
431
|
+
"""Yield any other infinite loopers which must also be run."""
|
432
|
+
yield from []
|
433
|
+
|
434
|
+
|
435
|
+
@dataclass(kw_only=True, slots=True)
|
436
|
+
class InfiniteLooperError(Exception):
|
437
|
+
event: Hashable
|
438
|
+
|
439
|
+
@override
|
440
|
+
def __str__(self) -> str:
|
441
|
+
return f"No event {self.event!r} found"
|
442
|
+
|
443
|
+
|
444
|
+
##
|
445
|
+
|
446
|
+
|
447
|
+
@dataclass(kw_only=True)
|
448
|
+
class InfiniteQueueLooper(InfiniteLooper[THashable], Generic[THashable, _T]):
|
449
|
+
"""An infinite loop which processes a queue."""
|
450
|
+
|
451
|
+
queue_type: type[Queue[_T]] = field(default=Queue, repr=False)
|
452
|
+
_queue: Queue[_T] = field(init=False)
|
453
|
+
_current: Queue[_T] = field(init=False)
|
454
|
+
|
455
|
+
@override
|
456
|
+
def __post_init__(self) -> None:
|
457
|
+
super().__post_init__()
|
458
|
+
self._queue = self.queue_type()
|
459
|
+
self._current = self.queue_type()
|
460
|
+
|
461
|
+
@override
|
462
|
+
async def _core(self) -> None:
|
463
|
+
"""Run the core part of the loop."""
|
464
|
+
items = await get_items(self._queue)
|
465
|
+
_ = get_items_nowait(self._current)
|
466
|
+
put_items_nowait(items, self._current)
|
467
|
+
try:
|
468
|
+
await self._process_items(*items)
|
469
|
+
except Exception:
|
470
|
+
put_items_nowait(items, self._queue)
|
471
|
+
raise
|
472
|
+
|
473
|
+
@abstractmethod
|
474
|
+
async def _process_items(self, *items: _T) -> None:
|
475
|
+
"""Process the items."""
|
476
|
+
|
477
|
+
def put_items_nowait(self, *items: _T) -> None:
|
478
|
+
"""Put items into the queue."""
|
479
|
+
put_items_nowait(items, self._queue)
|
388
480
|
|
389
481
|
|
390
482
|
##
|
@@ -465,9 +557,7 @@ def get_event(
|
|
465
557
|
##
|
466
558
|
|
467
559
|
|
468
|
-
async def get_items(
|
469
|
-
queue: Queue[_T], /, *, max_size: int | None = None, lock: Lock | None = None
|
470
|
-
) -> list[_T]:
|
560
|
+
async def get_items(queue: Queue[_T], /, *, max_size: int | None = None) -> list[_T]:
|
471
561
|
"""Get items from a queue; if empty then wait."""
|
472
562
|
try:
|
473
563
|
items = [await queue.get()]
|
@@ -476,28 +566,12 @@ async def get_items(
|
|
476
566
|
return []
|
477
567
|
raise
|
478
568
|
max_size_use = None if max_size is None else (max_size - 1)
|
479
|
-
|
480
|
-
items.extend(await get_items_nowait(queue, max_size=max_size_use))
|
481
|
-
else:
|
482
|
-
async with lock:
|
483
|
-
items.extend(await get_items_nowait(queue, max_size=max_size_use))
|
569
|
+
items.extend(get_items_nowait(queue, max_size=max_size_use))
|
484
570
|
return items
|
485
571
|
|
486
572
|
|
487
|
-
|
488
|
-
queue: Queue[_T], /, *, max_size: int | None = None, lock: Lock | None = None
|
489
|
-
) -> list[_T]:
|
573
|
+
def get_items_nowait(queue: Queue[_T], /, *, max_size: int | None = None) -> list[_T]:
|
490
574
|
"""Get items from a queue; no waiting."""
|
491
|
-
if lock is None:
|
492
|
-
return _get_items_nowait_core(queue, max_size=max_size)
|
493
|
-
async with lock:
|
494
|
-
return _get_items_nowait_core(queue, max_size=max_size)
|
495
|
-
|
496
|
-
|
497
|
-
def _get_items_nowait_core(
|
498
|
-
queue: Queue[_T], /, *, max_size: int | None = None
|
499
|
-
) -> list[_T]:
|
500
|
-
"""Get all the items from a queue; no waiting."""
|
501
575
|
items: list[_T] = []
|
502
576
|
if max_size is None:
|
503
577
|
while True:
|
@@ -517,6 +591,21 @@ def _get_items_nowait_core(
|
|
517
591
|
##
|
518
592
|
|
519
593
|
|
594
|
+
async def put_items(items: Iterable[_T], queue: Queue[_T], /) -> None:
|
595
|
+
"""Put items into a queue; if full then wait."""
|
596
|
+
for item in items:
|
597
|
+
await queue.put(item)
|
598
|
+
|
599
|
+
|
600
|
+
def put_items_nowait(items: Iterable[_T], queue: Queue[_T], /) -> None:
|
601
|
+
"""Put items into a queue; no waiting."""
|
602
|
+
for item in items:
|
603
|
+
queue.put_nowait(item)
|
604
|
+
|
605
|
+
|
606
|
+
##
|
607
|
+
|
608
|
+
|
520
609
|
async def sleep_dur(*, duration: Duration | None = None) -> None:
|
521
610
|
"""Sleep which accepts durations."""
|
522
611
|
if duration is None:
|
@@ -595,6 +684,7 @@ __all__ = [
|
|
595
684
|
"AsyncService",
|
596
685
|
"EnhancedTaskGroup",
|
597
686
|
"ExceptionProcessor",
|
687
|
+
"InfiniteLooperError",
|
598
688
|
"QueueProcessor",
|
599
689
|
"StreamCommandOutput",
|
600
690
|
"UniquePriorityQueue",
|
@@ -602,6 +692,8 @@ __all__ = [
|
|
602
692
|
"get_event",
|
603
693
|
"get_items",
|
604
694
|
"get_items_nowait",
|
695
|
+
"put_items",
|
696
|
+
"put_items_nowait",
|
605
697
|
"sleep_dur",
|
606
698
|
"stream_command",
|
607
699
|
"timeout_dur",
|
utilities/slack_sdk.py
CHANGED
@@ -78,7 +78,7 @@ class SlackHandler(Handler, QueueProcessor[str]):
|
|
78
78
|
@override
|
79
79
|
async def _process_item(self, item: str, /) -> None:
|
80
80
|
"""Process the first item."""
|
81
|
-
items = list(chain([item],
|
81
|
+
items = list(chain([item], self._get_items_nowait()))
|
82
82
|
text = "\n".join(items)
|
83
83
|
try:
|
84
84
|
async with timeout_dur(duration=self.timeout):
|
utilities/sqlalchemy.py
CHANGED
@@ -627,7 +627,7 @@ class Upserter(QueueProcessor[_InsertItem]):
|
|
627
627
|
@override
|
628
628
|
async def _process_item(self, item: _InsertItem, /) -> None:
|
629
629
|
"""Process the first item."""
|
630
|
-
items = list(chain([item],
|
630
|
+
items = list(chain([item], self._get_items_nowait()))
|
631
631
|
await self._pre_upsert(items)
|
632
632
|
await upsert_items(
|
633
633
|
self.engine,
|
File without changes
|
File without changes
|