16 KiB
Зависимости с yield
FastAPI поддерживает зависимости, которые выполняют некоторые дополнительные шаги после завершения.
Для этого используйте yield вместо return, а дополнительные шаги (код) напишите после него.
/// tip | Подсказка
Убедитесь, что используете yield только один раз на одну зависимость.
///
/// note | Технические детали
Любая функция, с которой можно корректно использовать:
будет корректной для использования в качестве зависимости FastAPI.
На самом деле, FastAPI использует эти два декоратора внутренне.
///
Зависимость базы данных с помощью yield
Например, с его помощью можно создать сессию работы с базой данных и закрыть её после завершения.
Перед созданием ответа будет выполнен только код до и включая оператор yield:
{* ../../docs_src/dependencies/tutorial007.py hl[2:4] *}
Значение, полученное из yield, внедряется в операции пути и другие зависимости:
{* ../../docs_src/dependencies/tutorial007.py hl[4] *}
Код, следующий за оператором yield, выполняется после ответа:
{* ../../docs_src/dependencies/tutorial007.py hl[5:6] *}
/// tip | Подсказка
Можно использовать как async, так и обычные функции.
FastAPI корректно обработает каждый вариант, так же как и с обычными зависимостями.
///
Зависимость с yield и try
Если использовать блок try в зависимости с yield, то вы получите любое исключение, которое было выброшено при использовании зависимости.
Например, если какой-то код в какой-то момент в середине, в другой зависимости или в операции пути, сделал "откат" транзакции базы данных или создал любую другую ошибку, то вы получите это исключение в своей зависимости.
Таким образом, можно искать конкретное исключение внутри зависимости с помощью except SomeException.
Точно так же можно использовать finally, чтобы убедиться, что обязательные шаги при выходе выполнены независимо от того, было ли исключение или нет.
{* ../../docs_src/dependencies/tutorial007.py hl[3,5] *}
Подзависимости с yield
Вы можете иметь подзависимости и "деревья" подзависимостей любого размера и формы, и любая из них или все они могут использовать yield.
FastAPI проследит за тем, чтобы «код выхода» в каждой зависимости с yield выполнялся в правильном порядке.
Например, dependency_c может зависеть от dependency_b, а dependency_b — от dependency_a:
{* ../../docs_src/dependencies/tutorial008_an_py39.py hl[6,14,22] *}
И все они могут использовать yield.
В этом случае dependency_c для выполнения своего кода выхода нуждается в том, чтобы значение из dependency_b (здесь dep_b) всё ещё было доступно.
И, в свою очередь, dependency_b нуждается в том, чтобы значение из dependency_a (здесь dep_a) было доступно для её кода выхода.
{* ../../docs_src/dependencies/tutorial008_an_py39.py hl[18:19,26:27] *}
Точно так же можно иметь часть зависимостей с yield, часть — с return, и какие-то из них могут зависеть друг от друга.
Либо у вас может быть одна зависимость, которая требует несколько других зависимостей с yield и т.д.
Комбинации зависимостей могут быть какими угодно.
FastAPI проследит за тем, чтобы всё выполнялось в правильном порядке.
/// note | Технические детали
Это работает благодаря менеджерам контекста в Python.
FastAPI использует их внутренне для достижения этого.
///
Зависимости с yield и HTTPException
Вы видели, что можно использовать зависимости с yield и иметь блоки try, которые пытаются выполнить некоторый код, а затем запускают код выхода в finally.
Также вы можете использовать except, чтобы поймать вызванное исключение и что-то с ним сделать.
Например, вы можете вызвать другое исключение, например HTTPException.
/// tip | Подсказка
Это довольно продвинутая техника, и в большинстве случаев она вам не понадобится, так как вы можете вызывать исключения (включая HTTPException) в остальном коде вашего приложения, например, в функции-обработчике пути.
Но если понадобится — возможность есть. 🤓
///
{* ../../docs_src/dependencies/tutorial008b_an_py39.py hl[18:22,31] *}
Если вы хотите перехватывать исключения и формировать на их основе пользовательский ответ, создайте Пользовательский обработчик исключений{.internal-link target=_blank}.
Зависимости с yield и except
Если вы ловите исключение с помощью except в зависимости с yield и не вызываете его снова (или не вызываете новое исключение), FastAPI не сможет заметить, что было исключение — так же, как это происходит в обычном Python:
{* ../../docs_src/dependencies/tutorial008c_an_py39.py hl[15:16] *}
В этом случае клиент получит HTTP 500 Internal Server Error, как и должно быть, поскольку мы не вызываем HTTPException или что-то подобное, но на сервере не будет никаких логов или других указаний на то, какая была ошибка. 😱
Всегда делайте raise в зависимостях с yield и except
Если вы ловите исключение в зависимости с yield, то, если вы не вызываете другой HTTPException или что-то подобное, вам следует повторно вызвать исходное исключение.
Вы можете повторно вызвать то же самое исключение с помощью raise:
{* ../../docs_src/dependencies/tutorial008d_an_py39.py hl[17] *}
Теперь клиент получит тот же HTTP 500 Internal Server Error, но на сервере в логах будет наше пользовательское InternalError. 😎
Выполнение зависимостей с yield
Последовательность выполнения примерно такая, как на этой схеме. Время течёт сверху вниз. А каждый столбец — это одна из частей, взаимодействующих с кодом или выполняющих код.
sequenceDiagram
participant client as Client
participant handler as Exception handler
participant dep as Dep with yield
participant operation as Path Operation
participant tasks as Background tasks
Note over client,operation: Can raise exceptions, including HTTPException
client ->> dep: Start request
Note over dep: Run code up to yield
opt raise Exception
dep -->> handler: Raise Exception
handler -->> client: HTTP error response
end
dep ->> operation: Run dependency, e.g. DB session
opt raise
operation -->> dep: Raise Exception (e.g. HTTPException)
opt handle
dep -->> dep: Can catch exception, raise a new HTTPException, raise other exception
end
handler -->> client: HTTP error response
end
operation ->> client: Return response to client
Note over client,operation: Response is already sent, can't change it anymore
opt Tasks
operation -->> tasks: Send background tasks
end
opt Raise other exception
tasks -->> tasks: Handle exceptions in the background task code
end
/// info | Дополнительная информация
Клиенту будет отправлен только один ответ. Это может быть один из ответов об ошибке или ответ от операции пути.
После отправки одного из этих ответов никакой другой ответ отправить нельзя.
///
/// tip | Подсказка
Если вы вызовете какое-либо исключение в коде из функции-обработчика пути, оно будет передано зависимостям с yield, включая HTTPException. В большинстве случаев вы захотите повторно вызвать то же самое исключение или новое из зависимости с yield, чтобы убедиться, что оно корректно обработано.
///
Зависимости с yield, HTTPException, except и фоновыми задачами
Зависимости с yield со временем эволюционировали, чтобы покрыть разные сценарии и исправить некоторые проблемы.
Если вы хотите посмотреть, что менялось в разных версиях FastAPI, вы можете прочитать об этом подробнее в продвинутом руководстве: Продвинутые зависимости — зависимости с yield, HTTPException, except и фоновыми задачами{.internal-link target=_blank}.
Контекстные менеджеры
Что такое «контекстные менеджеры»
«Контекстные менеджеры» — это любые объекты Python, которые можно использовать в операторе with.
Например, можно использовать with для чтения файла:
with open("./somefile.txt") as f:
contents = f.read()
print(contents)
Под капотом вызов open("./somefile.txt") создаёт объект, называемый «контекстным менеджером».
Когда блок with завершается, он обязательно закрывает файл, даже если были исключения.
Когда вы создаёте зависимость с yield, FastAPI внутренне создаёт для неё менеджер контекста и сочетает его с некоторыми другими связанными инструментами.
Использование менеджеров контекста в зависимостях с yield
/// warning | Внимание
Это, более или менее, «продвинутая» идея.
Если вы только начинаете работать с FastAPI, то лучше пока пропустить этот пункт.
///
В Python можно создавать менеджеры контекста, создав класс с двумя методами: __enter__() и __exit__().
Их также можно использовать внутри зависимостей FastAPI с yield, применяя операторы
with или async with внутри функции зависимости:
{* ../../docs_src/dependencies/tutorial010.py hl[1:9,13] *}
/// tip | Подсказка
Другой способ создания менеджера контекста — с помощью:
оформив ими функцию с одним yield.
Именно это FastAPI использует внутренне для зависимостей с yield.
Но использовать эти декораторы для зависимостей FastAPI не обязательно (и не стоит).
FastAPI сделает это за вас на внутреннем уровне.
///