Merge branch 'release/3.9.0' into master

This commit is contained in:
Roman Mogylatov 2017-12-25 17:44:49 +02:00
commit 9a634f86ab
27 changed files with 5973 additions and 3990 deletions

View File

@ -0,0 +1,77 @@
Bundles mini application example
--------------------------------
.. currentmodule:: dependency_injector.containers
"Bundles" is an example mini application that is intented to demonstrate the
power of dependency injection for creation of re-usable application components
("bundles") with 100% transparency of their dependencies.
Example application
~~~~~~~~~~~~~~~~~~~
"Bundles" mini application has next structure:
.. code-block:: bash
bundles/
bundles/ <-- Bundles package
photos/ <-- Photos bundle
__init__.py <-- Photos bundle dependency injection container
entities.py
repositories.py
users/ <-- Users bundle
__init__.py <-- Users bundle dependency injection container
entities.py
repositories.py
run.py <-- Entrypoint
IoC containers
~~~~~~~~~~~~~~
Next two listings show :py:class:`DeclarativeContainer`'s for "users" and
"photos" bundles.
Listing of ``bundeles/users/__init__.py``:
.. literalinclude:: ../../examples/miniapps/bundles/bundles/users/__init__.py
:language: python
:linenos:
.. note::
- ``Users`` container has dependency on database.
Listing of ``bundeles/photos/__init__.py``:
.. literalinclude:: ../../examples/miniapps/bundles/bundles/photos/__init__.py
:language: python
:linenos:
.. note::
- ``Photos`` container has dependencies on database and file storage.
Run application
~~~~~~~~~~~~~~~
Finally, both "bundles" are initialized by providing needed dependencies.
Initialization of dependencies happens right in the runtime, not earlier.
Generally, it means, that any part of any bundle could be overridden on the
fly.
Listing of ``run.py``:
.. literalinclude:: ../../examples/miniapps/bundles/run.py
:language: python
:linenos:
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/bundles>`_
.. disqus::

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

View File

@ -7,6 +7,23 @@ that were made in every particular version.
From version 0.7.6 *Dependency Injector* framework strictly
follows `Semantic versioning`_
3.9.0
-----
- Change initialization of declarative container, so it accepts overriding
providers as keyword arguments -
``DeclarativeContainer(**overriding_providers)``.
- Add method to dynamic catalog for setting groups of providers -
``DynamicContainer.set_providers(**providers)``.
- Add method to dynamic catalog for overriding groups of providers -
``DynamicContainer.set_providers(**overriding_providers)``.
- Rename ``ExternalDependency`` provider to ``Dependency``.
- Add default value for ``instance_of`` argument of ``Dependency`` provider -
``Dependency(instance_of=object)``.
- Fix bug when copying ``Configuration`` provider.
- Regenerate C sources using Cython 0.27.3.
- Add "bundles" example miniapp.
3.8.2
-----
- Fix padding problem in code samples in docs (part 2).

View File

@ -1,9 +1,9 @@
External dependency providers
-----------------------------
Dependency providers
--------------------
.. currentmodule:: dependency_injector.providers
:py:class:`ExternalDependency` provider can be useful for development of
:py:class:`Dependency` provider can be useful for development of
self-sufficient libraries / modules / applications that have required external
dependencies.
@ -35,11 +35,10 @@ Example:
Instead of this, ``UsersService`` has external dependency, that has to
be satisfied by cleint's code, out of library's scope.
.. image:: /images/providers/external_dependency.png
.. image:: /images/providers/dependency.png
.. literalinclude:: ../../examples/providers/external_dependency.py
.. literalinclude:: ../../examples/providers/dependency.py
:language: python
:linenos:
.. disqus::

View File

@ -21,6 +21,6 @@ Providers package API docs - :py:mod:`dependency_injector.providers`
singleton
callable
object
external_dependency
dependency
overriding
custom

View File

@ -0,0 +1,8 @@
Dependency Injector Bundles example
===================================
Instructions for running
.. code-block:: bash
python run.py

View File

@ -0,0 +1 @@
"""Bundles package."""

View File

@ -0,0 +1,20 @@
"""Photos bundle."""
from dependency_injector import containers
from dependency_injector import providers
from . import entities
from . import repositories
class Photos(containers.DeclarativeContainer):
"""Photos bundle container."""
database = providers.Dependency()
file_storage = providers.Dependency()
photo = providers.Factory(entities.Photo)
photo_repository = providers.Singleton(repositories.PhotoRepository,
object_factory=photo.provider,
fs=file_storage,
db=database)

View File

@ -0,0 +1,5 @@
"""Photos bundle entities module."""
class Photo(object):
"""Photo entity."""

View File

@ -0,0 +1,11 @@
"""Photos bundle entity repositories module."""
class PhotoRepository(object):
"""Photo entity repository."""
def __init__(self, object_factory, fs, db):
"""Initializer."""
self.object_factory = object_factory
self.fs = fs
self.db = db

View File

@ -0,0 +1,18 @@
"""Users bundle."""
from dependency_injector import containers
from dependency_injector import providers
from . import entities
from . import repositories
class Users(containers.DeclarativeContainer):
"""Users bundle container."""
database = providers.Dependency()
user = providers.Factory(entities.User)
user_repository = providers.Singleton(repositories.UserRepository,
object_factory=user.provider,
db=database)

View File

@ -0,0 +1,9 @@
"""Users bundle entities module."""
class User(object):
"""User entity."""
def __init__(self, id):
"""Initializer."""
self.id = id

View File

@ -0,0 +1,14 @@
"""Users bundle entity repositories module."""
class UserRepository(object):
"""User entity repository."""
def __init__(self, object_factory, db):
"""Initializer."""
self.object_factory = object_factory
self.db = db
def get(self, id):
"""Return user entity with given identifier."""
return self.object_factory(id=id)

View File

@ -0,0 +1,41 @@
"""Run 'Bundles' example application."""
import sqlite3
import boto3
from dependency_injector import containers
from dependency_injector import providers
from bundles.users import Users
from bundles.photos import Photos
class Core(containers.DeclarativeContainer):
"""Core container."""
config = providers.Configuration('config')
sqlite = providers.Singleton(sqlite3.connect, config.database.dsn)
s3 = providers.Singleton(
boto3.client, 's3',
aws_access_key_id=config.aws.access_key_id,
aws_secret_access_key=config.aws.secret_access_key)
if __name__ == '__main__':
# Initializing containers
core = Core()
core.config.update({'database': {'dsn': ':memory:'},
'aws': {'access_key_id': 'KEY',
'secret_access_key': 'SECRET'}})
users = Users(database=core.sqlite)
photos = Photos(database=core.sqlite, file_storage=core.s3)
# Fetching few users
user_repository = users.user_repository()
user1 = user_repository.get(id=1)
user2 = user_repository.get(id=2)
# Making some checks
assert user1.id == 1
assert user2.id == 2
assert user_repository.db is core.sqlite()

View File

@ -1,4 +1,4 @@
"""`ExternalDependency` providers example."""
"""`Dependency` providers example."""
import sqlite3
import contextlib
@ -45,7 +45,7 @@ class UsersService(object):
# Database and UsersService providers:
database = providers.ExternalDependency(instance_of=sqlite3.dbapi2.Connection)
database = providers.Dependency(instance_of=sqlite3.dbapi2.Connection)
users_service_factory = providers.Factory(UsersService,
database=database)

View File

@ -1,4 +1,4 @@
cython==0.27.1
cython==0.27.3
tox
unittest2
coverage

View File

@ -1,6 +1,6 @@
"""Dependency injector top-level package."""
__version__ = '3.8.2'
__version__ = '3.9.0'
"""Version number that follows semantic versioning.
:type: str

File diff suppressed because it is too large Load Diff

View File

@ -88,6 +88,18 @@ class DynamicContainer(object):
del self.providers[name]
super(DynamicContainer, self).__delattr__(name)
def set_providers(self, **providers):
"""Set container providers.
:param providers: Dictionary of providers
:type providers:
dict[str, :py:class:`dependency_injector.providers.Provider`]
:rtype: None
"""
for name, provider in six.iteritems(providers):
setattr(self, name, provider)
def override(self, object overriding):
"""Override current container by overriding container.
@ -111,6 +123,19 @@ class DynamicContainer(object):
except AttributeError:
pass
def override_providers(self, **overriding_providers):
"""Override container providers.
:param overriding_providers: Dictionary of providers
:type overriding_providers:
dict[str, :py:class:`dependency_injector.providers.Provider`]
:rtype: None
"""
for name, overriding_provider in six.iteritems(overriding_providers):
container_provider = getattr(self, name)
container_provider.override(overriding_provider)
def reset_last_overriding(self):
"""Reset last overriding provider for each container providers.
@ -265,18 +290,16 @@ class DeclarativeContainer(object):
:type: tuple[:py:class:`DeclarativeContainer`]
"""
def __new__(cls, *args, **kwargs):
def __new__(cls, **overriding_providers):
"""Constructor.
:return: Dynamic container with copy of all providers.
:rtype: :py:class:`DynamicContainer`
"""
container = cls.instance_type(*args, **kwargs)
container = cls.instance_type()
container.provider_type = cls.provider_type
for name, provider in six.iteritems(deepcopy(cls.providers)):
setattr(container, name, provider)
container.set_providers(**deepcopy(cls.providers))
container.override_providers(**overriding_providers)
return container
@classmethod

File diff suppressed because it is too large Load Diff

View File

@ -25,10 +25,14 @@ cdef class Delegate(Object):
pass
cdef class ExternalDependency(Provider):
cdef class Dependency(Provider):
cdef type __instance_of
cdef class ExternalDependency(Dependency):
pass
cdef class OverridingContext(object):
cdef Provider __overridden
cdef Provider __overriding

View File

@ -295,8 +295,8 @@ cdef class Delegate(Object):
super(Delegate, self).__init__(ensure_is_provider(provides))
cdef class ExternalDependency(Provider):
""":py:class:`ExternalDependency` provider describes dependency interface.
cdef class Dependency(Provider):
""":py:class:`Dependency` provider describes dependency interface.
This provider is used for description of dependency interface. That might
be useful when dependency could be provided in the client's code only,
@ -305,7 +305,7 @@ cdef class ExternalDependency(Provider):
.. code-block:: python
database_provider = ExternalDependency(sqlite3.dbapi2.Connection)
database_provider = Dependency(sqlite3.dbapi2.Connection)
database_provider.override(Factory(sqlite3.connect, ':memory:'))
database = database_provider()
@ -315,12 +315,12 @@ cdef class ExternalDependency(Provider):
Class of required dependency.
:type: type
"""
"""
def __init__(self, type instance_of):
def __init__(self, type instance_of=object):
"""Initializer."""
self.__instance_of = instance_of
super(ExternalDependency, self).__init__()
super(Dependency, self).__init__()
def __deepcopy__(self, memo):
"""Create and return full copy of provider."""
@ -391,6 +391,33 @@ cdef class ExternalDependency(Provider):
return self.override(provider)
cdef class ExternalDependency(Dependency):
""":py:class:`ExternalDependency` provider describes dependency interface.
This provider is used for description of dependency interface. That might
be useful when dependency could be provided in the client's code only,
but it's interface is known. Such situations could happen when required
dependency has non-determenistic list of dependencies itself.
.. code-block:: python
database_provider = ExternalDependency(sqlite3.dbapi2.Connection)
database_provider.override(Factory(sqlite3.connect, ':memory:'))
database = database_provider()
.. deprecated:: 3.9
Use :py:class:`Dependency` instead.
.. py:attribute:: instance_of
Class of required dependency.
:type: type
"""
cdef class OverridingContext(object):
"""Provider overriding context.
@ -718,12 +745,15 @@ cdef class Configuration(Provider):
def __deepcopy__(self, memo):
"""Create and return full copy of provider."""
cdef Configuration copied
copied = memo.get(id(self))
if copied is not None:
return copied
copied = self.__class__(self.__name)
copied.update(deepcopy(self.__value))
copied.__value = deepcopy(self.__value, memo)
copied.__children = deepcopy(self.__children, memo)
for overriding_provider in self.overridden:
copied.override(deepcopy(overriding_provider, memo))

View File

@ -265,3 +265,12 @@ class DeclarativeContainerTests(unittest.TestCase):
dict(Container1=Container.Container1,
Container2=Container.Container2,
Container3=Container.Container3))
def test_init_with_overriding_providers(self):
p1 = providers.Provider()
p2 = providers.Provider()
container = ContainerA(p11=p1, p12=p2)
self.assertIs(container.p11.last_overriding, p1)
self.assertIs(container.p12.last_overriding, p2)

View File

@ -75,6 +75,16 @@ class DeclarativeContainerInstanceTests(unittest.TestCase):
self.assertIs(ContainerA.provider_type,
containers.DeclarativeContainer.provider_type)
def test_set_providers(self):
p13 = providers.Provider()
p14 = providers.Provider()
container_a = ContainerA()
container_a.set_providers(p13=p13, p14=p14)
self.assertIs(container_a.p13, p13)
self.assertIs(container_a.p14, p14)
def test_override(self):
class _Container(containers.DeclarativeContainer):
p11 = providers.Provider()
@ -108,6 +118,22 @@ class DeclarativeContainerInstanceTests(unittest.TestCase):
with self.assertRaises(errors.Error):
container.override(container)
def test_override_providers(self):
p1 = providers.Provider()
p2 = providers.Provider()
container_a = ContainerA()
container_a.override_providers(p11=p1, p12=p2)
self.assertIs(container_a.p11.last_overriding, p1)
self.assertIs(container_a.p12.last_overriding, p2)
def test_override_providers_with_unknown_provider(self):
container_a = ContainerA()
with self.assertRaises(AttributeError):
container_a.override_providers(unknown=providers.Provider())
def test_reset_last_overridding(self):
class _Container(containers.DeclarativeContainer):
p11 = providers.Provider()

View File

@ -229,13 +229,13 @@ class DelegateTests(unittest.TestCase):
hex(id(self.delegate))))
class ExternalDependencyTests(unittest.TestCase):
class DependencyTests(unittest.TestCase):
def setUp(self):
self.provider = providers.ExternalDependency(instance_of=list)
self.provider = providers.Dependency(instance_of=list)
def test_init_with_not_class(self):
self.assertRaises(TypeError, providers.ExternalDependency, object())
self.assertRaises(TypeError, providers.Dependency, object())
def test_is_provider(self):
self.assertTrue(providers.is_provider(self.provider))
@ -252,15 +252,15 @@ class ExternalDependencyTests(unittest.TestCase):
self.assertRaises(errors.Error, self.provider)
def test_deepcopy(self):
provider = providers.ExternalDependency(int)
provider = providers.Dependency(int)
provider_copy = providers.deepcopy(provider)
self.assertIsNot(provider, provider_copy)
self.assertIsInstance(provider, providers.ExternalDependency)
self.assertIsInstance(provider, providers.Dependency)
def test_deepcopy_from_memo(self):
provider = providers.ExternalDependency(int)
provider = providers.Dependency(int)
provider_copy_memo = providers.Provider()
provider_copy = providers.deepcopy(
@ -269,7 +269,7 @@ class ExternalDependencyTests(unittest.TestCase):
self.assertIs(provider_copy, provider_copy_memo)
def test_deepcopy_overridden(self):
provider = providers.ExternalDependency(int)
provider = providers.Dependency(int)
overriding_provider = providers.Provider()
provider.override(overriding_provider)
@ -278,7 +278,7 @@ class ExternalDependencyTests(unittest.TestCase):
overriding_provider_copy = provider_copy.overridden[0]
self.assertIsNot(provider, provider_copy)
self.assertIsInstance(provider, providers.ExternalDependency)
self.assertIsInstance(provider, providers.Dependency)
self.assertIsNot(overriding_provider, overriding_provider_copy)
self.assertIsInstance(overriding_provider_copy, providers.Provider)
@ -286,6 +286,15 @@ class ExternalDependencyTests(unittest.TestCase):
def test_repr(self):
self.assertEqual(repr(self.provider),
'<dependency_injector.providers.'
'ExternalDependency({0}) at {1}>'.format(
'Dependency({0}) at {1}>'.format(
repr(list),
hex(id(self.provider))))
class ExternalDependencyTests(unittest.TestCase):
def setUp(self):
self.provider = providers.ExternalDependency(instance_of=list)
def test_is_instance(self):
self.assertIsInstance(self.provider, providers.Dependency)