Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Package level decoupling with FastAPI #797

Open
ilterisYucel opened this issue May 1, 2024 · 7 comments
Open

Package level decoupling with FastAPI #797

ilterisYucel opened this issue May 1, 2024 · 7 comments

Comments

@ilterisYucel
Copy link

ilterisYucel commented May 1, 2024

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.

@rustam-ashurov-mcx
Copy link

rustam-ashurov-mcx commented Jul 10, 2024

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?

@krzysztofkusmierczyk
Copy link

krzysztofkusmierczyk commented Aug 15, 2024

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

@ilterisYucel
Copy link
Author

ilterisYucel commented Aug 16, 2024

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

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.

@Jahongir-Qurbonov
Copy link

when library is upgraded for python 3.12

A release for python 3.12 is now available

@rumbarum
Copy link

rumbarum commented Sep 14, 2024

@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])

@rumbarum
Copy link

rumbarum commented Sep 14, 2024

@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
Copy link
Author

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants