Merge branch 'release/3.16.0' into master

This commit is contained in:
Roman Mogylatov 2020-06-14 17:50:28 -04:00
commit efd6e3e7e5
21 changed files with 4530 additions and 733 deletions

View File

@ -10,3 +10,4 @@ Dependency Injector Contributors
+ Jeroen Rietveld (jeroenrietveld)
+ Dmitry Kuzmin (xotonic)
+ supakeen (supakeen)
+ Bruno P. Kinoshita (kinow)

View File

@ -36,6 +36,9 @@ build: clean cythonize
# Compile C extensions
python setup.py build_ext --inplace
docs-live: clean
sphinx-autobuild docs docs/_build/html
install: uninstall clean cythonize
pip install -ve .

View File

@ -3,7 +3,7 @@ Bundles mini application example
.. currentmodule:: dependency_injector.containers
"Bundles" is an example mini application that is intented to demonstrate the
"Bundles" is an example mini application that is intended to demonstrate the
power of dependency injection for creation of re-usable application components
("bundles") with 100% transparency of their dependencies.
@ -33,7 +33,7 @@ IoC containers
Next two listings show :py:class:`DeclarativeContainer`'s for "users" and
"photos" bundles.
Listing of ``bundeles/users/__init__.py``:
Listing of ``bundles/users/__init__.py``:
.. literalinclude:: ../../examples/miniapps/bundles/bundles/users/__init__.py
:language: python
@ -43,7 +43,7 @@ Listing of ``bundeles/users/__init__.py``:
- ``Users`` container has dependency on database.
Listing of ``bundeles/photos/__init__.py``:
Listing of ``bundles/photos/__init__.py``:
.. literalinclude:: ../../examples/miniapps/bundles/bundles/photos/__init__.py
:language: python

View File

@ -77,7 +77,7 @@ definitely quite lower than in other languages (even with dynamic typing).
supported.
Talking about inversion of control, it is a software design principle that
also works for each programming language, not dependending on its typing type.
also works for each programming language, not depending on its typing type.
Inversion of control is used to increase modularity of the program and make
it extensible.

View File

@ -7,6 +7,19 @@ that were made in every particular version.
From version 0.7.6 *Dependency Injector* framework strictly
follows `Semantic versioning`_
3.16.0
------
- Add ``List`` provider
`issue #243 <https://github.com/ets-labs/python-dependency-injector/issues/243>`_,
`PR #251 <https://github.com/ets-labs/python-dependency-injector/pull/251>`_.
- Fix a few typos in docs (thanks to `Bruno P. Kinoshita <https://github.com/kinow>`_,
`issue #249 <https://github.com/ets-labs/python-dependency-injector/issues/249>`_,
`PR #250 <https://github.com/ets-labs/python-dependency-injector/pull/250>`_).
- Add support of six 1.15.0.
- Regenerate C sources using Cython 0.29.20.
3.15.6
------
- Fix changelog typo.
@ -322,7 +335,7 @@ follows `Semantic versioning`_
3.3.7
-----
- Fix minor bug related to patch of ``Configuration`` provider in version
3.3.6 - special attribues were identified by formula ``__{text}`` - now
3.3.6 - special attributes were identified by formula ``__{text}`` - now
they are identified by formula ``__{text}__``, that is more correct
according to Python Data Model.
@ -453,12 +466,12 @@ follows `Semantic versioning`_
- **Containers**
1. Module ``dependency_injector.containers`` was splitted into submodules
1. Module ``dependency_injector.containers`` was split into submodules
without any functional changes.
- **Utils**
1. Module ``dependency_injector.utils`` is splitted into
1. Module ``dependency_injector.utils`` is split into
``dependency_injector.containers`` and ``dependency_injector.providers``.
- **Miscellaneous**
@ -805,9 +818,9 @@ to be the first major release.
``Provider._provide()``.
- ``NewInstance`` provider was renamed to ``Factory`` provider.
``NewInstance`` still can be used, but it considered to be deprecated and
will be removed in futher releases.
will be removed in further releases.
- ``@inject`` decorator was refactored to keep all injections in
``_injections`` attribute of decorated callback. It will give a possibilty to
``_injections`` attribute of decorated callback. It will give a possibility to
track all the injections of particular callbacks and gives some performance
boost due minimizing number of calls for doing injections.
- A lot of documentation updates were made.

View File

