Merge branch 'release/4.1.0' into master

This commit is contained in:
Roman Mogylatov 2020-10-24 20:58:22 -04:00
commit 90af46da98
29 changed files with 13886 additions and 5957 deletions

View File

@ -1,4 +1,4 @@
Copyright (c) 2017, ETS Labs Copyright (c) 2020, ETS Labs
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without

View File

@ -57,16 +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``, ``Configuration``, ``Dependency`` and ``Selector`` providers that help assembling your ``List``, ``Dict``, ``Configuration``, ``Resource``, ``Dependency`` and ``Selector`` providers
objects. See `Providers <https://python-dependency-injector.ets-labs.org/providers/index.html>`_. that help assembling your objects.
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

@ -141,7 +141,7 @@ html_theme_path = [alabaster.get_path()]
# The name of an image file (within the static path) to use as favicon of the # The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large. # pixels large.
#html_favicon = None html_favicon = 'favicon.ico'
# Add any paths that contain custom static files (such as style sheets) here, # Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files, # relative to this directory. They are copied after the builtin static files,

View File

@ -21,4 +21,20 @@ The container also has:
:py:class:`DynamicContainer` has the same functionality. :py:class:`DynamicContainer` has the same functionality.
Another possible way to override container providers on declarative level is
``@containers.override()`` decorator:
.. literalinclude:: ../../examples/containers/declarative_override_decorator.py
:language: python
:lines: 3-
:emphasize-lines: 12-16
Decorator ``@containers.override()`` takes a container for overriding as an argument.
This container providers will be overridden by the providers with the same names from
the decorated container.
It helps to change the behaviour of application by importing extension modules but not a code change.
Imported module can override providers in main container. While the code uses main container as
before, the overridden providers provide components defined in the extension module.
.. disqus:: .. disqus::

BIN
docs/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -34,15 +34,15 @@ Dependency Injector --- Dependency injection framework for Python
:target: https://pypi.org/project/dependency-injector/ :target: https://pypi.org/project/dependency-injector/
:alt: Supported Python implementations :alt: Supported Python implementations
.. image:: https://pepy.tech/badge/dependency-injector .. image:: https://static.pepy.tech/badge/dependency-injector
:target: https://pepy.tech/project/dependency-injector :target: https://pepy.tech/project/dependency-injector
:alt: Downloads :alt: Downloads
.. image:: https://pepy.tech/badge/dependency-injector/month .. image:: https://static.pepy.tech/badge/dependency-injector/month
:target: https://pepy.tech/project/dependency-injector :target: https://pepy.tech/project/dependency-injector
:alt: Downloads :alt: Downloads
.. image:: https://pepy.tech/badge/dependency-injector/week .. image:: https://static.pepy.tech/badge/dependency-injector/week
:target: https://pepy.tech/project/dependency-injector :target: https://pepy.tech/project/dependency-injector
:alt: Downloads :alt: Downloads
@ -50,7 +50,7 @@ Dependency Injector --- Dependency injection framework for Python
:target: https://pypi.org/project/dependency-injector/ :target: https://pypi.org/project/dependency-injector/
:alt: Wheel :alt: Wheel
.. image:: https://travis-ci.org/ets-labs/python-dependency-injector.svg?branch=master .. image:: https://api.travis-ci.org/ets-labs/python-dependency-injector.svg?branch=master
:target: https://travis-ci.org/ets-labs/python-dependency-injector :target: https://travis-ci.org/ets-labs/python-dependency-injector
:alt: Build Status :alt: Build Status
@ -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``, ``Configuration``, ``Dependency`` and ``Selector`` providers that help assembling your ``List``, ``Dict``, ``Configuration``, ``Resource``, ``Dependency`` and ``Selector`` providers
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``, ``Configuration``, ``Dependency`` and ``Selector`` providers that help assembling your ``List``, ``Dict``, ``Configuration``, ``Resource``, ``Dependency`` and ``Selector`` providers
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

