
    ng\jV%             	         % S r SSKJr  SSKrSSKrSSKrSSKrSSKJrJ	r	  SSK
Jr  SSKJr  SSKJr  SSKJrJrJrJr  SS	KJr  SS
KJr  SSKJrJrJr  SSKJr  \R>                  " \ 5      r!\(       a  SSKJ"r"  \\#\$SSSSSS4   r%S\&S'   \S/S4   r' " S S5      r(S/S jr)\ " S S5      5       r* " S S5      r+ " S S5      r, " S S5      r- " S S5      r. " S S5      r/ " S  S5      r0 " S! S"\5      r1S0S# jr2    S1S$ jr3 " S% S&\5      r4 " S' S(\45      r5 " S) S*\Rl                  5      r7 " S+ S,\55      r8 " S- S.\55      r9g)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                  `   \ rS rSrSrSrSrSrSrSr	Sr
S	rS
rSrSrSrSrSrSrSrSrSrSrSrSrSrSrSrSrSrSrSrSr Sr!S r"S!r#S"r$S#r%S$r&S%r'S&r(S'r)S(r*S)r+S*r,S+r-S,r.S-r/S.r0S/r1S0r2S1r3S2r4S3r5S4r6S5r7S6r8S7r9S8r:S9r;S:r<S;r=S<r>S=r?S>r@S?rAS@rBSArCSBrDSCrESDrFSErGSFrHSGrISHrJSIrKSJrLSKrMSLrNSMrOSNrPSOrQSPrRSQrSSRrTSSrUSTrVSUrWgV)W	EventTypeo   z
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setifne N)X__name__
__module____qualname____firstlineno____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__static_attributes__rl       W/home/edenadmin/noVNC/venv/lib/python3.13/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                    / nSnU[        U 5      :  aj  U R                  SU5      n[        XU 5      nUS-   nUR                  XXT-    5        XT-   nU[        U 5      :  a  X   S:X  a  US-  nU[        U 5      :  a  Mj  U$ )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!Q5\"	q01nQ<AFcM1HC A, Nr   c                     \ rS rSr% Sr\R                  " S5      rS\S'   \R                  " S5      r	S\S'   \R                  " S5      r
S\S	'   \R                  " S
5      rS\S'   \R                  " S\R                  5      rS\S'   \R                  " S5      rS\S'   S\S'   S\S'   S\S'   S\S'   S\S'   S\S'   SrS\S'   S r\ S"     S#S jj5       r\ S"       S$S jj5       r\ S"       S%S jj5       rS&S  jrS!rg)'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                .    U R                   c  / U l         g g Nr   selfs    r   __post_init__KeyNotification.__post_init__'  s    <<DL  r   c                    Uc  gUR                  S5      nUS;  a  gUR                  S5      nUR                  S5      nUb  Uc  gU R                  XEU5      $ )a4  
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_messageKeyNotification.from_message+  s`    B ?;;v&22++i({{6"?dl}}WJ77r   c                R    [        U5      n[        U5      nU R                  XU5      $ )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   KeyNotification.try_parse[  s(    * 7#~zz'44r   c           
        U(       a  [        U5      OSnU R                  R                  U5      nU(       a^  UR                  5       u  pVUS:w  a  [	        U5      OSnUnU(       a%  UR                  U5      (       d  gU[        U5      S nU " UUUUSUS9$ U R                  R                  U5      nU(       a^  UR                  5       u  pXUS:w  a  [	        U5      OSnUnU(       a%  UR                  U5      (       d  gU[        U5      S nU " UUUUSUS9$ U R                  R                  U5      nU(       a  UR                  5       u  pVUS:w  a  [	        U5      OSnUR                  S5      n	USU	 n[        X)S-   S 5      n
U(       a%  UR                  U5      (       d  gU[        U5      S nU " UUUUSUU
S	9$ U R                  R                  U5      nU(       a  UR                  5       u  pXUS:w  a  [	        U5      OSnUR                  S
5      n[	        USU 5      nUS-   nX-X-    nX-   S-   n[        X.S 5      n
U(       a%  UR                  U5      (       d  gU[        U5      S nU " UUUUSUU
S	9$ U R                  R                  U5      nU(       a~  UR                  5       u  p_US:w  a  [	        U5      OSnUR                  S5      nUSU nUUS-   S nUnU(       a%  UR                  U5      (       d  gU[        U5      S nU " UUUUSUU/S	9$ U R                  R                  U5      nU(       a  UR                  5       u  nnUS:w  a  [	        U5      OSnUR                  S5      n	USU	 nUU	S-   S n[        U5      n
U(       a%  UR                  U5      (       d  gU[        U5      S nU " UUUUSUU
S	9$ g)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   KeyNotification._parseu  s    .8Xj)T
 %%++G4,,.KF&,ms6{HJ ~~j11#j/+,%!   %%++G4!&F&,ms6{HC ~~j11#j/+,%!!  ((..w7,,.KF&,ms6{Hzz#HixJ4TQ,.5IJG~~j11#j/+,%!   ((..w7!&F&,ms6{H

3I$z	*+G!AI9#67C%/!3M4T.5IJG~~j11#j/+,%!!  ,,227;%*\\^"F&,ms6{H(..t4K +.C#K!O$56FJ~~j11#j/+,%!   --33G<$)LLN!FM&,ms6{H$**3/H&y1J1/C4T:G~~j11#j/+,%!!  r   c                N    [        U5      nU R                  R                  U5      $ )z.Check if the key starts with the given prefix.)r   r   r  )r   prefixs     r   key_starts_withKeyNotification.key_starts_with  s!    &!xx""6**r   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   )rm   rn   ro   rp   rq   recompiler   __annotations__r   r   r   DOTALLr   r   r   r   classmethodr   r   r   r  r   rl   r   r   r   r      s   , /1jj'/+  /1jj'/+  24*2.  24*2.  68ZZ.		62  79jj/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                  |    \ rS rSr% SrSrS\S'   SSS jjrSS jr\	SS j5       r
SS	 jrSS
 jrSS jrSS jrSrg)r   i$  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                F    Xl         X l        U R                  5       U l        g)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__KeyspaceChannel.__init__D  s      - 668r   c                P    U R                    U R                   SU R                   3$ N__:r  r!  r   r   s    r   r"  %KeyspaceChannel._build_channel_stringP  &    ++twwis4+>+>*?@@r   c                ,    [        U R                  5      $ zCCheck if this channel contains wildcards and should use psubscribe._is_patternr   r   s    r   
is_patternKeyspaceChannel.is_patternS  s     4..//r   c                    U R                   $ r   r#  r   s    r   __str__KeyspaceChannel.__str__X         r   c                >    SU R                   < SU R                   S3$ )NzKeyspaceChannel(, db=)r   r!  r   s    r   __repr__KeyspaceChannel.__repr__[  s"    !$"5"5!8dggYaHHr   c                    [        U[        5      (       a  U R                  UR                  :H  $ [        U[        5      (       a  U R                  U:H  $ [        $ r   )
isinstancer   r#  r   NotImplementedr   others     r   __eq__KeyspaceChannel.__eq__^  J    e_--$$(:(:::eS!!$$--r   c                ,    [        U R                  5      $ r   hashr#  r   s    r   __hash__KeyspaceChannel.__hash__e      D%%&&r   r#  r!  r   Nr   r   r   r!  r   r  r   r  r   rB  objectr  r   r  r   rm   rn   ro   rp   rq   r  r  r%  r"  propertyr1  r5  r<  rC  rI  r   rl   r   r   r   r   $  sG    : *FM)
9A 0 0!I'r   c                      \ rS rSr% SrSrS\S'   SSS jjrSS jr\	SS j5       r
\SSS	 jj5       rSS
 jrSS jrSS jrSS jrSrg)r   ii  aH  
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                F    Xl         X l        U R                  5       U l        g)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   rY  r!  s      r   r%  KeyeventChannel.__init__  s     
 668r   c                P    U R                    U R                   SU R                   3$ r(  r  r!  rY  r   s    r   r"  %KeyeventChannel._build_channel_string  #    ++twwis4::,77r   c                ,    [        U R                  5      $ r.  r0  rY  r   s    r   r1  KeyeventChannel.is_pattern  s     4::&&r   c                    U " SUS9$ )al  
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!  rl   r   r!  s     r   
all_eventsKeyeventChannel.all_events  s    $ 32r   c                    U R                   $ r   r4  r   s    r   r5  KeyeventChannel.__str__  r7  r   c                >    SU R                   < SU R                   S3$ )NzKeyeventChannel(r9  r:  rY  r!  r   s    r   r<  KeyeventChannel.__repr__  s    !$**uTWWIQ??r   c                    [        U[        5      (       a  U R                  UR                  :H  $ [        U[        5      (       a  U R                  U:H  $ [        $ r   )r?  r   r#  r   r@  rA  s     r   rC  KeyeventChannel.__eq__  rE  r   c                ,    [        U R                  5      $ r   rG  r   s    r   rI  KeyeventChannel.__hash__  rK  r   r#  r!  rY  NrM  rY  r   r!  r   rO  rP  )r!  r   r  z'KeyeventChannel'rQ  rS  rm   rn   ro   rp   rq   r  r  r%  r"  rU  r1  r  rf  r5  r<  rC  rI  r   rl   r   r   r   r   i  sZ    8 *FM)
98 ' '  &!@'r   c                  |    \ rS rSr% SrSrS\S'   SSS jjrSS jr\	SS j5       r
SS	 jrSS
 jrSS jrSS jrSrg)r   i  ae  
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                F    Xl         X l        U R                  5       U l        g r   r  r$  s      r   r%  SubkeyspaceChannel.__init__  s    , 668r   c                P    U R                    U R                   SU R                   3$ r(  r*  r   s    r   r"  (SubkeyspaceChannel._build_channel_string  r,  r   c                ,    [        U R                  5      $ r   r/  r   s    r   r1  SubkeyspaceChannel.is_pattern  s    4..//r   c                    U R                   $ r   r4  r   s    r   r5  SubkeyspaceChannel.__str__  r7  r   c                >    SU R                   < SU R                   S3$ )NzSubkeyspaceChannel(r9  r:  r;  r   s    r   r<  SubkeyspaceChannel.__repr__  s"    $T%8%8$;5	KKr   c                    [        U[        5      (       a  U R                  UR                  :H  $ [        U[        5      (       a  U R                  U:H  $ [        $ r   )r?  r   r#  r   r@  rA  s     r   rC  SubkeyspaceChannel.__eq__  K    e/00$$(:(:::eS!!$$--r   c                ,    [        U R                  5      $ r   rG  r   s    r   rI  SubkeyspaceChannel.__hash__  rK  r   rL  NrM  rN  rO  rP  rQ  rS  rT  rl   r   r   r   r     sG     -FM,9
A 0 0!L'r   c                      \ rS rSr% SrSrS\S'   SSS jjrSS jr\	SS j5       r
\SSS	 jj5       rSS
 jrSS jrSS jrSS jrSrg)r   i  ab  
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                F    Xl         X l        U R                  5       U l        g r   rX  rZ  s      r   r%  SubkeyeventChannel.__init__  s    
 668r   c                P    U R                    U R                   SU R                   3$ r(  r]  r   s    r   r"  (SubkeyeventChannel._build_channel_string  r_  r   c                ,    [        U R                  5      $ r   ra  r   s    r   r1  SubkeyeventChannel.is_pattern  s    4::&&r   c                    U " SUS9$ )z+Create a channel for all subkeyevent types.r   rd  rl   re  s     r   rf  SubkeyeventChannel.all_events
  s     32r   c                    U R                   $ r   r4  r   s    r   r5  SubkeyeventChannel.__str__  r7  r   c                >    SU R                   < SU R                   S3$ )NzSubkeyeventChannel(r9  r:  rk  r   s    r   r<  SubkeyeventChannel.__repr__  s    $TZZN%yBBr   c                    [        U[        5      (       a  U R                  UR                  :H  $ [        U[        5      (       a  U R                  U:H  $ [        $ r   )r?  r   r#  r   r@  rA  s     r   rC  SubkeyeventChannel.__eq__  r  r   c                ,    [        U R                  5      $ r   rG  r   s    r   rI  SubkeyeventChannel.__hash__  rK  r   rq  NrM  rr  rO  rP  )r!  r   r  r   rQ  rS  rs  rl   r   r   r   r     sZ     -FM,9
8 ' '  !C'r   c                  |    \ rS rSr% SrSrS\S'   SSS jjrSS jr\	SS j5       r
SS	 jrSS
 jrSS jrSS jrSrg)r   i   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                R    Xl         X l        X0l        U R                  5       U l        g r   )r   subkey_or_patternr!  r"  r#  )r   r   r  r!  s       r   r%  SubkeyspaceitemChannel.__init__4  s$    ,!2 668r   c                j    U R                    U R                   SU R                   SU R                   3$ )Nr)  r   )r  r!  r   r  r   s    r   r"  ,SubkeyspaceitemChannel._build_channel_string:  s5    {{mDGG9C(;(;'<Bt?U?U>VW	
r   c                d    [        U R                  5      =(       d    [        U R                  5      $ r   )r0  r   r  r   s    r   r1  !SubkeyspaceitemChannel.is_pattern?  s#    4../V;t?U?U3VVr   c                    U R                   $ r   r4  r   s    r   r5  SubkeyspaceitemChannel.__str__C  r7  r   c                Z    SU R                   < SU R                  < SU R                   S3$ )NzSubkeyspaceitemChannel(, r9  r:  )r   r  r!  r   s    r   r<  SubkeyspaceitemChannel.__repr__F  s4    %d&9&9%<B%%(dggYa9	
r   c                    [        U[        5      (       a  U R                  UR                  :H  $ [        U[        5      (       a  U R                  U:H  $ [        $ r   )r?  r   r#  r   r@  rA  s     r   rC  SubkeyspaceitemChannel.__eq__L  sK    e344$$(:(:::eS!!$$--r   c                ,    [        U R                  5      $ r   rG  r   s    r   rI  SubkeyspaceitemChannel.__hash__S  rK  r   )r#  r!  r   r  NrM  )r   r   r  r   r!  r   rO  rP  rQ  rS  rT  rl   r   r   r   r      sG    " 1FM09

 W W!
'r   c                  |    \ rS rSr% SrSrS\S'   SSS jjrSS jr\	SS j5       r
SS	 jrSS
 jrSS jrSS jrSrg)r   iW  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                R    Xl         X l        X0l        U R                  5       U l        g r   )rY  r   r!  r"  r#  )r   rY  r   r!  s       r   r%   SubkeyspaceeventChannel.__init__g  s#    
, 668r   c                j    U R                    U R                   SU R                   SU R                   3$ )Nr)  r   )r  r!  rY  r   r   s    r   r"  -SubkeyspaceeventChannel._build_channel_stringm  s0    ++twwis4::,a8K8K7LMMr   c                d    [        U R                  5      =(       d    [        U R                  5      $ r   )r0  rY  r   r   s    r   r1  "SubkeyspaceeventChannel.is_patternp  s!    4::&J+d6I6I*JJr   c                    U R                   $ r   r4  r   s    r   r5  SubkeyspaceeventChannel.__str__t  r7  r   c                Z    SU R                   < SU R                  < SU R                   S3$ )NzSubkeyspaceeventChannel(r  r9  r:  )rY  r   r!  r   s    r   r<   SubkeyspaceeventChannel.__repr__w  s1    &tzznB""%U477)16	
r   c                    [        U[        5      (       a  U R                  UR                  :H  $ [        U[        5      (       a  U R                  U:H  $ [        $ r   )r?  r   r#  r   r@  rA  s     r   rC  SubkeyspaceeventChannel.__eq__}  sK    e455$$(:(:::eS!!$$--r   c                ,    [        U R                  5      $ r   rG  r   s    r   rI   SubkeyspaceeventChannel.__hash__  rK  r   )r#  r!  rY  r   NrM  )rY  r   r   r   r!  r   rO  rP  rQ  rS  rT  rl   r   r   r   r   W  sH     2FM19N K K!
'r   c                  0    \ rS rSrSrSrSrSrSrSr	Sr
S	rg
)ChannelTypei  u  
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subkeyspaceeventrl   N)rm   rn   ro   rp   rq   KEYSPACEKEYEVENTSUBKEYSPACESUBKEYEVENTSUBKEYSPACEITEMSUBKEYSPACEEVENTr   rl   r   r   r  r    s(    , HHKK'O)r   r  c                   [        U 5      nUR                  [        R                  5      (       a  [        R
                  $ UR                  [        R                  5      (       a  [        R                  $ UR                  [        R                  5      (       a  [        R                  $ UR                  [        R                  5      (       a  [        R                  $ UR                  [        R                  5      (       a  [        R                  $ UR                  [        R                  5      (       a  [        R                  $ g)ah  
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8&&&07788&&&o4455###o4455###r   c                b   [        U S5      (       a  U R                  n [        U 5      n SnU[        U 5      :  av  X   nUS:X  a  US-  nM   US;   a  gUS:X  aB  US-   nU[        U 5      :  a.  X   S:X  a  US-  nM  X   S	:X  a  gUS-  nU[        U 5      :  a  M.  US-  nU[        U 5      :  a  Mv  g
)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   r0  r0    s    ( w''&&wG 	
A
c'l
z4<FA:3;AAc'l":%FA:$Q c'l" 	
Q' c'l
( r   c                     \ rS rSrSr\SS.   SS jj5       r\SS j5       r\  S     SS jj5       r\  S     SS jj5       r	\  S     SS	 jj5       r
\  S     SS
 jj5       r\  S       SS jj5       r\  S       SS jj5       r\  S     SS jj5       r\S 5       r\S 5       r\S 5       r\S 5       r\\SS j5       5       r\   S       S S jj5       rSrg)!KeyspaceNotificationsInterfacei  a  
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                   g)z,Subscribe to keyspace notification channels.Nrl   )r   r  channelss      r   	subscribe(KeyspaceNotificationsInterface.subscribe       	r   c                    g)z0Unsubscribe from keyspace notification channels.Nrl   )r   r  s     r   unsubscribe*KeyspaceNotificationsInterface.unsubscribe       	r   c                    g)z6Subscribe to keyspace notifications for specific keys.Nrl   r   r   r!  r  s       r   subscribe_keyspace1KeyspaceNotificationsInterface.subscribe_keyspace       	r   c                    g)z=Subscribe to keyevent notifications for specific event types.Nrl   r   rY  r!  r  s       r   subscribe_keyevent1KeyspaceNotificationsInterface.subscribe_keyevent(  r  r   c                    g)z9Subscribe to subkeyspace notifications for specific keys.Nrl   r  s       r   subscribe_subkeyspace4KeyspaceNotificationsInterface.subscribe_subkeyspace2  r  r   c                    g)z@Subscribe to subkeyevent notifications for specific event types.Nrl   r  s       r   subscribe_subkeyevent4KeyspaceNotificationsInterface.subscribe_subkeyevent<  r  r   c                    g)zASubscribe to subkeyspaceitem notifications for a specific subkey.Nrl   )r   r   r  r!  r  s        r   subscribe_subkeyspaceitem8KeyspaceNotificationsInterface.subscribe_subkeyspaceitemF       	r   c                    g)zBSubscribe to subkeyspaceevent notifications for an event on a key.Nrl   )r   rY  r   r!  r  s        r   subscribe_subkeyspaceevent9KeyspaceNotificationsInterface.subscribe_subkeyspaceeventQ  r  r   c                    g)z7Get the next keyspace notification if one is available.Nrl   )r   ignore_subscribe_messagestimeouts      r   get_message*KeyspaceNotificationsInterface.get_message\  r  r   c                    g)z"Listen for keyspace notifications.Nrl   r   s    r   listen%KeyspaceNotificationsInterface.listene  r  r   c                    g)z6Close the notification manager and clean up resources.Nrl   r   s    r   close$KeyspaceNotificationsInterface.closej  r  r   c                    g r   rl   r   s    r   	__enter__(KeyspaceNotificationsInterface.__enter__o      r   c                    g r   rl   r   	_exc_type_exc_val_exc_tbs       r   __exit__'KeyspaceNotificationsInterface.__exit__s  r  r   c                    g);Check if there are any active subscriptions and not closed.Nrl   r   s    r   
subscribed)KeyspaceNotificationsInterface.subscribedw  s     	r   c                    g)z7Start a background thread that polls for notifications.Nrl   )r   poll_timeoutdaemonexception_handlers       r   run_in_thread,KeyspaceNotificationsInterface.run_in_thread}  s      	r   rl   r  r   r  SyncHandlerT | Noner  r   r   Nr   r   r!  r   r  r  rY  r   r!  r   r  r  r   r   r  r   r!  r   r  r  rY  r   r   r   r!  r   r  r  N        r  zbool | Noner  floatr  r  rP  r  FNr  r  r  r   r  \Callable[[BaseException, KeyspaceNotificationsInterface, KeyspaceWorkerThread], None] | Noner  KeyspaceWorkerThread)rm   rn   ro   rp   rq   r   r  r  r  r  r  r  r  r  r  r  r  r  r  rU  r  r  r   rl   r   r   r  r    s     (, %     '+	  %	   '+	  %	   '+	  %	   '+	  %	  
 '+  	
 %  
 '+  	
 %   26#.  
 	              "  	 
 r   r  c                     \ rS rSrSr  S   SS jjrSS.   SS jjr\      SS j5       rSS jr	\      SS	 j5       r
      SS
 jr      SS jr  S     SS jjr  S     SS jjr  S     SS jjr  S     SS jjr  S       S S jjr  S       S!S jjrS rS r   S"       S#S jjr\S$S j5       rSrg)%AbstractKeyspaceNotificationsi  ap  
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                *    Xl         X l        SU l        g)a  
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%  &AbstractKeyspaceNotifications.__init__  s     %)B&r   r  c                 ^^	 SnTb  U R                   m	UU	4S jnUn0 n0 nU HE  n[        US5      (       a  [        U5      nO[        U5      n[	        U5      (       a  X5U'   MA  X6U'   MG     U R                  XV5        U R                  XV5        g)aG  
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                F   > [         R                  U TS9nUb	  T" U5        g g )Nr   )r   r   )r   notificationr  r   s     r   _wrap_handler>AbstractKeyspaceNotifications.subscribe.<locals>._wrap_handler  s2    .;;
  <    +L) ,r   r#  )r   r  r   r   r0  _execute_subscribe_track_subscribe)
r   r  r  wrapped_handlerr,  patternsexact_channelsr   r  r   s
    `       @r   r  'AbstractKeyspaceNotifications.subscribe  s    ( ,0J* ,OGw//!'l&w/7##(7%.={+   	9h7r   c                    g)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)
Nrl   r   r1  r2  s      r   r.  0AbstractKeyspaceNotifications._execute_subscribe       	r   c                   / n/ nU H_  n[        US5      (       a  [        U5      nO[        U5      n[        U5      (       a  UR	                  U5        MN  UR	                  U5        Ma     U R                  X#5        U R                  X#5        g)z
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   r0  r(   _execute_unsubscribe_untrack_subscribe)r   r  r1  r2  r   r  s         r   r  )AbstractKeyspaceNotifications.unsubscribe  s{     Gw//!'l&w/7##,%%k2    	!!(;9r   c                    g)z
Execute the unsubscribe operation.

Args:
    patterns: List of pattern strings to punsubscribe from
    exact_channels: List of channel strings to unsubscribe from
Nrl   r5  s      r   r9  2AbstractKeyspaceNotifications._execute_unsubscribe  r7  r   c                    g)a3  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.
Nrl   r5  s      r   r/  .AbstractKeyspaceNotifications._track_subscribe      r   c                    g)zRemove patterns/channels from the subscription registry.

Override in subclasses that maintain their own subscription registry.
The default is a no-op.
Nrl   r5  s      r   r:  0AbstractKeyspaceNotifications._untrack_subscribe)  r@  r   c                4    [        XS9nU R                  XCS9  g)aN  
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)
rd  r  N)r   r  r   r   r!  r  r   s        r   r  0AbstractKeyspaceNotifications.subscribe_keyspace2  s    $ ".8w0r   c                4    [        XS9nU R                  XCS9  g)ac  
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)
rd  r  N)r   r  r   rY  r!  r  r   s        r   r  0AbstractKeyspaceNotifications.subscribe_keyeventG  s    $ "%/w0r   c                4    [        XS9nU R                  XCS9  g)a  
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.
rd  r  N)r   r  rD  s        r   r  3AbstractKeyspaceNotifications.subscribe_subkeyspace\  s      %^;w0r   c                4    [        XS9nU R                  XCS9  g)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.
rd  r  N)r   r  rG  s        r   r  3AbstractKeyspaceNotifications.subscribe_subkeyevento  s      %U2w0r   c                6    [        XUS9nU R                  XTS9  g)af  
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.
rd  r  N)r   r  )r   r   r  r!  r  r   s         r   r  7AbstractKeyspaceNotifications.subscribe_subkeyspaceitem  s    & )rRw0r   c                6    [        XUS9nU R                  XTS9  g)aL  
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.
rd  r  N)r   r  )r   rY  r   r!  r  r   s         r   r  8AbstractKeyspaceNotifications.subscribe_subkeyspaceevent  s    & *%BGw0r   c                    U $ r   rl   r   s    r   r  'AbstractKeyspaceNotifications.__enter__  s    r   c                $    U R                  5         g)NF)r  r  s       r   r  &AbstractKeyspaceNotifications.__exit__  s    

r   c                ^    U R                  5         [        U UUUS9nUR                  5         U$ )a  
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  +AbstractKeyspaceNotifications.run_in_thread  s7    f 	##%%/	
 	r   c                    g)zRaise :class:`~redis.RedisError` if any subscription lacks a handler.

Subclasses inspect their own subscription state to perform
the validation.
Nrl   r   s    r   rV  4AbstractKeyspaceNotifications._validate_all_handlers  r  r   )r&  r  r   NT)r   r  r  r   r  r1  dict[str, Any]r2  r]  r  Noner  r1  r   r2  r   r  r^  r  r  r  r  r  r  r   r  r^  )rm   rn   ro   rp   rq   r%  r  r   r.  r  r9  r/  r:  r  r  r  r  r  r  r  r  r  rV  r   rl   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                  Z   ^  \ rS rSrSr  S       SU 4S jjjrS	S jrS	S jrSrU =r	$ )
r"  i  ax  
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U ]  5         X0l        Xl        X l        X@l        [        R                  " 5       U l        g r   )	superr%  r  notificationsr  r  	threadingEvent_running)r   rd  r  r  r  	__class__s        r   r%  KeyspaceWorkerThread.__init__  s5     	*(!2!)r   c                   U R                   R                  5       (       a  gU R                   R                  5         U R                  nU R                  nU R                   R                  5       (       a2   UR                  SUS9  U R                   R                  5       (       a  M2  UR                  5         g! [         a*  nU R                  c  e U R                  X1U 5         SnANaSnAff = f)z=Main loop that polls for notifications and triggers handlers.NTr  r  )	rg  is_setr   rd  r  r  BaseExceptionr  r  )r   rd  r  es       r   runKeyspaceWorkerThread.run!  s    ==!!**((mm""$$?)).2L *  mm""$$ 		 ! ?))1&&q>>?s   3B5 5
C)? C$$C)c                8    U R                   R                  5         g)z
Stop the worker thread.

This signals the thread to exit its run loop. The thread will close
the notifications object before terminating.
N)rg  clearr   s    r   stopKeyspaceWorkerThread.stop3  s     	r   )rg  r  r  rd  r  )FN)rd  r  r  r  r  r   r  r!  r`  )
rm   rn   ro   rp   rq   r%  ro  rs  r   __classcell__rh  s   @r   r"  r"    sQ    "  *5* * 	*
* *,$ r   r"  c                     ^  \ rS rSrSr  S     SU 4S jjjr      SS jr      SS jr  S     SS jjrS r	\
SS j5       rSS	 jrS
 rSrU =r$ )KeyspaceNotificationsiB  a  
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                X   > [         TU ]  X#5        Xl        UR                  SS9U l        g)a&  
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)rc  r%  redispubsub_pubsub)r   redis_clientr   r  rh  s       r   r%  KeyspaceNotifications.__init__N  s.    & 	?!
 $**U*Kr   c                    U(       a  U R                   R                  " S0 UD6  U(       a  U R                   R                  " S0 UD6  gg)z2Execute subscribe on the single pubsub connection.Nrl   )r}  
psubscriber  r5  s      r   r.  (KeyspaceNotifications._execute_subscribeh  s9     LL##/h/LL""4^4 r   c                    U(       a  U R                   R                  " U6   U(       a  U R                   R                  " U6   gg)z4Execute unsubscribe on the single pubsub connection.N)r}  punsubscriber  r5  s      r   r9  *KeyspaceNotifications._execute_unsubscribeq  s3     LL%%x0LL$$n5 r   c                    Uc  U R                   nU R                  (       a  gU R                  R                  UUS9nUb  [        R                  X0R                  S9$ g)a  
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.
Nrk  r*  )r  r&  r}  r  r   r   r   )r   r  r  r   s       r   r  !KeyspaceNotifications.get_messagez  sd    ( %,(,(F(F%<<
 ,,**&? + 

 "//OO/TTr   c              #     #    U R                   (       a*  U R                  SS9nUb  Uv   U R                   (       a  M)  gg7f)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  KeyspaceNotifications.listen  8      oo++C+8L'"" ooo   9??c                ^    U R                   (       + =(       a    U R                  R                  $ r
  )r&  r}  r  r   s    r   r   KeyspaceNotifications.subscribed  s      <<;DLL$;$;;r   c                    U R                   R                  R                  5        H  u  pUb  M
  [        SU S35      e   U R                   R                  R                  5        H  u  p2Ub  M
  [        SU S35      e   g)zCRaise if any subscription in the underlying PubSub lacks a handler.N	Channel '' has no handler registered	Pattern ')r}  r  itemsr   r1  r   r   r  patterns       r   rV  ,KeyspaceNotifications._validate_all_handlers  sx     $ 5 5 ; ; =G 9WI5P!QRR !> !% 5 5 ; ; =G 9WI5P!QRR !>r   c                h    SU l          U R                  R                  5         g! [         a     gf = f)z3Close the pubsub connection and clean up resources.TN)r&  r}  r  	Exceptionr   s    r   r  KeyspaceNotifications.close  s0    	LL  		s   $ 
11)r&  r}  r{  r[  )r~  r   r   r  r  r   r\  r_  r  r  rP  r`  )rm   rn   ro   rp   rq   r%  r.  r9  r  r  rU  r  rV  r  r   ru  rv  s   @r   rx  rx  B  s    	 *.*.	LL 'L $(	L L45&58F5	56!63<6	6 26%#.% % 
 	%N#& < <S r   rx  c                  ,  ^  \ rS rSrSr  S     SU 4S jjjr\SS j5       r      SS jr      SS jr	SS jr
S rSS	 jrSS
 jr      SS jr      SS jrSS jr  S     S S jjr    S!S jrS rS rSS jrS rS rSrU =r$ )"ClusterKeyspaceNotificationsi  a8  
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U ]  X#5        Xl        0 U l        0 U l        0 U l        [        R                  " 5       U l        SU l	        g)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)
rc  r%  cluster_subscribed_patterns_subscribed_channels_node_pubsubsre  Lock_refresh_lock_poll_index)r   redis_clusterr   r  rh  s       r   r%  %ClusterKeyspaceNotifications.__init__  sN    & 	?$ 57!46! .0 '^^- r   c                    U R                   (       + =(       a(    [        U R                  =(       d    U R                  5      $ r  )r&  r   r  r  r   s    r   r  'ClusterKeyspaceNotifications.subscribed  s3     << 
D%%B)B)B%
 	
r   c                    U(       a  U R                   R                  U5        U(       a  U R                  R                  U5        gg)zATrack newly subscribed patterns/channels in the cluster registry.N)r  updater  r5  s      r   r/  -ClusterKeyspaceNotifications._track_subscribe  s7     %%,,X6%%,,^< r   c                    U H  nU R                   R                  US5        M!     U H  nU R                  R                  US5        M!     g)z3Remove patterns/channels from the cluster registry.N)r  popr  )r   r1  r2  pcs        r   r:  /ClusterKeyspaceNotifications._untrack_subscribe  sD     A%%))!T2 A%%))!T2  r   c                    U R                   R                  5        H  u  pUb  M
  [        SU S35      e   U R                  R                  5        H  u  p2Ub  M
  [        SU S35      e   g)zBRaise if any subscription in the cluster registry lacks a handler.Nr  r  r  )r  r  r   r  r  s       r   rV  3ClusterKeyspaceNotifications._validate_all_handlers  sr     $ 9 9 ? ? AG 9WI5P!QRR !B !% 9 9 ? ? AG 9WI5P!QRR !Br   c                6    U R                   R                  5       $ )z%Get all primary nodes in the cluster.)r  get_primariesr   s    r   _get_all_primary_nodes3ClusterKeyspaceNotifications._get_all_primary_nodes!  s    ||))++r   c                    U R                   R                  US5      nU(       a   UR                  5         gg! [         a     gf = f)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  r  r  r  )r   	node_namer|  s      r   _cleanup_node*ClusterKeyspaceNotifications._cleanup_node%  sF     ##''	48   s   7 
AAc                    UR                   U R                  ;  aB  U R                  R                  U5      nUR	                  SS9nX0R                  UR                   '   U R                  UR                      $ )z+Get or create a PubSub instance for a node.Frz  )namer  r  get_redis_connectionr|  )r   node
redis_connr|  s       r   _ensure_node_pubsub0ClusterKeyspaceNotifications._ensure_node_pubsub2  sd    99D...::4@J  &&&GF,2tyy)!!$)),,r   c                   U(       d  U(       d  g/ nU R                  5        H  nUR                  U R                  ;  nU R                  U5      n U(       aZ  U R                  (       a  UR
                  " S0 U R                  D6  U R                  (       a  UR                  " S0 U R                  D6  U(       a  UR
                  " S0 UD6  U(       a  UR                  " S0 UD6  M  M     U(       a&  [        R                  SSR                  U5      5        gg! [         a;    U R                  UR                  5        UR                  UR                  5         GM9  f = f)aG  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.r  rl   )r  r  r  r  r  r  r  r  r  r  r(   loggerwarningjoin)r   r1  r2  failed_nodesr  is_new_noder|  s          r   r.  /ClusterKeyspaceNotifications._execute_subscribe=  s!     "$//1D))4+=+==K--d3F/ 00))FD,E,EF00((E4+D+DE%%11!$$6~6 " 2, NNI		,'   / ""499-##DII..	/s   BDA EEc                b    U(       a  U R                  USS9  U(       a  U R                  USS9  gg)z)Execute unsubscribe on all cluster nodes.T)use_punsubscribeFN)_unsubscribe_from_all_nodesr5  s      r   r9  1ClusterKeyspaceNotifications._execute_unsubscriben  s5     ,,X,M,,^e,T r   c                p   / n[        U R                  R                  5       5       H-  u  pE U(       a  UR                  " U6   M  UR                  " U6   M/     U(       a&  [        R                  SSR                  U5      5        gg! [
         a&    U R                  U5        UR                  U5         M  f = f)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.r  N)listr  r  r  r  r  r  r(   r  r  r  )r   r  r  r  r  r|  s         r   r  8ClusterKeyspaceNotifications._unsubscribe_from_all_nodesw  s     #%!%d&8&8&>&>&@!AI/#''2&&1 "B NNL		,' 	  /""9-##I./s   BB,B54B5c                   U R                   (       a  g[        U R                  5      nUS:X  a  US:  a  [        R                  " U5        gUc  U R
                  nUS:X  a  U R                  U5      $ [        SU[        US5      -  5      n[        R                  " 5       nXR-   n [        R                  " 5       U:  a  g[        U R                  R                  5       5      nU(       d  gU R                  [        U5      -  U l        XpR                     nU =R                  S-  sl         UR                  UUS9n	U	b"  [$        R'                  XR(                  S9n
U
b  U
$ M  ! [        [        [         4 a    U R#                  5          M  f = f)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   rk  r*  )r&  r   r  timesleepr  _poll_all_nodes_onceminmax	monotonicr  valuesr  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  (ClusterKeyspaceNotifications.get_message  s   ( <<$,,-! {

7# %,(,(F(F% c>,,-FGG sGc+q.A$AB^^%
'~~8+4--4467G  $//#g,>D--.F! ,,.G, -  "  /;;  <    +''? & $\:> 446s   *E! !%F
	F
c                h   Sn[        U R                  R                  5       5       HT  n UR                  USS9nUc  M  [        R                  X@R                  S9nUc  M;  U(       a  U R                  5         Us  $    U(       a  U R                  5         g! [        [
        [        4 a    Sn M  f = f)a  
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  rk  TNr*  )r  r  r  r  r   r   r   r   r   r   r  )r   r  	had_errorr|  r   r+  s         r   r  1ClusterKeyspaceNotifications._poll_all_nodes_once  s     	4--4467F	 ,,.G -  "  /;;  <    + !<<>''/ 84 002- $\:>  !		s   BB10B1c              #     #    U R                   (       a*  U R                  SS9nUb  Uv   U R                   (       a  M)  gg7f)av  
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  #ClusterKeyspaceNotifications.listen  r  r  c                |    SU l          U R                  5         g! [         a    [        R	                  SSS9   gf = f)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)r  refresh_subscriptionsr  r  r  r   s    r   r  <ClusterKeyspaceNotifications._refresh_subscriptions_on_error"  sC     	&&( 	NNS  	s    ;;c                \     UR                   nUc  gUR                  $ ! [         a     gf = f)z,Check if a pubsub connection is still alive.F)
connectionis_connectedr  )r   r|  conns      r   _is_pubsub_connected1ClusterKeyspaceNotifications._is_pubsub_connected3  s8    	$$D|$$$ 		s     
++c                   U R                      U R                  5        Vs0 s H  oR                  U_M     nn[        U R                  R                  5       5      [        UR                  5       5      -
  nU H  nU R                  U5        M     [        U R                  R                  5       5      [        UR                  5       5      -  nU HP  nU R                  R                  U5      nU(       d  M'  U R                  U5      (       a  M?  U R                  U5        MR     [        UR                  5       5      [        U R                  R                  5       5      -
  n/ nU Hu  nX$   nU R                  U5      n U R                  (       a  UR                  " S0 U R                  D6  U R                  (       a  UR                  " S0 U R                  D6  Mu  Mw     U(       a  [!        SSR#                  U5       35      e SSS5        gs  snf ! [         a&    U R                  U5        UR                  U5         M  f = f! , (       d  f       g= f)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: r  Nrl   )r  r  r  r   r  keysr  r   r  r  r  r  r  r  r  r(   r   r  )	r   r  current_primariesremoved_nodesr  existing_nodesr|  	new_nodesr  s	            r   r  2ClusterKeyspaceNotifications.refresh_subscriptions=  s    ,0,G,G,I!,ID		4,I  !
   2 2 7 7 9:S!&&(> M +	""9- +
 !!3!3!8!8!:;c!&&(? N ,	++//	:6$";";F"C"C&&y1	 , -2245D<N<N<S<S<U8VVI&(L&	(311$7300))FD,E,EF00((E4+D+DE 1 ' %<TYY|=T<UV  U  !F ! 3&&y1 ''	23I  sN   IH
B;I5IA.I<AH*I
I,H?;I>H??I
Ic                    SU l         [        U R                  R                  5       5       H  nU R	                  U5        M     U R
                  R                  5         U R                  R                  5         g)z4Close all pubsub connections and clean up resources.TN)r&  r  r  r  r  r  rr  r  )r   r  s     r   r  "ClusterKeyspaceNotifications.closex  sY    d005578Iy) 9!!'')!!'')r   )r&  r  r  r  r  r  r  r[  )r  r   r   r  r  r   rP  r\  r_  r`  )r  r   r  r^  )r  r
   )r  r   r  r   r  r  )r  r   r  r  )rm   rn   ro   rp   rq   r%  rU  r  r/  r:  rV  r  r  r  r.  r9  r  r  r  r  r  r  r  r  r   ru  rv  s   @r   r  r    sA    *.*.	$#$ '$ $(	$ $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   ):rq   
__future__r   loggingr  re  r  abcr   r   collections.abcr   dataclassesr   enumr   typingr	   r
   r   r   redis.clientr   redis.clusterr   redis.exceptionsr   r   r   redis.utilsr   	getLoggerrm   r  r   r   bytesr   r  SyncHandlerTr   r   r   r   r   r   r   r   r   r  r  r0  r  r$  Threadr"  rx  r  rl   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   