1
1
import asyncio
2
2
from typing import Callable , Optional , Coroutine , Any , List , Union , Awaitable
3
3
4
+ from redis .asyncio .client import PubSubHandler
4
5
from redis .asyncio .multidb .command_executor import DefaultCommandExecutor
5
6
from redis .asyncio .multidb .database import AsyncDatabase , Databases
6
7
from redis .asyncio .multidb .failure_detector import AsyncFailureDetector
10
11
from redis .background import BackgroundScheduler
11
12
from redis .commands import AsyncRedisModuleCommands , AsyncCoreCommands
12
13
from redis .multidb .exception import NoValidDatabaseException
13
- from redis .typing import KeyT
14
+ from redis .typing import KeyT , EncodableT , ChannelT
14
15
15
16
16
17
class MultiDBClient (AsyncRedisModuleCommands , AsyncCoreCommands ):
@@ -222,6 +223,17 @@ async def transaction(
222
223
watch_delay = watch_delay ,
223
224
)
224
225
226
+ async def pubsub (self , ** kwargs ):
227
+ """
228
+ Return a Publish/Subscribe object. With this object, you can
229
+ subscribe to channels and listen for messages that get published to
230
+ them.
231
+ """
232
+ if not self .initialized :
233
+ await self .initialize ()
234
+
235
+ return PubSub (self , ** kwargs )
236
+
225
237
async def _check_databases_health (
226
238
self ,
227
239
on_error : Optional [Callable [[Exception ], Coroutine [Any , Any , None ]]] = None ,
@@ -340,4 +352,123 @@ async def execute(self) -> List[Any]:
340
352
try :
341
353
return await self ._client .command_executor .execute_pipeline (tuple (self ._command_stack ))
342
354
finally :
343
- await self .reset ()
355
+ await self .reset ()
356
+
357
+ class PubSub :
358
+ """
359
+ PubSub object for multi database client.
360
+ """
361
+ def __init__ (self , client : MultiDBClient , ** kwargs ):
362
+ """Initialize the PubSub object for a multi-database client.
363
+
364
+ Args:
365
+ client: MultiDBClient instance to use for pub/sub operations
366
+ **kwargs: Additional keyword arguments to pass to the underlying pubsub implementation
367
+ """
368
+
369
+ self ._client = client
370
+ self ._client .command_executor .pubsub (** kwargs )
371
+
372
+ async def __aenter__ (self ) -> "PubSub" :
373
+ return self
374
+
375
+ async def __aexit__ (self , exc_type , exc_value , traceback ) -> None :
376
+ await self .aclose ()
377
+
378
+ async def aclose (self ):
379
+ return await self ._client .command_executor .execute_pubsub_method ('aclose' )
380
+
381
+ @property
382
+ def subscribed (self ) -> bool :
383
+ return self ._client .command_executor .active_pubsub .subscribed
384
+
385
+ async def execute_command (self , * args : EncodableT ):
386
+ return await self ._client .command_executor .execute_pubsub_method ('execute_command' , * args )
387
+
388
+ async def psubscribe (self , * args : ChannelT , ** kwargs : PubSubHandler ):
389
+ """
390
+ Subscribe to channel patterns. Patterns supplied as keyword arguments
391
+ expect a pattern name as the key and a callable as the value. A
392
+ pattern's callable will be invoked automatically when a message is
393
+ received on that pattern rather than producing a message via
394
+ ``listen()``.
395
+ """
396
+ return await self ._client .command_executor .execute_pubsub_method (
397
+ 'psubscribe' ,
398
+ * args ,
399
+ ** kwargs
400
+ )
401
+
402
+ async def punsubscribe (self , * args : ChannelT ):
403
+ """
404
+ Unsubscribe from the supplied patterns. If empty, unsubscribe from
405
+ all patterns.
406
+ """
407
+ return await self ._client .command_executor .execute_pubsub_method (
408
+ 'punsubscribe' ,
409
+ * args
410
+ )
411
+
412
+ async def subscribe (self , * args : ChannelT , ** kwargs : Callable ):
413
+ """
414
+ Subscribe to channels. Channels supplied as keyword arguments expect
415
+ a channel name as the key and a callable as the value. A channel's
416
+ callable will be invoked automatically when a message is received on
417
+ that channel rather than producing a message via ``listen()`` or
418
+ ``get_message()``.
419
+ """
420
+ return await self ._client .command_executor .execute_pubsub_method (
421
+ 'subscribe' ,
422
+ * args ,
423
+ ** kwargs
424
+ )
425
+
426
+ async def unsubscribe (self , * args ):
427
+ """
428
+ Unsubscribe from the supplied channels. If empty, unsubscribe from
429
+ all channels
430
+ """
431
+ return await self ._client .command_executor .execute_pubsub_method (
432
+ 'unsubscribe' ,
433
+ * args
434
+ )
435
+
436
+ async def get_message (
437
+ self , ignore_subscribe_messages : bool = False , timeout : Optional [float ] = 0.0
438
+ ):
439
+ """
440
+ Get the next message if one is available, otherwise None.
441
+
442
+ If timeout is specified, the system will wait for `timeout` seconds
443
+ before returning. Timeout should be specified as a floating point
444
+ number or None to wait indefinitely.
445
+ """
446
+ return await self ._client .command_executor .execute_pubsub_method (
447
+ 'get_message' ,
448
+ ignore_subscribe_messages = ignore_subscribe_messages , timeout = timeout
449
+ )
450
+
451
+ async def run (
452
+ self ,
453
+ * ,
454
+ exception_handler : Optional ["PSWorkerThreadExcHandlerT" ] = None ,
455
+ poll_timeout : float = 1.0 ,
456
+ ) -> None :
457
+ """Process pub/sub messages using registered callbacks.
458
+
459
+ This is the equivalent of :py:meth:`redis.PubSub.run_in_thread` in
460
+ redis-py, but it is a coroutine. To launch it as a separate task, use
461
+ ``asyncio.create_task``:
462
+
463
+ >>> task = asyncio.create_task(pubsub.run())
464
+
465
+ To shut it down, use asyncio cancellation:
466
+
467
+ >>> task.cancel()
468
+ >>> await task
469
+ """
470
+ return await self ._client .command_executor .execute_pubsub_run (
471
+ exception_handler = exception_handler ,
472
+ sleep_time = poll_timeout ,
473
+ pubsub = self
474
+ )
0 commit comments