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
|
||||
constructors. Let's have for example::
|
||||
|
||||
>>> Database = Key('Database')
|
||||
>>> class Database(object): pass
|
||||
|
||||
>>> class User(object):
|
||||
... def __init__(self, name):
|
||||
... self.name = name
|
||||
|
|
42
injector.py
42
injector.py
|
@ -104,7 +104,6 @@ class ClassProvider(Provider):
|
|||
def get(self):
|
||||
return self.injector.create_object(self._cls)
|
||||
|
||||
|
||||
class CallableProvider(Provider):
|
||||
"""Provides something using a callable."""
|
||||
|
||||
|
@ -272,8 +271,8 @@ class Binder(object):
|
|||
elif issubclass(type(to), type):
|
||||
return ClassProvider(to, self.injector)
|
||||
elif isinstance(interface, AssistedBuilder):
|
||||
self.injector.install_into(interface)
|
||||
return InstanceProvider(interface)
|
||||
builder = AssistedBuilderImplementation(interface.interface, self.injector)
|
||||
return InstanceProvider(builder)
|
||||
elif isinstance(to, interface):
|
||||
return InstanceProvider(to)
|
||||
elif issubclass(type(interface), type):
|
||||
|
@ -510,7 +509,11 @@ class Injector(object):
|
|||
try:
|
||||
instance.__init__(**additional_kwargs)
|
||||
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
|
||||
|
||||
def install_into(self, instance):
|
||||
|
@ -706,6 +709,10 @@ def Annotation(name):
|
|||
class BaseKey(object):
|
||||
"""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):
|
||||
"""Create a new type key.
|
||||
|
@ -734,13 +741,30 @@ def Key(name):
|
|||
pass
|
||||
return type(name, (BaseKey,), {})
|
||||
|
||||
class AssistedBuilder(object):
|
||||
def __init__(self, cls):
|
||||
self.cls = cls
|
||||
class AssistedBuilder(tuple):
|
||||
def __new__(cls, interface):
|
||||
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):
|
||||
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):
|
||||
if hasattr(c, '__name__'):
|
||||
|
|
|
@ -20,7 +20,7 @@ import pytest
|
|||
from injector import (Binder, CallError, Injector, Scope, InstanceProvider, ClassProvider,
|
||||
inject, singleton, threadlocal, UnsatisfiedRequirement,
|
||||
CircularDependency, Module, provides, Key, extends, SingletonScope,
|
||||
ScopeDecorator, with_injector, AssistedBuilder)
|
||||
ScopeDecorator, with_injector, AssistedBuilder, BindingKey)
|
||||
|
||||
|
||||
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)
|
||||
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():
|
||||
A, B = prepare_basic_injection()
|
||||
|
@ -690,6 +694,31 @@ def test_assisted_builder_works_when_injected():
|
|||
x = injector.get(X)
|
||||
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):
|
||||
def setup(self):
|
||||
def configure(binder):
|
||||
|
|
Loading…
Reference in New Issue