Python: ошибка FastAPI 422 с POST-запросом при отправке данных JSON
Я создаю простой 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
данные в конечную точку, которая ожидает, что user
это query
параметр, а не JSON
полезная нагрузка. Отсюда и 422 unprocessable entity
ошибка. Ниже приведены четыре различных варианта определения конечной точки для ожидания JSON
данных.
Вариант 1
Согласно документации, когда вам нужно отправить JSON
данные от клиента (скажем, браузера) в ваш API, вы отправляете их как тело запроса (через POST
запрос). Для объявления тела запроса вы можете использовать модели Pydantic.
from pydantic import BaseModel
class User(BaseModel):
user: str
@app.post('/')
def main(user: User):
return user
Вариант 2
Если кто-то не хочет использовать модели Pydantic, они также могут использовать Body parameters. Если используется единственный параметр body (как в вашем примере), вы можете использовать специальный Body параметрembed.
from fastapi import Body
@app.post('/')
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('/')
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
метод, и, следовательно, нужно await
использовать его (взгляните на этот ответ для получения более подробной информации о def
vs 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
Прямо из документации:
Параметры функции будут распознаны следующим образом:
- Если параметр также объявлен в пути, он будет использоваться как параметр path .
- Если параметр имеет единственный тип (например, int, float, str, bool и т.д.), он будет интерпретирован как параметр запроса.
- Если параметр объявлен как тип Pydantic model, он будет интерпретирован как тело запроса."
Итак, чтобы создать конечную точку 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 API из другого проекта python следующим образом
queryResponse = requests.post(URL, data= query)
Я использовал свойство data, я изменил его на json, тогда у меня все заработало
queryResponse = requests.post(URL, json = query)
Ответ 4
Если вы используете fetch
API и по-прежнему получаете необработанный объект 422, убедитесь, что вы установили заголовок Content-Type:
fetch(someURL, {
method: "POST",
headers: {
"Content-type": "application/json"
},
body
}).then(...)
В моем случае это решило проблему. На стороне сервера я использую модели Pydantic, поэтому, если вы их не используете, ознакомьтесь с ответами выше.