
    f9                         d Z ddlZddlZddlZddlmZmZ ddlmZ ddl	m
Z
 ddlmZ ddlmZ dZd	 Z G d
 d      Z G d de      Z G d de      Zy)u  :class:`.RateLimiter` and :class:`.AsyncRateLimiter` allow to perform bulk
operations while gracefully handling error responses and adding delays
when needed.

In the example below a delay of 1 second (``min_delay_seconds=1``)
will be added between each pair of ``geolocator.geocode`` calls; all
:class:`geopy.exc.GeocoderServiceError` exceptions will be retried
(up to ``max_retries`` times)::

    import pandas as pd
    df = pd.DataFrame({'name': ['paris', 'berlin', 'london']})

    from geopy.geocoders import Nominatim
    geolocator = Nominatim(user_agent="specify_your_app_name_here")

    from geopy.extra.rate_limiter import RateLimiter
    geocode = RateLimiter(geolocator.geocode, min_delay_seconds=1)
    df['location'] = df['name'].apply(geocode)

    df['point'] = df['location'].apply(lambda loc: tuple(loc.point) if loc else None)

This would produce the following DataFrame::

    >>> df
         name                                           location  \
    0   paris  (Paris, Île-de-France, France métropolitaine, ...
    1  berlin  (Berlin, 10117, Deutschland, (52.5170365, 13.3...
    2  london  (London, Greater London, England, SW1A 2DU, UK...

                               point
    0   (48.8566101, 2.3514992, 0.0)
    1  (52.5170365, 13.3888599, 0.0)
    2  (51.5073219, -0.1276474, 0.0)

To pass extra options to the `geocode` call::

    from functools import partial
    df['location'] = df['name'].apply(partial(geocode, language='de'))

To see a progress bar::

    from tqdm import tqdm
    tqdm.pandas()
    df['location'] = df['name'].progress_apply(geocode)

Before using rate limiting classes, please consult with the Geocoding
service ToS, which might explicitly consider bulk requests (even throttled)
a violation.
    N)chaincount)sleepdefault_timer)GeocoderServiceError)logger)AsyncRateLimiterRateLimiterc                 <    t        d t        |       D        dg      S )z-list(_is_last_gen(2)) -> [False, False, True]c              3       K   | ]  }d   yw)FN ).0_s     ?D:\switchATM\venv\Lib\site-packages\geopy/extra/rate_limiter.py	<genexpr>z_is_last_gen.<locals>.<genexpr>B   s     .A%.s   T)r   range)r   s    r   _is_last_genr   @   s    .u.77    c                   4    e Zd ZdZefZd Zd Zd Zd Z	d Z
y)BaseRateLimiterz9Base Rate Limiter class for both sync and async versions.c                    || _         || _        || _        || _        |dk\  sJ t	        j
                         | _        d | _        y Nr   )min_delay_secondsmax_retriesswallow_exceptionsreturn_value_on_exception	threadingLock_lock
_last_call)selfr   r   r   r   s        r   __init__zBaseRateLimiter.__init__J   sF     "3&"4)B&a ^^%
r   c                     t               S Nr   )r"   s    r   _clockzBaseRateLimiter._clock\   s
    r   c              #     K   	 | j                   5  | j                         }| j                  || _        	 d d d        y || j                  z
  }| j                  |z
  }|dk  r|| _        	 d d d        y 	 d d d         |# 1 sw Y   xY wwr   )r    r&   r!   r   )r"   clockseconds_since_last_callwaits       r   _acquire_request_slot_genz)BaseRateLimiter._acquire_request_slot_gen_   s     *  ??*&+DO  +0$//*A'--0GG19&+DO   J  s,   B%B 	B+B )	B3B B	Bc           
   #   0  K   t        t               t        | j                              D ]  \  }}	 |  y  y # | j                  $ rK |rd nAt        j                  t        |       j                  dz   || j                  ||d       d Y fY hw xY ww)NTzB caught an error, retrying (%s/%s tries). Called with (*%r, **%r).exc_infoF)	zipr   r   r   _retry_exceptionsr	   warningtype__name__)r"   argskwargsiis_last_trys        r   _retries_genzBaseRateLimiter._retries_gen   s     !%'<8H8H+IJ 	NA{$ )	 )) JNNT
