
    AHjV%             	         U d Z ddlmZ ddlZddlZddlZddlZddlmZm	Z	 ddl
mZ ddlmZ ddlmZ ddlmZmZmZmZ dd	lmZ dd
lmZ ddlmZmZmZ ddlmZ  ej>                  e       Z!erddlm"Z" ee#e$ddddddf   Z%de&d<   edgdf   Z' G d d      Z(d/dZ)e G d d             Z* G d d      Z+ G d d      Z, G d d      Z- G d d      Z. G d d      Z/ G d  d      Z0 G d! d"e      Z1d0d#Z2	 	 	 	 d1d$Z3 G d% d&e      Z4 G d' d(e4      Z5 G d) d*ejl                        Z7 G d+ d,e5      Z8 G d- d.e5      Z9y)2a=  
Redis Keyspace Notifications support for redis-py.

This module provides utilities for subscribing to and parsing Redis keyspace
notifications. Keyspace notifications allow clients to receive events when
keys are modified in Redis.

Note: Keyspace notifications must be enabled on the Redis server via the
``notify-keyspace-events`` configuration option. This is a server-side
configuration that should be done by your infrastructure/operations team.
See the Redis documentation for details:
https://redis.io/docs/latest/develop/pubsub/keyspace-notifications/

Standalone Redis Example:
    >>> from redis import Redis
    >>> from redis.keyspace_notifications import (
    ...     KeyspaceNotifications,
    ...     KeyspaceChannel,
    ...     EventType,
    ... )
    >>>
    >>> r = Redis()
    >>> # Server must have notify-keyspace-events configured (e.g., "KEA")
    >>> ksn = KeyspaceNotifications(r)
    >>>
    >>> # Subscribe using Channel class (patterns auto-detected)
    >>> channel = KeyspaceChannel("user:*")
    >>> ksn.subscribe(channel)
    >>>
    >>> # Or use convenience methods for specific event types
    >>> ksn.subscribe_keyevent(EventType.SET)
    >>>
    >>> for notification in ksn.listen():
    ...     print(f"Key: {notification.key}, Event: {notification.event_type}")

Redis Cluster Example:
    >>> from redis.cluster import RedisCluster
    >>> from redis.keyspace_notifications import (
    ...     ClusterKeyspaceNotifications,
    ...     KeyspaceChannel,
    ...     EventType,
    ... )
    >>>
    >>> rc = RedisCluster(host="localhost", port=7000)
    >>> # Server must have notify-keyspace-events configured (e.g., "KEA")
    >>> ksn = ClusterKeyspaceNotifications(rc)
    >>>
    >>> # Subscribe using Channel class (patterns auto-detected)
    >>> channel = KeyspaceChannel("user:*")
    >>> ksn.subscribe(channel)
    >>>
    >>> # Or use convenience methods for specific event types
    >>> ksn.subscribe_keyevent(EventType.SET)
    >>>
    >>> for notification in ksn.listen():
    ...     print(f"Key: {notification.key}, Event: {notification.event_type}")
    )annotationsN)ABCabstractmethod)Callable)	dataclass)Enum)TYPE_CHECKINGAnyClassVarUnion)Redis)RedisCluster)ConnectionError
RedisErrorTimeoutError)safe_str)	TypeAliasKeyspaceChannelKeyeventChannelSubkeyspaceChannelSubkeyeventChannelSubkeyspaceitemChannelSubkeyspaceeventChannelr   ChannelTKeyNotificationc                  X   e Zd ZdZdZdZdZdZdZdZ	dZ
d	Zd
ZdZdZdZdZdZdZdZdZdZdZdZdZdZdZdZdZdZdZdZdZ dZ!d Z"d!Z#d"Z$d#Z%d$Z&d%Z'd&Z(d'Z)d(Z*d)Z+d*Z,d+Z-d,Z.d-Z/d.Z0d/Z1d0Z2d1Z3d2Z4d3Z5d4Z6d5Z7d6Z8d7Z9d8Z:d9Z;d:Z<d;Z=d<Z>d=Z?d>Z@d?ZAd@ZBdAZCdBZDdCZEdDZFdEZGdFZHdGZIdHZJdIZKdJZLdKZMdLZNdMZOdNZPdOZQdPZRdQZSdRZTdSZUyT)U	EventTypez
    Common Redis keyspace notification event type constants.

    These are provided for convenience and IDE autocomplete. You can use
    any string as an event type - new Redis events will work without
    needing library updates.
    setsetexsetnxsetrangeincrincrbyincrbyfloatdecrdecrbyappenddelunlinkrenamerename_from	rename_tocopy_tomoverestoreexpireexpireatpexpire	pexpireatexpiredpersistevictedlpushrpushlpoprpoplinsertlsetltrimlmoveblpopbrpopblmovesaddsremspopsmovesinterstoresunionstore
sdiffstorezaddzincrbyzremzremrangebyrankzremrangebyscorezremrangebylexzpopminzpopmaxbzpopminbzpopmaxzinterstorezunionstore
zdiffstorezrangestorehsethsetnxhdelhincrbyhincrbyfloatxaddxtrimxdelzxgroup-createzxgroup-createconsumerzxgroup-delconsumerzxgroup-destroyzxgroup-setidxsetidxclaim
xautoclaim
xreadgroupnew	sortstoregetexgetdelsetifgtsetifltsetifeqsetifneN)V__name__
__module____qualname____doc__SETSETEXSETNXSETRANGEINCRINCRBYINCRBYFLOATDECRDECRBYAPPENDDELUNLINKRENAMERENAME_FROM	RENAME_TOCOPY_TOMOVERESTOREEXPIREEXPIREATPEXPIRE	PEXPIREATEXPIREDPERSISTEVICTEDLPUSHRPUSHLPOPRPOPLINSERTLSETLTRIMLMOVEBLPOPBRPOPBLMOVESADDSREMSPOPSMOVESINTERSTORESUNIONSTORE
SDIFFSTOREZADDZINCRBYZREMZREMRANGEBYRANKZREMRANGEBYSCOREZREMRANGEBYLEXZPOPMINZPOPMAXBZPOPMINBZPOPMAXZINTERSTOREZUNIONSTORE
ZDIFFSTOREZRANGESTOREHSETHSETNXHDELHINCRBYHINCRBYFLOATXADDXTRIMXDELXGROUP_CREATEXGROUP_CREATECONSUMERXGROUP_DELCONSUMERXGROUP_DESTROYXGROUP_SETIDXSETIDXCLAIM
XAUTOCLAIM
XREADGROUPNEW	SORTSTOREGETEXGETDELSETIFGTSETIFLTSETIFEQSETIFNE     T/root/tools/cai/cai_env/lib/python3.12/site-packages/redis/keyspace_notifications.pyr   r   o   s    CEEHDFKDFF CFFKIGDG FHGIGG G EEDDGDEEEEF DDDEKKJ DGD'O)%NGGHHKKJK DFDG!L DED#M3-%N!LFFJJ CIEFGGGGr   r   c                    g }d}|t        |       k  rk| j                  d|      }t        | ||       }|dz   }|j                  | |||z           ||z   }|t        |       k  r| |   dk(  r|dz  }|t        |       k  rk|S )zParse a length-prefixed subkey list.

    The wire format is ``<len>:<subkey>[,<len>:<subkey>...]``.

    Returns:
        A list of subkey strings.
    r   :   ,)lenindexintr'   )ssubkeysposcolonlengthstarts         r   _parse_length_prefixed_subkeysr      s     G
C
A,S!Qs5\"	q01fnQ<AcFcM1HC A, Nr   c                     e Zd ZU dZ ej
                  d      Zded<    ej
                  d      Zded<    ej
                  d      Z	ded<    ej
                  d	      Z
