简体   繁体   English

Python 上 FastAPI 的依赖注入问题

[英]Dependency Injection problem with FastAPI on Python

Good day!再会! Please tell me how you can solve the following problem in Python + FastAPI.请告诉我如何在 Python + FastAPI 中解决以下问题。

There is a test project:有一个测试项目:

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

The main and routes structure is the same as in the example https://fastapi.tiangolo.com/tutorial/bigger-applications/ main 和 routes 结构与示例中的相同https://fastapi.tiangolo.com/tutorial/bigger-applications/

In the routes/users.py 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()

In the handlers/factory.py :handlers/factory.py中:

from fastapi import Depends
from .users import UserHandler1

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

In the handlers/users.py :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 :回购/factory.py

from fastapi import Depends
from ..repos.user_repository import UserRepository

def get_repo(repo=Depends(UserRepository)):
    return repo

repos/user_repository.py :存储库/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 :域/user.py

class User:
    id: int
    name: str

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

Then I run hypercorn server: app.main:app --reload Try call api method: http://127.0.0.1:8000/users/ And get the error AttributeError: 'Depends' object has no attribute 'get_all'然后我运行 hypercorn 服务器: app.main:app --reload尝试调用 api 方法:http: http://127.0.0.1:8000/users/ :8000/users/ 并得到错误 AttributeError: 'Depends' object has no attribute 'get_all'

If you remove the handlers layer and do this, then everything will work.如果您删除处理程序层并执行此操作,那么一切都会正常工作。

routes/users.py:路线/用户.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.

Question 1: How do I use "Depends" in this case and why doesn't it work?问题1:在这种情况下如何使用“Depends”,为什么它不起作用?

In general, the factory does not look like a good solution to this problem.一般来说,工厂看起来并不能很好地解决这个问题。 I saw an example of DI implementation using multiple inheritance but as for me it is the same as factory method.我看到了一个使用多重继承实现 DI 的示例,但对我来说它与工厂方法相同。 I also tried to use the Pinject library, but it requires the initial construction of a graph, which needs to be saved somewhere in order to access it in api handlers.我也尝试使用 Pinject 库,但它需要初始构建图形,需要将其保存在某个地方以便在 api 处理程序中访问它。

Question 2 (more important): How Dependency Injection can be applied in this case ?问题 2(更重要):在这种情况下如何应用依赖注入?

As noted in the comments, a dependency can be anything that is a callable and thus a class as well.正如评论中所指出的,依赖项可以是任何可调用的东西,因此也可以是一个类。 The only caveat in the latter case is that the class will only be initialized (ie only the init (..) function will be called).在后一种情况下唯一的警告是该类只会被初始化(即只会调用init (..) 函数)。

So, in order to have a class as dependency, as in the example of https://fastapi.tiangolo.com/tutorial/dependencies/classes-as-dependencies/#shortcut you just need to call the target functions within the init and set the values as attributes of the class.因此,为了将类作为依赖项,如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

QUESTION 2问题2

NB NB

This is a modelling question.这是一道建模题。 Here I propose what I believe is suitable from my point of view.在这里,我提出我认为适合的观点。 It does not necessarily have to be best or simplest approach.它不一定是最好或最简单的方法。

Answering your second question, I would not advice for such complex dependencies.回答你的第二个问题,我不会建议这种复杂的依赖关系。 If the dependencies are at the router level, you can simply add them to the router, using the parameter depends=[...] and providing a list of dependency classes/functions.如果依赖项在路由器级别,您可以简单地将它们添加到路由器,使用参数depends=[...]并提供依赖项类/函数的列表。

Alternatively, you could declare all of the dependencies as function parameters of the endpoint, as you did for the factory.或者,您可以将所有依赖项声明为端点的函数参数,就像对工厂所做的那样。 This method may lead to big chunks of code getting copied and pasted, so I advise for the above approach.这种方法可能会导致复制和粘贴大量代码,因此我建议采用上述方法。

If you need to process the data parameters, then you add them to the request and access them from within the endpoint.如果您需要处理数据参数,则将它们添加到请求中并从端点内访问它们。 See FastAPI get user ID from API key for a minimal example.有关最小示例,请参阅FastAPI 从 API 密钥获取用户 ID

__call__方法必须在类中实现。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM