python-dependency-injector
python-dependency-injector copied to clipboard
improve stack trace when object construction fails
When the objects defined in the container cannot be constructed because the incorrect arguments have been passed, the stack trace that's produced when using the DI framework is not very clear. For example, modifying the example given in the docs to add an extra argument to ApiClient that is not passed to the construction of the object:
from unittest import mock
from dependency_injector import containers, providers
from dependency_injector.wiring import Provide, inject
class ApiClient:
def __init__(self, api_key: str, timeout: int, foo: str):
self._api_key = api_key
self._timeout = timeout
class Service:
def __init__(self, api_client: ApiClient):
self._api_client = api_client
class Container(containers.DeclarativeContainer):
config = providers.Configuration()
api_client = providers.Singleton(
ApiClient,
api_key=config.api_key,
timeout=config.timeout,
)
service = providers.Factory(
Service,
api_client=api_client,
)
@inject
def main(service: Service = Provide[Container.service]) -> None:
pass
if __name__ == "__main__":
container = Container()
container.config.api_key.from_env("API_KEY", default='foo')
container.config.timeout.from_env("TIMEOUT", as_=int, default=5)
container.wire(modules=[__name__])
main() # <-- dependency is injected automatically
with container.api_client.override(mock.Mock()):
main() # <-- overridden dependency is injected automatically
Produces a stack trace that's difficult to identify which object failed to be instantiated correctly:
Traceback (most recent call last):
File "./di_test.py", line 43, in <module>
main() # <-- dependency is injected automatically
File "src/dependency_injector/_cwiring.pyx", line 26, in dependency_injector._cwiring._get_sync_patched._patched
File "src/dependency_injector/providers.pyx", line 225, in dependency_injector.providers.Provider.__call__
File "src/dependency_injector/providers.pyx", line 2689, in dependency_injector.providers.Factory._provide
File "src/dependency_injector/providers.pxd", line 650, in dependency_injector.providers.__factory_call
File "src/dependency_injector/providers.pxd", line 577, in dependency_injector.providers.__call
File "src/dependency_injector/providers.pxd", line 445, in dependency_injector.providers.__provide_keyword_args
File "src/dependency_injector/providers.pxd", line 365, in dependency_injector.providers.__get_value
File "src/dependency_injector/providers.pyx", line 225, in dependency_injector.providers.Provider.__call__
File "src/dependency_injector/providers.pyx", line 3049, in dependency_injector.providers.Singleton._provide
File "src/dependency_injector/providers.pxd", line 650, in dependency_injector.providers.__factory_call
File "src/dependency_injector/providers.pxd", line 608, in dependency_injector.providers.__call
TypeError: __init__() missing 1 required positional argument: 'foo'
Compare this with the stack trace produced when constructing the object directly:
if __name__ == "__main__":
api_client = ApiClient('foo', 5)
this gives a much clearer stack trace as to where the error is
File "./di_test.py", line 49, in <module>
api_client = ApiClient('foo', 5)
TypeError: __init__() missing 1 required positional argument: 'foo'
It would be useful to at least print the object that failed to be constructed in the DI stack trace. When using the framework to constructed many applications across multiple containers, the issue of having to identify which object failed to be constructed becomes even more difficult.