ded
<    ej
                  dej                        Zded<    ej
                  d      Zded<   ded<   ded<   ded<   ded<   ded<   ded<   dZded<   d Ze	 d 	 	 	 	 	 d!d       Ze	 d 	 	 	 	 	 	 	 d"d       Ze	 d 	 	 	 	 	 	 	 d#d       Zd$dZy)%r   a  
    Represents a parsed Redis keyspace, keyevent, or subkey notification.

    This class provides convenient access to the notification details
    like key, event type, database number, and affected subkeys.

    Attributes:
        key: The Redis key that was affected (for keyspace notifications)
             or the key name from the message data (for keyevent notifications)
        event_type: The type of operation that occurred (e.g., "set", "del").
                   This is a plain string, so new Redis events work automatically.
                   Compare against EventType constants or any string.
        database: The database number where the event occurred
        channel: The original channel name
        is_keyspace: True if this is a keyspace notification, False for keyevent
        data: The raw data payload from the notification message.
        subkeys: List of affected subkeys (fields) for subkey notifications.
                Empty list for regular keyspace/keyevent notifications.
    z^__keyspace@(\d+|\*)__:(.+)$zClassVar[re.Pattern]_KEYSPACE_PATTERNz^__keyevent@(\d+|\*)__:(.+)$_KEYEVENT_PATTERNz^__subkeyspace@(\d+|\*)__:(.+)$_SUBKEYSPACE_PATTERNz^__subkeyevent@(\d+|\*)__:(.+)$_SUBKEYEVENT_PATTERNz#^__subkeyspaceitem@(\d+|\*)__:(.+)$_SUBKEYSPACEITEM_PATTERNz$^__subkeyspaceevent@(\d+|\*)__:(.+)$_SUBKEYSPACEEVENT_PATTERNstrkey
event_typer   databasechannelboolis_keyspacedataN	list[str]r   c                ,    | j                   g | _         y y N)r   selfs    r   __post_init__zKeyNotification.__post_init__'  s    <<DL  r   c                    |y|j                  d      }|dvry|j                  d      }|j                  d      }||y| j                  |||      S )a  
        Parse a pub/sub message into a KeyNotification.

        Args:
            message: A pub/sub message dict with 'channel', 'data', and 'type' keys
            key_prefix: Optional prefix to filter and strip from keys.
                       If provided, only notifications for keys starting with
                       this prefix will be returned, and the prefix will be
                       stripped from the key.

        Returns:
            A KeyNotification if the message is a valid keyspace/keyevent
            notification, None otherwise.

        Example:
            >>> message = {
            ...     'type': 'pmessage',
            ...     'pattern': '__keyspace@0__:user:*',
            ...     'channel': '__keyspace@0__:user:123',
            ...     'data': 'set'
            ... }
            >>> notification = KeyNotification.from_message(message)
            >>> notification.key
            'user:123'
            >>> notification.event_type
            'set'
        Ntype)messagepmessager   r   )get	try_parse)clsr   
key_prefixmsg_typer   r   s         r   from_messagezKeyNotification.from_message+  sb    B ?;;v&22++i({{6"?dl}}WdJ77r   c                T    t        |      }t        |      }| j                  |||      S )a  
        Try to parse a channel and data into a KeyNotification.

        This is a lower-level method that takes the channel and data directly,
        useful when working with callback-based subscription handlers.

        Args:
            channel: The channel name (e.g., "__keyspace@0__:mykey")
            data: The message data (event type for keyspace, key for keyevent)
            key_prefix: Optional prefix to filter and strip from keys

        Returns:
            A KeyNotification if valid, None otherwise.
        )r   _parse)r   r   r   r   s       r   r   zKeyNotification.try_parse[  s*    * 7#~zz'444r   c           	        |rt        |      nd}| j                  j                  |      }|rW|j                         \  }}|dk7  rt	        |      nd}|}|r |j                  |      sy|t        |      d } | ||||d|      S | j                  j                  |      }|rW|j                         \  }}|dk7  rt	        |      nd}|}|r |j                  |      sy|t        |      d } | ||||d|      S | j                  j                  |      }|r}|j                         \  }}|dk7  rt	        |      nd}|j                  d      }	|d|	 }t        ||	dz   d       }
|r |j                  |      sy|t        |      d } | ||||d||
	      S | j                  j                  |      }|r|j                         \  }}|dk7  rt	        |      nd}|j                  d
      }t	        |d|       }|dz   }||||z    }||z   dz   }t        ||d       }
|r |j                  |      sy|t        |      d } | ||||d||
	      S | j                  j                  |      }|rw|j                         \  }}|dk7  rt	        |      nd}|j                  d      }|d| }||dz   d }|}|r |j                  |      sy|t        |      d } | ||||d||g	      S | j                  j                  |      }|r|j                         \  }}|dk7  rt	        |      nd}|j                  d      }	|d|	 }||	dz   d }t        |      }
|r |j                  |      sy|t        |      d } | ||||d||
	      S y)zInternal parsing logic.N*T)r   r   r   r   r   r   F|r   )r   r   r   r   r   r   r   r   
)r   r   matchgroupsr   
startswithr   r   r   r   r   r   r   r   )r   r   r   r   r   db_strr   r   r   pipe_idxr   	colon_idxkey_len	key_startsubkeys_startkey_and_subkeynewline_idxsubkeyevent_and_keys                      r   r   zKeyNotification._parseu  s    .8Xj)T
 %%++G4,,.KFC&,ms6{HJ ~~j1#j/+,%!   %%++G4!&FJ&,ms6{HC ~~j1#j/+,%!!  ((..w7,,.KFC&,ms6{Hzz#HixJ4T(Q,.5IJG~~j1#j/+,%!   ((..w7!&FJ&,ms6{H

3I$z	*+G!AIy9w#67C%/!3M4T-.5IJG~~j1#j/+,%!!  ,,227;%*\\^"FN&,ms6{H(..t4K +.C#K!O$56FJ~~j1#j/+,%!   --33G<$)LLN!FM&,ms6{H$**3/H&y1J1/C4T:G~~j1#j/+,%!!  r   c                N    t        |      }| j                  j                  |      S )z.Check if the key starts with the given prefix.)r   r   r   )r   prefixs     r   key_starts_withzKeyNotification.key_starts_with  s!    &!xx""6**r   r   )r   zdict[str, Any] | Noner   str | bytes | NonereturnKeyNotification | None)r   str | bytesr   r  r   r  r	  r
  )r   r   r   r   r   r  r	  r
  )r  r  r	  r   )rk   rl   rm   rn   recompiler   __annotations__r   r   r   DOTALLr   r   r   r   classmethodr   r   r   r  r   r   r   r   r      s   , /9bjj'/+  /9bjj'/+  2<*2.  2<*2.  6@RZZ.		62  7Abjj/73  
HOML
IGY  *.-8&-8 '-8 
 	-8 -8^ 
 *.	55 5 '	5
 
 5 52 
 *.	aa a '	a
 
 a aF+r   c                  d    e Zd ZU dZdZded<   dddZddZedd       Z	ddZ
dd	Zdd
ZddZy)r   a  
    Represents a keyspace notification channel for subscribing to events on keys.

    Keyspace notifications publish the event type (e.g., "set", "del") as the message
    when a key matching the pattern is modified.

    This class can be used directly with subscribe()/psubscribe() as it implements
    __str__ to return the channel string.

    Attributes:
        key_or_pattern: The key or pattern to monitor (use '*' for wildcards)
        db: The database number (defaults to 0, the only database in Redis Cluster)
        is_pattern: Whether this channel contains wildcards

    Examples:
        >>> channel = KeyspaceChannel("user:123", db=0)
        >>> str(channel)
        '__keyspace@0__:user:123'

        >>> # Pattern subscription (wildcards are auto-detected)
        >>> channel = KeyspaceChannel("user:*", db=0)
        >>> str(channel)
        '__keyspace@0__:user:*'

        >>> # Use with KeyspaceNotifications
        >>> notifications = KeyspaceNotifications(redis_client)
        >>> notifications.subscribe(channel)
    z__keyspace@ClassVar[str]PREFIXc                J    || _         || _        | j                         | _        y)z
        Create a keyspace notification channel.

        Args:
            key_or_pattern: The key or pattern to monitor. Use '*' for wildcards.
            db: The database number. Defaults to 0 (the only database in Redis Cluster).
        Nkey_or_patterndb_build_channel_string_channel_strr   r  r  s      r   __init__zKeyspaceChannel.__init__D  s$     - 668r   c                P    | j                    | j                   d| j                   S N__:r  r  r  r   s    r   r  z%KeyspaceChannel._build_channel_stringP  &    ++twwis4+>+>*?@@r   c                ,    t        | j                        S zCCheck if this channel contains wildcards and should use psubscribe._is_patternr  r   s    r   
is_patternzKeyspaceChannel.is_patternS  s     4..//r   c                    | j                   S r   r  r   s    r   __str__zKeyspaceChannel.__str__X         r   c                <    d| j                   d| j                   dS )NzKeyspaceChannel(, db=)r  r  r   s    r   __repr__zKeyspaceChannel.__repr__[  s"    !$"5"5!8dggYaHHr   c                    t        |t              r| j                  |j                  k(  S t        |t              r| j                  |k(  S t        S r   )
isinstancer   r  r   NotImplementedr   others     r   __eq__zKeyspaceChannel.__eq__^  D    e_-$$(:(:::eS!$$--r   c                ,    t        | j                        S r   hashr  r   s    r   __hash__zKeyspaceChannel.__hash__e      D%%&&r   Nr   r  r   r  r   r	  r   r	  r   r3  objectr	  r   r	  r   rk   rl   rm   rn   r  r  r  r  propertyr%  r(  r.  r4  r9  r   r   r   r   r   $  sG    : *FM)
9A 0 0!I'r   c                  x    e Zd ZU dZdZded<   dddZddZedd       Z	e
ddd       Zdd	Zdd
ZddZddZy)r   a  
    Represents a keyevent notification channel for subscribing to event types.

    Keyevent notifications publish the key name as the message when the specified
    event type occurs on any key.

    This class can be used directly with subscribe()/psubscribe() as it implements
    __str__ to return the channel string.

    Attributes:
        event: The event type to monitor
        db: The database number (defaults to 0, the only database in Redis Cluster)
        is_pattern: Whether this channel contains wildcards

    Examples:
        >>> channel = KeyeventChannel(EventType.SET, db=0)
        >>> str(channel)
        '__keyevent@0__:set'

        >>> channel = KeyeventChannel.all_events(db=0)
        >>> str(channel)
        '__keyevent@0__:*'

        >>> # Use with KeyspaceNotifications
        >>> notifications = KeyspaceNotifications(redis_client)
        >>> notifications.subscribe(channel)
    z__keyevent@r  r  c                J    || _         || _        | j                         | _        y)z
        Create a keyevent notification channel.

        Args:
            event: The event type to monitor (e.g., EventType.SET or "set")
            db: The database number. Defaults to 0 (the only database in Redis Cluster).
        Neventr  r  r  r   rG  r  s      r   r  zKeyeventChannel.__init__  s#     
 668r   c                P    | j                    | j                   d| j                   S r  r  r  rG  r   s    r   r  z%KeyeventChannel._build_channel_string  #    ++twwis4::,77r   c                ,    t        | j                        S r"  r$  rG  r   s    r   r%  zKeyeventChannel.is_pattern  s     4::&&r   c                     | d|      S )a  
        Create a keyevent pattern for subscribing to all event types.

        This is equivalent to KeyeventChannel("*").

        Args:
            db: The database number. Defaults to 0 (the only database in Redis Cluster).

        Returns:
            A KeyeventChannel configured to receive all events.

        Examples:
            >>> channel = KeyeventChannel.all_events()
            >>> str(channel)
            '__keyevent@0__:*'
        r   r  r   r   r  s     r   
all_eventszKeyeventChannel.all_events  s    $ 32r   c                    | j                   S r   r'  r   s    r   r(  zKeyeventChannel.__str__  r)  r   c                <    d| j                   d| j                   dS )NzKeyeventChannel(r+  r,  rG  r  r   s    r   r.  zKeyeventChannel.__repr__  s    !$**uTWWIQ??r   c                    t        |t              r| j                  |j                  k(  S t        |t              r| j                  |k(  S t        S r   )r0  r   r  r   r1  r2  s     r   r4  zKeyeventChannel.__eq__  r5  r   c                ,    t        | j                        S r   r7  r   s    r   r9  zKeyeventChannel.__hash__  r:  r   Nr;  rG  r   r  r   r=  r>  )r  r   r	  z'KeyeventChannel'r?  rA  rk   rl   rm   rn   r  r  r  r  rC  r%  r  rQ  r(  r.  r4  r9  r   r   r   r   r   i  sZ    8 *FM)
98 ' '  &!@'r   c                  d    e Zd ZU dZdZded<   dddZddZedd       Z	ddZ
dd	Zdd
ZddZy)r   a  
    Represents a subkeyspace notification channel for subscribing to
    subkey-level events on keys (e.g., hash field changes).

    The channel format is ``__subkeyspace@<db>__:<key>``.
    The message payload is ``<event>|<subkey_len>:<subkey>[,...]``.

    Examples:
        >>> channel = SubkeyspaceChannel("myhash", db=0)
        >>> str(channel)
        '__subkeyspace@0__:myhash'
    z__subkeyspace@r  r  c                J    || _         || _        | j                         | _        y r   r  r  s      r   r  zSubkeyspaceChannel.__init__  s"    , 668r   c                P    | j                    | j                   d| j                   S r  r  r   s    r   r  z(SubkeyspaceChannel._build_channel_string  r   r   c                ,    t        | j                        S r   r#  r   s    r   r%  zSubkeyspaceChannel.is_pattern  s    4..//r   c                    | j                   S r   r'  r   s    r   r(  zSubkeyspaceChannel.__str__  r)  r   c                <    d| j                   d| j                   dS )NzSubkeyspaceChannel(r+  r,  r-  r   s    r   r.  zSubkeyspaceChannel.__repr__  s"    $T%8%8$;5	KKr   c                    t        |t              r| j                  |j                  k(  S t        |t              r| j                  |k(  S t        S r   )r0  r   r  r   r1  r2  s     r   r4  zSubkeyspaceChannel.__eq__  E    e/0$$(:(:::eS!$$--r   c                ,    t        | j                        S r   r7  r   s    r   r9  zSubkeyspaceChannel.__hash__  r:  r   Nr;  r<  r=  r>  r?  rA  rB  r   r   r   r   r     sG     -FM,9
A 0 0!L'r   c                  x    e Zd ZU dZdZded<   dddZddZedd       Z	e
ddd       Zdd	Zdd
ZddZddZy)r   a  
    Represents a subkeyevent notification channel for subscribing to
    specific event types with subkey-level detail.

    The channel format is ``__subkeyevent@<db>__:<event>``.
    The message payload is ``<key_len>:<key>|<subkey_len>:<subkey>[,...]``.

    Examples:
        >>> channel = SubkeyeventChannel("hdel", db=0)
        >>> str(channel)
        '__subkeyevent@0__:hdel'
    z__subkeyevent@r  r  c                J    || _         || _        | j                         | _        y r   rF  rH  s      r   r  zSubkeyeventChannel.__init__  s!    
 668r   c                P    | j                    | j                   d| j                   S r  rJ  r   s    r   r  z(SubkeyeventChannel._build_channel_string  rK  r   c                ,    t        | j                        S r   rM  r   s    r   r%  zSubkeyeventChannel.is_pattern  s    4::&&r   c                     | d|      S )z+Create a channel for all subkeyevent types.r   rO  r   rP  s     r   rQ  zSubkeyeventChannel.all_events
  s     32r   c                    | j                   S r   r'  r   s    r   r(  zSubkeyeventChannel.__str__  r)  r   c                <    d| j                   d| j                   dS )NzSubkeyeventChannel(r+  r,  rT  r   s    r   r.  zSubkeyeventChannel.__repr__  s    $TZZN%yBBr   c                    t        |t              r| j                  |j                  k(  S t        |t              r| j                  |k(  S t        S r   )r0  r   r  r   r1  r2  s     r   r4  zSubkeyeventChannel.__eq__  r`  r   c                ,    t        | j                        S r   r7  r   s    r   r9  zSubkeyeventChannel.__hash__  r:  r   Nr;  rW  r=  r>  )r  r   r	  r   r?  rA  rX  r   r   r   r   r     sZ     -FM,9
8 ' '  !C'r   c                  d    e Zd ZU dZdZded<   dddZddZedd       Z	ddZ
dd	Zdd
ZddZy)r   a&  
    Represents a subkeyspaceitem notification channel for subscribing to
    events on a specific subkey (field) of a specific key.

    The channel format is ``__subkeyspaceitem@<db>__:<key>\n<subkey>``.
    The message payload is the event type (e.g., ``"hset"``).

    Note:
        The server only emits this notification when the key does not
        contain a newline character.

    Examples:
        >>> channel = SubkeyspaceitemChannel("myhash", "myfield", db=0)
        >>> str(channel)
        '__subkeyspaceitem@0__:myhash\nmyfield'
    z__subkeyspaceitem@r  r  c                X    || _         || _        || _        | j                         | _        y r   )r  subkey_or_patternr  r  r  )r   r  rm  r  s       r   r  zSubkeyspaceitemChannel.__init__4  s*    ,!2 668r   c                j    | j                    | j                   d| j                   d| j                   S )Nr  r   )r  r  r  rm  r   s    r   r  z,SubkeyspaceitemChannel._build_channel_string:  s5    {{mDGG9C(;(;'<Bt?U?U>VW	
r   c                Z    t        | j                        xs t        | j                        S r   )r$  r  rm  r   s    r   r%  z!SubkeyspaceitemChannel.is_pattern?  s#    4../V;t?U?U3VVr   c                    | j                   S r   r'  r   s    r   r(  zSubkeyspaceitemChannel.__str__C  r)  r   c                V    d| j                   d| j                  d| j                   dS )NzSubkeyspaceitemChannel(, r+  r,  )r  rm  r  r   s    r   r.  zSubkeyspaceitemChannel.__repr__F  s4    %d&9&9%<B%%(dggYa9	
r   c                    t        |t              r| j                  |j                  k(  S t        |t              r| j                  |k(  S t        S r   )r0  r   r  r   r1  r2  s     r   r4  zSubkeyspaceitemChannel.__eq__L  sE    e34$$(:(:::eS!$$--r   c                ,    t        | j                        S r   r7  r   s    r   r9  zSubkeyspaceitemChannel.__hash__S  r:  r   Nr;  )r  r   rm  r   r  r   r=  r>  r?  rA  rB  r   r   r   r   r      sG    " 1FM09

 W W!
'r   c                  d    e Zd ZU dZdZded<   dddZddZedd       Z	ddZ
dd	Zdd
ZddZy)r   a  
    Represents a subkeyspaceevent notification channel for subscribing to
    a specific event on a specific key, receiving affected subkeys.

    The channel format is ``__subkeyspaceevent@<db>__:<event>|<key>``.
    The message payload is a length-prefixed subkey list.

    Examples:
        >>> channel = SubkeyspaceeventChannel("hset", "myhash", db=0)
        >>> str(channel)
        '__subkeyspaceevent@0__:hset|myhash'
    z__subkeyspaceevent@r  r  c                X    || _         || _        || _        | j                         | _        y r   )rG  r  r  r  r  )r   rG  r  r  s       r   r  z SubkeyspaceeventChannel.__init__g  s)    
, 668r   c                j    | j                    | j                   d| j                   d| j                   S )Nr  r   )r  r  rG  r  r   s    r   r  z-SubkeyspaceeventChannel._build_channel_stringm  s0    ++twwis4::,a8K8K7LMMr   c                Z    t        | j                        xs t        | j                        S r   )r$  rG  r  r   s    r   r%  z"SubkeyspaceeventChannel.is_patternp  s!    4::&J+d6I6I*JJr   c                    | j                   S r   r'  r   s    r   r(  zSubkeyspaceeventChannel.__str__t  r)  r   c                V    d| j                   d| j                  d| j                   dS )NzSubkeyspaceeventChannel(rr  r+  r,  )rG  r  r  r   s    r   r.  z SubkeyspaceeventChannel.__repr__w  s1    &tzznB""%U477)16	
r   c                    t        |t              r| j                  |j                  k(  S t        |t              r| j                  |k(  S t        S r   )r0  r   r  r   r1  r2  s     r   r4  zSubkeyspaceeventChannel.__eq__}  sE    e45$$(:(:::eS!$$--r   c                ,    t        | j                        S r   r7  r   s    r   r9  z SubkeyspaceeventChannel.__hash__  r:  r   Nr;  )rG  r   r  r   r  r   r=  r>  r?  rA  rB  r   r   r   r   r   W  sH     2FM19N K K!
'r   c                  (    e Zd ZdZdZdZdZdZdZdZ	y)	ChannelTypeub  
    Enum representing the type of a Redis keyspace notification channel.

    Redis provides two types of keyspace notifications and four subkey
    notification types:

    - KEYSPACE: ``__keyspace@{db}__:{key}`` — data is the event type.
    - KEYEVENT: ``__keyevent@{db}__:{event}`` — data is the key name.
    - SUBKEYSPACE: ``__subkeyspace@{db}__:{key}`` — data is event + subkeys.
    - SUBKEYEVENT: ``__subkeyevent@{db}__:{event}`` — data is key + subkeys.
    - SUBKEYSPACEITEM: ``__subkeyspaceitem@{db}__:{key}\n{subkey}`` — data
      is the event type.
    - SUBKEYSPACEEVENT: ``__subkeyspaceevent@{db}__:{event}|{key}`` — data
      is a subkey list.

    Examples:
        >>> get_channel_type("__keyspace@0__:mykey")
        ChannelType.KEYSPACE
        >>> get_channel_type("__subkeyspace@0__:myhash")
        ChannelType.SUBKEYSPACE
    keyspacekeyeventsubkeyspacesubkeyeventsubkeyspaceitemsubkeyspaceeventN)
