ปัญหา Dependency Injection กับ FastAPI บน Python

Oct 05 2020

ขอให้เป็นวันที่ดี! โปรดบอกฉันว่าคุณสามารถแก้ปัญหาต่อไปนี้ใน Python + FastAPI ได้อย่างไร

มีโครงการทดสอบ:

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

โครงสร้างหลักและเส้นทางจะเหมือนกับในตัวอย่าง https://fastapi.tiangolo.com/tutorial/bigger-applications/

ในไฟล์เส้นทาง / 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()

ในตัวจัดการ / factory.py :

from fastapi import Depends
from .users import UserHandler1

def get_handler(handler=Depends(UserHandler1)):
    return 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')]

โดเมน / user.py :

class User:
    id: int
    name: str

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

จากนั้นฉันเรียกใช้เซิร์ฟเวอร์ไฮเปอร์คอร์น: app.main:app --reloadลองเรียกเมธอด api: http://127.0.0.1:8000/users/และรับข้อผิดพลาด AttributeError:'Depends' object has no attribute 'get_all'

หากคุณลบเลเยอร์ตัวจัดการและทำสิ่งนี้ทุกอย่างจะทำงาน

เส้นทาง / 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.

คำถามที่ 1:ฉันจะใช้ "ขึ้นอยู่" ในกรณีนี้ได้อย่างไรและทำไมจึงไม่ได้ผล?

โดยทั่วไปโรงงานไม่ได้มีลักษณะเป็นวิธีการแก้ปัญหาที่ดีสำหรับปัญหานี้ ฉันเห็นตัวอย่างการใช้งาน DI โดยใช้การสืบทอดหลายรายการ แต่สำหรับฉันแล้วมันเหมือนกับวิธีโรงงาน ฉันพยายามใช้ไลบรารี Pinject ด้วย แต่ต้องมีการสร้างกราฟเริ่มต้นซึ่งจำเป็นต้องบันทึกไว้ที่ไหนสักแห่งเพื่อเข้าถึงในตัวจัดการ api

คำถามที่ 2 (สำคัญกว่า): Dependency Injection สามารถใช้ได้อย่างไรในกรณีนี้?

คำตอบ

1 lsabi Oct 05 2020 at 12:37

ตามที่ระบุไว้ในความคิดเห็นการพึ่งพาอาจเป็นอะไรก็ได้ที่เรียกได้และเป็นชั้นเรียนเช่นกัน ข้อแม้เดียวในกรณีหลังคือคลาสจะถูกเริ่มต้นเท่านั้น (กล่าวคือจะเรียกใช้ฟังก์ชันinit (.. ) เท่านั้น)

ดังนั้นเพื่อให้มีคลาสเป็นการพึ่งพาดังตัวอย่างของ https://fastapi.tiangolo.com/tutorial/dependencies/classes-as-dependencies/#shortcutคุณเพียงแค่ต้องเรียกใช้ฟังก์ชันเป้าหมายภายในinitและตั้งค่าเป็นแอตทริบิวต์ของคลาส

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

คำถาม 2

NB

นี่คือคำถามการสร้างแบบจำลอง นี่ผมเสนอสิ่งที่ผมเชื่อว่าเป็นที่เหมาะสมจากฉันมุมมอง ไม่จำเป็นต้องเป็นแนวทางที่ดีที่สุดหรือง่ายที่สุด

ตอบคำถามที่สองของคุณฉันไม่แนะนำสำหรับการอ้างอิงที่ซับซ้อนเช่นนี้ หากการอ้างอิงอยู่ในระดับเราเตอร์คุณสามารถเพิ่มเข้าไปในเราเตอร์โดยใช้พารามิเตอร์depends=[...]และจัดเตรียมรายการคลาส / ฟังก์ชันการพึ่งพา

หรือคุณสามารถประกาศการอ้างอิงทั้งหมดเป็นพารามิเตอร์ฟังก์ชันของปลายทางได้เช่นเดียวกับที่คุณทำกับโรงงาน วิธีนี้อาจนำไปสู่การคัดลอกและวางโค้ดจำนวนมากดังนั้นฉันขอแนะนำสำหรับแนวทางข้างต้น

หากคุณต้องการประมวลผลพารามิเตอร์ข้อมูลคุณจึงต้องเพิ่มพารามิเตอร์เหล่านั้นลงในคำขอและเข้าถึงจากภายในปลายทาง ดูFastAPI รับ ID ผู้ใช้จากคีย์ APIสำหรับตัวอย่างเล็กน้อย