Разница FastAPI между `json.dumps()` и `JSONResponse()`

Я изучаю FastAPI, и он работает на моем рабочем столе Docker в Windows. Вот мой main.py, который успешно развернут в Docker:

#main.py
import fastapi
import json
from fastapi.responses import JSONResponse

app = fastapi.FastAPI()

@app.get('/api/get_weights1')
async def get_weights1():
    weights = {'aa': 10, 'bb': 20}
    return json.dumps(weights)

@app.get('/api/get_weights2')
async def get_weights2():
    weights = {'aa': 10, 'bb': 20}
    return JSONResponse(content=weights, status_code=200)

И у меня есть простой файл Python get_weights.py для запросов к этим двум API:

#get_weights.py
import requests
import json

resp = requests.get('http://127.0.0.1:8000/api/get_weights1')
print('ok', resp.status_code)
if resp.status_code == 200:
    print(resp.json())

resp = requests.get('http://127.0.0.1:8000/api/get_weights2')
print('ok', resp.status_code)
if resp.status_code == 200:
    print(resp.json())

Я получаю одинаковые ответы от двух API, вывод:

ok 200
{"aa": 10, "bb": 20}
ok 200
{'aa': 10, 'bb': 20}

Ответ кажется одинаковым, использую ли я json.dumps() или JSONResponse(). Я прочитал документацию FastAPI по JSONResponse, но у меня остались следующие вопросы:

Могу ли я узнать, есть ли разница между двумя методами?

Если есть разница, какой метод рекомендуется (и почему?)?

Подводя итог: не используйте ни то, ни другое, просто верните объект, похожий на словарь, или с помощью orm_mode=True в вашей модели ответа объект, который поддерживает поиск атрибутов (например, строку SQLAlchemy). Используйте response_model в декораторе маршрута или укажите тип возвращаемого значения для функции, которая определяет, как вы хотите сериализовать ответ.

MatsLindh 21.04.2023 10:50

Спасибо, ответ от @Matija ясен!

perpetualstudent 21.04.2023 17:16
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
2
109
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

В FastAPI вы можете создать ответ тремя разными способами (от самого лаконичного до самого гибкого):

1

return dict # Or model or ...

В документах, на которые вы ссылаетесь, мы видим, что FastAPI автоматически преобразует это dict в строку и завернет в JSONResponse. Этот способ наиболее лаконичен и охватывает большинство вариантов использования.

2

Однако иногда вам приходится возвращать собственные заголовки (например, REMOTE-USER=username) или другой код состояния (возможно, 201 — Создано или 202 — Принято). В этом случае вам нужно использовать JSONResponse.

return JSONResponse(content=dict) # Here we need to have dict.

Проблема в том, что теперь, если у нас нет простого словаря, мы должны использовать jsonable_encoder(some_model) # -> dict, чтобы получить его. Так что это немного более многословно. Доступные варианты смотрите в документации Starlette, так как FastAPI просто реэкспортирует ее.

Более сложный пример:

return JSONResponse(content=jsonable_encoder(some_model), status_code=201, headers = {"REMOTE-USER": username}) 

3

Наконец, вам не нужно возвращать json — вы также можете вернуть csv, html или любой другой тип файла. В этом случае мы должны использовать Response и указать media_type. Также используйте Документы Starlette.

return Response('Hello, world!', media_type='text/plain')

Суммируя

Обратите внимание, что в документации Fastapi указано:

Когда вы возвращаете ответ напрямую, его данные не проверяются, не преобразуются (сериализуются) и не документируются автоматически.

Итак, мы видим, в чем разница: первый метод имеет хорошую интеграцию с другими функциями FastAPI, поэтому его всегда следует отдавать предпочтение. Используйте второй вариант, только если вам нужно предоставить пользовательские заголовки или коды состояния. Наконец, используйте третий вариант, только если вы хотите вернуть что-то, что не является json.

Я экспериментировал с несколькими вариантами и нашел следующее...

(1) Оба метода не могут сериализовать объект даты и времени. Например, если веса:

weights = {'aa': 10, 'bb': 20, 'date': datetime.date.today()}

тогда оба метода будут иметь одинаковый возвращаемый статус и ошибку:

500 Внутренняя ошибка сервера
TypeError: объект типа date не сериализуем JSON

Чтобы преодолеть это использование

return json.dumps(weights, default=str)

и

from fastapi.encoders import jsonable_encoder
#blah
return JSONResponse(content=jsonable_encoder(weights), status_code=200)

(2) Я также экспериментировал с возвратом простого dict как есть, особенно если в нем есть объект datetime. Как уже упоминал @Matija, FastAPI автоматически преобразует это dict в строку и завершит его в ответ. Например:

@app.get('/api/get_weights1')
async def get_weights1():
    weights = {'aa': 10, 'bb': 20, 'date': datetime.date.today()}
    return weights    #<--- this is dict

Выход:

ok 200
{"aa": 10, "bb": 20, "date": "2023-04-21"}

(3) Как уже упоминал @Matija, метод JSONResponse() позволяет настраивать возвращаемый ответ. Например, статус ответа можно настроить как 201 (вместо 200). А также различные типы возвращаемых объектов. Это, вероятно, преимущество использования этого метода перед методом json.dumps(). Например:

#main.py
@app.get('/api/get_weights2')
async def get_weights2():
    weights = {'aa': 10, 'bb': 20}
    return JSONResponse(content=weights, status_code=201)    #<--201

#get_weights.py
resp = requests.get('http://127.0.0.1:8000/api/get_weights2')
print('ok', resp.status_code)
if resp.status_code == 201:    #<---201
    print(resp.json())

Тот же вывод, что и раньше:

ok 200
{'aa': 10, 'bb': 20}

Боюсь, вы ошибаетесь насчёт JSONResponse и json.dumps(), а также предположения о том, как работает FastAPI/Starlette под капотом. Если вы нашли время, чтобы взглянуть на ссылку, указанную в разделе комментариев выше, на ваши вопросы будут даны ответы (поскольку ваш вопрос, по сути, дублирует). Ответ @Matija также не очень точен, так как существует более трех способов вернуть Response (например, StreamingResponse, HTMLResponse и т. д.), а также можно использовать другие (более быстрые) кодировщики JSON, чем стандартная json библиотека. Все описано в связанном ответе выше

Chris 21.04.2023 17:42

спасибо, Крис, я бы повнимательнее прочитал, как работает FastAPI/Starlette!

perpetualstudent 21.04.2023 17:51

Пожалуйста, ознакомьтесь с приведенной выше ссылкой. Вы также можете найти этот ответ (см. Вариант 1) полезным.

Chris 21.04.2023 18:08

новая ссылка (вариант 1) понятнее, я понял, спасибо!

perpetualstudent 21.04.2023 18:14

Окей, сделано! Ваше здоровье!

perpetualstudent 21.04.2023 18:17

Другие вопросы по теме