U
    ]g#=                     @   s   d dl Z d dlZd dlmZmZ d dlmZ d dlmZ d dl	m
Z
mZ d dlmZ d dlmZmZmZmZmZmZ d dlmZ erd dlZd dlZG d	d
 d
ZeddG dd deee
ZeddG dd deZeddG dd deZdS )    N)TYPE_CHECKINGcast)versionadded)Version)MovingWindowSupportStorage)ConfigurationError)AsyncRedisClientDictOptionalTupleTypeUnion)get_package_datac                   @   s  e Zd ZU dZee dZee dZee dZee dZde	d< de	d< de	d	< de	d
< dZ
eedddZd%eeeeeedddZeeedddZeeddddZeeeeeef dddZd&eeeeeedddZeeedd d!Zeed"d#d$ZdS )'RedisInteractorzresources/redis/lua_scriptsz/moving_window.luaz/acquire_moving_window.luaz/clear_keys.luaz/incr_expire.luazcoredis.commands.Script[bytes]lua_moving_windowlua_acquire_windowlua_clear_keyslua_incr_expireZLIMITSkeyreturnc                 C   s   | j  d| S )N:)PREFIXselfr    r   </tmp/pip-unpacked-wheel-kizsipjx/limits/aio/storage/redis.pyprefixed_key#   s    zRedisInteractor.prefixed_keyF   )r   expiry
connectionelastic_expiryamountr   c                    s>   |  |}|||I dH }|s(||kr:|||I dH  |S )a  
        increments the counter for a given rate limit key

        :param connection: Redis connection
        :param key: the key to increment
        :param expiry: amount in seconds for the key to expire in
        :param amount: the number to increment by
        N)r   ZincrbyZexpire)r   r   r    r!   r"   r#   valuer   r   r   _incr&   s
    
zRedisInteractor._incr)r   r!   r   c                    s"   |  |}t||I dH pdS )zn
        :param connection: Redis connection
        :param key: the key to get the counter value for
        Nr   )r   intgetr   r   r!   r   r   r   _get>   s    
zRedisInteractor._getNc                    s    |  |}||gI dH  dS )zj
        :param key: the key to clear rate limits for
        :param connection: Redis connection
        N)r   deleter(   r   r   r   _clearG   s    
zRedisInteractor._clear)r   limitr    r   c                    sL   |  |}tt }| j|gt|| |gI dH }|rDt|S |dfS )z
        returns the starting point and the number of entries in the moving
        window

        :param key: rate limit key
        :param expiry: expiry of entry
        :return: (start of window, number of acquired entries)
        Nr   )r   r&   timer   executetuple)r   r   r,   r    	timestampZwindowr   r   r   get_moving_windowO   s    
 
z!RedisInteractor.get_moving_window)r   r,   r    r!   r#   r   c                    s8   |  |}t }| j|g||||gI dH }t|S )z
        :param key: rate limit key to acquire an entry in
        :param limit: amount of entries allowed
        :param expiry: expiry of the entry
        :param connection: Redis connection
        N)r   r-   r   r.   bool)r   r   r,   r    r!   r#   r0   Zacquiredr   r   r   _acquire_entryc   s    
 

zRedisInteractor._acquire_entryc                    s,   |  |}tt||I dH dt  S )zg
        :param key: the key to get the expiry for
        :param connection: Redis connection
        Nr   )r   r&   maxZttlr-   r(   r   r   r   _get_expiryy   s    
zRedisInteractor._get_expiry)r!   r   c                    s(   z|  I dH  W dS    Y dS X dS )zZ
        check if storage is healthy

        :param connection: Redis connection
        NTF)Zping)r   r!   r   r   r   _check   s
    zRedisInteractor._check)Fr   )r   )__name__
__module____qualname__ZRES_DIRr   SCRIPT_MOVING_WINDOWSCRIPT_ACQUIRE_MOVING_WINDOWSCRIPT_CLEAR_KEYSSCRIPT_INCR_EXPIRE__annotations__r   strr   r&   r	   r2   r%   r)   r+   r   r1   r3   r5   r6   r   r   r   r   r      sR   
  		  
 	r   z2.1)versionc                       s&  e Zd ZdZdddgZdediZd&eed	 e	e
eee	f dd
 fddZee
ee eee df f dddZeddddZd'eee	eed fddZeed fddZedd fddZd(eeeee	d fddZeed fd d!Ze	d fd"d#Zee dd$d%Z  ZS ))RedisStoragezS
    Rate limit storage with redis as backend.

    Depends on :pypi:`coredis`
    async+rediszasync+redisszasync+redis+unixcoredis3.4.0NFzcoredis.ConnectionPool)uriconnection_poolwrap_exceptionsoptionsr   c                    s   | ddd}| dd}t j|fd|i| | jd j| _|r^| jjf d|i|| _n| jjj|f|| _| 	| d	S )
