Problema de inyección de dependencia con FastAPI en Python

Oct 05 2020

¡Buen día! Dígame cómo puede resolver el siguiente problema en Python + FastAPI.

Hay un proyecto de prueba:

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

La estructura principal y de rutas es la misma que en el ejemplo https://fastapi.tiangolo.com/tutorial/bigger-applications/

En el archivo 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()

En handlers / factory.py :

from fastapi import Depends
from .users import UserHandler1

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

En 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

repositorios / 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')]

dominio / usuario.py :

class User:
    id: int
    name: str

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

Luego ejecuto el servidor hypercorn: app.main:app --reloadintente llamar al método api: http://127.0.0.1:8000/users/y obtenga el error AttributeError:'Depends' object has no attribute 'get_all'

Si quita la capa de controladores y hace esto, todo funcionará.

rutas / usuarios.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.

Pregunta 1: ¿Cómo utilizo "Depende" en este caso y por qué no funciona?

En general, la fábrica no parece una buena solución a este problema. Vi un ejemplo de implementación de DI usando herencia múltiple, pero para mí es el mismo que el método de fábrica. También intenté usar la biblioteca Pinject, pero requiere la construcción inicial de un gráfico, que debe guardarse en algún lugar para poder acceder a él en los controladores api.

Pregunta 2 (más importante): ¿Cómo se puede aplicar la inyección de dependencia en este caso?

Respuestas

1 lsabi Oct 05 2020 at 12:37

Como se señaló en los comentarios, una dependencia puede ser cualquier cosa que sea invocable y, por lo tanto, también una clase. La única advertencia en el último caso es que la clase solo se inicializará (es decir, solo se llamará a la función init (..)).

Entonces, para tener una clase como dependencia, como en el ejemplo de https://fastapi.tiangolo.com/tutorial/dependencies/classes-as-dependencies/#shortcutsolo necesita llamar a las funciones de destino dentro de init y establecer los valores como atributos de la clase.

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

PREGUNTA 2

nótese bien

Esta es una pregunta de modelado. Aquí propongo lo que considero adecuado desde mi punto de vista. No necesariamente tiene que ser el mejor enfoque o el más simple.

Respondiendo a su segunda pregunta, no recomendaría dependencias tan complejas. Si las dependencias están en el nivel del enrutador, simplemente puede agregarlas al enrutador, utilizando el parámetro depends=[...]y proporcionando una lista de clases / funciones de dependencia.

Alternativamente, puede declarar todas las dependencias como parámetros de función del punto final, como lo hizo para la fábrica. Este método puede llevar a que se copien y peguen grandes trozos de código, por lo que recomiendo el enfoque anterior.

Si necesita procesar los parámetros de datos, agréguelos a la solicitud y acceda a ellos desde el punto final. Consulte FastAPI obtener el ID de usuario de la clave API para ver un ejemplo mínimo.