@ -9,7 +9,7 @@ Below are some tips and recommendations that have to be met:
1. Every custom provider has to extend base provider class -
:py:class:`Provider`.
2. Cusom provider's ``__init__()`` could be overriden, but parent's
2. Custom provider's ``__init__()`` could be overridden, but parent's
initializer (:py:meth:`Provider.__init__`) has to be called.
3. Providing strategy has to be implemented in custom provider's
:py:meth:`Provider.__call__` method.

View File

@ -160,7 +160,7 @@ aggregates other :py:class:`Factory` providers.
:py:class:`FactoryAggregate` is not overridable. Calling of
:py:meth:`FactoryAggregate.override` will result in raising of an
expection.
exception.
Next prototype might be the best demonstration of
:py:class:`FactoryAggregate` features:

View File

@ -22,6 +22,7 @@ Providers package API docs - :py:mod:`dependency_injector.providers`
callable
coroutine
object
list
dependency
overriding
custom

34
docs/providers/list.rst Normal file
View File

@ -0,0 +1,34 @@
List providers
--------------
.. currentmodule:: dependency_injector.providers
:py:class:`List` provider provides a list of values.
.. literalinclude:: ../../examples/providers/list.py
:language: python
:lines: 23-29
:linenos:
:py:class:`List` provider is needed for injecting a list of dependencies. It handles
positional argument injections the same way as :py:class:`Factory` provider:
+ All providers (instances of :py:class:`Provider`) are called every time
when injection needs to be done.
+ Providers could be injected "as is" (delegated), if it is defined explicitly. Check out
:ref:`factory_providers_delegation`.
+ All other values are injected *"as is"*.
+ Positional context arguments will be appended after :py:class:`List` positional injections.
Full example:
.. literalinclude:: ../../examples/providers/list.py
:language: python
:emphasize-lines: 23-29
:linenos:
.. note::
Keyword argument injections are not supported.
.. disqus::

View File

@ -9,7 +9,7 @@ This gives opportunity to make system behaviour more flexible at some point.
The main feature is that while your code is using providers, it depends on
providers, but not on the objects that providers provide. As a result of this,
you can change providing by provider object to a different one, but still
compatible one, without chaning your previously written code.
compatible one, without changing your previously written code.
Provider overriding functionality has such interface:

View File

@ -22,11 +22,11 @@ Singleton providers resetting
Created and memorized by :py:class:`Singleton` instance can be reset. Reset of
:py:class:`Singleton`'s memorized instance is done by clearing reference to
it. Further lifecycle of memorized instance is out of :py:class:`Singleton`
provider's control and dependes on garbage collection strategy.
provider's control and depends on garbage collection strategy.
Example:
.. literalinclude:: ../../examples/providers/singleton_reseting.py
.. literalinclude:: ../../examples/providers/singleton_resetting.py
:language: python
:linenos:

View File

@ -0,0 +1,45 @@
"""`List` provider example."""
import dataclasses
from typing import List
from dependency_injector import providers
@dataclasses.dataclass
class Module:
"""Example module."""
name: str
@dataclasses.dataclass
class Dispatcher:
"""Example dispatcher."""
modules: List[Module]
dispatcher_factory = providers.Factory(
Dispatcher,
modules=providers.List(
providers.Factory(Module, name='m1'),
providers.Factory(Module, name='m2'),
),
)
if __name__ == '__main__':
dispatcher = dispatcher_factory()
assert isinstance(dispatcher.modules, list)
assert dispatcher.modules[0].name == 'm1'
assert dispatcher.modules[1].name == 'm2'
# Call of dispatcher_factory() is equivalent to:
dispatcher = Dispatcher(
modules=[
Module(name='m1'),
Module(name='m2'),
],
)

View File

@ -1,4 +1,4 @@
cython==0.29.14
cython==0.29.20
tox
unittest2
coverage

View File

@ -1 +1 @@
six>=1.7.0,<=1.14.0
six>=1.7.0,<=1.15.0

View File

