Add latest containers module updates + movie_lister refactoring

This commit is contained in:
Roman Mogilatov 2016-05-27 19:12:49 +03:00
parent d6f48b6e1d
commit 8fdb190118
6 changed files with 168 additions and 62 deletions

View File

@ -2,7 +2,10 @@
import six import six
from dependency_injector import utils from dependency_injector import (
utils,
errors,
)
class DeclarativeContainerMetaClass(type): class DeclarativeContainerMetaClass(type):
@ -21,6 +24,7 @@ class DeclarativeContainerMetaClass(type):
attributes['cls_providers'] = dict(cls_providers) attributes['cls_providers'] = dict(cls_providers)
attributes['inherited_providers'] = dict(inherited_providers) attributes['inherited_providers'] = dict(inherited_providers)
attributes['providers'] = dict(cls_providers + inherited_providers)
return type.__new__(mcs, class_name, bases, attributes) return type.__new__(mcs, class_name, bases, attributes)
@ -32,62 +36,69 @@ class DeclarativeContainerMetaClass(type):
""" """
if utils.is_provider(value): if utils.is_provider(value):
cls.providers[name] = value cls.providers[name] = value
cls.cls_providers[name] = value
super(DeclarativeContainerMetaClass, cls).__setattr__(name, value) super(DeclarativeContainerMetaClass, cls).__setattr__(name, value)
def __delattr__(cls, name):
"""Delete class attribute.
class Container(object): If value of attribute is provider, it will be deleted from providers
"""Inversion of control container."""
__IS_CATALOG__ = True
def __init__(self):
"""Initializer."""
self.providers = dict()
def bind_providers(self, **providers):
"""Bind providers to the container."""
for name, provider in six.iteritems(providers):
setattr(self, name, utils.ensure_is_provider(provider))
return self
def __setattr__(self, name, value):
"""Set instance attribute.
If value of attribute is provider, it will be added into providers
dictionary. dictionary.
""" """
if utils.is_provider(value): if name in cls.providers and name in cls.cls_providers:
self.providers[name] = value del cls.providers[name]
super(Container, self).__setattr__(name, value) del cls.cls_providers[name]
super(DeclarativeContainerMetaClass, cls).__delattr__(name)
@six.add_metaclass(DeclarativeContainerMetaClass) @six.add_metaclass(DeclarativeContainerMetaClass)
class DeclarativeContainer(object): class DeclarativeContainer(object):
"""Declarative inversion of control container.""" """Declarative inversion of control container."""
__IS_CATALOG__ = True
providers = dict()
cls_providers = dict() cls_providers = dict()
inherited_providers = dict() inherited_providers = dict()
def __init__(self): overridden_by = tuple()
"""Initializer."""
self.providers = dict() @classmethod
self.providers.update(self.__class__.inherited_providers) def override(cls, overriding):
self.providers.update(self.__class__.cls_providers) """Override current container by overriding container.
super(DeclarativeContainer, self).__init__()
:param overriding: Overriding container.
:type overriding: :py:class:`DeclarativeContainer`
:raise: :py:exc:`dependency_injector.errors.Error` if trying to
override container by itself or its subclasses
:rtype: None
"""
if issubclass(cls, overriding):
raise errors.Error('Catalog {0} could not be overridden '
'with itself or its subclasses'.format(cls))
cls.overridden_by += (overriding,)
for name, provider in six.iteritems(overriding.cls_providers):
try:
getattr(cls, name).override(provider)
except AttributeError:
pass
def override(declarative_container): def override(container):
""":py:class:`DeclarativeContainer` overriding decorator. """:py:class:`DeclarativeContainer` overriding decorator.
:param declarative_container: Container that should be overridden by :param catalog: Container that should be overridden by decorated container.
decorated container. :type catalog: :py:class:`DeclarativeContainer`
:type declarative_container: :py:class:`DeclarativeContainer`
:return: Declarative container's overriding decorator. :return: Declarative container's overriding decorator.
:rtype: callable(:py:class:`DeclarativeContainer`) :rtype: callable(:py:class:`DeclarativeContainer`)
""" """
def decorator(overriding_container): def decorator(overriding_container):
"""Overriding decorator.""" """Overriding decorator."""
declarative_container.override(overriding_container) container.override(overriding_container)
return overriding_container return overriding_container
return decorator return decorator

View File