@ -7,6 +7,18 @@ that were made in every particular version.
From version 0.7.6 *Dependency Injector* framework strictly From version 0.7.6 *Dependency Injector* framework strictly
follows `Semantic versioning`_ follows `Semantic versioning`_
4.1.0
-----
- Add ``Resource`` provider.
- Add ``Dict`` provider.
- "Un-deprecate" ``@containers.override()`` and ``@containers.copy()`` decorators (
see `Issue 301 <https://github.com/ets-labs/python-dependency-injector/issues/301>`_
for more information).
- Add favicon.
- Remove redirects that occur while getting badge images to optimize docs load speed.
- Update license year.
- Update short description on PyPI.
4.0.6 4.0.6
----- -----
- Fix wiring for top-level package ``__init__.py``. - Fix wiring for top-level package ``__init__.py``.
@ -51,8 +63,6 @@ Deprecations:
- Deprecate ``ext.aiohttp`` module in favor of ``wiring`` feature. - Deprecate ``ext.aiohttp`` module in favor of ``wiring`` feature.
- Deprecate ``ext.flask`` module in favor of ``wiring`` feature. - Deprecate ``ext.flask`` module in favor of ``wiring`` feature.
- Deprecate ``.delegate()`` provider method in favor of ``.provider`` attribute. - Deprecate ``.delegate()`` provider method in favor of ``.provider`` attribute.
- Deprecate ``@containers.override()`` decorator in favor of overriding container on instance level.
- Deprecate ``@containers.copy()`` decorator.
Removals: Removals:

23
docs/providers/dict.rst Normal file
View File

@ -0,0 +1,23 @@
Dict provider
=============
.. meta::
:keywords: Python,DI,Dependency injection,IoC,Inversion of Control,Dict,Injection
:description: Dict provider helps to inject a dictionary of the dependencies. This page demonstrates
how to use Dict provider.
.. currentmodule:: dependency_injector.providers
:py:class:`Dict` provider provides a dictionary of values.
.. literalinclude:: ../../examples/providers/dict.py
:language: python
:lines: 3-
:emphasize-lines: 21-24
``Dict`` provider handles keyword arguments the same way as a :ref:`factory-provider`.
.. note::
Positional argument are not supported.
.. disqus::

View File

@ -43,7 +43,9 @@ Providers module API docs - :py:mod:`dependency_injector.providers`
coroutine coroutine
object object
list list
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,25 @@
"""Declarative container provider overriding with `@override()` decorator."""
import sqlite3
from unittest import mock
from dependency_injector import containers, providers
class Container(containers.DeclarativeContainer):
database = providers.Singleton(sqlite3.connect, ':memory:')
# Overriding `Container` with `OverridingContainer`:
@containers.override(Container)
class OverridingContainer(containers.DeclarativeContainer):
database = providers.Singleton(mock.Mock)
if __name__ == '__main__':
container = Container()
database = container.database()
assert isinstance(database, mock.Mock)

View File

@ -0,0 +1,45 @@
"""`Dict` provider example."""
import dataclasses
from typing import Dict
from dependency_injector import containers, providers
@dataclasses.dataclass
class Module:
name: str
@dataclasses.dataclass
class Dispatcher:
modules: Dict[str, Module]
class Container(containers.DeclarativeContainer):
dispatcher_factory = providers.Factory(
Dispatcher,
modules=providers.Dict(
module1=providers.Factory(Module, name='m1'),
module2=providers.Factory(Module, name='m2'),
),
)
if __name__ == '__main__':
container = Container()
dispatcher = container.dispatcher_factory()
assert isinstance(dispatcher.modules, dict)
assert dispatcher.modules['module1'].name == 'm1'
assert dispatcher.modules['module2'].name == 'm2'
# Call "dispatcher = container.dispatcher_factory()" is equivalent to:
# dispatcher = Dispatcher(
# modules={
# 'module1': Module(name='m1'),
# 'module2': Module(name='m2'),
# },
# )

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',

View File

@ -1,6 +1,6 @@
"""Top-level package.""" """Top-level package."""
__version__ = '4.0.6' __version__ = '4.1.0'
"""Version number. """Version number.
:type: str :type: str

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

