178 dependencies container provider (#179)

* Add DependenciesContainer provider

* Remove bundles_v2 example

* Add use cases example

* Update changelog

* Update documentation requirements to use fixed version of sphinxcontrib-disqus

* Add use cases miniapp to docs

* Update changelog
This commit is contained in:
Roman Mogylatov 2018-01-21 23:55:32 +02:00 committed by GitHub
parent af28cb60c2
commit c50322db02
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 6084 additions and 3751 deletions

View File

@ -18,3 +18,4 @@ and powered by *Dependency Injector* framework.
movie_lister
services_miniapp
bundles_miniapp
use_cases_miniapp

View File

@ -0,0 +1,57 @@
Use cases mini application example
----------------------------------
.. currentmodule:: dependency_injector.providers
"Use cases" miniapp demonstrate usage of :py:class:`DependenciesContainer`
provider.
Example application
~~~~~~~~~~~~~~~~~~~
"Use cases" mini application has next structure:
.. code-block:: bash
use_cases/
example/ <-- Example package
__init__.py
adapters.py
use_cases.py
containers.py <-- Dependency injection containers
run.py <-- Entrypoint
IoC containers
~~~~~~~~~~~~~~
Listing of ``use_cases/containers.py``:
.. literalinclude:: ../../examples/miniapps/use_cases/containers.py
:language: python
:linenos:
Run application
~~~~~~~~~~~~~~~
Listing of ``run.py``:
.. literalinclude:: ../../examples/miniapps/use_cases/run.py
:language: python
:linenos:
Instructions for running:
.. code-block:: bash
python run.py prod example@example.com # Running in "production" environment
python run.py test example@example.com # Running in "testing" environment
Links
~~~~~
+ `Dependency Injector <https://github.com/ets-labs/python-dependency-injector/>`_
+ `Full example sources <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/use_cases>`_
.. disqus::

View File

@ -7,6 +7,15 @@ that were made in every particular version.
From version 0.7.6 *Dependency Injector* framework strictly
follows `Semantic versioning`_
3.10.0
------
- Add ``DependenciesContainer`` provider.
- Add "use_cases" example miniapp.
- Update documentation requirements to use fixed version of
``sphinxcontrib-disqus``.
3.9.1
-----
- Fix docs build problem (``sphinx`` is frozen on ``1.5.6`` version because of

View File

@ -0,0 +1,9 @@
Dependency Injector Use Cases example
=====================================
Instructions for running
.. code-block:: bash
python run.py prod example@example.com # Running in "production" environment
python run.py test example@example.com # Running in "testing" environment

View File

@ -0,0 +1,30 @@
"""Dependency injection containers for 'Use Cases' example application."""
from dependency_injector import containers, providers
from example.adapters import SmtpEmailSender, EchoEmailSender
from example.use_cases import SignupUseCase
class Adapters(containers.DeclarativeContainer):
"""Adapters container."""
email_sender = providers.Singleton(SmtpEmailSender)
class TestAdapters(containers.DeclarativeContainer):
"""Adapters container.
This container is used for testing purposes.
"""
email_sender = providers.Singleton(EchoEmailSender)
class UseCases(containers.DeclarativeContainer):
"""Use cases container."""
adapters = providers.DependenciesContainer()
signup = providers.Factory(SignupUseCase,
email_sender=adapters.email_sender)

View File

@ -0,0 +1 @@
"""Example top-level package."""

View File

@ -0,0 +1,25 @@
"""Example adapters package."""
class EmailSender(object):
"""Abstract email sender."""
def send(self, to, body):
"""Send email to specified email."""
raise NotImplementedError()
class SmtpEmailSender(object):
"""SMTP email sender uses SMTP protocol for sending emails."""
def send(self, to, body):
"""Send email to specified email."""
# Send email via SMTP
class EchoEmailSender(object):
"""Echo email sender prints emails to stdout."""
def send(self, to, body):
"""Send email to specified email."""
print('Sending email to "{0}", body = "{1}"'.format(to, body))

View File

@ -0,0 +1,22 @@
"""Example use cases package."""
class UseCase(object):
"""Abstract use case."""
def execute(self):
"""Execute use case handling."""
raise NotImplementedError()
class SignupUseCase(object):
"""Sign up use cases registers users."""
def __init__(self, email_sender):
"""Initializer."""
self.email_sender = email_sender
def execute(self, email):
"""Execute use case handling."""
print('Sign up user {0}'.format(email))
self.email_sender.send(email, 'Welcome, "{}"'.format(email))

View File

@ -0,0 +1,19 @@
"""Run 'Use Cases' example application."""
import sys
from containers import Adapters, TestAdapters, UseCases
if __name__ == '__main__':
environment, email = sys.argv[1:]
if environment == 'prod':
adapters = Adapters()
elif environment == 'test':
adapters = TestAdapters()
use_cases = UseCases(adapters=adapters)
use_case = use_cases.signup()
use_case.execute(email)

View File

@ -1,5 +1,5 @@
sphinx==1.5.6
sphinx_rtd_theme>=0.2.5b1
sphinx
-e git://github.com/rmk135/sphinxcontrib-disqus.git#egg=sphinxcontrib-disqus
sphinx_autobuild
sphinxcontrib-disqus
autodoc

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -13,6 +13,7 @@ cdef class Provider(object):
cdef object __overriding_lock
cpdef object _provide(self, tuple args, dict kwargs)
cpdef void _copy_overridings(self, Provider copied)
cdef class Object(Provider):
@ -33,6 +34,12 @@ cdef class ExternalDependency(Dependency):
pass
cdef class DependenciesContainer(Object):
cdef dict __providers
cpdef object _override_providers(self, object container)
cdef class OverridingContext(object):
cdef Provider __overridden
cdef Provider __overriding

View File

@ -107,8 +107,7 @@ cdef class Provider(object):
copied = self.__class__()
for overriding_provider in self.overridden:
copied.override(deepcopy(overriding_provider, memo))
self._copy_overridings(copied)
return copied
@ -215,6 +214,11 @@ cdef class Provider(object):
"""
raise NotImplementedError()
cpdef void _copy_overridings(self, Provider copied):
"""Copy provider overridings to a newly copied provider."""
copied.__overridden = deepcopy(self.__overridden)
copied.__last_overriding = deepcopy(self.__last_overriding)
cdef class Object(Provider):
"""Object provider returns provided instance "as is".
@ -243,8 +247,7 @@ cdef class Object(Provider):
copied = self.__class__(deepcopy(self.__provides, memo))
for overriding_provider in self.overridden:
copied.override(deepcopy(overriding_provider, memo))
self._copy_overridings(copied)
return copied
@ -330,8 +333,7 @@ cdef class Dependency(Provider):
copied = self.__class__(self.__instance_of)
for overriding_provider in self.overridden:
copied.override(deepcopy(overriding_provider, memo))
self._copy_overridings(copied)
return copied
@ -418,6 +420,121 @@ cdef class ExternalDependency(Dependency):
"""
cdef class DependenciesContainer(Object):
""":py:class:`DependenciesContainer` provider provides set of dependencies.
Dependencies container provider is used to implement late static binding
for a set of providers of a particular container.
Example code:
.. code-block:: python
class Adapters(containers.DeclarativeContainer):
email_sender = providers.Singleton(SmtpEmailSender)
class TestAdapters(containers.DeclarativeContainer):
email_sender = providers.Singleton(EchoEmailSender)
class UseCases(containers.DeclarativeContainer):
adapters = providers.DependenciesContainer()
signup = providers.Factory(SignupUseCase,
email_sender=adapters.email_sender)
use_cases = UseCases(adapters=Adapters)
# or
use_cases = UseCases(adapters=TestAdapters)
# Another file
from .containers import use_cases
use_case = use_cases.signup()
use_case.execute()
"""
def __init__(self, provides=None, **dependencies):
"""Initializer."""
self.__providers = dependencies
if provides:
self._override_providers(container=provides)
super(DependenciesContainer, self).__init__(provides)
def __deepcopy__(self, memo):
"""Create and return full copy of provider."""
cdef DependenciesContainer copied
copied = memo.get(id(self))
if copied is not None:
return copied
copied = self.__class__()
copied.__provides = deepcopy(self.__provides, memo)
copied.__providers = deepcopy(self.__providers, memo)
self._copy_overridings(copied)
return copied
def __getattr__(self, name):
"""Return dependency provider."""
if name.startswith('__') and name.endswith('__'):
raise AttributeError(
'\'{cls}\' object has no attribute '
'\'{attribute_name}\''.format(cls=self.__class__.__name__,
attribute_name=name))
provider = self.__providers.get(name)
if not provider:
provider = Dependency()
self.__providers[name] = provider
container = self.__call__()
if container:
dependency_provider = container.providers.get(name)
if dependency_provider:
provider.override(dependency_provider)
return provider
@property
def providers(self):
"""Read-only dictionary of dependency providers."""
return self.__providers
def override(self, provider):
"""Override provider with another provider.
:param provider: Overriding provider.
:type provider: :py:class:`Provider`
:raise: :py:exc:`dependency_injector.errors.Error`
:return: Overriding context.
:rtype: :py:class:`OverridingContext`
"""
self._override_providers(container=provider)
return super(DependenciesContainer, self).override(provider)
cpdef object _override_providers(self, object container):
"""Override providers with providers from provided container."""
for name, dependency_provider in container.providers.items():
provider = self.__providers.get(name)
if not provider:
continue
if provider.last_overriding is dependency_provider:
continue
provider.override(dependency_provider)
cdef class OverridingContext(object):
"""Provider overriding context.
@ -517,8 +634,7 @@ cdef class Callable(Provider):
*deepcopy(self.args, memo),
**deepcopy(self.kwargs, memo))
for overriding_provider in self.overridden:
copied.override(deepcopy(overriding_provider, memo))
self._copy_overridings(copied)
return copied
@ -755,8 +871,7 @@ cdef class Configuration(Provider):
copied.__value = deepcopy(self.__value, memo)
copied.__children = deepcopy(self.__children, memo)
for overriding_provider in self.overridden:
copied.override(deepcopy(overriding_provider, memo))
self._copy_overridings(copied)
return copied
@ -923,8 +1038,7 @@ cdef class Factory(Provider):
**deepcopy(self.kwargs, memo))
copied.set_attributes(**deepcopy(self.attributes, memo))
for overriding_provider in self.overridden:
copied.override(deepcopy(overriding_provider, memo))
self._copy_overridings(copied)
return copied
@ -1272,8 +1386,7 @@ cdef class BaseSingleton(Provider):
**deepcopy(self.kwargs, memo))
copied.set_attributes(**deepcopy(self.attributes, memo))
for overriding_provider in self.overridden:
copied.override(deepcopy(overriding_provider, memo))
self._copy_overridings(copied)
return copied

