【问题标题】:FastAPI - Pydantic - Value Error Raises Internal Server ErrorFastAPI - Pydantic - 值错误引发内部服务器错误
【发布时间】:2021-10-25 02:21:15
【问题描述】:

我在 Pydantic 中使用 FastAPI。

我的问题 - 我需要使用 Pydantic 引发 ValueError

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

当引发 ValueError 时,这段代码会给出Internal Server Error

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)

我还检查了https://github.com/tiangolo/fastapi/issues/2180

但我无法找到解决方案。

我需要做的是使用自定义状态代码引发 ValueError

注意 - 我知道我可以通过提高 HTTPException 来完成工作。

但我正在寻找使用ValueError的解决方案

你能告诉我哪里出错了吗?

在 Github 上也发布了这个问题 - https://github.com/tiangolo/fastapi/issues/3761

【问题讨论】:

  • ValueError 是 Python 中的基本异常类之一。这没有任何功能来处理状态代码。 (但是,你可以通过继承同一个类来做类似的事情)
  • 提高ValueError 显然会导致Internal Server Error,我不明白你为什么要这样
  • 我使用的是 HTTPException 本身。我被“要求”使用 ValueError。
  • 这太奇怪了,因为我希望这会被植入到 pydantic 验证中:S

标签: python python-3.x fastapi pydantic


【解决方案1】:

请注意,pydantic 预计验证器会引发 ValueErrorTypeErrorAssertionError(请参阅 docs),其中 pydantic 将转换为 ValidationError

进一步,根据FastAPI's documentation

当请求包含无效数据时,FastAPI 会在内部引发 RequestValidationError

and

RequestValidationError 是 Pydantic 的 ValidationError 的子类。

这样做的结果是在pydantic 的模型验证期间引发的标准Validation 错误将被转换为422 Unprocessable Entity,并且响应正文将包含有关验证失败原因的详细信息。

(附带说明:pydantic 附带 constrained types,它允许约束基本数据类型而无需编写显式验证器。)

如果上述情况不令人满意,并且您想更改行为,我将采用以下方法(有关 ValidationError 处理的详细信息,请参阅 here):

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):
    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

现在对端点的调用给出:

$ 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]."}

如果您要添加使用相同模型的不同端点,异常处理程序也会自动处理此问题,例如:

@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]."}

如果这不是你要找的,你能解释一下你为什么要提出ValueError吗?

【讨论】:

  • 这太奇怪了,因为我希望这会被植入到 pydantic 验证中:S
【解决方案2】:

如果您没有引发 HTTPException,那么通常任何其他未捕获的异常都会生成 500 响应(Internal Server Error)。如果您的意图是在引发特定异常时使用其他一些自定义错误消息和 HTTP 状态进行响应(例如,ValueError),那么您可以使用向您的应用添加全局异常处理程序:

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)},
    )

这将给出 400 响应(或者您可以将状态代码更改为您喜欢的任何内容),如下所示:

{
    "message": "Value Must be within range (0,1000000)"
}

【讨论】:

  • 感谢您的回答。我正在寻找的是可以在模型中使用的东西。
  • 模型是如何使用的?它是您路线中的类型提示吗?或者您是否在函数体中使用它(例如通过 parse_obj())?您可以通过更新您的问题来分享一个示例吗?如果它在正文中,那么您可以在那里捕获 ValueError 并返回有效响应(或引发 HTTPException),否则可能没有解决方案,因为模型引发的任何异常都将引发到 FastAPI 并且没有异常处理程序它将响应500。你不能从模型返回响应,你只能从你的路由函数、异常处理程序或一些中间件中做到这一点。
  • 好的,谢谢,您的模型被用作依赖项。 DI 系统将处理从模型验证器引发的 HTTPException,但其他任何事情都会导致设计为 500。 see here 如果您希望您的应用程序以自定义方式从模型响应而不使用 HTTPException,我知道的唯一方法是引发自定义异常并向返回自定义响应的应用程序添加自定义异常处理程序。但是,您不能直接从模型返回响应。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-28
  • 2014-10-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多