dycw-utilities 0.114.0__py3-none-any.whl → 0.114.2__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.114.0.dist-info → dycw_utilities-0.114.2.dist-info}/METADATA +9 -9
- {dycw_utilities-0.114.0.dist-info → dycw_utilities-0.114.2.dist-info}/RECORD +6 -6
- utilities/__init__.py +1 -1
- utilities/asyncio.py +117 -22
- {dycw_utilities-0.114.0.dist-info → dycw_utilities-0.114.2.dist-info}/WHEEL +0 -0
- {dycw_utilities-0.114.0.dist-info → dycw_utilities-0.114.2.dist-info}/licenses/LICENSE +0 -0
@@ -1,12 +1,12 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: dycw-utilities
|
3
|
-
Version: 0.114.
|
3
|
+
Version: 0.114.2
|
4
4
|
Author-email: Derek Wan <d.wan@icloud.com>
|
5
5
|
License-File: LICENSE
|
6
6
|
Requires-Python: >=3.12
|
7
7
|
Requires-Dist: typing-extensions<4.14,>=4.13.1
|
8
8
|
Provides-Extra: test
|
9
|
-
Requires-Dist: hypothesis<6.132,>=6.131.
|
9
|
+
Requires-Dist: hypothesis<6.132,>=6.131.17; extra == 'test'
|
10
10
|
Requires-Dist: pytest-asyncio<0.27,>=0.26.0; extra == 'test'
|
11
11
|
Requires-Dist: pytest-cov<6.2,>=6.1.1; extra == 'test'
|
12
12
|
Requires-Dist: pytest-instafail<0.6,>=0.5.0; extra == 'test'
|
@@ -38,7 +38,7 @@ Provides-Extra: zzz-test-cachetools
|
|
38
38
|
Requires-Dist: cachetools<5.6,>=5.5.2; extra == 'zzz-test-cachetools'
|
39
39
|
Provides-Extra: zzz-test-click
|
40
40
|
Requires-Dist: click<8.3,>=8.2.0; extra == 'zzz-test-click'
|
41
|
-
Requires-Dist: sqlalchemy<2.1,>=2.0.
|
41
|
+
Requires-Dist: sqlalchemy<2.1,>=2.0.41; extra == 'zzz-test-click'
|
42
42
|
Requires-Dist: whenever<0.8,>=0.7.3; extra == 'zzz-test-click'
|
43
43
|
Provides-Extra: zzz-test-contextlib
|
44
44
|
Provides-Extra: zzz-test-contextvars
|
@@ -80,12 +80,12 @@ Provides-Extra: zzz-test-hypothesis
|
|
80
80
|
Requires-Dist: aiosqlite<0.22,>=0.21.0; extra == 'zzz-test-hypothesis'
|
81
81
|
Requires-Dist: asyncpg<0.31,>=0.30.0; extra == 'zzz-test-hypothesis'
|
82
82
|
Requires-Dist: greenlet<3.3,>=3.2.0; extra == 'zzz-test-hypothesis'
|
83
|
-
Requires-Dist: hypothesis<6.132,>=6.131.
|
83
|
+
Requires-Dist: hypothesis<6.132,>=6.131.17; extra == 'zzz-test-hypothesis'
|
84
84
|
Requires-Dist: luigi<3.7,>=3.6.0; extra == 'zzz-test-hypothesis'
|
85
85
|
Requires-Dist: numpy<2.3,>=2.2.5; extra == 'zzz-test-hypothesis'
|
86
86
|
Requires-Dist: pathvalidate<3.3,>=3.2.3; extra == 'zzz-test-hypothesis'
|
87
|
-
Requires-Dist: redis<6.
|
88
|
-
Requires-Dist: sqlalchemy<2.1,>=2.0.
|
87
|
+
Requires-Dist: redis<6.2,>=6.1.0; extra == 'zzz-test-hypothesis'
|
88
|
+
Requires-Dist: sqlalchemy<2.1,>=2.0.41; extra == 'zzz-test-hypothesis'
|
89
89
|
Requires-Dist: tenacity<9.0,>=8.5.0; extra == 'zzz-test-hypothesis'
|
90
90
|
Requires-Dist: tzlocal<5.4,>=5.3.1; extra == 'zzz-test-hypothesis'
|
91
91
|
Requires-Dist: whenever<0.8,>=0.7.3; extra == 'zzz-test-hypothesis'
|
@@ -164,7 +164,7 @@ Provides-Extra: zzz-test-re
|
|
164
164
|
Provides-Extra: zzz-test-redis
|
165
165
|
Requires-Dist: orjson<3.11,>=3.10.15; extra == 'zzz-test-redis'
|
166
166
|
Requires-Dist: polars-lts-cpu<1.30,>=1.29.0; extra == 'zzz-test-redis'
|
167
|
-
Requires-Dist: redis<6.
|
167
|
+
Requires-Dist: redis<6.2,>=6.1.0; extra == 'zzz-test-redis'
|
168
168
|
Requires-Dist: rich<14.1,>=14.0.0; extra == 'zzz-test-redis'
|
169
169
|
Requires-Dist: tenacity<9.0,>=8.5.0; extra == 'zzz-test-redis'
|
170
170
|
Requires-Dist: tzlocal<5.4,>=5.3.1; extra == 'zzz-test-redis'
|
@@ -184,7 +184,7 @@ Requires-Dist: aiosqlite<0.22,>=0.21.0; extra == 'zzz-test-sqlalchemy'
|
|
184
184
|
Requires-Dist: asyncpg<0.31,>=0.30.0; extra == 'zzz-test-sqlalchemy'
|
185
185
|
Requires-Dist: greenlet<3.3,>=3.2.0; extra == 'zzz-test-sqlalchemy'
|
186
186
|
Requires-Dist: nest-asyncio<1.7,>=1.6.0; extra == 'zzz-test-sqlalchemy'
|
187
|
-
Requires-Dist: sqlalchemy<2.1,>=2.0.
|
187
|
+
Requires-Dist: sqlalchemy<2.1,>=2.0.41; extra == 'zzz-test-sqlalchemy'
|
188
188
|
Requires-Dist: tenacity<9.0,>=8.5.0; extra == 'zzz-test-sqlalchemy'
|
189
189
|
Provides-Extra: zzz-test-sqlalchemy-polars
|
190
190
|
Requires-Dist: aiosqlite<0.22,>=0.21.0; extra == 'zzz-test-sqlalchemy-polars'
|
@@ -192,7 +192,7 @@ Requires-Dist: asyncpg<0.31,>=0.30.0; extra == 'zzz-test-sqlalchemy-polars'
|
|
192
192
|
Requires-Dist: greenlet<3.3,>=3.2.0; extra == 'zzz-test-sqlalchemy-polars'
|
193
193
|
Requires-Dist: nest-asyncio<1.7,>=1.6.0; extra == 'zzz-test-sqlalchemy-polars'
|
194
194
|
Requires-Dist: polars-lts-cpu<1.30,>=1.29.0; extra == 'zzz-test-sqlalchemy-polars'
|
195
|
-
Requires-Dist: sqlalchemy<2.1,>=2.0.
|
195
|
+
Requires-Dist: sqlalchemy<2.1,>=2.0.41; extra == 'zzz-test-sqlalchemy-polars'
|
196
196
|
Requires-Dist: tenacity<9.0,>=8.5.0; extra == 'zzz-test-sqlalchemy-polars'
|
197
197
|
Requires-Dist: whenever<0.8,>=0.7.3; extra == 'zzz-test-sqlalchemy-polars'
|
198
198
|
Provides-Extra: zzz-test-streamlit
|
@@ -1,7 +1,7 @@
|
|
1
|
-
utilities/__init__.py,sha256=
|
1
|
+
utilities/__init__.py,sha256=oAmd7ttFz157b1dwFyTjWf9pMUSBunxAZPRwd4iDr68,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
|
@@ -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.114.
|
91
|
-
dycw_utilities-0.114.
|
92
|
-
dycw_utilities-0.114.
|
93
|
-
dycw_utilities-0.114.
|
90
|
+
dycw_utilities-0.114.2.dist-info/METADATA,sha256=mbXuybydflJ95yffJ4FaYeqq1tiATKR6DrtDd_5v02c,12943
|
91
|
+
dycw_utilities-0.114.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
92
|
+
dycw_utilities-0.114.2.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
|
93
|
+
dycw_utilities-0.114.2.dist-info/RECORD,,
|
utilities/__init__.py
CHANGED
utilities/asyncio.py
CHANGED
@@ -16,7 +16,7 @@ from asyncio import (
|
|
16
16
|
sleep,
|
17
17
|
timeout,
|
18
18
|
)
|
19
|
-
from collections.abc import Callable, Iterable, Mapping
|
19
|
+
from collections.abc import Callable, Hashable, Iterable, Iterator, Mapping
|
20
20
|
from contextlib import (
|
21
21
|
AsyncExitStack,
|
22
22
|
_AsyncGeneratorContextManager,
|
@@ -30,6 +30,7 @@ from sys import stderr, stdout
|
|
30
30
|
from typing import (
|
31
31
|
TYPE_CHECKING,
|
32
32
|
Generic,
|
33
|
+
NoReturn,
|
33
34
|
Self,
|
34
35
|
TextIO,
|
35
36
|
TypeVar,
|
@@ -43,7 +44,6 @@ from utilities.errors import ImpossibleCaseError
|
|
43
44
|
from utilities.functions import ensure_int, ensure_not_none
|
44
45
|
from utilities.sentinel import Sentinel, sentinel
|
45
46
|
from utilities.types import (
|
46
|
-
Coroutine1,
|
47
47
|
MaybeCallableEvent,
|
48
48
|
MaybeType,
|
49
49
|
THashable,
|
@@ -328,61 +328,155 @@ class ExceptionProcessor(QueueProcessor[Exception | type[Exception]]):
|
|
328
328
|
class InfiniteLooper(ABC, Generic[THashable]):
|
329
329
|
"""An infinite loop which can throw exceptions by setting events."""
|
330
330
|
|
331
|
-
|
331
|
+
_events: Mapping[THashable, Event] = field(
|
332
332
|
default_factory=dict, init=False, repr=False
|
333
333
|
)
|
334
334
|
sleep_core: Duration = SECOND
|
335
335
|
sleep_restart: Duration = MINUTE
|
336
336
|
|
337
337
|
def __post_init__(self) -> None:
|
338
|
-
self.
|
339
|
-
|
340
|
-
|
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."""
|
341
351
|
while True:
|
342
352
|
try:
|
343
353
|
self._reset_events()
|
344
354
|
try:
|
345
|
-
await self.
|
355
|
+
await self._initialize()
|
346
356
|
except Exception as error: # noqa: BLE001
|
347
|
-
self.
|
357
|
+
self._error_upon_initialize(error)
|
348
358
|
await sleep_dur(duration=self.sleep_restart)
|
349
359
|
else:
|
350
360
|
while True:
|
351
361
|
try:
|
352
362
|
event = next(
|
353
363
|
key
|
354
|
-
for (key, value) in self.
|
364
|
+
for (key, value) in self._events.items()
|
355
365
|
if value.is_set()
|
356
366
|
)
|
357
367
|
except StopIteration:
|
358
|
-
await self.
|
368
|
+
await self._core()
|
359
369
|
await sleep_dur(duration=self.sleep_core)
|
360
370
|
else:
|
361
|
-
|
371
|
+
self._raise_error(event)
|
372
|
+
except InfiniteLooperError:
|
373
|
+
raise
|
362
374
|
except Exception as error: # noqa: BLE001
|
363
|
-
self.
|
375
|
+
self._error_upon_core(error)
|
364
376
|
await sleep_dur(duration=self.sleep_restart)
|
365
377
|
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
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
|
370
389
|
|
371
|
-
async def
|
390
|
+
async def _initialize(self) -> None:
|
372
391
|
"""Initialize the loop."""
|
373
392
|
|
374
|
-
async def
|
375
|
-
"""Run the core."""
|
393
|
+
async def _core(self) -> None:
|
394
|
+
"""Run the core part of the loop."""
|
376
395
|
|
377
|
-
def
|
396
|
+
def _error_upon_initialize(self, error: Exception, /) -> None:
|
397
|
+
"""Handle any errors upon initializing the looper."""
|
378
398
|
_ = error
|
379
399
|
|
380
|
-
def
|
400
|
+
def _error_upon_core(self, error: Exception, /) -> None:
|
401
|
+
"""Handle any errors upon running the core function."""
|
381
402
|
_ = error
|
382
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
|
+
|
383
410
|
def _reset_events(self) -> None:
|
384
411
|
"""Reset the events."""
|
385
|
-
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)
|
386
480
|
|
387
481
|
|
388
482
|
##
|
@@ -590,6 +684,7 @@ __all__ = [
|
|
590
684
|
"AsyncService",
|
591
685
|
"EnhancedTaskGroup",
|
592
686
|
"ExceptionProcessor",
|
687
|
+
"InfiniteLooperError",
|
593
688
|
"QueueProcessor",
|
594
689
|
"StreamCommandOutput",
|
595
690
|
"UniquePriorityQueue",
|
File without changes
|
File without changes
|