o
    e9                     @   s  U d Z ddlZddlZddlZddlmZ ddlmZmZm	Z	m
Z
 ddlmZmZ er/ddlZddlmZmZ ddlmZ dae
d	 ed
< eddkZeG dd	 d	Zedde
e defddZdefddZde
e ddfddZdededefddZdededefddZdS )zSContains `WebhooksServer` and `webhook_endpoint` to create a webhook server easily.    N)wraps)TYPE_CHECKINGCallableDictOptional   )experimentalis_gradio_available)FastAPIRequest)JSONResponseWebhooksServer_global_appZSYSTEMspacesc                       sr   e Zd ZdZd fddZ		dded dee ddfd	d
Zddee defddZ	dddZ
dddZ  ZS )r   a<  
    The [`WebhooksServer`] class lets you create an instance of a Gradio app that can receive Huggingface webhooks.
    These webhooks can be registered using the [`~WebhooksServer.add_webhook`] decorator. Webhook endpoints are added to
    the app as a POST endpoint to the FastAPI router. Once all the webhooks are registered, the `run` method has to be
    called to start the app.

    It is recommended to accept [`WebhookPayload`] as the first argument of the webhook function. It is a Pydantic
    model that contains all the information about the webhook event. The data will be parsed automatically for you.

    Check out the [webhooks guide](../guides/webhooks_server) for a step-by-step tutorial on how to setup your
    WebhooksServer and deploy it on a Space.

    <Tip warning={true}>

    `WebhooksServer` is experimental. Its API is subject to change in the future.

    </Tip>

    <Tip warning={true}>

    You must have `gradio` installed to use `WebhooksServer` (`pip install --upgrade gradio`).

    </Tip>

    Args:
        ui (`gradio.Blocks`, optional):
            A Gradio UI instance to be used as the Space landing page. If `None`, a UI displaying instructions
            about the configured webhooks is created.
        webhook_secret (`str`, optional):
            A secret key to verify incoming webhook requests. You can set this value to any secret you want as long as
            you also configure it in your [webhooks settings panel](https://huggingface.co/settings/webhooks). You
            can also set this value as the `WEBHOOK_SECRET` environment variable. If no secret is provided, the
            webhook endpoints are opened without any security.

    Example:

        ```python
        import gradio as gr
        from huggingface_hub import WebhooksServer, WebhookPayload

        with gr.Blocks() as ui:
            ...

        app = WebhooksServer(ui=ui, webhook_secret="my_secret_key")

        @app.add_webhook("/say_hello")
        async def hello(payload: WebhookPayload):
            return {"message": "hello"}

        app.run()
        ```
    returnc                    s   t  stdt | S )NzjYou must have `gradio` installed to use `WebhooksServer`. Please run `pip install --upgrade gradio` first.)r	   ImportErrorsuper__new__)clsargskwargs	__class__ PD:\Projects\ConvertPro\env\Lib\site-packages\huggingface_hub/_webhooks_server.pyr   \   s
   zWebhooksServer.__new__Nui	gr.Blockswebhook_secretc                 C   s*   || _ |p	td| _i | _t| j d S )NZWEBHOOK_SECRET)_uiosgetenvr   registered_webhooks_warn_on_empty_secret)selfr   r   r   r   r   __init__d   s   zWebhooksServer.__init__pathc                    s0   t  r
  S ttj fdd}|S )au  
        Decorator to add a webhook to the [`WebhooksServer`] server.

        Args:
            path (`str`, optional):
                The URL path to register the webhook function. If not provided, the function name will be used as the
                path. In any case, all webhooks are registered under `/webhooks`.

        Raises:
            ValueError: If the provided path is already registered as a webhook.

        Example:
            ```python
            from huggingface_hub import WebhooksServer, WebhookPayload

            app = WebhooksServer()

            @app.add_webhook
            async def trigger_training(payload: WebhookPayload):
                if payload.repo.type == "dataset" and payload.event.action == "update":
                    # Trigger a training job if a dataset is updated
                    ...

            app.run()
        ```
        c                     sF   | d }d p	|j d }|jv rtd| d|j|< d S )Nr   z
