Skip to content

Commit

Permalink
Add latest containers module updates + movie_lister refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
rmk135 committed May 27, 2016
1 parent 3dea95f commit 2753af1
Show file tree
Hide file tree
Showing 6 changed files with 168 additions and 62 deletions.
79 changes: 45 additions & 34 deletions dependency_injector/containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

import six

from dependency_injector import utils
from dependency_injector import (
utils,
errors,
)


class DeclarativeContainerMetaClass(type):
Expand All @@ -21,6 +24,7 @@ def __new__(mcs, class_name, bases, attributes):

attributes['cls_providers'] = dict(cls_providers)
attributes['inherited_providers'] = dict(inherited_providers)
attributes['providers'] = dict(cls_providers + inherited_providers)

return type.__new__(mcs, class_name, bases, attributes)

Expand All @@ -32,62 +36,69 @@ def __setattr__(cls, name, value):
"""
if utils.is_provider(value):
cls.providers[name] = value
cls.cls_providers[name] = value
super(DeclarativeContainerMetaClass, cls).__setattr__(name, value)

def __delattr__(cls, name):
"""Delete class attribute.
class Container(object):
"""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
If value of attribute is provider, it will be deleted from providers
dictionary.
"""
if utils.is_provider(value):
self.providers[name] = value
super(Container, self).__setattr__(name, value)
if name in cls.providers and name in cls.cls_providers:
del cls.providers[name]
del cls.cls_providers[name]
super(DeclarativeContainerMetaClass, cls).__delattr__(name)


@six.add_metaclass(DeclarativeContainerMetaClass)
class DeclarativeContainer(object):
"""Declarative inversion of control container."""

__IS_CATALOG__ = True

providers = dict()
cls_providers = dict()
inherited_providers = dict()

def __init__(self):
"""Initializer."""
self.providers = dict()
self.providers.update(self.__class__.inherited_providers)
self.providers.update(self.__class__.cls_providers)
super(DeclarativeContainer, self).__init__()
overridden_by = tuple()

@classmethod
def override(cls, overriding):
"""Override current container by overriding container.
: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.
:param declarative_container: Container that should be overridden by
decorated container.
:type declarative_container: :py:class:`DeclarativeContainer`
:param catalog: Container that should be overridden by decorated container.
:type catalog: :py:class:`DeclarativeContainer`
:return: Declarative container's overriding decorator.
:rtype: callable(:py:class:`DeclarativeContainer`)
"""
def decorator(overriding_container):
"""Overriding decorator."""
declarative_container.override(overriding_container)
container.override(overriding_container)
return overriding_container
return decorator
10 changes: 5 additions & 5 deletions examples/miniapps/movie_lister/app_csv.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
csv file movies database.
"""

from dependency_injector import catalogs
from dependency_injector import containers
from dependency_injector import providers
from dependency_injector import injections

Expand All @@ -19,12 +19,12 @@
from settings import MOVIES_CSV_PATH


@catalogs.override(MoviesModule)
class MyMoviesModule(catalogs.DeclarativeCatalog):
"""Customized catalog of movies module component providers."""
@containers.override(MoviesModule)
class MyMoviesModule(containers.DeclarativeContainer):
"""IoC container for overriding movies module component providers."""

movie_finder = providers.Factory(finders.CsvMovieFinder,
*MoviesModule.movie_finder.injections,
movie_model=MoviesModule.movie_model,
csv_file=MOVIES_CSV_PATH,
delimeter=',')

Expand Down
14 changes: 7 additions & 7 deletions examples/miniapps/movie_lister/app_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

import sqlite3

from dependency_injector import catalogs
from dependency_injector import containers
from dependency_injector import providers
from dependency_injector import injections

Expand All @@ -21,18 +21,18 @@
from settings import MOVIES_DB_PATH


class ApplicationModule(catalogs.DeclarativeCatalog):
"""Catalog of application component providers."""
class ApplicationModule(containers.DeclarativeContainer):
"""IoC container of application component providers."""

database = providers.Singleton(sqlite3.connect, MOVIES_DB_PATH)


@catalogs.override(MoviesModule)
class MyMoviesModule(catalogs.DeclarativeCatalog):
"""Customized catalog of movies module component providers."""
@containers.override(MoviesModule)
class MyMoviesModule(containers.DeclarativeContainer):
"""IoC container for overriding movies module component providers."""

movie_finder = providers.Factory(finders.SqliteMovieFinder,
*MoviesModule.movie_finder.injections,
movie_model=MoviesModule.movie_model,
database=ApplicationModule.database)


Expand Down
18 changes: 9 additions & 9 deletions examples/miniapps/movie_lister/app_db_csv.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

import sqlite3

from dependency_injector import catalogs
from dependency_injector import containers
from dependency_injector import providers
from dependency_injector import injections

Expand All @@ -22,27 +22,27 @@
from settings import MOVIES_DB_PATH


class ApplicationModule(catalogs.DeclarativeCatalog):
"""Catalog of application component providers."""
class ApplicationModule(containers.DeclarativeContainer):
"""IoC container of application component providers."""

database = providers.Singleton(sqlite3.connect, MOVIES_DB_PATH)


@catalogs.copy(MoviesModule)
@containers.copy(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,
*MoviesModule.movie_finder.injections,
movie_model=MoviesModule.movie_model,
database=ApplicationModule.database)


@catalogs.copy(MoviesModule)
@containers.copy(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,
*MoviesModule.movie_finder.injections,
movie_model=MoviesModule.movie_model,
csv_file=MOVIES_CSV_PATH,
delimeter=',')

Expand Down
14 changes: 7 additions & 7 deletions examples/miniapps/movie_lister/movies/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"""Movies package.
Top-level package of movies library. This package contains catalog of movies
module component providers - ``MoviesModule``. It is recommended to use movies
library functionality by fetching required instances from ``MoviesModule``
providers.
Top-level package of movies library. This package contains IoC container of
movies module component providers - ``MoviesModule``. It is recommended to use
movies library functionality by fetching required instances from
``MoviesModule`` providers.
``MoviesModule.movie_finder`` is a factory that provides abstract component
``finders.MovieFinder``. This provider should be overridden by provider of
Expand All @@ -12,16 +12,16 @@
Each of ``MoviesModule`` providers could be overridden.
"""

from dependency_injector import catalogs
from dependency_injector import containers
from dependency_injector import providers

from . import finders
from . import listers
from . import models


class MoviesModule(catalogs.DeclarativeCatalog):
"""Catalog of movies module component providers."""
class MoviesModule(containers.DeclarativeContainer):
"""IoC container of movies module component providers."""

movie_model = providers.DelegatedFactory(models.Movie)

Expand Down
95 changes: 95 additions & 0 deletions tests/test_containers.py
Original file line number Diff line number Diff line change
@@ -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()

0 comments on commit 2753af1

Please sign in to comment.