dycw-utilities 0.125.26__py3-none-any.whl → 0.126.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.125.26.dist-info → dycw_utilities-0.126.0.dist-info}/METADATA +1 -1
- {dycw_utilities-0.125.26.dist-info → dycw_utilities-0.126.0.dist-info}/RECORD +6 -6
- utilities/__init__.py +1 -1
- utilities/redis.py +271 -70
- {dycw_utilities-0.125.26.dist-info → dycw_utilities-0.126.0.dist-info}/WHEEL +0 -0
- {dycw_utilities-0.125.26.dist-info → dycw_utilities-0.126.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,4 +1,4 @@
|
|
1
|
-
utilities/__init__.py,sha256=
|
1
|
+
utilities/__init__.py,sha256=aFDfK6NakMMklkGA0bazES2h83YpO5ps55Ep7UY5lgw,60
|
2
2
|
utilities/altair.py,sha256=Gpja-flOo-Db0PIPJLJsgzAlXWoKUjPU1qY-DQ829ek,9156
|
3
3
|
utilities/asyncio.py,sha256=K5Kj7rsM0nA17-b7d7mrNgPR1U_NbkfQmTruq5LBLRA,51778
|
4
4
|
utilities/atomicwrites.py,sha256=geFjn9Pwn-tTrtoGjDDxWli9NqbYfy3gGL6ZBctiqSo,5393
|
@@ -60,7 +60,7 @@ utilities/pytest_regressions.py,sha256=-SVT9647Dg6-JcdsiaDKXe3NdOmmrvGevLKWwGjxq
|
|
60
60
|
utilities/python_dotenv.py,sha256=iWcnpXbH7S6RoXHiLlGgyuH6udCupAcPd_gQ0eAenQ0,3190
|
61
61
|
utilities/random.py,sha256=lYdjgxB7GCfU_fwFVl5U-BIM_HV3q6_urL9byjrwDM8,4157
|
62
62
|
utilities/re.py,sha256=5J4d8VwIPFVrX2Eb8zfoxImDv7IwiN_U7mJ07wR2Wvs,3958
|
63
|
-
utilities/redis.py,sha256=
|
63
|
+
utilities/redis.py,sha256=BTsRqr6VL-BkGVdpj8PPOmJ04ijCrPn5LK5MaMOopfg,32916
|
64
64
|
utilities/reprlib.py,sha256=Re9bk3n-kC__9DxQmRlevqFA86pE6TtVfWjUgpbVOv0,1849
|
65
65
|
utilities/rich.py,sha256=t50MwwVBsoOLxzmeVFSVpjno4OW6Ufum32skXbV8-Bs,1911
|
66
66
|
utilities/scipy.py,sha256=X6ROnHwiUhAmPhM0jkfEh0-Fd9iRvwiqtCQMOLmOQF8,945
|
@@ -90,7 +90,7 @@ utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
|
|
90
90
|
utilities/whenever.py,sha256=jS31ZAY5OMxFxLja_Yo5Fidi87Pd-GoVZ7Vi_teqVDA,16743
|
91
91
|
utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
|
92
92
|
utilities/zoneinfo.py,sha256=-5j7IQ9nb7gR43rdgA7ms05im-XuqhAk9EJnQBXxCoQ,1874
|
93
|
-
dycw_utilities-0.
|
94
|
-
dycw_utilities-0.
|
95
|
-
dycw_utilities-0.
|
96
|
-
dycw_utilities-0.
|
93
|
+
dycw_utilities-0.126.0.dist-info/METADATA,sha256=9_nkcMMjhyueByXHhkoR268_woBxqk5jmOXg4mOPHz8,12851
|
94
|
+
dycw_utilities-0.126.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
95
|
+
dycw_utilities-0.126.0.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
|
96
|
+
dycw_utilities-0.126.0.dist-info/RECORD,,
|
utilities/__init__.py
CHANGED
utilities/redis.py
CHANGED
@@ -1,15 +1,20 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import asyncio
|
4
|
-
from
|
5
|
-
from
|
4
|
+
from asyncio import CancelledError, Event, Queue, Task, create_task
|
5
|
+
from collections.abc import AsyncIterator, Callable, Mapping
|
6
|
+
from contextlib import asynccontextmanager, suppress
|
6
7
|
from dataclasses import dataclass, field
|
8
|
+
from functools import partial
|
9
|
+
from operator import itemgetter
|
7
10
|
from typing import (
|
8
11
|
TYPE_CHECKING,
|
9
12
|
Any,
|
10
13
|
Generic,
|
11
14
|
Literal,
|
15
|
+
Self,
|
12
16
|
TypedDict,
|
17
|
+
TypeGuard,
|
13
18
|
TypeVar,
|
14
19
|
assert_never,
|
15
20
|
cast,
|
@@ -19,10 +24,9 @@ from typing import (
|
|
19
24
|
from uuid import UUID, uuid4
|
20
25
|
|
21
26
|
from redis.asyncio import Redis
|
22
|
-
from redis.asyncio.client import PubSub
|
23
27
|
from redis.typing import EncodableT
|
24
28
|
|
25
|
-
from utilities.asyncio import InfiniteQueueLooper, timeout_dur
|
29
|
+
from utilities.asyncio import EnhancedQueue, InfiniteQueueLooper, Looper, timeout_dur
|
26
30
|
from utilities.datetime import (
|
27
31
|
MILLISECOND,
|
28
32
|
SECOND,
|
@@ -31,7 +35,7 @@ from utilities.datetime import (
|
|
31
35
|
get_now,
|
32
36
|
)
|
33
37
|
from utilities.errors import ImpossibleCaseError
|
34
|
-
from utilities.functions import ensure_int, get_class_name
|
38
|
+
from utilities.functions import ensure_int, get_class_name, identity
|
35
39
|
from utilities.iterables import always_iterable, one
|
36
40
|
|
37
41
|
if TYPE_CHECKING:
|
@@ -39,14 +43,15 @@ if TYPE_CHECKING:
|
|
39
43
|
from collections.abc import (
|
40
44
|
AsyncIterator,
|
41
45
|
Awaitable,
|
42
|
-
|
46
|
+
Collection,
|
43
47
|
Iterable,
|
44
48
|
Iterator,
|
45
|
-
Mapping,
|
46
49
|
Sequence,
|
47
50
|
)
|
51
|
+
from types import TracebackType
|
48
52
|
|
49
53
|
from redis.asyncio import ConnectionPool
|
54
|
+
from redis.asyncio.client import PubSub
|
50
55
|
from redis.typing import ResponseT
|
51
56
|
|
52
57
|
from utilities.iterables import MaybeIterable
|
@@ -555,36 +560,63 @@ async def publish(
|
|
555
560
|
data: _T,
|
556
561
|
/,
|
557
562
|
*,
|
558
|
-
serializer: Callable[[_T], EncodableT],
|
563
|
+
serializer: Callable[[bytes | str | _T], EncodableT],
|
559
564
|
timeout: Duration = _PUBLISH_TIMEOUT,
|
560
565
|
) -> ResponseT: ...
|
561
566
|
@overload
|
562
567
|
async def publish(
|
563
568
|
redis: Redis,
|
564
569
|
channel: str,
|
565
|
-
data:
|
570
|
+
data: bytes | str,
|
566
571
|
/,
|
567
572
|
*,
|
568
|
-
serializer:
|
573
|
+
serializer: None = None,
|
569
574
|
timeout: Duration = _PUBLISH_TIMEOUT,
|
570
575
|
) -> ResponseT: ...
|
576
|
+
@overload
|
571
577
|
async def publish(
|
572
578
|
redis: Redis,
|
573
579
|
channel: str,
|
574
|
-
data:
|
580
|
+
data: bytes | str | _T,
|
575
581
|
/,
|
576
582
|
*,
|
577
|
-
serializer: Callable[[
|
583
|
+
serializer: Callable[[bytes | str | _T], EncodableT] | None = None,
|
584
|
+
timeout: Duration = _PUBLISH_TIMEOUT,
|
585
|
+
) -> ResponseT: ...
|
586
|
+
async def publish(
|
587
|
+
redis: Redis,
|
588
|
+
channel: str,
|
589
|
+
data: bytes | str | _T,
|
590
|
+
/,
|
591
|
+
*,
|
592
|
+
serializer: Callable[[bytes | str | _T], EncodableT] | None = None,
|
578
593
|
timeout: Duration = _PUBLISH_TIMEOUT,
|
579
594
|
) -> ResponseT:
|
580
595
|
"""Publish an object to a channel."""
|
581
|
-
|
582
|
-
|
583
|
-
)
|
596
|
+
if isinstance(data, bytes | str) and ( # skipif-ci-and-not-linux
|
597
|
+
serializer is None
|
598
|
+
):
|
599
|
+
data_use = data
|
600
|
+
elif serializer is not None: # skipif-ci-and-not-linux
|
601
|
+
data_use = serializer(data)
|
602
|
+
else: # skipif-ci-and-not-linux
|
603
|
+
raise PublishError(data=data, serializer=serializer)
|
584
604
|
async with timeout_dur(duration=timeout): # skipif-ci-and-not-linux
|
585
605
|
return await redis.publish(channel, data_use) # skipif-ci-and-not-linux
|
586
606
|
|
587
607
|
|
608
|
+
@dataclass(kw_only=True, slots=True)
|
609
|
+
class PublishError(Exception):
|
610
|
+
data: Any
|
611
|
+
serializer: Callable[[Any], EncodableT] | None = None
|
612
|
+
|
613
|
+
@override
|
614
|
+
def __str__(self) -> str:
|
615
|
+
return (
|
616
|
+
f"Unable to publish data {self.data!r} with serializer {self.serializer!r}"
|
617
|
+
)
|
618
|
+
|
619
|
+
|
588
620
|
##
|
589
621
|
|
590
622
|
|
@@ -624,6 +656,33 @@ class PublisherError(Exception):
|
|
624
656
|
return f"Error running {get_class_name(self.publisher)!r}" # skipif-ci-and-not-linux
|
625
657
|
|
626
658
|
|
659
|
+
@dataclass(kw_only=True)
|
660
|
+
class PublishService(Looper[tuple[str, _T]]):
|
661
|
+
"""Service to publish items to Redis."""
|
662
|
+
|
663
|
+
# base
|
664
|
+
freq: Duration = field(default=MILLISECOND, repr=False)
|
665
|
+
backoff: Duration = field(default=SECOND, repr=False)
|
666
|
+
empty_upon_exit: bool = field(default=True, repr=False)
|
667
|
+
# self
|
668
|
+
redis: Redis
|
669
|
+
serializer: Callable[[Any], EncodableT] | None = None
|
670
|
+
publish_timeout: Duration = SECOND
|
671
|
+
|
672
|
+
@override
|
673
|
+
async def core(self) -> None:
|
674
|
+
await super().core() # skipif-ci-and-not-linux
|
675
|
+
while not self.empty(): # skipif-ci-and-not-linux
|
676
|
+
channel, data = self.get_left_nowait()
|
677
|
+
_ = await publish(
|
678
|
+
self.redis,
|
679
|
+
channel,
|
680
|
+
cast("Any", data),
|
681
|
+
serializer=self.serializer,
|
682
|
+
timeout=self.publish_timeout,
|
683
|
+
)
|
684
|
+
|
685
|
+
|
627
686
|
##
|
628
687
|
|
629
688
|
|
@@ -632,90 +691,141 @@ _SUBSCRIBE_SLEEP: Duration = MILLISECOND
|
|
632
691
|
|
633
692
|
|
634
693
|
@overload
|
694
|
+
@asynccontextmanager
|
635
695
|
def subscribe(
|
636
|
-
|
696
|
+
redis: Redis,
|
637
697
|
channels: MaybeIterable[str],
|
698
|
+
queue: Queue[_RedisMessageSubscribe],
|
638
699
|
/,
|
639
700
|
*,
|
640
|
-
deserializer: Callable[[bytes], _T],
|
641
701
|
timeout: Duration | None = _SUBSCRIBE_TIMEOUT,
|
642
702
|
sleep: Duration = _SUBSCRIBE_SLEEP,
|
643
|
-
|
703
|
+
output: Literal["raw"],
|
704
|
+
) -> AsyncIterator[Task[None]]: ...
|
644
705
|
@overload
|
706
|
+
@asynccontextmanager
|
645
707
|
def subscribe(
|
646
|
-
|
708
|
+
redis: Redis,
|
647
709
|
channels: MaybeIterable[str],
|
710
|
+
queue: Queue[bytes],
|
648
711
|
/,
|
649
712
|
*,
|
650
|
-
deserializer: None = None,
|
651
713
|
timeout: Duration | None = _SUBSCRIBE_TIMEOUT,
|
652
714
|
sleep: Duration = _SUBSCRIBE_SLEEP,
|
653
|
-
|
654
|
-
|
655
|
-
|
715
|
+
output: Literal["bytes"],
|
716
|
+
) -> AsyncIterator[Task[None]]: ...
|
717
|
+
@overload
|
718
|
+
@asynccontextmanager
|
719
|
+
def subscribe(
|
720
|
+
redis: Redis,
|
656
721
|
channels: MaybeIterable[str],
|
722
|
+
queue: Queue[str],
|
657
723
|
/,
|
658
724
|
*,
|
659
|
-
deserializer: Callable[[bytes], _T] | None = None,
|
660
725
|
timeout: Duration | None = _SUBSCRIBE_TIMEOUT,
|
661
726
|
sleep: Duration = _SUBSCRIBE_SLEEP,
|
662
|
-
|
727
|
+
output: Literal["text"] = "text",
|
728
|
+
) -> AsyncIterator[Task[None]]: ...
|
729
|
+
@overload
|
730
|
+
@asynccontextmanager
|
731
|
+
def subscribe(
|
732
|
+
redis: Redis,
|
733
|
+
channels: MaybeIterable[str],
|
734
|
+
queue: Queue[_T],
|
735
|
+
/,
|
736
|
+
*,
|
737
|
+
timeout: Duration | None = _SUBSCRIBE_TIMEOUT,
|
738
|
+
sleep: Duration = _SUBSCRIBE_SLEEP,
|
739
|
+
output: Callable[[bytes], _T],
|
740
|
+
) -> AsyncIterator[Task[None]]: ...
|
741
|
+
@asynccontextmanager
|
742
|
+
async def subscribe(
|
743
|
+
redis: Redis,
|
744
|
+
channels: MaybeIterable[str],
|
745
|
+
queue: Queue[_RedisMessageSubscribe] | Queue[bytes] | Queue[_T],
|
746
|
+
/,
|
747
|
+
*,
|
748
|
+
timeout: Duration | None = _SUBSCRIBE_TIMEOUT,
|
749
|
+
sleep: Duration = _SUBSCRIBE_SLEEP,
|
750
|
+
output: Literal["raw", "bytes", "text"] | Callable[[bytes], _T] = "text",
|
751
|
+
) -> AsyncIterator[Task[None]]:
|
663
752
|
"""Subscribe to the data of a given channel(s)."""
|
664
753
|
channels = list(always_iterable(channels)) # skipif-ci-and-not-linux
|
665
|
-
|
666
|
-
|
754
|
+
match output: # skipif-ci-and-not-linux
|
755
|
+
case "raw":
|
756
|
+
transform = cast("Any", identity)
|
757
|
+
case "bytes":
|
758
|
+
transform = cast("Any", itemgetter("data"))
|
759
|
+
case "text":
|
760
|
+
|
761
|
+
def transform(message: _RedisMessageSubscribe, /) -> str: # pyright: ignore[reportRedeclaration]
|
762
|
+
return message["data"].decode()
|
763
|
+
|
764
|
+
case Callable() as deserialize:
|
765
|
+
|
766
|
+
def transform(message: _RedisMessageSubscribe, /) -> _T:
|
767
|
+
return deserialize(message["data"])
|
768
|
+
|
769
|
+
case _ as never:
|
770
|
+
assert_never(never)
|
771
|
+
|
772
|
+
task = create_task( # skipif-ci-and-not-linux
|
773
|
+
_subscribe_core(redis, channels, transform, queue, timeout=timeout, sleep=sleep)
|
667
774
|
)
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
775
|
+
try: # skipif-ci-and-not-linux
|
776
|
+
yield task
|
777
|
+
finally: # skipif-ci-and-not-linux
|
778
|
+
_ = task.cancel()
|
779
|
+
with suppress(CancelledError):
|
780
|
+
await task
|
674
781
|
|
675
782
|
|
676
|
-
async def
|
677
|
-
|
783
|
+
async def _subscribe_core(
|
784
|
+
redis: Redis,
|
678
785
|
channels: MaybeIterable[str],
|
786
|
+
transform: Callable[[_RedisMessageSubscribe], Any],
|
787
|
+
queue: Queue[Any],
|
679
788
|
/,
|
680
789
|
*,
|
681
790
|
timeout: Duration | None = _SUBSCRIBE_TIMEOUT,
|
682
791
|
sleep: Duration = _SUBSCRIBE_SLEEP,
|
683
|
-
) ->
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
timeout_use = (
|
697
|
-
None if timeout is None else datetime_duration_to_float(timeout)
|
792
|
+
) -> None:
|
793
|
+
timeout_use = ( # skipif-ci-and-not-linux
|
794
|
+
None if timeout is None else datetime_duration_to_float(timeout)
|
795
|
+
)
|
796
|
+
sleep_use = datetime_duration_to_float(sleep) # skipif-ci-and-not-linux
|
797
|
+
is_subscribe_message = partial( # skipif-ci-and-not-linux
|
798
|
+
_is_subscribe_message, channels={c.encode() for c in channels}
|
799
|
+
)
|
800
|
+
async with yield_pubsub(redis, channels) as pubsub: # skipif-ci-and-not-linux
|
801
|
+
while True:
|
802
|
+
message = cast(
|
803
|
+
"_RedisMessageSubscribe | _RedisMessageUnsubscribe | None",
|
804
|
+
await pubsub.get_message(timeout=timeout_use),
|
698
805
|
)
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
"_RedisMessageSubscribe | _RedisMessageUnsubscribe | None",
|
703
|
-
await pubsub.get_message(timeout=timeout_use),
|
704
|
-
)
|
705
|
-
if (
|
706
|
-
(message is not None)
|
707
|
-
and (
|
708
|
-
message["type"]
|
709
|
-
in {"subscribe", "psubscribe", "message", "pmessage"}
|
710
|
-
)
|
711
|
-
and (message["channel"] in channels_bytes)
|
712
|
-
and isinstance(message["data"], bytes)
|
713
|
-
):
|
714
|
-
yield cast("_RedisMessageSubscribe", message)
|
806
|
+
if is_subscribe_message(message):
|
807
|
+
if isinstance(queue, EnhancedQueue):
|
808
|
+
queue.put_right_nowait(transform(message))
|
715
809
|
else:
|
716
|
-
|
717
|
-
|
718
|
-
|
810
|
+
queue.put_nowait(transform(message))
|
811
|
+
else:
|
812
|
+
await asyncio.sleep(sleep_use)
|
813
|
+
|
814
|
+
|
815
|
+
def _is_subscribe_message(
|
816
|
+
message: Any, /, *, channels: Collection[bytes]
|
817
|
+
) -> TypeGuard[_RedisMessageSubscribe]:
|
818
|
+
return (
|
819
|
+
isinstance(message, Mapping)
|
820
|
+
and ("type" in message)
|
821
|
+
and (message["type"] in {"subscribe", "psubscribe", "message", "pmessage"})
|
822
|
+
and ("pattern" in message)
|
823
|
+
and ((message["pattern"] is None) or isinstance(message["pattern"], str))
|
824
|
+
and ("channel" in message)
|
825
|
+
and (message["channel"] in channels)
|
826
|
+
and ("data" in message)
|
827
|
+
and isinstance(message["data"], bytes)
|
828
|
+
)
|
719
829
|
|
720
830
|
|
721
831
|
class _RedisMessageSubscribe(TypedDict):
|
@@ -735,6 +845,95 @@ class _RedisMessageUnsubscribe(TypedDict):
|
|
735
845
|
##
|
736
846
|
|
737
847
|
|
848
|
+
@dataclass(kw_only=True)
|
849
|
+
class SubscribeService(Looper[_T]):
|
850
|
+
"""Service to subscribe to Redis."""
|
851
|
+
|
852
|
+
# base
|
853
|
+
freq: Duration = field(default=MILLISECOND, repr=False)
|
854
|
+
backoff: Duration = field(default=SECOND, repr=False)
|
855
|
+
logger: str | None = field(default=__name__, repr=False)
|
856
|
+
# self
|
857
|
+
redis: Redis
|
858
|
+
channel: str
|
859
|
+
deserializer: Callable[[bytes], _T] | None = None
|
860
|
+
subscribe_sleep: Duration = _SUBSCRIBE_SLEEP
|
861
|
+
subscribe_timeout: Duration | None = _SUBSCRIBE_TIMEOUT
|
862
|
+
_is_subscribed: Event = field(default_factory=Event, init=False, repr=False)
|
863
|
+
|
864
|
+
@override
|
865
|
+
async def __aenter__(self) -> Self:
|
866
|
+
_ = await super().__aenter__() # skipif-ci-and-not-linux
|
867
|
+
match self._is_subscribed.is_set(): # skipif-ci-and-not-linux
|
868
|
+
case True:
|
869
|
+
_ = self._debug and self._logger.debug("%s: already subscribing", self)
|
870
|
+
case False:
|
871
|
+
_ = self._debug and self._logger.debug(
|
872
|
+
"%s: starting subscription...", self
|
873
|
+
)
|
874
|
+
self._is_subscribed.set()
|
875
|
+
_ = await self._stack.enter_async_context(
|
876
|
+
subscribe(
|
877
|
+
self.redis,
|
878
|
+
self.channel,
|
879
|
+
self._queue,
|
880
|
+
sleep=self.subscribe_sleep,
|
881
|
+
timeout=self.subscribe_timeout,
|
882
|
+
output=cast(
|
883
|
+
"Any",
|
884
|
+
"text" if self.deserializer is None else self.deserializer,
|
885
|
+
),
|
886
|
+
)
|
887
|
+
)
|
888
|
+
case _ as never:
|
889
|
+
assert_never(never)
|
890
|
+
return self # skipif-ci-and-not-linux
|
891
|
+
|
892
|
+
@override
|
893
|
+
async def __aexit__(
|
894
|
+
self,
|
895
|
+
exc_type: type[BaseException] | None = None,
|
896
|
+
exc_value: BaseException | None = None,
|
897
|
+
traceback: TracebackType | None = None,
|
898
|
+
) -> None:
|
899
|
+
await super().__aexit__( # skipif-ci-and-not-linux
|
900
|
+
exc_type=exc_type, exc_value=exc_value, traceback=traceback
|
901
|
+
)
|
902
|
+
match self._is_subscribed.is_set(): # skipif-ci-and-not-linux
|
903
|
+
case True:
|
904
|
+
_ = self._debug and self._logger.debug(
|
905
|
+
"%s: stopping subscription...", self
|
906
|
+
)
|
907
|
+
self._is_subscribed.clear()
|
908
|
+
case False:
|
909
|
+
_ = self._debug and self._logger.debug(
|
910
|
+
"%s: already stopped subscription", self
|
911
|
+
)
|
912
|
+
case _ as never:
|
913
|
+
assert_never(never)
|
914
|
+
|
915
|
+
|
916
|
+
##
|
917
|
+
|
918
|
+
|
919
|
+
@asynccontextmanager
|
920
|
+
async def yield_pubsub(
|
921
|
+
redis: Redis, channels: MaybeIterable[str], /
|
922
|
+
) -> AsyncIterator[PubSub]:
|
923
|
+
"""Yield a PubSub instance subscribed to some channels."""
|
924
|
+
pubsub = redis.pubsub() # skipif-ci-and-not-linux
|
925
|
+
channels = list(always_iterable(channels)) # skipif-ci-and-not-linux
|
926
|
+
await pubsub.subscribe(*channels) # skipif-ci-and-not-linux
|
927
|
+
try: # skipif-ci-and-not-linux
|
928
|
+
yield pubsub
|
929
|
+
finally: # skipif-ci-and-not-linux
|
930
|
+
await pubsub.unsubscribe(*channels)
|
931
|
+
await pubsub.aclose()
|
932
|
+
|
933
|
+
|
934
|
+
##
|
935
|
+
|
936
|
+
|
738
937
|
_HOST = "localhost"
|
739
938
|
_PORT = 6379
|
740
939
|
|
@@ -812,14 +1011,16 @@ _ = _TestRedis
|
|
812
1011
|
|
813
1012
|
|
814
1013
|
__all__ = [
|
1014
|
+
"PublishService",
|
815
1015
|
"Publisher",
|
816
1016
|
"PublisherError",
|
817
1017
|
"RedisHashMapKey",
|
818
1018
|
"RedisKey",
|
1019
|
+
"SubscribeService",
|
819
1020
|
"publish",
|
820
1021
|
"redis_hash_map_key",
|
821
1022
|
"redis_key",
|
822
1023
|
"subscribe",
|
823
|
-
"
|
1024
|
+
"yield_pubsub",
|
824
1025
|
"yield_redis",
|
825
1026
|
]
|
File without changes
|
File without changes
|