From 7150e3ec6b6d9102a8dffa97db11c032fc09afea Mon Sep 17 00:00:00 2001 From: Jakub Stasiak Date: Wed, 16 Jan 2013 22:56:48 +0000 Subject: [PATCH 1/4] Make AssistedBuilder follow bindings when creating objects --- injector.py | 23 +++++++++++++++++------ injector_test.py | 9 +++++++++ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/injector.py b/injector.py index fe961f4..b6ddc5d 100644 --- a/injector.py +++ b/injector.py @@ -93,7 +93,6 @@ class ClassProvider(Provider): def get(self): return self.injector.create_object(self._cls) - class CallableProvider(Provider): """Provides something using a callable.""" @@ -484,7 +483,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): @@ -708,12 +711,20 @@ def Key(name): return type(name, (BaseKey,), {}) class AssistedBuilder(object): - def __init__(self, cls): - self.cls = cls + def __init__(self, interface): + self.interface = interface 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__'): diff --git a/injector_test.py b/injector_test.py index 177c432..49c42c4 100644 --- a/injector_test.py +++ b/injector_test.py @@ -664,3 +664,12 @@ def test_assisted_builder_works_when_injected(): injector = Injector() 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)) From ebbdb15b5b6c957fe5a3d6a964564ca74927a61e Mon Sep 17 00:00:00 2001 From: Jakub Stasiak Date: Mon, 28 Jan 2013 01:11:00 +0000 Subject: [PATCH 2/4] Prevent Key-created type instantiation --- README.rst | 3 ++- injector.py | 4 ++++ injector_test.py | 4 ++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 9fa700b..a463bbe 100644 --- a/README.rst +++ b/README.rst @@ -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 diff --git a/injector.py b/injector.py index f2d142a..7b071fe 100644 --- a/injector.py +++ b/injector.py @@ -692,6 +692,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. diff --git a/injector_test.py b/injector_test.py index e59d61a..6c018ca 100644 --- a/injector_test.py +++ b/injector_test.py @@ -35,6 +35,10 @@ def prepare_basic_injection(): return A, B +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() From 18c73652bcdd6e0868d48e89e60d262332aabcda Mon Sep 17 00:00:00 2001 From: Jakub Stasiak Date: Mon, 4 Feb 2013 22:18:21 +0000 Subject: [PATCH 3/4] Make AssistedBuilder injection safe to use with multiple injectors --- injector.py | 13 +++++++++---- injector_test.py | 11 +++++++++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/injector.py b/injector.py index 43d76ce..aa13e39 100644 --- a/injector.py +++ b/injector.py @@ -271,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): @@ -731,9 +731,14 @@ class AssistedBuilder(object): def __init__(self, interface): self.interface = interface +class AssistedBuilderImplementation(object): + def __init__(self, interface, injector): + self.interface = interface + self.injector = injector + def build(self, **kwargs): key = BindingKey(self.interface, None) - binder = self.__injector__.binder + binder = self.injector.binder binding = binder.get_binding(None, key) provider = binding.provider try: @@ -741,7 +746,7 @@ class AssistedBuilder(object): 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) + return self.injector.create_object(cls, additional_kwargs=kwargs) def _describe(c): if hasattr(c, '__name__'): diff --git a/injector_test.py b/injector_test.py index 3f2d097..d5b9e0b 100644 --- a/injector_test.py +++ b/injector_test.py @@ -679,6 +679,17 @@ def test_assisted_builder_uses_bindings(): 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)) + class TestThreadSafety(object): def setup(self): def configure(binder): From 82fae046666330201a39b7373024f63f968b4b5e Mon Sep 17 00:00:00 2001 From: Jakub Stasiak Date: Sun, 10 Feb 2013 00:40:59 +0000 Subject: [PATCH 4/4] Fix AssistedBuilder injection memory leak --- injector.py | 10 +++++++--- injector_test.py | 7 ++++++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/injector.py b/injector.py index aa13e39..3abf4e7 100644 --- a/injector.py +++ b/injector.py @@ -727,9 +727,13 @@ def Key(name): pass return type(name, (BaseKey,), {}) -class AssistedBuilder(object): - def __init__(self, interface): - self.interface = interface +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): diff --git a/injector_test.py b/injector_test.py index d5b9e0b..7a16814 100644 --- a/injector_test.py +++ b/injector_test.py @@ -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(): @@ -690,6 +690,11 @@ def test_assisted_builder_injection_is_safe_to_use_with_multiple_injectors(): 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):