Python:发送 JSON 数据时出现 POST 请求的 FastAPI 错误 422

Python: FastAPI error 422 with POST request when sending JSON data

提问人:Smith 提问时间:1/27/2020 最后编辑:ChrisSmith 更新时间:8/25/2023 访问量:179080

问:

我正在构建一个简单的 API 来测试数据库。当我使用request时,一切正常,但是如果我更改为,我就会出错。GETPOST422 Unprocessable Entity

以下是 FastAPI 代码:

from fastapi import FastAPI

app = FastAPI()

@app.post("/")
def main(user):
    return user

然后,我使用 JavaScript 的请求

let axios = require('axios')

data = { 
    user: 'smith' 
}

axios.post('http://localhost:8000', data)
    .then(response => (console.log(response.url)))

另外,使用 Python :requests

import requests

url = 'http://127.0.0.1:8000'
data = {'user': 'Smith'}

response = requests.post(url, json=data)
print(response.text)

我还尝试解析为 JSON,使用 编码并更改标头,但没有任何效果。utf-8

javascript python axios python-requests fastapi

评论


答:

55赞 michaeloliver 1/28/2020 #1

直接来自文档

函数参数将按如下方式识别:

  • 如果该参数也在路径中声明,则该参数将用作路径参数。
  • 如果参数是单数类型(如 int、float、str、bool 等),它将被解释为查询参数。
  • 如果参数被声明为 Pydantic 模型的类型,它将被解释为请求正文

因此,要创建一个接收带有用户字段的正文的 POST 端点,您可以执行如下操作:

from fastapi import FastAPI
from pydantic import BaseModel


app = FastAPI()


class Data(BaseModel):
    user: str


@app.post("/")
def main(data: Data):
    return data
4赞 Avinash Ravi 6/16/2020 #2

对于要接收请求正文的 POST 请求,您需要执行以下操作

创建 Pydantic 基础模型用户

from pydantic import BaseModel

class User(BaseModel):
    user_name: str


@app.post("/")
def main(user: User):
   return user
5赞 Yagiz Degirmenci 7/26/2020 #3

FastAPI 基于 Python 类型提示,因此当您传递查询参数时,它接受对,您需要以某种方式声明它。

即使是这样的东西也会起作用

from typing import Dict, Any
...
@app.post("/")
def main(user: Dict[Any, Any] = None):
    return user

Out: {"user":"Smith"}

但是使用Pydantic方式更有效

class User(BaseModel):
    user: str

@app.post("/")
def main(user: User):
    return user

Out: {"user":"Smith"}
49赞 Sunil Garg 7/15/2021 #4

就我而言,我是像这样从不同的 python 项目调用 python API

queryResponse = requests.post(URL, data= query)

我正在使用data属性,我将其更改为json,然后它对我有用

queryResponse = requests.post(URL, json = query)

评论

1赞 Mark Seagoe 11/11/2021
谢谢!这也让我的工作......但我仍然希望我知道为什么通过数据传递字典(如帮助文件指示)会导致 422 错误状态代码。
1赞 Mark Seagoe 11/11/2021
我读到数据字段是用于 FormData 格式的......它似乎是一个用于传递 HTML 表单数据的 javascript 类。github.com/tiangolo/fastapi/issues/3373
1赞 Alsushi 3/16/2022
哇 422 不可处理的实体毕竟是一个数据格式问题。错误代码和消息不明确。
1赞 Yong 12/29/2021 #5

