Merge pull request #3 from jstasiak/master
Add `@with_injector` method decorator useful for testing.
This commit is contained in:
commit
e3f9c29462
|
@ -0,0 +1 @@
|
|||
__pycache__/
|
34
README.rst
34
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)
|
||||
|
@ -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.
|
||||
|
|
37
injector.py
37
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):
|
||||
|
@ -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.
|
||||
|
|
|
@ -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')
|
||||
|
|
Loading…
Reference in New Issue