pythonjsonpython-3.8fastapipydantic

Obtain JSON from FastAPI using Pydantic Nested Models


The following code receives some JSON that was POSTed to a FastAPI server. FastAPI makes it available within a function as a Pydantic model. My example code processes it by writing a file. What I don't like (and it seems to be side-effect of using Pydantic List) is that I have to loop back around to get some usable JSON.

How can I do this without looping?

I feel it must be possible because return images just works.

from typing import List

from fastapi import FastAPI
from pydantic import BaseModel
import json

app = FastAPI()

class Image(BaseModel):
    url: str
    name: str

@app.post("/images/multiple/")
async def create_multiple_images(images: List[Image]):
  #return images             # returns json string
  #print(images)             # prints an Image object
  #print(images.json())      # AttributeError: 'list' object has no attribute 'json'
  #print(json.dumps(images)) # TypeError: Object of type Image is not JSON serializable
  img_data = list()          # does it really have to be this way?
  for i in images:
    img_data.append(i.dict())
  with open('./images.json', 'w') as f:  
    json.dump(img_data, f, indent=2)

'''
curl -v -d '[{"name":"wilma","url":"http://this.com"},{"name":"barney","url":"http://that.com"}]' http://localhost:8000/images/multiple/
'''

The example is expanded from the FastAPI docs


Solution

  • To dump a list of model objects without loops, pydantic provides the ability to define a model with a custom root type.

    Here is a small example of how it looks:

    class Image(BaseModel):
        url: str
        name: str
    
    
    class Images(BaseModel):
        __root__: List[Image]
    
    
    images_raw = '[{"url":"url1", "name":"name1"}, {"url":"url2", "name":"name2"}]'
    images = parse_raw_as(Images, images_raw)
    
    with open('./images.json', 'w') as f:
        f.write(images.json(indent=2))
    

    And the definition of your path operation would look like this:

    @app.post("/images/multiple/")
    async def create_multiple_images(images: Images):
        with open('./images.json', 'w') as f:
            f.write(images.json(indent=2))