alecthomas/injector — pull request #5 by jstasiak (2012-11-30 10:40:52+00:00)
Title: Add CallError exception class to better show why injection/instantiation fails

When injector fails to provide dependency or instantiate a class it's hard to understand from the stack trace what went wrong, what was injected and what was not. This patch makes stack traces a bit more readable.

It's not an ideal, maybe the exception should be slightly less verbose (to not include object/function identifier for example).

Example 1:

```
from injector import inject, Injector

class A(object):
def __init__(self, parameter):
self.parameter = parameter

class B(object):
@inject(a = A)
def __init__(self, a):
pass

class C(object):
@inject(a = A)
def __init__(self, a):
pass

@inject(b = B)
def fun(self, b, x):
pass

def main():
injector = Injector()
c = injector.get(C)
c.fun()

if __name__ == '__main__':
main()
```

Output before patch:
```
Traceback (most recent call last):
File "asd.py", line 30, in
main()
File "asd.py", line 26, in main
c = injector.get(C)
File "/home/a/ve/lib64/python2.7/site-packages/injector.py", line 462, in get
return scope_instance.get(key, binding.provider).get()
File "/home/a/ve/lib64/python2.7/site-packages/injector.py", line 86, in get
return self.injector.create_object(self._cls)
File "/home/a/ve/lib64/python2.7/site-packages/injector.py", line 472, in create_object
instance.__init__()
File "/home/a/ve/lib64/python2.7/site-packages/injector.py", line 629, in inject
owner_key=self_.__class__,
File "/home/a/ve/lib64/python2.7/site-packages/injector.py", line 507, in args_to_inject
annotation=key.annotation)
File "/home/a/ve/lib64/python2.7/site-packages/injector.py", line 462, in get
return scope_instance.get(key, binding.provider).get()
File "/home/a/ve/lib64/python2.7/site-packages/injector.py", line 86, in get
return self.injector.create_object(self._cls)
File "/home/a/ve/lib64/python2.7/site-packages/injector.py", line 472, in create_object
instance.__init__()
TypeError: __init__() takes exactly 2 arguments (1 given)
```

Output after patch:
```
Traceback (most recent call last):
File "ex1.py", line 30, in
main()
File "ex1.py", line 26, in main
c = injector.get(C)
File "/home/a/ve/lib64/python2.7/site-packages/injector.py", line 462, in get
return scope_instance.get(key, binding.provider).get()
File "/home/a/ve/lib64/python2.7/site-packages/injector.py", line 86, in get
return self.injector.create_object(self._cls)
File "/home/a/ve/lib64/python2.7/site-packages/injector.py", line 473, in create_object
instance.__init__()
File "/home/a/ve/lib64/python2.7/site-packages/injector.py", line 635, in inject
owner_key=self_.__class__,
File "/home/a/ve/lib64/python2.7/site-packages/injector.py", line 510, in args_to_inject
annotation=key.annotation)
File "/home/a/ve/lib64/python2.7/site-packages/injector.py", line 462, in get
return scope_instance.get(key, binding.provider).get()
File "/home/a/ve/lib64/python2.7/site-packages/injector.py", line 86, in get
return self.injector.create_object(self._cls)
File "/home/a/ve/lib64/python2.7/site-packages/injector.py", line 475, in create_object
raise CallError(None, instance.__init__, [], {}, e)
injector.CallError: Call to >() failed: __init__() takes exactly 2 arguments (1 given)
```

Example 2:
```
from injector import inject, Injector

class A(object):
@inject(parameter = dict)
def __init__(self, parameter):
self.parameter = parameter

class B(object):
@inject(a = A)
def __init__(self, a):
pass

class C(object):
@inject(a = A)
def __init__(self, a):
pass

@inject(b = B)
def fun(self, b, x):
pass

def main():
injector = Injector()
c = injector.get(C)
c.fun()

if __name__ == '__main__':
main()
```

Output before patch:
```
Traceback (most recent call last):
File "asd.py", line 31, in
main()
File "asd.py", line 28, in main
c.fun()
File "/home/a/ve/lib64/python2.7/site-packages/injector.py", line 632, in inject
return f(self_, *args, **dependencies)
TypeError: fun() takes exactly 3 arguments (2 given)
```

Output after patch:
```
Traceback (most recent call last):
File "ex2.py", line 31, in
main()
File "ex2.py", line 28, in main
c.fun()
File "/home/a/ve/lib64/python2.7/site-packages/injector.py", line 641, in inject
raise CallError(self_, f, args, dependencies, e)
injector.CallError: Call to <__main__.C object at 0x8d6390>.(b=<__main__.B object at 0x8d6490>) failed: fun() takes exactly 3 arguments (2 given)
```