Repository: gradio-app/gradio

Issue:
Update fastapi routes to use pydantic data models
There are several routes where we manually convert the request to a dictionary using e.g.:

```python
body = await request.json()
```

It is better to use FastAPI's built in support for pydantic data models, which allows automatic validation & typing.




Discussion:

Resolving patch:
diff --git a/gradio/interface.py b/gradio/interface.py
--- a/gradio/interface.py
+++ b/gradio/interface.py
@@ -28,7 +28,7 @@
 from gradio.outputs import State as o_State  # type: ignore
 from gradio.outputs import get_output_instance
 from gradio.process_examples import load_from_cache, process_example
-from gradio.routes import PredictRequest
+from gradio.routes import PredictBody
 
 if TYPE_CHECKING:  # Only import for type checking (is False at runtime).
     import flask
@@ -559,7 +559,7 @@ def run_prediction(
         else:
             return predictions
 
-    def process_api(self, data: PredictRequest, username: str = None) -> Dict[str, Any]:
+    def process_api(self, data: PredictBody, username: str = None) -> Dict[str, Any]:
         flag_index = None
         if data.example_id is not None:
             if self.cache_examples:
diff --git a/gradio/queueing.py b/gradio/queueing.py
--- a/gradio/queueing.py
+++ b/gradio/queueing.py
@@ -7,6 +7,8 @@
 
 import requests
 
+from gradio.routes import QueuePushBody
+
 DB_FILE = "gradio_queue.db"
 
 
@@ -106,8 +108,9 @@ def pop() -> Tuple[int, str, Dict, str]:
     return result[0], result[1], json.loads(result[2]), result[3]
 
 
-def push(input_data: Dict, action: str) -> Tuple[str, int]:
-    input_data = json.dumps(input_data)
+def push(body: QueuePushBody) -> Tuple[str, int]:
+    action = body.action
+    input_data = json.dumps({"data": body.data})
     hash = generate_hash()
     conn = sqlite3.connect(DB_FILE)
     c = conn.cursor()
diff --git a/gradio/routes.py b/gradio/routes.py
--- a/gradio/routes.py
+++ b/gradio/routes.py
@@ -66,13 +66,37 @@ def render(self, content: Any) -> bytes:
 ###########
 
 
-class PredictRequest(BaseModel):
+class PredictBody(BaseModel):
     session_hash: Optional[str]
     example_id: Optional[int]
-    data: Any
+    data: List[Any]
     state: Optional[Any]
 
 
+class FlagData(BaseModel):
+    input_data: List[Any]
+    output_data: List[Any]
+    flag_option: Optional[str]
+    flag_index: Optional[int]
+
+
+class FlagBody(BaseModel):
+    data: FlagData
+
+
+class InterpretBody(BaseModel):
+    data: List[Any]
+
+
+class QueueStatusBody(BaseModel):
+    hash: str
+
+
+class QueuePushBody(BaseModel):
+    action: str
+    data: Any
+
+
 ###########
 # Auth
 ###########
@@ -224,7 +248,7 @@ def api_docs(request: Request):
 
 
 @app.post("/api/predict/", dependencies=[Depends(login_check)])
-async def predict(body: PredictRequest, username: str = Depends(get_current_user)):
+async def predict(body: PredictBody, username: str = Depends(get_current_user)):
     if app.launchable.stateful:
         session_hash = body.session_hash
         state = app.state_holder.get(
@@ -247,29 +271,26 @@ async def predict(body: PredictRequest, username: str = Depends(get_current_user
 
 
 @app.post("/api/flag/", dependencies=[Depends(login_check)])
-async def flag(request: Request, username: str = Depends(get_current_user)):
+async def flag(body: FlagBody, username: str = Depends(get_current_user)):
     if app.launchable.analytics_enabled:
         await utils.log_feature_analytics(app.launchable.ip_address, "flag")
-    body = await request.json()
-    data = body["data"]
     await run_in_threadpool(
         app.launchable.flagging_callback.flag,
         app.launchable,
-        data["input_data"],
-        data["output_data"],
-        flag_option=data.get("flag_option"),
-        flag_index=data.get("flag_index"),
+        body.data.input_data,
+        body.data.output_data,
+        flag_option=body.data.flag_option,
+        flag_index=body.data.flag_index,
         username=username,
     )
     return {"success": True}
 
 
 @app.post("/api/interpret/", dependencies=[Depends(login_check)])
-async def interpret(request: Request):
+async def interpret(body: InterpretBody):
     if app.launchable.analytics_enabled:
         await utils.log_feature_analytics(app.launchable.ip_address, "interpret")
-    body = await request.json()
-    raw_input = body["data"]
+    raw_input = body.data
     interpretation_scores, alternative_outputs = await run_in_threadpool(
         app.launchable.interpret, raw_input
     )
@@ -280,18 +301,14 @@ async def interpret(request: Request):
 
 
 @app.post("/api/queue/push/", dependencies=[Depends(login_check)])
-async def queue_push(request: Request):
-    body = await request.json()
-    action = body["action"]
-    job_hash, queue_position = queueing.push(body, action)
+async def queue_push(body: QueuePushBody):
+    job_hash, queue_position = queueing.push(body)
     return {"hash": job_hash, "queue_position": queue_position}
 
 
 @app.post("/api/queue/status/", dependencies=[Depends(login_check)])
-async def queue_status(request: Request):
-    body = await request.json()
-    hash = body["hash"]
-    status, data = queueing.get_status(hash)
+async def queue_status(body: QueueStatusBody):
+    status, data = queueing.get_status(body.hash)
     return {"status": status, "data": data}
 
 


Test patch:
diff --git a/test/test_external.py b/test/test_external.py
--- a/test/test_external.py
+++ b/test/test_external.py
@@ -214,7 +214,7 @@ def test_numerical_to_label_space(self):
 
     def test_speech_recognition_model(self):
         interface_info = gr.external.load_interface(
-            "models/jonatasgrosman/wav2vec2-large-xlsr-53-english"
+            "models/facebook/wav2vec2-base-960h"
         )
         io = gr.Interface(**interface_info)
         io.api_mode = True
diff --git a/test/test_queuing.py b/test/test_queuing.py
--- a/test/test_queuing.py
+++ b/test/test_queuing.py
@@ -4,6 +4,7 @@
 import unittest
 
 from gradio import queueing
+from gradio.routes import QueuePushBody
 
 os.environ["GRADIO_ANALYTICS_ENABLED"] = "False"
 
@@ -30,9 +31,11 @@ def test_hashing(self):
         queueing.close()
 
     def test_push_pop_status(self):
-        hash1, position = queueing.push({"data": "test1"}, "predict")
+        request = QueuePushBody(data="test1", action="predict")
+        hash1, position = queueing.push(request)
         self.assertEquals(position, 0)
-        hash2, position = queueing.push({"data": "test2"}, "predict")
+        request = QueuePushBody(data="test2", action="predict")
+        hash2, position = queueing.push(request)
         self.assertEquals(position, 1)
         status, position = queueing.get_status(hash2)
         self.assertEquals(status, "QUEUED")
@@ -43,8 +46,9 @@ def test_push_pop_status(self):
         self.assertEquals(action, "predict")
 
     def test_jobs(self):
-        hash1, _ = queueing.push({"data": "test1"}, "predict")
-        hash2, position = queueing.push({"data": "test1"}, "predict")
+        request = QueuePushBody(data="test1", action="predict")
+        hash1, _ = queueing.push(request)
+        hash2, position = queueing.push(request)
         self.assertEquals(position, 1)
 
         queueing.start_job(hash1)