rk   rl   rm   rn   KEYSPACEKEYEVENTSUBKEYSPACESUBKEYEVENTSUBKEYSPACEITEMSUBKEYSPACEEVENTr   r   r   r~  r~    s(    , HHKK'O)r   r~  c                N   t        |       }|j                  t        j                        rt        j
                  S |j                  t        j                        rt        j                  S |j                  t        j                        rt        j                  S |j                  t        j                        rt        j                  S |j                  t        j                        rt        j                  S |j                  t        j                        rt        j                  S y)a  
    Determine the type of a Redis keyspace notification channel.

    Args:
        channel: The channel name to check (string or bytes).

    Returns:
        ChannelType.KEYSPACE if it's a keyspace notification channel,
        ChannelType.KEYEVENT if it's a keyevent notification channel,
        None if it's not a keyspace notification channel.

    Examples:
        >>> get_channel_type("__keyspace@0__:mykey")
        ChannelType.KEYSPACE
        >>> get_channel_type("__keyevent@0__:set")
        ChannelType.KEYEVENT
        >>> get_channel_type("regular_channel") is None
        True
        >>> get_channel_type(b"__keyspace@0__:mykey")
        ChannelType.KEYSPACE
    N)r   r   r   r  r~  r  r   r  r   r  r   r  r   r  r   r  )r   channel_strs     r   get_channel_typer    s    , 7#K4;;<***5<<=+++0778&&&0778&&&o445###o445###r   c                B   t        | d      r| j                  } t        |       } d}|t        |       k  rl| |   }|dk(  r|dz  }|dv ry|dk(  r>|dz   }|t        |       k  r+| |   dk(  r|dz  }| |   d	k(  ry|dz  }|t        |       k  r+|dz  }|t        |       k  rly
)a  
    Check if a channel string contains glob-style pattern characters.

    Redis uses glob-style patterns for psubscribe:
    - * matches any sequence of characters
    - ? matches any single character
    - [...] matches any character in the brackets

    Args:
        channel: The channel string to check. Can be a string, bytes,
                 or a KeyspaceChannel/KeyeventChannel object.

    Returns:
        True if the channel contains pattern characters, False otherwise.
    r  r   \   )r   ?T[r   ]F)hasattrr  r   r   )r   icharjs       r   r$  r$    s    ( w'&&wG 	
A
c'l
qz4<FA:3;AAc'l"1:%FA1:$Q c'l" 	
Q' c'l
( r   c                     e Zd ZdZedd	 	 	 dd       Zedd       Ze	 	 d	 	 	 	 	 dd       Ze	 	 d	 	 	 	 	 dd       Ze	 	 d	 	 	 	 	 dd       Z	e	 	 d	 	 	 	 	 dd	       Z
e	 	 d	 	 	 	 	 	 	 dd
       Ze	 	 d	 	 	 	 	 	 	 dd       Ze	 	 d	 	 	 	 	 dd       Zed        Zed        Zed        Zed        Zeedd              Ze	 	 	 d	 	 	 	 	 	 	 dd       Zy)KeyspaceNotificationsInterfacea1  
    Interface for keyspace notification managers.

    This interface provides a consistent API for both standalone (KeyspaceNotifications)
    and cluster (ClusterKeyspaceNotifications) implementations, allowing the same
    code patterns to work with both standalone and cluster Redis deployments.
    Nhandlerc                    y)z,Subscribe to keyspace notification channels.Nr   )r   r  channelss      r   	subscribez(KeyspaceNotificationsInterface.subscribe       	r   c                     y)z0Unsubscribe from keyspace notification channels.Nr   )r   r  s     r   unsubscribez*KeyspaceNotificationsInterface.unsubscribe       	r   c                     y)z6Subscribe to keyspace notifications for specific keys.Nr   r   r  r  r  s       r   subscribe_keyspacez1KeyspaceNotificationsInterface.subscribe_keyspace       	r   c                     y)z=Subscribe to keyevent notifications for specific event types.Nr   r   rG  r  r  s       r   subscribe_keyeventz1KeyspaceNotificationsInterface.subscribe_keyevent(  r  r   c                     y)z9Subscribe to subkeyspace notifications for specific keys.Nr   r  s       r   subscribe_subkeyspacez4KeyspaceNotificationsInterface.subscribe_subkeyspace2  r  r   c                     y)z@Subscribe to subkeyevent notifications for specific event types.Nr   r  s       r   subscribe_subkeyeventz4KeyspaceNotificationsInterface.subscribe_subkeyevent<  r  r   c                     y)zASubscribe to subkeyspaceitem notifications for a specific subkey.Nr   )r   r  rm  r  r  s        r   subscribe_subkeyspaceitemz8KeyspaceNotificationsInterface.subscribe_subkeyspaceitemF       	r   c                     y)zBSubscribe to subkeyspaceevent notifications for an event on a key.Nr   )r   rG  r  r  r  s        r   subscribe_subkeyspaceeventz9KeyspaceNotificationsInterface.subscribe_subkeyspaceeventQ  r  r   c                     y)z7Get the next keyspace notification if one is available.Nr   )r   ignore_subscribe_messagestimeouts      r   get_messagez*KeyspaceNotificationsInterface.get_message\  r  r   c                     y)z"Listen for keyspace notifications.Nr   r   s    r   listenz%KeyspaceNotificationsInterface.listene  r  r   c                     y)z6Close the notification manager and clean up resources.Nr   r   s    r   closez$KeyspaceNotificationsInterface.closej  r  r   c                     y r   r   r   s    r   	__enter__z(KeyspaceNotificationsInterface.__enter__o      r   c                     y r   r   r   	_exc_type_exc_val_exc_tbs       r   __exit__z'KeyspaceNotificationsInterface.__exit__s  r  r   c                     y);Check if there are any active subscriptions and not closed.Nr   r   s    r   
