mirror of
https://github.com/ets-labs/python-dependency-injector.git
synced 2024-11-22 01:26:51 +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
|
||||
services_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
|
||||
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
|
||||
|
|
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_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
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue
Block a user