The body of an HTTP PUT request is a JSON list - like this:
[item1, item2, item3, ...]
I can't change this. (If the root was a JSON object rather than a list there would be no problem.)
Using FastAPI I seem to be unable to access this content in the normal way:
@router.put('/data')
def set_data(data: DataModel): # This doesn't work; how do I even declare DataModel?
I found the following workaround, which seems like a very ugly hack:
class DataModel(BaseModel):
__root__: List[str]
from fastAPI import Request
@router.put('/data')
async def set_data(request: Request): # Get the request object directly
data = DataModel(__root__=await request.json())
This surely can't be the 'approved' way to achieve this. I've scoured the documentation both of FastAPI and pydantic. What am I missing?
Not only does the HTTP spec allow body data with GET request, but this is also common practice: The popular ElasticSearch engine's _search API recommends GET requests with the query attached in a JSON body. As a concession to incomplete HTTP client implementations, it also allows POST requests here.
Yes. In other words, any HTTP request message is allowed to contain a message body, and thus must parse messages with that in mind. Server semantics for GET, however, are restricted such that a body, if any, has no semantic meaning to the request.
In this post, we will learn how to use FastAPI Request Body. We will use Pydantic BaseModel class to create our own class that will act as a request body. When we need to send some data from client to API, we send it as a request body. In other words, a request body is data sent by client to server.
In FastAPI, you derive from BaseModel to describe the data models you send and receive (i.e. FastAPI also parses for you from a body and translates to Python objects). Also, it relies on the modeling and processing from pydantic.
If the parameter is declared to be of the type of a Pydantic model, it will be interpreted as a request body. FastAPI will know that the value of q is not required because of the default value = None.
So, you can declare deeply nested JSON "objects" with specific attribute names, types and validations. All that, arbitrarily nested. For example, we can define an Image model: And then we can use it as the type of an attribute: This would mean that FastAPI would expect a body similar to: Again, doing just that declaration, with FastAPI you get:
In FastAPI, you derive from BaseModel
to describe the data models you send and receive (i.e. FastAPI also parses for you from a body and translates to Python objects). Also, it relies on the modeling and processing from pydantic
.
from typing import List
from pydantic import BaseModel
class Item(BaseModel):
name: str
class ItemList(BaseModel):
items: List[Item]
def process_item_list(items: ItemList):
pass
This example would be able to parse JSON like
{"items": [{"name": "John"}, {"name": "Mary"}]}
In your case - depending on what shape your list entries have - you'd also go for proper type modeling, but you want to directly receive and process the list without the JSON dict wrapper around it. You could go for:
from typing import List
from pydantic import BaseModel
class Item(BaseModel):
name: str
def process_item_list(items: List[Item]):
pass
Which is now able to process JSON like:
[{"name": "John"}, {"name": "Mary"}]
This is probably what you're looking for and the last adaption to take is depending on the shape of your item* in the list you receive. If it's plain strings, you can also go for:
from typing import List
def process_item_list(items: List[str]):
pass
Which could process JSON like
["John", "Mary"]
I outlined the path from models down to primitives in lists because I think it's worth knowing where this can go if one needs more complexity in the data models.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With