++ /B B((!%  K s&   .B9BABBBBc                     | j                   rFt        j                  t        |       j                  dz   | j
                  ||d       | j                  S  )Nz> swallowed an error after %r retries. Called with (*%r, **%r).Tr-   )r   r	   r1   r2   r3   r   r   )r"   r4   r5   s      r   _handle_exczBaseRateLimiter._handle_exc   sQ    ""NNT
## '+ +   111r   N)r3   
__module____qualname____doc__r   r0   r#   r&   r+   r8   r:   r   r   r   r   r   E   s(    C-/$$L.r   r   c                   B     e Zd ZdZdddddd fd
Zd	 Zd
 Zd Z xZS )r   a  This is a Rate Limiter implementation for synchronous functions
    (like geocoders with the default :class:`geopy.adapters.BaseSyncAdapter`).

    Examples::

        from geopy.extra.rate_limiter import RateLimiter
        from geopy.geocoders import Nominatim

        geolocator = Nominatim(user_agent="specify_your_app_name_here")

        search = ["moscow", "paris", "berlin", "tokyo", "beijing"]
        geocode = RateLimiter(geolocator.geocode, min_delay_seconds=1)
        locations = [geocode(s) for s in search]

        search = [
            (55.47, 37.32), (48.85, 2.35), (52.51, 13.38),
            (34.69, 139.40), (39.90, 116.39)
        ]
        reverse = RateLimiter(geolocator.reverse, min_delay_seconds=1)
        locations = [reverse(s) for s in search]

    RateLimiter class is thread-safe. If geocoding service's responses
    are slower than `min_delay_seconds`, then you can benefit from
    parallelizing the work::

        import concurrent.futures

        geolocator = OpenMapQuest(api_key="...")
        geocode = RateLimiter(geolocator.geocode, min_delay_seconds=1/20)

        with concurrent.futures.ThreadPoolExecutor() as e:
            locations = list(e.map(geocode, search))

    .. versionchanged:: 2.0
       Added thread-safety support.
                     @TNr   r   error_wait_secondsr   r   c                d    t         |   ||||       || _        || _        ||k\  sJ |dk\  sJ ya  
        :param callable func:
            A function which should be wrapped by the rate limiter.

        :param float min_delay_seconds:
            Minimum delay in seconds between the wrapped ``func`` calls.
            To convert :abbr:`RPS (Requests Per Second)` rate to
            ``min_delay_seconds`` you need to divide 1 by RPS. For example,
            if you need to keep the rate at 20 RPS, you can use
            ``min_delay_seconds=1/20``.

        :param int max_retries:
            Number of retries on exceptions. Only
            :class:`geopy.exc.GeocoderServiceError` exceptions are
            retried -- others are always re-raised. ``max_retries + 1``
            requests would be performed at max per query. Set
            ``max_retries=0`` to disable retries.

        :param float error_wait_seconds:
            Time to wait between retries after errors. Must be
            greater or equal to ``min_delay_seconds``.

        :param bool swallow_exceptions:
            Should an exception be swallowed after retries? If not,
            it will be re-raised. If yes, the ``return_value_on_exception``
            will be returned.

        :param return_value_on_exception:
            Value to return on failure when ``swallow_exceptions=True``.

        )r   r   r   r   r   Nsuperr#   funcrC   r"   rH   r   r   rC   r   r   	__class__s          r   r#   zRateLimiter.__init__   O    R 	/#1&?	 	 	
 	"4!%666ar   c                 r    t        j                  t        |       j                  dz   |       t	        |       y Nz
 sleep(%r))r	   debugr2   r3   r   r"   secondss     r   _sleepzRateLimiter._sleep  s&    T$Z((<7Agr   c                 P    | j                         D ]  }| j                  |        y r%   r+   rQ   r"   r*   s     r   _acquire_request_slotz!RateLimiter._acquire_request_slot	  s&    224 	DKK	r   c                    | j                  ||      }|D ]H  }| j                          	  | j                  |i |}t        j                  |      rt        d      |c S  t        d      # | j                  $ rO}|j                  |      r| j                  ||      cY d }~c S | j                  | j                         Y d }~d }~ww xY w)NzoAn async awaitable has been passed to `RateLimiter`. Use `AsyncRateLimiter` instead, which supports awaitables.Should not have been reached)r8   rU   rH   inspectisawaitable
