Masalah Dependency Injection dengan FastAPI pada Python

Oct 05 2020

Selamat siang! Tolong beritahu saya bagaimana Anda dapat memecahkan masalah berikut dengan Python + FastAPI.

Ada proyek uji:

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

Struktur utama dan rute sama seperti pada contoh https://fastapi.tiangolo.com/tutorial/bigger-applications/

Di file 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()

Di handlers / factory.py :

from fastapi import Depends
from .users import UserHandler1

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

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

domain / user.py :

class User:
    id: int
    name: str

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

Kemudian saya menjalankan server hypercorn: app.main:app --reloadCoba metode api panggilan: http://127.0.0.1:8000/users/Dan dapatkan kesalahan AttributeError:'Depends' object has no attribute 'get_all'

Jika Anda menghapus lapisan penangan dan melakukan ini, maka semuanya akan bekerja.

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.

Pertanyaan 1: Bagaimana cara menggunakan "Tergantung" dalam kasus ini dan mengapa tidak berhasil?

Secara umum, pabrik sepertinya bukan solusi yang baik untuk masalah ini. Saya melihat contoh implementasi DI menggunakan multiple inheritance tetapi bagi saya itu sama dengan metode pabrik. Saya juga mencoba menggunakan pustaka Pinject, tetapi membutuhkan konstruksi awal grafik, yang perlu disimpan di suatu tempat untuk mengaksesnya di penangan api.

Pertanyaan 2 (lebih penting): Bagaimana Injeksi Ketergantungan dapat diterapkan dalam kasus ini?

Jawaban

1 lsabi Oct 05 2020 at 12:37

Seperti disebutkan di komentar, dependensi bisa berupa apa saja yang bisa dipanggil dan dengan demikian juga menjadi kelas. Satu-satunya peringatan dalam kasus terakhir adalah bahwa kelas hanya akan diinisialisasi (yaitu hanya fungsi init (..) yang akan dipanggil).

Jadi, untuk memiliki kelas sebagai ketergantungan, seperti pada contoh https://fastapi.tiangolo.com/tutorial/dependencies/classes-as-dependencies/#shortcutAnda hanya perlu memanggil fungsi target di dalam init dan menetapkan nilai sebagai atribut kelas.

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

PERTANYAAN 2

NB

Ini adalah pertanyaan pemodelan. Di sini saya mengusulkan apa yang saya yakini cocok dari sudut pandang saya . Ini tidak harus merupakan pendekatan terbaik atau paling sederhana.

Menjawab pertanyaan kedua Anda, saya tidak akan menyarankan untuk ketergantungan yang begitu rumit. Jika dependensi berada di level router, Anda cukup menambahkannya ke router, menggunakan parameter depends=[...]dan memberikan daftar kelas / fungsi dependensi.

Alternatifnya, Anda dapat mendeklarasikan semua dependensi sebagai parameter fungsi titik akhir, seperti yang Anda lakukan di pabrik. Metode ini dapat menyebabkan potongan besar kode disalin dan ditempel, jadi saya menyarankan untuk pendekatan di atas.

Jika Anda perlu memproses parameter data, Anda dapat menambahkannya ke permintaan dan mengaksesnya dari dalam endpoint. Lihat FastAPI mendapatkan ID pengguna dari kunci API untuk contoh minimal.