From 2ed460f054aa8d191c095aa871965ab0120c9e7e Mon Sep 17 00:00:00 2001 From: Roman Mogilatov Date: Wed, 14 Oct 2015 17:51:05 +0300 Subject: [PATCH] Refactor catalog subsets --- dependency_injector/__init__.py | 4 +- dependency_injector/catalog.py | 76 ++++++++++++++++++++++---------- dependency_injector/providers.py | 3 +- examples/catalogs/subsets.py | 7 ++- tests/test_utils.py | 10 +++-- 5 files changed, 68 insertions(+), 32 deletions(-) diff --git a/dependency_injector/__init__.py b/dependency_injector/__init__.py index d0cd6304..8b7bb3a0 100644 --- a/dependency_injector/__init__.py +++ b/dependency_injector/__init__.py @@ -1,7 +1,7 @@ """Dependency injector.""" from .catalog import AbstractCatalog -from .catalog import CatalogSubset +from .catalog import Subset from .catalog import override from .providers import Provider @@ -39,7 +39,7 @@ from .errors import Error __all__ = ( # Catalogs 'AbstractCatalog', - 'CatalogSubset', + 'Subset', 'override', # Providers diff --git a/dependency_injector/catalog.py b/dependency_injector/catalog.py index 544cb8c2..badf6d6e 100644 --- a/dependency_injector/catalog.py +++ b/dependency_injector/catalog.py @@ -29,7 +29,13 @@ class CatalogMetaClass(type): attributes['cls_providers'] = cls_providers attributes['inherited_providers'] = inherited_providers attributes['providers'] = providers - return type.__new__(mcs, class_name, bases, attributes) + + cls = type.__new__(mcs, class_name, bases, attributes) + + for name, provider in six.iteritems(cls_providers): + provider.bind = ProviderBinding(cls, name) + + return cls def __repr__(cls): """Return string representation of the catalog class.""" @@ -58,14 +64,15 @@ class AbstractCatalog(object): __IS_CATALOG__ = True - def __new__(cls, *providers): - """Catalog constructor. + @classmethod + def subset(cls, *provider_names): + """Catalog subset factory. - Catalogs are declaratives entities that could not be instantiated. - Catalog constructor is designed to produce subsets of catalog - providers. + Create subset of catalog providers using provider names. """ - return CatalogSubset(catalog=cls, providers=providers) + return Subset(*(provider + for name, provider in six.iteritems(cls.providers) + if name in provider_names)) @classmethod def is_subset_owner(cls, subset): @@ -103,28 +110,42 @@ class AbstractCatalog(object): return name in cls.providers -class CatalogSubset(object): +class ProviderBinding(object): + """Catalog provider binding.""" + + __slots__ = ('catalog', 'name') + + def __init__(self, catalog, name): + """Initializer.""" + self.catalog = catalog + self.name = name + + +class Subset(object): """Subset of catalog providers.""" __IS_SUBSET__ = True - __slots__ = ('catalog', 'available_providers', 'providers', '__dict__') + __slots__ = ('catalog', 'providers', '__dict__') - def __init__(self, catalog, providers): + def __init__(self, *providers): """Initializer.""" - self.catalog = catalog - self.available_providers = set(providers) + if not providers: + raise Error('Subset could not be initialized without providers') + + first_provider = providers[0] + self.catalog = self._get_provider_binding(first_provider).catalog + self.providers = dict() - for provider_name in self.available_providers: - try: - provider = self.catalog.providers[provider_name] - except KeyError: - raise Error('Subset could not add "{0}" provider in scope, ' - 'because {1} has no provider with ' - 'such name'.format(provider_name, self.catalog)) - else: - self.providers[provider_name] = provider - self.__dict__.update(self.providers) - super(CatalogSubset, self).__init__() + for provider in providers: + provider_bind = self._get_provider_binding(provider) + if not self.catalog.get(provider_bind.name) is provider: + raise Error('Subset can contain providers from ' + 'one catalog {0}, ' + 'unknown provider - {1}'.format(self.catalog, + provider)) + self.providers[provider_bind.name] = provider + self.__dict__[provider_bind.name] = provider + super(Subset, self).__init__() def get(self, name): """Return provider with specified name or raises error.""" @@ -144,7 +165,14 @@ class CatalogSubset(object): def __repr__(self): """Return string representation of subset.""" return ''.format( - ', '.join(self.available_providers), self.catalog) + ', '.join(six.iterkeys(self.providers)), self.catalog) + + def _get_provider_binding(self, provider): + """Return provider binding or raise error if provider is not boud.""" + if not provider.bind: + raise Error('Provider {0} is not bound to ' + 'any catalog'.format(provider)) + return provider.bind def _raise_undefined_provider_error(self, name): """Raise error for cases when there is no such provider in subset.""" diff --git a/dependency_injector/providers.py b/dependency_injector/providers.py index 69351cbb..501b90d8 100644 --- a/dependency_injector/providers.py +++ b/dependency_injector/providers.py @@ -18,11 +18,12 @@ class Provider(object): """Base provider class.""" __IS_PROVIDER__ = True - __slots__ = ('overridden_by',) + __slots__ = ('overridden_by', 'bind') def __init__(self): """Initializer.""" self.overridden_by = None + self.bind = None def __call__(self, *args, **kwargs): """Return provided instance.""" diff --git a/examples/catalogs/subsets.py b/examples/catalogs/subsets.py index 814af380..84206699 100644 --- a/examples/catalogs/subsets.py +++ b/examples/catalogs/subsets.py @@ -36,8 +36,11 @@ class PhotosView(BaseWebView): """Example photo processing web view.""" # Creating example views with appropriate service provider subsets: -auth_view = AuthView(Services('users', 'auth')) -photos_view = PhotosView(Services('users', 'photos')) +auth_view = AuthView(di.Subset(Services.users, + Services.auth)) +photos_view = PhotosView(di.Subset(Services.users, + Services.photos)) + # Making some asserts: assert auth_view.services.users is Services.users diff --git a/tests/test_utils.py b/tests/test_utils.py index 3d86b04d..3835a76b 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -215,12 +215,16 @@ class IsCatalogSubsetTests(unittest.TestCase): def test_with_cls(self): """Test with class.""" - self.assertFalse(di.is_catalog_subset(di.CatalogSubset)) + self.assertFalse(di.is_catalog_subset(di.Subset)) def test_with_instance(self): """Test with class.""" - self.assertTrue(di.is_catalog_subset( - di.CatalogSubset(catalog=di.AbstractCatalog, providers=tuple()))) + class Catalog(di.AbstractCatalog): + """Example catalog child class.""" + + p1 = di.Provider() + + self.assertTrue(di.is_catalog_subset(di.Subset(Catalog.p1))) def test_with_string(self): """Test with string."""