@ -1,13 +1,13 @@
"""Containers module.""" """Containers module."""
import sys import sys
import warnings
import six import six
from .errors import Error from .errors import Error
from .providers cimport ( from .providers cimport (
Provider, Provider,
Resource,
deepcopy, deepcopy,
) )
@ -214,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):
@ -428,11 +441,6 @@ def override(object container):
:return: Declarative container's overriding decorator. :return: Declarative container's overriding decorator.
:rtype: callable(:py:class:`DeclarativeContainer`) :rtype: callable(:py:class:`DeclarativeContainer`)
""" """
warnings.warn(
'Decorator "@override()" is deprecated since version 4.0.3. '
'Use overriding on instance level instead "container.override(AnotherContainer())".',
category=DeprecationWarning,
)
def _decorator(object overriding_container): def _decorator(object overriding_container):
"""Overriding decorator.""" """Overriding decorator."""
container.override(overriding_container) container.override(overriding_container)
@ -453,10 +461,6 @@ def copy(object container):
:return: Declarative container's copying decorator. :return: Declarative container's copying decorator.
:rtype: callable(:py:class:`DeclarativeContainer`) :rtype: callable(:py:class:`DeclarativeContainer`)
""" """
warnings.warn(
'Decorator "@copy()" is deprecated since version 4.0.3.',
category=DeprecationWarning,
)
def _decorator(copied_container): def _decorator(copied_container):
cdef dict memo = dict() cdef dict memo = dict()
for name, provider in six.iteritems(copied_container.cls_providers): for name, provider in six.iteritems(copied_container.cls_providers):

File diff suppressed because it is too large Load Diff

View File

@ -184,6 +184,28 @@ cdef class List(Provider):
cpdef object _provide(self, tuple args, dict kwargs) cpdef object _provide(self, tuple args, dict kwargs)
cdef class Dict(Provider):
cdef tuple __kwargs
cdef int __kwargs_len
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

