How to specify the response_model in FastAPI on a non-default return?

Viewed 58

I have the following route:

# 201 is the response from a creation
# 409 if it already exists
# The server SHOULD generate a payload that includes enough information for a user to recognize the source of the conflict.
@app.post("/users", status_code=status.HTTP_201_CREATED, response_model=schemas.UserResponse)
def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):

    # hash the password -- user.password
    user.password = utils.hash(user.password)
    new_user = models.User(**user.dict()) # get or create? -- https://stackoverflow.com/a/6078058/651174
    db.add(new_user)
    try:
        db.commit()
    except sqlalchemy.exc.IntegrityError:
        db.rollback()
        existing_user = db.query(models.User).filter(models.User.email == user.email).first()
        raise HTTPException(status.HTTP_409_CONFLICT, detail=existing_user)

    db.refresh(new_user) # the same as doing `RETURNING *`
    return new_user

The user object returns and is encoded fine when it doesn't hit the exception. However, when it does go through the exception, and executes:

raise HTTPException(status.HTTP_409_CONFLICT, detail=existing_user)

I get the following error:

TypeError: Object of type User is not JSON serializable

Is there a way to basically encode all responses using the model I specify in FastAPI?

1 Answers

The reason that your new_user object is returned successfully when the request doesn't hit the exception is because, as explained in the documentation:

By default, FastAPI would automatically convert that return value to JSON using the jsonable_encoder explained in JSON Compatible Encoder. Then, behind the scenes, it would put that JSON-compatible data (e.g. a dict) inside of a JSONResponse that would be used to send the response to the client.

However, when raising an HTTPException, which would actually return a JSONResponse directly without converting the data you passed into JSON (you can check that through http_exception_handler), you would first need to serialise the existing_user object/model using either json.dumps() from json module, or FastAPI's jsonable_encoder(). As described in the documentation:

You cannot put a Pydantic model in a JSONResponse without first converting it to a dict with all the data types (like datetime, UUID, etc) converted to JSON-compatible types.

Hence, that should be:

from fastapi.encoders import jsonable_encoder

raise HTTPException (detail=jsonable_encoder(existing_user), status_code=status.HTTP_409_CONFLICT)
Related