In our app there is a view that accepts an instance of a model as an argument, and if the request data misses some fields, the view does not get called, eg:
class Item(BaseModel):
id: int
price: float
is_offer: bool | None = False
@app.post("/")
async def hello_root(item: Item):
return dict(item)
This was fine for quite a while, but now we need to add the item to the database even if some of the fields are missing, but we still need to be able to tell that the item is invalid so we don't do some other logic with it.
The problem is that if the item is invalid, the view does not get called at all. Also, we can't replace item: Item
with item: dict
in the view function signature for historic reasons.
I tried adding a custom exception handler, but then it applies for all the views and I would have to figure out which view would have been called, and then reuse some logic from this particular one, and getting the item data is not that straightforward either:
@app.exception_handler(RequestValidationError)
async def req_validation_handler(request, exc):
print("We got an error")
...
My other idea was to create some sort of a custom field that could be nullable, but at the same time have a flag as to whether it is required or not which could be checked inside our view, but I still haven't figured out how to do that.
Is there a proper way of doing this?
One way would be to define item
as dict
/Body
/Form
type, or use the Request
object directly (and call await request.json()
)—as demonstrated here and here—and perform the pydantic model validation check in a dependency function/class yourself, as described in Method 3 of this answer, which would allow you to tell whether or not the Item
is valid and handle it as desired.
Given that you may not prefer the approach in Option 1, as you may need to stick to the Pydantic model definition in your endpoint (as mentioned in the question), you could either use a separate router (with optionally a custom APIRoute class, in case you would like to handle this before the request reaches the endpoint), or a sub-application, and to that router or sub-app, add only the endpoint for which you would like to handle such ValidationError
exceptions—using, for instance, @router.post('/')
or @subapi.post('/')
(examples of both options can be found here and here). Once you do that, use a custom exception handler for that router/sub-app (to handle exceptions being raised for the endpoint you added earlier), and catch/handle such exceptions occuring as desired—working examples can be found here, as well as here and here.