Зависимости
FastAPI использует очень мощную, но интуитивно понятную систему внедрения зависимостей.
Она разработана так, чтобы быть очень простой в использовании и позволять любому разработчику легко интегрировать другие компоненты с FastAPI.
Что такое «внедрение зависимостей»
«Внедрение зависимостей» в программировании означает, что ваш код (в данном случае — ваши функции-обработчики пути) может объявить, что ему требуется для работы и использования: «зависимости».
Затем эта система (в данном случае FastAPI) позаботится о том, чтобы сделать всё необходимое и предоставить вашему коду эти зависимости («внедрить» зависимости).
Это очень полезно, когда вам нужно:
- Использовать общую логику (повторно использовать один и тот же код).
- Совместно использовать подключения к базе данных.
- Обеспечивать безопасность, аутентификацию, требования к ролям и т. д.
- И многое другое...
И всё это позволяет свести повторение кода к минимуму.
Первые шаги
Рассмотрим очень простой пример. Пока он будет настолько простым, что не принесёт большой пользы.
Но так мы сможем сосредоточиться на том, как работает система внедрения зависимостей.
Создание зависимости, или «объекта-зависимости»
Для начала сосредоточимся на зависимости.
Это просто функция, которая может принимать те же параметры, что и функция-обработчик пути:
from typing import Annotated
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: Annotated[dict, Depends(common_parameters)]):
return commons
@app.get("/users/")
async def read_users(commons: Annotated[dict, Depends(common_parameters)]):
return commons
🤓 Другие версии и варианты
Совет
По возможности предпочтительно использовать вариант Annotated.
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):
return commons
@app.get("/users/")
async def read_users(commons: dict = Depends(common_parameters)):
return commons
Вот и всё.
2 строки.
У неё такая же форма и структура, как у всех ваших функций-обработчиков пути.
Можно считать её функцией-обработчиком пути без «декоратора» (без @app.get("/some-path")).
И она может возвращать всё, что вы захотите.
В данном случае эта зависимость ожидает:
- Необязательный параметр запроса
qтипаstr. - Необязательный параметр запроса
skipтипаint, значение по умолчанию —0. - Необязательный параметр запроса
limitтипаint, значение по умолчанию —100.
Затем она просто возвращает dict, содержащий эти значения.
Примечание
В версии 0.95.0 FastAPI добавил поддержку Annotated (и начал рекомендовать его).
При использовании более старой версии возникнут ошибки при попытке использовать Annotated.
Перед использованием Annotated убедитесь, что вы обновили версию FastAPI как минимум до 0.95.1.
Импорт Depends
from typing import Annotated
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: Annotated[dict, Depends(common_parameters)]):
return commons
@app.get("/users/")
async def read_users(commons: Annotated[dict, Depends(common_parameters)]):
return commons
🤓 Другие версии и варианты
Совет
По возможности предпочтительно использовать вариант Annotated.
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):
return commons
@app.get("/users/")
async def read_users(commons: dict = Depends(common_parameters)):
return commons
Объявление зависимости в «зависимом компоненте»
Так же, как вы используете Body, Query и т. д. в параметрах своей функции-обработчика пути, используйте Depends с новым параметром:
from typing import Annotated
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: Annotated[dict, Depends(common_parameters)]):
return commons
@app.get("/users/")
async def read_users(commons: Annotated[dict, Depends(common_parameters)]):
return commons
🤓 Другие версии и варианты
Совет
По возможности предпочтительно использовать вариант Annotated.
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):
return commons
@app.get("/users/")
async def read_users(commons: dict = Depends(common_parameters)):
return commons
Хотя в параметрах функции вы используете Depends так же, как Body, Query и т. д., Depends работает немного иначе.
Для Depends нужно указать только один параметр.
Этот параметр должен быть чем-то вроде функции.
Вы не вызываете её напрямую (не добавляете скобки в конце), а просто передаёте её как параметр в Depends().
Эта функция принимает параметры так же, как и функции-обработчики пути.
Совет
В следующей главе вы узнаете, какие ещё «объекты», помимо функций, можно использовать в качестве зависимостей.
При поступлении каждого нового запроса FastAPI позаботится о том, чтобы:
- Вызвать функцию зависимости («объект-зависимость») с правильными параметрами.
- Получить результат выполнения функции.
- Присвоить этот результат параметру вашей функции-обработчика пути.
graph TB common_parameters(["common_parameters"]) read_items["/items/"] read_users["/users/"] common_parameters --> read_items common_parameters --> read_users
Так вы пишете общий код один раз, а FastAPI заботится о его вызове для ваших операций пути.
Совет
Обратите внимание: не нужно создавать специальный класс и передавать его куда-либо в FastAPI, чтобы «зарегистрировать» его или сделать что-то подобное.
Просто передайте его в Depends, а FastAPI знает, что делать дальше.
Совместное использование зависимостей Annotated
В приведённых выше примерах есть небольшое дублирование кода.
Если вам нужно использовать зависимость common_parameters(), придётся записать параметр целиком, указав аннотацию типа и Depends():
commons: Annotated[dict, Depends(common_parameters)]
Но поскольку мы используем Annotated, можно сохранить это значение Annotated в переменную и использовать её в нескольких местах:
from typing import Annotated
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
CommonsDep = Annotated[dict, Depends(common_parameters)]
@app.get("/items/")
async def read_items(commons: CommonsDep):
return commons
@app.get("/users/")
async def read_users(commons: CommonsDep):
return commons
Совет
Это обычный Python. Такой приём называется «псевдонимом типа» и не является чем-то специфичным для FastAPI.
Но поскольку FastAPI основан на стандартах Python, включая Annotated, этот приём можно использовать в своём коде. 😎
Зависимости продолжат работать как ожидается, а самое главное — информация о типах сохранится. Это значит, что редактор сможет и дальше предоставлять вам автодополнение, подсказки об ошибках прямо в строке и т. д. То же относится и к другим инструментам, например mypy.
Это особенно пригодится при работе с большой кодовой базой, где вы снова и снова используете одни и те же зависимости во многих операциях пути.
Использовать async или нет: async
Поскольку зависимости также вызываются FastAPI (как и ваши функции-обработчики пути), при определении функций действуют те же правила.
Можно использовать async def или обычный def.
Можно объявлять зависимости с async def внутри обычных def функций-обработчиков пути, а также зависимости def внутри async def функций-обработчиков пути и т. д.
Это не имеет значения. FastAPI знает, что делать.
Примечание
Если вы не знаете, о чём речь, прочитайте раздел Асинхронность: «Некогда читать?» в документации — там рассказывается о async и await.
Интеграция с OpenAPI
Все объявления запросов, проверки и требования зависимостей (и подзависимостей) будут включены в ту же схему OpenAPI.
Таким образом, интерактивная документация также будет содержать всю информацию об этих зависимостях:
Простое использование
Если разобраться, функции-обработчики пути объявляются для вызова при совпадении пути и операции, а затем FastAPI заботится о вызове функции с правильными параметрами и извлечении данных из запроса.
На самом деле все (или почти все) веб-фреймворки работают таким же образом.
Вы никогда не вызываете эти функции напрямую. Их вызывает ваш фреймворк (в данном случае — FastAPI).
С помощью системы внедрения зависимостей можно также сообщить FastAPI, что ваша функция-обработчик пути «зависит» от чего-то ещё, что должно быть выполнено до неё. Тогда FastAPI выполнит это и «внедрит» результаты.
Для обозначения этой идеи «внедрения зависимостей» также часто используют следующие термины:
- ресурсы
- поставщики
- службы
- внедряемые объекты
- компоненты
Плагины FastAPI
Интеграции и «плагины» можно создавать с помощью системы внедрения зависимостей. Но на самом деле создавать «плагины» не нужно: зависимости позволяют объявить бесконечное количество интеграций и взаимодействий, доступных вашим функциям-обработчикам пути.
Зависимости можно создавать очень просто и интуитивно: достаточно импортировать нужные пакеты Python и буквально за пару строк кода интегрировать их с функциями вашего API.
Примеры этого вы увидите в следующих главах, посвящённых реляционным базам данных, базам данных NoSQL, безопасности и т. д.
Совместимость FastAPI
Простота системы внедрения зависимостей обеспечивает совместимость FastAPI со следующими решениями:
- всеми реляционными базами данных
- базами данных NoSQL
- внешними пакетами
- внешними API
- системами аутентификации и авторизации
- системами мониторинга использования API
- системами внедрения данных в ответы
- и т. д.
Просто и мощно
Хотя иерархическую систему внедрения зависимостей очень просто определять и использовать, она при этом остаётся очень мощной.
Можно определять зависимости, которые, в свою очередь, сами определяют зависимости.
В итоге выстраивается иерархическое дерево зависимостей, а система внедрения зависимостей сама разрешает все зависимости (и их подзависимости) и предоставляет (внедряет) результаты на каждом этапе.
Например, предположим, что у вас есть 4 конечные точки API (операции пути):
/items/public//items/private//users/{user_id}/activate/items/pro/
Тогда для каждой из них можно задать разные требования к разрешениям с помощью зависимостей и подзависимостей:
graph TB
current_user(["current_user"])
active_user(["active_user"])
admin_user(["admin_user"])
paying_user(["paying_user"])
public["/items/public/"]
private["/items/private/"]
activate_user["/users/{user_id}/activate"]
pro_items["/items/pro/"]
current_user --> active_user
active_user --> admin_user
active_user --> paying_user
current_user --> public
active_user --> private
admin_user --> activate_user
paying_user --> pro_items Интеграция с OpenAPI
Все эти зависимости при объявлении своих требований также добавляют параметры, проверки и т. д. к вашим операциям пути.
FastAPI добавит всё это в схему OpenAPI, чтобы информация отображалась в системах интерактивной документации.
© 2018 Sebastián Ramírez
Licensed under the MIT License.
https://fastapi.tiangolo.com/tutorial/dependencies/