Repository: gradio-app/gradio

Issue:
An error occurs when passing `datetime.datetime` to `gr.DateTime`
### Describe the bug

`datetime.datetime` is allowed as the value of `gr.DateTime` according to the docstring, but an error occurs when passing it.

Looks like `postprocess()` is called when calling `super().__init__()`, so
this
https://github.com/gradio-app/gradio/blob/ac132e3cbc8dbc7bec3d607d52bef347e90feb41/gradio/components/datetime.py#L77-L80
should come before
https://github.com/gradio-app/gradio/blob/ac132e3cbc8dbc7bec3d607d52bef347e90feb41/gradio/components/datetime.py#L63-L76


### Have you searched existing issues?  🔎

- [X] I have searched and found no existing issues

### Reproduction

```python
import datetime

import gradio as gr

with gr.Blocks() as demo:
    dt = gr.DateTime(
        type="datetime",
        value=datetime.datetime.now(tz=datetime.timezone.utc),
    )
demo.queue().launch()
```


### Screenshot

_No response_

### Logs

```shell
Traceback (most recent call last):
  File "/home/ubuntu/repos/temp/2024-07-24-datetime/app.py", line 6, in <module>
    dt = gr.DateTime(
  File "/home/ubuntu/.virtualenvs/py31014/lib/python3.10/site-packages/gradio/component_meta.py", line 163, in wrapper
    return fn(self, **kwargs)
  File "/home/ubuntu/.virtualenvs/py31014/lib/python3.10/site-packages/gradio/components/datetime.py", line 63, in __init__
    super().__init__(
  File "/home/ubuntu/.virtualenvs/py31014/lib/python3.10/site-packages/gradio/component_meta.py", line 163, in wrapper
    return fn(self, **kwargs)
  File "/home/ubuntu/.virtualenvs/py31014/lib/python3.10/site-packages/gradio/components/base.py", line 211, in __init__
    initial_value = self.postprocess(initial_value)
  File "/home/ubuntu/.virtualenvs/py31014/lib/python3.10/site-packages/gradio/components/datetime.py", line 112, in postprocess
    return datetime.strftime(value, self.time_format)
AttributeError: 'DateTime' object has no attribute 'time_format'
```


### System Info

```shell
Gradio Environment Information:
------------------------------
Operating System: Linux
gradio version: 4.39.0
gradio_client version: 1.1.1

------------------------------------------------
gradio dependencies in your environment:

aiofiles: 22.1.0
anyio: 3.7.1
fastapi: 0.111.0
ffmpy: 0.3.2
gradio-client==1.1.1 is not installed.
httpx: 0.27.0
huggingface-hub: 0.24.1
importlib-resources: 6.4.0
jinja2: 3.1.4
markupsafe: 2.1.5
matplotlib: 3.9.1
numpy: 1.26.4
orjson: 3.10.6
packaging: 24.1
pandas: 2.2.2
pillow: 10.4.0
pydantic: 2.8.2
pydub: 0.25.1
python-multipart: 0.0.9
pyyaml: 6.0.1
ruff: 0.5.1
semantic-version: 2.10.0
tomlkit==0.12.0 is not installed.
typer: 0.12.3
typing-extensions: 4.12.2
urllib3: 2.2.2
uvicorn: 0.30.1
authlib; extra == 'oauth' is not installed.
itsdangerous; extra == 'oauth' is not installed.


gradio_client dependencies in your environment:

fsspec: 2024.5.0
httpx: 0.27.0
huggingface-hub: 0.24.1
packaging: 24.1
typing-extensions: 4.12.2
websockets: 11.0.3
```


### Severity

I can work around it

Discussion:
@zwldarren: I think you should use strftime(): 
```
datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
```@kdlslyv: yeah same here, feels improper to work around it.

