Перейти к содержанию

Кастомные ответы — 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, от него наследуются все остальные ответы.

Его можно возвращать напрямую.

Он принимает следующие параметры:

  • contentstr или 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")
  1. Это функция-генератор. Она является «функцией-генератором», потому что содержит оператор(ы) yield внутри.
  2. Используя блок with, мы гарантируем, что файлоподобный объект будет закрыт после завершения работы функции-генератора. То есть после того, как она закончит отправку ответа.
  3. Этот 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.