Create AssistedFactoryProvider

This commit is contained in:
Jakub Stasiak 2012-12-05 00:56:08 +00:00
parent ca7c603346
commit 53dd617a29
3 changed files with 80 additions and 3 deletions

View File

@ -115,6 +115,7 @@ A means of providing an instance of a type. Built-in providers include
``ClassProvider`` (creates a new instance from a class),
``InstanceProvider`` (returns an existing instance directly) and
``CallableProvider`` (provides an instance by calling a function).
``AssistedFactoryProvider`` (provides a factory which can be used for assisted injection)
Scope
-----
@ -267,6 +268,44 @@ Or transitively::
>>> user.description
'Sherlock is a man of astounding insight'
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 User(object):
... def __init__(self, name):
... self.name = name
>>> class UserUpdater(object):
... @inject(db = Database)
... def __init__(self, db, user):
... pass
You may want to have database connection ``db`` injected into ``UserUpdater`` constructor,
but in the same time provide ``user`` object by yourself, and assuming that ``user`` object
is a value object and there's many users in your application it doesn't make much sense
to inject objects of class ``User``.
In this situation there's technique called Assisted injection::
>>> UserUpdaterFactory = Key('UserUpdaterFactory')
>>> def module(binder):
... binder.bind(UserUpdaterFactory, to=AssistedFactoryProvider(UserUpdater))
... injector = Injector(module)
... factory = injector.get(UserUpdaterFactory)
... user = User('John')
... user_updater = factory.create(user=user)
This way we don't make ``UserUpdater`` directly injectable - we provide injectable factory.
Such factory has ``create(**kwargs)`` method which takes non-injectable parameters, combines
them with injectable dependencies of ``UserUpdater`` and calls ``UserUpdater`` initializer
using all of them.
More information on this topic: `"How to use Google Guice to create objects that require parameters?" on Stack Overflow <http://stackoverflow.com/questions/996300/how-to-use-google-guice-to-create-objects-that-require-parameters>`_
Scopes
======

View File

@ -124,6 +124,23 @@ class MapBindProvider(ListOfProviders):
map.update(provider.get())
return map
class AssistedFactoryProvider(Provider):
class AssistedFactory(object):
def __init__(self, injector, cls):
self._injector = injector
self._cls = cls
def create(self, **kwargs):
return self._injector.create_object(self._cls, additional_kwargs=kwargs)
_injector = None
def __init__(self, cls):
self._cls = cls
def get(self):
return self.AssistedFactory(self._injector, self._cls)
# These classes are used internally by the Binder.
class BindingKey(tuple):
@ -234,6 +251,8 @@ class Binder(object):
def provider_for(self, interface, to=None):
if isinstance(to, Provider):
if isinstance(to, AssistedFactoryProvider):
to._injector = self.injector
return to
elif isinstance(to, (types.FunctionType, types.LambdaType,
types.MethodType, types.BuiltinFunctionType,
@ -449,15 +468,17 @@ class Injector(object):
'with Binder.bind_scope(scope_cls)' % e)
return scope_instance.get(key, binding.provider).get()
def create_object(self, cls):
def create_object(self, cls, additional_kwargs=None):
"""Create a new instance, satisfying any dependencies on cls."""
additional_kwargs = additional_kwargs or {}
instance = cls.__new__(cls)
try:
self.install_into(instance)
except AttributeError:
# Some builtin types can not be modified.
pass
instance.__init__()
instance.__init__(**additional_kwargs)
return instance
def install_into(self, instance):

View File

@ -16,7 +16,7 @@ import threading
import pytest
from injector import (Binder, Injector, Scope, InstanceProvider, ClassProvider,
from injector import (AssistedFactoryProvider, Binder, Injector, Scope, InstanceProvider, ClassProvider,
inject, singleton, threadlocal, UnsatisfiedRequirement,
CircularDependency, Module, provides, Key, extends, SingletonScope,
ScopeDecorator, with_injector)
@ -592,3 +592,20 @@ def test_binder_provider_for_type_with_metaclass():
binder = Injector().binder
assert (isinstance(binder.provider_for(A, None).get(), A))
def test_assisted_factory_provider_works():
class A(object):
@inject(aaa=str)
def __init__(self, aaa, bbb):
self.aaa = aaa
self.bbb = bbb
AFactory = Key('AFactory')
def conf(binder):
binder.bind(AFactory, to=AssistedFactoryProvider(A))
injector = Injector(conf)
factory = injector.get(AFactory)
a = factory.create(bbb=123)
assert (a.aaa == str())
assert (a.bbb == 123)