@ -1,6 +1,6 @@
"""Dependency injector top-level package."""
__version__ = '3.15.6'
__version__ = '3.16.0'
"""Version number that follows semantic versioning.
:type: str

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -75,6 +75,7 @@ cdef class CallableDelegate(Delegate):
cdef class Coroutine(Callable):
pass
cdef class DelegatedCoroutine(Coroutine):
pass
@ -165,6 +166,15 @@ cdef class SingletonDelegate(Delegate):
pass
# Miscellaneous providers
cdef class List(Provider):
cdef tuple __args
cdef int __args_len
cpdef object _provide(self, tuple args, dict kwargs)
# Injections
cdef class Injection(object):
cdef object __value

View File

@ -356,7 +356,7 @@ cdef class Dependency(Provider):
This provider is used for description of dependency interface. That might
be useful when dependency could be provided in the client's code only,
but it's interface is known. Such situations could happen when required
dependency has non-determenistic list of dependencies itself.
dependency has non-deterministic list of dependencies itself.
.. code-block:: python
@ -445,7 +445,7 @@ cdef class ExternalDependency(Dependency):
This provider is used for description of dependency interface. That might
be useful when dependency could be provided in the client's code only,
but it's interface is known. Such situations could happen when required
dependency has non-determenistic list of dependencies itself.
dependency has non-deterministic list of dependencies itself.
.. code-block:: python
@ -607,7 +607,7 @@ cdef class OverridingContext(object):
"""Provider overriding context.
:py:class:`OverridingContext` is used by :py:meth:`Provider.override` for
implemeting ``with`` contexts. When :py:class:`OverridingContext` is
implementing ``with`` contexts. When :py:class:`OverridingContext` is
closed, overriding that was created in this context is dropped also.
.. code-block:: python
@ -739,7 +739,7 @@ cdef class Callable(Provider):
return self
def set_args(self, *args):
"""Set postional argument injections.
"""Set positional argument injections.
Existing positional argument injections are dropped.
@ -750,7 +750,7 @@ cdef class Callable(Provider):
return self
def clear_args(self):
"""Drop postional argument injections.
"""Drop positional argument injections.
:return: Reference ``self``
"""
@ -1272,7 +1272,7 @@ cdef class Factory(Provider):
return self.__instantiator.args
def add_args(self, *args):
"""Add __init__ postional argument injections.
"""Add __init__ positional argument injections.
:return: Reference ``self``
"""
@ -1280,7 +1280,7 @@ cdef class Factory(Provider):
return self
def set_args(self, *args):
"""Set __init__ postional argument injections.
"""Set __init__ positional argument injections.
Existing __init__ positional argument injections are dropped.
@ -1290,7 +1290,7 @@ cdef class Factory(Provider):
return self
def clear_args(self):
"""Drop __init__ postional argument injections.
"""Drop __init__ positional argument injections.
:return: Reference ``self``
"""
@ -1966,6 +1966,108 @@ cdef class SingletonDelegate(Delegate):
super(SingletonDelegate, self).__init__(singleton)
cdef class List(Provider):
"""List provider provides a list of values.
:py:class:`List` provider is needed for injecting a list of dependencies. It handles
positional argument injections the same way as :py:class:`Factory` provider.
Keyword argument injections are not supported.
.. code-block:: python
dispatcher_factory = Factory(
Dispatcher,
modules=List(
Factory(ModuleA, dependency_a),
Factory(ModuleB, dependency_b),
),
)
dispatcher = dispatcher_factory()
# is equivalent to:
dispatcher = Dispatcher(
modules=[
ModuleA(dependency_a),
ModuleB(dependency_b),
],
)
"""
def __init__(self, *args):
"""Initializer."""
self.__args = tuple()
self.__args_len = 0
self.set_args(*args)
super(List, self).__init__()
def __deepcopy__(self, memo):
"""Create and return full copy of provider."""
copied = memo.get(id(self))
if copied is not None:
return copied
copied = self.__class__(*deepcopy(self.args, memo))
self._copy_overridings(copied, memo)
return copied
def __str__(self):
"""Return string representation of provider.
:rtype: str
"""
return represent_provider(provider=self, provides=list(self.args))
@property
def args(self):
"""Return positional argument injections."""
cdef int index
cdef PositionalInjection arg
cdef list args
args = list()
for index in range(self.__args_len):
arg = self.__args[index]
args.append(arg.__value)
return tuple(args)
def add_args(self, *args):
"""Add positional argument injections.
:return: Reference ``self``
"""
self.__args += parse_positional_injections(args)
self.__args_len = len(self.__args)
return self
def set_args(self, *args):
"""Set positional argument injections.
Existing positional argument injections are dropped.
:return: Reference ``self``
"""
self.__args = parse_positional_injections(args)
self.__args_len = len(self.__args)
return self
def clear_args(self):
"""Drop positional argument injections.
:return: Reference ``self``
"""
self.__args = tuple()
self.__args_len = len(self.__args)
return self
cpdef object _provide(self, tuple args, dict kwargs):
"""Return result of provided callable's call."""
return list(__provide_positional_args(args, self.__args, self.__args_len))
cdef class Injection(object):
"""Abstract injection class."""

