Resources (#312)

* Add prototype

* Add example

* Remove typing erros in Python 2.7 and 3.4

* Move resources example

* Draft resources docs

* Update resources docs

* Fix repr

* Rename dict provider test

* Add more tests

* Add tests + refactoring

* Add more tests

* Update tests to run only on 3.5+

* Update setup.py

* Add typing tests

* Update changelog

* Fix generator iteration

* Remove contextlib

* Hotfix aiohttp issue

* Move aiohttp fix to tox.ini

* Move aiohttp fix to a different place in tox
This commit is contained in:
Roman Mogylatov 2020-10-24 20:56:32 -04:00 committed by GitHub
parent b54bcb7b31
commit 47c79b2772
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 11847 additions and 6257 deletions

View File

@ -57,17 +57,20 @@ It helps implementing the dependency injection principle.
Key features of the ``Dependency Injector``: Key features of the ``Dependency Injector``:
- **Providers**. Provides ``Factory``, ``Singleton``, ``Callable``, ``Coroutine``, ``Object``, - **Providers**. Provides ``Factory``, ``Singleton``, ``Callable``, ``Coroutine``, ``Object``,
``List``, ``Dict``, ``Configuration``, ``Dependency`` and ``Selector`` providers that help ``List``, ``Dict``, ``Configuration``, ``Resource``, ``Dependency`` and ``Selector`` providers
assembling your objects. that help assembling your objects.
See `Providers <https://python-dependency-injector.ets-labs.org/providers/index.html>`_. See `Providers <https://python-dependency-injector.ets-labs.org/providers/index.html>`_.
- **Overriding**. Can override any provider by another provider on the fly. This helps in testing - **Overriding**. Can override any provider by another provider on the fly. This helps in testing
and configuring dev / stage environment to replace API clients with stubs etc. See and configuring dev / stage environment to replace API clients with stubs etc. See
`Provider overriding <https://python-dependency-injector.ets-labs.org/providers/overriding.html>`_. `Provider overriding <https://python-dependency-injector.ets-labs.org/providers/overriding.html>`_.
- **Configuration**. Read configuration from ``yaml`` & ``ini`` files, environment variables - **Configuration**. Reads configuration from ``yaml`` & ``ini`` files, environment variables
and dictionaries. and dictionaries.
See `Configuration provider <https://python-dependency-injector.ets-labs.org/providers/configuration.html>`_. See `Configuration provider <https://python-dependency-injector.ets-labs.org/providers/configuration.html>`_.
- **Containers**. Provides declarative and dynamic containers. - **Containers**. Provides declarative and dynamic containers.
See `Containers <https://python-dependency-injector.ets-labs.org/containers/index.html>`_. See `Containers <https://python-dependency-injector.ets-labs.org/containers/index.html>`_.
- **Resources**. Helps with initialization and configuring of logging, event loop, thread
or process pool, etc.
See `Resource provider <https://python-dependency-injector.ets-labs.org/providers/resource.html>`_.
- **Wiring**. Injects dependencies into functions and methods. Helps integrating with - **Wiring**. Injects dependencies into functions and methods. Helps integrating with
other frameworks: Django, Flask, Aiohttp, etc. other frameworks: Django, Flask, Aiohttp, etc.
See `Wiring <https://python-dependency-injector.ets-labs.org/wiring.html>`_. See `Wiring <https://python-dependency-injector.ets-labs.org/wiring.html>`_.

View File

@ -69,13 +69,15 @@ It helps implementing the dependency injection principle.
Key features of the ``Dependency Injector``: Key features of the ``Dependency Injector``:
- **Providers**. Provides ``Factory``, ``Singleton``, ``Callable``, ``Coroutine``, ``Object``, - **Providers**. Provides ``Factory``, ``Singleton``, ``Callable``, ``Coroutine``, ``Object``,
``List``, ``Dict``, ``Configuration``, ``Dependency`` and ``Selector`` providers that help ``List``, ``Dict``, ``Configuration``, ``Resource``, ``Dependency`` and ``Selector`` providers
assembling your objects. See :ref:`providers`. that help assembling your objects. See :ref:`providers`.
- **Overriding**. Can override any provider by another provider on the fly. This helps in testing - **Overriding**. Can override any provider by another provider on the fly. This helps in testing
and configuring dev / stage environment to replace API clients with stubs etc. See and configuring dev / stage environment to replace API clients with stubs etc. See
:ref:`provider-overriding`. :ref:`provider-overriding`.
- **Configuration**. Read configuration from ``yaml`` & ``ini`` files, environment variables - **Configuration**. Reads configuration from ``yaml`` & ``ini`` files, environment variables
and dictionaries. See :ref:`configuration-provider`. and dictionaries. See :ref:`configuration-provider`.
- **Resources**. Helps with initialization and configuring of logging, event loop, thread
or process pool, etc. See :ref:`resource-provider`.
- **Containers**. Provides declarative and dynamic containers. See :ref:`containers`. - **Containers**. Provides declarative and dynamic containers. See :ref:`containers`.
- **Wiring**. Injects dependencies into functions and methods. Helps integrating with - **Wiring**. Injects dependencies into functions and methods. Helps integrating with
other frameworks: Django, Flask, Aiohttp, etc. See :ref:`wiring`. other frameworks: Django, Flask, Aiohttp, etc. See :ref:`wiring`.

View File

@ -11,13 +11,15 @@ Key features
Key features of the ``Dependency Injector``: Key features of the ``Dependency Injector``:
- **Providers**. Provides ``Factory``, ``Singleton``, ``Callable``, ``Coroutine``, ``Object``, - **Providers**. Provides ``Factory``, ``Singleton``, ``Callable``, ``Coroutine``, ``Object``,
``List``, ``Dict``, ``Configuration``, ``Dependency`` and ``Selector`` providers that help ``List``, ``Dict``, ``Configuration``, ``Resource``, ``Dependency`` and ``Selector`` providers
assembling your objects. See :ref:`providers`. that help assembling your objects. See :ref:`providers`.
- **Overriding**. Can override any provider by another provider on the fly. This helps in testing - **Overriding**. Can override any provider by another provider on the fly. This helps in testing
and configuring dev / stage environment to replace API clients with stubs etc. See and configuring dev / stage environment to replace API clients with stubs etc. See
:ref:`provider-overriding`. :ref:`provider-overriding`.
- **Configuration**. Read configuration from ``yaml`` & ``ini`` files, environment variables - **Configuration**. Reads configuration from ``yaml`` & ``ini`` files, environment variables
and dictionaries. See :ref:`configuration-provider`. and dictionaries. See :ref:`configuration-provider`.
- **Resources**. Helps with initialization and configuring of logging, event loop, thread
or process pool, etc. See :ref:`resource-provider`.
- **Containers**. Provides declarative and dynamic containers. See :ref:`containers`. - **Containers**. Provides declarative and dynamic containers. See :ref:`containers`.
- **Wiring**. Injects dependencies into functions and methods. Helps integrating with - **Wiring**. Injects dependencies into functions and methods. Helps integrating with
other frameworks: Django, Flask, Aiohttp, etc. See :ref:`wiring`. other frameworks: Django, Flask, Aiohttp, etc. See :ref:`wiring`.

View File

@ -9,6 +9,7 @@ follows `Semantic versioning`_
Develop Develop
------- -------
- Add ``Resource`` provider.
- Add ``Dict`` provider. - Add ``Dict`` provider.
- "Un-deprecate" ``@containers.override()`` and ``@containers.copy()`` decorators ( - "Un-deprecate" ``@containers.override()`` and ``@containers.copy()`` decorators (
see `Issue 301 <https://github.com/ets-labs/python-dependency-injector/issues/301>`_ see `Issue 301 <https://github.com/ets-labs/python-dependency-injector/issues/301>`_
@ -16,6 +17,7 @@ Develop
- Add favicon. - Add favicon.
- Remove redirects that occur while getting badge images to optimize docs load speed. - Remove redirects that occur while getting badge images to optimize docs load speed.
- Update license year. - Update license year.
- Update short description on PyPI.
4.0.6 4.0.6
----- -----

View File

@ -45,6 +45,7 @@ Providers module API docs - :py:mod:`dependency_injector.providers`
list list
dict dict
configuration configuration
resource
selector selector
dependency dependency
overriding overriding

206
docs/providers/resource.rst Normal file
View File

@ -0,0 +1,206 @@
.. _resource-provider:
Resource provider
=================
.. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Resource,Injection,
Logging,Event Loop,Thread Pool
:description: Resource provider provides a component with initialization and shutdown. It works
well for configuring logging, event loop, thread or process pool, etc.
This page demonstrates how to use resource provider.
.. currentmodule:: dependency_injector.providers
:py:class:`Resource` provider provides a component with initialization and shutdown.
.. literalinclude:: ../../examples/providers/resource.py
:language: python
:lines: 3-
Resource providers help to initialize and configure logging, event loop, thread or process pool, etc.
Resource provider is similar to ``Singleton``. Resource initialization happens only once.
You can do injections and use provided instance the same way like you do with any other provider.
.. code-block:: python
:emphasize-lines: 12
class Container(containers.DeclarativeContainer):
config = providers.Configuration()
thread_pool = providers.Resource(
init_threat_pool,
max_workers=config.max_workers,
)
dispatcher = providers.Factory(
TaskDispatcher,
executor=thread_pool,
)
Container has an interface to initialize and shutdown all resources:
.. code-block:: python
container = Container()
container.init_resources()
container.shutdown_resources()
You also can initialize and shutdown resources one-by-one using ``init()`` and
``shutdown()`` methods of the provider:
.. code-block:: python
container = Container()
container.thread_pool.init()
container.thread_pool.shutdown()
Resource provider supports 3 types of initializers:
- Function
- Generator
- Subclass of ``resources.Resource``
Function initializer
--------------------
Function is the most common way to specify resource initialization:
.. code-block:: python
def init_resource(argument1=..., argument2=...):
return SomeResource()
class Container(containers.DeclarativeContainer):
resource = providers.Resource(
init_resource,
argument1=...,
argument2=...,
)
Function initializer may not return a value. This often happens when
you configure global resource:
.. code-block:: python
import logging.config
class Container(containers.DeclarativeContainer):
configure_logging = providers.Resource(
logging.config.fileConfig,
fname='logging.ini',
)
Function initializer does not support shutdown.
Generator initializer
---------------------
Resource provider can use 2-step generators:
- First step of generator is an initialization phase
- The second is step is a shutdown phase
.. code-block:: python
def init_resource(argument1=..., argument2=...):
resource = SomeResource() # initialization
yield resource
# shutdown
...
class Container(containers.DeclarativeContainer):
resource = providers.Resource(
init_resource,
argument1=...,
argument2=...,
)
Generator initialization phase ends on the first ``yield`` statement. You can return a
resource object using ``yield resource`` like in the example above. Returning of the
object is not mandatory. You can leave ``yield`` statement empty:
.. code-block:: python
def init_resource(argument1=..., argument2=...):
# initialization
...
yield
# shutdown
...
class Container(containers.DeclarativeContainer):
resource = providers.Resource(
init_resource,
argument1=...,
argument2=...,
)
Subclass initializer
--------------------
You can create resource initializer by implementing a subclass of the ``resources.Resource``:
.. code-block:: python
from dependency_injector import resources
class MyResource(resources.Resource):
def init(self, argument1=..., argument2=...) -> SomeResource:
return SomeResource()
def shutdown(self, resource: SomeResource) -> None:
# shutdown
...
class Container(containers.DeclarativeContainer):
resource = providers.Resource(
MyResource,
argument1=...,
argument2=...,
)
Subclass must implement two methods: ``init()`` and ``shutdown()``.
Method ``init()`` receives arguments specified in resource provider.
It performs initialization and returns resource object. Returning of the object
is not mandatory.
Method ``shutdown()`` receives resource object returned from ``init()``. If ``init()``
didn't return an object ``shutdown()`` method will be called anyway with ``None`` as a
first argument.
.. code-block:: python
from dependency_injector import resources
class MyResource(resources.Resource):
def init(self, argument1=..., argument2=...) -> None:
# initialization
...
def shutdown(self, _: None) -> None:
# shutdown
...
.. disqus::

View File

@ -0,0 +1,41 @@
"""`Resource` provider example."""
import sys
import logging
from concurrent.futures import ThreadPoolExecutor
from dependency_injector import containers, providers
def init_threat_pool(max_workers: int):
thread_pool = ThreadPoolExecutor(max_workers=max_workers)
yield thread_pool
thread_pool.shutdown(wait=True)
class Container(containers.DeclarativeContainer):
config = providers.Configuration()
thread_pool = providers.Resource(
init_threat_pool,
max_workers=config.max_workers,
)
logging = providers.Resource(
logging.basicConfig,
level=logging.INFO,
stream=sys.stdout,
)
if __name__ == '__main__':
container = Container(config={'max_workers': 4})
container.init_resources()
logging.info('Resources are initialized')
thread_pool = container.thread_pool()
thread_pool.map(print, range(10))
container.shutdown_resources()

View File

@ -31,11 +31,11 @@ if os.environ.get('DEPENDENCY_INJECTOR_DEBUG_MODE') == '1':
setup(name='dependency-injector', setup(name='dependency-injector',
version=version, version=version,
description='Dependency injection microframework for Python', description='Dependency injection framework for Python',
long_description=description, long_description=description,
author='ETS Labs', author='ETS Labs',
author_email='rmogilatov@gmail.com', author_email='rmogilatov@gmail.com',
maintainer='Roman Mogilatov', maintainer='Roman Mogylatov',
maintainer_email='rmogilatov@gmail.com', maintainer_email='rmogilatov@gmail.com',
url='https://github.com/ets-labs/python-dependency-injector', url='https://github.com/ets-labs/python-dependency-injector',
download_url='https://pypi.python.org/pypi/dependency_injector', download_url='https://pypi.python.org/pypi/dependency_injector',

File diff suppressed because it is too large Load Diff

View File

@ -25,6 +25,8 @@ class Container:
def resolve_provider_name(self, provider_to_resolve: Provider) -> Optional[str]: ... def resolve_provider_name(self, provider_to_resolve: Provider) -> Optional[str]: ...
def wire(self, modules: Optional[Iterable[ModuleType]] = None, packages: Optional[Iterable[ModuleType]] = None) -> None: ... def wire(self, modules: Optional[Iterable[ModuleType]] = None, packages: Optional[Iterable[ModuleType]] = None) -> None: ...
def unwire(self) -> None: ... def unwire(self) -> None: ...
def init_resources(self) -> None: ...
def shutdown_resources(self) -> None: ...
class DynamicContainer(Container): ... class DynamicContainer(Container): ...

View File

@ -7,6 +7,7 @@ import six
from .errors import Error from .errors import Error
from .providers cimport ( from .providers cimport (
Provider, Provider,
Resource,
deepcopy, deepcopy,
) )
@ -213,6 +214,19 @@ class DynamicContainer(object):
self.wired_to_modules.clear() self.wired_to_modules.clear()
self.wired_to_packages.clear() self.wired_to_packages.clear()
def init_resources(self):
"""Initialize all container resources."""
for provider in self.providers.values():
if not isinstance(provider, Resource):
continue
provider.init()
def shutdown_resources(self):
"""Shutdown all container resources."""
for provider in self.providers.values():
if not isinstance(provider, Resource):
continue
provider.shutdown()
class DeclarativeContainerMetaClass(type): class DeclarativeContainerMetaClass(type):

File diff suppressed because it is too large Load Diff

View File

@ -191,6 +191,21 @@ cdef class Dict(Provider):
cpdef object _provide(self, tuple args, dict kwargs) cpdef object _provide(self, tuple args, dict kwargs)
cdef class Resource(Provider):
cdef object __initializer
cdef bint __initialized
cdef object __shutdowner
cdef object __resource
cdef tuple __args
cdef int __args_len
cdef tuple __kwargs
cdef int __kwargs_len
cpdef object _provide(self, tuple args, dict kwargs)
cdef class Container(Provider): cdef class Container(Provider):
cdef object __container_cls cdef object __container_cls
cdef dict __overriding_providers cdef dict __overriding_providers

View File

@ -13,8 +13,13 @@ from typing import (
Optional, Optional,
Union, Union,
Coroutine as _Coroutine, Coroutine as _Coroutine,
Iterator as _Iterator,
Generator as _Generator,
overload,
) )
from . import resources
Injection = Any Injection = Any
T = TypeVar('T') T = TypeVar('T')
@ -274,6 +279,30 @@ class Dict(Provider):
def clear_kwargs(self) -> Dict: ... def clear_kwargs(self) -> Dict: ...
class Resource(Provider, Generic[T]):
@overload
def __init__(self, initializer: _Callable[..., resources.Resource[T]], *args: Injection, **kwargs: Injection) -> None: ...
@overload
def __init__(self, initializer: _Callable[..., _Iterator[T]], *args: Injection, **kwargs: Injection) -> None: ...
@overload
def __init__(self, initializer: _Callable[..., T], *args: Injection, **kwargs: Injection) -> None: ...
def __call__(self, *args: Injection, **kwargs: Injection) -> T: ...
@property
def args(self) -> Tuple[Injection]: ...
def add_args(self, *args: Injection) -> Resource: ...
def set_args(self, *args: Injection) -> Resource: ...
def clear_args(self) -> Resource: ...
@property
def kwargs(self) -> _Dict[Any, Injection]: ...
def add_kwargs(self, **kwargs: Injection) -> Resource: ...
def set_kwargs(self, **kwargs: Injection) -> Resource: ...
def clear_kwargs(self) -> Resource: ...
@property
def initialized(self) -> bool: ...
def init(self) -> T: ...
def shutdown(self) -> None: ...
class Container(Provider): class Container(Provider):
def __init__(self, container_cls: Type[T], container: Optional[T] = None, **overriding_providers: Provider) -> None: ... def __init__(self, container_cls: Type[T], container: Optional[T] = None, **overriding_providers: Provider) -> None: ...

View File

@ -3,6 +3,7 @@
from __future__ import absolute_import from __future__ import absolute_import
import copy import copy
import inspect
import os import os
import re import re
import sys import sys
@ -2502,7 +2503,7 @@ cdef class Dict(Provider):
:rtype: str :rtype: str
""" """
return represent_provider(provider=self, provides=dict(self.kwargs)) return represent_provider(provider=self, provides=self.kwargs)
@property @property
def kwargs(self): def kwargs(self):
@ -2551,6 +2552,211 @@ cdef class Dict(Provider):
return __provide_keyword_args(kwargs, self.__kwargs, self.__kwargs_len) return __provide_keyword_args(kwargs, self.__kwargs, self.__kwargs_len)
cdef class Resource(Provider):
"""Resource provider provides a component with initialization and shutdown."""
def __init__(self, initializer, *args, **kwargs):
self.__initializer = initializer
self.__initialized = False
self.__resource = None
self.__shutdowner = None
self.__args = tuple()
self.__args_len = 0
self.set_args(*args)
self.__kwargs = tuple()
self.__kwargs_len = 0
self.set_kwargs(**kwargs)
super().__init__()
def __deepcopy__(self, memo):
"""Create and return full copy of provider."""
copied = memo.get(id(self))
if copied is not None:
return copied
if self.__initialized:
raise Error('Can not copy initialized resource')
copied = self.__class__(
self.__initializer,
*deepcopy(self.args, memo),
**deepcopy(self.kwargs, memo),
)
self._copy_overridings(copied, memo)
return copied
def __repr__(self):
return (
f'{self.__class__.__name__}({self.__initializer}, '
f'initialized={self.__initialized})'
)
@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
@property
def kwargs(self):
"""Return keyword argument injections."""
cdef int index
cdef NamedInjection kwarg
cdef dict kwargs
kwargs = dict()
for index in range(self.__kwargs_len):
kwarg = self.__kwargs[index]
kwargs[kwarg.__name] = kwarg.__value
return kwargs
def add_kwargs(self, **kwargs):
"""Add keyword argument injections.
:return: Reference ``self``
"""
self.__kwargs += parse_named_injections(kwargs)
self.__kwargs_len = len(self.__kwargs)
return self
def set_kwargs(self, **kwargs):
"""Set keyword argument injections.
Existing keyword argument injections are dropped.
:return: Reference ``self``
"""
self.__kwargs = parse_named_injections(kwargs)
self.__kwargs_len = len(self.__kwargs)
return self
def clear_kwargs(self):
"""Drop keyword argument injections.
:return: Reference ``self``
"""
self.__kwargs = tuple()
self.__kwargs_len = len(self.__kwargs)
return self
@property
def initialized(self):
"""Check if resource is initialized."""
return self.__initialized
def init(self):
"""Initialize resource."""
return self.__call__()
def shutdown(self):
"""Shutdown resource."""
if not self.__initialized:
return
if self.__shutdowner:
try:
self.__shutdowner(self.__resource)
except StopIteration:
pass
self.__resource = None
self.__initialized = False
self.__shutdowner = None
cpdef object _provide(self, tuple args, dict kwargs):
if self.__initialized:
return self.__resource
if self._is_resource_subclass(self.__initializer):
initializer = self.__initializer()
self.__resource = __call(
initializer.init,
args,
self.__args,
self.__args_len,
kwargs,
self.__kwargs,
self.__kwargs_len,
)
self.__shutdowner = initializer.shutdown
elif inspect.isgeneratorfunction(self.__initializer):
initializer = __call(
self.__initializer,
args,
self.__args,
self.__args_len,
kwargs,
self.__kwargs,
self.__kwargs_len,
)
self.__resource = next(initializer)
self.__shutdowner = initializer.send
elif callable(self.__initializer):
self.__resource = __call(
self.__initializer,
args,
self.__args,
self.__args_len,
kwargs,
self.__kwargs,
self.__kwargs_len,
)
else:
raise Error('Unknown type of resource initializer')
self.__initialized = True
return self.__resource
@staticmethod
def _is_resource_subclass(instance):
if sys.version_info < (3, 5):
return False
if not isinstance(instance, CLASS_TYPES):
return
from . import resources
return issubclass(instance, resources.Resource)
cdef class Container(Provider): cdef class Container(Provider):
"""Container provider provides an instance of declarative container. """Container provider provides an instance of declarative container.

View File

@ -0,0 +1,31 @@
"""Resources module."""
import abc
import sys
from typing import TypeVar, Generic
if sys.version_info < (3, 7):
from typing import GenericMeta
else:
class GenericMeta(type):
...
T = TypeVar('T')
class ResourceMeta(GenericMeta, abc.ABCMeta):
def __getitem__(cls, item):
# Spike for Python 3.6
return cls(item)
class Resource(Generic[T], metaclass=ResourceMeta):
@abc.abstractmethod
def init(self, *args, **kwargs) -> T:
...
@abc.abstractmethod
def shutdown(self, resource: T) -> None:
...

43
tests/typing/resource.py Normal file
View File

@ -0,0 +1,43 @@
from typing import List, Iterator, Generator
from dependency_injector import providers, resources
# Test 1: to check the return type with function
def init1() -> List[int]:
return []
provider1 = providers.Resource(init1)
var1: List[int] = provider1()
# Test 2: to check the return type with iterator
def init2() -> Iterator[List[int]]:
yield []
provider2 = providers.Resource(init2)
var2: List[int] = provider2()
# Test 3: to check the return type with generator
def init3() -> Generator[List[int], None, None]:
yield []
provider3 = providers.Resource(init3)
var3: List[int] = provider3()
# Test 4: to check the return type with resource subclass
class MyResource4(resources.Resource[List[int]]):
def init(self, *args, **kwargs) -> List[int]:
return []
def shutdown(self, resource: List[int]) -> None:
...
provider4 = providers.Resource(MyResource4)
var4: List[int] = provider4()

View File

@ -186,6 +186,52 @@ class DeclarativeContainerInstanceTests(unittest.TestCase):
self.assertEqual(container.overridden, tuple()) self.assertEqual(container.overridden, tuple())
self.assertEqual(container.p11.overridden, tuple()) self.assertEqual(container.p11.overridden, tuple())
def test_init_shutdown_resources(self):
def _init1():
_init1.init_counter += 1
yield
_init1.shutdown_counter += 1
_init1.init_counter = 0
_init1.shutdown_counter = 0
def _init2():
_init2.init_counter += 1
yield
_init2.shutdown_counter += 1
_init2.init_counter = 0
_init2.shutdown_counter = 0
class Container(containers.DeclarativeContainer):
resource1 = providers.Resource(_init1)
resource2 = providers.Resource(_init2)
container = Container()
self.assertEqual(_init1.init_counter, 0)
self.assertEqual(_init1.shutdown_counter, 0)
self.assertEqual(_init2.init_counter, 0)
self.assertEqual(_init2.shutdown_counter, 0)
container.init_resources()
self.assertEqual(_init1.init_counter, 1)
self.assertEqual(_init1.shutdown_counter, 0)
self.assertEqual(_init2.init_counter, 1)
self.assertEqual(_init2.shutdown_counter, 0)
container.shutdown_resources()
self.assertEqual(_init1.init_counter, 1)
self.assertEqual(_init1.shutdown_counter, 1)
self.assertEqual(_init2.init_counter, 1)
self.assertEqual(_init2.shutdown_counter, 1)
container.init_resources()
container.shutdown_resources()
self.assertEqual(_init1.init_counter, 2)
self.assertEqual(_init1.shutdown_counter, 2)
self.assertEqual(_init2.init_counter, 2)
self.assertEqual(_init2.shutdown_counter, 2)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -93,7 +93,7 @@ class DictTests(unittest.TestCase):
self.assertIs(provider_copy, provider_copy_memo) self.assertIs(provider_copy, provider_copy_memo)
def test_deepcopy_args(self): def test_deepcopy_kwargs(self):
provider = providers.Dict() provider = providers.Dict()
dependent_provider1 = providers.Factory(list) dependent_provider1 = providers.Factory(list)
dependent_provider2 = providers.Factory(dict) dependent_provider2 = providers.Factory(dict)

View File

@ -0,0 +1,322 @@
"""Dependency injector resource provider unit tests."""
import sys
import unittest2 as unittest
from dependency_injector import containers, providers, resources, errors
def init_fn(*args, **kwargs):
return args, kwargs
class ResourceTests(unittest.TestCase):
def test_is_provider(self):
self.assertTrue(providers.is_provider(providers.Resource(init_fn)))
def test_provided_instance_provider(self):
provider = providers.Resource(init_fn)
self.assertIsInstance(provider.provided, providers.ProvidedInstance)
def test_injection(self):
resource = object()
def _init():
_init.counter += 1
return resource
_init.counter = 0
class Container(containers.DeclarativeContainer):
resource = providers.Resource(_init)
dependency1 = providers.List(resource)
dependency2 = providers.List(resource)
container = Container()
list1 = container.dependency1()
list2 = container.dependency2()
self.assertEqual(list1, [resource])
self.assertIs(list1[0], resource)
self.assertEqual(list2, [resource])
self.assertIs(list2[0], resource)
self.assertEqual(_init.counter, 1)
def test_init_function(self):
def _init():
_init.counter += 1
_init.counter = 0
provider = providers.Resource(_init)
result1 = provider()
self.assertIsNone(result1)
self.assertEqual(_init.counter, 1)
result2 = provider()
self.assertIsNone(result2)
self.assertEqual(_init.counter, 1)
provider.shutdown()
def test_init_generator(self):
def _init():
_init.init_counter += 1
yield
_init.shutdown_counter += 1
_init.init_counter = 0
_init.shutdown_counter = 0
provider = providers.Resource(_init)
result1 = provider()
self.assertIsNone(result1)
self.assertEqual(_init.init_counter, 1)
self.assertEqual(_init.shutdown_counter, 0)
provider.shutdown()
self.assertEqual(_init.init_counter, 1)
self.assertEqual(_init.shutdown_counter, 1)
result2 = provider()
self.assertIsNone(result2)
self.assertEqual(_init.init_counter, 2)
self.assertEqual(_init.shutdown_counter, 1)
provider.shutdown()
self.assertEqual(_init.init_counter, 2)
self.assertEqual(_init.shutdown_counter, 2)
def test_init_class(self):
class TestResource(resources.Resource):
init_counter = 0
shutdown_counter = 0
def init(self):
self.__class__.init_counter += 1
def shutdown(self, _):
self.__class__.shutdown_counter += 1
provider = providers.Resource(TestResource)
result1 = provider()
self.assertIsNone(result1)
self.assertEqual(TestResource.init_counter, 1)
self.assertEqual(TestResource.shutdown_counter, 0)
provider.shutdown()
self.assertEqual(TestResource.init_counter, 1)
self.assertEqual(TestResource.shutdown_counter, 1)
result2 = provider()
self.assertIsNone(result2)
self.assertEqual(TestResource.init_counter, 2)
self.assertEqual(TestResource.shutdown_counter, 1)
provider.shutdown()
self.assertEqual(TestResource.init_counter, 2)
self.assertEqual(TestResource.shutdown_counter, 2)
def test_init_not_callable(self):
provider = providers.Resource(1)
with self.assertRaises(errors.Error):
provider.init()
def test_init_and_shutdown(self):
def _init():
_init.init_counter += 1
yield
_init.shutdown_counter += 1
_init.init_counter = 0
_init.shutdown_counter = 0
provider = providers.Resource(_init)
result1 = provider.init()
self.assertIsNone(result1)
self.assertEqual(_init.init_counter, 1)
self.assertEqual(_init.shutdown_counter, 0)
provider.shutdown()
self.assertEqual(_init.init_counter, 1)
self.assertEqual(_init.shutdown_counter, 1)
result2 = provider.init()
self.assertIsNone(result2)
self.assertEqual(_init.init_counter, 2)
self.assertEqual(_init.shutdown_counter, 1)
provider.shutdown()
self.assertEqual(_init.init_counter, 2)
self.assertEqual(_init.shutdown_counter, 2)
def test_initialized(self):
provider = providers.Resource(init_fn)
self.assertFalse(provider.initialized)
provider.init()
self.assertTrue(provider.initialized)
provider.shutdown()
self.assertFalse(provider.initialized)
def test_call_with_context_args(self):
provider = providers.Resource(init_fn, 'i1', 'i2')
self.assertEqual(provider('i3', i4=4), (('i1', 'i2', 'i3'), {'i4': 4}))
def test_fluent_interface(self):
provider = providers.Resource(init_fn) \
.add_args(1, 2) \
.add_kwargs(a3=3, a4=4)
self.assertEqual(provider(), ((1, 2), {'a3': 3, 'a4': 4}))
def test_set_args(self):
provider = providers.Resource(init_fn) \
.add_args(1, 2) \
.set_args(3, 4)
self.assertEqual(provider.args, tuple([3, 4]))
def test_clear_args(self):
provider = providers.Resource(init_fn) \
.add_args(1, 2) \
.clear_args()
self.assertEqual(provider.args, tuple())
def test_set_kwargs(self):
provider = providers.Resource(init_fn) \
.add_kwargs(a1='i1', a2='i2') \
.set_kwargs(a3='i3', a4='i4')
self.assertEqual(provider.kwargs, {'a3': 'i3', 'a4': 'i4'})
def test_clear_kwargs(self):
provider = providers.Resource(init_fn) \
.add_kwargs(a1='i1', a2='i2') \
.clear_kwargs()
self.assertEqual(provider.kwargs, {})
def test_call_overridden(self):
provider = providers.Resource(init_fn, 1)
overriding_provider1 = providers.Resource(init_fn, 2)
overriding_provider2 = providers.Resource(init_fn, 3)
provider.override(overriding_provider1)
provider.override(overriding_provider2)
instance1 = provider()
instance2 = provider()
self.assertIs(instance1, instance2)
self.assertEqual(instance1, ((3,), {}))
self.assertEqual(instance2, ((3,), {}))
def test_deepcopy(self):
provider = providers.Resource(init_fn, 1, 2, a3=3, a4=4)
provider_copy = providers.deepcopy(provider)
self.assertIsNot(provider, provider_copy)
self.assertEqual(provider.args, provider_copy.args)
self.assertEqual(provider.kwargs, provider_copy.kwargs)
self.assertIsInstance(provider, providers.Resource)
def test_deepcopy_initialized(self):
provider = providers.Resource(init_fn)
provider.init()
with self.assertRaises(errors.Error):
providers.deepcopy(provider)
def test_deepcopy_from_memo(self):
provider = providers.Resource(init_fn)
provider_copy_memo = providers.Resource(init_fn)
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.Resource(init_fn)
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_kwargs(self):
provider = providers.Resource(init_fn)
dependent_provider1 = providers.Factory(list)
dependent_provider2 = providers.Factory(dict)
provider.add_kwargs(d1=dependent_provider1, d2=dependent_provider2)
provider_copy = providers.deepcopy(provider)
dependent_provider_copy1 = provider_copy.kwargs['d1']
dependent_provider_copy2 = provider_copy.kwargs['d2']
self.assertNotEqual(provider.kwargs, provider_copy.kwargs)
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.Resource(init_fn)
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.Resource)
self.assertIsNot(object_provider, object_provider_copy)
self.assertIsInstance(object_provider_copy, providers.Object)
def test_deepcopy_with_sys_streams(self):
provider = providers.Resource(init_fn)
provider.add_args(sys.stdin, sys.stdout, sys.stderr)
provider_copy = providers.deepcopy(provider)
self.assertIsNot(provider, provider_copy)
self.assertIsInstance(provider_copy, providers.Resource)
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.Resource(init_fn)
self.assertEqual(
repr(provider),
'Resource({0}, initialized={1})'.format(
init_fn,
provider.initialized,
)
)

View File

@ -5,6 +5,8 @@ envlist=
[testenv] [testenv]
deps= deps=
unittest2 unittest2
# TODO: Hotfix, remove when fixed https://github.com/aio-libs/aiohttp/issues/5107
typing_extensions
extras= extras=
yaml yaml
flask flask
@ -28,6 +30,8 @@ commands=
coveralls coveralls
[testenv:py27] [testenv:py27]
deps=
unittest2
extras= extras=
yaml yaml
flask flask
@ -35,6 +39,8 @@ commands=
unit2 discover -s tests/unit -p test_*_py2_py3.py unit2 discover -s tests/unit -p test_*_py2_py3.py
[testenv:py34] [testenv:py34]
deps=
unittest2
extras= extras=
flask flask
commands= commands=
@ -48,6 +54,8 @@ commands=
unit2 discover -s tests/unit -p test_*_py3.py unit2 discover -s tests/unit -p test_*_py3.py
[testenv:pypy] [testenv:pypy]
deps=
unittest2
extras= extras=
yaml yaml
flask flask