lundi 5 octobre 2020

Dependency Injection problem with FastAPI on Python

Good day! Please tell me how you can solve the following problem in 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/

In the routes/users.py file:

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:

from fastapi import Depends
from .users import UserHandler1

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

In the 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

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'

If you remove the handlers layer and do this, then everything will work.

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.

Question 1: How do I use "Depends" in this case and why doesn't it work?

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

Question 2 (more important): How DI can be applied in this case ?

1 commentaire:

  1. Your error is you need to apply dependendency injection in callable object's constructor. So the correct UserHandler1 should be this:

    class UserHandler1:
    def __init__(self, name= '', repo=Depends(get_repo)):
    self.name = name
    self.repo = repo

    def get_all(self, ):
    return self.repo.get_all()

    and it is working

    RépondreSupprimer