4

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

2
  • You are not returning anything here ? Commented Nov 22, 2020 at 19:05
  • You are dealing with a list of items, how someone could possibly retrieve any data from a list without looping (assuming he doesn't know the index) Commented Nov 23, 2020 at 3:40

1 Answer 1

5

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))
Sign up to request clarification or add additional context in comments.

1 Comment

Perfect. I had tried defining a parent 'class Images' exactly as you have shown. But I didn't know about the custom root type. This was the missing piece of syntax I was looking for. Thanks Alex!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.