subscribedz)KeyspaceNotificationsInterface.subscribedw  s     	r   c                     y)z7Start a background thread that polls for notifications.Nr   )r   poll_timeoutdaemonexception_handlers       r   run_in_threadz,KeyspaceNotificationsInterface.run_in_thread}  s      	r   r  r   r  SyncHandlerT | Noner  r   r   Nr  r   r  r   r  r  rG  r   r  r   r  r  r  r   rm  r   r  r   r  r  rG  r   r  r   r  r   r  r  N        r  zbool | Noner  floatr	  r
  r>  r  FNr  r  r  r   r  \Callable[[BaseException, KeyspaceNotificationsInterface, KeyspaceWorkerThread], None] | Noner	  KeyspaceWorkerThread)rk   rl   rm   rn   r   r  r  r  r  r  r  r  r  r  r  r  r  r  rC  r  r  r   r   r   r  r    s     (, %     '+	  %	   '+	  %	   '+	  %	   '+	  %	  
 '+  	
 %  
 '+  	
 %   26#.  
 	              "  	 
 r   r  c                  x   e Zd ZdZ	 	 d	 	 	 ddZdd	 	 	 ddZe	 	 	 	 	 	 dd       ZddZe	 	 	 	 	 	 dd       Z		 	 	 	 	 	 dd	Z
	 	 	 	 	 	 dd
Z	 	 d	 	 	 	 	 ddZ	 	 d	 	 	 	 	 ddZ	 	 d	 	 	 	 	 ddZ	 	 d	 	 	 	 	 ddZ	 	 d	 	 	 	 	 	 	 ddZ	 	 d	 	 	 	 	 	 	 ddZd Zd Z	 	 	 d 	 	 	 	 	 	 	 d!dZed"d       Zy)#AbstractKeyspaceNotificationsa  
    Abstract base class for keyspace notification managers.

    Provides shared implementation for subscribe/unsubscribe logic.
    Subclasses must implement:
    - _execute_subscribe: Execute the subscribe operation
    - _execute_unsubscribe: Execute the unsubscribe operation
    - get_message: Get the next notification
    - listen: Generator for notifications
    - close: Clean up resources
    Nc                .    || _         || _        d| _        y)aK  
        Initialize the base keyspace notification manager.

        Args:
            key_prefix: Optional prefix to filter and strip from keys in notifications
            ignore_subscribe_messages: If True, subscribe/unsubscribe confirmations
                                      are not returned by get_message/listen
        FN)r   r  _closed)r   r   r  s      r   r  z&AbstractKeyspaceNotifications.__init__  s     %)B&r   r  c                 	 d}| j                   		fd}|}i }i }|D ];  }t        |d      rt        |      }nt        |      }t	        |      r|||<   7|||<   = | j                  ||       | j                  ||       y)a  
        Subscribe to keyspace notification channels.

        Automatically detects whether each channel is a pattern (contains
        wildcards like *, ?, [) or an exact channel name and uses the
        appropriate Redis subscribe command internally.

        Args:
            *channels: Channels to subscribe to. Can be strings, KeyspaceChannel,
                      or KeyeventChannel objects. Patterns are auto-detected.
            handler: Optional callback function that receives KeyNotification
                    objects. If provided, notifications are passed to the handler
                    instead of being returned by get_message()/listen().
        Nc                J    t         j                  |       }|	 |       y y )Nr   )r   r   )r   notificationr  r   s     r   _wrap_handlerz>AbstractKeyspaceNotifications.subscribe.<locals>._wrap_handler  s2    .;;
  <    +L) ,r   r  )r   r  r   r   r$  _execute_subscribe_track_subscribe)