ValueErrorr0   throwr:   rQ   rC   RuntimeError)r"   r4   r5   genr   reses          r   __call__zRateLimiter.__call__  s    f- 	5A&&(5dii00&&s+$U  
	5  9:: )) 599Q<++D&99D3344	5s#   3A++C	:"CC	$CC		r3   r;   r<   r=   r#   rQ   rU   r`   __classcell__rJ   s   @r   r   r      s/    #R "&2 h;r   r   c                   B     e Zd ZdZdddddd fd
Zd	 Zd
 Zd Z xZS )r
   a  This is a Rate Limiter implementation for asynchronous functions
    (like geocoders with :class:`geopy.adapters.BaseAsyncAdapter`).

    Examples::

        from geopy.adapters import AioHTTPAdapter
        from geopy.extra.rate_limiter import AsyncRateLimiter
        from geopy.geocoders import Nominatim

        async with Nominatim(
            user_agent="specify_your_app_name_here",
            adapter_factory=AioHTTPAdapter,
        ) as geolocator:

            search = ["moscow", "paris", "berlin", "tokyo", "beijing"]
            geocode = AsyncRateLimiter(geolocator.geocode, min_delay_seconds=1)
            locations = [await geocode(s) for s in search]

            search = [
                (55.47, 37.32), (48.85, 2.35), (52.51, 13.38),
                (34.69, 139.40), (39.90, 116.39)
            ]
            reverse = AsyncRateLimiter(geolocator.reverse, min_delay_seconds=1)
            locations = [await reverse(s) for s in search]

    AsyncRateLimiter class is safe to use across multiple concurrent tasks.
    If geocoding service's responses are slower than `min_delay_seconds`,
    then you can benefit from parallelizing the work::

        import asyncio

        async with OpenMapQuest(
            api_key="...", adapter_factory=AioHTTPAdapter
        ) as geolocator:

            geocode = AsyncRateLimiter(geolocator.geocode, min_delay_seconds=1/20)
            locations = await asyncio.gather(*(geocode(s) for s in search))

    .. versionadded:: 2.0
    r?   r@   rA   TNrB   c                d    t         |   ||||       || _        || _        ||k\  sJ |dk\  sJ yrE   rF   rI   s          r   r#   zAsyncRateLimiter.__init__L  rK   r   c                    K   t        j                  t        |       j                  dz   |       t	        j
                  |       d {    y 7 wrM   )r	   rN   r2   r3   asyncior   rO   s     r   rQ   zAsyncRateLimiter._sleep  s5     T$Z((<7AmmG$$$s   AAAAc                 l   K   | j                         D ]  }| j                  |       d {     y 7 wr%   rS   rT   s     r   rU   z&AsyncRateLimiter._acquire_request_slot  s3     224 	$D++d###	$#s   (424c                   K   | j                  ||      }|D ]6  }| j                          d {    	  | j                  |i | d {   c S  t        d      7 -7 # | j                  $ rX}|j	                  |      r| j                  ||      cY d }~c S | j                  | j                         d {  7   Y d }~d }~ww xY ww)NrW   )	r8   rU   rH   r0   r[   r:   rQ   rC   r\   )r"   r4   r5   r]   r   r_   s         r   r`   zAsyncRateLimiter.__call__  s     f- 	;A,,...;&TYY7777	; 9:: /7)) ;99Q<++D&99kk$"9"9:::	;sh   +C	AC	AA	AC	AC."CCC	C6B97C<C	CC	ra   rc   s   @r   r
   r
   "  s/    'Z "&2 h%$;r   r
   )r=   rg   rX   r   	itertoolsr   r   timer   timeitr   	geopy.excr   
geopy.utilr	   __all__r   r   r   r
   r   r   r   <module>rp      sY   0d    "    * 
-8
c cLt;/ t;nr; r;r   