@ -9,12 +9,17 @@ from typing import (
Any, Any,
Tuple, Tuple,
List as _List, List as _List,
Dict as _Dict,
Optional, Optional,
Dict,
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')
@ -29,7 +34,7 @@ class OverridingContext:
class Provider(Generic[T]): class Provider(Generic[T]):
def __init__(self) -> None: ... def __init__(self) -> None: ...
def __call__(self, *args: Injection, **kwargs: Injection) -> T: ... def __call__(self, *args: Injection, **kwargs: Injection) -> T: ...
def __deepcopy__(self, memo: Optional[Dict[Any, Any]]) -> Provider: ... def __deepcopy__(self, memo: Optional[_Dict[Any, Any]]) -> Provider: ...
def __str__(self) -> str: ... def __str__(self) -> str: ...
def __repr__(self) -> str: ... def __repr__(self) -> str: ...
@property @property
@ -44,7 +49,7 @@ class Provider(Generic[T]):
def provider(self) -> Provider: ... def provider(self) -> Provider: ...
@property @property
def provided(self) -> ProvidedInstance: ... def provided(self) -> ProvidedInstance: ...
def _copy_overridings(self, copied: Provider, memo: Optional[Dict[Any, Any]]) -> None: ... def _copy_overridings(self, copied: Provider, memo: Optional[_Dict[Any, Any]]) -> None: ...
class Object(Provider, Generic[T]): class Object(Provider, Generic[T]):
@ -74,7 +79,7 @@ class DependenciesContainer(Object):
def __init__(self, **dependencies: Provider) -> None: ... def __init__(self, **dependencies: Provider) -> None: ...
def __getattr__(self, name: str) -> Provider: ... def __getattr__(self, name: str) -> Provider: ...
@property @property
def providers(self) -> Dict[str, Provider]: ... def providers(self) -> _Dict[str, Provider]: ...
class Callable(Provider, Generic[T]): class Callable(Provider, Generic[T]):
@ -88,7 +93,7 @@ class Callable(Provider, Generic[T]):
def set_args(self, *args: Injection) -> Callable[T]: ... def set_args(self, *args: Injection) -> Callable[T]: ...
def clear_args(self) -> Callable[T]: ... def clear_args(self) -> Callable[T]: ...
@property @property
def kwargs(self) -> Dict[str, Injection]: ... def kwargs(self) -> _Dict[str, Injection]: ...
def add_kwargs(self, **kwargs: Injection) -> Callable[T]: ... def add_kwargs(self, **kwargs: Injection) -> Callable[T]: ...
def set_kwargs(self, **kwargs: Injection) -> Callable[T]: ... def set_kwargs(self, **kwargs: Injection) -> Callable[T]: ...
def clear_kwargs(self) -> Callable[T]: ... def clear_kwargs(self) -> Callable[T]: ...
@ -135,7 +140,7 @@ class ConfigurationOption(Provider):
def update(self, value: Any) -> None: ... def update(self, value: Any) -> None: ...
def from_ini(self, filepath: Union[Path, str]) -> None: ... def from_ini(self, filepath: Union[Path, str]) -> None: ...
def from_yaml(self, filepath: Union[Path, str]) -> None: ... def from_yaml(self, filepath: Union[Path, str]) -> None: ...
def from_dict(self, options: Dict[str, Any]) -> None: ... def from_dict(self, options: _Dict[str, Any]) -> None: ...
def from_env(self, name: str, default: Optional[Any] = None) -> None: ... def from_env(self, name: str, default: Optional[Any] = None) -> None: ...
@ -156,7 +161,7 @@ class Configuration(Object):
def update(self, value: Any) -> None: ... def update(self, value: Any) -> None: ...
def from_ini(self, filepath: Union[Path, str]) -> None: ... def from_ini(self, filepath: Union[Path, str]) -> None: ...
def from_yaml(self, filepath: Union[Path, str]) -> None: ... def from_yaml(self, filepath: Union[Path, str]) -> None: ...
def from_dict(self, options: Dict[str, Any]) -> None: ... def from_dict(self, options: _Dict[str, Any]) -> None: ...
def from_env(self, name: str, default: Optional[Any] = None) -> None: ... def from_env(self, name: str, default: Optional[Any] = None) -> None: ...
@ -174,12 +179,12 @@ class Factory(Provider, Generic[T]):
def set_args(self, *args: Injection) -> Factory[T]: ... def set_args(self, *args: Injection) -> Factory[T]: ...
def clear_args(self) -> Factory[T]: ... def clear_args(self) -> Factory[T]: ...
@property @property
def kwargs(self) -> Dict[str, Injection]: ... def kwargs(self) -> _Dict[str, Injection]: ...
def add_kwargs(self, **kwargs: Injection) -> Factory[T]: ... def add_kwargs(self, **kwargs: Injection) -> Factory[T]: ...
def set_kwargs(self, **kwargs: Injection) -> Factory[T]: ... def set_kwargs(self, **kwargs: Injection) -> Factory[T]: ...
def clear_kwargs(self) -> Factory[T]: ... def clear_kwargs(self) -> Factory[T]: ...
@property @property
def attributes(self) -> Dict[str, Injection]: ... def attributes(self) -> _Dict[str, Injection]: ...
def add_attributes(self, **kwargs: Injection) -> Factory[T]: ... def add_attributes(self, **kwargs: Injection) -> Factory[T]: ...
def set_attributes(self, **kwargs: Injection) -> Factory[T]: ... def set_attributes(self, **kwargs: Injection) -> Factory[T]: ...
def clear_attributes(self) -> Factory[T]: ... def clear_attributes(self) -> Factory[T]: ...
@ -201,7 +206,7 @@ class FactoryAggregate(Provider):
def __call__(self, factory_name: str, *args: Injection, **kwargs: Injection) -> Any: ... def __call__(self, factory_name: str, *args: Injection, **kwargs: Injection) -> Any: ...
def __getattr__(self, factory_name: str) -> Factory: ... def __getattr__(self, factory_name: str) -> Factory: ...
@property @property
def factories(self) -> Dict[str, Factory]: ... def factories(self) -> _Dict[str, Factory]: ...
class BaseSingleton(Provider, Generic[T]): class BaseSingleton(Provider, Generic[T]):
@ -216,12 +221,12 @@ class BaseSingleton(Provider, Generic[T]):
def set_args(self, *args: Injection) -> Factory[T]: ... def set_args(self, *args: Injection) -> Factory[T]: ...
def clear_args(self) -> Factory[T]: ... def clear_args(self) -> Factory[T]: ...
@property @property
def kwargs(self) -> Dict[str, Injection]: ... def kwargs(self) -> _Dict[str, Injection]: ...
def add_kwargs(self, **kwargs: Injection) -> Factory[T]: ... def add_kwargs(self, **kwargs: Injection) -> Factory[T]: ...
def set_kwargs(self, **kwargs: Injection) -> Factory[T]: ... def set_kwargs(self, **kwargs: Injection) -> Factory[T]: ...
def clear_kwargs(self) -> Factory[T]: ... def clear_kwargs(self) -> Factory[T]: ...
@property @property
def attributes(self) -> Dict[str, Injection]: ... def attributes(self) -> _Dict[str, Injection]: ...
def add_attributes(self, **kwargs: Injection) -> Factory[T]: ... def add_attributes(self, **kwargs: Injection) -> Factory[T]: ...
def set_attributes(self, **kwargs: Injection) -> Factory[T]: ... def set_attributes(self, **kwargs: Injection) -> Factory[T]: ...
def clear_attributes(self) -> Factory[T]: ... def clear_attributes(self) -> Factory[T]: ...
@ -264,6 +269,40 @@ class List(Provider):
def clear_args(self) -> List: ... def clear_args(self) -> List: ...
class Dict(Provider):
def __init__(self, **kwargs: Injection): ...
def __call__(self, *args: Injection, **kwargs: Injection) -> _Dict[Any, Any]: ...
@property
def kwargs(self) -> _Dict[Any, Injection]: ...
def add_kwargs(self, **kwargs: Injection) -> Dict: ...
def set_kwargs(self, **kwargs: Injection) -> 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: ...
@ -278,7 +317,7 @@ class Selector(Provider):
def __call__(self, *args: Injection, **kwargs: Injection) -> Any: ... def __call__(self, *args: Injection, **kwargs: Injection) -> Any: ...
def __getattr__(self, name: str) -> Provider: ... def __getattr__(self, name: str) -> Provider: ...
@property @property
def providers(self) -> Dict[str, Provider]: ... def providers(self) -> _Dict[str, Provider]: ...
class ProvidedInstanceFluentInterface: class ProvidedInstanceFluentInterface:
@ -315,7 +354,7 @@ def is_delegated(instance: Any) -> bool: ...
def represent_provider(provider: Provider, provides: Any) -> str: ... def represent_provider(provider: Provider, provides: Any) -> str: ...
def deepcopy(instance: Any, memo: Optional[Dict[Any, Any]]): Any: ... def deepcopy(instance: Any, memo: Optional[_Dict[Any, Any]] = None): Any: ...
def merge_dicts(dict1: Dict[Any, Any], dict2: Dict[Any, Any]) -> Dict[Any, Any]: ... def merge_dicts(dict1: _Dict[Any, Any], dict2: _Dict[Any, Any]) -> _Dict[Any, Any]: ...

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
@ -2449,6 +2450,313 @@ cdef class List(Provider):
return list(__provide_positional_args(args, self.__args, self.__args_len)) return list(__provide_positional_args(args, self.__args, self.__args_len))
cdef class Dict(Provider):
"""Dict provider provides a dictionary of values.
:py:class:`Dict` provider is needed for injecting a dictionary of dependencies. It handles
keyword argument injections the same way as :py:class:`Factory` provider.
Positional argument injections are not supported.
.. code-block:: python
dispatcher_factory = Factory(
Dispatcher,
modules=Dict(
module1=Factory(ModuleA, dependency_a),
module2=Factory(ModuleB, dependency_b),
),
)
dispatcher = dispatcher_factory()
# is equivalent to:
dispatcher = Dispatcher(
modules={
'module1': ModuleA(dependency_a),
'module2': ModuleB(dependency_b),
},
)
"""
def __init__(self, **kwargs):
"""Initializer."""
self.__kwargs = tuple()
self.__kwargs_len = 0
self.set_kwargs(**kwargs)
super(Dict, 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.kwargs, memo))
self._copy_overridings(copied, memo)
return copied
def __str__(self):
"""Return string representation of provider.
:rtype: str
"""
return represent_provider(provider=self, provides=self.kwargs)
@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
cpdef object _provide(self, tuple args, dict kwargs):
"""Return result of provided callable's call."""
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