r   r  r  wrapped_handlerr  patternsexact_channelsr   r  r   s
    `       @r   r  z'AbstractKeyspaceNotifications.subscribe  s    ( ,0J* ,O 	>Gw/!'l&w/7#(7%.={+	> 	.9h7r   c                     y)z
        Execute the subscribe operation.

        Args:
            patterns: Dict mapping pattern strings to handlers (for psubscribe)
            exact_channels: Dict mapping channel strings to handlers (for subscribe)
        Nr   r   r  r  s      r   r  z0AbstractKeyspaceNotifications._execute_subscribe       	r   c                   g }g }|D ]S  }t        |d      rt        |      }nt        |      }t        |      r|j	                  |       C|j	                  |       U | j                  ||       | j                  ||       y)a  
        Unsubscribe from keyspace notification channels.

        Automatically detects whether each channel is a pattern or exact
        channel and uses the appropriate Redis unsubscribe command.

        Args:
            *channels: Channels to unsubscribe from.
        r  N)r  r   r   r$  r'   _execute_unsubscribe_untrack_subscribe)r   r  r  r  r   r  s         r   r  z)AbstractKeyspaceNotifications.unsubscribe  s~      	3Gw/!'l&w/7#,%%k2	3  	!!(N;.9r   c                     y)z
        Execute the unsubscribe operation.

        Args:
            patterns: List of pattern strings to punsubscribe from
            exact_channels: List of channel strings to unsubscribe from
        Nr   r  s      r   r  z2AbstractKeyspaceNotifications._execute_unsubscribe  r  r   c                     y)a[  Track newly subscribed patterns/channels.

        Override in subclasses that need to maintain their own subscription
        registry (e.g. cluster implementations that must re-subscribe
        new/failed-over nodes).  The default is a no-op because standalone
        implementations delegate tracking to the underlying PubSub object.
        Nr   r  s      r   r  z.AbstractKeyspaceNotifications._track_subscribe      r   c                     y)zRemove patterns/channels from the subscription registry.

        Override in subclasses that maintain their own subscription registry.
        The default is a no-op.
        Nr   r  s      r   r  z0AbstractKeyspaceNotifications._untrack_subscribe)  r  r   c                D    t        ||      }| j                  ||       y)a  
        Subscribe to keyspace notifications for specific keys.

        Args:
            key_or_pattern: The key or pattern to monitor. Use '*' for wildcards.
            db: The database number (default 0).
            handler: Optional callback for notifications.

        Example:
            >>> ksn.subscribe_keyspace("user:123", db=0)
            >>> ksn.subscribe_keyspace("user:*", db=0)
        rO  r  N)r   r  r   r  r  r  r   s        r   r  z0AbstractKeyspaceNotifications.subscribe_keyspace2  s     $ ".R8w0r   c                D    t        ||      }| j                  ||       y)a  
        Subscribe to keyevent notifications for specific event types.

        Args:
            event: The event type to monitor (e.g., EventType.SET or "set")
            db: The database number (default 0).
            handler: Optional callback for notifications.

        Example:
            >>> ksn.subscribe_keyevent(EventType.SET)
            >>> ksn.subscribe_keyevent(EventType.EXPIRED, handler=my_handler)
        rO  r  N)r   r  r   rG  r  r  r   s        r   r  z0AbstractKeyspaceNotifications.subscribe_keyeventG  s     $ "%B/w0r   c                D    t        ||      }| j                  ||       y)aK  
        Subscribe to subkeyspace notifications for specific keys.

        Receives events with affected subkeys (fields) for the given key.

        Args:
            key_or_pattern: The key or pattern to monitor.
            db: The database number (default 0).
            handler: Optional callback for notifications.
        rO  r  N)r   r  r  s        r   r  z3AbstractKeyspaceNotifications.subscribe_subkeyspace\  s       %^;w0r   c                D    t        ||      }| j                  ||       y)a]  
        Subscribe to subkeyevent notifications for specific event types.

        Receives the affected key and subkeys when the given event occurs.

        Args:
            event: The event type to monitor (e.g., "hset", "hdel").
            db: The database number (default 0).
            handler: Optional callback for notifications.
        rO  r  N)r   r  r  s        r   r  z3AbstractKeyspaceNotifications.subscribe_subkeyevento  s       %Ur2w0r   c                F    t        |||      }| j                  ||       y)a  
        Subscribe to subkeyspaceitem notifications for a specific subkey.

        Receives the event type when the given subkey of the given key is
        modified.

        Args:
            key_or_pattern: The key or pattern to monitor.
            subkey_or_pattern: The subkey (field) or pattern to monitor.
            db: The database number (default 0).
            handler: Optional callback for notifications.
        rO  r  N)r   r  )r   r  rm  r  r  r   s         r   r  z7AbstractKeyspaceNotifications.subscribe_subkeyspaceitem  s#    & )9JrRw0r   c                F    t        |||      }| j                  ||       y)a  
        Subscribe to subkeyspaceevent notifications for an event on a key.

        Receives the affected subkeys when the given event occurs on the
        given key.

        Args:
            event: The event type to monitor.
            key_or_pattern: The key or pattern to monitor.
            db: The database number (default 0).
            handler: Optional callback for notifications.
        rO  r  N)r   r  )r   rG  r  r  r  r   s         r   r  z8AbstractKeyspaceNotifications.subscribe_subkeyspaceevent  s"    & *%BGw0r   c                    | S r   r   r   s    r   r  z'AbstractKeyspaceNotifications.__enter__  s    r   c                $    | j                          y)NF)r  r  s       r   r  z&AbstractKeyspaceNotifications.__exit__  s    

r   c                d    | j                          t        | |||      }|j                          |S )az  
        Start a background thread that polls for notifications and triggers handlers.

        This method spawns a thread that continuously calls get_message() to
        process incoming notifications. When a notification arrives, any
        registered handler for that channel/pattern is invoked automatically.

        All subscriptions must have handlers registered before calling this method.

        Args:
            poll_timeout: Timeout in seconds for get_message() calls. When no message
                         is available, the thread waits up to this long before checking
                         again. Default 0.0 (non-blocking). WARNING: the default
                         causes a CPU spin-loop. It is preferred to pass a positive
                         value (e.g. 0.1 or 1.0).
            daemon: If True, the thread will be a daemon thread and will be
                   terminated when the main program exits. Default False.
            exception_handler: Optional callback invoked when an exception occurs
                              in the worker thread. Receives (exception, notifications,
                              thread) as arguments. If None, exceptions are raised.

        Returns:
            KeyspaceWorkerThread: The started worker thread. Call stop() on it
                                 to stop the thread and close the notifications.

        Raises:
            RedisError: If any subscription doesn't have a handler registered.

        Example:
            >>> def my_handler(notification):
            ...     print(f"Got: {notification.key} - {notification.event_type}")
            >>>
            >>> notifications.subscribe(KeyspaceChannel("user:*"), handler=my_handler)
            >>> thread = notifications.run_in_thread(poll_timeout=0.1, daemon=True)
            >>> # ... handlers are called automatically ...
            >>> thread.stop()
        )r  r  )_validate_all_handlersr  r   )r   r  r  r  threads        r   r  z+AbstractKeyspaceNotifications.run_in_thread  s7    f 	##%%/	
 	r   c                     y)zRaise :class:`~redis.RedisError` if any subscription lacks a handler.

        Subclasses inspect their own subscription state to perform
        the validation.
        Nr   r   s    r   r  z4AbstractKeyspaceNotifications._validate_all_handlers  r  r   NT)r   r  r  r   r  r  dict[str, Any]r  r  r	  Noner  r  r   r  r   r	  r  r  r  r  r  r  r  r  r	  r  )rk   rl   rm   rn   r  r  r   r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r   r   r   r  r    s<   
 *.*.& $(( (,4848 %48l 
&
8F
	
 
:@ 
!
3<
	
 
	&	8F			!3<	 '+	11 1 %	10 '+	11 1 %	10 '+	11 1 %	1, '+	11 1 %	1. '+11 1 	1
 %14 '+11 1 	1
 %1, " << <	< 
<|  r   r  c                  H     e Zd ZdZ	 	 d	 	 	 	 	 	 	 d fdZddZddZ xZS )r  a  
    Background thread for processing keyspace notifications.

    This thread continuously polls for notifications and invokes registered
    handlers. It works with both KeyspaceNotifications (standalone) and
    ClusterKeyspaceNotifications.

    Example:
        >>> thread = notifications.run_in_thread(poll_timeout=0.1)
        >>> # ... handlers are called automatically ...
        >>> thread.stop()
    c                    t         |           || _        || _        || _        || _        t        j                         | _        y r   )	superr  r  notificationsr  r  	threadingEvent_running)r   r  r  r  r  	__class__s        r   r  zKeyspaceWorkerThread.__init__  s=     	*(!2!)r   c                   | j                   j                         ry| j                   j                          | j                  }| j                  }| j                   j                         r/	 |j                  d|       | j                   j                         r/|j                          y# t        $ r*}| j                   | j                  |||        Y d}~Zd}~ww xY w)z=Main loop that polls for notifications and triggers handlers.NTr  r  )	r  is_setr   r  r  r  BaseExceptionr  r  )r   r  r  es       r   runzKeyspaceWorkerThread.run!  s    ==!**((mm""$?)).2L *  mm""$ 		 ! ?))1&&q->>?s   )B( (	C1 CCc                8    | j                   j                          y)z
        Stop the worker thread.

        This signals the thread to exit its run loop. The thread will close
        the notifications object before terminating.
        N)r  clearr   s    r   stopzKeyspaceWorkerThread.stop3  s     	r   )FN)r  r  r  r  r  r   r  r  r  )rk   rl   rm   rn   r  r  r  __classcell__r  s   @r   r  r    sG    "  *5* * 	*
*,$r   r  c                       e Zd ZdZ	 	 d
	 	 	 	 	 d fdZ	 	 	 	 	 	 ddZ	 	 	 	 	 	 ddZ	 	 d	 	 	 	 	 ddZd Ze	dd       Z
ddZd	 Z xZS )KeyspaceNotificationsa  
    Manages keyspace notification subscriptions for standalone Redis.

    For standalone Redis, keyspace notifications work with a single PubSub
    connection. This class wraps that connection and provides:
    - Automatic pattern vs exact channel detection
    - KeyNotification parsing with optional key_prefix filtering
    - Convenience methods for keyspace and keyevent subscriptions
    - Context manager and run_in_thread support
    c                b    t         |   ||       || _        |j                  d      | _        y)av  
        Initialize the standalone keyspace notification manager.

        Note: Keyspace notifications must be enabled on the Redis server via
        the ``notify-keyspace-events`` configuration option. This is a server-side
        configuration that should be done by your infrastructure/operations team.

        Args:
            redis_client: A Redis client instance
            key_prefix: Optional prefix to filter and strip from keys in notifications
            ignore_subscribe_messages: If True, subscribe/unsubscribe confirmations
                                      are not returned by get_message/listen
        Fr  N)r
  r  redispubsub_pubsub)r   redis_clientr   r  r  s       r   r  zKeyspaceNotifications.__init__N  s3    & 	%>?!
 $**U*Kr   c                ~    |r | j                   j                  di | |r | j                   j                  di | yy)z2Execute subscribe on the single pubsub connection.Nr   )r!  
psubscriber  r  s      r   r  z(KeyspaceNotifications._execute_subscribeh  s=     #DLL##/h/"DLL""4^4 r   c                r    |r | j                   j                  |  |r | j                   j                  |  yy)z4Execute unsubscribe on the single pubsub connection.N)r!  punsubscriber  r  s      r   r  z*KeyspaceNotifications._execute_unsubscribeq  s7     %DLL%%x0$DLL$$n5 r   c                    || j                   }| j                  ry| j                  j                  ||      }|!t        j                  || j                        S y)ae  
        Get the next keyspace notification if one is available.

        Note: If a handler was registered for the channel, pubsub will call
        the handler directly and this method returns None for that message.

        Args:
            ignore_subscribe_messages: If True, skip subscribe/unsubscribe messages.
                                      Defaults to the value set in __init__ (True).
            timeout: Time to wait for a message.

        Returns:
            A KeyNotification if a notification is available and no handler
            was registered for the channel, None otherwise.
        Nr  r  )r  r  r!  r  r   r   r   )r   r  r  r   s       r   r  z!KeyspaceNotifications.get_messagez  sf    ( %,(,(F(F%<<
 ,,**&? + 

 "//DOO/TTr   c              #  p   K   | j                   r&| j                  d      }|| | j                   r%yyw)a  
        Listen for keyspace notifications.

        This is a generator that yields KeyNotification objects as they arrive.
        It blocks until a notification is received.

        Yields:
            KeyNotification objects for each keyspace/keyevent notification.

        Example:
            >>> for notification in ksn.listen():
            ...     print(f"{notification.key}: {notification.event_type}")
              ?r  Nr  r  r   r  s     r   r  zKeyspaceNotifications.listen  7      oo++C+8L'"" oo   166c                L    | j                    xr | j                  j                  S r  )r  r!  r  r   s    r   r  z KeyspaceNotifications.subscribed  s      <<;DLL$;$;;r   c                    | j                   j                  j                         D ]  \  }}|	t        d| d       | j                   j                  j                         D ]  \  }}|	t        d| d       y)zCRaise if any subscription in the underlying PubSub lacks a handler.N	Channel '' has no handler registered	Pattern ')r!  r  itemsr   r  r   r   r  patterns       r   r  z,KeyspaceNotifications._validate_all_handlers  s     $ 5 5 ; ; = 	SGW 9WI5P!QRR	S !% 5 5 ; ; = 	SGW 9WI5P!QRR	Sr   c                f    d| _         	 | j                  j                          y# t        $ r Y yw xY w)z3Close the pubsub connection and clean up resources.TN)r  r!  r  	Exceptionr   s    r   r  zKeyspaceNotifications.close  s0    	LL  		s   $ 	00r  )r"  r   r   r  r  r   r  r  r  r  r>  r  )rk   rl   rm   rn   r  r  r  r  r  rC  r  r  r  r  r  s   @r   r  r  B  s    	 *.*.	LL 'L $(	L45&58F5	56!63<6	6 26%#.% % 
 	%N#& < <Sr   r  c                      e Zd ZdZ	 	 d	 	 	 	 	 d fdZedd       Z	 	 	 	 	 	 ddZ	 	 	 	 	 	 ddZddZ	d Z
ddZdd	Z	 	 	 	 	 	 dd
Z	 	 	 	 	 	 ddZddZ	 	 d	 	 	 	 	 ddZ	 	 	 	 ddZd Zd ZddZd Zd Z xZS ) ClusterKeyspaceNotificationsaL  
    Manages keyspace notification subscriptions across all nodes in a Redis Cluster.

    In Redis Cluster, keyspace notifications are NOT broadcast between nodes.
    Each node only emits notifications for keys it owns. This class automatically
    subscribes to all primary nodes in the cluster and handles topology changes.
    c                    t         |   ||       || _        i | _        i | _        i | _        t        j                         | _        d| _	        y)a{  
        Initialize the cluster keyspace notification manager.

        Note: Keyspace notifications must be enabled on all Redis cluster nodes via
        the ``notify-keyspace-events`` configuration option. This is a server-side
        configuration that should be done by your infrastructure/operations team.

        Args:
            redis_cluster: A RedisCluster instance
            key_prefix: Optional prefix to filter and strip from keys in notifications
            ignore_subscribe_messages: If True, subscribe/unsubscribe confirmations
                                      are not returned by get_message/listen
        r   N)
r
  r  cluster_subscribed_patterns_subscribed_channels_node_pubsubsr  Lock_refresh_lock_poll_index)r   redis_clusterr   r  r  s       r   r  z%ClusterKeyspaceNotifications.__init__  sS    & 	%>?$ 57!46! .0 '^^- r   c                f    | j                    xr# t        | j                  xs | j                        S r0  )r  r   r>  r?  r   s    r   r  z'ClusterKeyspaceNotifications.subscribed  s3     << 
D%%B)B)B%
 	
r   c                z    |r| j                   j                  |       |r| j                  j                  |       yy)zATrack newly subscribed patterns/channels in the cluster registry.N)r>  updater?  r  s      r   r  z-ClusterKeyspaceNotifications._track_subscribe  s7     %%,,X6%%,,^< r   c                    |D ]  }| j                   j                  |d         |D ]  }| j                  j                  |d         y)z3Remove patterns/channels from the cluster registry.N)r>  popr?  )r   r  r  pcs        r   r  z/ClusterKeyspaceNotifications._untrack_subscribe  sN      	3A%%))!T2	3 	3A%%))!T2	3r   c                    | j                   j                         D ]  \  }}|	t        d| d       | j                  j                         D ]  \  }}|	t        d| d       y)zBRaise if any subscription in the cluster registry lacks a handler.Nr2  r3  r4  )r?  r5  r   r>  r6  s       r   r  z3ClusterKeyspaceNotifications._validate_all_handlers  s     $ 9 9 ? ? A 	SGW 9WI5P!QRR	S !% 9 9 ? ? A 	SGW 9WI5P!QRR	Sr   c                6    | j                   j                         S )z%Get all primary nodes in the cluster.)r=  get_primariesr   s    r   _get_all_primary_nodesz3ClusterKeyspaceNotifications._get_all_primary_nodes!  s    ||))++r   c                    | j                   j                  |d      }|r	 |j                          yy# t        $ r Y yw xY w)zRemove and close a node's PubSub.

        Closing the ``PubSub`` disconnects its connection so it is not
        left in a subscribed state inside the connection pool.
        N)r@  rI  r  r9  )r   	node_namer   s      r   _cleanup_nodez*ClusterKeyspaceNotifications._cleanup_node%  sF     ##''	48   s   2 	>>c                    |j                   | j                  vrF| j                  j                  |      }|j	                  d      }|| j                  |j                   <   | j                  |j                      S )z+Get or create a PubSub instance for a node.Fr  )namer@  r=  get_redis_connectionr   )r   node
redis_connr   s       r   _ensure_node_pubsubz0ClusterKeyspaceNotifications._ensure_node_pubsub2  sf    99D...::4@J  &&&GF,2Dtyy)!!$)),,r   c                `   |s|syg }| j                         D ]  }|j                  | j                  v}| j                  |      }	 |rP| j                  r |j
                  di | j                   | j                  r |j                  di | j                   |r |j
                  di | |r |j                  di |  |r&t        j                  ddj                  |             yy# t        $ r: | j                  |j                         |j                  |j                         Y w xY w)a  Execute subscribe on all cluster nodes.

        Patterns and exact channels are subscribed in a single pass over
        nodes so that a mid-batch node failure cannot create a
        partially-caught-up replacement.  If a node fails during this
        call it is removed from ``_node_pubsubs`` and will be fully
        re-subscribed on the next ``refresh_subscriptions`` cycle.

        If a newly discovered node is encountered (not yet in
        ``_node_pubsubs``), it is also subscribed to all *previously*
        tracked patterns/channels so it doesn't miss notifications for
        subscriptions that were established before this node joined.
        Nz`Failed to subscribe on cluster nodes: %s. These nodes will be retried on the next refresh cycle.rr  r   )rO  rT  r@  rX  r>  r$  r?  r  r9  rR  r'   loggerwarningjoin)r   r  r  failed_nodesrV  is_new_noder   s          r   r  z/ClusterKeyspaceNotifications._execute_subscribe=  s'     "$//1 	/D))4+=+==K--d3F/ 00)))FD,E,EF00(((E4+D+DE%F%%11!$F$$6~6	/, NNI		,'   / ""499-##DII.	/s   A:C**?D-,D-c                Z    |r| j                  |d       |r| j                  |d       yy)z)Execute unsubscribe on all cluster nodes.T)use_punsubscribeFN)_unsubscribe_from_all_nodesr  s      r   r  z1ClusterKeyspaceNotifications._execute_unsubscriben  s5     ,,X,M,,^e,T r   c                T   g }t        | j                  j                               D ]'  \  }}	 |r |j                  |  n |j                  |  ) |r&t        j                  ddj                  |             yy# t
        $ r% | j                  |       |j                  |       Y w xY w)a  Unsubscribe from patterns/channels on all nodes.

        Best-effort: tries every node so that a single broken connection
        does not prevent the remaining nodes from being unsubscribed.
        Broken pubsubs are cleaned up; the tracking state is still removed
        by the caller, so ``refresh_subscriptions`` will *not* re-subscribe
        these channels on replacement nodes.
        zeFailed to unsubscribe on cluster nodes: %s. These nodes will be re-created on the next refresh cycle.rr  N)listr@  r5  r&  r  r9  rR  r'   rZ  r[  r\  )r   r  r`  r]  rQ  r   s         r   ra  z8ClusterKeyspaceNotifications._unsubscribe_from_all_nodesw  s     #%!%d&8&8&>&>&@!A 	/Iv/#'F''2&F&&1	/ NNL		,' 	  /""9-##I./s   !A99+B'&B'c                   | j                   ryt        | j                        }|dk(  r|dkD  rt        j                  |       y|| j
                  }|dk(  r| j                  |      S t        d|t        |d      z        }t        j                         }||z   }	 t        j                         |k\  ryt        | j                  j                               }|sy| j                  t        |      z  | _        || j                     }| xj                  dz  c_        	 |j                  ||      }	|	%t$        j'                  |	| j(                        }
