Merge pull request #3 from jstasiak/master

Add `@with_injector` method decorator useful for testing.
This commit is contained in:
Alec Thomas 2012-11-16 06:14:47 -08:00
commit e3f9c29462
4 changed files with 92 additions and 4 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
__pycache__/

View File

@ -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)
@ -331,6 +336,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.

View File

@ -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):
@ -449,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(*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(*injector_args, **injector_kwargs)
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.

View File

@ -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):
@ -329,6 +329,28 @@ 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_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')