如何在模型中引发 HTTP 400?

Raj*_*eer 3 python python-3.x pydantic fastapi

我的路线:

@router.get('/check/{value}', status_code=200)
def ranks_check(value: BasicInput = Depends()):
    """
    Test endpoint
    """

    return value
Run Code Online (Sandbox Code Playgroud)

我的型号:

class BasicInput:
    """
    Get Confidence to score Input class
    """

    value: int

    @validator('value')
    def check_if_value_in_range(cls, v):
        if not 0 < v < 1000001:
            raise ValueError('Value Exceeded Limit')
Run Code Online (Sandbox Code Playgroud)

我需要做什么:

我需要验证输入并在出现 ValueError 时引发 HTTP 400。

我知道我可以使用 Pydantic 的类型完成整数验证,并在路由函数本身中Field进行运行。check_if_value_in_range我正在寻找使用该模型的解决方案。

Gin*_*pin 9

请参阅 FastAPI 文档中有关在代码中引发 HTTPException 的内容

HTTPException是一个普通的 Python 异常,带有与 API 相关的附加数据。

因为它是 Python 异常,所以你不return它,你raise它。

这也意味着,如果您位于在路径操作函数内部调用的实用程序函数内,并且从该实用程序函数内部引发,则它不会运行路径操作函数HTTPException中的其余代码,它将立即终止该请求并将 HTTP 错误从 发送到客户端。HTTPException

from fastapi.exceptions import HTTPException

class BasicInput(BaseModel):
    value: int

    @validator("value")
    def check_if_value_in_range(cls, v):
        if not 0 < v < 1000001:
            # raise ValueError("Value Exceeded Limit")
            raise HTTPException(status_code=400, detail="value exceeded limit")

        return v
Run Code Online (Sandbox Code Playgroud)
from fastapi.exceptions import HTTPException

class BasicInput(BaseModel):
    value: int

    @validator("value")
    def check_if_value_in_range(cls, v):
        if not 0 < v < 1000001:
            # raise ValueError("Value Exceeded Limit")
            raise HTTPException(status_code=400, detail="value exceeded limit")

        return v
Run Code Online (Sandbox Code Playgroud)

但要使其发挥作用,当前代码中存在一些问题需要修复:

  1. 如果您使用 Pydantic 的验证器装饰器,那么您的类需要是 PydanticBaseModelPydanticdataclass

    class BasicInput(BaseModel):  # <--------------------
        value: int
    
    Run Code Online (Sandbox Code Playgroud)

    如果您还在函数print上添加语句或断点validator,您会发现它实际上从未被调用,因为它不是 Pydantic BaseModel

  2. 验证器函数缺少对条件ifFalse(当v在范围内有效时)的情况的处理。来自 Pydantic 文档:

    验证器应该返回解析的值或引发ValueErrorTypeErrorAssertionError(assert可以使用语句)。

    class BasicInput(BaseModel):
        value: int
    
        @validator("value")
        def check_if_value_in_range(cls, v):
            if not 0 < v < 1000001:
                raise ValueError("Value Exceeded Limit")
    
            # v is good
            return v  # <--------------------
    
    Run Code Online (Sandbox Code Playgroud)

在您的原始代码中,您提到您没有得到“什么”。我假设您收到的{}有效和无效响应均为空value

$ curl -i -XGET localhost:8000/check/1000002
HTTP/1.1 400 Bad Request
...

{"detail":"value exceeded limit"}

$ curl -i -XGET localhost:8000/check/42
HTTP/1.1 200 OK
...

{"value":42}
Run Code Online (Sandbox Code Playgroud)

这是因为在路由函数中,valueBasicInput类,它不是{value}路径值或BasicInput.value整数值

@router.get("/check/{value}", status_code=200)
def ranks_check(value: BasicInput = Depends()):
    print(type(value))  # <class 'main.BasicInput'>
    return value
Run Code Online (Sandbox Code Playgroud)

实际上得到的“空”响应是 FastAPI 将其jsonable_encoder应用于该类的结果BasicInput。当 FastAPI 到达return value您的路由函数时,它将用于jsonable_encoder将其转换为JSONResponse. 请参阅直接返回响应的文档。

在内部,由于BasicInput不是 PydanticBaseModel或可迭代对象(例如类似dict对象),因此它会导致空 dict {}。(您可以检查的代码jsonable_encoder,但这是因为dict(obj)vars(obj))。

因此,正如MatsLindh 的回答中提到的,FastAPI 请求/响应上下文中使用的模型通常是 Pydantic 的子类BaseModel,在正确子类化之后,您现在应该得到非空响应:

class BasicInput(BaseModel):  # <--------------------
    value: int
Run Code Online (Sandbox Code Playgroud)

修复此问题后,只需按照 FastAPI 的在代码中引发 HTTPException 中的指南(正如我在本答案开头提到的那样)即可直接从模型引发 HTTP 错误。

但我同意MatsLindh 的评论,这不是一个“好的”做法,因为它违反了SRP /单一责任原则。您的模型正在做两件事:验证您的输入引发适当的 HTTP 错误响应。

FastAPI 应该处理您的请求和响应,而 Pydantic 应该代表您的模型和数据。您让 FastAPI 接受请求,将其传递给 Pydantic 以验证并存储模型数据,然后让 FastAPI 将结果转换为适当的响应。您预期的解决方案也令人困惑,因为对于有效/成功的情况,它是在路由函数中处理的,但对于无效/错误的情况,它是在模型验证函数中处理的。

FastAPI 已经知道如何捕获 PydanticValidationError并将其转换为适当的 HTTP 错误响应。在这种情况下,FastAPI 返回 HTTP 500 内部服务器错误。

如果目的是提供特定于模型的错误响应,您应该raise ValueError在模型上保留 ,然后覆盖 FastAPI 的默认验证错误处理程序。请参阅“覆盖默认异常处理程序”部分。

这是我推荐的代码:

from fastapi import APIRouter, Depends, FastAPI
from fastapi.exceptions import ValidationError
from fastapi.responses import JSONResponse
from pydantic import BaseModel, validator

# MODELS

class BasicInput(BaseModel):
    value: int

    @validator("value")
    def check_if_value_in_range(cls, v):
        if not 0 < v < 1000001:
            raise ValueError("Value Exceeded Limit")

        return v

# VIEWS

api = FastAPI()
router = APIRouter()

@api.exception_handler(ValidationError)
async def validation_exception_handler(request, exc: ValidationError):
    return JSONResponse(status_code=400, content={"error": str(exc)})

@router.get("/check/{value}")
def ranks_check(value: BasicInput = Depends()):
    return value

api.include_router(router)
Run Code Online (Sandbox Code Playgroud)
class BasicInput(BaseModel):
    value: int

    @validator("value")
    def check_if_value_in_range(cls, v):
        if not 0 < v < 1000001:
            raise ValueError("Value Exceeded Limit")

        # v is good
        return v  # <--------------------
Run Code Online (Sandbox Code Playgroud)