Make (Class)AssistedBuilder and ProviderOf generic classes
This doesn't provide much benefit to Injector itself but now (Class)AssistedBuilder[T] and ProviderOf can be used in type hints[1] like this: class Class: def __init__(self, builder: AssistedBuilder[OtherClass]): # ... By being able to do that projects using Injector will gain more static type safety when tool like mypy is used to lint the code. Python 2.6 support is dropped because the typing module requires Python 2.7 or newer. [1] https://docs.python.org/3/library/typing.html
This commit is contained in:
parent
33bc8f336b
commit
2b14948aa7
|
@ -1,7 +1,6 @@
|
|||
sudo: false
|
||||
language: python
|
||||
python:
|
||||
- "2.6"
|
||||
- "2.7"
|
||||
- "3.2"
|
||||
- "3.3"
|
||||
|
@ -12,5 +11,5 @@ python:
|
|||
- "pypy"
|
||||
- "pypy3"
|
||||
install:
|
||||
- pip install pytest
|
||||
- pip install pytest typing
|
||||
script: py.test -vv
|
||||
|
|
14
CHANGES
14
CHANGES
|
@ -34,6 +34,20 @@ Backwards incompatible:
|
|||
Now you need to move the function with injectable dependencies to a class.
|
||||
|
||||
* Removed support for getting AssistedBuilder(callable=...)
|
||||
* Dropped Python 2.6 support
|
||||
* Changed the way AssistedBuilder and ProviderOf are used.
|
||||
Previously:
|
||||
|
||||
builder1 = injector.get(AssistedBuilder(Something))
|
||||
# or: builder1 = injector.get(AssistedBuilder(interface=Something))
|
||||
builder2 = injector.get(AssistedBuilder(cls=SomethingElse))
|
||||
provider = injector.get(ProviderOf(SomeOtherThing))
|
||||
|
||||
Now:
|
||||
|
||||
builder1 = injector.get(AssistedBuilder[Something])
|
||||
builder2 = injector.get(ClassAssistedBuilder[cls=SomethingElse])
|
||||
provider = injector.get(ProviderOf[SomeOtherThing])
|
||||
|
||||
0.10.1
|
||||
------
|
||||
|
|
|
@ -24,7 +24,7 @@ While being inspired by Guice, it does not slavishly replicate its API. Providin
|
|||
|
||||
* Documentation: http://injector.readthedocs.org
|
||||
|
||||
Injector works with CPython 2.6+/3.2+ and PyPy 1.9+.
|
||||
Injector works with CPython 2.7/3.2+ and PyPy 1.9+.
|
||||
|
||||
A Quick Example
|
||||
---------------
|
||||
|
|
|
@ -162,27 +162,28 @@ You may want to have database connection `db` injected into `UserUpdater` constr
|
|||
|
||||
In this situation there's technique called Assisted injection::
|
||||
|
||||
from injector import AssistedBuilder
|
||||
from injector import ClassAssistedBuilder
|
||||
injector = Injector()
|
||||
builder = injector.get(AssistedBuilder(cls=UserUpdater))
|
||||
builder = injector.get(ClassAssistedBuilder[UserUpdater])
|
||||
user = User('John')
|
||||
user_updater = builder.build(user=user)
|
||||
|
||||
This way we don't get `UserUpdater` directly but rather a builder object. Such builder has `build(**kwargs)` method which takes non-injectable parameters, combines them with injectable dependencies of `UserUpdater` and calls `UserUpdater` initializer using all of them.
|
||||
|
||||
`AssistedBuilder(...)` is injectable just as anything else, if you need instance of it you just ask for it like that::
|
||||
`AssistedBuilder[T]` and `ClassAssistedBuilder[T]` are injectable just as anything
|
||||
else, if you need instance of it you just ask for it like that::
|
||||
|
||||
class NeedsUserUpdater(object):
|
||||
@inject(updater_builder=AssistedBuilder(cls=UserUpdater))
|
||||
@inject(updater_builder=ClassAssistedBuilder[UserUpdater])
|
||||
def __init__(self, builder):
|
||||
self.updater_builder = builder
|
||||
|
||||
def method(self):
|
||||
updater = self.updater_builder.build(user=None)
|
||||
|
||||
`cls` needs to be a concrete class and no bindings will be used.
|
||||
`ClassAssistedBuilder` means it'll construct a concrete class and no bindings will be used.
|
||||
|
||||
If you want `AssistedBuilder` to follow bindings and construct class pointed to by a key you can do it like this::
|
||||
If you want to follow bindings and construct class pointed to by a key you use `AssistedBuilder` and can do it like this::
|
||||
|
||||
>>> DB = Key('DB')
|
||||
>>> class DBImplementation(object):
|
||||
|
@ -193,12 +194,10 @@ If you want `AssistedBuilder` to follow bindings and construct class pointed to
|
|||
... binder.bind(DB, to=DBImplementation)
|
||||
...
|
||||
>>> injector = Injector(configure)
|
||||
>>> builder = injector.get(AssistedBuilder(interface=DB))
|
||||
>>> builder = injector.get(AssistedBuilder[DB])
|
||||
>>> isinstance(builder.build(uri='x'), DBImplementation)
|
||||
True
|
||||
|
||||
Note: ``AssistedBuilder(X)`` is a shortcut for ``AssistedBuilder(interface=X)``
|
||||
|
||||
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>`_
|
||||
|
|
67
injector.py
67
injector.py
|
@ -24,6 +24,7 @@ import types
|
|||
import warnings
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from collections import namedtuple
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
|
||||
try:
|
||||
|
@ -398,11 +399,11 @@ class Binder(object):
|
|||
return Binding(interface, provider, scope)
|
||||
|
||||
def provider_for(self, interface, to=None):
|
||||
if isinstance(interface, ProviderOf):
|
||||
if isinstance(interface, type) and issubclass(interface, ProviderOf):
|
||||
(target,) = interface.__args__
|
||||
if to is not None:
|
||||
raise Exception('ProviderOf cannot be bound to anything')
|
||||
return InstanceProvider(
|
||||
BoundProvider(self.injector, interface.interface))
|
||||
return InstanceProvider(ProviderOf(self.injector, target))
|
||||
elif isinstance(to, Provider):
|
||||
return to
|
||||
elif isinstance(interface, Provider):
|
||||
|
@ -418,8 +419,9 @@ class Binder(object):
|
|||
def proxy(**kwargs):
|
||||
return interface.interface(**kwargs)
|
||||
return CallableProvider(proxy)
|
||||
elif isinstance(interface, AssistedBuilder):
|
||||
builder = AssistedBuilderImplementation(self.injector, *interface)
|
||||
elif isinstance(interface, type) and issubclass(interface, AssistedBuilder):
|
||||
(target,) = interface.__args__
|
||||
builder = interface(self.injector, target)
|
||||
return InstanceProvider(builder)
|
||||
elif isinstance(interface, (tuple, type)) and isinstance(to, interface):
|
||||
return InstanceProvider(to)
|
||||
|
@ -459,7 +461,7 @@ class Binder(object):
|
|||
# "Special" interfaces are ones that you cannot bind yourself but
|
||||
# you can request them (for example you cannot bind ProviderOf(SomeClass)
|
||||
# to anything but you can inject ProviderOf(SomeClass) just fine
|
||||
return isinstance(interface, (ProviderOf, AssistedBuilder))
|
||||
return issubclass(interface, (ProviderOf, AssistedBuilder))
|
||||
|
||||
|
||||
class Scope(object):
|
||||
|
@ -1101,30 +1103,18 @@ class BoundKey(tuple):
|
|||
return dict(self[1])
|
||||
|
||||
|
||||
class AssistedBuilder(namedtuple('_AssistedBuilder', 'interface cls')):
|
||||
def __new__(cls_, interface=None, cls=None):
|
||||
if len([x for x in (interface, cls) if x is not None]) != 1:
|
||||
raise Error('You need to specify exactly one of the following '
|
||||
'arguments: interface or cls')
|
||||
|
||||
return super(AssistedBuilder, cls_).__new__(cls_, interface, cls)
|
||||
T = TypeVar('T')
|
||||
|
||||
|
||||
class AssistedBuilderImplementation(namedtuple(
|
||||
'_AssistedBuilderImplementation', 'injector interface cls')):
|
||||
class AssistedBuilder(Generic[T]):
|
||||
|
||||
def __init__(self, injector, target):
|
||||
self._injector = injector
|
||||
self._target = target
|
||||
|
||||
def build(self, **kwargs):
|
||||
if self.interface is not None:
|
||||
return self.build_interface(**kwargs)
|
||||
else:
|
||||
return self.build_class(self.cls, **kwargs)
|
||||
|
||||
def build_class(self, cls, **kwargs):
|
||||
return self.injector.create_object(cls, additional_kwargs=kwargs)
|
||||
|
||||
def build_interface(self, **kwargs):
|
||||
key = BindingKey(self.interface)
|
||||
binder = self.injector.binder
|
||||
key = BindingKey(self._target)
|
||||
binder = self._injector.binder
|
||||
binding = binder.get_binding(None, key)
|
||||
provider = binding.provider
|
||||
if not isinstance(provider, ClassProvider):
|
||||
|
@ -1132,7 +1122,15 @@ class AssistedBuilderImplementation(namedtuple(
|
|||
'Assisted interface building works only with ClassProviders, '
|
||||
'got %r for %r' % (provider, self.interface))
|
||||
|
||||
return self.build_class(provider._cls, **kwargs)
|
||||
return self._build_class(provider._cls, **kwargs)
|
||||
|
||||
def _build_class(self, cls, **kwargs):
|
||||
return self._injector.create_object(cls, additional_kwargs=kwargs)
|
||||
|
||||
|
||||
class ClassAssistedBuilder(AssistedBuilder[T]):
|
||||
def build(self, **kwargs):
|
||||
return self._build_class(self._target, **kwargs)
|
||||
|
||||
|
||||
def _describe(c):
|
||||
|
@ -1143,8 +1141,8 @@ def _describe(c):
|
|||
return str(c)
|
||||
|
||||
|
||||
class ProviderOf(object):
|
||||
"""Can be used to get a :class:`BoundProvider` of an interface, for example:
|
||||
class ProviderOf(Generic[T]):
|
||||
"""Can be used to get a provider of an interface, for example:
|
||||
|
||||
>>> def provide_int():
|
||||
... print('providing')
|
||||
|
@ -1154,22 +1152,13 @@ class ProviderOf(object):
|
|||
... binder.bind(int, to=provide_int)
|
||||
>>>
|
||||
>>> injector = Injector(configure)
|
||||
>>> provider = injector.get(ProviderOf(int))
|
||||
>>> type(provider)
|
||||
<class 'injector.BoundProvider'>
|
||||
>>> provider = injector.get(ProviderOf[int])
|
||||
>>> value = provider.get()
|
||||
providing
|
||||
>>> value
|
||||
123
|
||||
"""
|
||||
|
||||
def __init__(self, interface):
|
||||
self.interface = interface
|
||||
|
||||
|
||||
class BoundProvider(object):
|
||||
"""A :class:`Provider` tied with particular :class:`Injector` instance"""
|
||||
|
||||
def __init__(self, injector, interface):
|
||||
self._injector = injector
|
||||
self._interface = interface
|
||||
|
|
|
@ -23,7 +23,7 @@ from injector import (
|
|||
inject, singleton, threadlocal, UnsatisfiedRequirement,
|
||||
CircularDependency, Module, provides, Key, SingletonScope,
|
||||
ScopeDecorator, with_injector, AssistedBuilder, BindingKey,
|
||||
SequenceKey, MappingKey, ProviderOf,
|
||||
SequenceKey, MappingKey, ProviderOf, ClassAssistedBuilder,
|
||||
)
|
||||
|
||||
|
||||
|
@ -150,7 +150,7 @@ def test_providers_arent_called_for_dependencies_that_are_already_provided():
|
|||
pass
|
||||
|
||||
injector = Injector(configure)
|
||||
builder = injector.get(AssistedBuilder(A))
|
||||
builder = injector.get(AssistedBuilder[A])
|
||||
|
||||
with pytest.raises(ZeroDivisionError):
|
||||
builder.build()
|
||||
|
@ -383,7 +383,7 @@ def test_dependency_cycle_can_be_worked_broken_by_assisted_building():
|
|||
self.i = i
|
||||
|
||||
class B(object):
|
||||
@inject(a_builder=AssistedBuilder(A))
|
||||
@inject(a_builder=AssistedBuilder[A])
|
||||
def __init__(self, a_builder):
|
||||
self.a = a_builder.build(i=self)
|
||||
|
||||
|
@ -820,14 +820,14 @@ class NeedsAssistance(object):
|
|||
|
||||
def test_assisted_builder_works_when_got_directly_from_injector():
|
||||
injector = Injector()
|
||||
builder = injector.get(AssistedBuilder(NeedsAssistance))
|
||||
builder = injector.get(AssistedBuilder[NeedsAssistance])
|
||||
obj = builder.build(b=123)
|
||||
assert ((obj.a, obj.b) == (str(), 123))
|
||||
|
||||
|
||||
def test_assisted_builder_works_when_injected():
|
||||
class X(object):
|
||||
@inject(builder=AssistedBuilder(NeedsAssistance))
|
||||
@inject(builder=AssistedBuilder[NeedsAssistance])
|
||||
def __init__(self, builder):
|
||||
self.obj = builder.build(b=234)
|
||||
|
||||
|
@ -843,7 +843,7 @@ def test_assisted_builder_uses_bindings():
|
|||
binder.bind(Interface, to=NeedsAssistance)
|
||||
|
||||
injector = Injector(configure)
|
||||
builder = injector.get(AssistedBuilder(Interface))
|
||||
builder = injector.get(AssistedBuilder[Interface])
|
||||
x = builder.build(b=333)
|
||||
assert ((type(x), x.b) == (NeedsAssistance, 333))
|
||||
|
||||
|
@ -857,25 +857,25 @@ def test_assisted_builder_uses_concrete_class_when_specified():
|
|||
binder.bind(X, to=lambda: 1 / 0)
|
||||
|
||||
injector = Injector(configure)
|
||||
builder = injector.get(AssistedBuilder(cls=X))
|
||||
builder = injector.get(ClassAssistedBuilder[X])
|
||||
builder.build()
|
||||
|
||||
|
||||
def test_assisted_builder_injection_is_safe_to_use_with_multiple_injectors():
|
||||
class X(object):
|
||||
@inject(builder=AssistedBuilder(NeedsAssistance))
|
||||
@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))
|
||||
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))
|
||||
gen_key = lambda: BindingKey(AssistedBuilder[NeedsAssistance])
|
||||
assert gen_key() == gen_key()
|
||||
|
||||
|
||||
|
@ -1018,7 +1018,7 @@ def test_providerof():
|
|||
|
||||
assert counter[0] == 0
|
||||
|
||||
provider = injector.get(ProviderOf(str))
|
||||
provider = injector.get(ProviderOf[str])
|
||||
assert counter[0] == 0
|
||||
|
||||
assert provider.get() == 'content'
|
||||
|
@ -1030,7 +1030,7 @@ def test_providerof():
|
|||
|
||||
def test_providerof_cannot_be_bound():
|
||||
def configure(binder):
|
||||
binder.bind(ProviderOf(int), to=InstanceProvider(None))
|
||||
binder.bind(ProviderOf[int], to=InstanceProvider(None))
|
||||
|
||||
with pytest.raises(Exception):
|
||||
Injector(configure)
|
||||
|
@ -1046,7 +1046,7 @@ def test_providerof_is_safe_to_use_with_multiple_injectors():
|
|||
injector1 = Injector(configure1)
|
||||
injector2 = Injector(configure2)
|
||||
|
||||
provider_of = ProviderOf(int)
|
||||
provider_of = ProviderOf[int]
|
||||
|
||||
provider1 = injector1.get(provider_of)
|
||||
provider2 = injector2.get(provider_of)
|
||||
|
@ -1075,10 +1075,10 @@ def test_special_interfaces_work_with_auto_bind_disabled():
|
|||
# raise UnsatisfiedRequirement(cls, key)
|
||||
# UnsatisfiedRequirement: unsatisfied requirement on
|
||||
# <injector.ProviderOf object at 0x10ff01550>
|
||||
injector.get(ProviderOf(InjectMe))
|
||||
injector.get(ProviderOf[InjectMe])
|
||||
|
||||
# This used to fail with an error similar to the ProviderOf one
|
||||
injector.get(AssistedBuilder(cls=InjectMe))
|
||||
injector.get(ClassAssistedBuilder[InjectMe])
|
||||
|
||||
|
||||
def test_binding_an_instance_regression():
|
||||
|
|
Loading…
Reference in New Issue