|
|
S # t        t        t         f$ r | j#                          Y w xY w)a  
        Get the next keyspace notification if one is available.

        This method polls all node pubsubs in round-robin fashion until
        a message is received or the timeout expires.
        If a connection error occurs, subscriptions are automatically refreshed.

        Args:
            ignore_subscribe_messages: If True, skip subscribe/unsubscribe messages.
                                      Defaults to the value set in __init__ (True).
            timeout: Total time to wait for a message (distributed across all nodes)

        Returns:
            A KeyNotification if a notification is available, None otherwise.
        Nr   r  g?r   r  r  )r  r   r@  timesleepr  _poll_all_nodes_onceminmax	monotonicrc  valuesrC  r  r   r   r   _refresh_subscriptions_on_errorr   r   r   )r   r  r  total_nodesper_node_timeout
start_timeend_timepubsubsr   r   r  s              r   r  z(ClusterKeyspaceNotifications.get_message  s   ( <<$,,-! {

7# %,(,(F(F% c>,,-FGG sGc+q.A$AB^^%
'~~8+4--4467G  $//#g,>DT--.F! ,,.G, -  "  /;;  <    +''? & $\:> 446s   E $E<;E<c                T   d}t        | j                  j                               D ]R  }	 |j                  |d      }|t        j                  || j                        }|>|r| j                          |c S  |r| j                          y# t        t
        t        f$ r d}Y w xY w)a3  
        Perform a single non-blocking poll over all node pubsubs.

        This is used when timeout=0 to match the expected semantics of
        PubSub.get_message(timeout=0) - a non-blocking check for messages.

        Returns:
            A KeyNotification if one is available, None otherwise.
        Fr  r  TNr  )rc  r@  rk  r  r   r   r   r   r   r   rl  )r   r  	had_errorr   r   r  s         r   rg  z1ClusterKeyspaceNotifications._poll_all_nodes_once  s     	4--4467 	(F	 ,,.G -  "  /;;  <    + !<<>''/	(4 002- $\:>  !		s   BB'&B'c              #  p   K   | j                   r&| j                  d      }|| | j                   r%yyw)a  
        Listen for keyspace notifications from all cluster nodes.

        This is a generator that yields KeyNotification objects as they arrive.
        It blocks until a notification is received.

        Yields:
            KeyNotification objects for each keyspace/keyevent notification.

        Example:
            >>> for notification in ksn.listen():
            ...     print(f"{notification.key}: {notification.event_type}")
        r)  r*  Nr+  r,  s     r   r  z#ClusterKeyspaceNotifications.listen  r-  r.  c                    d| _         	 | j                          y# t        $ r t        j	                  dd       Y yw xY w)z
        Refresh subscriptions after a connection error.

        This is called automatically when a connection error occurs during
        get_message(). It checks if nodes changed before refreshing.
        r   zAFailed to refresh cluster subscriptions, will retry on next errorT)exc_infoN)rC  refresh_subscriptionsr9  rZ  r[  r   s    r   rl  z<ClusterKeyspaceNotifications._refresh_subscriptions_on_error"  sC     	&&( 	NNS  	s     ==c                X    	 |j                   }|y|j                  S # t        $ r Y yw xY w)z,Check if a pubsub connection is still alive.F)
connectionis_connectedr9  )r   r   conns      r   _is_pubsub_connectedz1ClusterKeyspaceNotifications._is_pubsub_connected3  s8    	$$D|$$$ 		s     	))c                P   | j                   5  | j                         D ci c]  }|j                  | }}t        | j                  j                               t        |j                               z
  }|D ]  }| j                  |        t        | j                  j                               t        |j                               z  }|D ]C  }| j                  j                  |      }|s!| j                  |      r3| j                  |       E t        |j                               t        | j                  j                               z
  }g }|D ]i  }||   }| j                  |      }	 | j                  r |j                  di | j                   | j                  r |j                  di | j                   k |rt!        ddj#                  |             	 ddd       yc c}w # t        $ r% | j                  |       |j                  |       Y w xY w# 1 sw Y   yxY w)a  
        Refresh subscriptions after a topology change.

        This method is called automatically when topology changes are detected
        or when connection errors occur. You can also call it manually if needed.

        This method:
        1. Discovers any new primary nodes and subscribes them
        2. Removes pubsubs for nodes that are no longer primaries
        3. Re-creates broken pubsub connections for existing nodes
        z&Failed to subscribe to cluster nodes: rr  Nr   )rB  rO  rT  r   r@  keysrR  r   r|  rX  r>  r$  r?  r  r9  r'   r   r\  )	r   rV  current_primariesremoved_nodesrQ  existing_nodesr   	new_nodesr]  s	            r   rw  z2ClusterKeyspaceNotifications.refresh_subscriptions=  s$     -	,0,G,G,I!$(		4! !
   2 2 7 7 9:S!&&(> M + .	""9-.
 !!3!3!8!8!:;c!&&(? N , 2	++//	:$";";F"C&&y1	2 -2245D<N<N<S<S<U8VVI&(L& 3	(311$7300)))FD,E,EF00(((E4+D+DE3 %<TYY|=T<UV  U-	 -	!F ! 3&&y1 ''	23I-	 -	sN   HG&B6H+H=A-H+AG+;!H&H++HHHHH%c                    d| _         t        | j                  j                               D ]  }| j	                  |        | j
                  j                          | j                  j                          y)z4Close all pubsub connections and clean up resources.TN)r  rc  r@  r~  rR  r>  r  r?  )r   rQ  s     r   r  z"ClusterKeyspaceNotifications.closex  s^    d005578 	*Iy)	*!!'')!!'')r   r  )rD  r   r   r  r  r   r>  r  r  r  )rQ  r   r	  r  )r	  r
   )r  r   r`  r   r  r  )r  r   r	  r
  )rk   rl   rm   rn   r  rC  r  r  r  r  rO  rR  rX  r  r  ra  r  rg  r  rl  r|  rw  r  r  r  s   @r   r;  r;    s7    *.*.	$#$ '$ $(	$L 
 
=&=8F=	=3!33<3	3S,	-/&/8F/	/bU!U3<U	U: 26O(#.O( O( 
 	O(d))-)	)V#&"9v*r   r;  )r   r   r	  r   )r   r  r	  zChannelType | None)r   z/str | bytes | KeyspaceChannel | KeyeventChannelr	  r   ):rn   
__future__r   loggingr  r  re  abcr   r   collections.abcr   dataclassesr   enumr   typingr	   r
   r   r   redis.clientr   redis.clusterr   redis.exceptionsr   r   r   redis.utilsr   	getLoggerrk   rZ  r   r   bytesr   r  SyncHandlerTr   r   r   r   r   r   r   r   r   r~  r  r$  r  r  Threadr  r  r;  r   r   r   <module>r     s  8t #  	   # $ !  6 6  & 
 !			8	$  		) 	 *+T12m m`* g+ g+ g+^	B' B'JU' U'p*' *'Z/' /'d4' 4'n.' .'b*$ *>$N1<1	1rFS FRj$B jZ=9++ =JF9 F\n*#@ n*r   