dycw-utilities 0.116.4__py3-none-any.whl → 0.116.6__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.
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-utilities
3
- Version: 0.116.4
3
+ Version: 0.116.6
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.17; extra == 'test'
9
+ Requires-Dist: hypothesis<6.132,>=6.131.19; 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'
@@ -43,7 +43,7 @@ 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
45
45
  Provides-Extra: zzz-test-cryptography
46
- Requires-Dist: cryptography<44.1,>=44.0.2; extra == 'zzz-test-cryptography'
46
+ Requires-Dist: cryptography<46.0,>=45.0.2; extra == 'zzz-test-cryptography'
47
47
  Provides-Extra: zzz-test-cvxpy
48
48
  Requires-Dist: cvxpy<1.7,>=1.6.5; extra == 'zzz-test-cvxpy'
49
49
  Provides-Extra: zzz-test-dataclasses
@@ -80,7 +80,7 @@ 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.17; extra == 'zzz-test-hypothesis'
83
+ Requires-Dist: hypothesis<6.132,>=6.131.19; 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'
@@ -1,7 +1,7 @@
1
- utilities/__init__.py,sha256=b6XtB99cyX4IZ76DFvF8Igz1IiLess5LdFIaswgC0Ko,60
1
+ utilities/__init__.py,sha256=SnbbEgMhiQnik2PTXQdCVRriOVeUKTr4S6_qZSLJwj0,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=7qisjTnEGEKcguy_STXN6qOXij_yzTvcQ5fIbrPeyZ8,23305
4
+ utilities/asyncio.py,sha256=HX5iRmQCbipkbeUgT9Y47KxYvzdqJogysjWygMe5saA,23671
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
@@ -59,7 +59,7 @@ utilities/pytest_regressions.py,sha256=-SVT9647Dg6-JcdsiaDKXe3NdOmmrvGevLKWwGjxq
59
59
  utilities/python_dotenv.py,sha256=iWcnpXbH7S6RoXHiLlGgyuH6udCupAcPd_gQ0eAenQ0,3190
60
60
  utilities/random.py,sha256=lYdjgxB7GCfU_fwFVl5U-BIM_HV3q6_urL9byjrwDM8,4157
61
61
  utilities/re.py,sha256=5J4d8VwIPFVrX2Eb8zfoxImDv7IwiN_U7mJ07wR2Wvs,3958
62
- utilities/redis.py,sha256=0LAmbQFkwbJ3w6teTqGw1fNCSs1tDivL3DVAqob0S5c,26732
62
+ utilities/redis.py,sha256=P766qKT2SkDeKa9PpPEZIPCnAc2QDIi35ow2EEHGd20,27225
63
63
  utilities/reprlib.py,sha256=Re9bk3n-kC__9DxQmRlevqFA86pE6TtVfWjUgpbVOv0,1849
64
64
  utilities/rich.py,sha256=t50MwwVBsoOLxzmeVFSVpjno4OW6Ufum32skXbV8-Bs,1911
65
65
  utilities/scipy.py,sha256=X6ROnHwiUhAmPhM0jkfEh0-Fd9iRvwiqtCQMOLmOQF8,945
@@ -88,7 +88,7 @@ utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
88
88
  utilities/whenever.py,sha256=iLRP_-8CZtBpHKbGZGu-kjSMg1ZubJ-VSmgSy7Eudxw,17787
89
89
  utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
90
90
  utilities/zoneinfo.py,sha256=-Xm57PMMwDTYpxJdkiJG13wnbwK--I7XItBh5WVhD-o,1874
91
- dycw_utilities-0.116.4.dist-info/METADATA,sha256=P8hMpiUHgXKsNKT5dTwM4LbirjzE57NNYNB7KdmeZVQ,12943
92
- dycw_utilities-0.116.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
93
- dycw_utilities-0.116.4.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
94
- dycw_utilities-0.116.4.dist-info/RECORD,,
91
+ dycw_utilities-0.116.6.dist-info/METADATA,sha256=8P0vlfhUa8DiK1Pnx0T7YwYBvFO5S7xgFgV7PK6CqM0,12943
92
+ dycw_utilities-0.116.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
93
+ dycw_utilities-0.116.6.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
94
+ dycw_utilities-0.116.6.dist-info/RECORD,,
utilities/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.116.4"
3
+ __version__ = "0.116.6"
utilities/asyncio.py CHANGED
@@ -490,6 +490,9 @@ class InfiniteQueueLooper(InfiniteLooper[THashable], Generic[THashable, _T]):
490
490
  super().__post_init__()
491
491
  self._queue = self.queue_type()
492
492
 
493
+ def __len__(self) -> int:
494
+ return self._queue.qsize()
495
+
493
496
  @override
494
497
  async def _core(self) -> None:
495
498
  """Run the core part of the loop."""
@@ -505,10 +508,19 @@ class InfiniteQueueLooper(InfiniteLooper[THashable], Generic[THashable, _T]):
505
508
  async def _process_items(self, *items: _T) -> None:
506
509
  """Process the items."""
507
510
 
511
+ def empty(self) -> bool:
512
+ """Check if the queue is empty."""
513
+ return self._queue.empty()
514
+
508
515
  def put_items_nowait(self, *items: _T) -> None:
509
516
  """Put items into the queue."""
510
517
  put_items_nowait(items, self._queue)
511
518
 
519
+ async def run_until_empty(self) -> None:
520
+ """Run until the queue is empty."""
521
+ while not self.empty():
522
+ await self._process_items(*get_items_nowait(self._queue))
523
+
512
524
  @override
513
525
  def _error_upon_core(self, error: Exception, /) -> None:
514
526
  """Handle any errors upon running the core function."""
