Проблема внедрения зависимостей с FastAPI на Python
Добрый день! Подскажите, пожалуйста, как можно решить следующую проблему в Python + FastAPI.
Есть тестовый проект:
app / main.py - main file
app / routes / users.py -set of api methods
app / repos / factory.py - repository factory
app / repos / user_repository.py - repositories
app / handlers / factory.py - handler factory
app / handlers / users.py - handlers
app / domain / user.py - data class
Структура основных и маршрутов такая же, как в примере. https://fastapi.tiangolo.com/tutorial/bigger-applications/
В файле routes / users.py :
from fastapi import APIRouter, Depends
from ..handlers import factory
router = APIRouter()
@router.get("/users/", tags=["users"])
def read_users(handler=Depends(factory.get_handler)):
return handler.get_all()
В обработчиках / factory.py :
from fastapi import Depends
from .users import UserHandler1
def get_handler(handler=Depends(UserHandler1)):
return handler
В handlers / users.py :
from fastapi import Depends
from ..repos import factory
class UserHandler1:
def __init__(self):
pass
def get_all(self, repo=Depends(factory.get_repo)):
return repo.get_all()
репозитории / factory.py :
from fastapi import Depends
from ..repos.user_repository import UserRepository
def get_repo(repo=Depends(UserRepository)):
return repo
репозитории / user_repository.py :
from ..domain.user import User
class UserRepository:
def __init__(self):
pass
def get_all(self):
return [User(1, 'A'), User(2, 'B'), User(3, 'C')]
домен / user.py :
class User:
id: int
name: str
def __init__(self, id, name):
self.id = id
self.name = name
Затем я запускаю сервер Hypercorn: app.main:app --reloadпопробуйте вызвать метод api: http://127.0.0.1:8000/users/и получите ошибку AttributeError:'Depends' object has no attribute 'get_all'
Если удалить слой обработчиков и сделать так, то все заработает.
маршруты / users.py:
from fastapi import APIRouter, Depends
from ..repos import factory
router = APIRouter()
@router.get("/users/", tags=["users"])
def read_users(repo=Depends(factory.get_repo)):
return repo.get_all()
It also works if you completely remove all Depends and create
UserRepository and UserHandler1 directly in factories.
Вопрос 1: Как использовать "Depends" в этом случае и почему он не работает?
В целом завод не выглядит хорошим решением этой проблемы. Я видел пример реализации DI с использованием множественного наследования, но для меня это то же самое, что и фабричный метод. Я также пробовал использовать библиотеку Pinject, но для этого требуется первоначальное построение графика, который нужно где-то сохранить, чтобы получить к нему доступ в обработчиках api.
Вопрос 2 (более важный): как в этом случае можно применить внедрение зависимостей?
Ответы
Как отмечено в комментариях, зависимостью может быть все, что вызывается, а значит, и класс. Единственное предостережение в последнем случае заключается в том, что класс будет только инициализирован (т.е. будет вызываться только функция init (..)).
Итак, чтобы иметь класс как зависимость, как в примере с https://fastapi.tiangolo.com/tutorial/dependencies/classes-as-dependencies/#shortcutвам просто нужно вызвать целевые функции в init и установить значения как атрибуты класса.
from ..domain.user import User
class UserRepository:
def __init__(self):
self.get_all()
def get_all(self):
self.users = [User(1, 'A'), User(2, 'B'), User(3, 'C')]
from fastapi import Depends
from ..repos.user_repository import UserRepository
def get_repo(repo=Depends(UserRepository)):
print(repo.users) # This will print the list of users
return repo
ВОПРОС 2
NB
Это модельный вопрос. Здесь я предлагаю то, что считаю подходящим с моей точки зрения. Это не обязательно должен быть лучший или самый простой подход.
Отвечая на ваш второй вопрос, я бы не советовал использовать такие сложные зависимости. Если зависимости находятся на уровне маршрутизатора, вы можете просто добавить их в маршрутизатор, используя параметр depends=[...]и предоставив список классов / функций зависимостей.
В качестве альтернативы вы можете объявить все зависимости как параметры функции конечной точки, как вы это сделали для фабрики. Этот метод может привести к тому, что большие фрагменты кода будут скопированы и вставлены, поэтому я советую использовать описанный выше подход.
Если вам нужно обработать параметры данных, вы добавляете их в запрос и получаете к ним доступ из конечной точки. См. В разделе FastAPI получение идентификатора пользователя из ключа API для минимального примера.