Fastapi: [BUG] "Depends" breaks request processing

Created on 20 Jan 2020  路  3Comments  路  Source: tiangolo/fastapi

Describe the bug

After adding a dependency to parameter without changing the data format old requests stopped working with error: "422 Unprocessable Entity"

To Reproduce

  1. Create a file "run.py" with:
import requests
from fastapi import Depends, FastAPI
from pydantic import BaseModel
app = FastAPI()

class Item(BaseModel):
    name: str
    description: str = None
    price: float
    tax: float = None

class User(BaseModel):
    username: str
    full_name: str = None

def dependant(item: Item):
    return {'username': 'username', 'full_name': 'full_name'}

@app.post("/items/{item_id}")
def update_item(item_id: int, user: User, item: Item): #User = Depends(dependant)):
    results = {"item_id": item_id, "item": item, "user": user}
    return results
  1. Create a file "run.py" with:
import requests

sample_body = {
    "item": {
        "name": "Foo",
        "description": "The pretender",
        "price": 42.0,
        "tax": 3.2
    },
    "user": {
        "username": "dave",
        "full_name": "Dave Grohl"
    }
}

print(requests.post('http://localhost:2000/items/1', json=sample_body))
  1. Run the server and the test. It works.
  2. Change
@app.post("/items/{item_id}")
def update_item(item_id: int, user: User, item: Item): #User = Depends(dependant)):
    results = {"item_id": item_id, "item": item, "user": user}
    return results

to

@app.post("/items/{item_id}")
def update_item(item_id: int, user: User, item: User = Depends(dependant)):
    results = {"item_id": item_id, "item": item, "user": user}
    return results
  1. Run the server and the test. It does not work.
  2. But I expected it to be working like before.

Expected behavior

I expected, that the change would not affect input format, because the only difference is in internal processing. Also, auto documentation suggests the same.

Screenshots

Screenshot from 2020-01-20 21-25-46
Screenshot from 2020-01-20 21-26-17

Environment

  • OS: Linux
  • FastAPI Version 0.46.0
  • Python 3.6.9
bug

Most helpful comment

@AIshutin
Hi! Well, I'm not sure that this particular case has been clearly described in the docs, but you're actually looking for this page. As described there, FastAPI will expect your model to be the root for the JSON body being sent if this model is the only model defined on the route (or dependency in your second case). In the first case, you have already defined 2 models on the same route, so FastAPI will expect them as separate fields in JSON.

You can change FastAPI behavior with Body parameter. In your case, you should be interested in the embed argument, which tells FastAPI that the model should be expected as a JSON field and not the whole body.

So, if you change your application to this, then everything should work:

from fastapi import Depends, FastAPI, Body
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    description: str = None
    price: float
    tax: float = None


class User(BaseModel):
    username: str
    full_name: str = None


def dependant(item: Item = Body(..., embed=True)):
    return {"username": "username", "full_name": "full_name"}


@app.post("/items/{item_id}")
def update_item(
    item_id: int, user: User = Body(..., embed=True), item: User = Depends(dependant)
):
    results = {"item_id": item_id, "item": item, "user": user}
    return results

All 3 comments

@AIshutin
Hi! Well, I'm not sure that this particular case has been clearly described in the docs, but you're actually looking for this page. As described there, FastAPI will expect your model to be the root for the JSON body being sent if this model is the only model defined on the route (or dependency in your second case). In the first case, you have already defined 2 models on the same route, so FastAPI will expect them as separate fields in JSON.

You can change FastAPI behavior with Body parameter. In your case, you should be interested in the embed argument, which tells FastAPI that the model should be expected as a JSON field and not the whole body.

So, if you change your application to this, then everything should work:

from fastapi import Depends, FastAPI, Body
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    description: str = None
    price: float
    tax: float = None


class User(BaseModel):
    username: str
    full_name: str = None


def dependant(item: Item = Body(..., embed=True)):
    return {"username": "username", "full_name": "full_name"}


@app.post("/items/{item_id}")
def update_item(
    item_id: int, user: User = Body(..., embed=True), item: User = Depends(dependant)
):
    results = {"item_id": item_id, "item": item, "user": user}
    return results

Big thanks! You helped a lot.

Thanks for the help here @nsidnev ! :cake: :bowing_man:

Thanks @AIshutin for reporting back and closing the issue :+1:

Was this page helpful?
0 / 5 - 0 ratings