/webhooks//zWebhook z already exists.)__name__stripr!   
ValueError)r   r   funcabs_pathr%   r#   r   r   _inner_post   s
   
z/WebhooksServer.add_webhook.<locals>._inner_post)callableadd_webhookr   r
   post)r#   r%   r-   r   r,   r   r/   o   s
   zWebhooksServer.add_webhookc                    s   | j p|  }|jdtd\| _}}| j D ]\}}| jdur't|| jd}| j	|| q|j
p5|jd d}|dd fdd	| jD  7 }|d
7 }t| |  dS )zIStarts the Gradio app with the FastAPI server and registers the webhooks.T)Zprevent_thread_lockshareNr   r&   z/
Webhooks are correctly setup and ready to use:
c                 3   s    | ]
}d   | V  qdS )z	  - POST Nr   ).0webhookurlr   r   	<genexpr>   s    z%WebhooksServer.run.<locals>.<genexpr>zG
Go to https://huggingface.co/settings/webhooks to setup your webhooks.)r   _get_default_uiZlaunch	_is_localZfastapi_appr!   itemsr   _wrap_webhook_to_check_secretr0   Z	share_urlZ	local_urlr(   joinprintZblock_thread)r#   r   _r%   r*   messager   r6   r   run   s   
"zWebhooksServer.runc              	   C   s   ddl }| 6}|d |d |t| j dd ddd	 | j D   |tr3d
nd W d   |S 1 sAw   Y  |S )zLDefault UI if not provided (lists webhooks and provides basic instructions).r   Nu)   # This is an app to process 🤗 WebhooksaT  Webhooks are a foundation for MLOps-related features. They allow you to listen for new changes on specific repos or to all repos belonging to particular set of users/organizations (not just your repos, but any repo). Check out this [guide](https://huggingface.co/docs/hub/webhooks) to get to know more about webhooks on the Huggingface Hub.z webhook(s) are registered:z

z
 c                 s   s.    | ]\}}d | dt |j| dV  qdS )z- [z]()N)_get_webhook_doc_urlr'   )r4   webhook_pathr5   r   r   r   r8      s
    
z1WebhooksServer._get_default_ui.<locals>.<genexpr>zGo to https://huggingface.co/settings/webhooks to setup your webhooks.
You app is running locally. Please look at the logs to check the full URL you need to set.z
This app is running on a Space. You can find the corresponding URL in the options menu (top-right) > 'Embed the Space'. The URL looks like 'https://{username}-{repo_name}.hf.space'.)gradioZBlocksMarkdownlenr!   r=   r;   r:   )r#   grr   r   r   r   r9      s0   



zWebhooksServer._get_default_ui)r   r   )NNN)r   N)r   r   )r'   
__module____qualname____doc__r   r   strr$   r   r/   rA   r9   __classcell__r   r   r   r   r   %   s    5


+r%   r   c                    s6   t  r	t  S ttjdtdtf fdd}|S )a  Decorator to start a [`WebhooksServer`] and register the decorated function as a webhook endpoint.

    This is a helper to get started quickly. If you need more flexibility (custom landing page or webhook secret),
    you can use [`WebhooksServer`] directly. You can register multiple webhook endpoints (to the same server) by using
    this decorator multiple times.

    Check out the [webhooks guide](../guides/webhooks_server) for a step-by-step tutorial on how to setup your
    server and deploy it on a Space.

    <Tip warning={true}>

    `webhook_endpoint` is experimental. Its API is subject to change in the future.

    </Tip>

    <Tip warning={true}>

    You must have `gradio` installed to use `webhook_endpoint` (`pip install --upgrade gradio`).

    </Tip>

    Args:
        path (`str`, optional):
            The URL path to register the webhook function. If not provided, the function name will be used as the path.
            In any case, all webhooks are registered under `/webhooks`.

    Examples:
        The default usage is to register a function as a webhook endpoint. The function name will be used as the path.
        The server will be started automatically at exit (i.e. at the end of the script).

        ```python
        from huggingface_hub import webhook_endpoint, WebhookPayload

        @webhook_endpoint
        async def trigger_training(payload: WebhookPayload):
            if payload.repo.type == "dataset" and payload.event.action == "update":
                # Trigger a training job if a dataset is updated
                ...

        # Server is automatically started at the end of the script.
        ```

        Advanced usage: register a function as a webhook endpoint and start the server manually. This is useful if you
        are running it in a notebook.

        ```python
        from huggingface_hub import webhook_endpoint, WebhookPayload

        @webhook_endpoint
        async def trigger_training(payload: WebhookPayload):
            if payload.repo.type == "dataset" and payload.event.action == "update":
                # Trigger a training job if a dataset is updated
                ...

        # Start the server manually
        trigger_training.run()
        ```
    r*   r   c                    sN   t    |  t jdkrt j t j fdd}|| _| S )Nr   c                      s   t  j    d S rI   )atexit