@ -9,7 +9,7 @@ This mini application uses ``movies`` library, that is configured to work with
csv file movies database. csv file movies database.
""" """
from dependency_injector import catalogs from dependency_injector import containers
from dependency_injector import providers from dependency_injector import providers
from dependency_injector import injections from dependency_injector import injections
@ -19,12 +19,12 @@ from movies import finders
from settings import MOVIES_CSV_PATH from settings import MOVIES_CSV_PATH
@catalogs.override(MoviesModule) @containers.override(MoviesModule)
class MyMoviesModule(catalogs.DeclarativeCatalog): class MyMoviesModule(containers.DeclarativeContainer):
"""Customized catalog of movies module component providers.""" """IoC container for overriding movies module component providers."""
movie_finder = providers.Factory(finders.CsvMovieFinder, movie_finder = providers.Factory(finders.CsvMovieFinder,
*MoviesModule.movie_finder.injections, movie_model=MoviesModule.movie_model,
csv_file=MOVIES_CSV_PATH, csv_file=MOVIES_CSV_PATH,
delimeter=',') delimeter=',')

View File

@ -11,7 +11,7 @@ sqlite movies database.
import sqlite3 import sqlite3
from dependency_injector import catalogs from dependency_injector import containers
from dependency_injector import providers from dependency_injector import providers
from dependency_injector import injections from dependency_injector import injections
@ -21,18 +21,18 @@ from movies import finders
from settings import MOVIES_DB_PATH from settings import MOVIES_DB_PATH
class ApplicationModule(catalogs.DeclarativeCatalog): class ApplicationModule(containers.DeclarativeContainer):
"""Catalog of application component providers.""" """IoC container of application component providers."""
database = providers.Singleton(sqlite3.connect, MOVIES_DB_PATH) database = providers.Singleton(sqlite3.connect, MOVIES_DB_PATH)
@catalogs.override(MoviesModule) @containers.override(MoviesModule)
class MyMoviesModule(catalogs.DeclarativeCatalog): class MyMoviesModule(containers.DeclarativeContainer):
"""Customized catalog of movies module component providers.""" """IoC container for overriding movies module component providers."""
movie_finder = providers.Factory(finders.SqliteMovieFinder, movie_finder = providers.Factory(finders.SqliteMovieFinder,
*MoviesModule.movie_finder.injections, movie_model=MoviesModule.movie_model,
database=ApplicationModule.database) database=ApplicationModule.database)

View File

@ -11,7 +11,7 @@ sqlite movies database and csv file movies database.
import sqlite3 import sqlite3
from dependency_injector import catalogs from dependency_injector import containers
from dependency_injector import providers from dependency_injector import providers
from dependency_injector import injections from dependency_injector import injections
@ -22,27 +22,27 @@ from settings import MOVIES_CSV_PATH
from settings import MOVIES_DB_PATH from settings import MOVIES_DB_PATH
class ApplicationModule(catalogs.DeclarativeCatalog): class ApplicationModule(containers.DeclarativeContainer):
"""Catalog of application component providers.""" """IoC container of application component providers."""
database = providers.Singleton(sqlite3.connect, MOVIES_DB_PATH) database = providers.Singleton(sqlite3.connect, MOVIES_DB_PATH)
@catalogs.copy(MoviesModule) @containers.copy(MoviesModule)
class DbMoviesModule(MoviesModule): class DbMoviesModule(MoviesModule):
"""Customized catalog of movies module component providers.""" """IoC container for overriding movies module component providers."""
movie_finder = providers.Factory(finders.SqliteMovieFinder, movie_finder = providers.Factory(finders.SqliteMovieFinder,
*MoviesModule.movie_finder.injections, movie_model=MoviesModule.movie_model,
database=ApplicationModule.database) database=ApplicationModule.database)
@catalogs.copy(MoviesModule) @containers.copy(MoviesModule)
class CsvMoviesModule(MoviesModule): class CsvMoviesModule(MoviesModule):
"""Customized catalog of movies module component providers.""" """IoC container for overriding movies module component providers."""
movie_finder = providers.Factory(finders.CsvMovieFinder, movie_finder = providers.Factory(finders.CsvMovieFinder,
*MoviesModule.movie_finder.injections, movie_model=MoviesModule.movie_model,
csv_file=MOVIES_CSV_PATH, csv_file=MOVIES_CSV_PATH,
delimeter=',') delimeter=',')

View File

