mirror of
https://github.com/ets-labs/python-dependency-injector.git
synced 2024-11-25 11:04:01 +03:00
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:
parent
af28cb60c2
commit
c50322db02
|
@ -18,3 +18,4 @@ and powered by *Dependency Injector* framework.
|
||||||
movie_lister
|
movie_lister
|
||||||
services_miniapp
|
services_miniapp
|
||||||
bundles_miniapp
|
bundles_miniapp
|
||||||
|
use_cases_miniapp
|
||||||
|
|
57
docs/examples/use_cases_miniapp.rst
Normal file
57
docs/examples/use_cases_miniapp.rst
Normal 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::
|
|
@ -7,6 +7,15 @@ 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`_
|
||||||
|
|
||||||
|
|
||||||
|
3.10.0
|
||||||
|
------
|
||||||
|
- Add ``DependenciesContainer`` provider.
|
||||||
|
- Add "use_cases" example miniapp.
|
||||||
|
- Update documentation requirements to use fixed version of
|
||||||
|
``sphinxcontrib-disqus``.
|
||||||
|
|
||||||
|
|
||||||
3.9.1
|
3.9.1
|
||||||
-----
|
-----
|
||||||
- Fix docs build problem (``sphinx`` is frozen on ``1.5.6`` version because of
|
- Fix docs build problem (``sphinx`` is frozen on ``1.5.6`` version because of
|
||||||
|
|
9
examples/miniapps/use_cases/README.rst
Normal file
9
examples/miniapps/use_cases/README.rst
Normal 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
|
30
examples/miniapps/use_cases/containers.py
Normal file
30
examples/miniapps/use_cases/containers.py
Normal 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)
|
1
examples/miniapps/use_cases/example/__init__.py
Normal file
1
examples/miniapps/use_cases/example/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
"""Example top-level package."""
|
25
examples/miniapps/use_cases/example/adapters.py
Normal file
25
examples/miniapps/use_cases/example/adapters.py
Normal 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))
|
22
examples/miniapps/use_cases/example/use_cases.py
Normal file
22
examples/miniapps/use_cases/example/use_cases.py
Normal 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))
|
19
examples/miniapps/use_cases/run.py
Normal file
19
examples/miniapps/use_cases/run.py
Normal 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)
|
|
@ -1,5 +1,5 @@
|
||||||
sphinx==1.5.6
|
sphinx
|
||||||
sphinx_rtd_theme>=0.2.5b1
|
-e git://github.com/rmk135/sphinxcontrib-disqus.git#egg=sphinxcontrib-disqus
|
||||||
sphinx_autobuild
|
sphinx_autobuild
|
||||||
sphinxcontrib-disqus
|
sphinxcontrib-disqus
|
||||||
autodoc
|
autodoc
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -13,6 +13,7 @@ cdef class Provider(object):
|
||||||
cdef object __overriding_lock
|
cdef object __overriding_lock
|
||||||
|
|
||||||
cpdef object _provide(self, tuple args, dict kwargs)
|
cpdef object _provide(self, tuple args, dict kwargs)
|
||||||
|
cpdef void _copy_overridings(self, Provider copied)
|
||||||
|
|
||||||
|
|
||||||
cdef class Object(Provider):
|
cdef class Object(Provider):
|
||||||
|
@ -33,6 +34,12 @@ cdef class ExternalDependency(Dependency):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
cdef class DependenciesContainer(Object):
|
||||||
|
cdef dict __providers
|
||||||
|
|
||||||
|
cpdef object _override_providers(self, object container)
|
||||||
|
|
||||||
|
|
||||||
cdef class OverridingContext(object):
|
cdef class OverridingContext(object):
|
||||||
cdef Provider __overridden
|
cdef Provider __overridden
|
||||||
cdef Provider __overriding
|
cdef Provider __overriding
|
||||||
|
|
|
@ -107,8 +107,7 @@ cdef class Provider(object):
|
||||||
|
|
||||||
copied = self.__class__()
|
copied = self.__class__()
|
||||||
|
|
||||||
for overriding_provider in self.overridden:
|
self._copy_overridings(copied)
|
||||||
copied.override(deepcopy(overriding_provider, memo))
|
|
||||||
|
|
||||||
return copied
|
return copied
|
||||||
|
|
||||||
|
@ -215,6 +214,11 @@ cdef class Provider(object):
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
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):
|
cdef class Object(Provider):
|
||||||
"""Object provider returns provided instance "as is".
|
"""Object provider returns provided instance "as is".
|
||||||
|
@ -243,8 +247,7 @@ cdef class Object(Provider):
|
||||||
|
|
||||||
copied = self.__class__(deepcopy(self.__provides, memo))
|
copied = self.__class__(deepcopy(self.__provides, memo))
|
||||||
|
|
||||||
for overriding_provider in self.overridden:
|
self._copy_overridings(copied)
|
||||||
copied.override(deepcopy(overriding_provider, memo))
|
|
||||||
|
|
||||||
return copied
|
return copied
|
||||||
|
|
||||||
|
@ -330,8 +333,7 @@ cdef class Dependency(Provider):
|
||||||
|
|
||||||
copied = self.__class__(self.__instance_of)
|
copied = self.__class__(self.__instance_of)
|
||||||
|
|
||||||
for overriding_provider in self.overridden:
|
self._copy_overridings(copied)
|
||||||
copied.override(deepcopy(overriding_provider, memo))
|
|
||||||
|
|
||||||
return 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):
|
cdef class OverridingContext(object):
|
||||||
"""Provider overriding context.
|
"""Provider overriding context.
|
||||||
|
|
||||||
|
@ -517,8 +634,7 @@ cdef class Callable(Provider):
|
||||||
*deepcopy(self.args, memo),
|
*deepcopy(self.args, memo),
|
||||||
**deepcopy(self.kwargs, memo))
|
**deepcopy(self.kwargs, memo))
|
||||||
|
|
||||||
for overriding_provider in self.overridden:
|
self._copy_overridings(copied)
|
||||||
copied.override(deepcopy(overriding_provider, memo))
|
|
||||||
|
|
||||||
return copied
|
return copied
|
||||||
|
|
||||||
|
@ -755,8 +871,7 @@ cdef class Configuration(Provider):
|
||||||
copied.__value = deepcopy(self.__value, memo)
|
copied.__value = deepcopy(self.__value, memo)
|
||||||
copied.__children = deepcopy(self.__children, memo)
|
copied.__children = deepcopy(self.__children, memo)
|
||||||
|
|
||||||
for overriding_provider in self.overridden:
|
self._copy_overridings(copied)
|
||||||
copied.override(deepcopy(overriding_provider, memo))
|
|
||||||
|
|
||||||
return copied
|
return copied
|
||||||
|
|
||||||
|
@ -923,8 +1038,7 @@ cdef class Factory(Provider):
|
||||||
**deepcopy(self.kwargs, memo))
|
**deepcopy(self.kwargs, memo))
|
||||||
copied.set_attributes(**deepcopy(self.attributes, memo))
|
copied.set_attributes(**deepcopy(self.attributes, memo))
|
||||||
|
|
||||||
for overriding_provider in self.overridden:
|
self._copy_overridings(copied)
|
||||||
copied.override(deepcopy(overriding_provider, memo))
|
|
||||||
|
|
||||||
return copied
|
return copied
|
||||||
|
|
||||||
|
@ -1272,8 +1386,7 @@ cdef class BaseSingleton(Provider):
|
||||||
**deepcopy(self.kwargs, memo))
|
**deepcopy(self.kwargs, memo))
|
||||||
copied.set_attributes(**deepcopy(self.attributes, memo))
|
copied.set_attributes(**deepcopy(self.attributes, memo))
|
||||||
|
|
||||||
for overriding_provider in self.overridden:
|
self._copy_overridings(copied)
|
||||||
copied.override(deepcopy(overriding_provider, memo))
|
|
||||||
|
|
||||||
return copied
|
return copied
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
import unittest2 as unittest
|
import unittest2 as unittest
|
||||||
|
|
||||||
from dependency_injector import (
|
from dependency_injector import (
|
||||||
|
containers,
|
||||||
providers,
|
providers,
|
||||||
errors,
|
errors,
|
||||||
)
|
)
|
||||||
|
@ -298,3 +299,52 @@ class ExternalDependencyTests(unittest.TestCase):
|
||||||
|
|
||||||
def test_is_instance(self):
|
def test_is_instance(self):
|
||||||
self.assertIsInstance(self.provider, providers.Dependency)
|
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)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user