utilities/redis.py CHANGED
@@ -19,6 +19,7 @@ from typing import (
19
19
  from uuid import UUID, uuid4
20
20
 
21
21
  from redis.asyncio import Redis
22
+ from redis.asyncio.client import PubSub
22
23
  from redis.typing import EncodableT
23
24
 
24
25
  from utilities.asyncio import InfiniteQueueLooper, QueueProcessor, timeout_dur
@@ -46,7 +47,6 @@ if TYPE_CHECKING:
46
47
  )
47
48
 
48
49
  from redis.asyncio import ConnectionPool
49
- from redis.asyncio.client import PubSub
50
50
  from redis.typing import ResponseT
51
51
 
52
52
  from utilities.iterables import MaybeIterable
@@ -649,7 +649,7 @@ _SUBSCRIBE_SLEEP: Duration = MILLISECOND
649
649
 
650
650
  @overload
651
651
  def subscribe(
652
- pubsub: PubSub,
652
+ redis_or_pubsub: Redis | PubSub,
653
653
  channels: MaybeIterable[str],
654
654
  /,
655
655
  *,
@@ -659,7 +659,7 @@ def subscribe(
659
659
  ) -> AsyncIterator[_T]: ...
660
660
  @overload
661
661
  def subscribe(
662
- pubsub: PubSub,
662
+ redis_or_pubsub: Redis | PubSub,
663
663
  channels: MaybeIterable[str],
664
664
  /,
665
665
  *,
@@ -668,7 +668,7 @@ def subscribe(
668
668
  sleep: Duration = _SUBSCRIBE_SLEEP,
669
669
  ) -> AsyncIterator[bytes]: ...
670
670
  async def subscribe( # pyright: ignore[reportInconsistentOverload]
671
- pubsub: PubSub,
671
+ redis_or_pubsub: Redis | PubSub,
672
672
  channels: MaybeIterable[str],
673
673
  /,
674
674
  *,
@@ -679,7 +679,7 @@ async def subscribe( # pyright: ignore[reportInconsistentOverload]
679
679
  """Subscribe to the data of a given channel(s)."""
680
680
  channels = list(always_iterable(channels)) # skipif-ci-and-not-linux
681
681
  messages = subscribe_messages( # skipif-ci-and-not-linux
682
- pubsub, channels, timeout=timeout, sleep=sleep
682
+ redis_or_pubsub, channels, timeout=timeout, sleep=sleep
683
683
  )
684
684
  if deserializer is None: # skipif-ci-and-not-linux
685
685
  async for message in messages:
@@ -690,7 +690,7 @@ async def subscribe( # pyright: ignore[reportInconsistentOverload]
690
690
 
691
691
 
692
692
  async def subscribe_messages(
693
- pubsub: PubSub,
693
+ redis_or_pubsub: Redis | PubSub,
694
694
  channels: MaybeIterable[str],
695
695
  /,
696
696
  *,
@@ -698,28 +698,40 @@ async def subscribe_messages(
698
698
  sleep: Duration = _SUBSCRIBE_SLEEP,
699
699
  ) -> AsyncIterator[_RedisMessageSubscribe]:
700
700
  """Subscribe to the messages of a given channel(s)."""
701
- channels = list(always_iterable(channels)) # skipif-ci-and-not-linux
702
- for channel in channels: # skipif-ci-and-not-linux
703
- await pubsub.subscribe(channel)
704
- channels_bytes = [c.encode() for c in channels] # skipif-ci-and-not-linux
705
- timeout_use = ( # skipif-ci-and-not-linux
706
- None if timeout is None else datetime_duration_to_float(timeout)
707
- )
708
- sleep_use = datetime_duration_to_float(sleep) # skipif-ci-and-not-linux
709
- while True: # skipif-ci-and-not-linux
710
- message = cast(
711
- "_RedisMessageSubscribe | _RedisMessageUnsubscribe | None",
712
- await pubsub.get_message(timeout=timeout_use),
713
- )
714
- if (
715
- (message is not None)
716
- and (message["type"] in {"subscribe", "psubscribe", "message", "pmessage"})
717
- and (message["channel"] in channels_bytes)
718
- and isinstance(message["data"], bytes)
719
- ):
720
- yield cast("_RedisMessageSubscribe", message)
721
- else:
722
- await asyncio.sleep(sleep_use)
701
+ match redis_or_pubsub: # skipif-ci-and-not-linux
702
+ case Redis() as redis:
703
+ async for msg in subscribe_messages(
704
+ redis.pubsub(), channels, timeout=timeout, sleep=sleep
705
+ ):
706
+ yield msg
707
+ case PubSub() as pubsub:
708
+ channels = list(always_iterable(channels))
709
+ for channel in channels:
710
+ await pubsub.subscribe(channel)
711
+ channels_bytes = [c.encode() for c in channels]
712
+ timeout_use = (
713
+ None if timeout is None else datetime_duration_to_float(timeout)
714
+ )
715
+ sleep_use = datetime_duration_to_float(sleep)
716
+ while True:
717
+ message = cast(
718
+ "_RedisMessageSubscribe | _RedisMessageUnsubscribe | None",
719
+ await pubsub.get_message(timeout=timeout_use),
720
+ )
721
+ if (
722
+ (message is not None)
723
+ and (
724
+ message["type"]
725
+ in {"subscribe", "psubscribe", "message", "pmessage"}
726
+ )
727
+ and (message["channel"] in channels_bytes)
728
+ and isinstance(message["data"], bytes)
729
+ ):
730
+ yield cast("_RedisMessageSubscribe", message)
731
+ else:
732
+ await asyncio.sleep(sleep_use)
733
+ case _ as never:
734
+ assert_never(never)
723
735
 
724
736
 
725
737
  class _RedisMessageSubscribe(TypedDict):