U
    diGd                     @   s  U d Z ddlZddlZddlZddlZddlZddlZddlmZ ddl	m
Z
 ddlmZmZmZmZmZmZm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
dlmZm Z m!Z!m"Z"m#Z#m$Z$m%Z% ddlm&Z& ddl'm(Z( ddl)m*Z* ddl+m,Z, e&-e.Z/dZ0dZ1dZ2ej3dej4dZ5G dd deZ6G dd deZ7ej8dddZ9eg ej8f Z:e9a;e:e<d< e9fe:ddd d!Z=ej8dd"d#Z>ddd$d%Z?ee@e@ej8d&d'd(ZAd)dd*ejBejCejDjEfd+d,e,eFe@eGeGeeeH eeeH d-f f ee@ee@d-f f ed.d/d0ZIeFeeF eFd1d2d3ZJdAeeeF dd4d5d6ZKee# eFee#d7d8d9ZLejeFd:d;d<ZMe3d=ejNZOeeF e@eeF d>d?d@ZPdS )Bz>Contains utilities to handle HTTP requests in Huggingface Hub.    N)	lru_cache)quote)AnyCallableListOptionalTupleTypeUnion)	HTTPErrorResponse)HTTPAdapter)PreparedRequest)OfflineModeIsEnabled   )	constants)BadRequestErrorDisabledRepoErrorEntryNotFoundErrorGatedRepoErrorHfHubHTTPErrorRepositoryNotFoundErrorRevisionNotFoundError   )logging)JSONDecodeError)SliceFileObj)HTTP_METHOD_TX-Amzn-Trace-Idzx-request-idzx-amz-cf-ida  
        # staging or production endpoint
        ^https://[^/]+
        (
            # on /api/repo_type/repo_id
            /api/(models|datasets|spaces)/(.+)
            |
            # or /repo_id/resolve/revision/...
            /(.+)/resolve/(.+)
        )
    )flagsc                       s4   e Zd ZdZ fddZeed fddZ  ZS )UniqueRequestIdAdapterr   c                    s   t  j|f| t|jkr8|jtp0tt |jt< t	t|jdddk}t
d|jt  d|j d|j d| d	 d S )	Nauthorization r   zRequest :  z (authenticated: ))superadd_headersX_AMZN_TRACE_IDheadersgetX_REQUEST_IDstruuidZuuid4lenloggerdebugmethodurl)selfrequestkwargsZ	has_token	__class__ ?/tmp/pip-unpacked-wheel-ohruig0i/huggingface_hub/utils/_http.pyr'   N   s    
&z"UniqueRequestIdAdapter.add_headersr4   returnc              
      s   t jrtdt|  zt j|f||W S  tjk
