Merge branch 'master' into child-injectors
Conflicts: injector_test.py
This commit is contained in:
commit
3de2f605fc
|
@ -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
|
||||||
|
|
42
injector.py
42
injector.py
|
@ -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__'):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
Loading…
Reference in New Issue