Merge branch 'master' into child-injectors

Conflicts:
	injector_test.py
This commit is contained in:
Jakub Stasiak 2013-02-18 17:41:41 +00:00
commit 3de2f605fc
3 changed files with 65 additions and 11 deletions

View File

@ -272,7 +272,8 @@ Assisted injection
Sometimes there are classes that have injectable and non-injectable parameters in their Sometimes there are classes that have injectable and non-injectable parameters in their
constructors. Let's have for example:: constructors. Let's have for example::
>>> Database = Key('Database') >>> class Database(object): pass
>>> class User(object): >>> class User(object):
... def __init__(self, name): ... def __init__(self, name):
... self.name = name ... self.name = name

View File

@ -104,7 +104,6 @@ class ClassProvider(Provider):
def get(self): def get(self):
return self.injector.create_object(self._cls) return self.injector.create_object(self._cls)
class CallableProvider(Provider): class CallableProvider(Provider):
"""Provides something using a callable.""" """Provides something using a callable."""
@ -272,8 +271,8 @@ class Binder(object):
elif issubclass(type(to), type): elif issubclass(type(to), type):
return ClassProvider(to, self.injector) return ClassProvider(to, self.injector)
elif isinstance(interface, AssistedBuilder): elif isinstance(interface, AssistedBuilder):
self.injector.install_into(interface) builder = AssistedBuilderImplementation(interface.interface, self.injector)
return InstanceProvider(interface) return InstanceProvider(builder)
elif isinstance(to, interface): elif isinstance(to, interface):
return InstanceProvider(to) return InstanceProvider(to)
elif issubclass(type(interface), type): elif issubclass(type(interface), type):
@ -510,7 +509,11 @@ class Injector(object):
try: try:
instance.__init__(**additional_kwargs) instance.__init__(**additional_kwargs)
except TypeError as e: except TypeError as e:
raise CallError(instance, instance.__init__.__func__, (), additional_kwargs, e) # The reason why getattr() fallback is used here is that
# __init__.__func__ apparently doesn't exist for Key-type objects
raise CallError(instance,
getattr(instance.__init__, '__func__', instance.__init__),
(), additional_kwargs, e)
return instance return instance
def install_into(self, instance): def install_into(self, instance):
@ -706,6 +709,10 @@ def Annotation(name):
class BaseKey(object): class BaseKey(object):
"""Base type for binding keys.""" """Base type for binding keys."""
def __init__(self):
raise Exception('Instantiation of %s prohibited - it is derived from BaseKey '
'so most likely you should bind it to something.' % (self.__class__,))
def Key(name): def Key(name):
"""Create a new type key. """Create a new type key.
@ -734,13 +741,30 @@ def Key(name):
pass pass
return type(name, (BaseKey,), {}) return type(name, (BaseKey,), {})
class AssistedBuilder(object): class AssistedBuilder(tuple):
def __init__(self, cls): def __new__(cls, interface):
self.cls = cls return super(AssistedBuilder, cls).__new__(cls, (interface,))
@property
def interface(self):
return self[0]
class AssistedBuilderImplementation(object):
def __init__(self, interface, injector):
self.interface = interface
self.injector = injector
def build(self, **kwargs): def build(self, **kwargs):
return self.__injector__.create_object(self.cls, additional_kwargs=kwargs) key = BindingKey(self.interface, None)
binder = self.injector.binder
binding = binder.get_binding(None, key)
provider = binding.provider
try:
cls = provider._cls
except AttributeError:
raise Error('Assisted building works only with ClassProviders, '
'got %r for %r' % (provider, self.interface))
return self.injector.create_object(cls, additional_kwargs=kwargs)
def _describe(c): def _describe(c):
if hasattr(c, '__name__'): if hasattr(c, '__name__'):

View File

@ -20,7 +20,7 @@ import pytest
from injector import (Binder, CallError, Injector, Scope, InstanceProvider, ClassProvider, from injector import (Binder, CallError, Injector, Scope, InstanceProvider, ClassProvider,
inject, singleton, threadlocal, UnsatisfiedRequirement, inject, singleton, threadlocal, UnsatisfiedRequirement,
CircularDependency, Module, provides, Key, extends, SingletonScope, CircularDependency, Module, provides, Key, extends, SingletonScope,
ScopeDecorator, with_injector, AssistedBuilder) ScopeDecorator, with_injector, AssistedBuilder, BindingKey)
def prepare_basic_injection(): def prepare_basic_injection():
@ -59,6 +59,10 @@ def test_scopes_are_only_bound_to_root_injector():
parent.binder.bind(A, to=A, scope=singleton) parent.binder.bind(A, to=A, scope=singleton)
assert (parent.get(A) is child.get(A)) assert (parent.get(A) is child.get(A))
def test_key_cannot_be_instantiated():
with pytest.raises(Exception):
Interface = Key('Interface')
i = Interface()
def test_get_default_injected_instances(): def test_get_default_injected_instances():
A, B = prepare_basic_injection() A, B = prepare_basic_injection()
@ -690,6 +694,31 @@ def test_assisted_builder_works_when_injected():
x = injector.get(X) x = injector.get(X)
assert ((x.obj.a, x.obj.b) == (str(), 234)) assert ((x.obj.a, x.obj.b) == (str(), 234))
def test_assisted_builder_uses_bindings():
Interface = Key('Interface')
def configure(binder):
binder.bind(Interface, to=NeedsAssistance)
injector = Injector(configure)
builder = injector.get(AssistedBuilder(Interface))
x = builder.build(b=333)
assert ((type(x), x.b) == (NeedsAssistance, 333))
def test_assisted_builder_injection_is_safe_to_use_with_multiple_injectors():
class X(object):
@inject(builder=AssistedBuilder(NeedsAssistance))
def y(self, builder):
return builder
i1, i2 = Injector(), Injector()
b1 = i1.get(X).y()
b2 = i2.get(X).y()
assert ((b1.injector, b2.injector) == (i1, i2))
def test_assisted_builder_injection_uses_the_same_binding_key_every_time():
# if we have different BindingKey for every AssistedBuilder(...) we will get memory leak
gen_key = lambda: BindingKey(AssistedBuilder(NeedsAssistance), None)
assert gen_key() == gen_key()
class TestThreadSafety(object): class TestThreadSafety(object):
def setup(self): def setup(self):
def configure(binder): def configure(binder):