The design of my system consists of two parts:
- There is a FastAPI webserver interface which wraps some core logic
- There is a class which contains all of the core logic, let’s call it
KeyValueStore
for this example
The core logic can raise exceptions. I am not sure how I should be handling these in the FastAPI layer.
Let’s say that the core logic is as simple as a data structure which stores key-value pairs:
class KeyValueStore():
def __init__(self):
self.data = {}
def put(self, key, value):
if key in self.data:
raise RuntimeError(f'duplicate key {key}')
self.data[key] = value
def get(self, key):
# doesn't really matter what the implementation is
As you can see, if KeyValueStore.put()
is called with a key which has previously been used, this results in an error of time RuntimeError
being raised.
This is caught in the FastAPI layer. The code looks something like this:
from fastapi import FastAPI
from fastapi.responses import JSONResponse
from fastapi import status
from pydantic import BaseModel
class FastAPI_KeyValuePair(BaseModel):
key: str
value: str
class FastAPI_ReturnStatus(BaseModel):
status: str
message: str|None = None
@app.post('/put_key_value')
def put_key_value(key_value_pair: FastAPI_KeyValuePair):
try:
key_value_store.put(
key_value_pair.key,
key_value_pair.value,
)
return FastAPI_ReturnStatus(status='success', message=None)
except RuntimeError as error:
return JSONResponse(
status_code=status.HTTP_409_CONFLICT,
content=FastAPI_ReturnStatus(
status='error',
message=str(error),
)
)
In this particular case, I want to return a 409 Conflict status code, but I wasn’t sure how to do this while also returning some JSON body.
A normal return statement looks something like this:
return FastAPI_ReturnStatus(status='success', message=None)
If I understand correctly, FastAPI will serialize this type to JSON using json.dumps
. This serialized content will be returned as the response body. In addition, the status code is automatically set to 200.
What I wanted to do was set this status code to something else, such as 409. I couldn’t find a way to do that, except by using JSONResponse
:
return JSONResponse(
status_code=status.HTTP_409_CONFLICT,
content=FastAPI_ReturnStatus(
status='error',
message=str(error),
)
)
I do not know if this is really the right approach. I just chose to use JSONResponse
as there didn’t seem to be any other way to return a JSON body, and set an error status code (in this case, 409).
What actually happens with this code is that I get an exception:
TypeError: Object of type FastAPI_ReturnStatus is not JSON serializable
This confuses me, because this type is JSON serializable. It must be, because the line:
return FastAPI_ReturnStatus(status='success', message=None)
works.
Why is FastAPI_ReturnStatus
not serializable in this context, but is serializable if the return type is FastAPI_ReturnStatus
(not nested in some other object)?
2
Answers
You can set a custom status code by including the default response in the list of parameters to your controller function:
Since this is the response object that will be used unless you return a custom
Response
object yourself, the status code will now be409
if an error occurs instead of the default200
.First, I would highly suggest having a look at this answer and this answer, which would help you understand how FastAPI deals with responses (and serialisation, behind the scenes), and hence, understand the nature of the error.
As for returning a JSON response that includes a Pydantic
BaseModel
and a customstatus_code
(again, please have a look at the linked answers above for more details), one could choose between the following options.Option 1
Use the
Response
object to change thestatus_code
, before returning the model as is.Option 2
Return a
JSONResponse
, after serialising the model using either FastAPI’sjsonable_encoder
or other (likely faster) JSON encoders, as described in this answer.Option 3
Alternatively, you could raise an
HTTPException
as follows.One could also create a custom
Response
class, as demonstrated in this answer.