@ -0,0 +1,149 @@
"""Dependency injector dict provider unit tests."""
import sys
import unittest2 as unittest
from dependency_injector import providers
class DictTests(unittest.TestCase):
def test_is_provider(self):
self.assertTrue(providers.is_provider(providers.Dict()))
def test_provided_instance_provider(self):
provider = providers.Dict()
self.assertIsInstance(provider.provided, providers.ProvidedInstance)
def test_call_with_init_keyword_args(self):
provider = providers.Dict(a1='i1', a2='i2')
dict1 = provider()
dict2 = provider()
self.assertEqual(dict1, {'a1': 'i1', 'a2': 'i2'})
self.assertEqual(dict2, {'a1': 'i1', 'a2': 'i2'})
self.assertIsNot(dict1, dict2)
def test_call_with_context_keyword_args(self):
provider = providers.Dict(a1='i1', a2='i2')
self.assertEqual(
provider(a3='i3', a4='i4'),
{'a1': 'i1', 'a2': 'i2', 'a3': 'i3', 'a4': 'i4'},
)
def test_call_with_provider(self):
provider = providers.Dict(
a1=providers.Factory(str, 'i1'),
a2=providers.Factory(str, 'i2'),
)
self.assertEqual(provider(), {'a1': 'i1', 'a2': 'i2'})
def test_fluent_interface(self):
provider = providers.Dict() \
.add_kwargs(a1='i1', a2='i2')
self.assertEqual(provider(), {'a1': 'i1', 'a2': 'i2'})
def test_set_kwargs(self):
provider = providers.Dict() \
.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.Dict() \
.add_kwargs(a1='i1', a2='i2') \
.clear_kwargs()
self.assertEqual(provider.kwargs, {})
def test_call_overridden(self):
provider = providers.Dict(a1='i1', a2='i2')
overriding_provider1 = providers.Dict(a2='i2', a3='i3')
overriding_provider2 = providers.Dict(a3='i3', a4='i4')
provider.override(overriding_provider1)
provider.override(overriding_provider2)
instance1 = provider()
instance2 = provider()
self.assertIsNot(instance1, instance2)
self.assertEqual(instance1, {'a3': 'i3', 'a4': 'i4'})
self.assertEqual(instance2, {'a3': 'i3', 'a4': 'i4'})
def test_deepcopy(self):
provider = providers.Dict(a1='i1', a2='i2')
provider_copy = providers.deepcopy(provider)
self.assertIsNot(provider, provider_copy)
self.assertEqual(provider.kwargs, provider_copy.kwargs)
self.assertIsInstance(provider, providers.Dict)
def test_deepcopy_from_memo(self):
provider = providers.Dict(a1='i1', a2='i2')
provider_copy_memo = providers.Dict(a1='i1', a2='i2')
provider_copy = providers.deepcopy(
provider,
memo={id(provider): provider_copy_memo},
)
self.assertIs(provider_copy, provider_copy_memo)
def test_deepcopy_kwargs(self):
provider = providers.Dict()
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.Dict()
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.kwargs, provider_copy.kwargs)
self.assertIsInstance(provider, providers.Dict)
self.assertIsNot(object_provider, object_provider_copy)
self.assertIsInstance(object_provider_copy, providers.Object)
def test_deepcopy_with_sys_streams(self):
provider = providers.Dict()
provider.add_kwargs(stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr)
provider_copy = providers.deepcopy(provider)
self.assertIsNot(provider, provider_copy)
self.assertIsInstance(provider_copy, providers.Dict)
self.assertIs(provider.kwargs['stdin'], sys.stdin)
self.assertIs(provider.kwargs['stdout'], sys.stdout)
self.assertIs(provider.kwargs['stderr'], sys.stderr)
def test_repr(self):
provider = providers.Dict(a1=1, a2=2)
self.assertEqual(repr(provider),
'<dependency_injector.providers.'
'Dict({0}) at {1}>'.format(
repr(provider.kwargs),
hex(id(provider))))

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