Кастомные ответы — HTML, поток, файл и другие¶
По умолчанию FastAPI возвращает ответы с помощью JSONResponse
.
Вы можете переопределить это, вернув Response
напрямую, как показано в разделе Вернуть Response напрямую.
Но если вы возвращаете Response
напрямую (или любой его подкласс, например JSONResponse
), данные не будут автоматически преобразованы (даже если вы объявили response_model
), и документация не будет автоматически сгенерирована (например, со специфичным «типом содержимого» в HTTP-заголовке Content-Type
как частью сгенерированного OpenAPI).
Но вы можете также объявить Response
, который хотите использовать (например, любой подкласс Response
), в декораторе операции пути, используя параметр response_class
.
Содержимое, которое вы возвращаете из своей функции-обработчика пути, будет помещено внутрь этого Response
.
И если у этого Response
тип содержимого JSON (application/json
), как в случае с JSONResponse
и UJSONResponse
, данные, которые вы возвращаете, будут автоматически преобразованы (и отфильтрованы) любым объявленным вами в декораторе операции пути Pydantic response_model
.
Примечание
Если вы используете класс ответа без типа содержимого, FastAPI будет ожидать, что у вашего ответа нет содержимого, поэтому он не будет документировать формат ответа в сгенерированной документации OpenAPI.
Используйте ORJSONResponse
¶
Например, если вы выжимаете максимум производительности, вы можете установить и использовать orjson
и задать ответ как ORJSONResponse
.
Импортируйте класс (подкласс) Response
, который вы хотите использовать, и объявите его в декораторе операции пути.
Для больших ответов возвращать Response
напрямую значительно быстрее, чем возвращать словарь.
Это потому, что по умолчанию FastAPI проверяет каждый элемент внутри и убеждается, что он сериализуем в JSON, используя тот же JSON Compatible Encoder, объяснённый в руководстве. Это позволяет возвращать произвольные объекты, например модели из базы данных.
Но если вы уверены, что содержимое, которое вы возвращаете, сериализуемо в JSON, вы можете передать его напрямую в класс ответа и избежать дополнительных накладных расходов, которые FastAPI понёс бы, пропуская возвращаемое содержимое через jsonable_encoder
перед передачей в класс ответа.
from fastapi import FastAPI
from fastapi.responses import ORJSONResponse
app = FastAPI()
@app.get("/items/", response_class=ORJSONResponse)
async def read_items():
return ORJSONResponse([{"item_id": "Foo"}])
Информация
Параметр response_class
также используется для указания «типа содержимого» ответа.
В этом случае HTTP-заголовок Content-Type
будет установлен в application/json
.
И это будет задокументировано как таковое в OpenAPI.
Совет
ORJSONResponse
доступен только в FastAPI, а не в Starlette.
HTML-ответ¶
Чтобы вернуть ответ с HTML напрямую из FastAPI, используйте HTMLResponse
.
- Импортируйте
HTMLResponse
. - Передайте
HTMLResponse
в параметрresponse_class
вашего декоратора операции пути.
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
app = FastAPI()
@app.get("/items/", response_class=HTMLResponse)
async def read_items():
return """
<html>
<head>
<title>Some HTML in here</title>
</head>
<body>
<h1>Look ma! HTML!</h1>
</body>
</html>
"""
Информация
Параметр response_class
также используется для указания «типа содержимого» ответа.
В этом случае HTTP-заголовок Content-Type
будет установлен в text/html
.
И это будет задокументировано как таковое в OpenAPI.
Вернуть Response
¶
Как показано в разделе Вернуть Response напрямую, вы также можете переопределить ответ прямо в своей операции пути, просто вернув его.
Тот же пример сверху, возвращающий HTMLResponse
, может выглядеть так:
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
app = FastAPI()
@app.get("/items/")
async def read_items():
html_content = """
<html>
<head>
<title>Some HTML in here</title>
</head>
<body>
<h1>Look ma! HTML!</h1>
</body>
</html>
"""
return HTMLResponse(content=html_content, status_code=200)
Предупреждение
Response
, возвращённый напрямую вашей функцией-обработчиком пути, не будет задокументирован в OpenAPI (например, Content-Type
нне будет задокументирова) и не будет виден в автоматически сгенерированной интерактивной документации.
Информация
Разумеется, фактические заголовок Content-Type
, статус-код и т.д. возьмутся из объекта Response
, который вы вернули.
Задокументировать в OpenAPI и переопределить Response
¶
Если вы хотите переопределить ответ внутри функции, но при этом задокументировать «тип содержимого» в OpenAPI, вы можете использовать параметр response_class
И вернуть объект Response
.
Тогда response_class
будет использоваться только для документирования операции пути в OpenAPI, а ваш Response
будет использован как есть.
Вернуть HTMLResponse
напрямую¶
Например, это может быть что-то вроде:
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
app = FastAPI()
def generate_html_response():
html_content = """
<html>
<head>
<title>Some HTML in here</title>
</head>
<body>
<h1>Look ma! HTML!</h1>
</body>
</html>
"""
return HTMLResponse(content=html_content, status_code=200)
@app.get("/items/", response_class=HTMLResponse)
async def read_items():
return generate_html_response()
В этом примере функция generate_html_response()
уже генерирует и возвращает Response
вместо возврата HTML в str
.
Возвращая результат вызова generate_html_response()
, вы уже возвращаете Response
, который переопределит поведение FastAPI по умолчанию.
Но поскольку вы также передали HTMLResponse
в response_class
, FastAPI будет знать, как задокументировать это в OpenAPI и интерактивной документации как HTML с text/html
:
Доступные ответы¶
Ниже перечислены некоторые доступные классы ответов.
Учтите, что вы можете использовать Response
, чтобы вернуть что угодно ещё, или даже создать собственный подкласс.
Технические детали
Вы также могли бы использовать from starlette.responses import HTMLResponse
.
FastAPI предоставляет те же starlette.responses
как fastapi.responses
для вашего удобства как разработчика. Но большинство доступных классов ответов приходят непосредственно из Starlette.
Response
¶
Базовый класс Response
, от него наследуются все остальные ответы.
Его можно возвращать напрямую.
Он принимает следующие параметры:
content
—str
илиbytes
.status_code
— целое число, HTTP статус-код.headers
— словарь строк.media_type
— строка, задающая тип содержимого. Например,"text/html"
.
FastAPI (фактически Starlette) автоматически добавит заголовок Content-Length. Также будет добавлен заголовок Content-Type, основанный на media_type
и с добавлением charset для текстовых типов.
from fastapi import FastAPI, Response
app = FastAPI()
@app.get("/legacy/")
def get_legacy_data():
data = """<?xml version="1.0"?>
<shampoo>
<Header>
Apply shampoo here.
</Header>
<Body>
You'll have to use soap here.
</Body>
</shampoo>
"""
return Response(content=data, media_type="application/xml")
HTMLResponse
¶
Принимает текст или байты и возвращает HTML-ответ, как описано выше.
PlainTextResponse
¶
Принимает текст или байты и возвращает ответ в виде простого текста.
from fastapi import FastAPI
from fastapi.responses import PlainTextResponse
app = FastAPI()
@app.get("/", response_class=PlainTextResponse)
async def main():
return "Hello World"
JSONResponse
¶
Принимает данные и возвращает ответ, кодированный как application/json
.
Это ответ по умолчанию, используемый в FastAPI, как было сказано выше.
ORJSONResponse
¶
Быстрая альтернативная реализация JSON-ответа с использованием orjson
, как было сказано выше.
Информация
Требуется установка orjson
, например командой pip install orjson
.
UJSONResponse
¶
Альтернативная реализация JSON-ответа с использованием ujson
.
Информация
Требуется установка ujson
, например командой pip install ujson
.
Предупреждение
ujson
менее аккуратен, чем встроенная реализация Python, в обработке некоторых крайних случаев.
from fastapi import FastAPI
from fastapi.responses import UJSONResponse
app = FastAPI()
@app.get("/items/", response_class=UJSONResponse)
async def read_items():
return [{"item_id": "Foo"}]
Совет
Возможно, ORJSONResponse
окажется более быстрым вариантом.
RedirectResponse
¶
Возвращает HTTP-редирект. По умолчанию использует статус-код 307 (Temporary Redirect — временное перенаправление).
Вы можете вернуть RedirectResponse
напрямую:
from fastapi import FastAPI
from fastapi.responses import RedirectResponse
app = FastAPI()
@app.get("/typer")
async def redirect_typer():
return RedirectResponse("https://typer.tiangolo.com")
Или можно использовать его в параметре response_class
:
from fastapi import FastAPI
from fastapi.responses import RedirectResponse
app = FastAPI()
@app.get("/fastapi", response_class=RedirectResponse)
async def redirect_fastapi():
return "https://fastapi.tiangolo.com"
Если вы сделаете так, то сможете возвращать URL напрямую из своей функции-обработчика пути.
В этом случае будет использован статус-код по умолчанию для RedirectResponse
, то есть 307
.
Также вы можете использовать параметр status_code
в сочетании с параметром response_class
:
from fastapi import FastAPI
from fastapi.responses import RedirectResponse
app = FastAPI()
@app.get("/pydantic", response_class=RedirectResponse, status_code=302)
async def redirect_pydantic():
return "https://docs.pydantic.dev/"
StreamingResponse
¶
Принимает асинхронный генератор или обычный генератор/итератор и отправляет тело ответа потоково.
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
app = FastAPI()
async def fake_video_streamer():
for i in range(10):
yield b"some fake video bytes"
@app.get("/")
async def main():
return StreamingResponse(fake_video_streamer())
Использование StreamingResponse
с файлоподобными объектами¶
Если у вас есть файлоподобный объект (например, объект, возвращаемый open()
), вы можете создать функцию-генератор для итерации по этому файлоподобному объекту.
Таким образом, вам не нужно сначала читать всё в память, вы можете передать эту функцию-генератор в StreamingResponse
и вернуть его.
Это включает многие библиотеки для работы с облачным хранилищем, обработки видео и т.д.
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
some_file_path = "large-video-file.mp4"
app = FastAPI()
@app.get("/")
def main():
def iterfile(): # (1)
with open(some_file_path, mode="rb") as file_like: # (2)
yield from file_like # (3)
return StreamingResponse(iterfile(), media_type="video/mp4")
- Это функция-генератор. Она является «функцией-генератором», потому что содержит оператор(ы)
yield
внутри. - Используя блок
with
, мы гарантируем, что файлоподобный объект будет закрыт после завершения работы функции-генератора. То есть после того, как она закончит отправку ответа. - Этот
yield from
говорит функции итерироваться по объекту с именемfile_like
. И затем, для каждой итерации, отдавать эту часть как исходящую из этой функции-генератора (iterfile
).
Таким образом, это функция-генератор, которая внутренне передаёт работу по «генерации» чему-то другому.
Делая это таким образом, мы можем поместить её в блок with
и тем самым гарантировать, что файлоподобный объект будет закрыт после завершения.
Совет
Заметьте, что здесь мы используем стандартный open()
, который не поддерживает async
и await
, поэтому объявляем операцию пути обычной def
.
FileResponse
¶
Асинхронно отправляет файл как ответ.
Для создания экземпляра принимает иной набор аргументов, чем другие типы ответов:
path
— путь к файлу, который будет отправлен.headers
— любые дополнительные заголовки для включения, в виде словаря.media_type
— строка, задающая тип содержимого. Если не задан, для определения типа содержимого будет использовано имя файла или путь.filename
— если задан, будет включён в заголовок ответаContent-Disposition
.
Файловые ответы будут содержать соответствующие заголовки Content-Length
, Last-Modified
и ETag
.
from fastapi import FastAPI
from fastapi.responses import FileResponse
some_file_path = "large-video-file.mp4"
app = FastAPI()
@app.get("/")
async def main():
return FileResponse(some_file_path)
Вы также можете использовать параметр response_class
:
from fastapi import FastAPI
from fastapi.responses import FileResponse
some_file_path = "large-video-file.mp4"
app = FastAPI()
@app.get("/", response_class=FileResponse)
async def main():
return some_file_path
В этом случае вы можете возвращать путь к файлу напрямую из своей функции-обработчика пути.
Пользовательский класс ответа¶
Вы можете создать собственный класс ответа, унаследовавшись от Response
, и использовать его.
Например, предположим, что вы хотите использовать orjson
, но с некоторыми пользовательскими настройками, которые не используются во встроенном классе ORJSONResponse
.
Скажем, вы хотите, чтобы возвращался отформатированный JSON с отступами, то есть хотите использовать опцию orjson orjson.OPT_INDENT_2
.
Вы могли бы создать CustomORJSONResponse
. Главное, что вам нужно сделать — реализовать метод Response.render(content)
, который возвращает содержимое как bytes
:
from typing import Any
import orjson
from fastapi import FastAPI, Response
app = FastAPI()
class CustomORJSONResponse(Response):
media_type = "application/json"
def render(self, content: Any) -> bytes:
assert orjson is not None, "orjson must be installed"
return orjson.dumps(content, option=orjson.OPT_INDENT_2)
@app.get("/", response_class=CustomORJSONResponse)
async def main():
return {"message": "Hello World"}
Теперь вместо того, чтобы возвращать:
{"message": "Hello World"}
...этот ответ вернёт:
{
"message": "Hello World"
}
Разумеется, вы наверняка найдёте гораздо более полезные способы воспользоваться этим, чем просто форматирование JSON. 😉
Класс ответа по умолчанию¶
При создании экземпляра класса FastAPI или APIRouter
вы можете указать, какой класс ответа использовать по умолчанию.
Параметр, который это определяет, — default_response_class
.
В примере ниже FastAPI будет использовать ORJSONResponse
по умолчанию во всех операциях пути вместо JSONResponse
.
from fastapi import FastAPI
from fastapi.responses import ORJSONResponse
app = FastAPI(default_response_class=ORJSONResponse)
@app.get("/items/")
async def read_items():
return [{"item_id": "Foo"}]
Совет
Вы по-прежнему можете переопределять response_class
в операциях пути, как и раньше.
Дополнительная документация¶
Вы также можете объявить тип содержимого и многие другие детали в OpenAPI с помощью responses
: Дополнительные ответы в OpenAPI.