ets-labs / python-dependency-injector

Dependency injection framework for Python
https://python-dependency-injector.ets-labs.org/
BSD 3-Clause "New" or "Revised" License
3.89k stars 304 forks source link

Typing and code autocomplete with multi-containers setup #590

Open rustam-ashurov-mcx opened 2 years ago

rustam-ashurov-mcx commented 2 years ago

Hi, I decided to switch to set up with multiple Containers used in one main container. Copied code from example: https://python-dependency-injector.ets-labs.org/examples/application-multiple-containers.html

So my code looks merely the same with one container that aggregates many other:

class Container(containers.DeclarativeContainer):
    child = providers.Container(ChildContainer)

And having FastAPI as the main framework I inject dependencies in a router like this:

@router.get("")
@inject
async def test(logger: Logger = Depends(Provide[Container.child.logger])):
    logger.info("Oh, hi Mark")

The issue is that Container.child is of Container[ChildContainer] type and even having logger on ChildContainer it is not visible anymore, therefore VS Code doesn't suggest autocomplete options (also not sure whether MyPy will be happy about it, but it is another story)

Is there a legal and safe way to inject dependencies from ChildContainer (when it is provided via another parent container) and have all beauty of a language server functionality?

rustam-ashurov-mcx commented 2 years ago

Example:

from dependency_injector import containers, providers
from dependency_injector.wiring import Provide, inject

class Service:
    def hello(self):
        print("Hello World")

class ServiceFactory:
    def create_service(self):
        return Service()

class InnerContainer(containers.DeclarativeContainer):
    service_factory = providers.Factory(ServiceFactory)
    service = providers.Callable(service_factory().create_service)

class Container(containers.DeclarativeContainer):
    inner = providers.Container(InnerContainer)

@inject
def main(service: Service = Provide[Container.inner.service]) -> None: # .service is Any here and auto-completion fails to suggest proper field name 
    service.hello()

container = Container()
container.wire(modules=[__name__])
main()
lmzwk commented 1 year ago

I also have this problem. VScode cannot provide typing hints for Container [T]. Use . container in Container [T] to solve this problem


from dependency_injector import containers, providers
from dependency_injector.wiring import Provide, inject

class UserRepo:
    pass

class UserService:
    def __init__(self, repo: UserRepo) -> None:
        self._repo: UserRepo = repo

    def test(self):
        print("hello")

class Repositories(containers.DeclarativeContainer):
    user_repo = providers.Singleton(UserRepo)

class Services(containers.DeclarativeContainer):
    repositories = providers.Container(Repositories)

    user_service = providers.Singleton(UserService, repo=repositories.container.user_repo) # .user_repo typing hints

class Application(containers.DeclarativeContainer):

    repositories = providers.Container(Repositories)
    services = providers.Container(Services, repositories=repositories)

@inject
def main( user_service: UserService = Provide[Application.services.container.user_service]):  # .user_service   typing hints
    user_service.test()

if __name__ == "__main__":
    container = Application()
    container.wire(modules=[__name__])

    main()