Problem mit der Abhängigkeitsinjektion mit FastAPI unter Python

Oct 05 2020

Schönen Tag! Bitte sagen Sie mir, wie Sie das folgende Problem in Python + FastAPI lösen können.

Es gibt ein Testprojekt:

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

Die Haupt- und Routenstruktur ist dieselbe wie im Beispiel https://fastapi.tiangolo.com/tutorial/bigger-applications/

In der Datei 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 den Handlern / factory.py :

from fastapi import Depends
from .users import UserHandler1

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

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

domain / user.py :

class User:
    id: int
    name: str

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

Dann starte ich den Hypercorn-Server: app.main:app --reloadVersuche die API-Methode aufzurufen: http://127.0.0.1:8000/users/Und erhalte den Fehler AttributeError:'Depends' object has no attribute 'get_all'

Wenn Sie die Handler-Ebene entfernen und dies tun, funktioniert alles.

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

Frage 1: Wie verwende ich in diesem Fall "Abhängig" und warum funktioniert es nicht?

Im Allgemeinen scheint die Fabrik keine gute Lösung für dieses Problem zu sein. Ich habe ein Beispiel für eine DI-Implementierung mit Mehrfachvererbung gesehen, aber für mich ist es dasselbe wie die Factory-Methode. Ich habe auch versucht, die Pinject-Bibliothek zu verwenden, aber es erfordert die anfängliche Erstellung eines Diagramms, das irgendwo gespeichert werden muss, um in API-Handlern darauf zugreifen zu können.

Frage 2 (wichtiger): Wie kann die Abhängigkeitsinjektion in diesem Fall angewendet werden?

Antworten

1 lsabi Oct 05 2020 at 12:37

Wie in den Kommentaren erwähnt, kann eine Abhängigkeit alles sein, was aufrufbar ist und somit auch eine Klasse. Die einzige Einschränkung im letzteren Fall ist, dass die Klasse nur initialisiert wird (dh nur die Funktion init (..) wird aufgerufen).

Also, um eine Klasse als Abhängigkeit zu haben, wie im Beispiel von https://fastapi.tiangolo.com/tutorial/dependencies/classes-as-dependencies/#shortcutSie müssen nur die Zielfunktionen innerhalb des Init aufrufen und die Werte als Attribute der Klasse festlegen.

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

FRAGE 2

NB

Dies ist eine Modellierungsfrage. Hier schlage ich vor, was ich aus meiner Sicht für geeignet halte. Es muss nicht unbedingt der beste oder einfachste Ansatz sein.

Bei der Beantwortung Ihrer zweiten Frage würde ich nicht für solch komplexe Abhängigkeiten raten. Wenn sich die Abhängigkeiten auf Routerebene befinden, können Sie sie einfach mithilfe des Parameters zum Router hinzufügen depends=[...]und eine Liste der Abhängigkeitsklassen / -funktionen bereitstellen.

Alternativ können Sie alle Abhängigkeiten als Funktionsparameter des Endpunkts deklarieren, wie Sie es für die Factory getan haben. Diese Methode kann dazu führen, dass große Codestücke kopiert und eingefügt werden. Ich empfehle daher den oben beschriebenen Ansatz.

Wenn Sie die Datenparameter verarbeiten müssen, fügen Sie sie der Anforderung hinzu und greifen vom Endpunkt aus darauf zu. Ein minimales Beispiel finden Sie unter FastAPI-Abrufen der Benutzer-ID vom API-Schlüssel .