A way to handle multiple request content types #7786
Replies: 19 comments 1 reply
-
A single request can read a JSON body or (exclusive or) form data. That doesn't depend on FastAPI but more on how the web (HTTP) works. So there's no straightforward way to do it using the same normal methods. Nevertheless, you can access the request object directly, and extract the information from it by hand: https://fastapi.tiangolo.com/advanced/using-request-directly/ |
Beta Was this translation helpful? Give feedback.
-
Mmm... sorry for inaccuracy, I mean to define two content-types somehow, to see them in acceptable types in |
Beta Was this translation helpful? Give feedback.
-
Reference: https://swagger.io/docs/specification/describing-request-body/ |
Beta Was this translation helpful? Give feedback.
-
@phy25 Is it possible to achieve multiple request body for same endpoint? |
Beta Was this translation helpful? Give feedback.
-
yes, but you should decode it by yourself. Small example below:
|
Beta Was this translation helpful? Give feedback.
-
@evstratbg In this approach, content-type will be multipart-formdata right? |
Beta Was this translation helpful? Give feedback.
-
Yes |
Beta Was this translation helpful? Give feedback.
-
@evstratbg there is no limitations like that in http protocol. I have implemented in other frameworks like flask, spring, etc.. OpenAPI spec also supports multiple content type for one endpoint. |
Beta Was this translation helpful? Give feedback.
-
Using multipart? Can you post a small example of what you implemented? |
Beta Was this translation helpful? Give feedback.
-
Not using multipart. I use content-type to accept application/json or application/xml or application/pdf... I'll try to post you same with other framework. Thank you |
Beta Was this translation helpful? Give feedback.
-
Hi, I'm also interested in being able to define a single route that can consume either:
At the moment, the only way I can see to do this is to get the raw request, inspect the content-type and then handle it accordingly. |
Beta Was this translation helpful? Give feedback.
-
Thanks for the help here everyone! 🚀 Yeah, the way to support different content types on the same path operation would be to read the request directly and parse it depending on the content type. But then you have to do the data validation and documentation in OpenAPI yourself, in your code. There wouldn't be an easy way to do it more automatically based on type annotations as the body has to be consumed from the ASGI messages, and it would be a different process for each media type. What I would try to do is to have a different path for each media type, that way I can enforce validation for the JSON or form data while reading the content directly for other complex types. |
Beta Was this translation helpful? Give feedback.
-
Assuming the original issue was solved, it will be automatically closed now. But feel free to add more comments or create new issues. |
Beta Was this translation helpful? Give feedback.
-
I have a similar problem when integrating third party backends like filebrowser.org. In Django REST Framework Content Negotiation is accomplished using "Renderers", i.e. a way to convert the response to different output languages. |
Beta Was this translation helpful? Give feedback.
-
Finally I used my own simple media renderers which do a simplistic approach to process the "Accept" header and return a response on given renders (json, xml, plain text) |
Beta Was this translation helpful? Give feedback.
-
sounds like it can't be done without fighting the framework but here's how I thought it might work. You create a normal BaseModel class for json and some other class for multipart like FormModel. The FormModel class has attributes defined similarly to how they would be passed in ie. (Form, File, UploadFile) then you have duplicate routes defined in a router where one takes the BaseModel and the other takes the FormModel as the request object. Then in the docs you have a single route with either json or multipart. I think the first part wouldn't be terribly hard to do but I don't know how much of a hurdle it would be to have that duplicate route work correctly |
Beta Was this translation helpful? Give feedback.
-
Would it be possible in the documentation for me to inform the two possibilities of media type? |
Beta Was this translation helpful? Give feedback.
-
Please give an example without validation of how this can be implemented manually. I don't want to use different body types AT THE SAME TIME. I want to use application/json OR multipart/form-data. There are no HTTP protocol restrictions here. I just want to accept Request and if the request body is application/json then process one algorithm, but if the request body is multipart/form-data then process another algorithm. Please give a simple example of how this can be implemented manually. Writing validation is not a problem for me, but I don't understand how to make the request accept different body types with FastAPI / Swagger. |
Beta Was this translation helpful? Give feedback.
-
I have encountered this problem myself. As a solution to this problem, you can implement some request body handler. from fastapi import Request
from pydantic import BaseModel
class RequestPydanticParser:
def __init__(self,
base_model: Type[BaseModel],
query_content: bool = True,
json_content: bool = True,
form_content: bool = True,
):
self.base_model = base_model
self.query_content = query_content
self.json_content = json_content
self.form_content = form_content
async def __call__(self, request: Request):
# Getting data from query parameters (if there is no 'Content-Type')
if 'Content-Type' not in request.headers or request.headers['Content-Type'] is None:
if self.query_content:
model_params = dict(request.query_params)
else:
raise ValueError('Query parameters are not supported')
# Getting data from JSON
elif self.json_content and request.headers['Content-Type'] == 'application/json':
model_params = await request.json()
# Getting data from the form
elif self.form_content and request.headers['Content-Type'].startswith('multipart/form-data'):
model_params = await request.form()
elif self.form_content and request.headers['Content-Type'] == 'application/x-www-form-urlencoded':
model_params = await request.form()
# Raising error that the current content type is not supported
else:
raise ValueError(f"Content type as '{request.headers['Content-Type']}' is not supported")
# Filtering out the parameters received from the body
available_model_params = self.base_model.model_fields.keys()
model_params = {
key: val for key, val in model_params.items() if key in available_model_params
}
# Building and validation model
items = self.base_model.model_validate(model_params)
return items P.S. The resulting implementation is a bit cumbersome and can be improved. Then the route itself can be organized as follows. from pydantic import BaseModel
class Items(BaseModel):
x: int
name: str
desc: Optional[str] = None
@app.post(
"/test",
openapi_extra={
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": Items.model_json_schema(),
},
"application/json": {
"schema": Items.model_json_schema(),
},
},
"required": True,
},
},
)
async def test(request: Request):
try:
request_parser = RequestPydanticParser(Items)
items = await request_parser(request)
except ValidationError as e:
detail = e.errors(include_url=False)
return JSONResponse({"detail": detail}, status_code=422)
except ValueError as e:
data = {
"message": f"{e}",
}
return JSONResponse(data, status_code=400)
return items.model_dump_json() |
Beta Was this translation helpful? Give feedback.
-
OpenAPI allows describing multiple accepted content-types for a request.
Is it possible via FastAPI means?
I'd like to make a password login endpoint accepting both
application/json
andapplication/x-www-form-urlencoded
using a single handler.Beta Was this translation helpful? Give feedback.
All reactions