Problème d'injection de dépendances avec FastAPI sur Python
Bonne journée! Veuillez me dire comment vous pouvez résoudre le problème suivant en Python + FastAPI.
Il existe un projet de test:
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
La structure principale et des routes est la même que dans l'exemple https://fastapi.tiangolo.com/tutorial/bigger-applications/
Dans le fichier 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()
Dans handlers / factory.py :
from fastapi import Depends
from .users import UserHandler1
def get_handler(handler=Depends(UserHandler1)):
return handler
Dans 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()
repos / factory.py :
from fastapi import Depends
from ..repos.user_repository import UserRepository
def get_repo(repo=Depends(UserRepository)):
return repo
repos / 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')]
domaine / user.py :
class User:
id: int
name: str
def __init__(self, id, name):
self.id = id
self.name = name
Ensuite, j'exécute le serveur hypercorn: app.main:app --reloadEssayez d'appeler la méthode api: http://127.0.0.1:8000/users/et obtenez l'erreur AttributeError:'Depends' object has no attribute 'get_all'
Si vous supprimez le calque des gestionnaires et faites cela, tout fonctionnera.
routes / 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.
Question 1: Comment utiliser "Depends" dans ce cas et pourquoi cela ne fonctionne-t-il pas?
En général, l'usine ne semble pas être une bonne solution à ce problème. J'ai vu un exemple d'implémentation DI utilisant l'héritage multiple mais pour moi, c'est la même chose que la méthode d'usine. J'ai également essayé d'utiliser la bibliothèque Pinject, mais cela nécessite la construction initiale d'un graphe, qui doit être sauvegardé quelque part pour y accéder dans les gestionnaires d'API.
Question 2 (plus importante): Comment l'injection de dépendance peut-elle être appliquée dans ce cas?
Réponses
Comme indiqué dans les commentaires, une dépendance peut être tout ce qui est appelable et donc également une classe. La seule mise en garde dans ce dernier cas est que la classe ne sera initialisée que (c'est-à-dire que seule la fonction init (..) sera appelée).
Donc, pour avoir une classe comme dépendance, comme dans l'exemple de https://fastapi.tiangolo.com/tutorial/dependencies/classes-as-dependencies/#shortcutil vous suffit d'appeler les fonctions cibles dans l' initialisation et de définir les valeurs comme attributs de la classe.
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
QUESTION 2
NB
C'est une question de modélisation. Ici, je propose ce que je crois convenable de mon point de vue. Il ne doit pas nécessairement s'agir de l'approche la meilleure ou la plus simple.
En réponse à votre deuxième question, je ne conseillerais pas pour des dépendances aussi complexes. Si les dépendances sont au niveau du routeur, vous pouvez simplement les ajouter au routeur, en utilisant le paramètre depends=[...]et en fournissant une liste de classes / fonctions de dépendances.
Vous pouvez également déclarer toutes les dépendances en tant que paramètres de fonction du point de terminaison, comme vous l'avez fait pour l'usine. Cette méthode peut entraîner la copie et le collage de gros morceaux de code, je vous conseille donc l'approche ci-dessus.
Si vous devez traiter les paramètres de données, ajoutez-les à la demande et accédez-y à partir du point de terminaison. Voir FastAPI obtenir l'ID utilisateur à partir de la clé API pour un exemple minimal.