From 97d1dab00af4db4c6c29380490a5e90cbddf1dc3 Mon Sep 17 00:00:00 2001 From: Roman Mogilatov Date: Fri, 27 May 2016 14:37:15 +0300 Subject: [PATCH 01/16] Add containers module --- dependency_injector/containers.py | 93 +++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 dependency_injector/containers.py diff --git a/dependency_injector/containers.py b/dependency_injector/containers.py new file mode 100644 index 00000000..858d8e1a --- /dev/null +++ b/dependency_injector/containers.py @@ -0,0 +1,93 @@ +"""IoC containers module.""" + +import six + +from dependency_injector import utils + + +class DeclarativeContainerMetaClass(type): + """Declarative inversion of control container meta class.""" + + def __new__(mcs, class_name, bases, attributes): + """Declarative container class factory.""" + cls_providers = tuple((name, provider) + for name, provider in six.iteritems(attributes) + if utils.is_provider(provider)) + + inherited_providers = tuple((name, provider) + for base in bases if utils.is_catalog(base) + for name, provider in six.iteritems( + base.cls_providers)) + + attributes['cls_providers'] = dict(cls_providers) + attributes['inherited_providers'] = dict(inherited_providers) + + return type.__new__(mcs, class_name, bases, attributes) + + def __setattr__(cls, name, value): + """Set class attribute. + + If value of attribute is provider, it will be added into providers + dictionary. + """ + if utils.is_provider(value): + cls.providers[name] = value + super(DeclarativeContainerMetaClass, cls).__setattr__(name, value) + + +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 + dictionary. + """ + if utils.is_provider(value): + self.providers[name] = value + super(Container, self).__setattr__(name, value) + + +@six.add_metaclass(DeclarativeContainerMetaClass) +class DeclarativeContainer(object): + """Declarative inversion of control container.""" + + 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__() + + +def override(declarative_container): + """:py:class:`DeclarativeContainer` overriding decorator. + + :param declarative_container: Container that should be overridden by + decorated container. + :type declarative_container: :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) + return overriding_container + return decorator From 7206b4dbb89ab38f09e478ea35d007749824638b Mon Sep 17 00:00:00 2001 From: Roman Mogilatov Date: Fri, 27 May 2016 14:38:42 +0300 Subject: [PATCH 02/16] Update services example with new containers --- .../miniapps/services/{catalogs.py => containers.py} | 10 +++++----- ...logs_alt_syntax_1.py => containers_alt_syntax_1.py} | 10 +++++----- ...logs_alt_syntax_2.py => containers_alt_syntax_2.py} | 10 +++++----- examples/miniapps/services/main.py | 2 +- 4 files changed, 16 insertions(+), 16 deletions(-) rename examples/miniapps/services/{catalogs.py => containers.py} (77%) rename examples/miniapps/services/{catalogs_alt_syntax_1.py => containers_alt_syntax_1.py} (77%) rename examples/miniapps/services/{catalogs_alt_syntax_2.py => containers_alt_syntax_2.py} (77%) diff --git a/examples/miniapps/services/catalogs.py b/examples/miniapps/services/containers.py similarity index 77% rename from examples/miniapps/services/catalogs.py rename to examples/miniapps/services/containers.py index 37a5bdf7..31391ee1 100644 --- a/examples/miniapps/services/catalogs.py +++ b/examples/miniapps/services/containers.py @@ -4,12 +4,12 @@ import sqlite3 import boto.s3.connection import example.services -from dependency_injector import catalogs +from dependency_injector import containers from dependency_injector import providers -class Platform(catalogs.DeclarativeCatalog): - """Catalog of platform service providers.""" +class Platform(containers.DeclarativeContainer): + """Container of platform service providers.""" database = providers.Singleton(sqlite3.connect, ':memory:') @@ -18,8 +18,8 @@ class Platform(catalogs.DeclarativeCatalog): aws_secret_access_key='SECRET') -class Services(catalogs.DeclarativeCatalog): - """Catalog of business service providers.""" +class Services(containers.DeclarativeContainer): + """Container of business service providers.""" users = providers.Factory(example.services.Users, db=Platform.database) diff --git a/examples/miniapps/services/catalogs_alt_syntax_1.py b/examples/miniapps/services/containers_alt_syntax_1.py similarity index 77% rename from examples/miniapps/services/catalogs_alt_syntax_1.py rename to examples/miniapps/services/containers_alt_syntax_1.py index 7f2c615f..441a9785 100644 --- a/examples/miniapps/services/catalogs_alt_syntax_1.py +++ b/examples/miniapps/services/containers_alt_syntax_1.py @@ -7,12 +7,12 @@ import sqlite3 import boto.s3.connection import example.services -from dependency_injector import catalogs +from dependency_injector import containers from dependency_injector import providers -class Platform(catalogs.DeclarativeCatalog): - """Catalog of platform service providers.""" +class Platform(containers.DeclarativeContainer): + """Container of platform service providers.""" database = providers.Singleton(sqlite3.connect) \ .add_args(':memory:') @@ -22,8 +22,8 @@ class Platform(catalogs.DeclarativeCatalog): aws_secret_access_key='SECRET') -class Services(catalogs.DeclarativeCatalog): - """Catalog of business service providers.""" +class Services(containers.DeclarativeContainer): + """Container of business service providers.""" users = providers.Factory(example.services.Users) \ .add_kwargs(db=Platform.database) diff --git a/examples/miniapps/services/catalogs_alt_syntax_2.py b/examples/miniapps/services/containers_alt_syntax_2.py similarity index 77% rename from examples/miniapps/services/catalogs_alt_syntax_2.py rename to examples/miniapps/services/containers_alt_syntax_2.py index 0f3c245d..2e2d783b 100644 --- a/examples/miniapps/services/catalogs_alt_syntax_2.py +++ b/examples/miniapps/services/containers_alt_syntax_2.py @@ -7,12 +7,12 @@ import sqlite3 import boto.s3.connection import example.services -from dependency_injector import catalogs +from dependency_injector import containers from dependency_injector import providers -class Platform(catalogs.DeclarativeCatalog): - """Catalog of platform service providers.""" +class Platform(containers.DeclarativeContainer): + """Container of platform service providers.""" database = providers.Singleton(sqlite3.connect) database.add_args(':memory:') @@ -22,8 +22,8 @@ class Platform(catalogs.DeclarativeCatalog): aws_secret_access_key='SECRET') -class Services(catalogs.DeclarativeCatalog): - """Catalog of business service providers.""" +class Services(containers.DeclarativeContainer): + """Container of business service providers.""" users = providers.Factory(example.services.Users) users.add_kwargs(db=Platform.database) diff --git a/examples/miniapps/services/main.py b/examples/miniapps/services/main.py index 9398c8b3..b6c5c864 100644 --- a/examples/miniapps/services/main.py +++ b/examples/miniapps/services/main.py @@ -2,7 +2,7 @@ from dependency_injector.injections import inject -from catalogs import Services +from containers import Services @inject(users_service=Services.users) From 05c2c864a731678b3267a2553cec78b849bdff66 Mon Sep 17 00:00:00 2001 From: Roman Mogilatov Date: Fri, 27 May 2016 14:39:25 +0300 Subject: [PATCH 03/16] Update README with new containers --- README.rst | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/README.rst b/README.rst index 7e82353c..f30d4095 100644 --- a/README.rst +++ b/README.rst @@ -52,7 +52,7 @@ Installation Example ------- -Brief example below demonstrates usage of *Dependency Injector* catalogs and +Brief example below demonstrates usage of *Dependency Injector* containers and providers for definition of several IoC containers for some microservice system that consists from several business and platform services: @@ -64,12 +64,12 @@ system that consists from several business and platform services: import boto.s3.connection import example.services - from dependency_injector import catalogs + from dependency_injector import containers from dependency_injector import providers - class Platform(catalogs.DeclarativeCatalog): - """Catalog of platform service providers.""" + class Platform(containers.DeclarativeContainer): + """Container of platform service providers.""" database = providers.Singleton(sqlite3.connect, ':memory:') @@ -78,8 +78,8 @@ system that consists from several business and platform services: aws_secret_access_key='SECRET') - class Services(catalogs.DeclarativeCatalog): - """Catalog of business service providers.""" + class Services(containers.DeclarativeContainer): + """Container of business service providers.""" users = providers.Factory(example.services.Users, db=Platform.database) @@ -101,7 +101,7 @@ defined above: from dependency_injector.injections import inject - from catalogs import Services + from containers import Services @inject(users_service=Services.users) @@ -127,8 +127,8 @@ IoC containers from previous example could look like these: .. code-block:: python - class Platform(catalogs.DeclarativeCatalog): - """Catalog of platform service providers.""" + class Platform(containers.DeclarativeContainer): + """Container of platform service providers.""" database = providers.Singleton(sqlite3.connect) \ .add_args(':memory:') @@ -138,8 +138,8 @@ IoC containers from previous example could look like these: aws_secret_access_key='SECRET') - class Services(catalogs.DeclarativeCatalog): - """Catalog of business service providers.""" + class Services(containers.DeclarativeContainer): + """Container of business service providers.""" users = providers.Factory(example.services.Users) \ .add_kwargs(db=Platform.database) @@ -156,8 +156,8 @@ or like this these: .. code-block:: python - class Platform(catalogs.DeclarativeCatalog): - """Catalog of platform service providers.""" + class Platform(containers.DeclarativeContainer): + """Container of platform service providers.""" database = providers.Singleton(sqlite3.connect) database.add_args(':memory:') @@ -167,8 +167,8 @@ or like this these: aws_secret_access_key='SECRET') - class Services(catalogs.DeclarativeCatalog): - """Catalog of business service providers.""" + class Services(containers.DeclarativeContainer): + """Container of business service providers.""" users = providers.Factory(example.services.Users) users.add_kwargs(db=Platform.database) From e669270bfbe65830d167874b4d1850079d37c9fb Mon Sep 17 00:00:00 2001 From: Roman Mogilatov Date: Fri, 27 May 2016 14:57:37 +0300 Subject: [PATCH 04/16] Update doc blocks for IoC containers in services example --- examples/miniapps/services/containers.py | 4 ++-- examples/miniapps/services/containers_alt_syntax_1.py | 4 ++-- examples/miniapps/services/containers_alt_syntax_2.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/miniapps/services/containers.py b/examples/miniapps/services/containers.py index 31391ee1..3b291d78 100644 --- a/examples/miniapps/services/containers.py +++ b/examples/miniapps/services/containers.py @@ -9,7 +9,7 @@ from dependency_injector import providers class Platform(containers.DeclarativeContainer): - """Container of platform service providers.""" + """IoC container of platform service providers.""" database = providers.Singleton(sqlite3.connect, ':memory:') @@ -19,7 +19,7 @@ class Platform(containers.DeclarativeContainer): class Services(containers.DeclarativeContainer): - """Container of business service providers.""" + """IoC container of business service providers.""" users = providers.Factory(example.services.Users, db=Platform.database) diff --git a/examples/miniapps/services/containers_alt_syntax_1.py b/examples/miniapps/services/containers_alt_syntax_1.py index 441a9785..2b925020 100644 --- a/examples/miniapps/services/containers_alt_syntax_1.py +++ b/examples/miniapps/services/containers_alt_syntax_1.py @@ -12,7 +12,7 @@ from dependency_injector import providers class Platform(containers.DeclarativeContainer): - """Container of platform service providers.""" + """IoC container of platform service providers.""" database = providers.Singleton(sqlite3.connect) \ .add_args(':memory:') @@ -23,7 +23,7 @@ class Platform(containers.DeclarativeContainer): class Services(containers.DeclarativeContainer): - """Container of business service providers.""" + """IoC container of business service providers.""" users = providers.Factory(example.services.Users) \ .add_kwargs(db=Platform.database) diff --git a/examples/miniapps/services/containers_alt_syntax_2.py b/examples/miniapps/services/containers_alt_syntax_2.py index 2e2d783b..9763ac26 100644 --- a/examples/miniapps/services/containers_alt_syntax_2.py +++ b/examples/miniapps/services/containers_alt_syntax_2.py @@ -12,7 +12,7 @@ from dependency_injector import providers class Platform(containers.DeclarativeContainer): - """Container of platform service providers.""" + """IoC container of platform service providers.""" database = providers.Singleton(sqlite3.connect) database.add_args(':memory:') @@ -23,7 +23,7 @@ class Platform(containers.DeclarativeContainer): class Services(containers.DeclarativeContainer): - """Container of business service providers.""" + """IoC container of business service providers.""" users = providers.Factory(example.services.Users) users.add_kwargs(db=Platform.database) From d6f48b6e1d524dafe954be9bdc1c751099ded42f Mon Sep 17 00:00:00 2001 From: Roman Mogilatov Date: Fri, 27 May 2016 14:59:03 +0300 Subject: [PATCH 05/16] Update doc blocks for examples in README --- README.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index f30d4095..2e227bc2 100644 --- a/README.rst +++ b/README.rst @@ -69,7 +69,7 @@ system that consists from several business and platform services: class Platform(containers.DeclarativeContainer): - """Container of platform service providers.""" + """IoC container of platform service providers.""" database = providers.Singleton(sqlite3.connect, ':memory:') @@ -79,7 +79,7 @@ system that consists from several business and platform services: class Services(containers.DeclarativeContainer): - """Container of business service providers.""" + """IoC container of business service providers.""" users = providers.Factory(example.services.Users, db=Platform.database) @@ -128,7 +128,7 @@ IoC containers from previous example could look like these: .. code-block:: python class Platform(containers.DeclarativeContainer): - """Container of platform service providers.""" + """IoC container of platform service providers.""" database = providers.Singleton(sqlite3.connect) \ .add_args(':memory:') @@ -139,7 +139,7 @@ IoC containers from previous example could look like these: class Services(containers.DeclarativeContainer): - """Container of business service providers.""" + """IoC container of business service providers.""" users = providers.Factory(example.services.Users) \ .add_kwargs(db=Platform.database) @@ -157,7 +157,7 @@ or like this these: .. code-block:: python class Platform(containers.DeclarativeContainer): - """Container of platform service providers.""" + """IoC container of platform service providers.""" database = providers.Singleton(sqlite3.connect) database.add_args(':memory:') @@ -168,7 +168,7 @@ or like this these: class Services(containers.DeclarativeContainer): - """Container of business service providers.""" + """IoC container of business service providers.""" users = providers.Factory(example.services.Users) users.add_kwargs(db=Platform.database) From 8fdb190118211f9cd5e2c7cbcd2f776c147270d8 Mon Sep 17 00:00:00 2001 From: Roman Mogilatov Date: Fri, 27 May 2016 19:12:49 +0300 Subject: [PATCH 06/16] Add latest containers module updates + movie_lister refactoring --- dependency_injector/containers.py | 79 ++++++++------- examples/miniapps/movie_lister/app_csv.py | 10 +- examples/miniapps/movie_lister/app_db.py | 14 +-- examples/miniapps/movie_lister/app_db_csv.py | 18 ++-- .../miniapps/movie_lister/movies/__init__.py | 14 +-- tests/test_containers.py | 95 +++++++++++++++++++ 6 files changed, 168 insertions(+), 62 deletions(-) create mode 100644 tests/test_containers.py diff --git a/dependency_injector/containers.py b/dependency_injector/containers.py index 858d8e1a..6c6d690d 100644 --- a/dependency_injector/containers.py +++ b/dependency_injector/containers.py @@ -2,7 +2,10 @@ import six -from dependency_injector import utils +from dependency_injector import ( + utils, + errors, +) class DeclarativeContainerMetaClass(type): @@ -21,6 +24,7 @@ class DeclarativeContainerMetaClass(type): 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) @@ -32,62 +36,69 @@ class DeclarativeContainerMetaClass(type): """ 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 diff --git a/examples/miniapps/movie_lister/app_csv.py b/examples/miniapps/movie_lister/app_csv.py index f56dec68..64a3866f 100644 --- a/examples/miniapps/movie_lister/app_csv.py +++ b/examples/miniapps/movie_lister/app_csv.py @@ -9,7 +9,7 @@ This mini application uses ``movies`` library, that is configured to work with csv file movies database. """ -from dependency_injector import catalogs +from dependency_injector import containers from dependency_injector import providers from dependency_injector import injections @@ -19,12 +19,12 @@ from movies import finders 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=',') diff --git a/examples/miniapps/movie_lister/app_db.py b/examples/miniapps/movie_lister/app_db.py index f9e614fc..5e62676b 100644 --- a/examples/miniapps/movie_lister/app_db.py +++ b/examples/miniapps/movie_lister/app_db.py @@ -11,7 +11,7 @@ sqlite movies database. import sqlite3 -from dependency_injector import catalogs +from dependency_injector import containers from dependency_injector import providers from dependency_injector import injections @@ -21,18 +21,18 @@ from movies import finders 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) diff --git a/examples/miniapps/movie_lister/app_db_csv.py b/examples/miniapps/movie_lister/app_db_csv.py index 3608b97e..83d2739f 100644 --- a/examples/miniapps/movie_lister/app_db_csv.py +++ b/examples/miniapps/movie_lister/app_db_csv.py @@ -11,7 +11,7 @@ sqlite movies database and csv file movies database. import sqlite3 -from dependency_injector import catalogs +from dependency_injector import containers from dependency_injector import providers from dependency_injector import injections @@ -22,27 +22,27 @@ from settings import MOVIES_CSV_PATH 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=',') diff --git a/examples/miniapps/movie_lister/movies/__init__.py b/examples/miniapps/movie_lister/movies/__init__.py index 360c3979..38d11157 100644 --- a/examples/miniapps/movie_lister/movies/__init__.py +++ b/examples/miniapps/movie_lister/movies/__init__.py @@ -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 @@ -12,7 +12,7 @@ concrete finder implementation in terms of library configuration. 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 @@ -20,8 +20,8 @@ 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) diff --git a/tests/test_containers.py b/tests/test_containers.py new file mode 100644 index 00000000..38e6e667 --- /dev/null +++ b/tests/test_containers.py @@ -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() From 83bbbd2be7363eefd64e4fa363994fe092769ae0 Mon Sep 17 00:00:00 2001 From: Roman Mogilatov Date: Sun, 29 May 2016 17:17:27 +0300 Subject: [PATCH 07/16] Update movie lister example --- dependency_injector/containers.py | 40 ++++++++++++++++++-- examples/miniapps/movie_lister/app_csv.py | 4 +- examples/miniapps/movie_lister/app_db.py | 4 +- examples/miniapps/movie_lister/app_db_csv.py | 8 ++-- 4 files changed, 44 insertions(+), 12 deletions(-) diff --git a/dependency_injector/containers.py b/dependency_injector/containers.py index 6c6d690d..0982e0ef 100644 --- a/dependency_injector/containers.py +++ b/dependency_injector/containers.py @@ -91,14 +91,46 @@ class DeclarativeContainer(object): def override(container): """:py:class:`DeclarativeContainer` overriding decorator. - :param catalog: Container that should be overridden by decorated container. - :type catalog: :py:class:`DeclarativeContainer` + :param container: Container that should be overridden by decorated + container. + :type container: :py:class:`DeclarativeContainer` :return: Declarative container's overriding decorator. :rtype: callable(:py:class:`DeclarativeContainer`) """ - def decorator(overriding_container): + def _decorator(overriding_container): """Overriding decorator.""" container.override(overriding_container) return overriding_container - return decorator + return _decorator + + +def copy(container): + """:py:class:`DeclarativeContainer` copying decorator. + + This decorator copy all providers from provided container to decorated one. + If one of the decorated container providers matches to source container + providers by name, it would be replaced by reference. + + :param container: Container that should be copied by decorated container. + :type container :py:class:`DeclarativeContainer` + + :return: Declarative container's copying decorator. + :rtype: callable(:py:class:`DeclarativeContainer`) + """ + def _decorator(copied_container): + memo = dict() + for name, provider in six.iteritems(copied_container.cls_providers): + try: + source_provider = getattr(container, name) + except AttributeError: + pass + else: + memo[id(source_provider)] = provider + + providers_copy = utils._copy_providers(container.providers, memo) + for name, provider in six.iteritems(providers_copy): + setattr(copied_container, name, provider) + + return copied_container + return _decorator diff --git a/examples/miniapps/movie_lister/app_csv.py b/examples/miniapps/movie_lister/app_csv.py index 64a3866f..3fb5bba3 100644 --- a/examples/miniapps/movie_lister/app_csv.py +++ b/examples/miniapps/movie_lister/app_csv.py @@ -24,9 +24,9 @@ class MyMoviesModule(containers.DeclarativeContainer): """IoC container for overriding movies module component providers.""" movie_finder = providers.Factory(finders.CsvMovieFinder, - movie_model=MoviesModule.movie_model, csv_file=MOVIES_CSV_PATH, - delimeter=',') + delimeter=',', + **MoviesModule.movie_finder.kwargs) @injections.inject(MoviesModule.movie_lister) diff --git a/examples/miniapps/movie_lister/app_db.py b/examples/miniapps/movie_lister/app_db.py index 5e62676b..a6555f17 100644 --- a/examples/miniapps/movie_lister/app_db.py +++ b/examples/miniapps/movie_lister/app_db.py @@ -32,8 +32,8 @@ class MyMoviesModule(containers.DeclarativeContainer): """IoC container for overriding movies module component providers.""" movie_finder = providers.Factory(finders.SqliteMovieFinder, - movie_model=MoviesModule.movie_model, - database=ApplicationModule.database) + database=ApplicationModule.database, + **MoviesModule.movie_finder.kwargs) @injections.inject(MoviesModule.movie_lister) diff --git a/examples/miniapps/movie_lister/app_db_csv.py b/examples/miniapps/movie_lister/app_db_csv.py index 83d2739f..975d1b5d 100644 --- a/examples/miniapps/movie_lister/app_db_csv.py +++ b/examples/miniapps/movie_lister/app_db_csv.py @@ -33,8 +33,8 @@ class DbMoviesModule(MoviesModule): """IoC container for overriding movies module component providers.""" movie_finder = providers.Factory(finders.SqliteMovieFinder, - movie_model=MoviesModule.movie_model, - database=ApplicationModule.database) + database=ApplicationModule.database, + **MoviesModule.movie_finder.kwargs) @containers.copy(MoviesModule) @@ -42,9 +42,9 @@ class CsvMoviesModule(MoviesModule): """IoC container for overriding movies module component providers.""" movie_finder = providers.Factory(finders.CsvMovieFinder, - movie_model=MoviesModule.movie_model, csv_file=MOVIES_CSV_PATH, - delimeter=',') + delimeter=',', + **MoviesModule.movie_finder.kwargs) @injections.inject(db_movie_lister=DbMoviesModule.movie_lister) From b2d5819da7e87d745f2558f506f79d6902919418 Mon Sep 17 00:00:00 2001 From: Roman Mogilatov Date: Sun, 29 May 2016 17:33:31 +0300 Subject: [PATCH 08/16] Update IoC and DI demos --- examples/ioc_di_demos/car_engine.py | 2 +- examples/ioc_di_demos/car_engine_ioc.py | 2 +- examples/ioc_di_demos/car_engine_ioc_container.py | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/ioc_di_demos/car_engine.py b/examples/ioc_di_demos/car_engine.py index 44e9e5c8..fc09c16d 100644 --- a/examples/ioc_di_demos/car_engine.py +++ b/examples/ioc_di_demos/car_engine.py @@ -10,7 +10,7 @@ class Car(object): def __init__(self): """Initializer.""" - self.engine = Engine() + self.engine = Engine() # Engine is a "hardcoded" dependency if __name__ == '__main__': diff --git a/examples/ioc_di_demos/car_engine_ioc.py b/examples/ioc_di_demos/car_engine_ioc.py index 48a9e435..7e8c3457 100644 --- a/examples/ioc_di_demos/car_engine_ioc.py +++ b/examples/ioc_di_demos/car_engine_ioc.py @@ -10,7 +10,7 @@ class Car(object): def __init__(self, engine): """Initializer.""" - self.engine = engine + self.engine = engine # Engine is an "injected" dependency if __name__ == '__main__': diff --git a/examples/ioc_di_demos/car_engine_ioc_container.py b/examples/ioc_di_demos/car_engine_ioc_container.py index 18fd6dd7..dd92d18b 100644 --- a/examples/ioc_di_demos/car_engine_ioc_container.py +++ b/examples/ioc_di_demos/car_engine_ioc_container.py @@ -1,14 +1,14 @@ """Example of inversion of control container for Car & Engine example.""" -from dependency_injector import catalogs +from dependency_injector import containers from dependency_injector import providers from car_engine_ioc import Car from car_engine_ioc import Engine -class Components(catalogs.DeclarativeCatalog): - """Catalog of component providers.""" +class Components(containers.DeclarativeContainer): + """IoC container of component providers.""" engine = providers.Factory(Engine) From ed7f354afe03d26a9461df7ec3e70c95f5f229ef Mon Sep 17 00:00:00 2001 From: Roman Mogilatov Date: Sun, 29 May 2016 17:49:59 +0300 Subject: [PATCH 09/16] Update and rename callbacks_based_container example --- ...callbacks.py => callbacks_based_container.py} | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) rename examples/misc/{catalog_providing_callbacks.py => callbacks_based_container.py} (77%) diff --git a/examples/misc/catalog_providing_callbacks.py b/examples/misc/callbacks_based_container.py similarity index 77% rename from examples/misc/catalog_providing_callbacks.py rename to examples/misc/callbacks_based_container.py index 0ba82db0..1d679858 100644 --- a/examples/misc/catalog_providing_callbacks.py +++ b/examples/misc/callbacks_based_container.py @@ -1,8 +1,8 @@ -"""Pythonic way for Dependency Injection - Providing Callbacks Catalog.""" +"""Pythonic way for Dependency Injection - callback-based IoC container.""" import sqlite3 -from dependency_injector import catalogs +from dependency_injector import containers from dependency_injector import providers from dependency_injector import injections @@ -24,14 +24,14 @@ class AuthService(object): self.users_service = users_service -class Services(catalogs.DeclarativeCatalog): - """Catalog of service providers.""" +class Services(containers.DeclarativeContainer): + """IoC container of service providers.""" @providers.Singleton def database(): """Provide database connection. - :rtype: providers.Provider -> sqlite3.Connection + :rtype: sqlite3.Connection """ return sqlite3.connect(':memory:') @@ -40,7 +40,7 @@ class Services(catalogs.DeclarativeCatalog): def users(**kwargs): """Provide users service. - :rtype: providers.Provider -> UsersService + :rtype: UsersService """ return UsersService(**kwargs) @@ -50,12 +50,12 @@ class Services(catalogs.DeclarativeCatalog): def auth(**kwargs): """Provide users service. - :rtype: providers.Provider -> AuthService + :rtype: AuthService """ return AuthService(**kwargs) -# Retrieving catalog providers: +# Retrieving services: users_service = Services.users() auth_service = Services.auth() From d8f03c4487a886814fb31fc5f0c04e78baf1dd14 Mon Sep 17 00:00:00 2001 From: Roman Mogilatov Date: Sun, 29 May 2016 22:05:43 +0300 Subject: [PATCH 10/16] Add provider type checks for declarative container --- dependency_injector/containers.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/dependency_injector/containers.py b/dependency_injector/containers.py index 0982e0ef..c745d57f 100644 --- a/dependency_injector/containers.py +++ b/dependency_injector/containers.py @@ -26,7 +26,18 @@ class DeclarativeContainerMetaClass(type): attributes['inherited_providers'] = dict(inherited_providers) attributes['providers'] = dict(cls_providers + inherited_providers) - return type.__new__(mcs, class_name, bases, attributes) + cls = type.__new__(mcs, class_name, bases, attributes) + + if cls.provider_type: + for provider in six.itervalues(cls.providers): + try: + assert isinstance(provider, cls.provider_type) + except AssertionError: + raise errors.Error('{0} can contain only {1} ' + 'instances'.format(cls, + cls.provider_type)) + + return cls def __setattr__(cls, name, value): """Set class attribute. @@ -57,6 +68,8 @@ class DeclarativeContainer(object): __IS_CATALOG__ = True + provider_type = None + providers = dict() cls_providers = dict() inherited_providers = dict() From 5361694272eb458ef652ae6d8c33051cda2a8726 Mon Sep 17 00:00:00 2001 From: Roman Mogilatov Date: Sun, 29 May 2016 22:06:02 +0300 Subject: [PATCH 11/16] Add container examples --- examples/containers/declarative.py | 22 +++++++++ .../containers/declarative_inheritance.py | 30 ++++++++++++ examples/containers/declarative_injections.py | 47 +++++++++++++++++++ .../declarative_provider_type/container.py | 17 +++++++ .../declarative_provider_type/core.py | 26 ++++++++++ .../declarative_provider_type/main.py | 42 +++++++++++++++++ .../declarative_provider_type/services.py | 22 +++++++++ examples/containers/override_declarative.py | 45 ++++++++++++++++++ .../override_declarative_decorator.py | 41 ++++++++++++++++ 9 files changed, 292 insertions(+) create mode 100644 examples/containers/declarative.py create mode 100644 examples/containers/declarative_inheritance.py create mode 100644 examples/containers/declarative_injections.py create mode 100644 examples/containers/declarative_provider_type/container.py create mode 100644 examples/containers/declarative_provider_type/core.py create mode 100644 examples/containers/declarative_provider_type/main.py create mode 100644 examples/containers/declarative_provider_type/services.py create mode 100644 examples/containers/override_declarative.py create mode 100644 examples/containers/override_declarative_decorator.py diff --git a/examples/containers/declarative.py b/examples/containers/declarative.py new file mode 100644 index 00000000..b6a57b42 --- /dev/null +++ b/examples/containers/declarative.py @@ -0,0 +1,22 @@ +"""Declarative IoC container simple example.""" + +from dependency_injector import containers +from dependency_injector import providers + + +# Defining declarative IoC container: +class Container(containers.DeclarativeContainer): + """Example IoC container.""" + + factory1 = providers.Factory(object) + + factory2 = providers.Factory(object) + +# Creating some objects: +object1 = Container.factory1() +object2 = Container.factory2() + +# Making some asserts: +assert object1 is not object2 +assert isinstance(object1, object) +assert isinstance(object2, object) diff --git a/examples/containers/declarative_inheritance.py b/examples/containers/declarative_inheritance.py new file mode 100644 index 00000000..3f2b46db --- /dev/null +++ b/examples/containers/declarative_inheritance.py @@ -0,0 +1,30 @@ +"""Declarative IoC containers inheritance example.""" + +from dependency_injector import containers +from dependency_injector import providers + + +class ContainerA(containers.DeclarativeContainer): + """Example IoC container A.""" + + provider1 = providers.Factory(object) + + +class ContainerB(ContainerA): + """Example IoC container B.""" + + provider2 = providers.Singleton(object) + + +# Making some asserts for `providers` attribute: +assert ContainerA.providers == dict(provider1=ContainerA.provider1) +assert ContainerB.providers == dict(provider1=ContainerA.provider1, + provider2=ContainerB.provider2) + +# Making some asserts for `cls_providers` attribute: +assert ContainerA.cls_providers == dict(provider1=ContainerA.provider1) +assert ContainerB.cls_providers == dict(provider2=ContainerB.provider2) + +# Making some asserts for `inherited_providers` attribute: +assert ContainerA.inherited_providers == dict() +assert ContainerB.inherited_providers == dict(provider1=ContainerB.provider1) diff --git a/examples/containers/declarative_injections.py b/examples/containers/declarative_injections.py new file mode 100644 index 00000000..f3bbf349 --- /dev/null +++ b/examples/containers/declarative_injections.py @@ -0,0 +1,47 @@ +"""Declarative IoC container's provider injections example.""" + +import sqlite3 + +from dependency_injector import containers +from dependency_injector import providers + + +class UsersService(object): + """Users service, that has dependency on database.""" + + def __init__(self, db): + """Initializer.""" + self.db = db + + +class AuthService(object): + """Auth service, that has dependencies on users service and database.""" + + def __init__(self, db, users_service): + """Initializer.""" + self.db = db + self.users_service = users_service + + +class Services(containers.DeclarativeContainer): + """IoC container of service providers.""" + + database = providers.Singleton(sqlite3.connect, ':memory:') + + users = providers.Factory(UsersService, + db=database) + + auth = providers.Factory(AuthService, + db=database, + users_service=users) + + +# Retrieving service providers from container: +users_service = Services.users() +auth_service = Services.auth() + +# Making some asserts: +assert users_service.db is auth_service.db is Services.database() +assert isinstance(auth_service.users_service, UsersService) +assert users_service is not Services.users() +assert auth_service is not Services.auth() diff --git a/examples/containers/declarative_provider_type/container.py b/examples/containers/declarative_provider_type/container.py new file mode 100644 index 00000000..0dd89d30 --- /dev/null +++ b/examples/containers/declarative_provider_type/container.py @@ -0,0 +1,17 @@ +"""Specialized declarative IoC container example.""" + +import core +import services + + +class Services(core.ServicesContainer): + """IoC container of service providers.""" + + users = core.ServiceProvider(services.UsersService, + config={'option1': '111', + 'option2': '222'}) + + auth = core.ServiceProvider(services.AuthService, + config={'option3': '333', + 'option4': '444'}, + users_service=users) diff --git a/examples/containers/declarative_provider_type/core.py b/examples/containers/declarative_provider_type/core.py new file mode 100644 index 00000000..ed385df7 --- /dev/null +++ b/examples/containers/declarative_provider_type/core.py @@ -0,0 +1,26 @@ +"""Base classes for services.""" + +from dependency_injector import containers +from dependency_injector import providers + + +class BaseService(object): + """Base service class.""" + + +class ServiceProvider(providers.Factory): + """Service provider. + + Can provide :py:class:`Base` only. + """ + + provided_type = BaseService + + +class ServicesContainer(containers.DeclarativeContainer): + """Base IoC container of service providers. + + Can include :py:class:`Provider`'s only. + """ + + provider_type = ServiceProvider diff --git a/examples/containers/declarative_provider_type/main.py b/examples/containers/declarative_provider_type/main.py new file mode 100644 index 00000000..3bd7e21d --- /dev/null +++ b/examples/containers/declarative_provider_type/main.py @@ -0,0 +1,42 @@ +"""Main module.""" + +import core +import services +import container + +from dependency_injector import providers +from dependency_injector import errors + + +if __name__ == '__main__': + # Creating users & auth services: + users_service = container.Services.users() + auth_service = container.Services.auth() + + # Making some asserts: + assert users_service.config == {'option1': '111', + 'option2': '222'} + assert auth_service.config == {'option3': '333', + 'option4': '444'} + assert isinstance(auth_service.users_service, services.UsersService) + + # Trying to declare services container with other provider type: + try: + class _Services1(core.ServicesContainer): + + users = providers.Factory(services.UsersService) + except errors.Error as exception: + print exception + # can contain only + # instances + + # Trying to declare services container with correct provider by invalid + # provided type: + try: + class _Services2(core.ServicesContainer): + + users = core.ServiceProvider(object) + except errors.Error as exception: + print exception + # can provide only + # instances diff --git a/examples/containers/declarative_provider_type/services.py b/examples/containers/declarative_provider_type/services.py new file mode 100644 index 00000000..f6c78e0b --- /dev/null +++ b/examples/containers/declarative_provider_type/services.py @@ -0,0 +1,22 @@ +"""Base classes for services.""" + +import core + + +class UsersService(core.BaseService): + """Users service.""" + + def __init__(self, config): + """Initializer.""" + self.config = config + super(UsersService, self).__init__() + + +class AuthService(core.BaseService): + """Auth service.""" + + def __init__(self, config, users_service): + """Initializer.""" + self.config = config + self.users_service = users_service + super(AuthService, self).__init__() diff --git a/examples/containers/override_declarative.py b/examples/containers/override_declarative.py new file mode 100644 index 00000000..40c1fb27 --- /dev/null +++ b/examples/containers/override_declarative.py @@ -0,0 +1,45 @@ +"""Declarative IoC container overriding example.""" + +import collections + +from dependency_injector import containers +from dependency_injector import providers + + +# Creating some example classes: +Object1 = collections.namedtuple('Object1', ['arg1', 'arg2']) +Object2 = collections.namedtuple('Object2', ['object1']) +ExtendedObject2 = collections.namedtuple('ExtendedObject2', []) + + +class Container(containers.DeclarativeContainer): + """Example IoC container.""" + + object1_factory = providers.Factory(Object1, + arg1=1, + arg2=2) + + object2_factory = providers.Factory(Object2, + object1=object1_factory) + + +class OverridingContainer(containers.DeclarativeContainer): + """Overriding IoC container.""" + + object2_factory = providers.Factory(ExtendedObject2) + + +# Overriding `Container` with `OverridingContainer`: +Container.override(OverridingContainer) + +# Creating some objects using overridden container: +object2_1 = Container.object2_factory() +object2_2 = Container.object2_factory() + +# Making some asserts: +assert Container.overridden_by == (OverridingContainer,) + +assert object2_1 is not object2_2 + +assert isinstance(object2_1, ExtendedObject2) +assert isinstance(object2_2, ExtendedObject2) diff --git a/examples/containers/override_declarative_decorator.py b/examples/containers/override_declarative_decorator.py new file mode 100644 index 00000000..c1e259ce --- /dev/null +++ b/examples/containers/override_declarative_decorator.py @@ -0,0 +1,41 @@ +"""Declarative IoC container overriding using `@override()` decorator.""" + +import collections + +from dependency_injector import containers +from dependency_injector import providers + + +# Creating some example classes: +Object1 = collections.namedtuple('Object1', ['arg1', 'arg2']) +Object2 = collections.namedtuple('Object2', ['object1']) +ExtendedObject2 = collections.namedtuple('ExtendedObject2', []) + + +class Container(containers.DeclarativeContainer): + """Example IoC container.""" + + object1_factory = providers.Factory(Object1, arg1=1, arg2=2) + + object2_factory = providers.Factory(Object2, object1=object1_factory) + + +# Overriding `Container` with `OverridingContainer`: +@containers.override(Container) +class OverridingContainer(containers.DeclarativeContainer): + """Overriding IoC container.""" + + object2_factory = providers.Factory(ExtendedObject2) + + +# Creating some objects using overridden container: +object2_1 = Container.object2_factory() +object2_2 = Container.object2_factory() + +# Making some asserts: +assert Container.overridden_by == (OverridingContainer,) + +assert object2_1 is not object2_2 + +assert isinstance(object2_1, ExtendedObject2) +assert isinstance(object2_2, ExtendedObject2) From 68ae1b80df480aa7a18fa60baf88ca3a5bc75060 Mon Sep 17 00:00:00 2001 From: Roman Mogilatov Date: Sun, 29 May 2016 22:21:50 +0300 Subject: [PATCH 12/16] Drop catalog examples --- examples/catalogs/declarative.py | 22 ------ examples/catalogs/declarative_inheritance.py | 30 -------- examples/catalogs/declarative_injections.py | 47 ------------ .../declarative_provider_type/catalog.py | 73 ------------------- .../declarative_provider_type/services.py | 26 ------- examples/catalogs/dynamic.py | 18 ----- .../catalogs/dynamic_provider_type/catalog.py | 63 ---------------- .../dynamic_provider_type/services.py | 26 ------- examples/catalogs/dynamic_runtime_creation.py | 66 ----------------- examples/catalogs/override_declarative.py | 45 ------------ .../override_declarative_by_dynamic.py | 41 ----------- .../override_declarative_decorator.py | 43 ----------- 12 files changed, 500 deletions(-) delete mode 100644 examples/catalogs/declarative.py delete mode 100644 examples/catalogs/declarative_inheritance.py delete mode 100644 examples/catalogs/declarative_injections.py delete mode 100644 examples/catalogs/declarative_provider_type/catalog.py delete mode 100644 examples/catalogs/declarative_provider_type/services.py delete mode 100644 examples/catalogs/dynamic.py delete mode 100644 examples/catalogs/dynamic_provider_type/catalog.py delete mode 100644 examples/catalogs/dynamic_provider_type/services.py delete mode 100644 examples/catalogs/dynamic_runtime_creation.py delete mode 100644 examples/catalogs/override_declarative.py delete mode 100644 examples/catalogs/override_declarative_by_dynamic.py delete mode 100644 examples/catalogs/override_declarative_decorator.py diff --git a/examples/catalogs/declarative.py b/examples/catalogs/declarative.py deleted file mode 100644 index a483fc88..00000000 --- a/examples/catalogs/declarative.py +++ /dev/null @@ -1,22 +0,0 @@ -"""Declarative catalog simple example.""" - -from dependency_injector import catalogs -from dependency_injector import providers - - -# Defining declarative catalog: -class Catalog(catalogs.DeclarativeCatalog): - """Providers catalog.""" - - factory1 = providers.Factory(object) - - factory2 = providers.Factory(object) - -# Creating some objects: -object1 = Catalog.factory1() -object2 = Catalog.factory2() - -# Making some asserts: -assert object1 is not object2 -assert isinstance(object1, object) -assert isinstance(object2, object) diff --git a/examples/catalogs/declarative_inheritance.py b/examples/catalogs/declarative_inheritance.py deleted file mode 100644 index e0ff494c..00000000 --- a/examples/catalogs/declarative_inheritance.py +++ /dev/null @@ -1,30 +0,0 @@ -"""Declarative catalogs inheritance example.""" - -from dependency_injector import catalogs -from dependency_injector import providers - - -class CatalogA(catalogs.DeclarativeCatalog): - """Example catalog A.""" - - provider1 = providers.Factory(object) - - -class CatalogB(CatalogA): - """Example catalog B.""" - - provider2 = providers.Singleton(object) - - -# Making some asserts for `providers` attribute: -assert CatalogA.providers == dict(provider1=CatalogA.provider1) -assert CatalogB.providers == dict(provider1=CatalogA.provider1, - provider2=CatalogB.provider2) - -# Making some asserts for `cls_providers` attribute: -assert CatalogA.cls_providers == dict(provider1=CatalogA.provider1) -assert CatalogB.cls_providers == dict(provider2=CatalogB.provider2) - -# Making some asserts for `inherited_providers` attribute: -assert CatalogA.inherited_providers == dict() -assert CatalogB.inherited_providers == dict(provider1=CatalogA.provider1) diff --git a/examples/catalogs/declarative_injections.py b/examples/catalogs/declarative_injections.py deleted file mode 100644 index 85dc8f87..00000000 --- a/examples/catalogs/declarative_injections.py +++ /dev/null @@ -1,47 +0,0 @@ -"""Declarative catalog's provider injections example.""" - -import sqlite3 - -from dependency_injector import catalogs -from dependency_injector import providers - - -class UsersService(object): - """Users service, that has dependency on database.""" - - def __init__(self, db): - """Initializer.""" - self.db = db - - -class AuthService(object): - """Auth service, that has dependencies on users service and database.""" - - def __init__(self, db, users_service): - """Initializer.""" - self.db = db - self.users_service = users_service - - -class Services(catalogs.DeclarativeCatalog): - """Catalog of service providers.""" - - database = providers.Singleton(sqlite3.connect, ':memory:') - - users = providers.Factory(UsersService, - db=database) - - auth = providers.Factory(AuthService, - db=database, - users_service=users) - - -# Retrieving service providers from catalog: -users_service = Services.users() -auth_service = Services.auth() - -# Making some asserts: -assert users_service.db is auth_service.db is Services.database() -assert isinstance(auth_service.users_service, UsersService) -assert users_service is not Services.users() -assert auth_service is not Services.auth() diff --git a/examples/catalogs/declarative_provider_type/catalog.py b/examples/catalogs/declarative_provider_type/catalog.py deleted file mode 100644 index 520da6cc..00000000 --- a/examples/catalogs/declarative_provider_type/catalog.py +++ /dev/null @@ -1,73 +0,0 @@ -"""Specialized declarative catalog example.""" - -import services - -from dependency_injector import providers -from dependency_injector import errors - - -class UsersService(services.Base): - """Users service.""" - - def __init__(self, config): - """Initializer.""" - self.config = config - super(UsersService, self).__init__() - - -class AuthService(services.Base): - """Auth service.""" - - def __init__(self, config, users_service): - """Initializer.""" - self.config = config - self.users_service = users_service - super(AuthService, self).__init__() - - -class Services(services.Catalog): - """Services catalog.""" - - users = services.Provider(UsersService, - config={'option1': '111', - 'option2': '222'}) - - auth = services.Provider(AuthService, - config={'option3': '333', - 'option4': '444'}, - users_service=users) - - -# Creating users & auth services: -users_service = Services.users() -auth_service = Services.auth() - -# Making some asserts: -assert users_service.config == {'option1': '111', - 'option2': '222'} -assert auth_service.config == {'option3': '333', - 'option4': '444'} -assert isinstance(auth_service.users_service, UsersService) - -# Trying to declare services catalog with other provider type: -try: - class Services1(services.Catalog): - """Services catalog.""" - - users = providers.Factory(UsersService) -except errors.Error as exception: - print exception - # <__main__.Services1()> can contain only - # instances - -# Trying to declare services catalog with correct provider by invalid provided -# type: -try: - class Services2(services.Catalog): - """Services catalog.""" - - users = services.Provider(object) -except errors.Error as exception: - print exception - # can provide only - # instances diff --git a/examples/catalogs/declarative_provider_type/services.py b/examples/catalogs/declarative_provider_type/services.py deleted file mode 100644 index 7a1f0432..00000000 --- a/examples/catalogs/declarative_provider_type/services.py +++ /dev/null @@ -1,26 +0,0 @@ -"""Base classes for services.""" - -from dependency_injector import catalogs -from dependency_injector import providers - - -class Base(object): - """Base service class.""" - - -class Provider(providers.Factory): - """Service provider. - - Can provide :py:class:`Base` only. - """ - - provided_type = Base - - -class Catalog(catalogs.DeclarativeCatalog): - """Base catalog of services. - - Can include :py:class:`Provider`'s only. - """ - - provider_type = Provider diff --git a/examples/catalogs/dynamic.py b/examples/catalogs/dynamic.py deleted file mode 100644 index b89bc92c..00000000 --- a/examples/catalogs/dynamic.py +++ /dev/null @@ -1,18 +0,0 @@ -"""Dynamic catalog simple example.""" - -from dependency_injector import catalogs -from dependency_injector import providers - - -# Defining dynamic catalog: -catalog = catalogs.DynamicCatalog(factory1=providers.Factory(object), - factory2=providers.Factory(object)) - -# Creating some objects: -object1 = catalog.factory1() -object2 = catalog.factory2() - -# Making some asserts: -assert object1 is not object2 -assert isinstance(object1, object) -assert isinstance(object2, object) diff --git a/examples/catalogs/dynamic_provider_type/catalog.py b/examples/catalogs/dynamic_provider_type/catalog.py deleted file mode 100644 index 7578c492..00000000 --- a/examples/catalogs/dynamic_provider_type/catalog.py +++ /dev/null @@ -1,63 +0,0 @@ -"""Specialized dynamic catalog example.""" - -import services - -from dependency_injector import providers -from dependency_injector import errors - - -class UsersService(services.Base): - """Users service.""" - - def __init__(self, config): - """Initializer.""" - self.config = config - super(UsersService, self).__init__() - - -class AuthService(services.Base): - """Auth service.""" - - def __init__(self, config, users_service): - """Initializer.""" - self.config = config - self.users_service = users_service - super(AuthService, self).__init__() - - -services_catalog = services.Catalog() -services_catalog.users = services.Provider(UsersService, - config={'option1': '111', - 'option2': '222'}) -services_catalog.auth = services.Provider(AuthService, - config={'option3': '333', - 'option4': '444'}, - users_service=services_catalog.users) - -# Creating users & auth services: -users_service = services_catalog.users() -auth_service = services_catalog.auth() - -# Making some asserts: -assert users_service.config == {'option1': '111', - 'option2': '222'} -assert auth_service.config == {'option3': '333', - 'option4': '444'} -assert isinstance(auth_service.users_service, UsersService) - -# Trying to declare services catalog with other provider type: -try: - services_catalog.users = providers.Factory(UsersService) -except errors.Error as exception: - print exception - # can contain only - # instances - -# Trying to declare services catalog with correct provider by invalid provided -# type: -try: - services_catalog.users = services.Provider(object) -except errors.Error as exception: - print exception - # can provide only - # instances diff --git a/examples/catalogs/dynamic_provider_type/services.py b/examples/catalogs/dynamic_provider_type/services.py deleted file mode 100644 index 853ea1b4..00000000 --- a/examples/catalogs/dynamic_provider_type/services.py +++ /dev/null @@ -1,26 +0,0 @@ -"""Base classes for services.""" - -from dependency_injector import catalogs -from dependency_injector import providers - - -class Base(object): - """Base service class.""" - - -class Provider(providers.Factory): - """Service provider. - - Can provide :py:class:`Base` only. - """ - - provided_type = Base - - -class Catalog(catalogs.DynamicCatalog): - """Base catalog of services. - - Can include :py:class:`Provider`'s only. - """ - - provider_type = Provider diff --git a/examples/catalogs/dynamic_runtime_creation.py b/examples/catalogs/dynamic_runtime_creation.py deleted file mode 100644 index c42570b1..00000000 --- a/examples/catalogs/dynamic_runtime_creation.py +++ /dev/null @@ -1,66 +0,0 @@ -"""Dynamic catalog creation and runtime filling of it example.""" - -from dependency_injector import catalogs - - -# Defining several example services: -class UsersService(object): - """Example users service.""" - - -class AuthService(object): - """Example auth service.""" - - -def import_cls(cls_name): - """Import class by its fully qualified name. - - In terms of current example it is just a small helper function. Please, - don't use it in production approaches. - """ - path_components = cls_name.split('.') - if len(path_components) == 1: - path_components.insert(0, '__main__') - module = __import__('.'.join(path_components[0:-1]), - locals(), - globals(), - fromlist=path_components[-1:]) - return getattr(module, path_components[-1]) - - -# "Parsing" some configuration: -config = { - 'services': { - 'users': { - 'class': 'UsersService', - 'provider_class': 'dependency_injector.providers.Factory', - }, - 'auth': { - 'class': 'AuthService', - 'provider_class': 'dependency_injector.providers.Factory', - } - } -} - -# Defining dynamic service providers catalog: -services = catalogs.DynamicCatalog() - -# Filling dynamic service providers catalog according to the configuration: -for service_name, service_info in config['services'].iteritems(): - # Runtime importing of service and service provider classes: - service_cls = import_cls(service_info['class']) - service_provider_cls = import_cls(service_info['provider_class']) - - # Creating service provider: - service_provider = service_provider_cls(service_cls) - - # Binding service provider to the dynamic service providers catalog: - services.bind_provider(service_name, service_provider) - -# Creating some objects: -users_service = services.users() -auth_service = services.auth() - -# Making some asserts: -assert isinstance(users_service, UsersService) -assert isinstance(auth_service, AuthService) diff --git a/examples/catalogs/override_declarative.py b/examples/catalogs/override_declarative.py deleted file mode 100644 index a38777ad..00000000 --- a/examples/catalogs/override_declarative.py +++ /dev/null @@ -1,45 +0,0 @@ -"""Declarative catalog overriding example.""" - -import collections - -from dependency_injector import catalogs -from dependency_injector import providers - - -# Creating some example classes: -Object1 = collections.namedtuple('Object1', ['arg1', 'arg2']) -Object2 = collections.namedtuple('Object2', ['object1']) -ExtendedObject2 = collections.namedtuple('ExtendedObject2', []) - - -class Catalog(catalogs.DeclarativeCatalog): - """Catalog of some providers.""" - - object1_factory = providers.Factory(Object1, - arg1=1, - arg2=2) - - object2_factory = providers.Factory(Object2, - object1=object1_factory) - - -class AnotherCatalog(catalogs.DeclarativeCatalog): - """Overriding catalog.""" - - object2_factory = providers.Factory(ExtendedObject2) - - -# Overriding `Catalog` with `AnotherCatalog`: -Catalog.override(AnotherCatalog) - -# Creating some objects using overridden catalog: -object2_1 = Catalog.object2_factory() -object2_2 = Catalog.object2_factory() - -# Making some asserts: -assert Catalog.is_overridden - -assert object2_1 is not object2_2 - -assert isinstance(object2_1, ExtendedObject2) -assert isinstance(object2_2, ExtendedObject2) diff --git a/examples/catalogs/override_declarative_by_dynamic.py b/examples/catalogs/override_declarative_by_dynamic.py deleted file mode 100644 index 7246e5f4..00000000 --- a/examples/catalogs/override_declarative_by_dynamic.py +++ /dev/null @@ -1,41 +0,0 @@ -"""Declarative catalog overriding by dynamic catalog example.""" - -import collections - -from dependency_injector import catalogs -from dependency_injector import providers - - -# Creating some example classes: -Object1 = collections.namedtuple('Object1', ['arg1', 'arg2']) -Object2 = collections.namedtuple('Object2', ['object1']) -ExtendedObject2 = collections.namedtuple('ExtendedObject2', []) - - -class Catalog(catalogs.DeclarativeCatalog): - """Catalog of some providers.""" - - object1_factory = providers.Factory(Object1, - arg1=1, - arg2=2) - - object2_factory = providers.Factory(Object2, - object1=object1_factory) - - -# Overriding `Catalog` with some `DynamicCatalog` instance: -overriding_catalog = catalogs.DynamicCatalog( - object2_factory=providers.Factory(ExtendedObject2)) -Catalog.override(overriding_catalog) - -# Creating some objects using overridden catalog: -object2_1 = Catalog.object2_factory() -object2_2 = Catalog.object2_factory() - -# Making some asserts: -assert Catalog.is_overridden - -assert object2_1 is not object2_2 - -assert isinstance(object2_1, ExtendedObject2) -assert isinstance(object2_2, ExtendedObject2) diff --git a/examples/catalogs/override_declarative_decorator.py b/examples/catalogs/override_declarative_decorator.py deleted file mode 100644 index cb59a752..00000000 --- a/examples/catalogs/override_declarative_decorator.py +++ /dev/null @@ -1,43 +0,0 @@ -"""Declarative catalog overriding using `@override()` decorator example.""" - -import collections - -from dependency_injector import catalogs -from dependency_injector import providers - -# Creating some example classes: -Object1 = collections.namedtuple('Object1', ['arg1', 'arg2']) -Object2 = collections.namedtuple('Object2', ['object1']) -ExtendedObject2 = collections.namedtuple('ExtendedObject2', []) - - -class Catalog(catalogs.DeclarativeCatalog): - """Catalog of some providers.""" - - object1_factory = providers.Factory(Object1, - arg1=1, - arg2=2) - - object2_factory = providers.Factory(Object2, - object1=object1_factory) - - -# Overriding `Catalog` with `AnotherCatalog`: -@catalogs.override(Catalog) -class AnotherCatalog(catalogs.DeclarativeCatalog): - """Overriding catalog.""" - - object2_factory = providers.Factory(ExtendedObject2) - - -# Creating some objects using overridden catalog: -object2_1 = Catalog.object2_factory() -object2_2 = Catalog.object2_factory() - -# Making some asserts: -assert Catalog.is_overridden - -assert object2_1 is not object2_2 - -assert isinstance(object2_1, ExtendedObject2) -assert isinstance(object2_2, ExtendedObject2) From a35db5889df52bb6b78fad9dfdf38d9ebb04683a Mon Sep 17 00:00:00 2001 From: Roman Mogilatov Date: Mon, 30 May 2016 23:34:14 +0300 Subject: [PATCH 13/16] Add some functionality and tests for declarative containers + Add checks for valid provider type + Add some wider functionality for overriding --- dependency_injector/containers.py | 51 ++++++++--- dependency_injector/providers/base.py | 10 +-- dependency_injector/utils.py | 12 +++ tests/test_containers.py | 121 +++++++++++++++++++++++++- 4 files changed, 173 insertions(+), 21 deletions(-) diff --git a/dependency_injector/containers.py b/dependency_injector/containers.py index c745d57f..2cf435fc 100644 --- a/dependency_injector/containers.py +++ b/dependency_injector/containers.py @@ -3,6 +3,7 @@ import six from dependency_injector import ( + providers, utils, errors, ) @@ -18,7 +19,8 @@ class DeclarativeContainerMetaClass(type): if utils.is_provider(provider)) inherited_providers = tuple((name, provider) - for base in bases if utils.is_catalog(base) + for base in bases if utils.is_container( + base) for name, provider in six.iteritems( base.cls_providers)) @@ -28,14 +30,8 @@ class DeclarativeContainerMetaClass(type): cls = type.__new__(mcs, class_name, bases, attributes) - if cls.provider_type: - for provider in six.itervalues(cls.providers): - try: - assert isinstance(provider, cls.provider_type) - except AssertionError: - raise errors.Error('{0} can contain only {1} ' - 'instances'.format(cls, - cls.provider_type)) + for provider in six.itervalues(cls.providers): + cls._check_provider_type(provider) return cls @@ -46,6 +42,7 @@ class DeclarativeContainerMetaClass(type): dictionary. """ if utils.is_provider(value): + cls._check_provider_type(value) cls.providers[name] = value cls.cls_providers[name] = value super(DeclarativeContainerMetaClass, cls).__setattr__(name, value) @@ -61,14 +58,19 @@ class DeclarativeContainerMetaClass(type): del cls.cls_providers[name] super(DeclarativeContainerMetaClass, cls).__delattr__(name) + def _check_provider_type(cls, provider): + if not isinstance(provider, cls.provider_type): + raise errors.Error('{0} can contain only {1} ' + 'instances'.format(cls, cls.provider_type)) + @six.add_metaclass(DeclarativeContainerMetaClass) class DeclarativeContainer(object): """Declarative inversion of control container.""" - __IS_CATALOG__ = True + __IS_CONTAINER__ = True - provider_type = None + provider_type = providers.Provider providers = dict() cls_providers = dict() @@ -89,7 +91,7 @@ class DeclarativeContainer(object): :rtype: None """ if issubclass(cls, overriding): - raise errors.Error('Catalog {0} could not be overridden ' + raise errors.Error('Container {0} could not be overridden ' 'with itself or its subclasses'.format(cls)) cls.overridden_by += (overriding,) @@ -100,6 +102,31 @@ class DeclarativeContainer(object): except AttributeError: pass + @classmethod + def reset_last_overriding(cls): + """Reset last overriding provider for each container providers. + + :rtype: None + """ + if not cls.overridden_by: + raise errors.Error('Container {0} is not overridden'.format(cls)) + + cls.overridden_by = cls.overridden_by[:-1] + + for provider in six.itervalues(cls.providers): + provider.reset_last_overriding() + + @classmethod + def reset_override(cls): + """Reset all overridings for each container providers. + + :rtype: None + """ + cls.overridden_by = tuple() + + for provider in six.itervalues(cls.providers): + provider.reset_override() + def override(container): """:py:class:`DeclarativeContainer` overriding decorator. diff --git a/dependency_injector/providers/base.py b/dependency_injector/providers/base.py index 36b9a6e9..4b740497 100644 --- a/dependency_injector/providers/base.py +++ b/dependency_injector/providers/base.py @@ -64,7 +64,7 @@ class Provider(object): def __init__(self): """Initializer.""" - self.overridden_by = None + self.overridden_by = tuple() super(Provider, self).__init__() # Enable __call__() / _provide() optimization if self.__class__.__OPTIMIZED_CALLS__: @@ -124,10 +124,7 @@ class Provider(object): if not is_provider(provider): provider = Object(provider) - if not self.is_overridden: - self.overridden_by = (ensure_is_provider(provider),) - else: - self.overridden_by += (ensure_is_provider(provider),) + self.overridden_by += (ensure_is_provider(provider),) # Disable __call__() / _provide() optimization if self.__class__.__OPTIMIZED_CALLS__: @@ -145,6 +142,7 @@ class Provider(object): """ if not self.overridden_by: raise Error('Provider {0} is not overridden'.format(str(self))) + self.overridden_by = self.overridden_by[:-1] if not self.is_overridden: @@ -157,7 +155,7 @@ class Provider(object): :rtype: None """ - self.overridden_by = None + self.overridden_by = tuple() # Enable __call__() / _provide() optimization if self.__class__.__OPTIMIZED_CALLS__: diff --git a/dependency_injector/utils.py b/dependency_injector/utils.py index f65dfcea..f1354a14 100644 --- a/dependency_injector/utils.py +++ b/dependency_injector/utils.py @@ -59,6 +59,18 @@ def ensure_is_provider(instance): return instance +def is_container(instance): + """Check if instance is container instance. + + :param instance: Instance to be checked. + :type instance: object + + :rtype: bool + """ + return (hasattr(instance, '__IS_CONTAINER__') and + getattr(instance, '__IS_CONTAINER__', False) is True) + + def is_catalog(instance): """Check if instance is catalog instance. diff --git a/tests/test_containers.py b/tests/test_containers.py index 38e6e667..39bf7d1e 100644 --- a/tests/test_containers.py +++ b/tests/test_containers.py @@ -5,6 +5,7 @@ import unittest2 as unittest from dependency_injector import ( containers, providers, + errors, ) @@ -28,7 +29,7 @@ class ContainerB(ContainerA): class DeclarativeContainerTests(unittest.TestCase): """Declarative container tests.""" - def test_providers_attribute_with(self): + def test_providers_attribute(self): """Test providers attribute.""" self.assertEqual(ContainerA.providers, dict(p11=ContainerA.p11, p12=ContainerA.p12)) @@ -37,7 +38,7 @@ class DeclarativeContainerTests(unittest.TestCase): p21=ContainerB.p21, p22=ContainerB.p22)) - def test_cls_providers_attribute_with(self): + def test_cls_providers_attribute(self): """Test cls_providers attribute.""" self.assertEqual(ContainerA.cls_providers, dict(p11=ContainerA.p11, p12=ContainerA.p12)) @@ -51,7 +52,7 @@ class DeclarativeContainerTests(unittest.TestCase): dict(p11=ContainerA.p11, p12=ContainerA.p12)) - def test_set_get_del_provider_attribute(self): + def test_set_get_del_providers(self): """Test set/get/del provider attributes.""" a_p13 = providers.Provider() b_p23 = providers.Provider() @@ -90,6 +91,120 @@ class DeclarativeContainerTests(unittest.TestCase): self.assertEqual(ContainerB.cls_providers, dict(p21=ContainerB.p21, p22=ContainerB.p22)) + def test_declare_with_valid_provider_type(self): + """Test declaration of container with valid provider type.""" + class _Container(containers.DeclarativeContainer): + provider_type = providers.Object + px = providers.Object(object()) + + self.assertIsInstance(_Container.px, providers.Object) + + def test_declare_with_invalid_provider_type(self): + """Test declaration of container with invalid provider type.""" + with self.assertRaises(errors.Error): + class _Container(containers.DeclarativeContainer): + provider_type = providers.Object + px = providers.Provider() + + def test_seth_valid_provider_type(self): + """Test setting of valid provider.""" + class _Container(containers.DeclarativeContainer): + provider_type = providers.Object + + _Container.px = providers.Object(object()) + + self.assertIsInstance(_Container.px, providers.Object) + + def test_set_invalid_provider_type(self): + """Test setting of invalid provider.""" + class _Container(containers.DeclarativeContainer): + provider_type = providers.Object + + with self.assertRaises(errors.Error): + _Container.px = providers.Provider() + + def test_override(self): + """Test override.""" + class _Container(containers.DeclarativeContainer): + p11 = providers.Provider() + + class _OverridingContainer1(containers.DeclarativeContainer): + p11 = providers.Provider() + + class _OverridingContainer2(containers.DeclarativeContainer): + p11 = providers.Provider() + p12 = providers.Provider() + + _Container.override(_OverridingContainer1) + _Container.override(_OverridingContainer2) + + self.assertEqual(_Container.overridden_by, + (_OverridingContainer1, + _OverridingContainer2)) + self.assertEqual(_Container.p11.overridden_by, + (_OverridingContainer1.p11, + _OverridingContainer2.p11)) + + def test_override_decorator(self): + """Test override decorator.""" + class _Container(containers.DeclarativeContainer): + p11 = providers.Provider() + + @containers.override(_Container) + class _OverridingContainer1(containers.DeclarativeContainer): + p11 = providers.Provider() + + @containers.override(_Container) + class _OverridingContainer2(containers.DeclarativeContainer): + p11 = providers.Provider() + p12 = providers.Provider() + + self.assertEqual(_Container.overridden_by, + (_OverridingContainer1, + _OverridingContainer2)) + self.assertEqual(_Container.p11.overridden_by, + (_OverridingContainer1.p11, + _OverridingContainer2.p11)) + + def test_reset_last_overridding(self): + """Test reset of last overriding.""" + class _Container(containers.DeclarativeContainer): + p11 = providers.Provider() + + class _OverridingContainer1(containers.DeclarativeContainer): + p11 = providers.Provider() + + class _OverridingContainer2(containers.DeclarativeContainer): + p11 = providers.Provider() + p12 = providers.Provider() + + _Container.override(_OverridingContainer1) + _Container.override(_OverridingContainer2) + _Container.reset_last_overriding() + + self.assertEqual(_Container.overridden_by, + (_OverridingContainer1,)) + self.assertEqual(_Container.p11.overridden_by, + (_OverridingContainer1.p11,)) + + def test_reset_override(self): + """Test reset all overridings.""" + class _Container(containers.DeclarativeContainer): + p11 = providers.Provider() + + class _OverridingContainer1(containers.DeclarativeContainer): + p11 = providers.Provider() + + class _OverridingContainer2(containers.DeclarativeContainer): + p11 = providers.Provider() + p12 = providers.Provider() + + _Container.override(_OverridingContainer1) + _Container.override(_OverridingContainer2) + _Container.reset_override() + + self.assertEqual(_Container.overridden_by, tuple()) + self.assertEqual(_Container.p11.overridden_by, tuple()) if __name__ == '__main__': unittest.main() From 1c1596543d025a9ed05ef59d94e3603aba13b6ed Mon Sep 17 00:00:00 2001 From: Roman Mogilatov Date: Wed, 1 Jun 2016 12:03:17 +0300 Subject: [PATCH 14/16] Add some tests for containers --- tests/test_containers.py | 70 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/tests/test_containers.py b/tests/test_containers.py index 39bf7d1e..46de2e7f 100644 --- a/tests/test_containers.py +++ b/tests/test_containers.py @@ -145,6 +145,16 @@ class DeclarativeContainerTests(unittest.TestCase): (_OverridingContainer1.p11, _OverridingContainer2.p11)) + def test_override_with_itself(self): + """Test override with itself.""" + with self.assertRaises(errors.Error): + ContainerA.override(ContainerA) + + def test_override_with_parent(self): + """Test override with parent.""" + with self.assertRaises(errors.Error): + ContainerB.override(ContainerA) + def test_override_decorator(self): """Test override decorator.""" class _Container(containers.DeclarativeContainer): @@ -187,6 +197,11 @@ class DeclarativeContainerTests(unittest.TestCase): self.assertEqual(_Container.p11.overridden_by, (_OverridingContainer1.p11,)) + def test_reset_last_overridding_when_not_overridden(self): + """Test reset of last overriding.""" + with self.assertRaises(errors.Error): + ContainerA.reset_last_overriding() + def test_reset_override(self): """Test reset all overridings.""" class _Container(containers.DeclarativeContainer): @@ -206,5 +221,60 @@ class DeclarativeContainerTests(unittest.TestCase): self.assertEqual(_Container.overridden_by, tuple()) self.assertEqual(_Container.p11.overridden_by, tuple()) + def test_copy(self): + """Test copy decorator.""" + @containers.copy(ContainerA) + class _Container1(ContainerA): + pass + + @containers.copy(ContainerA) + class _Container2(ContainerA): + pass + + self.assertIsNot(ContainerA.p11, _Container1.p11) + self.assertIsNot(ContainerA.p12, _Container1.p12) + + self.assertIsNot(ContainerA.p11, _Container2.p11) + self.assertIsNot(ContainerA.p12, _Container2.p12) + + self.assertIsNot(_Container1.p11, _Container2.p11) + self.assertIsNot(_Container1.p12, _Container2.p12) + + def test_copy_with_replacing(self): + """Test copy decorator with providers replacement.""" + class _Container(containers.DeclarativeContainer): + p11 = providers.Object(0) + p12 = providers.Factory(dict, p11=p11) + + @containers.copy(_Container) + class _Container1(_Container): + p11 = providers.Object(1) + p13 = providers.Object(11) + + @containers.copy(_Container) + class _Container2(_Container): + p11 = providers.Object(2) + p13 = providers.Object(22) + + self.assertIsNot(_Container.p11, _Container1.p11) + self.assertIsNot(_Container.p12, _Container1.p12) + + self.assertIsNot(_Container.p11, _Container2.p11) + self.assertIsNot(_Container.p12, _Container2.p12) + + self.assertIsNot(_Container1.p11, _Container2.p11) + self.assertIsNot(_Container1.p12, _Container2.p12) + + self.assertIs(_Container.p12.kwargs['p11'], _Container.p11) + self.assertIs(_Container1.p12.kwargs['p11'], _Container1.p11) + self.assertIs(_Container2.p12.kwargs['p11'], _Container2.p11) + + self.assertEqual(_Container.p12(), dict(p11=0)) + self.assertEqual(_Container1.p12(), dict(p11=1)) + self.assertEqual(_Container2.p12(), dict(p11=2)) + + self.assertEqual(_Container1.p13(), 11) + self.assertEqual(_Container2.p13(), 22) + if __name__ == '__main__': unittest.main() From be94a1badc42e11b89112bfbd9fab2039b70452c Mon Sep 17 00:00:00 2001 From: Roman Mogilatov Date: Wed, 1 Jun 2016 16:00:11 +0300 Subject: [PATCH 15/16] Extract providers type checker into function --- dependency_injector/containers.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/dependency_injector/containers.py b/dependency_injector/containers.py index 2cf435fc..8e36f3ba 100644 --- a/dependency_injector/containers.py +++ b/dependency_injector/containers.py @@ -31,7 +31,7 @@ class DeclarativeContainerMetaClass(type): cls = type.__new__(mcs, class_name, bases, attributes) for provider in six.itervalues(cls.providers): - cls._check_provider_type(provider) + _check_provider_type(cls, provider) return cls @@ -42,7 +42,7 @@ class DeclarativeContainerMetaClass(type): dictionary. """ if utils.is_provider(value): - cls._check_provider_type(value) + _check_provider_type(cls, value) cls.providers[name] = value cls.cls_providers[name] = value super(DeclarativeContainerMetaClass, cls).__setattr__(name, value) @@ -58,11 +58,6 @@ class DeclarativeContainerMetaClass(type): del cls.cls_providers[name] super(DeclarativeContainerMetaClass, cls).__delattr__(name) - def _check_provider_type(cls, provider): - if not isinstance(provider, cls.provider_type): - raise errors.Error('{0} can contain only {1} ' - 'instances'.format(cls, cls.provider_type)) - @six.add_metaclass(DeclarativeContainerMetaClass) class DeclarativeContainer(object): @@ -174,3 +169,9 @@ def copy(container): return copied_container return _decorator + + +def _check_provider_type(cls, provider): + if not isinstance(provider, cls.provider_type): + raise errors.Error('{0} can contain only {1} ' + 'instances'.format(cls, cls.provider_type)) From 3416728309cc9dc1de91f4213fddc97a6f428df8 Mon Sep 17 00:00:00 2001 From: Roman Mogilatov Date: Wed, 1 Jun 2016 18:53:35 +0300 Subject: [PATCH 16/16] Add dynamic containers + Drop catalogs --- dependency_injector/catalogs/__init__.py | 20 - dependency_injector/catalogs/declarative.py | 440 ------------------- dependency_injector/catalogs/dynamic.py | 309 -------------- dependency_injector/catalogs/utils.py | 62 --- dependency_injector/containers.py | 97 ++++- dependency_injector/utils.py | 46 +- setup.py | 1 - tests/catalogs/__init__.py | 1 - tests/catalogs/test_declarative.py | 443 -------------------- tests/catalogs/test_dynamic.py | 221 ---------- tests/catalogs/test_override.py | 142 ------- tests/test_containers.py | 157 +++++++ tests/test_utils.py | 54 --- 13 files changed, 257 insertions(+), 1736 deletions(-) delete mode 100644 dependency_injector/catalogs/__init__.py delete mode 100644 dependency_injector/catalogs/declarative.py delete mode 100644 dependency_injector/catalogs/dynamic.py delete mode 100644 dependency_injector/catalogs/utils.py delete mode 100644 tests/catalogs/__init__.py delete mode 100644 tests/catalogs/test_declarative.py delete mode 100644 tests/catalogs/test_dynamic.py delete mode 100644 tests/catalogs/test_override.py diff --git a/dependency_injector/catalogs/__init__.py b/dependency_injector/catalogs/__init__.py deleted file mode 100644 index c6e139fd..00000000 --- a/dependency_injector/catalogs/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -"""Dependency injector catalogs package.""" - -from dependency_injector.catalogs.dynamic import DynamicCatalog -from dependency_injector.catalogs.declarative import ( - DeclarativeCatalogMetaClass, - DeclarativeCatalog, -) -from dependency_injector.catalogs.utils import ( - copy, - override -) - - -__all__ = ( - 'DynamicCatalog', - 'DeclarativeCatalogMetaClass', - 'DeclarativeCatalog', - 'copy', - 'override', -) diff --git a/dependency_injector/catalogs/declarative.py b/dependency_injector/catalogs/declarative.py deleted file mode 100644 index bc276efa..00000000 --- a/dependency_injector/catalogs/declarative.py +++ /dev/null @@ -1,440 +0,0 @@ -"""Dependency injector declarative catalog module.""" - -import six - -from dependency_injector.catalogs.dynamic import DynamicCatalog -from dependency_injector.utils import ( - is_provider, - is_catalog, - is_declarative_catalog, -) -from dependency_injector.errors import ( - Error, - UndefinedProviderError, -) - - -@six.python_2_unicode_compatible -class DeclarativeCatalogMetaClass(type): - """Declarative catalog meta class.""" - - def __new__(mcs, class_name, bases, attributes): - """Declarative catalog class factory.""" - cls_providers = tuple((name, provider) - for name, provider in six.iteritems(attributes) - if is_provider(provider)) - - inherited_providers = tuple((name, provider) - for base in bases if is_catalog(base) - for name, provider in six.iteritems( - base.providers)) - - providers = cls_providers + inherited_providers - - cls = type.__new__(mcs, class_name, bases, attributes) - - if cls.provider_type: - cls._catalog = type('DynamicCatalog', - (DynamicCatalog,), - dict(provider_type=cls.provider_type))() - else: - cls._catalog = DynamicCatalog() - - cls._catalog.bind_providers(dict(providers)) - - cls._cls_providers = dict(cls_providers) - cls._inherited_providers = dict(inherited_providers) - - return cls - - @property - def providers(cls): - """Read-only dictionary of all providers. - - :type: dict[str, :py:class:`dependency_injector.providers.Provider`] - """ - return cls._catalog.providers - - @property - def cls_providers(cls): - """Read-only dictionary of current catalog providers. - - :rtype: dict[str, :py:class:`dependency_injector.providers.Provider`] - """ - return cls._cls_providers - - @property - def inherited_providers(cls): - """Read-only dictionary of inherited providers. - - :rtype: dict[str, :py:class:`dependency_injector.providers.Provider`] - """ - return cls._inherited_providers - - @property - def overridden_by(cls): - """Tuple of overriding catalogs. - - :type: tuple[ - :py:class:`DeclarativeCatalog` | - :py:class:`DynamicCatalog`] - """ - return cls._catalog.overridden_by - - @property - def is_overridden(cls): - """Read-only property that is set to ``True`` if catalog is overridden. - - :rtype: bool - """ - return cls._catalog.is_overridden - - @property - def last_overriding(cls): - """Read-only reference to the last overriding catalog, if any. - - :type: :py:class:`DeclarativeCatalog` | :py:class:`DynamicCatalog` | - None - """ - return cls._catalog.last_overriding - - def __getattr__(cls, name): - """Return provider with specified name or raise en error. - - :param name: Attribute's name. - :type name: str - - :raise: :py:exc:`dependency_injector.errors.UndefinedProviderError` - """ - raise UndefinedProviderError('There is no provider "{0}" in ' - 'catalog {1}'.format(name, cls)) - - def __setattr__(cls, name, value): - """Handle setting of catalog attributes. - - Setting of attributes works as usual, but if value of attribute is - provider, this provider will be bound to catalog. - - :param name: Attribute's name. - :type name: str - - :param value: Attribute's value. - :type value: :py:class:`dependency_injector.providers.Provider` | - object - - :rtype: None - """ - if is_provider(value): - cls.bind_provider(name, value, _set_as_attribute=False) - return super(DeclarativeCatalogMetaClass, cls).__setattr__(name, value) - - def __delattr__(cls, name): - """Handle deleting of catalog attibute. - - Deleting of attributes works as usual, but if value of attribute is - provider, this provider will be unbound from catalog. - - :param name: Attribute's name. - :type name: str - - :rtype: None - """ - if is_provider(getattr(cls, name)): - delattr(cls._catalog, name) - return super(DeclarativeCatalogMetaClass, cls).__delattr__(name) - - def __repr__(cls): - """Return string representation of the catalog. - - :rtype: str - """ - return '<{0}({1})>'.format('.'.join((cls.__module__, cls.__name__)), - ', '.join(six.iterkeys(cls.providers))) - - __str__ = __repr__ - - -@six.add_metaclass(DeclarativeCatalogMetaClass) -class DeclarativeCatalog(object): - """Declarative catalog of providers. - - :py:class:`DeclarativeCatalog` is a catalog of providers that could be - defined in declarative manner. It should cover most of the cases when list - of providers that would be included in catalog is deterministic (catalog - will not change its structure in runtime). - - .. code-block:: python - - class Services(DeclarativeCatalog): - - auth = providers.Factory(AuthService) - - users = providers.Factory(UsersService) - - users_service = Services.users() - - .. py:attribute:: provider_type - - If provider type is defined, :py:class:`DeclarativeCatalog` checks that - all of its providers are instances of - :py:attr:`DeclarativeCatalog.provider_type`. - - :type: type | None - """ - - provider_type = None - - _catalog = DynamicCatalog - - _cls_providers = dict() - _inherited_providers = dict() - - __IS_CATALOG__ = True - - @property - def providers(self): - """Read-only dictionary of all providers. - - :rtype: dict[str, :py:class:`dependency_injector.providers.Provider`] - """ - return self.__class__.providers - - @property - def cls_providers(self): - """Read-only dictionary of current catalog providers. - - :rtype: dict[str, :py:class:`dependency_injector.providers.Provider`] - """ - return self.__class__.cls_providers - - @property - def inherited_providers(self): - """Read-only dictionary of inherited providers. - - :rtype: dict[str, :py:class:`dependency_injector.providers.Provider`] - """ - return self.__class__.inherited_providers - - @property - def overridden_by(self): - """Tuple of overriding catalogs. - - :rtype: tuple[:py:class:`DeclarativeCatalog` | - :py:class:`DynamicCatalog`] - """ - return self.__class__.overridden_by - - @property - def is_overridden(self): - """Read-only property that is set to ``True`` if catalog is overridden. - - :rtype: bool - """ - return self.__class__.is_overridden - - @property - def last_overriding(self): - """Read-only reference to the last overriding catalog, if any. - - :rtype: :py:class:`DeclarativeCatalog` | :py:class:`DynamicCatalog` | - None - """ - return self.__class__.last_overriding - - @classmethod - def get_provider_bind_name(cls, provider): - """Return provider's name in catalog. - - :param provider: Provider instance. - :type provider: :py:class:`dependency_injector.providers.Provider` - - :raise: :py:exc:`dependency_injector.errors.UndefinedProviderError` - - :return: Provider's name. - :rtype: str - """ - return cls._catalog.get_provider_bind_name(provider) - - @classmethod - def is_provider_bound(cls, provider): - """Check if provider is bound to the catalog. - - :param provider: Provider instance. - :type provider: :py:class:`dependency_injector.providers.Provider` - - :rtype: bool - """ - return cls._catalog.is_provider_bound(provider) - - @classmethod - def filter(cls, provider_type): - """Return dictionary of providers, that are instance of provided type. - - :param provider_type: Provider's type. - :type provider_type: :py:class:`dependency_injector.providers.Provider` - - :rtype: dict[str, :py:class:`dependency_injector.providers.Provider`] - """ - return cls._catalog.filter(provider_type) - - @classmethod - def override(cls, overriding): - """Override current catalog providers by overriding catalog providers. - - :param overriding: Overriding catalog. - :type overriding: :py:class:`DeclarativeCatalog` | - :py:class:`DynamicCatalog` - - :raise: :py:exc:`dependency_injector.errors.Error` if trying to - override catalog by itself or its subclasses - - :rtype: None - """ - if is_declarative_catalog(overriding) and issubclass(cls, overriding): - raise Error('Catalog {0} could not be overridden ' - 'with itself or its subclasses'.format(cls)) - return cls._catalog.override(overriding) - - @classmethod - def reset_last_overriding(cls): - """Reset last overriding catalog. - - :rtype: None - """ - cls._catalog.reset_last_overriding() - - @classmethod - def reset_override(cls): - """Reset all overridings for all catalog providers. - - :rtype: None - """ - cls._catalog.reset_override() - - @classmethod - def get_provider(cls, name): - """Return provider with specified name or raise an error. - - :param name: Provider's name. - :type name: str - - :raise: :py:exc:`dependency_injector.errors.UndefinedProviderError` - - :return: Provider with specified name. - :rtype: :py:class:`dependency_injector.providers.Provider` - """ - return cls._catalog.get_provider(name) - - @classmethod - def bind_provider(cls, name, provider, force=False, - _set_as_attribute=True): - """Bind provider to catalog with specified name. - - :param name: Name of the provider. - :type name: str - - :param provider: Provider instance. - :type provider: :py:class:`dependency_injector.providers.Provider` - - :param force: Force binding of provider. - :type force: bool - - :raise: :py:exc:`dependency_injector.errors.Error` - - :rtype: None - """ - if cls._catalog.is_provider_bound(provider): - bindind_name = cls._catalog.get_provider_bind_name(provider) - if bindind_name == name and not force: - return - - cls._catalog.bind_provider(name, provider, force) - cls.cls_providers[name] = provider - - if _set_as_attribute: - setattr(cls, name, provider) - - @classmethod - def bind_providers(cls, providers, force=False): - """Bind providers dictionary to catalog. - - :param providers: Dictionary of providers, where key is a name - and value is a provider. - :type providers: - dict[str, :py:class:`dependency_injector.providers.Provider`] - - :param force: Force binding of providers. - :type force: bool - - :raise: :py:exc:`dependency_injector.errors.Error` - - :rtype: None - """ - for name, provider in six.iteritems(providers): - cls.bind_provider(name, provider, force=force) - - @classmethod - def has_provider(cls, name): - """Check if there is provider with certain name. - - :param name: Provider's name. - :type name: str - - :rtype: bool - """ - return hasattr(cls, name) - - @classmethod - def unbind_provider(cls, name): - """Remove provider binding. - - :param name: Provider's name. - :type name: str - - :rtype: None - """ - delattr(cls, name) - del cls.cls_providers[name] - - @classmethod - def __getattr__(cls, name): # pragma: no cover - """Return provider with specified name or raise en error. - - :param name: Attribute's name. - :type name: str - - :raise: :py:exc:`dependency_injector.errors.UndefinedProviderError` - """ - raise NotImplementedError('Implementated in metaclass') - - @classmethod - def __setattr__(cls, name, value): # pragma: no cover - """Handle setting of catalog attributes. - - Setting of attributes works as usual, but if value of attribute is - provider, this provider will be bound to catalog. - - :param name: Attribute's name. - :type name: str - - :param value: Attribute's value. - :type value: :py:class:`dependency_injector.providers.Provider` | - object - - :rtype: None - """ - raise NotImplementedError('Implementated in metaclass') - - @classmethod - def __delattr__(cls, name): # pragma: no cover - """Handle deleting of catalog attibute. - - Deleting of attributes works as usual, but if value of attribute is - provider, this provider will be unbound from catalog. - - :param name: Attribute's name. - :type name: str - - :rtype: None - """ - raise NotImplementedError('Implementated in metaclass') diff --git a/dependency_injector/catalogs/dynamic.py b/dependency_injector/catalogs/dynamic.py deleted file mode 100644 index f943b044..00000000 --- a/dependency_injector/catalogs/dynamic.py +++ /dev/null @@ -1,309 +0,0 @@ -"""Dependency injector dynamic catalog module.""" - -import six - -from dependency_injector.utils import ( - is_provider, - ensure_is_provider, -) -from dependency_injector.errors import ( - Error, - UndefinedProviderError, -) - - -@six.python_2_unicode_compatible -class DynamicCatalog(object): - """Dynamic catalog of providers. - - :py:class:`DynamicCatalog` is a catalog of providers that could be created - in application's runtime. It should cover most of the cases when list of - providers that would be included in catalog is non-deterministic in terms - of apllication code (catalog's structure could be determined just after - application will be started and will do some initial work, like parsing - list of catalog's providers from the configuration). - - .. code-block:: python - - services = DynamicCatalog(auth=providers.Factory(AuthService), - users=providers.Factory(UsersService)) - - users_service = services.users() - - .. py:attribute:: providers - - Dictionary of all providers. - - :type: dict[str, :py:class:`dependency_injector.providers.Provider`] - - .. py:attribute:: overridden_by - - Tuple of overriding catalogs. - - :type: tuple[ - :py:class:`DeclarativeCatalog` | :py:class:`DynamicCatalog`] - - .. py:attribute:: provider_type - - If provider type is defined, :py:class:`DynamicCatalog` checks that - all of its providers are instances of - :py:attr:`DynamicCatalog.provider_type`. - - :type: type | None - """ - - provider_type = None - - __IS_CATALOG__ = True - __slots__ = ('providers', 'provider_names', 'overridden_by') - - def __init__(self, **providers): - """Initializer. - - :param providers: Dictionary of catalog providers. - :type providers: - dict[str, :py:class:`dependency_injector.providers.Provider`] - """ - self.providers = dict() - self.provider_names = dict() - self.overridden_by = tuple() - self.bind_providers(providers) - super(DynamicCatalog, self).__init__() - - def get_provider_bind_name(self, provider): - """Return provider's name in catalog. - - :param provider: Provider instance. - :type provider: :py:class:`dependency_injector.providers.Provider` - - :raise: :py:exc:`dependency_injector.errors.UndefinedProviderError` - - :return: Provider's name. - :rtype: str - """ - if not self.is_provider_bound(provider): - raise Error('Can not find bind name for {0} in catalog {1}'.format( - provider, self)) - return self.provider_names[provider] - - def is_provider_bound(self, provider): - """Check if provider is bound to the catalog. - - :param provider: Provider instance. - :type provider: :py:class:`dependency_injector.providers.Provider` - - :rtype: bool - """ - return provider in self.provider_names - - def filter(self, provider_type): - """Return dictionary of providers, that are instance of provided type. - - :param provider_type: Provider's type. - :type provider_type: :py:class:`dependency_injector.providers.Provider` - - :rtype: dict[str, :py:class:`dependency_injector.providers.Provider`] - """ - return dict((name, provider) - for name, provider in six.iteritems(self.providers) - if isinstance(provider, provider_type)) - - @property - def is_overridden(self): - """Read-only property that is set to ``True`` if catalog is overridden. - - :rtype: bool - """ - return bool(self.overridden_by) - - @property - def last_overriding(self): - """Read-only reference to the last overriding catalog, if any. - - :type: :py:class:`DeclarativeCatalog` | :py:class:`DynamicCatalog` | - None - """ - return self.overridden_by[-1] if self.overridden_by else None - - def override(self, overriding): - """Override current catalog providers by overriding catalog providers. - - :param overriding: Overriding catalog. - :type overriding: :py:class:`DeclarativeCatalog` | - :py:class:`DynamicCatalog` - - :raise: :py:exc:`dependency_injector.errors.Error` if trying to - override catalog by itself - - :rtype: None - """ - if overriding is self: - raise Error('Catalog {0} could not be overridden ' - 'with itself'.format(self)) - self.overridden_by += (overriding,) - for name, provider in six.iteritems(overriding.providers): - self.get_provider(name).override(provider) - - def reset_last_overriding(self): - """Reset last overriding catalog. - - :rtype: None - """ - if not self.is_overridden: - raise Error('Catalog {0} is not overridden'.format(self)) - self.overridden_by = self.overridden_by[:-1] - for provider in six.itervalues(self.providers): - provider.reset_last_overriding() - - def reset_override(self): - """Reset all overridings for all catalog providers. - - :rtype: None - """ - self.overridden_by = tuple() - for provider in six.itervalues(self.providers): - provider.reset_override() - - def get_provider(self, name): - """Return provider with specified name or raise an error. - - :param name: Provider's name. - :type name: str - - :raise: :py:exc:`dependency_injector.errors.UndefinedProviderError` - - :return: Provider with specified name. - :rtype: :py:class:`dependency_injector.providers.Provider` - """ - try: - return self.providers[name] - except KeyError: - raise UndefinedProviderError('{0} has no provider with such ' - 'name - {1}'.format(self, name)) - - def bind_provider(self, name, provider, force=False): - """Bind provider to catalog with specified name. - - :param name: Name of the provider. - :type name: str - - :param provider: Provider instance. - :type provider: :py:class:`dependency_injector.providers.Provider` - - :param force: Force binding of provider. - :type force: bool - - :raise: :py:exc:`dependency_injector.errors.Error` - - :rtype: None - """ - provider = ensure_is_provider(provider) - - if (self.__class__.provider_type and - not isinstance(provider, self.__class__.provider_type)): - raise Error('{0} can contain only {1} instances'.format( - self, self.__class__.provider_type)) - - if not force: - if name in self.providers: - raise Error('Catalog {0} already has provider with ' - 'such name - {1}'.format(self, name)) - if provider in self.provider_names: - raise Error('Catalog {0} already has such provider ' - 'instance - {1}'.format(self, provider)) - - self.providers[name] = provider - self.provider_names[provider] = name - - def bind_providers(self, providers, force=False): - """Bind providers dictionary to catalog. - - :param providers: Dictionary of providers, where key is a name - and value is a provider. - :type providers: - dict[str, :py:class:`dependency_injector.providers.Provider`] - - :param force: Force binding of providers. - :type force: bool - - :raise: :py:exc:`dependency_injector.errors.Error` - - :rtype: None - """ - for name, provider in six.iteritems(providers): - self.bind_provider(name, provider, force) - - def has_provider(self, name): - """Check if there is provider with certain name. - - :param name: Provider's name. - :type name: str - - :rtype: bool - """ - return name in self.providers - - def unbind_provider(self, name): - """Remove provider binding. - - :param name: Provider's name. - :type name: str - - :rtype: None - """ - provider = self.get_provider(name) - del self.providers[name] - del self.provider_names[provider] - - def __getattr__(self, name): - """Return provider with specified name or raise en error. - - :param name: Attribute's name. - :type name: str - - :raise: :py:exc:`dependency_injector.errors.UndefinedProviderError` - """ - return self.get_provider(name) - - def __setattr__(self, name, value): - """Handle setting of catalog attributes. - - Setting of attributes works as usual, but if value of attribute is - provider, this provider will be bound to catalog. - - :param name: Attribute's name. - :type name: str - - :param value: Attribute's value. - :type value: :py:class:`dependency_injector.providers.Provider` | - object - - :rtype: None - """ - if is_provider(value): - return self.bind_provider(name, value) - return super(DynamicCatalog, self).__setattr__(name, value) - - def __delattr__(self, name): - """Handle deleting of catalog attibute. - - Deleting of attributes works as usual, but if value of attribute is - provider, this provider will be unbound from catalog. - - :param name: Attribute's name. - :type name: str - - :rtype: None - """ - self.unbind_provider(name) - - def __repr__(self): - """Return Python representation of catalog. - - :rtype: str - """ - return '<{0}({1})>'.format('.'.join((self.__class__.__module__, - self.__class__.__name__)), - ', '.join(six.iterkeys(self.providers))) - - __str__ = __repr__ diff --git a/dependency_injector/catalogs/utils.py b/dependency_injector/catalogs/utils.py deleted file mode 100644 index 6eb264cf..00000000 --- a/dependency_injector/catalogs/utils.py +++ /dev/null @@ -1,62 +0,0 @@ -"""Dependency injector catalog utils.""" - -import six - -from dependency_injector.utils import _copy_providers -from dependency_injector.errors import UndefinedProviderError - - -def copy(catalog): - """:py:class:`DeclarativeCatalog` copying decorator. - - This decorator copy all providers from provided catalog to decorated one. - If one of the decorated catalog providers matches to source catalog - providers by name, it would be replaced by reference. - - :param catalog: Catalog that should be copied by decorated catalog. - :type catalog: :py:class:`DeclarativeCatalog` - - :return: Declarative catalog's copying decorator. - :rtype: - callable(:py:class:`DeclarativeCatalog`) - """ - def decorator(copied_catalog): - """Copying decorator. - - :param copied_catalog: Decorated catalog. - :type copied_catalog: :py:class:`DeclarativeCatalog` - - :return: Decorated catalog. - :rtype: - :py:class:`DeclarativeCatalog` - """ - memo = dict() - for name, provider in six.iteritems(copied_catalog.cls_providers): - try: - source_provider = catalog.get_provider(name) - except UndefinedProviderError: - pass - else: - memo[id(source_provider)] = provider - - copied_catalog.bind_providers(_copy_providers(catalog.providers, memo), - force=True) - - return copied_catalog - return decorator - - -def override(catalog): - """:py:class:`DeclarativeCatalog` overriding decorator. - - :param catalog: Catalog that should be overridden by decorated catalog. - :type catalog: :py:class:`DeclarativeCatalog` - - :return: Declarative catalog's overriding decorator. - :rtype: callable(:py:class:`DeclarativeCatalog`) - """ - def decorator(overriding_catalog): - """Overriding decorator.""" - catalog.override(overriding_catalog) - return overriding_catalog - return decorator diff --git a/dependency_injector/containers.py b/dependency_injector/containers.py index 8e36f3ba..da794a88 100644 --- a/dependency_injector/containers.py +++ b/dependency_injector/containers.py @@ -9,6 +9,86 @@ from dependency_injector import ( ) +class DynamicContainer(object): + """Dynamic inversion of control container.""" + + __IS_CONTAINER__ = True + + def __init__(self): + """Initializer.""" + self.provider_type = providers.Provider + self.providers = dict() + self.overridden_by = tuple() + super(DynamicContainer, self).__init__() + + def __setattr__(self, name, value): + """Set instance attribute. + + If value of attribute is provider, it will be added into providers + dictionary. + """ + if utils.is_provider(value): + _check_provider_type(self, value) + self.providers[name] = value + super(DynamicContainer, self).__setattr__(name, value) + + def __delattr__(self, name): + """Delete instance attribute. + + If value of attribute is provider, it will be deleted from providers + dictionary. + """ + if name in self.providers: + del self.providers[name] + super(DynamicContainer, self).__delattr__(name) + + def override(self, 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 overriding is self: + raise errors.Error('Container {0} could not be overridden ' + 'with itself'.format(self)) + + self.overridden_by += (overriding,) + + for name, provider in six.iteritems(overriding.providers): + try: + getattr(self, name).override(provider) + except AttributeError: + pass + + def reset_last_overriding(self): + """Reset last overriding provider for each container providers. + + :rtype: None + """ + if not self.overridden_by: + raise errors.Error('Container {0} is not overridden'.format(self)) + + self.overridden_by = self.overridden_by[:-1] + + for provider in six.itervalues(self.providers): + provider.reset_last_overriding() + + def reset_override(self): + """Reset all overridings for each container providers. + + :rtype: None + """ + self.overridden_by = tuple() + + for provider in six.itervalues(self.providers): + provider.reset_override() + + class DeclarativeContainerMetaClass(type): """Declarative inversion of control container meta class.""" @@ -20,7 +100,7 @@ class DeclarativeContainerMetaClass(type): inherited_providers = tuple((name, provider) for base in bases if utils.is_container( - base) + base) and base is not DynamicContainer for name, provider in six.iteritems( base.cls_providers)) @@ -70,9 +150,20 @@ class DeclarativeContainer(object): providers = dict() cls_providers = dict() inherited_providers = dict() - overridden_by = tuple() + instance_type = DynamicContainer + + def __new__(cls, *args, **kwargs): + """Constructor.""" + container = cls.instance_type(*args, **kwargs) + container.provider_type = cls.provider_type + + for name, provider in six.iteritems(utils.deepcopy(cls.providers)): + setattr(container, name, provider) + + return container + @classmethod def override(cls, overriding): """Override current container by overriding container. @@ -163,7 +254,7 @@ def copy(container): else: memo[id(source_provider)] = provider - providers_copy = utils._copy_providers(container.providers, memo) + providers_copy = utils.deepcopy(container.providers, memo) for name, provider in six.iteritems(providers_copy): setattr(copied_container, name, provider) diff --git a/dependency_injector/utils.py b/dependency_injector/utils.py index f1354a14..0b596b49 100644 --- a/dependency_injector/utils.py +++ b/dependency_injector/utils.py @@ -1,7 +1,7 @@ """Utils module.""" import sys -import copy +import copy as _copy import types import threading @@ -23,9 +23,9 @@ else: # pragma: no cover _OBJECT_INIT = None if six.PY2: # pragma: no cover - copy._deepcopy_dispatch[types.MethodType] = \ + _copy._deepcopy_dispatch[types.MethodType] = \ lambda obj, memo: type(obj)(obj.im_func, - copy.deepcopy(obj.im_self, memo), + _copy.deepcopy(obj.im_self, memo), obj.im_class) @@ -71,40 +71,6 @@ def is_container(instance): getattr(instance, '__IS_CONTAINER__', False) is True) -def is_catalog(instance): - """Check if instance is catalog instance. - - :param instance: Instance to be checked. - :type instance: object - - :rtype: bool - """ - return (hasattr(instance, '__IS_CATALOG__') and - getattr(instance, '__IS_CATALOG__', False) is True) - - -def is_dynamic_catalog(instance): - """Check if instance is dynamic catalog instance. - - :param instance: Instance to be checked. - :type instance: object - - :rtype: bool - """ - return (not isinstance(instance, six.class_types) and is_catalog(instance)) - - -def is_declarative_catalog(instance): - """Check if instance is declarative catalog instance. - - :param instance: Instance to be checked. - :type instance: object - - :rtype: bool - """ - return (isinstance(instance, six.class_types) and is_catalog(instance)) - - def represent_provider(provider, provides): """Return string representation of provider. @@ -142,6 +108,6 @@ def fetch_cls_init(cls): return cls_init -def _copy_providers(providers, memo=None): - """Make full copy of providers dictionary.""" - return copy.deepcopy(providers, memo) +def deepcopy(instance, memo=None): + """Make full copy of instance.""" + return _copy.deepcopy(instance, memo) diff --git a/setup.py b/setup.py index 19e8f3b2..b530f507 100644 --- a/setup.py +++ b/setup.py @@ -54,7 +54,6 @@ setup(name='dependency_injector', download_url='https://pypi.python.org/pypi/dependency_injector', license='BSD New', packages=['dependency_injector', - 'dependency_injector.catalogs', 'dependency_injector.providers'], platforms=['any'], zip_safe=True, diff --git a/tests/catalogs/__init__.py b/tests/catalogs/__init__.py deleted file mode 100644 index 1e758990..00000000 --- a/tests/catalogs/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Dependency injector catalogs unittests.""" diff --git a/tests/catalogs/test_declarative.py b/tests/catalogs/test_declarative.py deleted file mode 100644 index a35b9bc5..00000000 --- a/tests/catalogs/test_declarative.py +++ /dev/null @@ -1,443 +0,0 @@ -"""Dependency injector declarative catalog unittests.""" - -import unittest2 as unittest - -from dependency_injector import ( - catalogs, - providers, - injections, - errors, -) - - -class CatalogA(catalogs.DeclarativeCatalog): - """Test catalog A.""" - - p11 = providers.Provider() - p12 = providers.Provider() - - -class CatalogB(CatalogA): - """Test catalog B.""" - - p21 = providers.Provider() - p22 = providers.Provider() - - -class DeclarativeCatalogTests(unittest.TestCase): - """Declarative catalog tests.""" - - def test_cls_providers(self): - """Test `di.DeclarativeCatalog.cls_providers` contents.""" - class CatalogA(catalogs.DeclarativeCatalog): - """Test catalog A.""" - - p11 = providers.Provider() - p12 = providers.Provider() - - class CatalogB(CatalogA): - """Test catalog B.""" - - p21 = providers.Provider() - p22 = providers.Provider() - self.assertDictEqual(CatalogA.cls_providers, - dict(p11=CatalogA.p11, - p12=CatalogA.p12)) - self.assertDictEqual(CatalogB.cls_providers, - dict(p21=CatalogB.p21, - p22=CatalogB.p22)) - - def test_inherited_providers(self): - """Test `di.DeclarativeCatalog.inherited_providers` contents.""" - self.assertDictEqual(CatalogA.inherited_providers, dict()) - self.assertDictEqual(CatalogB.inherited_providers, - dict(p11=CatalogA.p11, - p12=CatalogA.p12)) - - def test_providers(self): - """Test `di.DeclarativeCatalog.inherited_providers` contents.""" - self.assertDictEqual(CatalogA.providers, - dict(p11=CatalogA.p11, - p12=CatalogA.p12)) - self.assertDictEqual(CatalogB.providers, - dict(p11=CatalogA.p11, - p12=CatalogA.p12, - p21=CatalogB.p21, - p22=CatalogB.p22)) - - def test_bind_provider(self): - """Test setting of provider via bind_provider() to catalog.""" - px = providers.Provider() - py = providers.Provider() - - CatalogA.bind_provider('px', px) - CatalogA.bind_provider('py', py) - - self.assertIs(CatalogA.px, px) - self.assertIs(CatalogA.get_provider('px'), px) - - self.assertIs(CatalogA.py, py) - self.assertIs(CatalogA.get_provider('py'), py) - - del CatalogA.px - del CatalogA.py - - def test_bind_existing_provider(self): - """Test setting of provider via bind_provider() to catalog.""" - with self.assertRaises(errors.Error): - CatalogA.p11 = providers.Provider() - - with self.assertRaises(errors.Error): - CatalogA.bind_provider('p11', providers.Provider()) - - def test_bind_provider_with_valid_provided_type(self): - """Test setting of provider with provider type restriction.""" - class SomeProvider(providers.Provider): - """Some provider.""" - - class SomeCatalog(catalogs.DeclarativeCatalog): - """Some catalog with provider type restriction.""" - - provider_type = SomeProvider - - px = SomeProvider() - py = SomeProvider() - - SomeCatalog.bind_provider('px', px) - SomeCatalog.py = py - - self.assertIs(SomeCatalog.px, px) - self.assertIs(SomeCatalog.get_provider('px'), px) - - self.assertIs(SomeCatalog.py, py) - self.assertIs(SomeCatalog.get_provider('py'), py) - - def test_bind_provider_with_invalid_provided_type(self): - """Test setting of provider with provider type restriction.""" - class SomeProvider(providers.Provider): - """Some provider.""" - - class SomeCatalog(catalogs.DeclarativeCatalog): - """Some catalog with provider type restriction.""" - - provider_type = SomeProvider - - px = providers.Provider() - - with self.assertRaises(errors.Error): - SomeCatalog.bind_provider('px', px) - - with self.assertRaises(errors.Error): - SomeCatalog.px = px - - with self.assertRaises(errors.Error): - SomeCatalog.bind_providers(dict(px=px)) - - def test_bind_providers(self): - """Test setting of provider via bind_providers() to catalog.""" - px = providers.Provider() - py = providers.Provider() - - CatalogB.bind_providers(dict(px=px, py=py)) - - self.assertIs(CatalogB.px, px) - self.assertIs(CatalogB.get_provider('px'), px) - - self.assertIs(CatalogB.py, py) - self.assertIs(CatalogB.get_provider('py'), py) - - del CatalogB.px - del CatalogB.py - - def test_setattr(self): - """Test setting of providers via attributes to catalog.""" - px = providers.Provider() - py = providers.Provider() - - CatalogB.px = px - CatalogB.py = py - - self.assertIs(CatalogB.px, px) - self.assertIs(CatalogB.get_provider('px'), px) - - self.assertIs(CatalogB.py, py) - self.assertIs(CatalogB.get_provider('py'), py) - - del CatalogB.px - del CatalogB.py - - def test_unbind_provider(self): - """Test that catalog unbinds provider correct.""" - CatalogB.px = providers.Provider() - CatalogB.unbind_provider('px') - self.assertFalse(CatalogB.has_provider('px')) - - def test_unbind_via_delattr(self): - """Test that catalog unbinds provider correct.""" - CatalogB.px = providers.Provider() - del CatalogB.px - self.assertFalse(CatalogB.has_provider('px')) - - def test_provider_is_bound(self): - """Test that providers are bound to the catalogs.""" - self.assertTrue(CatalogA.is_provider_bound(CatalogA.p11)) - self.assertEquals(CatalogA.get_provider_bind_name(CatalogA.p11), 'p11') - - self.assertTrue(CatalogA.is_provider_bound(CatalogA.p12)) - self.assertEquals(CatalogA.get_provider_bind_name(CatalogA.p12), 'p12') - - def test_provider_binding_to_different_catalogs(self): - """Test that provider could be bound to different catalogs.""" - p11 = CatalogA.p11 - p12 = CatalogA.p12 - - class CatalogD(catalogs.DeclarativeCatalog): - """Test catalog.""" - - pd1 = p11 - pd2 = p12 - - class CatalogE(catalogs.DeclarativeCatalog): - """Test catalog.""" - - pe1 = p11 - pe2 = p12 - - self.assertTrue(CatalogA.is_provider_bound(p11)) - self.assertTrue(CatalogD.is_provider_bound(p11)) - self.assertTrue(CatalogE.is_provider_bound(p11)) - self.assertEquals(CatalogA.get_provider_bind_name(p11), 'p11') - self.assertEquals(CatalogD.get_provider_bind_name(p11), 'pd1') - self.assertEquals(CatalogE.get_provider_bind_name(p11), 'pe1') - - self.assertTrue(CatalogA.is_provider_bound(p12)) - self.assertTrue(CatalogD.is_provider_bound(p12)) - self.assertTrue(CatalogE.is_provider_bound(p12)) - self.assertEquals(CatalogA.get_provider_bind_name(p12), 'p12') - self.assertEquals(CatalogD.get_provider_bind_name(p12), 'pd2') - self.assertEquals(CatalogE.get_provider_bind_name(p12), 'pe2') - - def test_provider_rebinding_to_the_same_catalog(self): - """Test provider rebinding to the same catalog.""" - with self.assertRaises(errors.Error): - class TestCatalog(catalogs.DeclarativeCatalog): - """Test catalog.""" - - p1 = providers.Provider() - p2 = p1 - - def test_provider_rebinding_to_the_same_catalogs_hierarchy(self): - """Test provider rebinding to the same catalogs hierarchy.""" - class TestCatalog1(catalogs.DeclarativeCatalog): - """Test catalog.""" - - p1 = providers.Provider() - - with self.assertRaises(errors.Error): - class TestCatalog2(TestCatalog1): - """Test catalog.""" - - p2 = TestCatalog1.p1 - - def test_get(self): - """Test getting of providers using get() method.""" - self.assertIs(CatalogB.get_provider('p11'), CatalogB.p11) - self.assertIs(CatalogB.get_provider('p12'), CatalogB.p12) - self.assertIs(CatalogB.get_provider('p22'), CatalogB.p22) - self.assertIs(CatalogB.get_provider('p22'), CatalogB.p22) - - def test_get_undefined(self): - """Test getting of undefined providers using get() method.""" - with self.assertRaises(errors.UndefinedProviderError): - CatalogB.get('undefined') - - with self.assertRaises(errors.UndefinedProviderError): - CatalogB.get_provider('undefined') - - with self.assertRaises(errors.UndefinedProviderError): - CatalogB.undefined - - def test_has(self): - """Test checks of providers availability in catalog.""" - self.assertTrue(CatalogB.has_provider('p11')) - self.assertTrue(CatalogB.has_provider('p12')) - self.assertTrue(CatalogB.has_provider('p21')) - self.assertTrue(CatalogB.has_provider('p22')) - self.assertFalse(CatalogB.has_provider('undefined')) - - def test_filter_all_providers_by_type(self): - """Test getting of all catalog providers of specific type.""" - self.assertTrue(len(CatalogB.filter(providers.Provider)) == 4) - self.assertTrue(len(CatalogB.filter(providers.Object)) == 0) - - def test_repr(self): - """Test catalog representation.""" - self.assertIn('CatalogA', repr(CatalogA)) - self.assertIn('p11', repr(CatalogA)) - self.assertIn('p12', repr(CatalogA)) - - self.assertIn('CatalogB', repr(CatalogB)) - self.assertIn('p11', repr(CatalogB)) - self.assertIn('p12', repr(CatalogB)) - self.assertIn('p21', repr(CatalogB)) - self.assertIn('p22', repr(CatalogB)) - - -class TestCatalogWithProvidingCallbacks(unittest.TestCase): - """Catalog with providing callback tests.""" - - def test_concept(self): - """Test concept.""" - class UsersService(object): - """Users service, that has dependency on database.""" - - class AuthService(object): - """Auth service, that has dependencies on users service.""" - - def __init__(self, users_service): - """Initializer.""" - self.users_service = users_service - - class Services(catalogs.DeclarativeCatalog): - """Catalog of service providers.""" - - @providers.Factory - def users(): - """Provide users service. - - :rtype: providers.Provider -> UsersService - """ - return UsersService() - - @providers.Factory - @injections.inject(users_service=users) - def auth(**kwargs): - """Provide users service. - - :rtype: providers.Provider -> AuthService - """ - return AuthService(**kwargs) - - # Retrieving catalog providers: - users_service = Services.users() - auth_service = Services.auth() - - # Making some asserts: - self.assertIsInstance(auth_service.users_service, UsersService) - self.assertIsNot(users_service, Services.users()) - self.assertIsNot(auth_service, Services.auth()) - - # Overriding auth service provider and making some asserts: - class ExtendedAuthService(AuthService): - """Extended version of auth service.""" - - def __init__(self, users_service, ttl): - """Initializer.""" - self.ttl = ttl - super(ExtendedAuthService, self).__init__( - users_service=users_service) - - class OverriddenServices(Services): - """Catalog of service providers.""" - - @providers.override(Services.auth) - @providers.Factory - @injections.inject(users_service=Services.users) - @injections.inject(ttl=3600) - def auth(**kwargs): - """Provide users service. - - :rtype: providers.Provider -> AuthService - """ - return ExtendedAuthService(**kwargs) - - auth_service = Services.auth() - - self.assertIsInstance(auth_service, ExtendedAuthService) - - -class CopyingTests(unittest.TestCase): - """Declarative catalogs copying tests.""" - - def test_copy(self): - """Test catalog providers copying.""" - @catalogs.copy(CatalogA) - class CatalogA1(CatalogA): - pass - - @catalogs.copy(CatalogA) - class CatalogA2(CatalogA): - pass - - self.assertIsNot(CatalogA.p11, CatalogA1.p11) - self.assertIsNot(CatalogA.p12, CatalogA1.p12) - - self.assertIsNot(CatalogA.p11, CatalogA2.p11) - self.assertIsNot(CatalogA.p12, CatalogA2.p12) - - self.assertIsNot(CatalogA1.p11, CatalogA2.p11) - self.assertIsNot(CatalogA1.p12, CatalogA2.p12) - - def test_copy_with_replacing(self): - """Test catalog providers copying.""" - class CatalogA(catalogs.DeclarativeCatalog): - p11 = providers.Object(0) - p12 = providers.Factory(dict) \ - .add_kwargs(p11=p11) - - @catalogs.copy(CatalogA) - class CatalogA1(CatalogA): - p11 = providers.Object(1) - p13 = providers.Object(11) - - @catalogs.copy(CatalogA) - class CatalogA2(CatalogA): - p11 = providers.Object(2) - p13 = providers.Object(22) - - self.assertIsNot(CatalogA.p11, CatalogA1.p11) - self.assertIsNot(CatalogA.p12, CatalogA1.p12) - - self.assertIsNot(CatalogA.p11, CatalogA2.p11) - self.assertIsNot(CatalogA.p12, CatalogA2.p12) - - self.assertIsNot(CatalogA1.p11, CatalogA2.p11) - self.assertIsNot(CatalogA1.p12, CatalogA2.p12) - - self.assertIs(CatalogA.p12.kwargs['p11'], CatalogA.p11) - self.assertIs(CatalogA1.p12.kwargs['p11'], CatalogA1.p11) - self.assertIs(CatalogA2.p12.kwargs['p11'], CatalogA2.p11) - - self.assertEqual(CatalogA.p12(), dict(p11=0)) - self.assertEqual(CatalogA1.p12(), dict(p11=1)) - self.assertEqual(CatalogA2.p12(), dict(p11=2)) - - self.assertEqual(CatalogA1.p13(), 11) - self.assertEqual(CatalogA2.p13(), 22) - - -class InstantiationTests(unittest.TestCase): - """Declarative catalogs instantiation tests.""" - - def setUp(self): - """Set test environment up.""" - self.catalog = CatalogA() - - def tearDown(self): - """Tear test environment down.""" - self.catalog = None - - def test_access_instance_attributes(self): - """Test accessing declarative catalog instance attributes.""" - self.assertEqual(self.catalog.providers, - CatalogA.providers) - self.assertEqual(self.catalog.cls_providers, - CatalogA.cls_providers) - self.assertEqual(self.catalog.inherited_providers, - CatalogA.inherited_providers) - self.assertEqual(self.catalog.overridden_by, - CatalogA.overridden_by) - self.assertEqual(self.catalog.is_overridden, - CatalogA.is_overridden) - self.assertEqual(self.catalog.last_overriding, - CatalogA.last_overriding) diff --git a/tests/catalogs/test_dynamic.py b/tests/catalogs/test_dynamic.py deleted file mode 100644 index 6bf40451..00000000 --- a/tests/catalogs/test_dynamic.py +++ /dev/null @@ -1,221 +0,0 @@ -"""Dependency injector dynamic catalog unittests.""" - -import unittest2 as unittest - -from dependency_injector import ( - catalogs, - providers, - errors, -) - - -class DynamicCatalogTests(unittest.TestCase): - """Dynamic catalog tests.""" - - catalog = None - """:type: di.DynamicCatalog""" - - def setUp(self): - """Set test environment up.""" - self.catalog = catalogs.DynamicCatalog(p1=providers.Provider(), - p2=providers.Provider()) - - def test_providers(self): - """Test `di.DeclarativeCatalog.inherited_providers` contents.""" - self.assertDictEqual(self.catalog.providers, - dict(p1=self.catalog.p1, - p2=self.catalog.p2)) - - def test_bind_provider(self): - """Test setting of provider via bind_provider() to catalog.""" - px = providers.Provider() - py = providers.Provider() - - self.catalog.bind_provider('px', px) - self.catalog.bind_provider('py', py) - - self.assertIs(self.catalog.px, px) - self.assertIs(self.catalog.get_provider('px'), px) - - self.assertIs(self.catalog.py, py) - self.assertIs(self.catalog.get_provider('py'), py) - - def test_bind_existing_provider(self): - """Test setting of provider via bind_provider() to catalog.""" - with self.assertRaises(errors.Error): - self.catalog.bind_provider('p1', providers.Factory(object)) - - def test_force_bind_existing_provider(self): - """Test setting of provider via bind_provider() to catalog.""" - p1 = providers.Factory(object) - self.catalog.bind_provider('p1', p1, force=True) - self.assertIs(self.catalog.p1, p1) - - def test_bind_provider_with_valid_provided_type(self): - """Test setting of provider with provider type restriction.""" - class SomeProvider(providers.Provider): - """Some provider.""" - - class SomeCatalog(catalogs.DynamicCatalog): - """Some catalog with provider type restriction.""" - - provider_type = SomeProvider - - px = SomeProvider() - py = SomeProvider() - catalog = SomeCatalog() - - catalog.bind_provider('px', px) - catalog.py = py - - self.assertIs(catalog.px, px) - self.assertIs(catalog.get_provider('px'), px) - - self.assertIs(catalog.py, py) - self.assertIs(catalog.get_provider('py'), py) - - def test_bind_provider_with_invalid_provided_type(self): - """Test setting of provider with provider type restriction.""" - class SomeProvider(providers.Provider): - """Some provider.""" - - class SomeCatalog(catalogs.DynamicCatalog): - """Some catalog with provider type restriction.""" - - provider_type = SomeProvider - - px = providers.Provider() - catalog = SomeCatalog() - - with self.assertRaises(errors.Error): - catalog.bind_provider('px', px) - - with self.assertRaises(errors.Error): - catalog.px = px - - with self.assertRaises(errors.Error): - catalog.bind_providers(dict(px=px)) - - def test_bind_providers(self): - """Test setting of provider via bind_providers() to catalog.""" - px = providers.Provider() - py = providers.Provider() - - self.catalog.bind_providers(dict(px=px, py=py)) - - self.assertIs(self.catalog.px, px) - self.assertIs(self.catalog.get_provider('px'), px) - - self.assertIs(self.catalog.py, py) - self.assertIs(self.catalog.get_provider('py'), py) - - def test_bind_providers_with_existing(self): - """Test setting of provider via bind_providers() to catalog.""" - with self.assertRaises(errors.Error): - self.catalog.bind_providers(dict(p1=providers.Factory(object))) - - def test_bind_providers_force(self): - """Test setting of provider via bind_providers() to catalog.""" - p1 = providers.Factory(object) - self.catalog.bind_providers(dict(p1=p1), force=True) - self.assertIs(self.catalog.p1, p1) - - def test_setattr(self): - """Test setting of providers via attributes to catalog.""" - px = providers.Provider() - py = providers.Provider() - - self.catalog.px = px - self.catalog.py = py - - self.assertIs(self.catalog.px, px) - self.assertIs(self.catalog.get_provider('px'), px) - - self.assertIs(self.catalog.py, py) - self.assertIs(self.catalog.get_provider('py'), py) - - def test_unbind_provider(self): - """Test that catalog unbinds provider correct.""" - self.catalog.px = providers.Provider() - self.catalog.unbind_provider('px') - self.assertFalse(self.catalog.has_provider('px')) - - def test_unbind_via_delattr(self): - """Test that catalog unbinds provider correct.""" - self.catalog.px = providers.Provider() - del self.catalog.px - self.assertFalse(self.catalog.has_provider('px')) - - def test_provider_is_bound(self): - """Test that providers are bound to the catalogs.""" - self.assertTrue(self.catalog.is_provider_bound(self.catalog.p1)) - self.assertEquals( - self.catalog.get_provider_bind_name(self.catalog.p1), 'p1') - self.assertTrue(self.catalog.is_provider_bound(self.catalog.p2)) - self.assertEquals( - self.catalog.get_provider_bind_name(self.catalog.p2), 'p2') - - def test_provider_binding_to_different_catalogs(self): - """Test that provider could be bound to different catalogs.""" - p1 = self.catalog.p1 - p2 = self.catalog.p2 - - catalog_a = catalogs.DynamicCatalog(pa1=p1, pa2=p2) - catalog_b = catalogs.DynamicCatalog(pb1=p1, pb2=p2) - - self.assertTrue(self.catalog.is_provider_bound(p1)) - self.assertTrue(catalog_a.is_provider_bound(p1)) - self.assertTrue(catalog_b.is_provider_bound(p1)) - self.assertEquals(self.catalog.get_provider_bind_name(p1), 'p1') - self.assertEquals(catalog_a.get_provider_bind_name(p1), 'pa1') - self.assertEquals(catalog_b.get_provider_bind_name(p1), 'pb1') - - self.assertTrue(self.catalog.is_provider_bound(p2)) - self.assertTrue(catalog_a.is_provider_bound(p2)) - self.assertTrue(catalog_b.is_provider_bound(p2)) - self.assertEquals(self.catalog.get_provider_bind_name(p2), 'p2') - self.assertEquals(catalog_a.get_provider_bind_name(p2), 'pa2') - self.assertEquals(catalog_b.get_provider_bind_name(p2), 'pb2') - - def test_provider_rebinding_to_the_same_catalog(self): - """Test provider rebinding to the same catalog.""" - with self.assertRaises(errors.Error): - self.catalog.p3 = self.catalog.p1 - - def test_provider_binding_with_the_same_name(self): - """Test binding of provider with the same name.""" - with self.assertRaises(errors.Error): - self.catalog.bind_provider('p1', providers.Provider()) - - def test_get(self): - """Test getting of providers using get() method.""" - self.assertIs(self.catalog.get_provider('p1'), self.catalog.p1) - self.assertIs(self.catalog.get_provider('p2'), self.catalog.p2) - - def test_get_undefined(self): - """Test getting of undefined providers using get() method.""" - with self.assertRaises(errors.UndefinedProviderError): - self.catalog.get_provider('undefined') - - with self.assertRaises(errors.UndefinedProviderError): - self.catalog.undefined - - def test_has_provider(self): - """Test checks of providers availability in catalog.""" - self.assertTrue(self.catalog.has_provider('p1')) - self.assertTrue(self.catalog.has_provider('p2')) - self.assertFalse(self.catalog.has_provider('undefined')) - - def test_filter_all_providers_by_type(self): - """Test getting of all catalog providers of specific type.""" - self.assertTrue(len(self.catalog.filter(providers.Provider)) == 2) - self.assertTrue(len(self.catalog.filter(providers.Object)) == 0) - - def test_repr(self): - """Test catalog representation.""" - representation = repr(self.catalog) - - self.assertIn('dependency_injector.catalogs.dynamic.DynamicCatalog', - representation) - self.assertIn('p1', representation) - self.assertIn('p2', representation) diff --git a/tests/catalogs/test_override.py b/tests/catalogs/test_override.py deleted file mode 100644 index a40123eb..00000000 --- a/tests/catalogs/test_override.py +++ /dev/null @@ -1,142 +0,0 @@ -"""Dependency injector catalogs overriding unittests.""" - -import unittest2 as unittest - -from dependency_injector import ( - catalogs, - providers, - errors, -) - - -class CatalogA(catalogs.DeclarativeCatalog): - """Test catalog A.""" - - p11 = providers.Provider() - p12 = providers.Provider() - - -class CatalogB(CatalogA): - """Test catalog B.""" - - p21 = providers.Provider() - p22 = providers.Provider() - - -class OverrideTests(unittest.TestCase): - """Catalog overriding and override decorator test cases.""" - - def tearDown(self): - """Tear test environment down.""" - CatalogA.reset_override() - - def test_overriding(self): - """Test catalog overriding with another catalog.""" - @catalogs.override(CatalogA) - class OverridingCatalog(catalogs.DeclarativeCatalog): - """Overriding catalog.""" - - p11 = providers.Object(1) - p12 = providers.Object(2) - - self.assertEqual(CatalogA.p11(), 1) - self.assertEqual(CatalogA.p12(), 2) - self.assertEqual(len(CatalogA.overridden_by), 1) - - def test_override_declarative_catalog_with_itself(self): - """Test catalog overriding of declarative catalog with itself.""" - with self.assertRaises(errors.Error): - CatalogA.override(CatalogA) - - def test_override_declarative_catalog_with_subclass(self): - """Test catalog overriding of declarative catalog with subclass.""" - with self.assertRaises(errors.Error): - CatalogB.override(CatalogA) - - def test_override_dynamic_catalog_with_itself(self): - """Test catalog overriding of dynamic catalog with itself.""" - catalog = catalogs.DynamicCatalog(p11=providers.Object(1), - p12=providers.Object(2)) - with self.assertRaises(errors.Error): - catalog.override(catalog) - - def test_overriding_with_dynamic_catalog(self): - """Test catalog overriding with another dynamic catalog.""" - CatalogA.override(catalogs.DynamicCatalog(p11=providers.Object(1), - p12=providers.Object(2))) - self.assertEqual(CatalogA.p11(), 1) - self.assertEqual(CatalogA.p12(), 2) - self.assertEqual(len(CatalogA.overridden_by), 1) - - def test_is_overridden(self): - """Test catalog is_overridden property.""" - self.assertFalse(CatalogA.is_overridden) - - @catalogs.override(CatalogA) - class OverridingCatalog(catalogs.DeclarativeCatalog): - """Overriding catalog.""" - - self.assertTrue(CatalogA.is_overridden) - - def test_last_overriding(self): - """Test catalog last_overriding property.""" - @catalogs.override(CatalogA) - class OverridingCatalog1(catalogs.DeclarativeCatalog): - """Overriding catalog.""" - - @catalogs.override(CatalogA) - class OverridingCatalog2(catalogs.DeclarativeCatalog): - """Overriding catalog.""" - - self.assertIs(CatalogA.last_overriding, OverridingCatalog2) - - def test_last_overriding_on_not_overridden(self): - """Test catalog last_overriding property on not overridden catalog.""" - self.assertIsNone(CatalogA.last_overriding) - - def test_reset_last_overriding(self): - """Test resetting last overriding catalog.""" - @catalogs.override(CatalogA) - class OverridingCatalog1(catalogs.DeclarativeCatalog): - """Overriding catalog.""" - - p11 = providers.Object(1) - p12 = providers.Object(2) - - @catalogs.override(CatalogA) - class OverridingCatalog2(catalogs.DeclarativeCatalog): - """Overriding catalog.""" - - p11 = providers.Object(3) - p12 = providers.Object(4) - - CatalogA.reset_last_overriding() - - self.assertEqual(CatalogA.p11(), 1) - self.assertEqual(CatalogA.p12(), 2) - - def test_reset_last_overriding_when_not_overridden(self): - """Test resetting last overriding catalog when it is not overridden.""" - with self.assertRaises(errors.Error): - CatalogA.reset_last_overriding() - - def test_reset_override(self): - """Test resetting all catalog overrides.""" - @catalogs.override(CatalogA) - class OverridingCatalog1(catalogs.DeclarativeCatalog): - """Overriding catalog.""" - - p11 = providers.Object(1) - p12 = providers.Object(2) - - @catalogs.override(CatalogA) - class OverridingCatalog2(catalogs.DeclarativeCatalog): - """Overriding catalog.""" - - p11 = providers.Object(3) - p12 = providers.Object(4) - - CatalogA.reset_override() - - self.assertFalse(CatalogA.p11.is_overridden) - self.assertFalse(CatalogA.p12.is_overridden) diff --git a/tests/test_containers.py b/tests/test_containers.py index 46de2e7f..6d9a3788 100644 --- a/tests/test_containers.py +++ b/tests/test_containers.py @@ -276,5 +276,162 @@ class DeclarativeContainerTests(unittest.TestCase): self.assertEqual(_Container1.p13(), 11) self.assertEqual(_Container2.p13(), 22) + +class DeclarativeContainerInstanceTests(unittest.TestCase): + """Declarative container instance tests.""" + + def test_providers_attribute(self): + """Test providers attribute.""" + container_a1 = ContainerA() + container_a2 = ContainerA() + + self.assertIsNot(container_a1.p11, container_a2.p11) + self.assertIsNot(container_a1.p12, container_a2.p12) + self.assertNotEqual(container_a1.providers, container_a2.providers) + + def test_set_get_del_providers(self): + """Test set/get/del provider attributes.""" + p13 = providers.Provider() + + container_a1 = ContainerA() + container_a2 = ContainerA() + + container_a1.p13 = p13 + container_a2.p13 = p13 + + self.assertEqual(ContainerA.providers, dict(p11=ContainerA.p11, + p12=ContainerA.p12)) + self.assertEqual(ContainerA.cls_providers, dict(p11=ContainerA.p11, + p12=ContainerA.p12)) + + self.assertEqual(container_a1.providers, dict(p11=container_a1.p11, + p12=container_a1.p12, + p13=p13)) + self.assertEqual(container_a2.providers, dict(p11=container_a2.p11, + p12=container_a2.p12, + p13=p13)) + + del container_a1.p13 + self.assertEqual(container_a1.providers, dict(p11=container_a1.p11, + p12=container_a1.p12)) + + del container_a2.p13 + self.assertEqual(container_a2.providers, dict(p11=container_a2.p11, + p12=container_a2.p12)) + + del container_a1.p11 + del container_a1.p12 + self.assertEqual(container_a1.providers, dict()) + self.assertEqual(ContainerA.providers, dict(p11=ContainerA.p11, + p12=ContainerA.p12)) + + del container_a2.p11 + del container_a2.p12 + self.assertEqual(container_a2.providers, dict()) + self.assertEqual(ContainerA.providers, dict(p11=ContainerA.p11, + p12=ContainerA.p12)) + + def test_set_invalid_provider_type(self): + """Test setting of invalid provider.""" + container_a = ContainerA() + container_a.provider_type = providers.Object + + with self.assertRaises(errors.Error): + container_a.px = providers.Provider() + + self.assertIs(ContainerA.provider_type, + containers.DeclarativeContainer.provider_type) + + def test_override(self): + """Test override.""" + class _Container(containers.DeclarativeContainer): + p11 = providers.Provider() + + class _OverridingContainer1(containers.DeclarativeContainer): + p11 = providers.Provider() + + class _OverridingContainer2(containers.DeclarativeContainer): + p11 = providers.Provider() + p12 = providers.Provider() + + container = _Container() + overriding_container1 = _OverridingContainer1() + overriding_container2 = _OverridingContainer2() + + container.override(overriding_container1) + container.override(overriding_container2) + + self.assertEqual(container.overridden_by, + (overriding_container1, + overriding_container2)) + self.assertEqual(container.p11.overridden_by, + (overriding_container1.p11, + overriding_container2.p11)) + + self.assertEqual(_Container.overridden_by, tuple()) + self.assertEqual(_Container.p11.overridden_by, tuple()) + + def test_override_with_itself(self): + """Test override container with itself.""" + container = ContainerA() + with self.assertRaises(errors.Error): + container.override(container) + + def test_reset_last_overridding(self): + """Test reset of last overriding.""" + class _Container(containers.DeclarativeContainer): + p11 = providers.Provider() + + class _OverridingContainer1(containers.DeclarativeContainer): + p11 = providers.Provider() + + class _OverridingContainer2(containers.DeclarativeContainer): + p11 = providers.Provider() + p12 = providers.Provider() + + container = _Container() + overriding_container1 = _OverridingContainer1() + overriding_container2 = _OverridingContainer2() + + container.override(overriding_container1) + container.override(overriding_container2) + container.reset_last_overriding() + + self.assertEqual(container.overridden_by, + (overriding_container1,)) + self.assertEqual(container.p11.overridden_by, + (overriding_container1.p11,)) + + def test_reset_last_overridding_when_not_overridden(self): + """Test reset of last overriding.""" + container = ContainerA() + + with self.assertRaises(errors.Error): + container.reset_last_overriding() + + def test_reset_override(self): + """Test reset all overridings.""" + class _Container(containers.DeclarativeContainer): + p11 = providers.Provider() + + class _OverridingContainer1(containers.DeclarativeContainer): + p11 = providers.Provider() + + class _OverridingContainer2(containers.DeclarativeContainer): + p11 = providers.Provider() + p12 = providers.Provider() + + container = _Container() + overriding_container1 = _OverridingContainer1() + overriding_container2 = _OverridingContainer2() + + container.override(overriding_container1) + container.override(overriding_container2) + container.reset_override() + + self.assertEqual(container.overridden_by, tuple()) + self.assertEqual(container.p11.overridden_by, tuple()) + + if __name__ == '__main__': unittest.main() diff --git a/tests/test_utils.py b/tests/test_utils.py index 95b3a826..67774fcc 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -4,7 +4,6 @@ import unittest2 as unittest from dependency_injector import utils from dependency_injector import providers -from dependency_injector import catalogs from dependency_injector import errors @@ -69,56 +68,3 @@ class EnsureIsProviderTests(unittest.TestCase): def test_with_object(self): """Test with object.""" self.assertRaises(errors.Error, utils.ensure_is_provider, object()) - - -class IsCatalogTests(unittest.TestCase): - """`is_catalog()` test cases.""" - - def test_with_declarative_catalog(self): - """Test with class.""" - self.assertTrue(utils.is_catalog(catalogs.DeclarativeCatalog)) - - def test_with_dynamic_catalog(self): - """Test with class.""" - self.assertTrue(utils.is_catalog(catalogs.DynamicCatalog())) - - def test_with_child_class(self): - """Test with parent class.""" - class Catalog(catalogs.DeclarativeCatalog): - """Example catalog child class.""" - - self.assertTrue(utils.is_catalog(Catalog)) - - def test_with_string(self): - """Test with string.""" - self.assertFalse(utils.is_catalog('some_string')) - - def test_with_object(self): - """Test with object.""" - self.assertFalse(utils.is_catalog(object())) - - -class IsDynamicCatalogTests(unittest.TestCase): - """`is_dynamic_catalog()` test cases.""" - - def test_with_declarative_catalog(self): - """Test with declarative catalog.""" - self.assertFalse(utils.is_dynamic_catalog(catalogs.DeclarativeCatalog)) - - def test_with_dynamic_catalog(self): - """Test with dynamic catalog.""" - self.assertTrue(utils.is_dynamic_catalog(catalogs.DynamicCatalog())) - - -class IsDeclarativeCatalogTests(unittest.TestCase): - """`is_declarative_catalog()` test cases.""" - - def test_with_declarative_catalog(self): - """Test with declarative catalog.""" - self.assertTrue(utils.is_declarative_catalog( - catalogs.DeclarativeCatalog)) - - def test_with_dynamic_catalog(self): - """Test with dynamic catalog.""" - self.assertFalse(utils.is_declarative_catalog( - catalogs.DynamicCatalog()))