
    Oi9                         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dS )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                 R    t          d t          |           D             dg          S )z-list(_is_last_gen(2)) -> [False, False, True]c              3      K   | ]}d V  dS )FN ).0_s     P/srv/django_bis/venv311/lib/python3.11/site-packages/geopy/extra/rate_limiter.py	<genexpr>z_is_last_gen.<locals>.<genexpr>B   s"      ..A%......    T)r   range)r   s    r   _is_last_genr   @   s)    ..u...777r   c                   6    e Zd ZdZefZd Zd Zd Zd Z	d Z
dS )BaseRateLimiterz9Base Rate Limiter class for both sync and async versions.c                    || _         || _        || _        || _        |dk    sJ t	          j                    | _        d | _        d S )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   sQ     "3&"4)B&a ^%%
r   c                     t                      S Nr   )r!   s    r   _clockzBaseRateLimiter._clock\   s    r   c              #     K   	 | j         5  |                                 }| j        || _        	 d d d            d S || j        z
  }| j        |z
  }|dk    r|| _        	 d d d            d S 	 d d d            n# 1 swxY w Y   |V  )NTr   )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199&+DO                       JJJ	s   $A8"A88A<?A<c           
   #   "  K   t          t                      t          | j                            D ]]\  }}	 |V   d S # | j        $ rD |rdV  n:t          j        t          |           j        dz   || j        ||d           dV  Y XY Zw xY wd S )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      !%''<8H+I+IJJ 	 	NA{$ # )    JJJJNT

+ /B B(!%     KKKH J	 	s   >A	BBc                     | j         r:t          j        t          |           j        dz   | j        ||d           | j        S  )Nz> swallowed an error after %r retries. Called with (*%r, **%r).Tr,   )r   r	   r0   r1   r2   r   r   )r!   r3   r4   s      r   _handle_exczBaseRateLimiter._handle_exc   sY    " 	NT

# '+ +     11r   N)r2   
__module____qualname____doc__r   r/   r"   r%   r*   r7   r9   r   r   r   r   r   E   so        C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                    t                                          ||||           || _        || _        ||k    sJ |dk    sJ dS 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"   funcrB   r!   rG   r   r   rB   r   r   	__class__s          r   r"   zRateLimiter.__init__   m    R 	/#1&?	 	 	
 	
 	
 	"4!%66666ar   c                 x    t          j        t          |           j        dz   |           t	          |           d S Nz
 sleep(%r))r	   debugr1   r2   r   r!   secondss     r   _sleepzRateLimiter._sleep  s1    T$ZZ(<7AAAgr   c                 ^    |                                  D ]}|                     |           d S r$   r*   rP   r!   r)   s     r   _acquire_request_slotz!RateLimiter._acquire_request_slot	  s<    2244 	 	DKK	 	r   c                    |                      ||          }|D ]}|                                  	  | j        |i |}t          j        |          rt          d          |c S # | j        $ rV}|                    |          r|                     ||          cY d }~c S | 	                    | j
                   Y d }~d }~ww xY wt          d          )NzoAn async awaitable has been passed to `RateLimiter`. Use `AsyncRateLimiter` instead, which supports awaitables.Should not have been reached)r7   rT   rG   inspectisawaitable
ValueErrorr/   throwr9   rP   rB   RuntimeError)r!   r3   r4   genr   reses          r   __call__zRateLimiter.__call__  s   f-- 	5 	5A&&(((5di000&s++ $U   


) 5 5 599Q<< :++D&9999999999D344444444	5 9:::s#   1A$$
C.*B?C B??C	r2   r:   r;   r<   r"   rP   rT   r_   __classcell__rI   s   @r   r   r      s        # #R "&2  2  2  2  2  2  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?   r@   TNrA   c                    t                                          ||||           || _        || _        ||k    sJ |dk    sJ dS rD   rE   rH   s          r   r"   zAsyncRateLimiter.__init__L  rJ   r   c                    K   t          j        t          |           j        dz   |           t	          j        |           d {V  d S rL   )r	   rM   r1   r2   asyncior   rN   s     r   rP   zAsyncRateLimiter._sleep  sN      T$ZZ(<7AAAmG$$$$$$$$$$$r   c                 n   K   |                                  D ]}|                     |           d {V  d S r$   rR   rS   s     r   rT   z&AsyncRateLimiter._acquire_request_slot  sR      2244 	$ 	$D++d##########	$ 	$r   c                   K   |                      ||          }|D ]}|                                  d {V  	  | j        |i | d {V c S # | j        $ r\}|                    |          r|                     ||          cY d }~c S |                     | j                   d {V  Y d }~d }~ww xY wt          d          )NrV   )	r7   rT   rG   r/   rZ   r9   rP   rB   r[   )r!   r3   r4   r\   r   r^   s         r   r_   zAsyncRateLimiter.__call__  s(     f-- 	; 	;A,,.........;&TY777777777777) ; ; ;99Q<< :++D&9999999999kk$"9::::::::::::::	; 9:::s#   A
B3*B.B3	 B..B3r`   rb   s   @r   r
   r
   "  s        ' 'Z "&2  2  2  2  2  2  2 h% % %$ $ $; ; ; ; ; ; ;r   r
   )r<   rf   rW   r   	itertoolsr   r   timer   timeitr   	geopy.excr   
geopy.utilr	   __all__r   r   r   r
   r   r   r   <module>ro      sF  0 0d       " " " " " " " "                   * * * * * *      
-8 8 8
c c c c c c c cLt; t; t; t; t;/ t; t; t;nr; r; r; r; r; r; r; r; r; r;r   