o
    ÔèFh‹9  ã                   @   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   úQ/var/www/html/loop/nvenv/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