Python:发送 JSON 数据时,FastAPI 出现 POST 请求错误 422
- 2024-11-25 08:49:00
- admin 原创
- 605
问题描述:
我正在构建一个简单的 API 来测试数据库。当我使用GET
请求时,一切都正常,但如果我更改为POST
,就会出现422 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
,并更改标题,但对我而言都不起作用。
解决方案 1:
具有状态代码的响应422
(即Unprocessable entity
)将具有指定错误消息的响应主体,准确说明请求的哪一部分缺失或与预期格式不匹配。您提供的代码片段显示您正尝试将数据发布到期望作为参数而不是有效负载的JSON
端点。因此,出现错误。下面给出了四种不同的选项,介绍如何定义端点以期望数据,以及用于测试它们的 Python 和 JavaScript HTTP 客户端。user
`queryJSON
422 Unprocessable entity`JSON
选项 1
根据文档,当您需要JSON
从客户端(例如浏览器)将数据发送到 API 时,可以将其作为请求主体发送(通过POST
请求)。要声明请求主体,您可以使用Pydantic模型。
from fastapi import FastAPI
from pydantic import BaseModel
class Base(BaseModel):
user: str
app = FastAPI()
@app.post('/')
async def main(base: Base):
return base
选项 2
如果不想使用 Pydantic 模型,也可以使用Body参数。如果使用单个 body 参数(如您的示例所示),则可以使用特殊的Body参数embed。
from fastapi import Body
@app.post('/')
async def main(user: str = Body(..., embed=True)):
return {'user': user}
选项 3
另一种(不太推荐)方法是使用Dict
类型(或简单地dict
在 Python 3.9+ 中)声明一key:value
对。但是,通过这种方式,您无法JSON
像使用 Pydantic 模型或Body
字段那样对预期中的各种属性使用自定义验证(例如,检查电子邮件地址是否有效,或者字符串是否遵循特定模式)。
from typing import Dict, Any
@app.post('/')
async def main(payload: Dict[Any, Any]):
return payload
在上面的例子中,payload
也可以定义为payload: dict[Any, Any]
,或者简称payload: dict
。
选项 4
如果您确信传入的数据是有效的JSON
,则可以使用Starlette 的Request
对象直接将请求主体解析为JSON
,使用await request.json()
。但是,使用这种方法,您不仅不能对属性使用自定义验证,而且还需要使用定义端点async def
,因为request.json()
是一种async
方法,因此需要它(有关vs的更多详细信息,await
请查看此答案)。def
`async def`
from fastapi import Request
@app.post('/')
async def main(request: Request):
return await request.json()
如果您愿意,您还可以Content-Type
在尝试解析数据之前对请求标头值进行一些检查,类似于此答案。但是,仅仅因为请求application/json
在Content-Type
标头中说明,并不总是意味着这是真的,或者传入的数据是有效的JSON
(即,可能缺少花括号,没有值的键等)。因此,try-except
当您尝试解析数据时,可以使用一个块,让您处理任何问题,以防数据格式化JSONDecodeError
的方式出现问题。JSON
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);
});
解决方案 2:
直接来自文档:
函数参数将被识别如下:
如果该参数也在路径中声明,它将被用作路径参数。
如果参数是单一类型(如 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
解决方案 3:
就我而言,我从不同的 Python 项目调用 Python API,如下所示
queryResponse = requests.post(URL, data= query)
我使用的是 data 属性,我将其更改为 json,然后它对我有用
queryResponse = requests.post(URL, json = query)
解决方案 4:
如果您正在使用fetch
API 并仍然收到422 Unprocessable Entity,请确保已设置Content-Type标头:
fetch(someURL, {
method: "POST",
headers: {
"Content-type": "application/json"
},
body
}).then(...)
这解决了我的问题。在服务器端,我使用的是 Pydantic 模型,因此如果您没有使用这些模型,请参阅上述答案。
解决方案 5:
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"}
解决方案 6:
对于接收请求主体的 POST 请求,您需要执行以下操作
创建一个 Pydantic 基础模型用户
from pydantic import BaseModel
class User(BaseModel):
user_name: str
@app.post("/")
def main(user: User):
return user
解决方案 7:
就我的情况而言,我的 FastAPI 端点需要的是表单数据而不是 JSON。因此,解决方法是发送表单数据而不是 JSON。(注意:对于 node-js,FormData 不可用,可以使用表单数据)
解决方案 8:
(如果不是像上面那样的语法错误)从 post 请求收到响应 422 的原因可能有很多。
可以通过以下方式复制:
编辑你的身体结构
改变你的身体类型(发送任意字符串)
更改/删除标题内容类型
我通常的调试方法如下:
如果您使用的是 FastAPI,请使用内置的“/docs”路由在本地主机上进行测试,如果帖子在那里失败,则可能是语法/逻辑错误,与您的帖子路由无关。FastAPI 的这个功能非常有用。注意 Post 请求不需要/不需要 UI 上的标头,因为它为您提供了一个文本位置来填写它。
测试是在变量主体端点上进行的:您可以像这样设置:
@app.post('/test')
async def function(objectName: dict = Body(...)):
发送带有任何 JSON 的请求,如果仍然收到 422,则转到下一步。
确保标题内容类型正确,最常见的是:
headers = {'Content-Type': 'application/json'};
解决方案 9:
该错误似乎是由于在 FastAPI 完成之前源就挂断了而导致的。
我从 Java 调用 FastAPI 但返回得太早了。
为了修复这个错误,我添加了用法CompletableFuture<String>
和使用HTTPClient.sendAsync
函数,然后调用CompletableFuture.get
了承诺。
解决方案 10:
对我来说,问题是我的 POST 主体不包含端点正在寻找的所有属性:
邮政
{
"gateway": "",
"nameservers": [],
"deleteWiFi": true,
"ssidPassword": ""
}
FastAPI Python
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
这不是一个描述性很强的错误,很难找到原因。
解决方案 11:
我在使用 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
在我的例子中,我没有使用OAuth2PasswordRequestForm
,而是使用了 UserScheme 来捕获username
并password
在函数签名中将其声明为 Body 参数,即
@app.post('/login', response_model=Dict)
def login(
payload: UserSchema = Body()
....
....
)
以上内容并非完全错误。当我按原样使用登录端点时,它确实运行良好。
当我尝试使用授权按钮进行身份验证时,该按钮使用login
终端底层(因为这是作为 tokenUrl 传递的),这时我得到了无法处理的实体错误
希望这有帮助
编辑 1(添加更多上下文)
解决方案 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"}'
解决方案 13:
在你的 FastAPI 代码中,你只需要定义你想要接受为请求主体的用户变量。为此,你只需要在你的 FastAPI 代码中做一些小的更改:
from fastapi import Body, FastAPI
app = FastAPI()
@app.post("/")
def main(user = Body(...)):
return user
并且你的 javascript 代码不需要任何更改:
let axios = require('axios')
data = {
user: 'smith'
}
axios.post('http://localhost:8000', data)
.then(response => (console.log(response)))