Problema de injeção de dependência com FastAPI em Python
Dia bom! Diga-me como você pode resolver o seguinte problema em Python + FastAPI.
Existe um projeto de teste:
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
A estrutura principal e das rotas é a mesma do exemplo https://fastapi.tiangolo.com/tutorial/bigger-applications/
No arquivo 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()
Em handlers / factory.py :
from fastapi import Depends
from .users import UserHandler1
def get_handler(handler=Depends(UserHandler1)):
return handler
Em 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')]
domínio / usuário.py :
class User:
id: int
name: str
def __init__(self, id, name):
self.id = id
self.name = name
Em seguida, executo o servidor hypercorn: app.main:app --reloadtente chamar o método api: http://127.0.0.1:8000/users/E recebo o erro AttributeError:'Depends' object has no attribute 'get_all'
Se você remover a camada de manipuladores e fizer isso, tudo funcionará.
rotas / 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.
Pergunta 1: Como uso "Depends" neste caso e por que não funciona?
Em geral, a fábrica não parece uma boa solução para este problema. Eu vi um exemplo de implementação de DI usando herança múltipla, mas para mim é o mesmo que o método de fábrica. Também tentei usar a biblioteca Pinject, mas ela requer a construção inicial de um gráfico, que precisa ser salvo em algum lugar para ser acessado nos manipuladores de API.
Questão 2 (mais importante): como a injeção de dependência pode ser aplicada neste caso?
Respostas
Conforme observado nos comentários, uma dependência pode ser qualquer coisa que possa ser chamada e, portanto, uma classe também. A única ressalva no último caso é que a classe será apenas inicializada (ou seja, apenas a função init (..) será chamada).
Então, para ter uma classe como dependência, como no exemplo de https://fastapi.tiangolo.com/tutorial/dependencies/classes-as-dependencies/#shortcutvocê só precisa chamar as funções de destino dentro do init e definir os valores como atributos da classe.
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
QUESTÃO 2
NB
Esta é uma questão de modelagem. Aqui, proponho o que acredito ser adequado do meu ponto de vista. Não precisa necessariamente ser a abordagem melhor ou mais simples.
Respondendo a sua segunda pergunta, eu não aconselharia essas dependências complexas. Se as dependências estiverem no nível do roteador, você pode simplesmente adicioná-las ao roteador, usando o parâmetro depends=[...]e fornecendo uma lista de classes / funções de dependência.
Como alternativa, você pode declarar todas as dependências como parâmetros de função do nó de extremidade, como fez para a fábrica. Esse método pode fazer com que grandes pedaços de código sejam copiados e colados, portanto, aconselho a abordagem acima.
Se você precisar processar os parâmetros de dados, adicione-os à solicitação e os acesse de dentro do terminal. Consulte FastAPI obter ID do usuário da chave de API para um exemplo mínimo.