提问人:Smith 提问时间:1/27/2020 最后编辑:ChrisSmith 更新时间:8/25/2023 访问量:179080
Python:发送 JSON 数据时出现 POST 请求的 FastAPI 错误 422
Python: FastAPI error 422 with POST request when sending JSON data
问:
我正在构建一个简单的 API 来测试数据库。当我使用request时,一切正常,但是如果我更改为,我就会出错。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
答:
直接来自文档:
函数参数将按如下方式识别:
- 如果该参数也在路径中声明,则该参数将用作路径参数。
- 如果参数是单数类型(如 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
对于要接收请求正文的 POST 请求,您需要执行以下操作
创建 Pydantic 基础模型用户
from pydantic import BaseModel
class User(BaseModel):
user_name: str
@app.post("/")
def main(user: User):
return user
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"}
就我而言,我是像这样从不同的 python 项目调用 python API
queryResponse = requests.post(URL, data= query)
我正在使用data属性,我将其更改为json,然后它对我有用
queryResponse = requests.post(URL, json = query)
评论
就我而言,我的 FastAPI 端点需要表单数据而不是 JSON。因此,解决方法是发送表单数据而不是JSON。(注意:对于 node-js,FormData 不可用,可以使用 form-data)
具有 () 状态代码的响应将具有指定错误消息的响应正文,准确告知请求的哪一部分缺失或与预期格式不匹配。您提供的代码片段显示,您正在尝试将数据发布到期望为参数(而不是有效负载)的端点。因此,错误。下面提供了四个不同的选项,说明如何定义一个期望数据的终结点。422
unprocessable entity
JSON
user
query
JSON
422 unprocessable entity
JSON
选项 1
根据文档,当您需要将数据从客户端(例如浏览器)发送到您的 API 时,您可以将其作为请求正文(通过请求)发送。要声明请求正文,可以使用 Pydantic 模型。JSON
POST
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 模型或字段那样对预期的各种属性使用自定义验证(例如,检查电子邮件地址是否有效,或者字符串是否遵循特定模式)。Dict
dict
key:value
JSON
Body
from typing import Dict, Any
@app.post('/')
def main(payload: Dict[Any, Any]):
return payload
在上面的例子中,也可以定义为 ,或简单地定义为 。payload
payload: dict[Any, Any]
payload: dict
选项 4
如果您确信传入的数据是有效的,则可以直接使用 Starlette 的 Request
对象来获取解析为 的请求正文,使用 await request.json()。
但是,使用这种方法,您不仅不能对属性使用自定义验证,而且还需要使用 定义端点,因为 是一种方法,因此需要它(请查看此答案以获取有关 vs 的更多详细信息)。JSON
JSON
async def
request.json()
async
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);
});
如果您使用的是 fetch
API,但仍获取 422 Unprocessable Entity,请确保已设置 Content-Type 标头:
fetch(someURL, {
method: "POST",
headers: {
"Content-type": "application/json"
},
body
}).then(...)
这解决了我的问题。在服务器端,我使用的是 Pydantic 模型,所以如果你没有使用这些模型,请参阅上面的答案。
评论
(如果不是如上所述的语法错误) 收到来自帖子请求的响应 422 的原因可能有很多。
可以通过以下方式复制这一点:
- 编辑你的身体结构
- 更改身体类型(发送任何字符串)
- 更改/删除标头内容类型
我通常如何调试它如下:
如果您使用的是 FastAPI,请使用内置的“/docs”路由在本地主机上进行测试,如果帖子在那里失败,则可能是语法/逻辑错误,与您的帖子路由无关。FastAPI 的这个特性非常有用。注意 发布请求不需要/期望 UI 上的标题,因为它为您提供了一个文本位置来填充它。
测试在可变体端点上进行:您可以像这样进行设置:
@app.post('/test')
async def function(objectName: dict = Body(...)):
发送包含任何 JSON 的请求,如果仍然收到 422,则转到下一步。
- 确保您的标头内容类型正确无误,最常见的是:
headers = {'Content-Type': 'application/json'};
对我来说,问题是我的 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
这不是一个描述性很强的错误,很难找到原因。
我在使用 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 参数,即OAuth2PasswordRequestForm
username
password
@app.post('/login', response_model=Dict)
def login(
payload: UserSchema = Body()
....
....
)
以上并非完全错误。当我按原样使用登录端点时,它确实工作得很好。
在尝试使用“授权”按钮进行身份验证时,该按钮使用端点(因为这是作为 tokenUrl 传递的内容),这时我收到不可处理的实体错误login
希望这会有所帮助
编辑 1 ( 添加更多上下文 )
此错误似乎是由于 origin 在 FastAPI 完成之前挂起的结果。
我从 Java 调用 FastAPI,但它返回得太早了。
为了修复这个错误,我添加了 use of 和 using function,然后调用 promise。CompletableFuture<String>
HTTPClient.sendAsync
CompletableFuture.get
简短的回答:错误表示传递了错误的内容类型。
当我将 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"}'
评论