o
    {g9                     @   s   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	d
 ZG dd dZG dd deZG d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                 C   s   t dd t| D dgS )z-list(_is_last_gen(2)) -> [False, False, True]c                 s   s    | ]}d V  qdS )FN ).0_r   r   u/var/www/bot.gig.net.ua/public_html/telegram/P1/HellBot/venv/lib/python3.10/site-packages/geopy/extra/rate_limiter.py	<genexpr>B   s    z_is_last_gen.<locals>.<genexpr>T)r   range)r   r   r   r   _is_last_gen@   s   r   c                   @   s>   e Zd ZdZefZdd Zdd Zdd Zdd	 Z	d
d Z
dS )BaseRateLimiterz9Base Rate Limiter class for both sync and async versions.c                C   s8   || _ || _|| _|| _|dksJ t | _d | _d S )Nr   )min_delay_secondsmax_retriesswallow_exceptionsreturn_value_on_exception	threadingLock_lock
_last_call)selfr   r   r   r   r   r   r   __init__J   s   

zBaseRateLimiter.__init__c                 C   s   t  S Nr   )r   r   r   r   _clock\   s   zBaseRateLimiter._clockc                 c   s    	 | j 7 |  }| jd u r|| _	 W d    d S || j }| j| }|dkr5|| _	 W d    d S W d    n1 s?w   Y  |V  q)NTr   )r   r   r   r   )r   clockseconds_since_last_callwaitr   r   r   _acquire_request_slot_gen_   s$   


z)BaseRateLimiter._acquire_request_slot_genc                 c   s|    t t t| jD ]1\}}z|V  W  d S  | jy;   |r#dV  ntjt| jd || j||dd dV  Y q
Y q
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_tryr   r   r   _retries_gen   s*   	zBaseRateLimiter._retries_genc                 C   s.   | j rtjt| jd | j||dd | jS  )Nz> swallowed an error after %r retries. Called with (*%r, **%r).Tr#   )r   r   r'   r(   r)   r   r   )r   r*   r+   r   r   r   _handle_exc   s   zBaseRateLimiter._handle_excN)r)   
__module____qualname____doc__r   r&   r   r   r"   r.   r/   r   r   r   r   r   E   s    &r   c                       F   e Zd ZdZdddddd fdd	
Zd
d Zdd Zdd Z  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  j||||d || _|| _||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   funcr8   r   r=   r   r   r8   r   r   	__class__r   r   r         )zRateLimiter.__init__c                 C   s"   t t| jd | t| d S Nz
 sleep(%r))r   debugr(   r)   r   r   secondsr   r   r   _sleep  s   zRateLimiter._sleepc                 C   s   |   D ]}| | qd S r   r"   rF   r   r!   r   r   r   _acquire_request_slot	  s   z!RateLimiter._acquire_request_slotc                 O   s   |  ||}|D ]H}|   z| j|i |}t|r td|W   S  | jyP } z||r@| ||W  Y d }~  S | 	| j
 W Y d }~qd }~ww td)NzoAn async awaitable has been passed to `RateLimiter`. Use `AsyncRateLimiter` instead, which supports awaitables.Should not have been reached)r.   rI   r=   inspectisawaitable
ValueErrorr&   throwr/   rF   r8   RuntimeError)r   r*   r+   genr   reser   r   r   __call__  s"   


zRateLimiter.__call__	r)   r0   r1   r2   r   rF   rI   rS   __classcell__r   r   r?   r   r
      s    )4r
   c                       r3   )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
    r4   r5   r6   TNr7   c                   r9   r:   r;   r>   r?   r   r   r   L  rA   zAsyncRateLimiter.__init__c                    s,   t t| jd | t|I d H  d S rB   )r   rC   r(   r)   asyncior   rD   r   r   r   rF     s   zAsyncRateLimiter._sleepc                    s$   |   D ]
}| |I d H  qd S r   rG   rH   r   r   r   rI     s   z&AsyncRateLimiter._acquire_request_slotc                    s   |  ||}|D ]F}|  I d H  z| j|i |I d H W   S  | jyO } z"||r<| ||W  Y d }~  S | | jI d H  W Y d }~q	d }~ww td)NrJ   )	r.   rI   r=   r&   rN   r/   rF   r8   rO   )r   r*   r+   rP   r   rR   r   r   r   rS     s   
zAsyncRateLimiter.__call__rT   r   r   r?   r   r	   "  s    -4r	   )r2   rV   rK   r   	itertoolsr   r   timer   timeitr   	geopy.excr   
geopy.utilr   __all__r   r   r
   r	   r   r   r   r   <module>   s    2fw