8.5 KiB
Потокова передача даних
Якщо ви хочете передавати потоком дані, які можна структурувати як 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
У наведених вище прикладах байти даних передавалися потоком, але у відповіді не було заголовка Content-Type, тому клієнт не знав, який тип даних він отримує.
Можна створити власний підклас StreamingResponse, який встановлює заголовок Content-Type відповідно до типу даних, що ви стрімите.
Наприклад, можна створити PNGStreamingResponse, який встановлює заголовок 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 виконуватиме її в працівнику пулу потоків, щоб не блокувати головний цикл.
{* ../../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] *}