python-dependency-injector
python-dependency-injector copied to clipboard
Package level decoupling with FastAPI
version: 4.41.0 fastapi version: 0.110.2
In the project I developed with Fastapi, I want to use di as decoupled example at the package level. I have OwnerContainer in owner package and i have ApplicationContainer in top level main package.
class OwnerContainer(containers.DeclarativeContainer):
db_session = providers.Dependency()
owner_service = providers.Factory(
OwnerService,
db_session=db_session,
)
class ApplicationContainer(containers.DeclarativeContainer):
config = providers.Configuration()
database_session = providers.Resource(
get_db_session
)
owner_package = providers.Container(
OwnerContainer,
db_session=database_session
)
I want to use OwnerService in my owner router
@owner_router.get("/", response_model=List[GetOwnerSchema])
@inject
async def get_owners(service: OwnerService = Depends(Provide[OwnerContainer.owner_service])):
try:
result: List[GetOwnerSchema] = await service.get_owners()
return result
except Exception as e:
print(e)
raise HTTPException(status_code=500)
I got error 'Provide' object has no attribute 'get_owners'
According to my research, this is related to the dependency injector not being able to wire. I can't figure out whether this is related to a bug or whether I made a mistake, but I think I used it in accordance with the examples. Has anyone encountered this type of problem? I need some help.
Have a similar issue and can confirm,
I want to make a library with some REST API routers for FastAPI application and with some DI bindings pre-configured:
Here is the LoggignContainer where I have logging-related dependencies:
class LoggingContainer(containers.DeclarativeContainer):
logger = providers.Singleton(MyLogger)
And here is a main BaseContainer where I aggregated all smaller containers from my library:
class BaseContainer(containers.DeclarativeContainer):
app = providers.Container(AppContainer)
logging = providers.Container(LoggingContainer)
web_api = providers.Container(WebApiContainer, app=app, logging=logging)
Here is my library router, very simple one and which uses DI from my lib, specifically my logger:
router = APIRouter()
tag = "Ping"
@router.get("/ping", tags=[tag], status_code=status.HTTP_200_OK)
@inject
async def ping_async(logger: Logger = Depends(Provide[LoggingContainer.logger])):
logger.trace("Ping")
return
In a client code a client Container was defined, it has it's own application related dependencies + inherits from my BaseContainer (alternatively base container could be added via providers.Container(...) what is up to user):
class AppContainer(BaseContainer):
hello_world_service = providers.Factory(HelloWorldService) <- some application related registration
Now client code need to register the library router (typical registration via FastAPI methods) + wire the router :
if __name__ == "__main__":
container = Container()
... here happens FastAPI code and registration of router there ....
container.wire(modules=[ping_router])
container.wire(modules=[__name__])
configure_services()
run_app()
My expectations are Logger from my library will be injected in my router but the results is: "AttributeError: 'Provide' object has no attribute logger"
I tried many combinations and changed code many ways but unless I directly use AppContainer in my library code (what is impossible to expect beyond local examples and testing since I can not know what is App level container in advance) wiring doesn't work
I also tried to make router initialization based on the factory method, this way I provided instance of the AppContainer which was inherited from BaseContainer:
def createPingRouter(baseContainer: BaseContainer):
router = APIRouter()
tag = "Ping"
@router.get("/ping", tags=[tag], status_code=status.HTTP_200_OK)
@inject
async def get_examples_async(logger: Logger = Depends(Provide[BaseContainer.logging.logger])):
logger.trace("Ping")
return
return router
So client code could provide me actual container, and in debug mode I have seen that instance is right one and it has all providers inside. However results was the same:
"AttributeError: 'Provide' object has no attribute logger"
Is there any solution for this issue?
Try setting WiringConfiguration on your Container.
class Container(containers.DeclarativeContainer):
wiring_config = containers.WiringConfiguration(packages=["yourapp.routers"])
https://github.com/ets-labs/python-dependency-injector/blob/master/examples/miniapps/fastapi/giphynavigator/containers.py#L10
Try setting
WiringConfigurationon yourContainer.class Container(containers.DeclarativeContainer): wiring_config = containers.WiringConfiguration(packages=["yourapp.routers"])https://github.com/ets-labs/python-dependency-injector/blob/master/examples/miniapps/fastapi/giphynavigator/containers.py#L10
Thank you for your response. I upgrded my python version 3.12, when library is upgraded for python 3.12, i will try your solution.
@ilterisYucel You have 2 container: ApplicationContainer, OwnerContainer.
You can not have 2 global container at same time. So you can call the OwnerContainer like below, ( wiring_config also needs on ApplicationContainer)
# not working
Depends(Provide[OwnerContainer.owner_service])
# working
Depends(Provide[owner_package.owner_service])
# or
Depends(Provide["owner_package.owner_service"])
# or
Depends(Provide[ApplicationContainer.owner_package.owner_service])
@rustam-ashurov-mcx
You have 2 container: BaseContainer for global scope , LoggingContainer for local scope
You can call the LoggingContainer like below, ( wiring_config including router.py also needed on BaseContainer )
# not working, LoggingContainer is not Global container .
Depends(Provide[LoggingContainer. logger])
# working
Depends(Provide[logging.logger])
# or
Depends(Provide["logging.logger"])
# or
Depends(Provide[BaseContainer.logging.logger])
@ilterisYucel You have 2 container: ApplicationContainer, OwnerContainer.
You can not have 2 global container at same time. So you can call the OwnerContainer like below, ( wiring_config also needs on ApplicationContainer)
# not working Depends(Provide[OwnerContainer.owner_service]) # working Depends(Provide[owner_package.owner_service]) # or Depends(Provide["owner_package.owner_service"]) # or Depends(Provide[ApplicationContainer.owner_package.owner_service])
Thank you for your suggestion. I will try as you suggested.