r } z0|j	
t}|dk	rn|jd| df|_ W 5 d}~X Y nX dS )zSCatch any RequestException to append request id to the error message for debugging.zSend: Nz(Request ID: r%   )r   ZHF_DEBUGr/   r0   _curlifyr&   sendrequestsRequestExceptionr)   r*   r(   args)r3   r4   r@   r5   e
request_idr6   r8   r9   r=   [   s    zUniqueRequestIdAdapter.send)	__name__
__module____qualname__r(   r'   r   r   r=   __classcell__r8   r8   r6   r9   r    K   s   r    c                   @   s   e Zd ZeedddZdS )OfflineAdapterr:   c                 O   s   t d|j dd S )NzCannot reach za: offline mode is enabled. To disable it, please unset the `HF_HUB_OFFLINE` environment variable.)r   r2   )r3   r4   r@   r5   r8   r8   r9   r=   j   s    zOfflineAdapter.sendN)rC   rD   rE   r   r   r=   r8   r8   r8   r9   rG   i   s   rG   )r;   c                  C   sL   t  } tjr,| dt  | dt  n| dt  | dt  | S )Nzhttp://zhttps://)r>   Sessionr   ZHF_HUB_OFFLINEmountrG   r    )sessionr8   r8   r9   _default_backend_factoryp   s    rK   _GLOBAL_BACKEND_FACTORY)backend_factoryr;   c                 C   s   | a t  dS )a  
    Configure the HTTP backend by providing a `backend_factory`. Any HTTP calls made by `huggingface_hub` will use a
    Session object instantiated by this factory. This can be useful if you are running your scripts in a specific
    environment requiring custom configuration (e.g. custom proxy or certifications).

    Use [`get_session`] to get a configured Session. Since `requests.Session` is not guaranteed to be thread-safe,
    `huggingface_hub` creates 1 Session instance per thread. They are all instantiated using the same `backend_factory`
    set in [`configure_http_backend`]. A LRU cache is used to cache the created sessions (and connections) between
    calls. Max size is 128 to avoid memory leaks if thousands of threads are spawned.

    See [this issue](https://github.com/psf/requests/issues/2766) to know more about thread-safety in `requests`.

    Example:
    ```py
    import requests
    from huggingface_hub import configure_http_backend, get_session

    # Create a factory function that returns a Session with configured proxies
    def backend_factory() -> requests.Session:
        session = requests.Session()
        session.proxies = {"http": "http://10.10.1.10:3128", "https": "https://10.10.1.11:1080"}
        return session

    # Set it as the default session factory
    configure_http_backend(backend_factory=backend_factory)

    # In practice, this is mostly done internally in `huggingface_hub`
    session = get_session()
    ```
    N)rL   reset_sessions)rM   r8   r8   r9   configure_http_backend   s     rO   c                   C   s   t t t dS )a  
    Get a `requests.Session` object, using the session factory from the user.

    Use [`get_session`] to get a configured Session. Since `requests.Session` is not guaranteed to be thread-safe,
    `huggingface_hub` creates 1 Session instance per thread. They are all instantiated using the same `backend_factory`
    set in [`configure_http_backend`]. A LRU cache is used to cache the created sessions (and connections) between
    calls. Max size is 128 to avoid memory leaks if thousands of threads are spawned.

    See [this issue](https://github.com/psf/requests/issues/2766) to know more about thread-safety in `requests`.

    Example:
    ```py
    import requests
    from huggingface_hub import configure_http_backend, get_session

    # Create a factory function that returns a Session with configured proxies
    def backend_factory() -> requests.Session:
        session = requests.Session()
        session.proxies = {"http": "http://10.10.1.10:3128", "https": "https://10.10.1.11:1080"}
        return session

    # Set it as the default session factory
    configure_http_backend(backend_factory=backend_factory)

    # In practice, this is mostly done internally in `huggingface_hub`
    session = get_session()
    ```
    
process_id	thread_id)_get_session_from_cacheosgetpid	threading	get_identr8   r8   r8   r9   get_session   s    rX   c                   C   s   t   dS )zReset the cache of sessions.

    Mostly used internally when sessions are reconfigured or an SSLError is raised.
    See [`configure_http_backend`] for more details.
    N)rS   cache_clearr8   r8   r8   r9   rN      s    rN   )rQ   rR   r;   c                 C   s   t  S )z
    Create a new session per thread using global factory. Using LRU cache (maxsize 128) to avoid memory leaks when
    using thousands of threads. Cache is cleared when `configure_http_backend` is called.
    )rL   rP   r8   r8   r9   rS      s    rS         )i  i  i  i  )max_retriesbase_wait_timemax_wait_timeretry_on_exceptionsretry_on_status_codes.)r1   r2   r\   r]   r^   r_   r`   r;   c                K   sv  t |tr|f}t |tr |f}d}|}	d}
d|krTt |d tjtfrT|d  }
t }|d7 }zt|
dk	rz|d |
 |j	f | |d|}|j
|kr|W S td|j
 d|  d|  ||kr|  |W S W nb |k
r8 } zBtd	| d
|  d|  t |tjrt  ||kr(|W 5 d}~X Y nX td|	 d| d| d t|	 t||	d }	qZdS )a7  Wrapper around requests to retry calls on an endpoint, with exponential backoff.

    Endpoint call is retried on exceptions (ex: connection timeout, proxy error,...)
    and/or on specific status codes (ex: service unavailable). If the call failed more
    than `max_retries`, the exception is thrown or `raise_for_status` is called on the
    response object.

    Re-implement mechanisms from the `backoff` library to avoid adding an external
    dependencies to `hugging_face_hub`. See https://github.com/litl/backoff.

    Args:
        method (`Literal["GET", "OPTIONS", "HEAD", "POST", "PUT", "PATCH", "DELETE"]`):
            HTTP method to perform.
        url (`str`):
            The URL of the resource to fetch.
        max_retries (`int`, *optional*, defaults to `5`):
            Maximum number of retries, defaults to 5 (no retries).
        base_wait_time (`float`, *optional*, defaults to `1`):
            Duration (in seconds) to wait before retrying the first time.
            Wait time between retries then grows exponentially, capped by
            `max_wait_time`.
        max_wait_time (`float`, *optional*, defaults to `8`):
            Maximum duration (in seconds) to wait before retrying.
        retry_on_exceptions (`Type[Exception]` or `Tuple[Type[Exception]]`, *optional*):
            Define which exceptions must be caught to retry the request. Can be a single type or a tuple of types.
            By default, retry on `requests.Timeout`, `requests.ConnectionError` and `requests.exceptions.ChunkedEncodingError`.
        retry_on_status_codes (`int` or `Tuple[int]`, *optional*, defaults to `(500, 502, 503, 504)`):
            Define on which status codes the request must be retried. By default, 5xx errors are retried.
        **kwargs (`dict`, *optional*):
            kwargs to pass to `requests.request`.

    Example:
    ```
    >>> from huggingface_hub.utils import http_backoff

    # Same usage as "requests.request".
    >>> response = http_backoff("GET", "https://www.google.com")
    >>> response.raise_for_status()

    # If you expect a Gateway Timeout from time to time
    >>> http_backoff("PUT", upload_url, data=data, retry_on_status_codes=504)
    >>> response.raise_for_status()
    ```

    > [!WARNING]
    > When using `requests` it is possible to stream data by passing an iterator to the
    > `data` argument. On http backoff this is a problem as the iterator is not reset
    > after a failed call. This issue is mitigated for file objects or any IO streams
    > by saving the initial position of the cursor (with `data.tell()`) and resetting the
    > cursor between each call (with `data.seek()`). For arbitrary iterators, http backoff
    > will fail. If this is a hard constraint for you, please let us know by opening an
    > issue on [Github](https://github.com/huggingface/huggingface_hub).
    r   Ndatar   )r1   r2   zHTTP Error z thrown while requesting r$   'z' thrown while requesting zRetrying in z	s [Retry /z].r   )
isinstancetypeintioIOBaser   tellrX   seekr4   status_coder/   warningraise_for_statusr>   ConnectionErrorrN   timesleepmin)r1   r2   r\   r]   r^   r_   r`   r5   Znb_triesZ
sleep_timeZio_obj_initial_posrJ   responseerrr8   r8   r9   http_backoff   s<    D





rt   )r2   endpointr;   c                 C   sD   |r| dntj}|tjtjfkr@| tj|} | tj|} | S )zReplace the default endpoint in a URL by a custom one.

    This is useful when using a proxy and the Hugging Face Hub returns a URL with the default endpoint.
    rc   )rstripr   ZENDPOINTZ_HF_DEFAULT_ENDPOINTZ_HF_DEFAULT_STAGING_ENDPOINTreplace)r2   ru   r8   r8   r9   fix_hf_endpoint_in_urlP  s
    rx   )rr   endpoint_namer;   c              
   C   s  z|    W nx tk
r } zV| jd}| jd}|dkrt| j dd d| j d }tt|| |n|dkr| j dd d	| j d }tt|| |n|d
kr| j dd d| j d }tt	|| |n|dkr&| j dd d| j d d d }tt
|| |n>|dkst| jdkr|dkr| jdk	r| jjdk	rt| jjdk	r| j dd d| j d d }tt|| |n| jdkr|dk	rd| dnd}tt|| |n| jdkrd| j d| dd| j d d }tt|| |nF| jdkrd| jjd}| d| d | jd! d}tt|| |ttt|| |W 5 d}~X Y nX dS )"a(  
    Internal version of `response.raise_for_status()` that will refine a
    potential HTTPError. Raised exception will be an instance of `HfHubHTTPError`.

    This helper is meant to be the unique method to raise_for_status when making a call
    to the Hugging Face Hub.


    Example:
    ```py
        import requests
        from huggingface_hub.utils import get_session, hf_raise_for_status, HfHubHTTPError

        response = get_session().post(...)
        try:
            hf_raise_for_status(response)
        except HfHubHTTPError as e:
            print(str(e)) # formatted message
            e.request_id, e.server_message # details returned by server

            # Complete the error message with additional information once it's raised
            e.append_to_message("
`create_commit` expects the repository to exist.")
            raise
    ```

    Args:
        response (`Response`):
            Response from the server.
        endpoint_name (`str`, *optional*):
            Name of the endpoint that has been called. If provided, the error message
            will be more complete.

    > [!WARNING]
    > Raises when the request has failed:
    >
    >     - [`~utils.RepositoryNotFoundError`]
    >         If the repository to download from cannot be found. This may be because it
    >         doesn't exist, because `repo_type` is not set correctly, or because the repo
    >         is `private` and you do not have access.
    >     - [`~utils.GatedRepoError`]
    >         If the repository exists but is gated and the user is not on the authorized
    >         list.
    >     - [`~utils.RevisionNotFoundError`]
    >         If the repository exists but the revision couldn't be find.
    >     - [`~utils.EntryNotFoundError`]
    >         If the repository exists but the entry (e.g. the requested file) couldn't be
    >         find.
    >     - [`~utils.BadRequestError`]
    >         If request failed with a HTTP 400 BadRequest error.
    >     - [`~utils.HfHubHTTPError`]
    >         If request failed for a reason not listed above.
    zX-Error-CodeX-Error-MessageZRevisionNotFoundz Client Error.