就我而言,我的 FastAPI 端点需要表单数据而不是 JSON。因此,解决方法是发送表单数据而不是JSON。(注意:对于 node-js,FormData 不可用,可以使用 form-data

52赞 Chris 1/9/2022 #6

具有 () 状态代码的响应将具有指定错误消息的响应正文,准确告知请求的哪一部分缺失或与预期格式不匹配。您提供的代码片段显示,您正在尝试将数据发布到期望为参数(而不是有效负载)的端点。因此,错误。下面提供了四个不同的选项,说明如何定义一个期望数据的终结点。422unprocessable entityJSONuserqueryJSON422 unprocessable entityJSON

选项 1

根据文档,当您需要将数据从客户端(例如浏览器)发送到您的 API 时,您可以将其作为请求正文(通过请求)发送。要声明请求正文,可以使用 Pydantic 模型。JSONPOST

from pydantic import BaseModel

class User(BaseModel):
    user: str

@app.post('/')
def main(user: User):
    return user

选项 2

如果不想使用 Pydantic 模型,他们也可以使用 Body 参数。如果使用单个 body 参数(如示例中所示),则可以使用特殊的 Body 参数 embed

from fastapi import Body

@app.post('/')
def main(user: str = Body(..., embed=True)):
    return {'user': user}

选项 3

另一种(不太推荐的)方法是使用类型(或简单地在 Python 3.9+ 中)来声明一对。但是,这样一来,您就不能像使用 Pydantic 模型或字段那样对预期的各种属性使用自定义验证(例如,检查电子邮件地址是否有效,或者字符串是否遵循特定模式)。Dictdictkey:valueJSONBody

from typing import Dict, Any

@app.post('/')
def main(payload: Dict[Any, Any]): 
    return payload

在上面的例子中,也可以定义为 ,或简单地定义为 。payloadpayload: dict[Any, Any]payload: dict

选项 4

如果您确信传入的数据是有效的,则可以直接使用 Starlette 的 Request 对象来获取解析为 的请求正文,使用 await request.json()。但是,使用这种方法,您不仅不能对属性使用自定义验证,而且还需要使用 定义端点,因为 是一种方法,因此需要它(请查看此答案以获取有关 vs 的更多详细信息)。JSONJSONasync defrequest.json()asyncawaitdefasync def

from fastapi import Request

@app.post('/')
async def main(request: Request): 
    return await request.json()

如果您愿意,还可以在尝试解析数据之前对请求标头值进行一些检查,类似于此答案。但是,仅仅因为请求在标头中显示,并不总是意味着这是真的,或者传入的数据是有效的(即,可能缺少大括号,具有没有值的键等)。因此,您可以在尝试解析数据时使用块,以防数据格式化方式出现问题。Content-Typeapplication/jsonContent-TypeJSONtry-exceptJSONDecodeErrorJSON

from fastapi import Request, HTTPException
from json import JSONDecodeError

@app.post('/')
async def main(request: Request):
    content_type = request.headers.get('Content-Type')
    
    if content_type is None:
        raise HTTPException(status_code=400, detail='No Content-Type provided')
    elif content_type == 'application/json':
        try:
            return await request.json()
        except JSONDecodeError:
            raise HTTPException(status_code=400, detail='Invalid JSON data')
    else:
        raise HTTPException(status_code=400, detail='Content-Type not supported')

如果您希望端点同时接受特定/预定义和任意 JSON 数据,请查看此答案

测试上述选项

使用 Python 请求库

相关答案可以在这里找到。

import requests

url = 'http://127.0.0.1:8000/'
payload ={'user': 'foo'}
resp = requests.post(url=url, json=payload)
print(resp.json())

使用 JavaScript Fetch API

相关答案也可以在这里这里找到。对于使用 的示例,请查看此答案,以及此答案和此答案axios

fetch('/', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({'user': 'foo'})
    })
    .then(resp => resp.json()) // or, resp.text(), etc
    .then(data => {
        console.log(data); // handle response data
    })
    .catch(error => {
        console.error(error);
    });
17赞 Alan 1/16/2022 #7

如果您使用的是 fetch API,但仍获取 422 Unprocessable Entity,请确保已设置 Content-Type 标头:

fetch(someURL, {
  method: "POST",
  headers: {
    "Content-type": "application/json"
  },
  body
}).then(...)

这解决了我的问题。在服务器端,我使用的是 Pydantic 模型,所以如果你没有使用这些模型,请参阅上面的答案。

评论

1赞 David W. 2/11/2022
值得一提的是,直到我将 fastapi 从 0.63 版更新到 0.70 版,我才开始遇到问题。我把头撞在墙上,直到我看到你的回应。我最初使用的是jquery,其中“type”设置为“json”。我更改了我的保存函数,以使用如上所述设置了 Content-type 的 fetch api,问题解决了!
1赞 Jorangutang 4/26/2022 #8

(如果不是如上所述的语法错误) 收到来自帖子请求的响应 422 的原因可能有很多。

可以通过以下方式复制这一点:

  • 编辑你的身体结构
  • 更改身体类型(发送任何字符串)
  • 更改/删除标头内容类型

我通常如何调试它如下:

  1. 如果您使用的是 FastAPI,请使用内置的“/docs”路由在本地主机上进行测试,如果帖子在那里失败,则可能是语法/逻辑错误,与您的帖子路由无关。FastAPI 的这个特性非常有用。注意 发布请求不需要/期望 UI 上的标题,因为它为您提供了一个文本位置来填充它。

  2. 测试在可变体端点上进行:您可以像这样进行设置:

@app.post('/test')
async def function(objectName: dict = Body(...)):

发送包含任何 JSON 的请求,如果仍然收到 422,则转到下一步。

  1. 确保您的标头内容类型正确无误,最常见的是:
headers = {'Content-Type': 'application/json'};
0赞 xinthose 8/5/2022 #9

对我来说,问题是我的 POST 正文不包含端点正在寻找的所有属性:

发布

{
    "gateway": "",
    "nameservers": [],
    "deleteWiFi": true,
    "ssidPassword": ""
}

FastAPI 蟒蛇

class SubmitWiFi(BaseModel):
    gateway: str
    addresses: list[str]    # missing
    nameservers: list[str]
    deleteWiFi: bool
    ssid: str   # missing
    ssidPassword: str

@app.post("/submitWiFi")
async def submitWiFi(data: SubmitWiFi):
    # my code

这不是一个描述性很强的错误,很难找到原因。

0赞 spaceofmiah 2/3/2023 #10

我在使用 FastAPI 进行用户身份验证时遇到 - “POST /login HTTP/1.1” 422 Unprocessable Entity 错误。此问题是因为我如何从客户端捕获身份验证数据。我将分享解决方案以及我做错了什么

溶液
from fastapi.security import OAuth2PasswordBearer
from fastapi.security import OAuth2PasswordRequestForm
from fastapi import Depends

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="login")

@app.post('/login', response_model=Dict)
def login(
        payload: OAuth2PasswordRequestForm = Depends(),   # <--- here's all that matters
        session: Session = Depends(get_db)
    ):
    ### login logic

就我而言,我没有使用 ,而是使用一个 UserScheme 来捕获并声明它是函数签名的 Body 参数,即OAuth2PasswordRequestFormusernamepassword

@app.post('/login', response_model=Dict)
def login(
        payload: UserSchema = Body()
       ....
       ....
)

以上并非完全错误。当我按原样使用登录端点时,它确实工作得很好。

Build with FastAPI - authorize buttons

在尝试使用“授权”按钮进行身份验证时,该按钮使用端点(因为这是作为 tokenUrl 传递的内容),这时我收到不可处理的实体错误login

希望这会有所帮助

编辑 1 ( 添加更多上下文 )

1赞 Max Barrass 2/16/2023 #11

此错误似乎是由于 origin 在 FastAPI 完成之前挂起的结果。

我从 Java 调用 FastAPI,但它返回得太早了。

为了修复这个错误,我添加了 use of 和 using function,然后调用 promise。CompletableFuture<String>HTTPClient.sendAsyncCompletableFuture.get

0赞 Airenas 8/25/2023 #12

简短的回答:错误表示传递了错误的内容类型。

当我将 FastAPI 从 0.61.2 升级到 0.101.1 时,我开始从 curl 收到这些错误。我的命令是这样的:

curl -i -X POST http://localhost:8000/model -d '{"text":"o"}'

原来,新的 FastAPI 需要指定内容类型:

curl -i -X POST http://localhost:8000/model -H "Content-type: application/json" -d '{"text":"o"}'