@ -1,9 +1,9 @@
"""Movies package. """Movies package.
Top-level package of movies library. This package contains catalog of movies Top-level package of movies library. This package contains IoC container of
module component providers - ``MoviesModule``. It is recommended to use movies movies module component providers - ``MoviesModule``. It is recommended to use
library functionality by fetching required instances from ``MoviesModule`` movies library functionality by fetching required instances from
providers. ``MoviesModule`` providers.
``MoviesModule.movie_finder`` is a factory that provides abstract component ``MoviesModule.movie_finder`` is a factory that provides abstract component
``finders.MovieFinder``. This provider should be overridden by provider of ``finders.MovieFinder``. This provider should be overridden by provider of
@ -12,7 +12,7 @@ concrete finder implementation in terms of library configuration.
Each of ``MoviesModule`` providers could be overridden. Each of ``MoviesModule`` providers could be overridden.
""" """
from dependency_injector import catalogs from dependency_injector import containers
from dependency_injector import providers from dependency_injector import providers
from . import finders from . import finders
@ -20,8 +20,8 @@ from . import listers
from . import models from . import models
class MoviesModule(catalogs.DeclarativeCatalog): class MoviesModule(containers.DeclarativeContainer):
"""Catalog of movies module component providers.""" """IoC container of movies module component providers."""
movie_model = providers.DelegatedFactory(models.Movie) movie_model = providers.DelegatedFactory(models.Movie)

95
tests/test_containers.py Normal file
View File

@ -0,0 +1,95 @@
"""Dependency injector container unit tests."""
import unittest2 as unittest
from dependency_injector import (
containers,
providers,
)
class ContainerA(containers.DeclarativeContainer):
"""Declarative IoC container A."""
p11 = providers.Provider()
p12 = providers.Provider()
class ContainerB(ContainerA):
"""Declarative IoC container B.
Extends container A.
"""
p21 = providers.Provider()
p22 = providers.Provider()
class DeclarativeContainerTests(unittest.TestCase):
"""Declarative container tests."""
def test_providers_attribute_with(self):
"""Test providers attribute."""
self.assertEqual(ContainerA.providers, dict(p11=ContainerA.p11,
p12=ContainerA.p12))
self.assertEqual(ContainerB.providers, dict(p11=ContainerA.p11,
p12=ContainerA.p12,
p21=ContainerB.p21,
p22=ContainerB.p22))
def test_cls_providers_attribute_with(self):
"""Test cls_providers attribute."""
self.assertEqual(ContainerA.cls_providers, dict(p11=ContainerA.p11,
p12=ContainerA.p12))
self.assertEqual(ContainerB.cls_providers, dict(p21=ContainerB.p21,
p22=ContainerB.p22))
def test_inherited_providers_attribute(self):
"""Test inherited_providers attribute."""
self.assertEqual(ContainerA.inherited_providers, dict())
self.assertEqual(ContainerB.inherited_providers,
dict(p11=ContainerA.p11,
p12=ContainerA.p12))
def test_set_get_del_provider_attribute(self):
"""Test set/get/del provider attributes."""
a_p13 = providers.Provider()
b_p23 = providers.Provider()
ContainerA.p13 = a_p13
ContainerB.p23 = b_p23
self.assertEqual(ContainerA.providers, dict(p11=ContainerA.p11,
p12=ContainerA.p12,
p13=a_p13))
self.assertEqual(ContainerB.providers, dict(p11=ContainerA.p11,
p12=ContainerA.p12,
p21=ContainerB.p21,
p22=ContainerB.p22,
p23=b_p23))
self.assertEqual(ContainerA.cls_providers, dict(p11=ContainerA.p11,
p12=ContainerA.p12,
p13=a_p13))
self.assertEqual(ContainerB.cls_providers, dict(p21=ContainerB.p21,
p22=ContainerB.p22,
p23=b_p23))
del ContainerA.p13
del ContainerB.p23
self.assertEqual(ContainerA.providers, dict(p11=ContainerA.p11,
p12=ContainerA.p12))
self.assertEqual(ContainerB.providers, dict(p11=ContainerA.p11,
p12=ContainerA.p12,
p21=ContainerB.p21,
p22=ContainerB.p22))
self.assertEqual(ContainerA.cls_providers, dict(p11=ContainerA.p11,
p12=ContainerA.p12))
self.assertEqual(ContainerB.cls_providers, dict(p21=ContainerB.p21,
p22=ContainerB.p22))
if __name__ == '__main__':
unittest.main()