View File

@ -0,0 +1,136 @@
"""Dependency injector list provider unit tests."""
import sys
import unittest2 as unittest
from dependency_injector import providers
class ListTests(unittest.TestCase):
def test_is_provider(self):
self.assertTrue(providers.is_provider(providers.List()))
def test_call_with_init_positional_args(self):
provider = providers.List('i1', 'i2')
list1 = provider()
list2 = provider()
self.assertEqual(list1, ['i1', 'i2'])
self.assertEqual(list2, ['i1', 'i2'])
self.assertIsNot(list1, list2)
def test_call_with_context_args(self):
provider = providers.List('i1', 'i2')
self.assertEqual(provider('i3', 'i4'), ['i1', 'i2', 'i3', 'i4'])
def test_fluent_interface(self):
provider = providers.List() \
.add_args(1, 2)
self.assertEqual(provider(), [1, 2])
def test_set_args(self):
provider = providers.List() \
.add_args(1, 2) \
.set_args(3, 4)
self.assertEqual(provider.args, tuple([3, 4]))
def test_clear_args(self):
provider = providers.List() \
.add_args(1, 2) \
.clear_args()
self.assertEqual(provider.args, tuple())
def test_call_overridden(self):
provider = providers.List(1, 2)
overriding_provider1 = providers.List(2, 3)
overriding_provider2 = providers.List(3, 4)
provider.override(overriding_provider1)
provider.override(overriding_provider2)
instance1 = provider()
instance2 = provider()
self.assertIsNot(instance1, instance2)
self.assertEqual(instance1, [3, 4])
self.assertEqual(instance2, [3, 4])
def test_deepcopy(self):
provider = providers.List(1, 2)
provider_copy = providers.deepcopy(provider)
self.assertIsNot(provider, provider_copy)
self.assertEqual(provider.args, provider_copy.args)
self.assertIsInstance(provider, providers.List)
def test_deepcopy_from_memo(self):
provider = providers.List(1, 2)
provider_copy_memo = providers.List(1, 2)
provider_copy = providers.deepcopy(
provider, memo={id(provider): provider_copy_memo})
self.assertIs(provider_copy, provider_copy_memo)
def test_deepcopy_args(self):
provider = providers.List()
dependent_provider1 = providers.Factory(list)
dependent_provider2 = providers.Factory(dict)
provider.add_args(dependent_provider1, dependent_provider2)
provider_copy = providers.deepcopy(provider)
dependent_provider_copy1 = provider_copy.args[0]
dependent_provider_copy2 = provider_copy.args[1]
self.assertNotEqual(provider.args, provider_copy.args)
self.assertIs(dependent_provider1.cls, dependent_provider_copy1.cls)
self.assertIsNot(dependent_provider1, dependent_provider_copy1)
self.assertIs(dependent_provider2.cls, dependent_provider_copy2.cls)
self.assertIsNot(dependent_provider2, dependent_provider_copy2)
def test_deepcopy_overridden(self):
provider = providers.List()
object_provider = providers.Object(object())
provider.override(object_provider)
provider_copy = providers.deepcopy(provider)
object_provider_copy = provider_copy.overridden[0]
self.assertIsNot(provider, provider_copy)
self.assertEqual(provider.args, provider_copy.args)
self.assertIsInstance(provider, providers.List)
self.assertIsNot(object_provider, object_provider_copy)
self.assertIsInstance(object_provider_copy, providers.Object)
def test_deepcopy_with_sys_streams(self):
provider = providers.List()
provider.add_args(sys.stdin, sys.stdout, sys.stderr)
provider_copy = providers.deepcopy(provider)
self.assertIsNot(provider, provider_copy)
self.assertIsInstance(provider_copy, providers.List)
self.assertIs(provider.args[0], sys.stdin)
self.assertIs(provider.args[1], sys.stdout)
self.assertIs(provider.args[2], sys.stderr)
def test_repr(self):
provider = providers.List(1, 2)
self.assertEqual(repr(provider),
'<dependency_injector.providers.'
'List({0}) at {1}>'.format(
repr(list(provider.args)),
hex(id(provider))))