View File

@ -3,6 +3,7 @@
import unittest2 as unittest
from dependency_injector import (
containers,
providers,
errors,
)
@ -298,3 +299,52 @@ class ExternalDependencyTests(unittest.TestCase):
def test_is_instance(self):
self.assertIsInstance(self.provider, providers.Dependency)
class DependenciesContainerTests(unittest.TestCase):
class Container(containers.DeclarativeContainer):
dependency = providers.Provider()
def setUp(self):
self.provider = providers.DependenciesContainer()
self.container = self.Container()
def test_getattr(self):
has_dependency = hasattr(self.provider, 'dependency')
dependency = self.provider.dependency
self.assertIsInstance(dependency, providers.Dependency)
self.assertIs(dependency, self.provider.dependency)
self.assertTrue(has_dependency)
self.assertIsNone(dependency.last_overriding)
def test_getattr_with_container(self):
self.provider.override(self.container)
dependency = self.provider.dependency
self.assertTrue(dependency.overridden)
self.assertIs(dependency.last_overriding, self.container.dependency)
def test_providers(self):
dependency1 = self.provider.dependency1
dependency2 = self.provider.dependency2
self.assertEqual(self.provider.providers, {'dependency1': dependency1,
'dependency2': dependency2})
def test_override(self):
dependency = self.provider.dependency
self.provider.override(self.container)
self.assertTrue(dependency.overridden)
self.assertIs(dependency.last_overriding, self.container.dependency)
def test_init_with_container_and_providers(self):
provider = providers.DependenciesContainer(
self.container, dependency=providers.Dependency())
dependency = provider.dependency
self.assertTrue(dependency.overridden)
self.assertIs(dependency.last_overriding, self.container.dependency)