modern-python / that-depends

DI-framework, inspired by python-dependency-injector, but without wiring. Python 3.12 is supported
https://that-depends.readthedocs.io/
MIT License
156 stars 12 forks source link

Is That-Depends could be used in a Library? #50

Closed rustam-ashurov-mcx closed 3 months ago

rustam-ashurov-mcx commented 3 months ago

Hey mates, On the main page it's said that this lib is good to switch from dependency-injector so I'm hyped already. But can this library help with wiring FastAPI routers which are defined in a library and should be used eventually in a customer application code?

To elaborate a bit more about my particular example (which fails with other DI libraries):

I want to make a library with some REST API routers for FastAPI application and with some DI bindings to pre-configured so my routers will receive my services:

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, it then can be used in the client code to register (for me) and access (for client) my services if needed:

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 which should automate some work, very simple one and which uses services registred in IoC Contaier 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 some steps should be done, firstly a client Container to be defined, it has it's own application-related dependencies + inherits from my BaseContainer:

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

So now I started to check you examples and in the FastAPI example I see such lines of code:

@ROUTER.get("/decks/")
async def list_decks(
    decks_repo: DecksRepository = fastapi.Depends(ioc.IOCContainer.decks_repo),
) -> schemas.Decks:
    objects = await decks_repo.all()
    return typing.cast(schemas.Decks, {"items": objects})

Where ioc.IOCContainer.decks_repo is a reference to the container in the application code and I decided to ask in advance.

Is it possible in your lib to:

Thank you 🙂

lesnik512 commented 3 months ago

@rustam-ashurov-mcx Hello, Rustam!

Seems like you doing something like this https://python-dependency-injector.ets-labs.org/examples/application-multiple-containers.html. I'm personally don't like of idea of multiple containers and can't give advices for such use cases. But that-depends can be used with multiple containers like this https://that-depends.readthedocs.io/introduction/multiple-containers.html