a  
        :param uri: uri of the form:

         - ``async+redis://[:password]@host:port``
         - ``async+redis://[:password]@host:port/db``
         - ``async+rediss://[:password]@host:port``
         - ``async+redis+unix:///path/to/sock?db=0`` etc...

         This uri is passed directly to :meth:`coredis.Redis.from_url` with
         the initial ``async`` removed, except for the case of ``async+redis+unix``
         where it is replaced with ``unix``.
        :param connection_pool: if provided, the redis client is initialized with
         the connection pool and any other params passed as :paramref:`options`
        :param wrap_exceptions: Whether to wrap storage exceptions in
         :exc:`limits.errors.StorageError` before raising it.
        :param options: all remaining keyword arguments are passed
         directly to the constructor of :class:`coredis.Redis`
        :raise ConfigurationError: when the redis library is not available
        rB   Zredisr   z
redis+unixunixrG   rC   rF   N)
replacesuper__init__dependenciesmodule
dependencyZRedisstorageZfrom_urlinitialize_storage)r   rE   rF   rG   rH   	__class__r   r   rL      s    

zRedisStorage.__init__.r   c                 C   s
   | j jjS N)rO   
exceptionsZ
RedisErrorr   r   r   r   base_exceptions   s    zRedisStorage.base_exceptions)_urir   c                 C   sD   | j | j| _| j | j| _| j | j| _| j tj	| _
d S rU   )rP   Zregister_scriptr:   r   r;   r   r<   r   rA   r=   r   )r   rY   r   r   r   rQ      s    zRedisStorage.initialize_storager   )r   r    r"   r#   r   c                    sN   |r t  ||| j||I dH S | |}tt| j|g||gI dH S dS )z
        increments the counter for a given rate limit key

        :param key: the key to increment
        :param expiry: amount in seconds for the key to expire in
        :param amount: the number to increment by
        N)rK   r%   rP   r   r   r&   r   r.   )r   r   r    r"   r#   rR   r   r   incr   s        

 zRedisStorage.incrr   c                    s   t  || jI dH S zB
        :param key: the key to get the counter value for
        N)rK   r)   rP   r   rR   r   r   r'      s    zRedisStorage.getc                    s   t  || jI dH S )z>
        :param key: the key to clear rate limits for
        N)rK   r+   rP   r   rR   r   r   clear   s    zRedisStorage.clear)r   r,   r    r#   r   c                    s   t  |||| j|I dH S )z
        :param key: rate limit key to acquire an entry in
        :param limit: amount of entries allowed
        :param expiry: expiry of the entry
        :param amount: the number of entries to acquire
        N)rK   r3   rP   )r   r   r,   r    r#   rR   r   r   acquire_entry   s    
zRedisStorage.acquire_entryc                    s   t  || jI dH S z;
        :param key: the key to get the expiry for
        N)rK   r5   rP   r   rR   r   r   
get_expiry  s    zRedisStorage.get_expiryc                    s   t  | jI dH S )zS
        Check if storage is healthy by calling :meth:`coredis.Redis.ping`
        N)rK   r6   rP   rW   rR   r   r   check  s    zRedisStorage.checkc                    s$   |  d}tt| j|gI dH S )aL  
        This function calls a Lua Script to delete keys prefixed with
        ``self.PREFIX`` in blocks of 5000.

        .. warning:: This operation was designed to be fast, but was not tested
           on a large production based system. Be careful with its usage as it
           could be slow on very large data sets.
        *N)r   r   r&   r   r.   )r   prefixr   r   r   reset  s    

zRedisStorage.reset)NF)Fr   )r   )r7   r8   r9   __doc__STORAGE_SCHEMEr   DEPENDENCIESr?   r   r2   r   floatrL   propertyr   	Exceptionr   rX   rQ   r&   rZ   r'   r\   r]   r_   r`   rc   __classcell__r   r   rR   r   rA      sL   
  *          rA   c                       st   e Zd ZU dZdgZddiZeeee	ee
f f ed< dee
ee	ee
f dd fd	d
Zee dddZ  ZS )RedisClusterStoragezZ
    Rate limit storage with redis cluster as backend

    Depends on :pypi:`coredis`
    zasync+redis+clusterZmax_connectionsi  DEFAULT_OPTIONSFN)rE   rG   rH   r   c                    s   t j|}i }|jr |j|d< |jr0|j|d< |jdd }g }|j|d dD ]&}|d\}	}
||	t	|
d qXt
t| j|fd	|i| | jd
 j| _| jjf d|i| j||| _| | dS )av  
        :param uri: url of the form
         ``async+redis+cluster://[:password]@host:port,host:port``
        :param options: all remaining keyword arguments are passed
         directly to the constructor of :class:`coredis.RedisCluster`
        :raise ConfigurationError: when the coredis library is not
         available or if the redis host cannot be pinged.
        usernamepassword@r   N,r   )hostportrG   rC   Zstartup_nodes)urllibparseurlparserm   rn   netlocfindsplitappendr&   rK   rA   rL   rM   rN   rO   ZRedisClusterrl   rP   rQ   )r   rE   rG   rH   parsedparsed_authsepZcluster_hostslocrq   rr   rR   r   r   rL   6  s2    




zRedisClusterStorage.__init__rT   c                    sF   |  d}| j|I dH }d}|D ]}|| j|gI dH 7 }q$|S )a  
        Redis Clusters are sharded and deleting across shards
        can't be done atomically. Because of this, this reset loops over all
        keys that are prefixed with ``self.PREFIX`` and calls delete on them,
        one at a time.

        .. warning:: This operation was not tested with extremely large data sets.
           On a large production based system, care should be taken with its
           usage as it could be slow on very large data sets
        ra   Nr   )r   rP   keysr*   )r   rb   r~   countr   r   r   r   rc   _  s    
zRedisClusterStorage.reset)F)r7   r8   r9   rd   re   rl   r
   r?   r   rg   r2   r>   rL   r   r&   rc   rj   r   r   rR   r   rk   $  s   
  )rk   c                       s   e Zd ZdZdgZdediZdeee e	ee
eeeee	f f  eeee	f d fdd	Zeed
 fddZeed
 fddZe	d fddZ  ZS )RedisSentinelStoragez[
    Rate limit storage with redis sentinel as backend

    Depends on :pypi:`coredis`
    zasync+redis+sentinelcoredis.sentinelrD   NT)rE   service_nameuse_replicassentinel_kwargsrH   c                    s4  t j|}g }| }|r$| ni }	i }
|jr<|j|
d< |jrL|j|
d< |jdd }|j|d dD ]$}|d\}}|	|t
|f qp|jr|jdd	n|| _| jdkrtd
tt|   | jd j| _| jj|fd|
|	i|
|| _| j| j| _| j| j| _|| _| | dS )a  
        :param uri: url of the form
         ``async+redis+sentinel://host:port,host:port/service_name``
        :param service_name, optional: sentinel service name
         (if not provided in `uri`)
        :param use_replicas: Whether to use replicas for read only operations
        :param sentinel_kwargs, optional: kwargs to pass as
         ``sentinel_kwargs`` to :class:`coredis.sentinel.Sentinel`
        :param options: all remaining keyword arguments are passed
         directly to the constructor of :class:`coredis.sentinel.Sentinel`
        :raise ConfigurationError: when the coredis library is not available
         or if the redis primary host cannot be pinged.
        rm   rn   ro   r   Nrp   r   / z'service_name' not providedr   r   )rs   rt   ru   copyrm   rn   rv   rw   rx   ry   r&   pathrJ   r   r   rK   rA   rL   rM   rN   rO   ZSentinelsentinelZprimary_forrP   Zreplica_forstorage_replicar   rQ   )r   rE   r   r   r   rH   rz   Zsentinel_configurationZconnection_optionsZsentinel_optionsr{   r|   r}   rq   rr   rR   r   r   rL     s<    


zRedisSentinelStorage.__init__r   c                    s"   t  || jr| jn| jI dH S r[   )rK   r)   r   r   rP   r   rR   r   r   r'     s     zRedisSentinelStorage.getc                    s"   t  || jr| jn| jI dH S r^   )rK   r5   r   r   rP   r   rR   r   r   r_     s     zRedisSentinelStorage.get_expiryrT   c                    s    t  | jr| jn| jI dH S )zk
        Check if storage is healthy by calling :meth:`coredis.Redis.ping`
        on the replica.
        N)rK   r6   r   r   rP   rW   rR   r   r   r`     s    zRedisSentinelStorage.check)NTN)r7   r8   r9   rd   re   r   rf   r?   r   r2   r
   r   rg   rL   r&   r'   r_   r`   rj   r   r   rR   r   r   s  s       <		r   )r-   rs   typingr   r   Zdeprecated.sphinxr   Zpackaging.versionr   Zlimits.aio.storage.baser   r   Zlimits.errorsr   Zlimits.typingr	   r
   r   r   r   r   Zlimits.utilr   rC   Zcoredis.commandsr   rA   rk   r   r   r   r   r   <module>   s&    ~ N