Problema di Dependency Injection con FastAPI su Python

Oct 05 2020

Buona giornata! Per favore dimmi come puoi risolvere il seguente problema in Python + FastAPI.

C'è un progetto di prova:

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 struttura principale e delle rotte è la stessa dell'esempio https://fastapi.tiangolo.com/tutorial/bigger-applications/

Nel file route / 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()

In handlers / factory.py :

from fastapi import Depends
from .users import UserHandler1

def get_handler(handler=Depends(UserHandler1)):
    return handler

In 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')]

dominio / utente.py :

class User:
    id: int
    name: str

    def __init__(self, id, name):
        self.id = id
        self.name = name

Quindi eseguo il server hypercorn: app.main:app --reloadprova a chiamare il metodo api: http://127.0.0.1:8000/users/e ottengo l'errore AttributeError:'Depends' object has no attribute 'get_all'

Se rimuovi il livello dei gestori e lo fai, tutto funzionerà.

route / 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.

Domanda 1: come si usa "Depends" in questo caso e perché non funziona?

In generale, la fabbrica non sembra una buona soluzione a questo problema. Ho visto un esempio di implementazione DI utilizzando l'ereditarietà multipla, ma per me è lo stesso del metodo di fabbrica. Ho anche provato a utilizzare la libreria Pinject, ma richiede la costruzione iniziale di un grafico, che deve essere salvato da qualche parte per accedervi nei gestori api.

Domanda 2 (più importante): come può essere applicata la Dependency Injection in questo caso?

Risposte

1 lsabi Oct 05 2020 at 12:37

Come notato nei commenti, una dipendenza può essere qualsiasi cosa richiamabile e quindi anche una classe. L'unico avvertimento in quest'ultimo caso è che la classe verrà solo inizializzata (cioè verrà chiamata solo la funzione init (..)).

Quindi, per avere una classe come dipendenza, come nell'esempio di https://fastapi.tiangolo.com/tutorial/dependencies/classes-as-dependencies/#shortcutdevi solo chiamare le funzioni di destinazione all'interno dell'init e impostare i valori come attributi della 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

DOMANDA 2

NB

Questa è una domanda modellistica. Qui propongo quello che credo sia adatto dal mio punto di vista. Non deve essere necessariamente l'approccio migliore o più semplice.

Rispondendo alla tua seconda domanda, non consiglierei per dipendenze così complesse. Se le dipendenze sono a livello di router, puoi semplicemente aggiungerle al router, utilizzando il parametro depends=[...]e fornendo un elenco di classi / funzioni di dipendenza.

In alternativa, puoi dichiarare tutte le dipendenze come parametri di funzione dell'endpoint, come hai fatto per la fabbrica. Questo metodo può portare a grandi blocchi di codice che vengono copiati e incollati, quindi consiglio l'approccio sopra.

Se è necessario elaborare i parametri dei dati, aggiungerli alla richiesta e accedervi dall'endpoint. Vedere FastAPI ottenere l'ID utente dalla chiave API per un esempio minimo.