fastapi/docs/ru/docs/advanced/stream-data.md

8.9 KiB
Raw Blame History

Потоковая передача данных

Если вам нужно передавать потоковые данные, которые можно представить как JSON, воспользуйтесь стримингом JSON Lines.

Но если вы хотите передавать в потоке чистые бинарные данные или строки, ниже показано, как это сделать.

/// info | Информация

Добавлено в FastAPI 0.134.0.

///

Варианты использования

Это можно использовать, если вы хотите стримить чистые строки, например, напрямую из вывода сервиса AI LLM.

Также вы можете стримить большие бинарные файлы, передавая каждый чанк данных по мере чтения, без необходимости загружать всё в память сразу.

Аналогично можно стримить видео или аудио — данные могут даже генерироваться по мере обработки и отправки.

«StreamingResponse с yield»

Если вы укажете response_class=StreamingResponse в своей функции-обработчике пути, вы можете использовать yield, чтобы по очереди отправлять каждый чанк данных.

{* ../../docs_src/stream_data/tutorial001_py310.py ln[1:23] hl[20,23] *}

FastAPI будет передавать каждый чанк данных в StreamingResponse как есть, не пытаясь конвертировать его в JSON или что-то подобное.

Не-async функции-обработчики пути

Можно использовать и обычные функции def (без async) и точно так же применять yield.

{* ../../docs_src/stream_data/tutorial001_py310.py ln[26:29] hl[27] *}

Без аннотации

Для потоковой передачи бинарных данных вам не нужно указывать аннотацию типа возвращаемого значения.

Поскольку FastAPI не будет пытаться конвертировать данные в JSON с помощью Pydantic или сериализовать их каким-либо образом, в данном случае аннотация типа нужна только для вашего редактора кода и инструментов, FastAPI её использовать не будет.

{* ../../docs_src/stream_data/tutorial001_py310.py ln[32:35] hl[33] *}

Это также означает, что с StreamingResponse у вас есть и свобода, и ответственность — производить и кодировать байты данных ровно в том виде, в котором они должны быть отправлены, независимо от аннотаций типов. 🤓

Потоковая передача байтов

Один из основных сценариев — стримить bytes вместо строк, и, конечно, это можно сделать.

{* ../../docs_src/stream_data/tutorial001_py310.py ln[44:47] hl[47] *}

Пользовательский PNGStreamingResponse

В примерах выше байты данных передавались потоком, но в ответе не было HTTP-заголовка Content-Type, поэтому клиент не знал, какой тип данных он получает.

Вы можете создать пользовательский подкласс StreamingResponse, который устанавливает HTTP-заголовок Content-Type в тип данных, которые вы стримите.

Например, вы можете создать PNGStreamingResponse, который устанавливает HTTP-заголовок Content-Type в image/png с помощью атрибута media_type:

{* ../../docs_src/stream_data/tutorial002_py310.py ln[6,19:20] hl[20] *}

Затем вы можете использовать этот новый класс в response_class=PNGStreamingResponse в своей функции-обработчике пути:

{* ../../docs_src/stream_data/tutorial002_py310.py ln[23:27] hl[23] *}

Симулировать файл

В этом примере мы симулируем файл с помощью io.BytesIO — это «файлоподобный» объект, который существует только в памяти, но позволяет использовать тот же интерфейс.

Например, мы можем итерироваться по нему, чтобы потреблять его содержимое, как и по обычному файлу.

{* ../../docs_src/stream_data/tutorial002_py310.py ln[1:27] hl[3,12:13,25] *}

/// note | Технические детали

Две другие переменные, image_base64 и binary_image, — это изображение, закодированное в Base64, а затем преобразованное в байты, после чего переданное в io.BytesIO.

Только для того, чтобы всё помещалось в одном файле для этого примера, и вы могли скопировать код и запустить его как есть. 🥚

///

Используя блок with, мы гарантируем, что объект, ведущий себя как файл, будет закрыт после завершения работы функции‑генератора (функции с yield). То есть после того, как она закончит отправку ответа.

В этом конкретном примере это не столь важно, потому что это «фейковый» файл в памяти (io.BytesIO), но для реального файла важно удостовериться, что файл закрыт после завершения работы с ним.

Файлы и async

В большинстве случаев «файлоподобные» объекты по умолчанию не совместимы с async и await.

Например, у них нет await file.read() или async for chunk in file.

И во многих случаях чтение таких объектов будет блокирующей операцией (которая может заблокировать цикл событий), потому что данные читаются с диска или из сети.

/// info | Информация

Приведённый выше пример — исключение, потому что объект io.BytesIO уже находится в памяти, поэтому чтение ничего не блокирует.

Но во многих случаях чтение файла или «файлоподобного» объекта будет блокировать.

///

Чтобы не блокировать цикл событий, вы можете просто объявить функцию-обработчик пути обычной def вместо async def. Тогда FastAPI выполнит её в воркере из пула потоков (threadpool), чтобы не блокировать основной цикл.

{* ../../docs_src/stream_data/tutorial002_py310.py ln[30:34] hl[31] *}

/// tip | Совет

Если вам нужно вызвать блокирующий код изнутри async-функции, или async-функцию изнутри блокирующей функции, вы можете использовать Asyncer, родственную библиотеку к FastAPI.

///

yield from

Когда вы итерируетесь по чему‑то, например, по «файлоподобному» объекту, и делаете yield для каждого элемента, вы можете также использовать yield from, чтобы отдавать каждый элемент напрямую и не писать цикл for.

Это не специфично для FastAPI, это просто Python, но полезный приём. 😎

{* ../../docs_src/stream_data/tutorial002_py310.py ln[37:40] hl[40] *}