unregisterrA   r   Zappr   r   _run_now  s   z2webhook_endpoint.<locals>._inner.<locals>._run_now)_get_global_appr/   rG   r!   rO   registerrA   r   )r*   rR   r%   rQ   r   _inner  s   z webhook_endpoint.<locals>._inner)r.   webhook_endpointr   r   r/   r   )r%   rV   r   rU   r   rW      s
   <
rW   c                   C   s   t d u rt a t S rI   )r   r   r   r   r   r   rS   )  s   rS   r   c                 C   s0   | d u rt d t d t d d S t d d S )NzZWebhook secret is not defined. This means your webhook endpoints will be open to everyone.zTo add a secret, set `WEBHOOK_SECRET` as environment variable or pass it at initialization: 
	`app = WebhooksServer(webhook_secret='my_secret', ...)`zpFor more details about webhook secrets, please refer to https://huggingface.co/docs/hub/webhooks#webhook-secret.z$Webhook secret is correctly defined.)r>   r2   r   r   r   r"   0  s   r"   webhook_namerD   c                 C   s   d|  | dd d S )z@Returns the anchor to a given webhook in the docs (experimental)z/docs#/default/r&   r?   _post)replace)rX   rD   r   r   r   rC   ?  s   rC   r*   c                    sd   t  t dtf fdd}djvr0jt jdt jjtdftj	  d|_
|S )a  Wraps a webhook function to check the webhook secret before calling the function.

    This is a hacky way to add the `request` parameter to the function signature. Since FastAPI based itself on route
    parameters to inject the values to the function, we need to hack the function signature to retrieve the `Request`
    object (and hence the headers). A far cleaner solution would be to use a middleware. However, since
    `fastapi==0.90.1`, a middleware cannot be added once the app has started. And since the FastAPI app is started by
    Gradio internals (and not by us), we cannot add a middleware.

    This method is called only when a secret has been defined by the user. If a request is sent without the
    "x-webhook-secret", the function will return a 401 error (unauthorized). If the header is sent but is incorrect,
    the function will return a 403 error (forbidden).

    Inspired by https://stackoverflow.com/a/33112180.
    requestc                    s|   | j d}|d u rtddiddS |krtddiddS djv r(| |d< t r7 d	i |I d H S  d	i |S )
Nzx-webhook-secreterrorz x-webhook-secret header not set.i  )status_codezInvalid webhook secret.i  r[   r   )headersgetr   
parametersinspectiscoroutinefunction)r[   r   Zrequest_secretr*   Zinitial_sigr   r   r   _protected_funcU  s   

z6_wrap_webhook_to_check_secret.<locals>._protected_func)namekind
annotation)r`   )ra   	signaturer   r   r`   rZ   	ParameterPOSITIONAL_OR_KEYWORDtuplevalues__signature__)r*   r   rd   r   rc   r   r<   D  s   

r<   rI   ) rL   rO   ra   r   	functoolsr   typingr   r   r   r   utilsr   r	   rE   rH   Zfastapir
   r   Zfastapi.responsesr   r   __annotations__r    r:   r   rM   rW   rS   r"   rC   r<   r   r   r   r   <module>   s,    0S