I am using FastAPI with Pydantic.
My problem - I need to raise ValueError using Pydantic
from fastapi import FastAPI
from pydantic import BaseModel, validator
from fastapi import Depends, HTTPException
app = FastAPI()
class RankInput(BaseModel):
rank: int
@validator('rank')
def check_if_value_in_range(cls, v):
"""
check if input rank is within range
"""
if not 0 < v < 1000001:
raise ValueError("Rank Value Must be within range (0,1000000)")
#raise HTTPException(status_code=400, detail="Rank Value Error") - this works But I am looking for a solution using ValueError
return v
def get_info_by_rank(rank):
return rank
@app.get('/rank/{rank}')
async def get_rank(value: RankInput = Depends()):
result = get_info_by_rank(value.rank)
return result
this piece of code gives Internal Server Error
when a ValueError is raised
INFO: 127.0.0.1:59427 - "GET /info/?rank=-1 HTTP/1.1" 500 Internal Server Error
ERROR: Exception in ASGI application
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/uvicorn/protocols/http/h11_impl.py", line 396, in run_asgi
result = await app(self.scope, self.receive, self.send)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/uvicorn/middleware/proxy_headers.py", line 45, in __call__
return await self.app(scope, receive, send)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/fastapi/applications.py", line 199, in __call__
await super().__call__(scope, receive, send)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/applications.py", line 111, in __call__
await self.middleware_stack(scope, receive, send)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/middleware/errors.py", line 181, in __call__
raise exc from None
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/middleware/errors.py", line 159, in __call__
await self.app(scope, receive, _send)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/exceptions.py", line 82, in __call__
raise exc from None
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/exceptions.py", line 71, in __call__
await self.app(scope, receive, sender)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/routing.py", line 566, in __call__
await route.handle(scope, receive, send)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/routing.py", line 227, in handle
await self.app(scope, receive, send)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/routing.py", line 41, in app
response = await func(request)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/fastapi/routing.py", line 195, in app
dependency_overrides_provider=dependency_overrides_provider,
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/fastapi/dependencies/utils.py", line 550, in solve_dependencies
solved = await run_in_threadpool(call, **sub_values)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/concurrency.py", line 34, in run_in_threadpool
return await loop.run_in_executor(None, func, *args)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/concurrent/futures/thread.py", line 57, in run
result = self.fn(*self.args, **self.kwargs)
File "pydantic/main.py", line 400, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 1 validation error for GetInput
rank
ValueError() takes no keyword arguments (type=type_error)
ERROR:uvicorn.error:Exception in ASGI application
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/uvicorn/protocols/http/h11_impl.py", line 396, in run_asgi
result = await app(self.scope, self.receive, self.send)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/uvicorn/middleware/proxy_headers.py", line 45, in __call__
return await self.app(scope, receive, send)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/fastapi/applications.py", line 199, in __call__
await super().__call__(scope, receive, send)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/applications.py", line 111, in __call__
await self.middleware_stack(scope, receive, send)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/middleware/errors.py", line 181, in __call__
raise exc from None
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/middleware/errors.py", line 159, in __call__
await self.app(scope, receive, _send)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/exceptions.py", line 82, in __call__
raise exc from None
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/exceptions.py", line 71, in __call__
await self.app(scope, receive, sender)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/routing.py", line 566, in __call__
await route.handle(scope, receive, send)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/routing.py", line 227, in handle
await self.app(scope, receive, send)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/routing.py", line 41, in app
response = await func(request)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/fastapi/routing.py", line 195, in app
dependency_overrides_provider=dependency_overrides_provider,
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/fastapi/dependencies/utils.py", line 550, in solve_dependencies
solved = await run_in_threadpool(call, **sub_values)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/starlette/concurrency.py", line 34, in run_in_threadpool
return await loop.run_in_executor(None, func, *args)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/concurrent/futures/thread.py", line 57, in run
result = self.fn(*self.args, **self.kwargs)
File "pydantic/main.py", line 400, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 1 validation error for GetInput
rank
ValueError() takes no keyword arguments (type=type_error)
I also checked https://github.com/tiangolo/fastapi/issues/2180.
But I was not able to figure out a solution.
What I need to do is Raise ValueError
with a Custom Status Code.
Note - I know I can get the Job Done by raising HTTPException
.
But I am looking for a solution using ValueError
Could you tell me where I am going wrong?
Have Also Posted this Issue on Github - https://github.com/tiangolo/fastapi/issues/3761
The HTTP status code 500 is a generic error response. It means that the server encountered an unexpected condition that prevented it from fulfilling the request. This error is usually returned by the server when no other error code is suitable.
If you're not raising an HTTPException
then normally any other uncaught exception will generate a 500 response (an Internal Server Error
). If your intent is to respond with some other custom error message and HTTP status when raising a particular exception - say, ValueError
- then you can use add a global exception handler to your app:
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
@app.exception_handler(ValueError)
async def value_error_exception_handler(request: Request, exc: ValueError):
return JSONResponse(
status_code=400,
content={"message": str(exc)},
)
This will give a 400 response (or you can change the status code to whatever you like) like this:
{
"message": "Value Must be within range (0,1000000)"
}
Please note that pydantic
expects that validators raise a ValueError
, TypeError
, or AssertionError
(see docs) which pydantic
will convert into a ValidationError
.
Further, as per FastAPI's documentation:
When a request contains invalid data, FastAPI internally raises a
RequestValidationError
.
and
RequestValidationError
is a sub-class of Pydantic'sValidationError
.
The result of this is that a standard Validation
error raised during pydantic
's Model validation will be translated into a 422 Unprocessable Entity
, and the response body will contain details on why the validation failed.
(As a side note: pydantic
comes with constrained types which allow to constrain basic datatypes without having to write explicit validators.)
If the above is not satisfactory and you'd like to change the behaviour, here's how I would approach it (see here for details on the ValidationError
handling):
from fastapi import Depends, FastAPI, Request, status
from fastapi.exceptions import RequestValidationError
from fastapi.exception_handlers import request_validation_exception_handler
from fastapi.responses import JSONResponse
from pydantic import BaseModel, conint
class RankInput(BaseModel):
# Constrained integer, must be greater that or equal to 0
# and less than or equal to 1 million.
rank: conint(ge=0, le=1_000_000)
async def rank_out_of_bound_handler(request: Request, exc: RequestValidationError):
validation_errors = exc.errors()
for err in validation_errors:
# You could check for other things here as well, e.g. the error type.
if "rank" in err["loc"]:
return JSONResponse(
status_code=status.HTTP_400_BAD_REQUEST,
content={"message": "Rank must be in range [0, 1000000]."}
)
# Default response in every other case.
return await request_validation_exception_handler(request, exc)
def get_info_by_rank(rank):
return rank
app = FastAPI(
exception_handlers={RequestValidationError: rank_out_of_bound_handler},
)
@app.get('/rank/{rank}')
async def get_rank(value: RankInput = Depends()):
result = get_info_by_rank(value.rank)
return result
A call to the endpoint now gives:
$ curl -i "http://127.0.0.1:8000/rank/1"
HTTP/1.1 200 OK
date: Sat, 28 Aug 2021 20:47:58 GMT
server: uvicorn
content-length: 1
content-type: application/json
1
$ curl -i "http://127.0.0.1:8000/rank/-1"
HTTP/1.1 400 Bad Request
date: Sat, 28 Aug 2021 20:48:24 GMT
server: uvicorn
content-length: 49
content-type: application/json
{"message":"Rank must be in range [0, 1000000]."}
$ curl -i "http://127.0.0.1:8000/rank/1000001"
HTTP/1.1 400 Bad Request
date: Sat, 28 Aug 2021 20:48:51 GMT
server: uvicorn
content-length: 49
content-type: application/json
{"message":"Rank must be in range [0, 1000000]."}
If you were to add a different endpoint that uses the same model, the exception handler will automatically take care of this as well, e.g.:
@app.get('/other-rank/{rank}')
async def get_other_rank(value: RankInput = Depends()):
result = get_info_by_rank(value.rank)
return result
$ curl -i "http://127.0.0.1:8000/other-rank/-1"
HTTP/1.1 400 Bad Request
date: Sat, 28 Aug 2021 20:54:16 GMT
server: uvicorn
content-length: 49
content-type: application/json
{"message":"Rank must be in range [0, 1000000]."}
If this is not what you're looking for, could you explain why exactly you'd like to raise a ValueError
?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With