Problem wstrzykiwania zależności w FastAPI w Pythonie

Oct 05 2020

Dobry dzień! Proszę powiedz mi, jak możesz rozwiązać następujący problem w Pythonie + FastAPI.

Istnieje projekt testowy:

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

Struktura główna i trasy jest taka sama jak w przykładzie https://fastapi.tiangolo.com/tutorial/bigger-applications/

W pliku tours / 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()

W pliku handlers / factory.py :

from fastapi import Depends
from .users import UserHandler1

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

W 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()

repozytoria / 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

Następnie uruchamiam serwer hypercorn: app.main:app --reloadwypróbuj metodę call api: http://127.0.0.1:8000/users/i otrzymuję błąd AttributeError:'Depends' object has no attribute 'get_all'

Jeśli usuniesz warstwę obsługi i zrobisz to, wszystko będzie działać.

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.

Pytanie 1: Jak używać opcji „Zależy” w tym przypadku i dlaczego nie działa?

Generalnie fabryka nie wygląda na dobre rozwiązanie tego problemu. Widziałem przykład implementacji DI z dziedziczeniem wielokrotnym, ale jak dla mnie jest to to samo, co metoda fabryczna. Próbowałem też użyć biblioteki Pinject, ale wymaga to wstępnej konstrukcji wykresu, który trzeba gdzieś zapisać, aby uzyskać do niego dostęp w programach obsługi API.

Pytanie 2 (ważniejsze): Jak w tym przypadku zastosować Dependency Injection?

Odpowiedzi

1 lsabi Oct 05 2020 at 12:37

Jak zauważono w komentarzach, zależnością może być wszystko, co jest wywoływalne, a zatem także klasa. Jedynym zastrzeżeniem w tym drugim przypadku jest to, że klasa zostanie tylko zainicjowana (tzn . Zostanie wywołana tylko funkcja init (..)).

Tak więc, aby mieć klasę jako zależność, jak w przykładzie https://fastapi.tiangolo.com/tutorial/dependencies/classes-as-dependencies/#shortcutwystarczy wywołać funkcje docelowe w init i ustawić wartości jako atrybuty klasy.

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

PYTANIE 2

NB

To jest pytanie modelujące. Tutaj proponuję to, co uważam za odpowiednie z mojego punktu widzenia. Nie musi to być koniecznie najlepsze czy najprostsze podejście.

Odpowiadając na twoje drugie pytanie, nie radziłbym w przypadku tak złożonych zależności. Jeśli zależności są na poziomie routera, możesz po prostu dodać je do routera, używając parametru depends=[...]i podając listę klas / funkcji zależności.

Alternatywnie można zadeklarować wszystkie zależności jako parametry funkcji punktu końcowego, tak jak w przypadku fabryki. Ta metoda może prowadzić do kopiowania i wklejania dużych fragmentów kodu, więc radzę zastosować powyższe podejście.

Jeśli musisz przetworzyć parametry danych, dodajesz je do żądania i uzyskujesz do nich dostęp z punktu końcowego. Minimalny przykład można znaleźć w artykule FastAPI get user ID from API key .