b N ddlZddlZddlZddlZddlZddlZddlZddlmZddl m Z m Z m Z m Z ddlmZmZmZmZddlmZmZmZddlmZmZmZmZddlmZmZddlm Z m!Z!m"Z"m#Z#m$Z$m%Z%m&Z&m'Z'm(Z(m)Z)m*Z*m+Z+m,Z,m-Z-m.Z.m/Z/dd l0m1Z1dd l2m3Z3m4Z4m5Z5m6Z6m7Z7ejpe9Z:d e;d edZ?dZ@de de d e e e$>$M$M Q%%r5c ti}g}|jD]\}}t|fi|\}}|||<||z } ||fSN)itemsr ) commandresr<cursorsret node_nameresponsecursorrs r3parse_scan_resultrH7sXG C"yy{ 8x373 #  q C<r5c t}|jD]}|D]\}} ||xx|z cc<|jDcgc] \}}||f }}}|S#t$r|||<YNwxYwcc}}wr>)rvaluesKeyErrorr?) r@rAr<numsub_d numsub_tupschannel numsubbednumsub ret_numsubs r3parse_pubsub_numsubrRBs}Hzz|. "- . GY .!Y.! ..>;KL7F#LJL   .$-! .Ms A A2A/.A/respr<c |jdd dtdtttff fd }i}|D].}|dd\}}}|dd}|||D cgc]} ||  c} d|||f<0|Scc} w)N current_hostr;r.c2t|dxs|dfSNr)r+)r;rUs r3 fix_serverz'parse_cluster_slots..fix_serverTs DG$4 d1g==r5)primaryreplicas)getrrstr) rSr<rZslotsslotstartendr\r]replicarUs @r3parse_cluster_slotsreOs;;~r2L>#>%S/> E "2AhsG8!7+=EF'W-F eSj  LGs A*c `g}|D]}ggd}tdt|ddD]'}|dj|d||d|dzf)|d}|D]A}i}tdt|dD]}||dz|||<|dj|C|j||S)z( Parse CLUSTER SHARDS response. )r`nodesrrYr`r[rg)rangelenappend) rSr<shardsxshardirgnode dict_nodes r3parse_cluster_shardsrrcsF  r*q#ad)Q' )sumlistrJr@rAs r3zAbstractRedisCluster.ZsCSZZ\8J4Kr5rr) rrrrrrrrrrrcXt|trt|jS|Sr>) isinstancedictrrJrs r3rzAbstractRedisCluster.kjd6KSZZ\!2QTr5rrcXt|trt|jS|Sr>)rrrrJrs r3rzAbstractRedisCluster.orr5rcBt|jdkDrdSdSrX)rrJrs r3rzAbstractRedisCluster.rs#cjjl:Ka:OQUVr5rrcPt|jjSr>)rrJpoprs r3rzAbstractRedisCluster.vs$szz|2D2H2H2Jr5rcft|jDcgc] }t|c}Scc}wr>)ziprJr)r@rArs r3rzAbstractRedisCluster.ys#S#**,EW4XSV4X4Xs.rc4t|jSr>)rrJrs r3rzAbstractRedisCluster.{sSZZ\ARr5N)rrrRedisClusterRequestTTL PRIMARIESREPLICAS ALL_NODESRANDOM DEFAULT_NODE NODE_FLAGSr'r(SLOT_ID COMMAND_FLAGSSEARCH_COMMANDSrerr#CLUSTER_COMMANDS_RESPONSE_CALLBACKSrRr)rHRESULT_CALLBACKSrr#rERRORS_ALLOW_RETRYr1r5r3rrstIHI F!LXy&,GJH R UK X    ?+V4   }hMV "OJ-.+' "?+-@A  K  6#45|D  U  v  T     V  6($56 OJ    X  >*,RSI%N*<9IJr5rceZdZedZ d%dZdZdZdZdZ dZ d Z d&d Z d Z d Zd ZdZd'dZdZdZd(dZd&dZd)dZ d*dZdZdZdZdZdZdZdZdZdZ dZ!d Z"d!Z#d"Z$d#Z%d$Z&y)+ RedisClusterc |dd|i|S)a Return a Redis client object configured from the given URL For example:: redis://[[username]:[password]]@localhost:6379/0 rediss://[[username]:[password]]@localhost:6379/0 unix://[[username]:[password]]@/path/to/socket.sock?db=0 Three URL schemes are supported: - `redis://` creates a TCP socket connection. See more at: - `rediss://` creates a SSL wrapped TCP socket connection. See more at: - ``unix://``: creates a Unix Domain Socket connection. The username, password, hostname, path and all querystring values are passed through urllib.parse.unquote in order to replace any percent-encoded values with their corresponding characters. There are several ways to specify a database number. The first value found will be used: 1. A ``db`` querystring option, e.g. redis://localhost?db=0 2. If using the redis:// or rediss:// schemes, the path argument of the url, e.g. redis://localhost/0 3. A ``db`` keyword argument to this function. If none of these options are specified, the default db=0 is used. All querystring options are cast to their appropriate Python types. Boolean arguments can be specified with string values "True"/"False" or "Yes"/"No". Values that cannot be properly cast cause a ``ValueError`` to be raised. Once parsed, the querystring arguments and keyword arguments are passed to the ``ConnectionPool``'s class initializer. In the case of conflicting arguments, querystring arguments always win. urlr1r1)clsrrs r3from_urlzRedisCluster.from_urlsT%s%f%%r5Nc  |g}d| vr tdd} | d} t| } d| vr tdd| vr| ddk7r td | j| | jd }| jd |}|j t ||n9|||j t ||nt |dk(r td tjd || jdd|_ | jd|jitdi| } t| jdd| jdd| jdd|_||_|j j"j%|_|j j(j%|_||_d|_||_d|_t5d|| ||d| |_t7|j j8|_t7|j j<|_tA||_!tEjF|_$y)a Initialize a new RedisCluster client. :startup_nodes: 'list[ClusterNode]' List of nodes from which initial bootstrapping can be done :host: 'str' Can be used to point to a startup node :port: 'int' Can be used to point to a startup node :require_full_coverage: 'bool' When set to False (default value): the client will not require a full coverage of the slots. However, if not all slots are covered, and at least one node has 'cluster-require-full-coverage' set to 'yes,' the server will throw a ClusterDownError for some key-based commands. See - https://redis.io/topics/cluster-tutorial#redis-cluster-configuration-parameters When set to True: all slots must be covered to construct the cluster client. If not all slots are covered, RedisClusterException will be thrown. :read_from_replicas: 'bool' Enable read from replicas in READONLY mode. You can read possibly stale data. When set to true, read commands will be assigned between the primary and its replications in a Round-Robin manner. :dynamic_startup_nodes: 'bool' Set the RedisCluster's startup nodes to all of the discovered nodes. If true (default value), the cluster's discovered nodes will be used to determine the cluster nodes-slots mapping in the next topology refresh. It will remove the initial passed startup nodes if their endpoints aren't listed in the CLUSTER SLOTS output. If you use dynamic DNS endpoints for startup nodes but CLUSTER SLOTS lists specific IP addresses, it is best to set it to false. :cluster_error_retry_attempts: 'int' Retry command execution attempts when encountering ClusterDownError or ConnectionError :reinitialize_steps: 'int' Specifies the number of MOVED errors that need to occur before reinitializing the whole cluster topology. If a MOVED error occurs and the cluster does not need to be reinitialized on this current error handling, only the MOVED slot will be patched with the redirected node. To reinitialize the cluster on every MOVED error, set reinitialize_steps to 1. To avoid reinitializing the cluster on moved errors, set reinitialize_steps to 0. :**kwargs: Extra arguments that will be sent into Redis instance when created (See Official redis-py doc for supported kwargs [https://github.com/andymccurdy/redis-py/blob/master/redis/client.py]) Some kwargs are not supported and will raise a RedisClusterException: - db (Redis do not support database SELECT in cluster mode) Nrvz4Argument 'db' is not possible to use in cluster modeFTpathzFRedisCluster does not currently support Unix Domain Socket connectionsrz9A ``db`` querystring option can only be 0 in cluster moder,r-a5RedisCluster requires at least one node to discover the cluster. Please provide one of the followings: 1. host and port, for example: RedisCluster(host='localhost', port=6379) 2. list of startup nodes, for example: RedisCluster(startup_nodes=[ClusterNode('localhost', 6379), ClusterNode('localhost', 6378)])zstartup_nodes : r}rxutf-8rystrictrw) startup_nodesrrequire_full_coveragedynamic_startup_nodesr1)%rrupdater^rk ClusterNoderjlogdebugruser_on_connect_func on_connectrrencodercluster_error_retry_attempts __class__rcopy command_flagsr node_flagsread_from_replicasreinitialize_counterreinitialize_steps nodes_manager NodesManagerrrcluster_response_callbacksrresult_callbacksr commands_parser threadingr&_lock) selfr,r-rrrr rrrrr url_optionss r3__init__zRedisCluster.__init__s`F  M 6>'F   ?H#C.K$+){"{4'8A'=+O MM+ &::f%D::fd+D  T4!8 9  $"2  T4!8 9  1 $'4  $]O45 %+JJ/CT$J! +T__=>)&) JJz7 + JJ(( 3 JJ)5 1  -I)!^^99>>@..3388:"4$%!"4!) '"7"7    +> NN > >+ '!4DNN4S4S T-d3^^% r5c|Sr>r1rs r3 __enter__zRedisCluster.__enter__?s r5c$|jyr>closerexc_type exc_value tracebacks r3__exit__zRedisCluster.__exit__B  r5c$|jyr>rrs r3__del__zRedisCluster.__del__Er r5c|jD]4}|js |jjj6y#t$rYCwxYwr>) get_nodesredis_connectionr8 disconnectOSErrorrrps r3disconnect_connection_poolsz(RedisCluster.disconnect_connection_poolsHsRNN$ D$$))99DDF s$A  AAc|jt|j|jr8|j dt |j dk7r td|j|j|yy)z Initialize the connection, authenticate and select a database and send READONLY if it is set during object initialization. rOKzREADONLY command failedN) set_parserrrr send_commandr+ read_responserr)rr7s r3rzRedisCluster.on_connectQsx m,  " "  # #J /J44674?%&?@@  $ $ 0  % %j 1 1r5c|jsI|j5|js|jj|gddd|jS|jS#1swY|jSxYwr>)r%rr create_redis_connectionsr(s r3get_redis_connectionz!RedisCluster.get_redis_connectionfsk$$ H,,&&??G H$$$t$$$ H$$$s )A""A6c<|jj|||Sr>)r get_noderr,r-rDs r3r3zRedisCluster.get_nodems!!**4yAAr5c@|jjtSr>)r get_nodes_by_server_typePRIMARYrs r3 get_primarieszRedisCluster.get_primariesp!!::7CCr5c@|jjtSr>)r r6REPLICArs r3 get_replicaszRedisCluster.get_replicassr9r5ctjt|jjj Sr>)rchoicerr  nodes_cacherJrs r3get_random_nodezRedisCluster.get_random_nodevs+}}T$"4"4"@"@"G"G"IJKKr5c\t|jjjSr>)rr r?rJrs r3r$zRedisCluster.get_nodesys"D&&2299;<?r5c||j}|jtd|jd|jj S)ai Returns a Monitor object for the specified target node. The default cluster node will be selected if no target node was specified. Monitor is useful for handling the MONITOR command to the redis server. next_command() method returns one command from monitor listen() method yields commands from monitor. z Cluster Node z has no redis_connection)rLr%rrOmonitor)r target_nodes r3rSzRedisCluster.monitors\  //1K  ' ' /' 0 011IJ ++3355r5c "t|f|||d|S)z~ Allows passing a ClusterNode, or host&port, to get a pubsub instance connected to the specified node )rpr,r-) ClusterPubSub)rrpr,r-rs r3pubsubzRedisCluster.pubsubs TM4dMfMMr5c &|r td|r tdt|j|j|jj|j |j |j|j|j|j S)ac Cluster impl: Pipelines do not work in cluster mode the same way they do in normal mode. Create a clone of this object so that simulating pipelines will work correctly. Each command will be called directly when used and when calling execute() will only return the result stack. z(shard_hint is deprecated in cluster modez)transaction is deprecated in cluster mode) r rrrr rrr lock) rClusterPipeliner rrrr rrr r)r transaction shard_hints r3pipelinezRedisCluster.pipelines '(RS S '(ST T,, 00,,::!22'+'F'F)-)J)J#66#66  r5c 0|t}||||||||S)aL Return a new Lock object using key ``name`` that mimics the behavior of threading.Lock. If specified, ``timeout`` indicates a maximum life for the lock. By default, it will remain locked until release() is called. ``sleep`` indicates the amount of time to sleep per loop iteration when the lock is in blocking mode and another client is currently holding the lock. ``blocking`` indicates whether calling ``acquire`` should block until the lock has been acquired or to fail immediately, causing ``acquire`` to return False and the lock not being acquired. Defaults to True. Note this value can be overridden by passing a ``blocking`` argument to ``acquire``. ``blocking_timeout`` indicates the maximum amount of time in seconds to spend trying to acquire the lock. A value of ``None`` indicates continue trying forever. ``blocking_timeout`` can be specified as a float or integer, both representing the number of seconds to wait. ``lock_class`` forces the specified lock implementation. Note that as of redis-py 3.0, the only lock class we implement is ``Lock`` (which is a Lua-based lock). So, it's unlikely you'll need this parameter, unless you have created your own custom lock class. ``thread_local`` indicates whether the lock token is placed in thread-local storage. By default, the token is placed in thread local storage so that a thread only sees its token, not a token set by another thread. Consider the following timeline: time: 0, thread-1 acquires `my-lock`, with a timeout of 5 seconds. thread-1 sets the token to "abc" time: 1, thread-2 blocks trying to acquire `my-lock` using the Lock instance. time: 5, thread-1 has not yet completed. redis expires the lock key. time: 5, thread-2 acquired `my-lock` now that it's available. thread-2 sets the token to "xyz" time: 6, thread-1 finishes its work and calls release(). if the token is *not* stored in thread local storage, then thread-1 would see the token value as "xyz" and would be able to successfully release the thread-2's lock. In some use cases it's necessary to disable thread local storage. For example, if you have code where one thread acquires a lock and passes that lock instance to a worker thread to release later. If thread local storage isn't disabled in this case, the worker thread won't see the token set by the thread that acquired the lock. Our assumption is that these cases aren't common and as such default to using thread local storage.)timeoutsleepblockingblocking_timeout thread_localr%)rrOr_r`rarb lock_classrcs r3rYzRedisCluster.locks2|  J  -%  r5c"||j|<y)zSet a custom Response CallbackN)r )rr@callbacks r3set_response_callbackz"RedisCluster.set_response_callback#s3;''0r5c.|dj}t|dk\rB|dd|dj|jvr|dd|dj}|jdd}||}n|jj |}|rt j d|d|||jjk(r|jgS||jjk(r|jS||jjk(r|jS||jjk(r|jS||jj k(r|j"j$gS||jj&dvr|j"j$gS|j(|}|j"j+||j,xr|t.v}t j d|d ||gS) Nrrh rYr|zTarget node/s for z: z Target for z: slot )upperrjrrr^rrrrr@rr8rr<rr$rr rKrdetermine_slotget_node_from_slotrr )rr;rr@r| command_flagrarps r3_determine_nodeszRedisCluster._determine_nodes'sq'--/ t9>a 47)4::<@R@RRa 47),224GZZ d3  !%L --11':L  II*7)2l^D E 4>>00 0((*+ + T^^55 5%%' ' T^^44 4$$& & T^^55 5>># # T^^88 8&&334 4 66q9 9&&334 4'4&&-D%%88d--J']2JD II D67 86Mr5cZ|jdk(ry|j|jzdk(S)NrF)r r rs r3_should_reinitializedz"RedisCluster._should_reinitializedOs1  " "a ',,t/F/FF!K Kr5cN|jj|}t|S)z Calculate keyslot for a given key. See Keys distribution model in https://redis.io/topics/cluster-spec )rencoder)rrFrs r3rDzRedisCluster.keyslotYs" LL   ${r5cp|jj}|jj|g|S)a Get the keys in the command. If the command has no keys in in, None is returned. NOTE: Due to a bug in redis<7.0, this function does not work properly for EVAL or EVALSHA when the `numkeys` arg is 0. - issue: https://github.com/redis/redis/issues/9493 - fix: https://github.com/redis/redis/pull/9733 So, don't use this function with EVAL or EVALSHA. )rLr%rget_keys)rr; redis_conns r3_get_command_keyszRedisCluster._get_command_keysas6**,== ,t##,,Z?$??r5c|d}|jj|tk(r|dS|dvrTt|dkrt d||d}|dd|z}t|dk(rt j dtS|}nK|j|}|t|dk(r,|dvrt j dtSt d|t|dk(r|j|dS|Dchc]}|j|}}t|dk7rt |d |jScc}w) a Figure out what slot to use based on args. Raises a RedisClusterException if there's a missing key and we can't determine what slots to map the command to; or, if the keys don't all map to the same key slot. rrY)EVALEVALSHArhzInvalid args in command: r[)FCALLFCALL_ROzNo way to dispatch this command to Redis Cluster. Missing key. You can execute the command by specifying target nodes. Command: z) - all keys must map to the same key slot) rr^rrjrr randrangerrvrDr)rr;r@num_actual_keys eval_keyskeysrFr`s r3rkzRedisCluster.determine_slotpsiq'    ! !' *g 57N ) )4yA~+.Gv,NOO"1gOQ_!45I9~"''+CDDD)4))40D|s4yA~33!++A/GHH+//3f6 t9><<Q( (/33sc"33 u:?')DE yy{ 4s6D<c|jS)z. Get the connections' encoder )rrs r3 get_encoderzRedisCluster.get_encoders||r5c.|jjS)z9 Get the connections' key-word arguments )r rrs r3get_connection_kwargsz"RedisCluster.get_connection_kwargss!!333r5cBt|txr||jvSr>)rr_r)r target_nodess r3_is_nodes_flagzRedisCluster._is_nodes_flags,,P1PPr5ct|tr|}|St|tr|g}|St|tr|j }|St dt |)Nztarget_nodes type can be one of the following: node_flag (PRIMARIES, REPLICAS, RANDOM, ALL_NODES),ClusterNode, list, or dict. The passed type is )rrrrrJ TypeErrortype)rrrgs r3_parse_target_nodesz RedisCluster._parse_target_nodess{ lD ) E   k 2!NE  d +!'')E &'+<&8%9; r5cd}d}|jdd}|$|j|s|j|}d}|rdn |j}d}t d|D]p} i} |s'|j |i|d|i}|st d|d |D]%} |j| g|i|| | j<'|j|d| fi|cS|#t$r0} t| |jjvr| }n| Yd} ~ d} ~ wwxYw) a" Wrapper for ERRORS_ALLOW_RETRY error handling. It will try the number of times specified by the config option "self.cluster_error_retry_attempts" which defaults to 3 unless manually configured. If it reaches the number of times, the command will raise the exception Key argument :target_nodes: can be passed with the following types: nodes_flag: PRIMARIES, REPLICAS, ALL_NODES, RANDOM ClusterNode list dict FNrTrYrr|!No targets were found to execute command on)rrrrrirnr_execute_commandrO_process_result BaseExceptionrrr) rr;rtarget_nodes_specifiedrpassed_targetsretry_attempts exception_rArpes r3execute_commandzRedisCluster.execute_commandsb "' ND9  %d.A.A..Q33NCL%) "(AT-N-N  q.) A -#84#8#8$!'$4B$L(3?v[Q)RD%:T%:%:4%Q$%Q&%QC NR,t++DGSCFCC 6! 7dnn???!"IG  sA+C D &DD c|d}d}d}d}d}d} t|j} d} | dkDr@| dz} |r|j|}nC| rA|j|} |jj | |j xr|tv}d} tjd|d|jd|j|j|}t|g|i|}|r'|jd |j|d fi|d}|j||j||fi|} ||j vr|j || fi|} | ||j"j%|SStYd#t&t(t*f$r$}tj-t/|d}~wt0t2f$r}}tj-t/|||j5| dz } | d krt7j8d n"d|_|jj=Yd}~nd}~wt>$r}tj-d |xj@dz c_ |jCr"|jj=d|_ n|jjE|d } Yd}~nUd}~wtF$r@tj-d| |jdz krt7j8dYn tH$rB}tj-dtK|jL|jN}d }Yd}~nd}~wtP$rK}tj-dt7j8d |jj=|d}~wtR$r/}|jU}tj-d||d}~wtV$r.}tj-d|r|j5|d}~wwxYw|?|j"j%|n##||j"j%|wwxYw| dkDr0)z9 Send a command to a node in the cluster rNFrYrNzExecuting command z on target node: riASKINGg?rTr$rhg?rr2rzResponseError: rzTTL exhausted.)-intrr3rkr rlrr rr server_typerOr1r9r-parse_responser r8releaserrrrrrr#r&timer`r% initializerr rpupdate_moved_exceptionr$rr4r,r-rr!__str__rr)rrTr;rr@r:r7 redirect_addraskingmovedttlconnection_error_retry_counterrarErmessages r3rzRedisCluster._execute_command sq'   $--.)*&Ag 1HCf C"&----"HK/4..5D"&"4"4"G"Gd55R']:R#K"E ( 1B"../q1A1A0BD"66{C +JHHH ++H5-J--j(MfM"F' ''.4:44ZSFSd===Gt>>wG  $* H P)..66zB*+,,S*+;=PQ  d1g&#\2  d1g&)))+.!3. 2A5JJt$48K0&&113 %  l+))Q.)--/&&11301D-&&==a@  % o.44q88JJt$  j) -166 G #  01 4 ""--/  ))+ y9:   o.))+   )..66zB)..66zB*OAgsDE88P F++P=A2H5/P35 PA9K:P3APP3 P8M P3 PAN"" P.*O P$)P  PP33 Qc |j5|jr|jjdddy#1swYyxYw#t$rYywxYwr>)rr rAttributeErrorrs r3rzRedisCluster.closesQ  /%%&&,,. / / /   s+ A '>A AA A AAc ||jvr|j|||fi|St|dk(rt|jdS|S)a  Process the result of the executed command. The function would return a dict or a single value. :type command: str :type res: dict `res` should be in the following format: Dict rYr)rrjrrJ)rr@rArs r3rzRedisCluster._process_resultsY d++ +14((1'3I&I I X] %a( (Jr5ct|||y)a This function can be used to add externally defined redis modules, and their namespaces to the redis client. ``funcname`` - A string containing the name of the function to create ``func`` - The function, being added to this class. N)setattr)rfuncnamefuncs r3load_external_modulez!RedisCluster.load_external_modules h%r5) NiNr[F FTNNNN)Fr>NN)Ng?TNNT)'rrr classmethodrrrrr"r)rr1r3r8r<r@r$rIrLrQrSrWr]rYrgrnrprDrvrkrrrrrrrrrr1r5r3rrs)&)&Z %&# " O&b2*%BDDL=$(/  6"N <H T<&PL @7r 4 Q(=~w-r(&r5rc&eZdZddZdZdZdZy)rNc|dk(rtj|}||_||_t |||_||_||_y)N localhost)socket gethostbynamer,r-r4rOrr%)rr,r-rr%s r3rzClusterNode.__init__sF ; ''-D  !$- & 0r5c d|jd|jd|jd|jd|jd S)Nz[host=z,port=z,name=z ,server_type=z,redis_connection=])r,r-rOrr%rs r3__repr__zClusterNode.__repr__sWTYYK II;II;++,- $ 5 56a  9 r5cXt|txr|j|jk(Sr>)rrrO)robjs r3__eq__zClusterNode.__eq__s!#{+EDII0EEr5cR|j|jjyyr>)r%rrs r3r"zClusterNode.__del__s%  ,  ! ! ' ' ) -r5r)rrrrrrr"r1r5r3rrs1 F*r5rc>eZdZdZd deddfdZdededefdZd d Zy) LoadBalancerz$ Round-Robin Load Balancing start_indexr.Nc i|_||_yr>)primary_to_idxr)rrs r3rzLoadBalancer.__init__s &r5r\ list_sizec||jj||j}|dz|z|j|<|S)NrY)r setdefaultr)rr\r server_indexs r3get_server_indexzLoadBalancer.get_server_indexs@**55gt?O?OP (4q(8I'EG$r5c8|jjyr>)rclearrs r3resetzLoadBalancer.resets !!#r5)r)r.N) rrr__doc__rrr_rrr1r5r3rrs9'C'' $r5rcneZdZ ddZddZdZdZddZdZdZ d Z d Z d Z d Z d ZdZdZy)r Nc i|_i|_i|_d|_|j |||_||_||_d|_||_ t|_ |tj}||_|jyr>)r?rErrKpopulate_startup_nodesr_require_full_coverage_dynamic_startup_nodes_moved_exceptionrrread_load_balancerrr&rr)rrrrrYrrs r3rzNodesManager.__init__s  ##M2  &;#&;# $!'".. <>>#D  r5c|rB|r@|dk(rtj|}|jjt ||S|r|jj|St j dy)z Get the requested node from the cluster's nodes. nodes. :return: ClusterNode if the node exists, else None rr2zEget_node requires one of the following: 1. node name 2. host and portN)rrr?r^r4rerrorr4s r3r3zNodesManager.get_nodeso D{"++D1##'' 4d(KL L ##'' 2 2 II#  r5c||_yr>)r)rrs r3rz#NodesManager.update_moved_exception s )r5c|j}|j|j|j}||jt urJt |_n>t |j|jt }||j|j<||j|jvr|j|jd}t|_|j|jj||j|jj|||j|jd<|j|k(r)||_d|_y|g|j|j<d|_y)z@ Update the slot's node with the redirected one r2Nr)rr3r,r-rr7rr?rOrEslot_idr;rkremoverK)rrredirected_node old_primarys r3_update_moved_slotsz NodesManager._update_moved_slots sE  ! !--QVV!&&-A  &**'9.5+*!&&!&&'BO5DD  _11 2 d..qyy9 9**1995a8K'.K #   QYY ' . .{ ;   QYY ' . . ?-(>(>))6:6L6L)% r5c |jr=|jd|i|jd|ittdi|}|Std||d|}|S)Nr,r-)r8r2r1)rrr r)rr,r-rrGs r3rzNodesManager.create_redis_node|s] == MM64. ) MM64. )n&>v&>?A54d5f5Ar5ct||}|j|}|6|jj|}| |j t |||}|Sr>)r4r^r?r%r)rr,r-roletmp_nodes_cacherDrTs r3_get_or_create_cluster_nodez(NodesManager._get_or_create_cluster_nodesa!$- %)))4  **..y9K"k&B&B&J)$d; r5c  tjd|ji}i}g}d}d}|j}|jj D]} |j r |j }notj|} | jddd|j|j|jfi| }||j|j_t|jj!ddur t#dt%|j'd} d}t5| dk(rDt5| ddddk(r-t5|jdk(r|j| ddd<| D]}|d}t%|d}|dk(r |j}t7|d}|j9||t:|}|||j<t=t7|dt7|ddzD]}||vrg||<||j?|t=dt5|Dcgc]}|| }}|D]P}t%|d}|d}|j9||t@|}||j?||||j<R||d}|j|jk7s|j?|jd|jd|t5|dkDst#ddjC||jE|}|sn|s t#d|jGtI|j |s+|jJrt#dt5|dtLd||_'||_(|jSt:d|_*|jVr||_d |_,y #t(t*f$r<} | j,} tj/d |jd | Yd } ~ d } ~ wt0$rI} tj/d | j-} d | vsd| vrYd } ~ t#d|d| d } ~ wt2$r0} | j-} t#d|jd| d } ~ wwxYwcc}w) z Initializes the nodes cache, slots cache and redis connections. :startup_nodes: Responsible for discovering other nodes in the cluster z/Initializing the nodes' topology of the clusterFTr)rwrxcluster_enabledz(Cluster mode is not enabled on this noderzQAn exception occurred while trying to initialize the cluster using the seed node z: Nz6ReseponseError sending "cluster slots" to redis serverrrz7ERROR sending "cluster slots" command to redis server: z . error: z6ERROR sending "cluster slots" command to redis server rYrrhrVr[z vs z on slot: rz6startup_nodes could not agree on a valid slots cache: z, zORedis Cluster cannot be connected. Please provide at least one reachable node. z9All slots are not covered after query all startup_nodes. z of z covered...)-rrrrrrJr%rdeepcopyrrr,r-rOboolrPr^rr+rrr#rrr! Exceptionrjrrr7rirkr;joinrr0rrrr?rEr6rKrr)rr tmp_slots disagreementsstartup_nodes_reachable fully_coveredr startup_noderG copy_kwargs cluster_slotsrmsgrra primary_noder,r-rTroj replica_nodes replica_nodetarget_replica_nodetmp_slots r3rzNodesManager.initializes CD   "' '' ..557p L- 00$55A#'--"7K&&Dg'VW...$))<+<+<@KANOD&&|'8'89J %678EA/B!-Q->->-O P *.'JM"a' a(+A./14**+q0)5):): a #A&%, "#Aw #LO42:',,D<?+">>$ 5@ 0 01s47|Sa\A-=>"A )') ! !! ++K8:?3t9:M(NQa(N (N,9 4L#/ Q#@D#/?D262R2R $dG_3/&aL//0CD!4, 3 8 8 4$-Q<?#==K,<,<<)00#+==/k6F6F5GzRSQT U #=1A5&;'448IIm4L3M%O'"!"9", "\!55i@Map d'''  %%d?+A+A+C&DE!"$'?&@A +$ 99'B1E  & &!0D  $u$\2 ii $))*#cU4   VW))+ G+|w/F/##/. 'D ))++*//0 'D L)Os=CO' R< 'R961P-- R99-Q=,Q== R9 +R44R9cd|_|jjD])}|js|jj +yr>)rKr?rJr%rr(s r3rzNodesManager.close3sC $$++- .D$$%%++- .r5cX |jjy#t$rYywxYwr>)rrrrs r3rzNodesManager.reset9s+   # # ) ) +   s  )))FFNTr)FN)rrrrr3rrrlr6rrr0rrrrrr1r5r3r r sZ# " 2**%%N!0F  + \%|. r5r cDeZdZdZdfd ZddZdZdZdZdZ xZ S) rVz Wrapper for PubSub class. IMPORTANT: before using ClusterPubSub, read about the known limitations with pubsub in Cluster mode and learn how to workaround them: https://redis-py-cluster.readthedocs.io/en/stable/pubsub.html c d|_|j|||||jdn$|j|jj}||_t |di|||jdy)a* When a pubsub instance is created without specifying a node, a single node will be transparently chosen for the pubsub connection on the first command execution. The node will be determined by: 1. Hashing the channel name in the request to find its keyslot 2. Selecting a node that handles the keyslot: If read_from_replicas is set to true, a replica can be selected. :type redis_cluster: RedisCluster :type node: ClusterNode :type host: str :type port: int N)r8rr1)rpset_pubsub_noder1r8clustersuperrr)r redis_clusterrpr,r-rr8rs r3rzClusterPubSub.__init__Js|  ]D$=yy  33DII>NN  %    &5}?T?T r5c|2|j|||j|j|}||_y|3|1|j||}|j|||||}||_yt ||gdur t dd}||_y)aN The pubsub node will be set according to the passed node, host and port When none of the node, host, or port are specified - the node is set to None and will be determined by the keyslot of the channel in the first command to be executed. RedisClusterException will be thrown if the passed node does not exist in the cluster. If host is passed without port, or vice versa, a DataError will be thrown. :type cluster: RedisCluster :type node: ClusterNode :type host: str :type port: int Nr2Tz6Passing a host requires passing a port, and vice versa)_raise_on_invalid_noder,r-r3anyrrp)rrrpr,r- pubsub_nodes r3rzClusterPubSub.set_pubsub_nodeds    ' 'tyy$)) LK   $"2##D#9D  ' 'tT BK  $ $ &WX XK r5c|jS)zJ Get the node that is being used as the pubsub connection )rprs r3get_pubsub_nodezClusterPubSub.get_pubsub_nodesyyr5cd||j|jtd|d|dy)zl Raise a RedisClusterException if the node is None or doesn't exist in the cluster. NrNzNode r0z doesn't exist in the cluster)r3rOr)rr rpr,r-s r3r z$ClusterPubSub._raise_on_invalid_nodesF <=11DII1FN'vQtf$AB Or5c|j|jt|dkDr[|d}|jj |}|jj j ||jj}n|jj}||_ |jj|}|j|_|jjd|j|_|jj|j|j}|j||j g|y)z Execute a publish/subscribe command. Taken code from redis-py and tweak to make it work within a cluster. NrYrW)r7r8rjrrDr rlrr@rpr1r9r\register_connect_callbackr_executer-)rr;rrNrarpr%r7s r3rzClusterPubSub.execute_commands ?? "##+t9q=#1gG<<//8D<<55HHdll==D  <<779D  #'<<#D#DT#J '7'G'G$"22AA$//DO OO 5 5doo F__  j*"9"9ADAr5cH|j|jjSy)zH Get the Redis connection of the pubsub connected node. N)rpr%rs r3r1z"ClusterPubSub.get_redis_connections# 99 99-- - !r5r) rrrrrrrr rr1 __classcell__rs@r3rVrVAs) 4 B !BF.r5rVceZdZdZeeeeefZ ddZ dZ dZ dZ dZdZdZd Zd Zd Zd Zd ZddZdZ ddZ dfd ZdZdZdZdZdZdZdZ dZ!dZ"dZ#dZ$xZ%S) rZz8 Support for Redis pipeline in cluster mode c  g|_||_||_d|_|xs$|jj j |_|r|ng|_||_ |jjj |_ ||_ ||_ d|_||_t!| j#dd| j#dd| j#dd|_| t'j(} | |_y) riFrrxrryrrwN) command_stackr rrefresh_table_asaprrrrrrrrr rr r rr^rrr&r) rr rrr rrrr rYrs r3rzClusterPipeline.__init__s *."'  F ? ? D D F /<]"4!^^99>>@*D',H)$%!"4 JJz7 + JJ(( 3 JJ)5 1  <>>#D r5c.t|jSri)rrrs r3rzClusterPipeline.__repr__st*%%&'r5c|Srr1rs r3rzClusterPipeline.__enter__s r5c$|jyriN)rrs r3rzClusterPipeline.__exit__s  r5cD |jy#t$rYywxYwr>)rrrs r3r"zClusterPipeline.__del__s"  JJL   s  c,t|jSr)rjrrs r3__len__zClusterPipeline.__len__s4%%&&r5cy)z@Pipeline instances should always evaluate to True on Python 2.7Tr1rs r3 __nonzero__zClusterPipeline.__nonzero__ r5cy)z?Pipeline instances should always evaluate to True on Python 3+Tr1rs r3__bool__zClusterPipeline.__bool__ r&r5c&|j|i|S)z? Wrapper function for pipeline_execute_command )pipeline_execute_commandrr;rs r3rzClusterPipeline.execute_commands-t,,d=f==r5c x|jjt||t|j|S)zN Appends the executed command to the pipeline's command stack )rrkPipelineCommandrjrr;r<s r3r*z(ClusterPipeline.pipeline_execute_commands6 !! D'3t/A/A+B C  r5c|D]J}|j}t|ts |j||jdz|j |y)z8 Raise the first exception on the stack rYN)resultrrannotate_exceptionpositionr;)rstackcrGs r3raise_first_errorz!ClusterPipeline.raise_first_error sI AA!Y'''1::>166B  r5cdjtt|}d|d|d|jd}|f|jddz|_y)zS Provides extra context to the exception prior to it being handled riz Command # z (z) of pipeline caused error: rrYN)rmapr*r;)rrnumberr@cmdrs r3r1z"ClusterPipeline.annotate_exception*s_hhs8W-.3%(&^^A./ 1 ).."44 r5c|j} |j|||jS#|jwxYw)zB Execute all the commands in the current pipeline )rsend_cluster_commandsr)rraise_on_errorr3s r3executezClusterPipeline.execute5s7"" --e^D JJLDJJLs 0AcLg|_t|_d|_d|_y)z/ Reset back to empty pipeline. FN)rsetscriptswatchingexplicit_transactionrs r3rzClusterPipeline.reset?s& u  $)!r5c|sgStd|jD]} |j|||cStd#t$rY1wxYw)a Wrapper for CLUSTERDOWN error handling. If the cluster reports it is down it is assumed that: - connection_pool was disconnected - connection_pool was reseted - refereh_table_asap set to True It will try the number of times specified by the config option "self.cluster_error_retry_attempts" which defaults to 3 unless manually configured. If it reaches the number of times, the command will raises ClusterDownException. r)r<allow_redirectionsz0CLUSTERDOWN error. Unable to rebuild the cluster)rir_send_cluster_commandsr)rr3r<rDrs r3r;z%ClusterPipeline.send_cluster_commands_sr$Iq$;;< A 22#1'93 QRR$  sA A  A c t|d}i}|D]}|jjdd}|r#|j|s|j |}n7|j |j d|i}|std|j dt|dkDrtd |j |d } | j} | |vrK|j| } t| |j } t| j| j| || <|| j||j!} | D]}|j#| D]}|j%|j!D]'}|jj'|j()td |Dd }|r|rt*j-d |dj dt/|dj0j2dt5|dj0|xj6dz c_|j9r|j:j=|D]+} t?||j i|j|_-g}t|dD]t}|j d |jDvr<|jD|j d |j0fi|j|_|j|j0v|r|jG||S#tB$r}||_Yd}~d}~wwxYw)z Send a bunch of cluster commands to the redis cluster. `allow_redirections` If the pipeline should follow `ASK` & `MOVED` responses automatically. If set to false it will raise RedisClusterException. c|jSr>r2rms r3rz8ClusterPipeline._send_cluster_commands..s ajjr5)rFrN node_flagrrrYzToo many targets for command rc3hK|]*}t|jtjr|,ywr>)rr0rZr).0r4s r3 z9ClusterPipeline._send_cluster_commands..s, ahh(J(JK s02c|jSr>rHrIs r3rz8ClusterPipeline._send_cluster_commands..s !**r5z7An exception occurred during pipeline execution. args: z , error: ric|jSr>rHrIs r3rz8ClusterPipeline._send_cluster_commands..s QZZr5)$sortedr<rrrrnr;rrjrOr1r9 NodeCommandsrr8rkrJwritereadrr7rrrr0rr_r rpr rr rr r r5)rr3r<rDattemptrgr4rrrprDr:r7 node_commandsrrrErs r3rEz&ClusterPipeline._send_cluster_commandss:$89  'AYY]]>4@Nd&9&9.&I#77G 4t44affWW #/;AFF8;O< 1$+.KAFF8,TUU?D I%!66t< +J? #/--z/I/I:$i  )  # #A &9 'L   A GGI  A FFH * 4A   % %all 3 4    %   )& MM ))*+wr{112;;  5*B>B#SL>BWr5HI XPIK 5r5rZrO.cfd}|S)zi Prints error because some pipelined commands should be blocked when running in cluster-mode c"tdd)Nz"ERROR: Calling pipelined function z1 is blocked when running redis in cluster mode...rX)r;rrOs r3innerz%block_pipeline_command..inner]s!#074 5  r5r1)rOrus` r3block_pipeline_commandrvWs  Lr5)E BGREWRITEAOFrBITOP BRPOPLPUSHrrrrCLIENTrrrrCONFIGrECHOryrrrrrMGETzMGET NONATOMICMOVEMSETzMSET NONATOMICMSETNXPFCOUNTPFMERGErPUBLISHrrrRENAMERENAMENX RPOPLPUSHrrrrz SCRIPT KILLrSCRIPTSDIFF SDIFFSTOREz SENTINEL GET MASTER ADDR BY NAMEzSENTINEL MASTERzSENTINEL MASTERSzSENTINEL MONITORzSENTINEL REMOVEzSENTINEL SENTINELSz SENTINEL SETzSENTINEL SLAVESSENTINELrSINTER SINTERSTORESLAVEOFrrrSLOWLOGSMOVESORTSUNION SUNIONSTORErrirceZdZdZddZy)r-riNc`||_|i}||_||_d|_d|_d|_y)NF)r;r<r2r0rpr)rr;r<r2s r3rzPipelineCommand.__init__s6 ?G      r5r)rrrrrr1r5r3r-r-s  r5r-c(eZdZdZdZdZdZdZy)rRric<||_||_||_g|_yr )rr8r7commands)rrr8r7s r3rzNodeCommands.__init__s ,.$ r5c:|jj|yr )rrk)rr4s r3rkzNodeCommands.appends Qr5c |j}|j}|D] }d|_  |j|j |Dcgc]}|j c}ycc}w#t tf$r}|D] }||_ Yd}~yd}~wwxYw)z= Code borrowed from Redis so it can be fixed N)r7rr0send_packed_command pack_commandsr;rr#)rr7rr4rs r3rSzNodeCommands.writes__ == AAH     * *((()CQ!&&)CD )C.    s)A&A!  A&!A&&B 5BB cp|j}|jD]@}|j |j||jdfi|j |_By#t tf$r#}|jD] }||_ Yd}~yd}~wt$rtjd|_YwxYw)riNrrY) r7rr0rr;r<rr#r sysexc_info)rr7r4rs r3rTzNodeCommands.reads__  1A(xx12t22:qvvayVAIIVAH- 1.(6!]]%#$%!1"||~a0AH1s0AB5,B  (B54B5N)rrrrrrkrSrTr1r5r3rRrRs  ,1r5rR)Yrloggingrrrrr collectionsrtypingrrrr redis.clientrr r r redis.commandsr r rredis.connectionrrrr redis.crcrrredis.exceptionsrrrrrrrrrrrr r!r"r#r$ redis.lockr& redis.utilsr'r(r)r*r+ getLoggerrrr_rr4r9rHrRrerrr7r;rrrrrrrrrr rVrZrvPIPELINE_BLOCKED_COMMANDSr@replacelowerrr-rRr1r5r3rs   #--GGNNNN8$g!33    %S/4S> )*((   @(  M EKEKPh &')=h &V**8$$&ccL |.F|.~T5lT5n  #s();  FN)GGooc3'--/G OW&