U
    ”Ê¹j8=  ã                   @   s8  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
mZ ddlmZmZmZ errddlZddlmZ eƒ r–ddlmZmZ dd	lmZ nd Z ZZdaed
 ed< ej d¡dkZeG dd
„ d
ƒƒZedee e	dœdd„ƒZedœdd„Zee ddœdd„Z eeedœdd„Z!e	ee	dœdd„Z"dS )zSContains `WebhooksServer` and `webhook_endpoint` to create a webhook server easily.é    N)Úwraps)ÚTYPE_CHECKINGÚAnyÚCallableÚDictÚOptionalé   )ÚexperimentalÚis_fastapi_availableÚis_gradio_available)ÚRequest)ÚFastAPIr   )ÚJSONResponseÚWebhooksServerÚ_global_appZSPACE_IDc                       sz   e Zd ZdZd dœ‡ fdd„Zded ee ddœdd	„Zdee ed
œdd„Z	de
eddœdd„Zd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 `launch` 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.

    > [!WARNING]
    > `WebhooksServer` is experimental. Its API is subject to change in the future.

    > [!WARNING]
    > You must have `gradio` installed to use `WebhooksServer` (`pip install --upgrade gradio`).

    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.launch()
        ```
    ©Úreturnc                    s(   t ƒ stdƒ‚tƒ stdƒ‚tƒ  | ¡S )NzjYou must have `gradio` installed to use `WebhooksServer`. Please run `pip install --upgrade gradio` first.zlYou must have `fastapi` installed to use `WebhooksServer`. Please run `pip install --upgrade fastapi` first.)r   ÚImportErrorr
   ÚsuperÚ__new__)ÚclsÚargsÚkwargs©Ú	__class__© ú_/var/www/html/bwave/python/venv/lib/python3.8/site-packages/huggingface_hub/_webhooks_server.pyr   [   s    ÿÿzWebhooksServer.__new__Nz	gr.Blocks)ÚuiÚwebhook_secretr   c                 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__h   s    zWebhooksServer.__init__©Úpathr   c                    s0   t ˆ ƒrˆ ¡ ˆ ƒS ttjƒ‡ ‡fdd„ƒ}|S )ax  
        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.launch()
        ```
        c                     sF   | d }dˆ p|j  d¡› �}|ˆjkr8t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   r1   s   s
    zWebhooksServer.add_webhookF)Úprevent_thread_lockÚlaunch_kwargsr   c           	         sú   | j p|  ¡ }| dt¡ |jf ddi|—Ž\| _}}| j ¡ D ]0\}}| jdk	r`t	|| jd�}| j 
|¡|ƒ q@tj d¡}|dk	rŽd| n
|jp˜|j‰ ˆ dkrªtdƒ‚ˆ  d	¡‰ d
}|dd ‡ fdd„| jD ƒ¡ 7 }|d7 }t|ƒ |sö| ¡  dS )z Launch the Gradio app and register webhooks to the underlying FastAPI server.

        Input parameters are forwarded to Gradio when launching the app.
        Zsharer3   TN©r   Z
SPACE_HOSTzhttps://zWCannot find the URL of the app. Please provide a valid `ui` or update `gradio` version.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.launch.<locals>.<genexpr>zG
Go to https://huggingface.co/settings/webhooks to setup your webhooks.)r   Ú_get_default_uiÚ
setdefaultÚ	_is_localÚlaunchZfastapi_appr"   Úitemsr   Ú_wrap_webhook_to_check_secretr2   r    ÚenvironÚgetZ	share_urlZ	local_urlr+   r*   ÚjoinÚprintZblock_thread)	r$   r3   r4   r   Ú_r'   r,   Z
space_hostÚmessager   r9   r   r?   ž   s$    

"zWebhooksServer.launchc              	   C   sz   ddl }| ¡ �`}| d¡ | d¡ | t| jƒ› d�d d dd	„ | j ¡ D ƒ¡ ¡ | trfd
nd¡ W 5 Q R X |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)   )r7   Úwebhook_pathr8   r   r   r   r;   Ñ   s   ÿz1WebhooksServer._get_default_ui.<locals>.<genexpr>z¡Go 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"   rD   r@   r>   )r$   Úgrr   r   r   r   r<   Â   s*    

ÿÿ
þþÿþû	zWebhooksServer._get_default_ui)NN)N)F)r)   Ú
__module__Ú__qualname__Ú__doc__r   r   Ústrr%   r   r1   Úboolr   r?   r<   Ú__classcell__r   r   r   r   r   *   s   /  ýü+$r&   c                    s4   t ˆ ƒrtƒ ˆ ƒS ttjƒttdœ‡ 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.

    > [!WARNING]
    > `webhook_endpoint` is experimental. Its API is subject to change in the future.

    > [!WARNING]
    > You must have `gradio` installed to use `webhook_endpoint` (`pip install --upgrade gradio`).

    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.launch()
        ```
    )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 ©N)ÚatexitÚ
unregisterr?   r   ©Zappr   r   Ú_launch_now$  s    z5webhook_endpoint.<locals>._inner.<locals>._launch_now)Ú_get_global_appr1   rM   r"   rV   Úregisterr?   r   )r,   rY   ©r'   rX   r   Ú_inner  s    z webhook_endpoint.<locals>._inner)r0   Úwebhook_endpointr   r   r1   r   )r'   r]   r   r\   r   r^   â   s
    6
r^   r   c                   C   s   t d krtƒ a t S rU   )r   r   r   r   r   r   rZ   0  s    rZ   )r   r   c                 C   s.   | d kr"t dƒ t dƒ t dƒ nt dƒ d S )NzZWebhook secret is not defined. This means your webhook endpoints will be open to everyone.z•To 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.)rE   r5   r   r   r   r#   7  s    ÿÿr#   )Úwebhook_namerJ   r   c                 C   s   d|  |  dd¡ d S )z@Returns the anchor to a given webhook in the docs (experimental)z/docs#/default/r(   rF   Ú_post)Úreplace)r_   rJ   r   r   r   rI   F  s    rI   )r,   r   r   c                    sd   t  ˆ ¡‰tˆ ƒtdœ‡ ‡‡fdd„ƒ}dˆjkr`ˆ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                 ›   sv   | j  d¡}|d kr$tddidd�S |ˆkr<tddidd�S dˆjkrN| |d< t ˆ ¡rhˆ f |ŽI d H S ˆ f |ŽS d S )	Nzx-webhook-secretÚerrorz x-webhook-secret header not set.i‘  )Ústatus_codezInvalid webhook secret.i“  rb   )ÚheadersrC   r   Ú
parametersÚinspectÚiscoroutinefunction)rb   r   Zrequest_secret©r,   Zinitial_sigr   r   r   Ú_protected_func\  s    

z6_wrap_webhook_to_check_secret.<locals>._protected_funcrb   )ÚnameÚkindÚ
annotation)rf   )rg   Ú	signaturer   r   rf   ra   Ú	ParameterÚPOSITIONAL_OR_KEYWORDÚtupleÚvaluesÚ__signature__)r,   r   rj   r   ri   r   rA   K  s    

ÿýÿrA   )N)#rQ   rV   rg   r    Ú	functoolsr   Útypingr   r   r   r   r   Úutilsr	   r
   r   rK   rN   Zfastapir   r   Zfastapi.responsesr   r   Ú__annotations__rB   rC   r>   r   rR   r^   rZ   r#   rI   rA   r   r   r   r   Ú<module>   s0    8M