zRevision Not Found for url: .ZEntryNotFoundzEntry Not Found for url: Z	GatedRepoz!Cannot access gated repo for url z$Access to this resource is disabled.z!Cannot access repository for url 
ZRepoNotFoundi  z+Invalid credentials in Authorization headerNzRepository Not Found for url: z
Please make sure you specified the correct `repo_id` and `repo_type`.
If you are trying to access a private or gated repo, make sure you are authenticated. For more details, see https://huggingface.co/docs/huggingface_hub/authenticationi  z

Bad request for z
 endpoint:z

Bad request:i  z Forbidden: z
Cannot access content at: z2
Make sure your token has the correct permissions.i  ZRangez. Requested range: z. Content-Range: zContent-Range)rm   r   r)   r*   rk   r2   _formatr   r   r   r   r4   REPO_API_REGEXsearchr   r   r   r,   )rr   ry   rA   Z
error_codeerror_messagemessageZrange_headerr8   r8   r9   hf_raise_for_status]  s    5




	r   )
error_typecustom_messagerr   r;   c                 C   s   g }|j d}|d k	r"|| zp| }|d}|d k	r^t|trT|| n
|| |d}|d k	r|D ]}d|krt||d  qtW n@ tk
r   |j dd}|jrd|	 kr||j Y nX dd	 |D }tt
|}d
|}	|}
|	r:|		 |	 kr:d|kr.|
d
|	 7 }
n|
d|	 7 }
d}d}tdftdftdffD ]<\}}|j |}|rXt|}d| d| d} qqX|r|	 |
	 krd
|
kr|
d
}|
d | | |
|d   }
n|
|7 }
| |
 ||	pd dS )Nrz   errorerrorsr   zContent-Typer"   htmlc                 S   s$   g | ]}t | rt | qS r8   )r,   strip).0liner8   r8   r9   
<listcomp>  s      z_format.<locals>.<listcomp>r}   r{   z
Request IDzAmzn Trace IDz	Amz CF IDz (r#   r%   )rr   server_message)r)   r*   appendjsonrd   listextendr   textlowerdictfromkeysjoinr+   r(   X_AMZ_CF_IDr,   indexr   )r   r   rr   Zserver_errorsZfrom_headersra   r   r   content_typer   Zfinal_error_messagerB   Zrequest_id_messageheaderlabelvalueZnewline_indexr8   r8   r9   r~     s\    









r~   r:   c                 C   s  dd| j fg}t| j D ].\}}| dkr4d}|dd||fg7 }q| jr| j}t|trr|j	ddd	}nt
|d
rd}t|dkr|dd d }|d|ddfg7 }|d| jfg7 }g }|D ],\}}|r|t| |r|t| qd|S )zConvert a `requests.PreparedRequest` into a curl command (str).

    Used for debug purposes only.

    Implementation vendored from https://github.com/ofw/curlify/blob/master/curlify.py.
    MIT License Copyright (c) 2016 Egor.
    )ZcurlNz-Xr!   z<TOKEN>z-Hz{0}: {1}zutf-8ignore)r   readz<file-like object>i  Nz ... [truncated]z-dr}   r"   r$   )r1   sortedr)   itemsr   formatbodyrd   bytesdecodehasattrr.   rw   r2   r   r   r   )r4   partskvr   Z
flat_partsr8   r8   r9   r<   0  s0    	

r<   z%^\s*bytes\s*=\s*(\d*)\s*-\s*(\d*)\s*$)original_rangeresume_sizer;   c                 C   s   | sd| dS d| kr(t d| dt| }|sFtd| d| \}}|s|sjtd| dt|| }d| }|d	krtd
|d|S t|}|| }|rt|}d| d| }||krtd
|d|S d| dS )zB
    Adjust HTTP Range header to account for resume position.
    zbytes=-,zMultiple ranges detected - z, not supported yet.zInvalid range format - r|   zbytes=-r   zEmpty new range - )
ValueErrorRANGE_REGEXmatchRuntimeErrorgroupsrf   )r   r   r   startend
new_suffixZ	new_rangeZ	new_startr8   r8   r9   _adjust_range_header\  s2    

r   )N)Q__doc__rg   rT   rerV   ro   r-   	functoolsr   shlexr   typingr   r   r   r   r   r	   r
   r>   r   r   Zrequests.adaptersr   Zrequests.modelsr   Zhuggingface_hub.errorsr   r"   r   r   r   r   r   r   r   r   r   r   Z_fixesr   Z_lfsr   _typingr   Z
get_loggerrC   r/   r(   r+   r   compileVERBOSEr   r    rG   rH   rK   ZBACKEND_FACTORY_TrL   __annotations__rO   rX   rN   rf   rS   Timeoutrn   
exceptionsChunkedEncodingErrorr,   float	Exceptionrt   rx   r   r~   r<   
IGNORECASEr   r   r8   r8   r8   r9   <module>   s~   $$	
$ 	{ Q)