From 1cdfb4437b97718d6ceb1adf0f220c9eb2b19cbe Mon Sep 17 00:00:00 2001 From: Jakub Stasiak Date: Sat, 3 Nov 2012 23:56:43 +0100 Subject: [PATCH 1/4] .gitignore pycache --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c18dd8d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__/ From 966271b24729198d9c6b5c610e36e8cebc724555 Mon Sep 17 00:00:00 2001 From: Jakub Stasiak Date: Sat, 3 Nov 2012 23:58:16 +0100 Subject: [PATCH 2/4] Make Injector accept also classes as modules (it will instantiate them) --- injector.py | 11 +++++++++-- injector_test.py | 8 ++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/injector.py b/injector.py index 14bfda7..10ad168 100644 --- a/injector.py +++ b/injector.py @@ -393,9 +393,13 @@ class Injector(object): def __init__(self, modules=None, auto_bind=True): """Construct a new Injector. - :param modules: A callable, or list of callables, used to configure the + :param modules: A callable, class, or list of callables/classes, used to configure the Binder associated with this Injector. Typically these - callables will be subclasses of :class:`Module` . + callables will be subclasses of :class:`Module`. + + In case of class, it's instance will be created using parameterless + constructor before the configuration process begins. + Signature is ``configure(binder)``. :param auto_bind: Whether to automatically bind missing types. """ @@ -420,6 +424,9 @@ class Injector(object): self.binder.bind(Binder, to=self.binder) # Initialise modules for module in modules: + if isinstance(module, type): + module = module() + module(self.binder) def get(self, interface, annotation=None, scope=None): diff --git a/injector_test.py b/injector_test.py index 0ac21fd..95ed42f 100644 --- a/injector_test.py +++ b/injector_test.py @@ -329,6 +329,14 @@ def test_module_provides(): injector = Injector(module) assert (injector.get(str, annotation='name') == 'Bob') +def test_module_class_gets_instantiated(): + name = 'Meg' + class MyModule(Module): + def configure(self, binder): + binder.bind(str, to = name) + + injector = Injector(MyModule) + assert (injector.get(str) == name) def test_bind_using_key(): Name = Key('name') From c06b9c26703e52bb6e0eb7f8a46a52779e86b887 Mon Sep 17 00:00:00 2001 From: Jakub Stasiak Date: Sat, 3 Nov 2012 23:57:49 +0100 Subject: [PATCH 3/4] Create Injector.install_into() function and with_injector decorator --- README.rst | 29 +++++++++++++++++++++++++++++ injector.py | 26 +++++++++++++++++++++++++- injector_test.py | 16 +++++++++++++++- 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index d088308..9c590e1 100644 --- a/README.rst +++ b/README.rst @@ -331,6 +331,35 @@ scope is "entered" in some low-level code by calling a method on the scope instance that creates this cache. Once the request is complete, the scope is "left" and the cache cleared. +Tests +===== + +When you use unit test framework such as ``unittest2`` or ``nose`` you can also +profit from ``injector``. However, manually creating injectors and test classes +can be quite annoying. There is, however, ``with_injector`` method decorator which +has parameters just as ``Injector`` construtor and installes configured injector into +class instance on the time of method call:: + + >>> from injector import Module, with_injector + >>> class UsernameModule(Module): + ... def configure(self, binder): + ... binder.bind(str, 'Maria') + ... + >>> class TestSomethingClass(object): + ... @with_injector(UsernameModule()) + ... def setup(self): + ... pass + ... + ... @inject(username = str) + ... def test_username(self, username): + ... assert (username == 'Maria') + +*Each* method call re-initializes ``Injector`` - if you want to you can also put +``with_injector`` decorator on class constructor. + +After such call all ``inject``-decorated methods will work just as you'd expect +them to work. + Footnote ======== This framework is similar to snake-guice, but aims for simplification. diff --git a/injector.py b/injector.py index 10ad168..8843d70 100644 --- a/injector.py +++ b/injector.py @@ -456,13 +456,37 @@ class Injector(object): """Create a new instance, satisfying any dependencies on cls.""" instance = cls.__new__(cls) try: - instance.__injector__ = self + self.install_into(instance) except AttributeError: # Some builtin types can not be modified. pass instance.__init__() return instance + def install_into(self, instance): + ''' + Puts injector reference in given object. + ''' + instance.__injector__ = self + +def with_injector(modules = None, auto_bind = True): + ''' + Decorator for a method. Installs Injector object which the method belongs + to before the decorated method is executed. + + Parameters are the same as for Injector constructor. + ''' + def wrapper(f): + @functools.wraps(f) + def setup(self_, *args, **kwargs): + injector = Injector(modules, auto_bind) + injector.install_into(self_) + return f(self_, *args, **kwargs) + + return setup + + return wrapper + def provides(interface, annotation=None, scope=None): """Decorator for :class:`Module` methods, registering a provider of a type. diff --git a/injector_test.py b/injector_test.py index 95ed42f..db692b7 100644 --- a/injector_test.py +++ b/injector_test.py @@ -19,7 +19,7 @@ import pytest from injector import (Binder, Injector, Scope, InstanceProvider, ClassProvider, inject, singleton, threadlocal, UnsatisfiedRequirement, CircularDependency, Module, provides, Key, extends, SingletonScope, - ScopeDecorator) + ScopeDecorator, with_injector) class TestBasicInjection(object): @@ -338,6 +338,20 @@ def test_module_class_gets_instantiated(): injector = Injector(MyModule) assert (injector.get(str) == name) +def test_with_injector_works(): + name = 'Victoria' + def configure(binder): + binder.bind(str, to = name) + + class Aaa(object): + @with_injector(configure) + @inject(username = str) + def __init__(self, username): + self.username = username + + aaa = Aaa() + assert (aaa.username == name) + def test_bind_using_key(): Name = Key('name') Age = Key('age') From 50d9527788e83565b6c41bf2adef396c95987801 Mon Sep 17 00:00:00 2001 From: Jakub Stasiak Date: Sun, 4 Nov 2012 02:46:33 +0100 Subject: [PATCH 4/4] Fix some cosmetic issues --- README.rst | 7 ++++++- injector.py | 12 ++++++------ injector_test.py | 6 +++--- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/README.rst b/README.rst index 9c590e1..7dbbc82 100644 --- a/README.rst +++ b/README.rst @@ -245,6 +245,11 @@ dependency graph:: >>> from injector import Injector >>> injector = Injector([UserModule(), UserAttributeModule()]) +You can also pass classes instead of instances to ``Injector``, it will +instantiate them for you:: + + >>> injector = Injector([UserModule, UserAttributeModule]) + The injector can then be used to acquire instances of a type, either directly:: >>> injector.get(Name) @@ -350,7 +355,7 @@ class instance on the time of method call:: ... def setup(self): ... pass ... - ... @inject(username = str) + ... @inject(username=str) ... def test_username(self, username): ... assert (username == 'Maria') diff --git a/injector.py b/injector.py index 8843d70..0ad40b0 100644 --- a/injector.py +++ b/injector.py @@ -464,22 +464,22 @@ class Injector(object): return instance def install_into(self, instance): - ''' + """ Puts injector reference in given object. - ''' + """ instance.__injector__ = self -def with_injector(modules = None, auto_bind = True): - ''' +def with_injector(*injector_args, **injector_kwargs): + """ Decorator for a method. Installs Injector object which the method belongs to before the decorated method is executed. Parameters are the same as for Injector constructor. - ''' + """ def wrapper(f): @functools.wraps(f) def setup(self_, *args, **kwargs): - injector = Injector(modules, auto_bind) + injector = Injector(*injector_args, **injector_kwargs) injector.install_into(self_) return f(self_, *args, **kwargs) diff --git a/injector_test.py b/injector_test.py index db692b7..1a02e7b 100644 --- a/injector_test.py +++ b/injector_test.py @@ -333,7 +333,7 @@ def test_module_class_gets_instantiated(): name = 'Meg' class MyModule(Module): def configure(self, binder): - binder.bind(str, to = name) + binder.bind(str, to=name) injector = Injector(MyModule) assert (injector.get(str) == name) @@ -341,11 +341,11 @@ def test_module_class_gets_instantiated(): def test_with_injector_works(): name = 'Victoria' def configure(binder): - binder.bind(str, to = name) + binder.bind(str, to=name) class Aaa(object): @with_injector(configure) - @inject(username = str) + @inject(username=str) def __init__(self, username): self.username = username