Solution patch:
diff --git a/.changeset/dirty-buses-throw.md b/.changeset/dirty-buses-throw.md
new file mode 100644
index 000000000..bca7da9f7
--- /dev/null
+++ b/.changeset/dirty-buses-throw.md
@@ -0,0 +1,5 @@
+---
+"gradio": patch
+---
+
+fix:Fix file upload progress
diff --git a/.changeset/lazy-coins-marry.md b/.changeset/lazy-coins-marry.md
new file mode 100644
index 000000000..6dfc4ce4b
--- /dev/null
+++ b/.changeset/lazy-coins-marry.md
@@ -0,0 +1,5 @@
+---
+"gradio": minor
+---
+
+feat:Add `openapi.json` route for Gradio apps as well as docs on the "view API" page
diff --git a/.changeset/open-dragons-melt.md b/.changeset/open-dragons-melt.md
new file mode 100644
index 000000000..1841f6bfd
--- /dev/null
+++ b/.changeset/open-dragons-melt.md
@@ -0,0 +1,5 @@
+---
+"gradio": patch
+---
+
+fix:Fix gradio cc environment
diff --git a/.changeset/quick-clowns-build.md b/.changeset/quick-clowns-build.md
new file mode 100644
index 000000000..9b965513b
--- /dev/null
+++ b/.changeset/quick-clowns-build.md
@@ -0,0 +1,5 @@
+---
+"gradio": patch
+---
+
+fix:Fix passing datetime.datetime instance to gr.Datetime
diff --git a/demo/chatbot_editable/run.ipynb b/demo/chatbot_editable/run.ipynb
index 13a255864..8c541db75 100644
--- a/demo/chatbot_editable/run.ipynb
+++ b/demo/chatbot_editable/run.ipynb
@@ -1 +1 @@
-{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: chatbot_editable"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "\n", "with gr.Blocks() as demo:\n", "    with gr.Row():\n", "        chatbot = gr.Chatbot(value=[], type=\"messages\", editable=\"user\")\n", "        chatbot2 = gr.Chatbot(value=[], type=\"tuples\", editable=\"user\")\n", "    add_message_btn = gr.Button(\"Add Message\")\n", "    add_user_message_btn = gr.Button(\"Add User Message\")\n", "\n", "    with gr.Row():\n", "        concatenated_text1 = gr.Textbox(label=\"Concatenated Chat 1\")\n", "        concatenated_text2 = gr.Textbox(label=\"Concatenated Chat 2\")\n", "        edited_messages = gr.Textbox(label=\"Edited Message\")\n", "\n", "    def add_message(history: list, history2: list[list[str]]):\n", "        usr_msg = \"I'm a user\"\n", "        bot_msg = \"I'm a bot\"\n", "        history.append({\"role\": \"user\", \"content\": usr_msg})\n", "        history.append({\"role\": \"assistant\", \"content\": bot_msg})\n", "        history2.append([usr_msg, bot_msg])\n", "        return history, history2\n", "    \n", "    def add_user_message(history: list, history2: list[list[str | None]]):\n", "        usr_msg = \"I'm a user\"\n", "        history.append({\"role\": \"user\", \"content\": usr_msg})\n", "        history2.append([usr_msg, None])\n", "        return history, history2\n", "    \n", "    add_message_btn.click(add_message, [chatbot, chatbot2], [chatbot, chatbot2])\n", "    add_user_message_btn.click(add_user_message, [chatbot, chatbot2], [chatbot, chatbot2])\n", "    chatbot.change(lambda m: \"|\".join(m[\"content\"] for m in m), chatbot, concatenated_text1)\n", "    chatbot2.change(lambda m: \"|\".join(\"|\".join(filter(None, x)) for x in m), chatbot2, concatenated_text2)\n", "\n", "    def edit_message(edited_message: gr.EditData):\n", "        return f\"from {edited_message.previous_value} to {edited_message.value} at {edited_message.index}\"\n", "    \n", "    chatbot.edit(edit_message, None, edited_messages)\n", "    chatbot2.edit(edit_message, None, edited_messages)\n", "\n", "if __name__ == \"__main__\":\n", "    demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
\ No newline at end of file
+{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: chatbot_editable"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "\n", "with gr.Blocks() as demo:\n", "    with gr.Row():\n", "        chatbot = gr.Chatbot(value=[], type=\"messages\", editable=\"user\")\n", "        chatbot2 = gr.Chatbot(value=[], type=\"tuples\", editable=\"user\")\n", "    add_message_btn = gr.Button(\"Add Message\")\n", "    add_user_message_btn = gr.Button(\"Add User Message\")\n", "\n", "    with gr.Row():\n", "        concatenated_text1 = gr.Textbox(label=\"Concatenated Chat 1\")\n", "        concatenated_text2 = gr.Textbox(label=\"Concatenated Chat 2\")\n", "        edited_messages = gr.Textbox(label=\"Edited Message\")\n", "\n", "    def add_message(history: list, history2: list[list[str]]):\n", "        usr_msg = \"I'm a user\"\n", "        bot_msg = \"I'm a bot\"\n", "        history.append({\"role\": \"user\", \"content\": usr_msg})\n", "        history.append({\"role\": \"assistant\", \"content\": bot_msg})\n", "        history2.append([usr_msg, bot_msg])\n", "        return history, history2\n", "\n", "    def add_user_message(history: list, history2: list[list[str | None]]):\n", "        usr_msg = \"I'm a user\"\n", "        history.append({\"role\": \"user\", \"content\": usr_msg})\n", "        history2.append([usr_msg, None])\n", "        return history, history2\n", "\n", "    add_message_btn.click(add_message, [chatbot, chatbot2], [chatbot, chatbot2])\n", "    add_user_message_btn.click(add_user_message, [chatbot, chatbot2], [chatbot, chatbot2])\n", "    chatbot.change(lambda m: \"|\".join(m[\"content\"] for m in m), chatbot, concatenated_text1)\n", "    chatbot2.change(lambda m: \"|\".join(\"|\".join(filter(None, x)) for x in m), chatbot2, concatenated_text2)\n", "\n", "    def edit_message(edited_message: gr.EditData): # type: ignore\n", "        return f\"from {edited_message.previous_value} to {edited_message.value} at {edited_message.index}\" # type: ignore\n", "\n", "    chatbot.edit(edit_message, None, edited_messages)\n", "    chatbot2.edit(edit_message, None, edited_messages)\n", "\n", "if __name__ == \"__main__\":\n", "    demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
\ No newline at end of file
diff --git a/demo/chatbot_editable/run.py b/demo/chatbot_editable/run.py
index d231195d8..9074a5395 100644
--- a/demo/chatbot_editable/run.py
+++ b/demo/chatbot_editable/run.py
@@ -19,21 +19,21 @@ with gr.Blocks() as demo:
         history.append({"role": "assistant", "content": bot_msg})
         history2.append([usr_msg, bot_msg])
         return history, history2
-    
+
     def add_user_message(history: list, history2: list[list[str | None]]):
         usr_msg = "I'm a user"
         history.append({"role": "user", "content": usr_msg})
         history2.append([usr_msg, None])
         return history, history2
-    
+
     add_message_btn.click(add_message, [chatbot, chatbot2], [chatbot, chatbot2])
     add_user_message_btn.click(add_user_message, [chatbot, chatbot2], [chatbot, chatbot2])
     chatbot.change(lambda m: "|".join(m["content"] for m in m), chatbot, concatenated_text1)
     chatbot2.change(lambda m: "|".join("|".join(filter(None, x)) for x in m), chatbot2, concatenated_text2)
 
-    def edit_message(edited_message: gr.EditData):
-        return f"from {edited_message.previous_value} to {edited_message.value} at {edited_message.index}"
-    
+    def edit_message(edited_message: gr.EditData): # type: ignore
+        return f"from {edited_message.previous_value} to {edited_message.value} at {edited_message.index}" # type: ignore
+
     chatbot.edit(edit_message, None, edited_messages)
     chatbot2.edit(edit_message, None, edited_messages)
 
diff --git a/gradio/cli/commands/cli_env_info.py b/gradio/cli/commands/cli_env_info.py
index 795bd0cdd..6259868f4 100644
--- a/gradio/cli/commands/cli_env_info.py
+++ b/gradio/cli/commands/cli_env_info.py
@@ -7,6 +7,7 @@ from __future__ import annotations
 import platform
 from importlib import metadata
 
+from packaging.requirements import Requirement
 from rich import print
 
 
@@ -28,17 +29,11 @@ def print_environment_info():
             print(f"{package_name} dependencies in your environment:\n")
             if dist.requires is not None:
                 for req in dist.requires:
-                    req_base_name = (
-                        req.split(">")[0]
-                        .split("<")[0]
-                        .split("~")[0]
-                        .split("[")[0]
-                        .split("!")[0]
-                    )
+                    req_obj = Requirement(req)
                     try:
-                        print(f"{req_base_name}: {metadata.version(req_base_name)}")
+                        print(f"{req_obj.name}: {metadata.version(req_obj.name)}")
                     except metadata.PackageNotFoundError:
-                        print(f"{req_base_name} is not installed.")
+                        print(f"{req_obj.name} is not installed.")
                 print("\n")
         except metadata.PackageNotFoundError:
             print(f"{package_name} package is not installed.")
diff --git a/gradio/components/datetime.py b/gradio/components/datetime.py
index f552b5ace..414fe4aa0 100644
--- a/gradio/components/datetime.py
+++ b/gradio/components/datetime.py
@@ -61,20 +61,6 @@ class DateTime(FormComponent):
             render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.
             key: if assigned, will be used to assume identity across a re-render. Components that have the same key across a re-render will have their value preserved.
         """
-        super().__init__(
-            every=every,
-            scale=scale,
-            min_width=min_width,
-            visible=visible,
-            label=label,
-            show_label=show_label,
-            info=info,
-            elem_id=elem_id,
-            elem_classes=elem_classes,
-            render=render,
-            key=key,
-            value=value,
-        )
         self.type = type
         self.include_time = include_time
         self.interactive = interactive
@@ -89,6 +75,20 @@ class DateTime(FormComponent):
             if include_time
             else "a %Y-%m-%d formatted string"
         )
+        super().__init__(
+            every=every,
+            scale=scale,
+            min_width=min_width,
+            visible=visible,
+            label=label,
+            show_label=show_label,
+            info=info,
+            elem_id=elem_id,
+            elem_classes=elem_classes,
+            render=render,
+            key=key,
+            value=value,
+        )
 
     def preprocess(self, payload: str | None) -> str | float | datetime | None:
         """
diff --git a/gradio/route_utils.py b/gradio/route_utils.py
index f7de88fe7..33d6639f8 100644
--- a/gradio/route_utils.py
+++ b/gradio/route_utils.py
@@ -11,7 +11,7 @@ import re
 import shutil
 import threading
 import uuid
-from collections import deque
+from collections import defaultdict, deque
 from collections.abc import AsyncGenerator, Callable
 from contextlib import AbstractAsyncContextManager, AsyncExitStack, asynccontextmanager
 from dataclasses import dataclass as python_dataclass
@@ -485,10 +485,15 @@ class FileUploadProgressNotQueuedError(Exception):
 class FileUploadProgress:
     def __init__(self) -> None:
         self._statuses: dict[str, FileUploadProgressTracker] = {}
+        self._signals = defaultdict(asyncio.Event)
 
     def track(self, upload_id: str):
         if upload_id not in self._statuses:
             self._statuses[upload_id] = FileUploadProgressTracker(deque(), False)
+            self._signals[upload_id].set()
+
+    async def is_tracked(self, upload_id: str) -> bool:
+        return await self._signals[upload_id].wait()
 
     def append(self, upload_id: str, filename: str, message_bytes: bytes):
         if upload_id not in self._statuses:
diff --git a/gradio/routes.py b/gradio/routes.py
index 613e07428..1b35396d2 100644
--- a/gradio/routes.py
+++ b/gradio/routes.py
@@ -690,6 +690,104 @@ class App(FastAPI):
                 app.api_info = api_info
             return app.api_info
 
+        @router.get("/openapi.json", dependencies=[Depends(login_check)])
+        def openapi_schema(request: fastapi.Request):
+            """Generate an OpenAPI schema from the Gradio app's API info."""
+            info = api_info(request)
+            schema = {
+                "openapi": "3.0.2",
+                "info": {
+                    "title": getattr(app.get_blocks(), "title", "Gradio App"),
+                    "description": getattr(app.get_blocks(), "description", ""),
+                    "version": VERSION,
+                },
+                "paths": {},
+                "components": {"schemas": {}},
+            }
+
+            for endpoint_path, endpoint_info in info.get("named_endpoints", {}).items():  # type: ignore
+                if not endpoint_info.get("show_api", True):
+                    continue
+                path_item = {
+                    "post": {
+                        "summary": endpoint_info.get(
+                            "description", f"Endpoint {endpoint_path}"
+                        ),
+                        "description": endpoint_info.get("description", ""),
+                        "operationId": endpoint_path.strip("/").replace("/", "_"),
+                        "requestBody": {
+                            "required": True,
+                            "content": {
+                                "application/json": {
+                                    "schema": {"type": "object", "properties": {}}
+                                }
+                            },
+                        },
+                        "responses": {
+                            "200": {
+                                "description": "Successful response",
+                                "content": {
+                                    "application/json": {
+                                        "schema": {"type": "object", "properties": {}}
+                                    }
+                                },
+                            }
+                        },
+                    }
+                }
+
+                request_properties = path_item["post"]["requestBody"]["content"][
+                    "application/json"
+                ]["schema"]["properties"]
+                for param in endpoint_info.get("parameters", []):
+                    param_name = param["parameter_name"]
+                    param_type = param.get("type", {})
+
+                    if "additional_description" in param_type:
+                        param_type = dict(param_type)
+                        param_type.pop("additional_description", None)
+
+                    if "properties" in param_type and "type" not in param_type:
+                        param_type = dict(param_type)
+                        param_type["type"] = "object"
+
+                    request_properties[param_name] = param_type
+
+                    if "example_input" in param:
+                        if (
+                            "examples"
+                            not in path_item["post"]["requestBody"]["content"][
+                                "application/json"
+                            ]
+                        ):
+                            path_item["post"]["requestBody"]["content"][
+                                "application/json"
+                            ]["examples"] = {"example1": {"value": {}}}
+                        path_item["post"]["requestBody"]["content"]["application/json"][
+                            "examples"
+                        ]["example1"]["value"][param_name] = param["example_input"]
+
+                response_properties = path_item["post"]["responses"]["200"]["content"][
+                    "application/json"
+                ]["schema"]["properties"]
+                for i, ret in enumerate(endpoint_info.get("returns", [])):
+                    ret_name = f"output_{i}" if i > 0 else "output"
+                    ret_type = ret.get("type", {})
+
+                    if "additional_description" in ret_type:
+                        ret_type = dict(ret_type)
+                        ret_type.pop("additional_description", None)
+
+                    if "properties" in ret_type and "type" not in ret_type:
+                        ret_type = dict(ret_type)
+                        ret_type["type"] = "object"
+
+                    response_properties[ret_name] = ret_type
+
+                schema["paths"][f"/run{endpoint_path}"] = path_item
+
+            return schema
+
         @app.get("/config/", dependencies=[Depends(login_check)])
         @app.get("/config", dependencies=[Depends(login_check)])
         def get_config(request: fastapi.Request, deep_link: str = ""):
@@ -1406,7 +1504,7 @@ class App(FastAPI):
             return app.get_blocks()._queue.get_status()
 
         @router.get("/upload_progress")
-        def get_upload_progress(upload_id: str, request: fastapi.Request):
+        async def get_upload_progress(upload_id: str, request: fastapi.Request):
             async def sse_stream(request: fastapi.Request):
                 last_heartbeat = time.perf_counter()
                 is_done = False
@@ -1441,6 +1539,13 @@ class App(FastAPI):
                             yield f"data: {json.dumps(message)}\n\n"
                             last_heartbeat = time.perf_counter()
 
+            try:
+                await asyncio.wait_for(
+                    file_upload_statuses.is_tracked(upload_id), timeout=3
+                )
+            except (asyncio.TimeoutError, TimeoutError):
+                return PlainTextResponse("Upload not found", status_code=404)
+
             return StreamingResponse(
                 sse_stream(request),
                 media_type="text/event-stream",
diff --git a/guides/04_additional-features/07_sharing-your-app.md b/guides/04_additional-features/07_sharing-your-app.md
index 695915903..2d20424e6 100644
--- a/guides/04_additional-features/07_sharing-your-app.md
+++ b/guides/04_additional-features/07_sharing-your-app.md
@@ -176,13 +176,13 @@ This is a page that lists the endpoints that can be used to query the Gradio app
 
 ![](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/gradio-guides/view-api.png)
 
-The endpoints are automatically created when you launch a Gradio `Interface`. If you are using Gradio `Blocks`, you can also set up a Gradio API page, though we recommend that you explicitly name each event listener, such as
+The endpoints are automatically created when you launch a Gradio application. If you are using Gradio `Blocks`, you can also name each event listener, such as
 
 ```python
 btn.click(add, [num1, num2], output, api_name="addition")
 ```
 
-This will add and document the endpoint `/api/addition/` to the automatically generated API page. Otherwise, your API endpoints will appear as "unnamed" endpoints.
+This will add and document the endpoint `/addition/` to the automatically generated API page. Read more about the [API page here](./view-api-page).
 
 ## Accessing the Network Request Directly
 
diff --git a/guides/04_additional-features/14_view-api-page.md b/guides/04_additional-features/14_view-api-page.md
new file mode 100644
index 000000000..698207525
--- /dev/null
+++ b/guides/04_additional-features/14_view-api-page.md
@@ -0,0 +1,59 @@
+# API Page
+
+You can use almost any Gradio app programmatically via the built-in API! In the footer of any Gradio app, you'll see a "Use via API" link. Clicking on the link opens up a detailed documentation page for the API that Gradio generates based on the function signatures in your Gradio app.
+
+![](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/gradio-guides/view-api-animated.gif)
+
+## Configuring the API Page
+
+**API endpoint names**
+
+When you create a Gradio application, the API endpoint names are automatically generated based on the function names. You can change this by using the `api_name` parameter in `gr.Interface` or `gr.ChatInterface`. If you are using Gradio `Blocks`, you can name each event listener, like this:
+
+```python
+btn.click(add, [num1, num2], output, api_name="addition")
+```
+
+**Hiding API endpoints**
+
+When building a complex Gradio app, you might want to hide certain API endpoints from appearing on the view API page, e.g. if they correspond to functions that simply update the UI. You can set the  `show_api` parameter to `False` in any `Blocks` event listener to achieve this, e.g. 
+
+```python
+btn.click(add, [num1, num2], output, show_api=False)
+```
+
+**Disabling API endpoints**
+
+Hiding the API endpoint doesn't disable it. A user can still programmatically call the API endpoint if they know the name. If you want to disable an API endpoint altogether, set `api_name=False`, e.g. 
+
+```python
+btn.click(add, [num1, num2], output, api_name=False)
+```
+
+Note: setting an `api_name=False` also means that downstream apps will not be able to load your Gradio app using `gr.load()` as this function uses the Gradio API under the hood.
+
+
+## The Clients
+
+This API page not only lists all of the endpoints that can be used to query the Gradio app, but also shows the usage of both [the Gradio Python client](https://gradio.app/guides/getting-started-with-the-python-client/), and [the Gradio JavaScript client](https://gradio.app/guides/getting-started-with-the-js-client/). 
+
+For each endpoint, Gradio automatically generates a complete code snippet with the parameters and their types, as well as example inputs, allowing you to immediately test an endpoint. Here's an example showing an image file input and `str` output:
+
+![](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/gradio-guides/view-api-snippet.png)
+
+
+## The API Recorder 🪄
+
+Instead of reading through the view API page, you can also use Gradio's built-in API recorder to generate the relevant code snippet. Simply click on the "API Recorder" button, use your Gradio app via the UI as you would normally, and then the API Recorder will generate the code using the Clients to recreate your all of your interactions programmatically.
+
+![](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/gradio-guides/api-recorder.gif)
+
+## MCP Server
+
+The API page also includes instructions on how to use the Gradio app as an Model Context Protocol (MCP) server, which is a standardized way to expose functions as tools so that they can be used by LLMs. 
+
+![](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/gradio-guides/view-api-mcp.png)
+
+For the MCP sever, each tool, its description, and its parameters are listed, along with instructions on how to integrate with popular MCP Clients. Read more about Gradio's [MCP integration here](https://www.gradio.app/guides/building-mcp-server-with-gradio).
+
+


Test patch:
diff --git a/test/components/test_datetime.py b/test/components/test_datetime.py
index 1078126f2..147d31101 100644
--- a/test/components/test_datetime.py
+++ b/test/components/test_datetime.py
@@ -1,4 +1,4 @@
-from datetime import datetime
+from datetime import datetime, timezone
 
 import gradio as gr
 
@@ -34,6 +34,12 @@ class TestDateTime:
         assert dt.postprocess(1500000000) == "2017-07-13 19:40:00"
         assert dt2.postprocess(1500000000) == "2017-07-14 04:40:00"
 
+    def test_can_pass_datetime_value(self):
+        gr.DateTime(
+            type="datetime",
+            value=datetime.now(tz=timezone.utc),
+        )
+
     def test_in_interface(self):
         """
         Interface, process
diff --git a/test/test_routes.py b/test/test_routes.py
index 49e3f773e..8abe27984 100644
--- a/test/test_routes.py
+++ b/test/test_routes.py
@@ -75,6 +75,11 @@ class TestRoutes:
         response = test_client.get("/favicon.ico")
         assert response.status_code == 200
 
+    def test_openapi_route(self, test_client):
+        response = test_client.get(f"{API_PREFIX}/openapi.json")
+        assert response.status_code == 200
+        assert response.json()["openapi"] == "3.0.2"
+
     def test_upload_path(self, test_client):
         with open("test/test_files/alphabet.txt", "rb") as f:
             response = test_client.post(f"{API_PREFIX}/upload", files={"files": f})