diff --git a/README.rst b/README.rst index 4b14a9a0..6c8cbbd1 100644 --- a/README.rst +++ b/README.rst @@ -67,7 +67,10 @@ Examples """Concept example of `Dependency Injector`.""" import sqlite3 - import dependency_injector as di + + from dependency_injector import catalogs + from dependency_injector import providers + from dependency_injector import injections class UsersService(object): @@ -87,20 +90,20 @@ Examples self.users_service = users_service - class Services(di.DeclarativeCatalog): + class Services(catalogs.DeclarativeCatalog): """Catalog of service providers.""" - database = di.Singleton(sqlite3.connect, ':memory:') - """:type: di.Provider -> sqlite3.Connection""" + database = providers.Singleton(sqlite3.connect, ':memory:') + """:type: providers.Provider -> sqlite3.Connection""" - users = di.Factory(UsersService, - db=database) - """:type: di.Provider -> UsersService""" + users = providers.Factory(UsersService, + db=database) + """:type: providers.Provider -> UsersService""" - auth = di.Factory(AuthService, - db=database, - users_service=users) - """:type: di.Provider -> AuthService""" + auth = providers.Factory(AuthService, + db=database, + users_service=users) + """:type: providers.Provider -> AuthService""" # Retrieving catalog providers: @@ -115,9 +118,9 @@ Examples # Making some "inline" injections: - @di.inject(users_service=Services.users) - @di.inject(auth_service=Services.auth) - @di.inject(database=Services.database) + @injections.inject(users_service=Services.users) + @injections.inject(auth_service=Services.auth) + @injections.inject(database=Services.database) def example(users_service, auth_service, database): """Example callback.""" assert users_service.db is auth_service.db diff --git a/dependency_injector/__init__.py b/dependency_injector/__init__.py index 1332f8b5..990eadd0 100644 --- a/dependency_injector/__init__.py +++ b/dependency_injector/__init__.py @@ -47,7 +47,11 @@ from .errors import UndefinedProviderError from . import catalogs catalog = catalogs -VERSION = '0.10.5' +VERSION = '0.11.0' +"""Version number that follows semantic versioning. + +:type: str +""" __all__ = ( diff --git a/dependency_injector/catalogs.py b/dependency_injector/catalogs.py index b1bd26d6..5c8b5ff3 100644 --- a/dependency_injector/catalogs.py +++ b/dependency_injector/catalogs.py @@ -7,40 +7,79 @@ from .errors import UndefinedProviderError from .utils import is_provider from .utils import is_catalog +from .utils import is_declarative_catalog from .utils import ensure_is_provider from .utils import ensure_is_catalog_bundle @six.python_2_unicode_compatible class CatalogBundle(object): - """Bundle of catalog providers.""" + """Bundle of catalog providers. + + :py:class:`CatalogBundle` is a frozen, limited collection of catalog + providers. While catalog could be used as a centralized place for + particular providers group, such bundles of catalog providers can be used + for creating several frozen, limited scopes that could be passed to + different subsystems. + + :py:class:`CatalogBundle` has API's parity with catalogs + (:py:class:`DeclarativeCatalog` or :py:class:`DynamicCatalog`) in terms of + retrieving the providers, but it is "frozen" in terms of modification + provider's list. + + :py:class:`CatalogBundle` is considered to be dependable on catalogs + (:py:class:`DeclarativeCatalog` or :py:class:`DynamicCatalog`) entity by + its design. + """ catalog = None - """:type: DeclarativeCatalog""" + """Bundle's catalog. + + :type: :py:class:`DeclarativeCatalog` | :py:class:`DynamicCatalog` + """ __IS_CATALOG_BUNDLE__ = True __slots__ = ('providers', '__dict__') + @classmethod + def sub_cls_factory(cls, catalog): + """Create bundle subclass for catalog. + + :return: Subclass of :py:class:`CatalogBundle`. + :rtype: :py:class:`CatalogBundle` + """ + return type('BundleSubclass', (cls,), dict(catalog=catalog)) + def __init__(self, *providers): - """Initializer.""" + """Initializer. + + :param providers: Tuple of catalog's bundle providers. + :type providers: tuple[ + :py:class:`dependency_injector.providers.Provider`] + """ self.providers = dict() + """Dictionary of all providers. + + :type: dict[str, :py:class:`dependency_injector.providers.Provider`] + """ + for provider in providers: provider_name = self.catalog.get_provider_bind_name(provider) self.providers[provider_name] = provider self.__dict__.update(self.providers) super(CatalogBundle, self).__init__() - @classmethod - def sub_cls_factory(cls, catalog): - """Create bundle class for catalog. - - :rtype: CatalogBundle - :return: Subclass of CatalogBundle - """ - return type('BundleSubclass', (cls,), dict(catalog=catalog)) - def get_provider(self, name): - """Return provider with specified name or raise an error.""" + """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: @@ -48,18 +87,33 @@ class CatalogBundle(object): self)) def has_provider(self, name): - """Check if there is provider with certain 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 __getattr__(self, item): - """Raise an error on every attempt to get undefined provider.""" + """Return provider with specified name or raise en error. + + :param name: Attribute's name. + :type name: str + + :raise: :py:exc:`dependency_injector.errors.UndefinedProviderError` + """ if item.startswith('__') and item.endswith('__'): return super(CatalogBundle, self).__getattr__(item) raise UndefinedProviderError('Provider "{0}" is not a part ' 'of {1}'.format(item, self)) def __repr__(self): - """Return string representation of catalog bundle.""" + """Return string representation of catalog's bundle. + + :rtype: str + """ return '<{0}.Bundle({1})>'.format( self.catalog.name, ', '.join(six.iterkeys(self.providers))) @@ -68,7 +122,22 @@ class CatalogBundle(object): @six.python_2_unicode_compatible class DynamicCatalog(object): - """Catalog of providers.""" + """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() + """ __IS_CATALOG__ = True __slots__ = ('name', 'providers', 'provider_names', 'overridden_by', @@ -77,64 +146,132 @@ class DynamicCatalog(object): def __init__(self, **providers): """Initializer. - :type providers: dict[str, dependency_injector.providers.Provider] + :param providers: Dictionary of catalog providers. + :type providers: + dict[str, :py:class:`dependency_injector.providers.Provider`] """ + self.Bundle = CatalogBundle.sub_cls_factory(self) + """Catalog's bundle class. + + :type: :py:class:`CatalogBundle` + """ + self.name = '.'.join((self.__class__.__module__, self.__class__.__name__)) - self.providers = dict() - self.provider_names = dict() - self.overridden_by = tuple() + """Catalog's name. - self.Bundle = CatalogBundle.sub_cls_factory(self) + By default, it is catalog's module + catalog's class name. + + :type: str + """ + + self.providers = dict() + """Dictionary of all providers. + + :type: dict[str, :py:class:`dependency_injector.providers.Provider`] + """ + + self.provider_names = dict() + + self.overridden_by = tuple() + """Tuple of overriding catalogs. + + :type: tuple[ + :py:class:`DeclarativeCatalog` | :py:class:`DynamicCatalog`] + """ self.bind_providers(providers) super(DynamicCatalog, self).__init__() def is_bundle_owner(self, bundle): - """Check if catalog is bundle owner.""" + """Check if catalog is bundle owner. + + :param bundle: Catalog's bundle instance. + :type bundle: :py:class:`CatalogBundle` + + :rtype: bool + """ return ensure_is_catalog_bundle(bundle) and bundle.catalog is self def get_provider_bind_name(self, provider): - """Return provider's name in catalog.""" + """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.""" + """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 dict of providers, that are instance of provided 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): - """Check if catalog is overridden by another catalog.""" + """Read-only property that is set to ``True`` if catalog is overridden. + + :rtype: bool + """ return bool(self.overridden_by) @property def last_overriding(self): - """Return last overriding catalog.""" - try: - return self.overridden_by[-1] - except (TypeError, IndexError): - raise Error('Catalog {0} is not overridden'.format(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. - :type overriding: DynamicCatalog + :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.""" + """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] @@ -142,13 +279,25 @@ class DynamicCatalog(object): provider.reset_last_overriding() def reset_override(self): - """Reset all overridings for all catalog providers.""" + """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.""" + """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: @@ -156,7 +305,18 @@ class DynamicCatalog(object): 'name - {1}'.format(self, name)) def bind_provider(self, name, provider): - """Bind provider to catalog with specified name.""" + """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` + + :raise: :py:exc:`dependency_injector.errors.Error` + + :rtype: None + """ provider = ensure_is_provider(provider) if name in self.providers: @@ -170,29 +330,66 @@ class DynamicCatalog(object): self.provider_names[provider] = name def bind_providers(self, providers): - """Bind providers dictionary to catalog.""" + """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`] + + :raise: :py:exc:`dependency_injector.errors.Error` + + :rtype: None + """ for name, provider in six.iteritems(providers): self.bind_provider(name, provider) def has_provider(self, name): - """Check if there is provider with certain 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.""" + """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.""" + """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 correctly. + 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) @@ -202,12 +399,20 @@ class DynamicCatalog(object): """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 correctly. + 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.""" + """Return Python representation of catalog. + + :rtype: str + """ return '<{0}({1})>'.format(self.name, ', '.join(six.iterkeys(self.providers))) @@ -233,44 +438,70 @@ class DeclarativeCatalogMetaClass(type): cls = type.__new__(mcs, class_name, bases, attributes) - cls.catalog = DynamicCatalog() - cls.catalog.name = '.'.join((cls.__module__, cls.__name__)) - cls.catalog.bind_providers(dict(providers)) + cls._catalog = DynamicCatalog() + cls._catalog.name = '.'.join((cls.__module__, cls.__name__)) + cls._catalog.bind_providers(dict(providers)) cls.cls_providers = dict(cls_providers) cls.inherited_providers = dict(inherited_providers) - cls.Bundle = cls.catalog.Bundle + cls.Bundle = cls._catalog.Bundle return cls @property def name(cls): - """Return catalog's name.""" - return cls.catalog.name + """Read-only property that represents catalog's name. + + Catalog's name is catalog's module + catalog's class name. + + :type: str + """ + return cls._catalog.name @property def providers(cls): - """Return dict of catalog's providers.""" - return cls.catalog.providers + """Read-only dictionary of all providers. + + :type: dict[str, :py:class:`dependency_injector.providers.Provider`] + """ + return cls._catalog.providers @property def overridden_by(cls): - """Return tuple of overriding catalogs.""" - return cls.catalog.overridden_by + """Tuple of overriding catalogs. + + :type: tuple[ + :py:class:`DeclarativeCatalog` | + :py:class:`DynamicCatalog`] + """ + return cls._catalog.overridden_by @property def is_overridden(cls): - """Check if catalog is overridden by another catalog.""" - return cls.catalog.is_overridden + """Read-only property that is set to ``True`` if catalog is overridden. + + :rtype: bool + """ + return cls._catalog.is_overridden @property def last_overriding(cls): - """Return last overriding catalog.""" - return cls.catalog.last_overriding + """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.""" + """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)) @@ -278,24 +509,41 @@ class DeclarativeCatalogMetaClass(type): """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 correctly. + 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): - setattr(cls.catalog, name, value) + setattr(cls._catalog, name, value) 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 correctly. + 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) + delattr(cls._catalog, name) return super(DeclarativeCatalogMetaClass, cls).__delattr__(name) def __repr__(cls): - """Return string representation of the catalog.""" + """Return string representation of the catalog. + + :rtype: str + """ return '<{0}({1})>'.format(cls.name, ', '.join(six.iterkeys(cls.providers))) @@ -304,121 +552,274 @@ class DeclarativeCatalogMetaClass(type): @six.add_metaclass(DeclarativeCatalogMetaClass) class DeclarativeCatalog(object): - """Declarative catalog catalog of providers. + """Declarative catalog of providers. - :type name: str - :param name: Catalog's name + :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). - :type catalog: DynamicCatalog - :param catalog: Instance of dynamic catalog + .. code-block:: python - :type Bundle: CatalogBundle - :param Bundle: Catalog's bundle class + class Services(DeclarativeCatalog): - :type providers: dict[str, dependency_injector.Provider] - :param providers: Dict of all catalog providers, including inherited from - parent catalogs + auth = providers.Factory(AuthService) - :type cls_providers: dict[str, dependency_injector.Provider] - :param cls_providers: Dict of current catalog providers + users = providers.Factory(UsersService) - :type inherited_providers: dict[str, dependency_injector.Provider] - :param inherited_providers: Dict of providers, that are inherited from - parent catalogs - - :type overridden_by: tuple[DeclarativeCatalog] - :param overridden_by: Tuple of overriding catalogs - - :type is_overridden: bool - :param is_overridden: Read-only, evaluated in runtime, property that is - set to True if catalog is overridden - - :type last_overriding: DeclarativeCatalog | None - :param last_overriding: Reference to the last overriding catalog, if any + users_service = Services.users() """ Bundle = CatalogBundle + """Catalog's bundle class. + + :type: :py:class:`CatalogBundle` + """ name = str() + """Read-only property that represents catalog's name. + + Catalog's name is catalog's module + catalog's class name. + + :type: str + """ cls_providers = dict() + """Read-only dictionary of current catalog providers. + + :type: dict[str, :py:class:`dependency_injector.providers.Provider`] + """ + inherited_providers = dict() + """Read-only dictionary of inherited providers. + + :type: dict[str, :py:class:`dependency_injector.providers.Provider`] + """ + providers = dict() + """Read-only dictionary of all providers. + + :type: dict[str, :py:class:`dependency_injector.providers.Provider`] + """ overridden_by = tuple() - is_overridden = bool - last_overriding = None + """Tuple of overriding catalogs. - catalog = DynamicCatalog + :type: tuple[:py:class:`DeclarativeCatalog` | + :py:class:`DynamicCatalog`] + """ + + is_overridden = bool + """Read-only property that is set to ``True`` if catalog is overridden. + + :type: bool + """ + + last_overriding = None + """Read-only reference to the last overriding catalog, if any. + + :type: :py:class:`DeclarativeCatalog` | :py:class:`DynamicCatalog` | None + """ + + _catalog = DynamicCatalog __IS_CATALOG__ = True @classmethod def is_bundle_owner(cls, bundle): - """Check if catalog is bundle owner.""" - return cls.catalog.is_bundle_owner(bundle) + """Check if catalog is bundle owner. + + :param bundle: Catalog's bundle instance. + :type bundle: :py:class:`CatalogBundle` + + :rtype: bool + """ + return cls._catalog.is_bundle_owner(bundle) @classmethod def get_provider_bind_name(cls, provider): - """Return provider's name in catalog.""" - return cls.catalog.get_provider_bind_name(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.""" - return cls.catalog.is_provider_bound(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 dict of providers, that are instance of provided type.""" - return cls.catalog.filter(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. - :type overriding: DeclarativeCatalog | DynamicCatalog + :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 """ - return cls.catalog.override(overriding) + 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.""" - cls.catalog.reset_last_overriding() + """Reset last overriding catalog. + + :rtype: None + """ + cls._catalog.reset_last_overriding() @classmethod def reset_override(cls): - """Reset all overridings for all catalog providers.""" - cls.catalog.reset_override() + """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.""" - return cls.catalog.get_provider(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) + + get = get_provider # Backward compatibility for versions < 0.11.* @classmethod def bind_provider(cls, name, provider): - """Bind provider to catalog with specified name.""" + """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` + + :raise: :py:exc:`dependency_injector.errors.Error` + + :rtype: None + """ setattr(cls, name, provider) @classmethod def bind_providers(cls, providers): - """Bind providers dictionary to catalog.""" + """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`] + + :raise: :py:exc:`dependency_injector.errors.Error` + + :rtype: None + """ for name, provider in six.iteritems(providers): setattr(cls, name, provider) @classmethod def has_provider(cls, name): - """Check if there is provider with certain name.""" + """Check if there is provider with certain name. + + :param name: Provider's name. + :type name: str + + :rtype: bool + """ return hasattr(cls, name) + has = has_provider # Backward compatibility for versions < 0.11.* + @classmethod def unbind_provider(cls, name): - """Remove provider binding.""" + """Remove provider binding. + + :param name: Provider's name. + :type name: str + + :rtype: None + """ delattr(cls, name) - get = get_provider # Backward compatibility for versions < 0.11.* - has = has_provider # Backward compatibility for versions < 0.11.* + @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') # Backward compatibility for versions < 0.11.* @@ -426,7 +827,14 @@ AbstractCatalog = DeclarativeCatalog def override(catalog): - """Catalog overriding decorator.""" + """:py:class:`DeclarativeCatalog` overriding decorator. + + :param catalog: Catalog that should be overridden by decorated catalog. + :type catalog: :py:class:`DeclarativeCatalog` | :py:class:`DynamicCatalog` + + :return: Declarative catalog's overriding decorator. + :rtype: callable(:py:class:`DeclarativeCatalog`) + """ def decorator(overriding_catalog): """Overriding decorator.""" catalog.override(overriding_catalog) diff --git a/dependency_injector/errors.py b/dependency_injector/errors.py index 37be4f82..b5f9fc04 100644 --- a/dependency_injector/errors.py +++ b/dependency_injector/errors.py @@ -2,8 +2,21 @@ class Error(Exception): - """Base error.""" + """Base error. + + All dependency injector errors extend this error class. + """ class UndefinedProviderError(Error, AttributeError): - """Undefined provider error.""" + """Undefined provider error. + + This error is used when provider could not be defined, for example: + + - provider with certain name could not be defined + - catalog's name of the certain provider could not be defined + - etc... + + Also this error extends standard :py:class:`AttributeError`. This gives + possibility to use it correctly with ``__getattr__()``. + """ diff --git a/dependency_injector/injections.py b/dependency_injector/injections.py index c6e44b60..8fa440cd 100644 --- a/dependency_injector/injections.py +++ b/dependency_injector/injections.py @@ -13,33 +13,60 @@ from .utils import is_kwarg_injection from .errors import Error -IS_PYPY = '__pypy__' in sys.builtin_module_names -if IS_PYPY or six.PY3: # pragma: no cover - OBJECT_INIT = six.get_unbound_function(object.__init__) +_IS_PYPY = '__pypy__' in sys.builtin_module_names +if _IS_PYPY or six.PY3: # pragma: no cover + _OBJECT_INIT = six.get_unbound_function(object.__init__) else: # pragma: no cover - OBJECT_INIT = None + _OBJECT_INIT = None class Injection(object): - """Base injection class.""" + """Base injection class. + + All injections extend this class. + """ __IS_INJECTION__ = True __slots__ = ('injectable', 'is_provider') def __init__(self, injectable): - """Initializer.""" + """Initializer. + + :param injectable: Injectable value, could be provider or any + other object. + :type injectable: object | + :py:class:`dependency_injector.providers.Provider` + """ self.injectable = injectable + """Injectable value, could be provider or any other object. + + :type: object | :py:class:`dependency_injector.providers.Provider` + """ + self.is_provider = is_provider(injectable) + """Flag that is set to ``True`` if injectable value is provider. + + :type: bool + """ + + super(Injection, self).__init__() @property def value(self): - """Return injectable value.""" + """Read-only property that represents injectable value. + + Injectable values are provided "as is", except of providers + (subclasses of :py:class:`dependency_injector.providers.Provider`). + Providers will be called every time, when injection needs to be done. + + :rtype: object + """ if self.is_provider: return self.injectable() return self.injectable -class NamedInjection(Injection): +class _NamedInjection(Injection): """Base class of named injections.""" __slots__ = ('name',) @@ -47,7 +74,7 @@ class NamedInjection(Injection): def __init__(self, name, injectable): """Initializer.""" self.name = name - super(NamedInjection, self).__init__(injectable) + super(_NamedInjection, self).__init__(injectable) class Arg(Injection): @@ -56,19 +83,19 @@ class Arg(Injection): __IS_ARG_INJECTION__ = True -class KwArg(NamedInjection): +class KwArg(_NamedInjection): """Keyword argument injection.""" __IS_KWARG_INJECTION__ = True -class Attribute(NamedInjection): +class Attribute(_NamedInjection): """Attribute injection.""" __IS_ATTRIBUTE_INJECTION__ = True -class Method(NamedInjection): +class Method(_NamedInjection): """Method injection.""" __IS_METHOD_INJECTION__ = True @@ -77,7 +104,48 @@ class Method(NamedInjection): def inject(*args, **kwargs): """Dependency injection decorator. - :return: (callable) -> (callable) + :py:func:`inject` decorator can be used for making inline dependency + injections. It patches decorated callable in such way that dependency + injection will be done during every call of decorated callable. + + :py:func:`inject` decorator supports different syntaxes of passing + injections: + + .. code-block:: python + + # Positional arguments injections (simplified syntax): + @inject(1, 2) + def some_function(arg1, arg2): + pass + + # Keyword arguments injections (simplified syntax): + @inject(arg1=1) + @inject(arg2=2) + def some_function(arg1, arg2): + pass + + # Keyword arguments injections (extended (full) syntax): + @inject(KwArg('arg1', 1)) + @inject(KwArg('arg2', 2)) + def some_function(arg1, arg2): + pass + + # Keyword arguments injections into class init (simplified syntax): + @inject(arg1=1) + @inject(arg2=2) + class SomeClass(object): + + def __init__(self, arg1, arg2): + pass + + :param args: Tuple of context positional arguments. + :type args: tuple[object] + + :param kwargs: Dictionary of context keyword arguments. + :type kwargs: dict[str, object] + + :return: Class / callable decorator + :rtype: (callable) -> (type | callable) """ arg_injections = _parse_args_injections(args) kwarg_injections = _parse_kwargs_injections(args, kwargs) @@ -88,7 +156,7 @@ def inject(*args, **kwargs): cls = callback_or_cls try: cls_init = six.get_unbound_function(cls.__init__) - assert cls_init is not OBJECT_INIT + assert cls_init is not _OBJECT_INIT except (AttributeError, AssertionError): raise Error( 'Class {0}.{1} has no __init__() '.format(cls.__module__, diff --git a/dependency_injector/providers.py b/dependency_injector/providers.py index f9d8cd30..5c25536b 100644 --- a/dependency_injector/providers.py +++ b/dependency_injector/providers.py @@ -16,7 +16,45 @@ from .errors import Error class Provider(object): - """Base provider class.""" + """Base provider class. + + :py:class:`Provider` is callable (implements ``__call__`` method). Every + call to provider object returns provided result, according to the providing + strategy of particular provider. This ``callable`` functionality is a + regular part of providers API and it should be the same for all provider's + subclasses. + + Implementation of particular providing strategy should be done in + :py:meth:`Provider._provide` of :py:class:`Provider` subclass. Current + method is called every time when not overridden provider is called. + + :py:class:`Provider` implements provider overriding logic that should be + also common for all providers: + + .. code-block:: python + + provider1 = Factory(SomeClass) + provider2 = Factory(ChildSomeClass) + + provider1.override(provider2) + + some_instance = provider1() + assert isinstance(some_instance, ChildSomeClass) + + Also :py:class:`Provider` implements helper function for creating its + delegates: + + .. code-block:: python + + provider = Factory(object) + delegate = provider.delegate() + + delegated = delegate() + + assert provider is delegated + + All providers should extend this class. + """ __IS_PROVIDER__ = True __slots__ = ('overridden_by',) @@ -24,9 +62,23 @@ class Provider(object): def __init__(self): """Initializer.""" self.overridden_by = None + """Tuple of overriding providers, if any. + + :type: tuple[:py:class:`Provider`] | None + """ + + super(Provider, self).__init__() def __call__(self, *args, **kwargs): - """Return provided instance.""" + """Return provided instance. + + Implementation of current method adds ``callable`` functionality for + providers API and it should be common for all provider's subclasses. + Also this method implements provider overriding logic that is also + common for all providers. Implementation of particular providing + strategy should be done in :py:meth:`Provider._provide` of + :py:class:`Provider` subclass. + """ if self.overridden_by: return self.last_overriding(*args, **kwargs) return self._provide(*args, **kwargs) @@ -40,12 +92,30 @@ class Provider(object): """ raise NotImplementedError() - def delegate(self): - """Return provider's delegate.""" - return Delegate(self) + @property + def is_overridden(self): + """Read-only property that is set to ``True`` if provider is overridden. + + :rtype: bool + """ + return bool(self.overridden_by) + + @property + def last_overriding(self): + """Read-only reference to the last overriding provider, if any. + + :type: :py:class:`Provider` | None + """ + return self.overridden_by[-1] if self.overridden_by else None def override(self, provider): - """Override provider with another provider.""" + """Override provider with another provider. + + :param provider: Overriding provider. + :type provider: :py:class:`Provider` + + :raise: :py:exc:`dependency_injector.errors.Error` + """ if provider is self: raise Error('Provider {0} could not be overridden ' 'with itself'.format(self)) @@ -54,74 +124,174 @@ class Provider(object): else: self.overridden_by += (ensure_is_provider(provider),) - @property - def is_overridden(self): - """Check if provider is overridden by another provider.""" - return bool(self.overridden_by) - - @property - def last_overriding(self): - """Return last overriding provider.""" - try: - return self.overridden_by[-1] - except (TypeError, IndexError): - raise Error('Provider {0} is not overridden'.format(str(self))) - def reset_last_overriding(self): - """Reset last overriding provider.""" - if not self.is_overridden: + """Reset last overriding provider. + + :raise: :py:exc:`dependency_injector.errors.Error` if provider is not + overridden. + + :rtype: None + """ + if not self.overridden_by: raise Error('Provider {0} is not overridden'.format(str(self))) self.overridden_by = self.overridden_by[:-1] def reset_override(self): - """Reset all overriding providers.""" + """Reset all overriding providers. + + :rtype: None + """ self.overridden_by = None + def delegate(self): + """Return provider's delegate. + + :rtype: :py:class:`Delegate` + """ + return Delegate(self) + class Delegate(Provider): - """Provider's delegate.""" + """:py:class:`Delegate` provider delegates another provider. + + .. code-block:: python + + provider = Factory(object) + delegate = Delegate(provider) + + delegated = delegate() + + assert provider is delegated + """ __slots__ = ('delegated',) def __init__(self, delegated): """Initializer. - :type delegated: Provider + :provider delegated: Delegated provider. + :type delegated: :py:class:`Provider` """ self.delegated = ensure_is_provider(delegated) super(Delegate, self).__init__() def _provide(self, *args, **kwargs): - """Return provided instance.""" + """Return provided instance. + + :param args: Tuple of context positional arguments. + :type args: tuple[object] + + :param kwargs: Dictionary of context keyword arguments. + :type kwargs: dict[str, object] + + :rtype: object + """ return self.delegated class Factory(Provider): - """Factory provider. + """:py:class:`Factory` provider creates new instance on every call. - Factory provider creates new instance of specified class on every call. + :py:class:`Factory` supports different syntaxes of passing injections: + + .. code-block:: python + + # simplified syntax for passing positional and keyword argument + # injections only: + factory = Factory(SomeClass, 'arg1', 'arg2', arg3=3, arg4=4) + + # extended (full) syntax for passing any type of injections: + factory = Factory(SomeClass, + injections.Arg(1), + injections.Arg(2), + injections.KwArg('some_arg', 3), + injections.KwArg('other_arg', 4), + injections.Attribute('some_attribute', 5)) + + Retrieving of provided instance can be performed via calling + :py:class:`Factory` object: + + .. code-block:: python + + factory = Factory(SomeClass, + some_arg1=1, + some_arg2=2) + some_object = factory() """ __slots__ = ('provides', 'args', 'kwargs', 'attributes', 'methods') def __init__(self, provides, *args, **kwargs): - """Initializer.""" + """Initializer. + + :param provides: Class or other callable that provides object + for creation. + :type provides: type | callable + + :param args: Tuple of injections. + :type args: tuple + + :param kwargs: Dictionary of injections. + :type kwargs: dict + """ if not callable(provides): raise Error('Factory provider expects to get callable, ' + 'got {0} instead'.format(str(provides))) self.provides = provides + """Class or other callable that provides object for creation. + + :type: type | callable + """ + self.args = _parse_args_injections(args) + """Tuple of positional argument injections. + + :type: tuple[:py:class:`dependency_injector.injections.Arg`] + """ + self.kwargs = _parse_kwargs_injections(args, kwargs) + """Tuple of keyword argument injections. + + :type: tuple[:py:class:`dependency_injector.injections.KwArg`] + """ + self.attributes = tuple(injection for injection in args if is_attribute_injection(injection)) + """Tuple of attribute injections. + + :type: tuple[:py:class:`dependency_injector.injections.Attribute`] + """ + self.methods = tuple(injection for injection in args if is_method_injection(injection)) + """Tuple of method injections. + + :type: tuple[:py:class:`dependency_injector.injections.Method`] + """ + super(Factory, self).__init__() + @property + def injections(self): + """Read-only tuple of all injections. + + :rtype: tuple[:py:class:`dependency_injector.injections.Injection`] + """ + return self.args + self.kwargs + self.attributes + self.methods + def _provide(self, *args, **kwargs): - """Return provided instance.""" + """Return provided instance. + + :param args: Tuple of context positional arguments. + :type args: tuple[object] + + :param kwargs: Dictionary of context keyword arguments. + :type kwargs: dict[str, object] + + :rtype: object + """ instance = self.provides(*_get_injectable_args(args, self.args), **_get_injectable_kwargs(kwargs, self.kwargs)) for attribute in self.attributes: @@ -131,48 +301,145 @@ class Factory(Provider): return instance - @property - def injections(self): - """Return tuple of all injections.""" - return self.args + self.kwargs + self.attributes + self.methods - class Singleton(Provider): - """Singleton provider. + """:py:class:`Singleton` provider returns same instance on every call. + + :py:class:`Singleton` provider creates instance once and return it on every + call. :py:class:`Singleton` uses :py:class:`Factory` for creation of + instance, so, please follow :py:class:`Factory` documentation to go inside + with injections syntax. + + :py:class:`Singleton` is thread-safe and could be used in multithreading + environment without any negative impact. + + Retrieving of provided instance can be performed via calling + :py:class:`Singleton` object: + + .. code-block:: python + + singleton = Singleton(SomeClass, + some_arg1=1, + some_arg2=2) + some_object = singleton() - Singleton provider will create instance once and return it on every call. """ __slots__ = ('instance', 'factory') def __init__(self, provides, *args, **kwargs): - """Initializer.""" + """Initializer. + + :param provides: Class or other callable that provides object + for creation. + :type provides: type | callable + + :param args: Tuple of injections. + :type args: tuple + + :param kwargs: Dictionary of injections. + :type kwargs: dict + """ self.instance = None + """Read-only reference to singleton's instance. + + :type: object + """ + self.factory = Factory(provides, *args, **kwargs) + """Singleton's factory object. + + :type: :py:class:`Factory` + """ + super(Singleton, self).__init__() + @property + def provides(self): + """Class or other callable that provides object for creation. + + :type: type | callable + """ + return self.factory.provides + + @property + def args(self): + """Tuple of positional argument injections. + + :type: tuple[:py:class:`dependency_injector.injections.Arg`] + """ + return self.factory.args + + @property + def kwargs(self): + """Tuple of keyword argument injections. + + :type: tuple[:py:class:`dependency_injector.injections.KwArg`] + """ + return self.factory.kwargs + + @property + def attributes(self): + """Tuple of attribute injections. + + :type: tuple[:py:class:`dependency_injector.injections.Attribute`] + """ + return self.factory.attributes + + @property + def methods(self): + """Tuple of method injections. + + :type: tuple[:py:class:`dependency_injector.injections.Method`] + """ + return self.factory.methods + + @property + def injections(self): + """Read-only tuple of all injections. + + :rtype: tuple[:py:class:`dependency_injector.injections.Injection`] + """ + return self.factory.injections + + def reset(self): + """Reset cached instance, if any. + + :rtype: None + """ + self.instance = None + def _provide(self, *args, **kwargs): - """Return provided instance.""" + """Return provided instance. + + :param args: Tuple of context positional arguments. + :type args: tuple[object] + + :param kwargs: Dictionary of context keyword arguments. + :type kwargs: dict[str, object] + + :rtype: object + """ with GLOBAL_LOCK: if not self.instance: self.instance = self.factory(*args, **kwargs) return self.instance - def reset(self): - """Reset instance.""" - self.instance = None - - @property - def injections(self): - """Return tuple of all injections.""" - return self.factory.injections - class ExternalDependency(Provider): - """External dependency provider. + """:py:class:`ExternalDependency` provider describes dependency interface. - Those provider is used when dependency obviously have to be overridden by - the client's code, but it's interface is known. + This provider is used for description of dependency interface. That might + be useful when dependency could be provided in the client's code only, + but it's interface is known. Such situations could happen when required + dependency has non-determenistic list of dependencies itself. + + .. code-block:: python + + database_provider = ExternalDependency(sqlite3.dbapi2.Connection) + database_provider.override(Factory(sqlite3.connect, ':memory:')) + + database = database_provider() """ __slots__ = ('instance_of',) @@ -183,10 +450,25 @@ class ExternalDependency(Provider): raise Error('ExternalDependency provider expects to get class, ' + 'got {0} instead'.format(str(instance_of))) self.instance_of = instance_of + """Class of required dependency. + + :type: type + """ super(ExternalDependency, self).__init__() def __call__(self, *args, **kwargs): - """Return provided instance.""" + """Return provided instance. + + :param args: Tuple of context positional arguments. + :type args: tuple[object] + + :param kwargs: Dictionary of context keyword arguments. + :type kwargs: dict[str, object] + + :raise: :py:exc:`dependency_injector.errors.Error` + + :rtype: object + """ if not self.is_overridden: raise Error('Dependency is not defined') @@ -199,97 +481,207 @@ class ExternalDependency(Provider): return instance def provided_by(self, provider): - """Set external dependency provider.""" + """Set external dependency provider. + + :param provider: Provider that provides required dependency. + :type provider: :py:class:`Provider` + + :rtype: None + """ return self.override(provider) class StaticProvider(Provider): - """Static provider. + """:py:class:`StaticProvider` returns provided instance "as is". - Static provider is base implementation that provides exactly the same as - it got on input. + :py:class:`StaticProvider` is base implementation that provides exactly + the same as it got on input. """ __slots__ = ('provides',) def __init__(self, provides): - """Initializer.""" + """Initializer. + + :param provides: Value that have to be provided. + :type provides: object + """ self.provides = provides + """Value that have to be provided. + + :type: object + """ super(StaticProvider, self).__init__() def _provide(self, *args, **kwargs): - """Return provided instance.""" + """Return provided instance. + + :param args: Tuple of context positional arguments. + :type args: tuple[object] + + :param kwargs: Dictionary of context keyword arguments. + :type kwargs: dict[str, object] + + :rtype: object + """ return self.provides class Class(StaticProvider): - """Class provider provides class.""" + """:py:class:`Class` returns provided class "as is". + + .. code-block:: python + + cls_provider = Class(object) + object_cls = cls_provider() + """ class Object(StaticProvider): - """Object provider provides object.""" + """:py:class:`Object` returns provided object "as is". + + .. code-block:: python + + object_provider = Object(object()) + object_instance = object_provider() + """ class Function(StaticProvider): - """Function provider provides function.""" + """:py:class:`Function` returns provided function "as is". + + .. code-block:: python + + function_provider = Function(len) + len_function = function_provider() + """ class Value(StaticProvider): - """Value provider provides value.""" + """:py:class:`Value` returns provided value "as is". + + .. code-block:: python + + value_provider = Value(31337) + value = value_provider() + """ class Callable(Provider): - """Callable provider. + """:py:class:`Callable` provider calls wrapped callable on every call. - Callable provider provides callable that is called on every provider call - with some predefined dependency injections. + :py:class:`Callable` provider provides callable that is called on every + provider call with some predefined dependency injections. + + :py:class:`Callable` syntax of passing injections is the same like + :py:class:`Factory` one: + + .. code-block:: python + + some_function = Callable(some_function, 'arg1', 'arg2', arg3=3, arg4=4) + result = some_function() """ __slots__ = ('callback', 'args', 'kwargs') def __init__(self, callback, *args, **kwargs): - """Initializer.""" + """Initializer. + + :param callback: Wrapped callable. + :type callback: callable + + :param args: Tuple of injections. + :type args: tuple + + :param kwargs: Dictionary of injections. + :type kwargs: dict + """ if not callable(callback): raise Error('Callable expected, got {0}'.format(str(callback))) self.callback = callback - self.args = _parse_args_injections(args) - self.kwargs = _parse_kwargs_injections(args, kwargs) - super(Callable, self).__init__() + """Wrapped callable. - def _provide(self, *args, **kwargs): - """Return provided instance.""" - return self.callback(*_get_injectable_args(args, self.args), - **_get_injectable_kwargs(kwargs, self.kwargs)) + :type: callable + """ + + self.args = _parse_args_injections(args) + """Tuple of positional argument injections. + + :type: tuple[:py:class:`dependency_injector.injections.Arg`] + """ + + self.kwargs = _parse_kwargs_injections(args, kwargs) + """Tuple of keyword argument injections. + + :type: tuple[:py:class:`dependency_injector.injections.KwArg`] + """ + + super(Callable, self).__init__() @property def injections(self): - """Return tuple of all injections.""" + """Read-only tuple of all injections. + + :rtype: tuple[:py:class:`dependency_injector.injections.Injection`] + """ return self.args + self.kwargs + def _provide(self, *args, **kwargs): + """Return provided instance. + + :param args: Tuple of context positional arguments. + :type args: tuple[object] + + :param kwargs: Dictionary of context keyword arguments. + :type kwargs: dict[str, object] + + :rtype: object + """ + return self.callback(*_get_injectable_args(args, self.args), + **_get_injectable_kwargs(kwargs, self.kwargs)) + class Config(Provider): - """Config provider. + """:py:class:`Config` provider provide dict values. - Config provider provides dict values. Also config provider creates - child config objects for all undefined attribute calls. It makes possible - to create deferred config value provider. + :py:class:`Config` provider creates :py:class:`ChildConfig` objects for all + undefined attribute calls. It makes possible to create deferred config + value providers. It might be useful in cases where it is needed to + define / pass some configuration in declarative manner, while + configuration values will be loaded / updated in application's runtime. """ __slots__ = ('value',) def __init__(self, value=None): - """Initializer.""" + """Initializer. + + :param value: Configuration dictionary. + :type value: dict[str, object] + """ if not value: value = dict() self.value = value super(Config, self).__init__() def __getattr__(self, item): - """Return instance of deferred config.""" + """Return instance of deferred config. + + :param item: Name of configuration option or section. + :type item: str + + :rtype: :py:class:`ChildConfig` + """ return ChildConfig(parents=(item,), root_config=self) def _provide(self, paths=None): - """Return provided instance.""" + """Return provided instance. + + :param paths: Tuple of pieces of configuration option / section path. + :type args: tuple[str] + + :rtype: object + """ value = self.value if paths: for path in paths: @@ -301,30 +693,69 @@ class Config(Provider): return value def update_from(self, value): - """Update current value from another one.""" + """Update current value from another one. + + :param value: Configuration dictionary. + :type value: dict[str, object] + + :rtype: None + """ self.value.update(value) class ChildConfig(Provider): - """Child config provider. + """:py:class:`ChildConfig` provider provides value from :py:class:`Config`. - Child config provide an value from the root config object according to - the current path in the config tree. + :py:class:`ChildConfig` provides value from the root config object + according to the current path in the config tree. """ __slots__ = ('parents', 'root_config') def __init__(self, parents, root_config): - """Initializer.""" + """Initializer. + + :param parents: Tuple of pieces of configuration option / section + parent path. + :type parents: tuple[str] + + :param root_config: Root configuration object. + :type root_config: :py:class:`Config` + """ self.parents = parents + """Tuple of pieces of configuration option / section parent path. + + :type: tuple[str] + """ + self.root_config = root_config + """Root configuration object. + + :type: :py:class:`Config` + """ + super(ChildConfig, self).__init__() def __getattr__(self, item): - """Return instance of deferred config.""" + """Return instance of deferred config. + + :param item: Name of configuration option or section. + :type item: str + + :rtype: :py:class:`ChildConfig` + """ return ChildConfig(parents=self.parents + (item,), root_config=self.root_config) def _provide(self, *args, **kwargs): - """Return provided instance.""" + """Return provided instance. + + :param args: Tuple of context positional arguments. + :type args: tuple[object] + + :param kwargs: Dictionary of context keyword arguments. + :type kwargs: dict[str, object] + + :rtype: object + """ return self.root_config(self.parents) diff --git a/dependency_injector/utils.py b/dependency_injector/utils.py index cb837d87..14caf4b1 100644 --- a/dependency_injector/utils.py +++ b/dependency_injector/utils.py @@ -8,10 +8,20 @@ from .errors import Error GLOBAL_LOCK = threading.RLock() +"""Dependency injector global reentrant lock. + +:type: :py:class:`threading.RLock` +""" def is_provider(instance): - """Check if instance is provider instance.""" + """Check if instance is provider instance. + + :param instance: Instance to be checked. + :type instance: object + + :rtype: bool + """ return (not isinstance(instance, six.class_types) and hasattr(instance, '__IS_PROVIDER__') and getattr(instance, '__IS_PROVIDER__') is True) @@ -20,7 +30,13 @@ def is_provider(instance): def ensure_is_provider(instance): """Check if instance is provider instance and return it. - :raise: Error if provided instance is not provider. + :param instance: Instance to be checked. + :type instance: object + + :raise: :py:exc:`dependency_injector.errors.Error` if provided instance is + not provider. + + :rtype: :py:class:`dependency_injector.providers.Provider` """ if not is_provider(instance): raise Error('Expected provider instance, ' @@ -29,14 +45,29 @@ def ensure_is_provider(instance): def is_injection(instance): - """Check if instance is injection instance.""" + """Check if instance is injection instance. + + :param instance: Instance to be checked. + :type instance: object + + :rtype: bool + """ return (not isinstance(instance, six.class_types) and hasattr(instance, '__IS_INJECTION__') and getattr(instance, '__IS_INJECTION__') is True) def ensure_is_injection(instance): - """Check if instance is injection instance, otherwise raise and error.""" + """Check if instance is injection instance and return it. + + :param instance: Instance to be checked. + :type instance: object + + :raise: :py:exc:`dependency_injector.errors.Error` if provided instance is + not injection. + + :rtype: :py:class:`dependency_injector.injections.Injection` + """ if not is_injection(instance): raise Error('Expected injection instance, ' 'got {0}'.format(str(instance))) @@ -44,51 +75,99 @@ def ensure_is_injection(instance): def is_arg_injection(instance): - """Check if instance is positional argument injection instance.""" + """Check if instance is positional argument injection instance. + + :param instance: Instance to be checked. + :type instance: object + + :rtype: bool + """ return (not isinstance(instance, six.class_types) and hasattr(instance, '__IS_ARG_INJECTION__') and getattr(instance, '__IS_ARG_INJECTION__', False) is True) def is_kwarg_injection(instance): - """Check if instance is keyword argument injection instance.""" + """Check if instance is keyword argument injection instance. + + :param instance: Instance to be checked. + :type instance: object + + :rtype: bool + """ return (not isinstance(instance, six.class_types) and hasattr(instance, '__IS_KWARG_INJECTION__') and getattr(instance, '__IS_KWARG_INJECTION__', False) is True) def is_attribute_injection(instance): - """Check if instance is attribute injection instance.""" + """Check if instance is attribute injection instance. + + :param instance: Instance to be checked. + :type instance: object + + :rtype: bool + """ return (not isinstance(instance, six.class_types) and hasattr(instance, '__IS_ATTRIBUTE_INJECTION__') and getattr(instance, '__IS_ATTRIBUTE_INJECTION__', False) is True) def is_method_injection(instance): - """Check if instance is method injection instance.""" + """Check if instance is method injection instance. + + :param instance: Instance to be checked. + :type instance: object + + :rtype: bool + """ return (not isinstance(instance, six.class_types) and hasattr(instance, '__IS_METHOD_INJECTION__') and getattr(instance, '__IS_METHOD_INJECTION__', False) is True) def is_catalog(instance): - """Check if instance 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.""" + """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.""" + """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 is_catalog_bundle(instance): - """Check if instance is catalog bundle instance.""" + """Check if instance is catalog bundle instance. + + :param instance: Instance to be checked. + :type instance: object + + :rtype: bool + """ return (not isinstance(instance, six.class_types) and hasattr(instance, '__IS_CATALOG_BUNDLE__') and getattr(instance, '__IS_CATALOG_BUNDLE__', False) is True) @@ -97,7 +176,13 @@ def is_catalog_bundle(instance): def ensure_is_catalog_bundle(instance): """Check if instance is catalog bundle instance and return it. - :raise: Error if provided instance is not catalog bundle. + :param instance: Instance to be checked. + :type instance: object + + :raise: :py:exc:`dependency_injector.errors.Error` if provided instance + is not catalog bundle. + + :rtype: :py:class:`dependency_injector.catalogs.CatalogBundle` """ if not is_catalog_bundle(instance): raise Error('Expected catalog bundle instance, ' diff --git a/docs/advanced_usage/index.rst b/docs/advanced_usage/index.rst index c2133e63..2548e27e 100644 --- a/docs/advanced_usage/index.rst +++ b/docs/advanced_usage/index.rst @@ -7,28 +7,35 @@ Current section of documentation describes advanced usage of @inject decorator ----------------- -``@di.inject()`` decorator can be used for making *inline* dependency +.. module:: dependency_injector.injections + +:py:func:`inject` decorator is a part of +:py:mod:`dependency_injector.injections` module. + +:py:func:`inject` decorator can be used for making *inline* dependency injections. It *patches* decorated callable in such way that dependency injection will be done during every call of decorated callable. -``di.inject()`` takes a various number of positional and keyword arguments +:py:func:`inject` takes a various number of positional and keyword arguments that are used as decorated callable injections. Every time, when -``di.inject()`` is called, positional and keyword argument injections would be -passed as an callable arguments. +:py:func:`inject` is called, positional and keyword argument injections would +be passed as an callable arguments. Such behaviour is very similar to the standard Python ``functools.partial`` object, except of one thing: all injectable values are provided -*"as is"*, except of providers (subclasses of ``di.Provider``). Providers +*"as is"*, except of providers (subclasses of +:py:class:`dependency_injector.providers.Provider`). Providers will be called every time, when injection needs to be done. For example, -if injectable value of injection is a ``di.Factory``, it will provide new one +if injectable value of injection is a +:py:class:`dependency_injector.providers.Factory`, it will provide new one instance (as a result of its call) every time, when injection needs to be done. -``di.inject()`` behaviour with context positional and keyword arguments is +:py:func:`inject` behaviour with context positional and keyword arguments is very like a standard Python ``functools.partial``: -- Positional context arguments will be appended after ``di.inject()`` +- Positional context arguments will be appended after :py:func:`inject` positional injections. -- Keyword context arguments have priority on ``di.inject()`` keyword +- Keyword context arguments have priority on :py:func:`inject` keyword injections and will be merged over them. Example: @@ -36,7 +43,7 @@ Example: .. literalinclude:: ../../examples/advanced_usage/inject_simple.py :language: python -Example of usage ``@di.inject()`` decorator with Flask: +Example of usage :py:func:`inject` decorator with Flask: .. literalinclude:: ../../examples/advanced_usage/inject_flask.py :language: python @@ -45,11 +52,12 @@ Example of usage ``@di.inject()`` decorator with Flask: @inject decorator with classes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -``@di.inject()`` could be applied for classes. In such case, it will look for +:py:func:`inject` could be applied for classes. In such case, it will look for class ``__init__()`` method and pass injection to it. If decorated class has -no ``__init__()`` method, appropriate ``di.Error`` will be raised. +no ``__init__()`` method, appropriate +:py:exc:`dependency_injector.errors.Error` will be raised. -Example of usage ``@di.inject()`` with Flask class-based view: +Example of usage :py:func:`inject` with Flask class-based view: .. literalinclude:: ../../examples/advanced_usage/inject_flask_class_based.py :language: python diff --git a/docs/api/catalogs.rst b/docs/api/catalogs.rst new file mode 100644 index 00000000..90c22739 --- /dev/null +++ b/docs/api/catalogs.rst @@ -0,0 +1,7 @@ +``dependency_injector.catalogs`` +-------------------------------- + +.. automodule:: dependency_injector.catalogs + :members: + :member-order: bysource + :special-members: diff --git a/docs/api/errors.rst b/docs/api/errors.rst new file mode 100644 index 00000000..c58cbf23 --- /dev/null +++ b/docs/api/errors.rst @@ -0,0 +1,6 @@ +``dependency_injector.errors`` +------------------------------ + +.. automodule:: dependency_injector.errors + :members: + :member-order: bysource diff --git a/docs/api/index.rst b/docs/api/index.rst new file mode 100644 index 00000000..6d767939 --- /dev/null +++ b/docs/api/index.rst @@ -0,0 +1,12 @@ +API Documentation +================= + +.. toctree:: + :maxdepth: 2 + + top_level + providers + injections + catalogs + utils + errors diff --git a/docs/api/injections.rst b/docs/api/injections.rst new file mode 100644 index 00000000..d7ef5949 --- /dev/null +++ b/docs/api/injections.rst @@ -0,0 +1,7 @@ +``dependency_injector.injections`` +---------------------------------- + +.. automodule:: dependency_injector.injections + :members: + :member-order: bysource + :inherited-members: diff --git a/docs/api/providers.rst b/docs/api/providers.rst new file mode 100644 index 00000000..5e47db10 --- /dev/null +++ b/docs/api/providers.rst @@ -0,0 +1,7 @@ +``dependency_injector.providers`` +--------------------------------- + +.. automodule:: dependency_injector.providers + :members: + :member-order: bysource + :inherited-members: diff --git a/docs/api/top_level.rst b/docs/api/top_level.rst new file mode 100644 index 00000000..22e22af0 --- /dev/null +++ b/docs/api/top_level.rst @@ -0,0 +1,5 @@ +``dependency_injector`` +----------------------- + +.. automodule:: dependency_injector + :members: VERSION diff --git a/docs/api/utils.rst b/docs/api/utils.rst new file mode 100644 index 00000000..a999772e --- /dev/null +++ b/docs/api/utils.rst @@ -0,0 +1,6 @@ +``dependency_injector.utils`` +----------------------------- + +.. automodule:: dependency_injector.utils + :members: + :member-order: bysource diff --git a/docs/catalogs/bundles.rst b/docs/catalogs/bundles.rst index 50c76866..b9d305b9 100644 --- a/docs/catalogs/bundles.rst +++ b/docs/catalogs/bundles.rst @@ -1,23 +1,43 @@ -Creating catalog provider bundles ---------------------------------- +Catalog provider bundles +------------------------ -``di.DeclarativeCatalog.Bundle`` is a limited collection of catalog providers. -While catalog could be used as a centralized place for particular providers -group, such bundles of catalog providers can be used for creating several -limited scopes that could be passed to different subsystems. +.. module:: dependency_injector.catalogs -``di.DeclarativeCatalog.Bundle`` has exactly the same API as -``di.DeclarativeCatalog`` except of the limitations on getting providers. +:py:class:`CatalogBundle` is a frozen, limited collection of catalog +providers. While catalog could be used as a centralized place for +particular providers group, such bundles of catalog providers can be used +for creating several frozen, limited scopes that could be passed to different +subsystems. -Each ``di.DeclarativeCatalog`` has a reference to its bundle class - -``di.DeclarativeCatalog.Bundle``. For example, if some concrete catalog has name -``SomeCatalog``, then its bundle class could be reached as -``SomeCatalog.Bundle``. +:py:class:`CatalogBundle` has API's parity with catalogs +(:py:class:`DeclarativeCatalog` or :py:class:`DynamicCatalog`) in terms of +retrieving the providers, but it is "frozen" in terms of modification +provider's list. -``di.DeclarativeCatalog.Bundle`` expects to get the list of its catalog providers +:py:class:`CatalogBundle` is considered to be dependable on catalogs +(:py:class:`DeclarativeCatalog` or :py:class:`DynamicCatalog`) entity by +its design. + +Each catalog (:py:class:`DeclarativeCatalog` or :py:class:`DynamicCatalog`) +has a reference to its bundle class - :py:attr:`DeclarativeCatalog.Bundle` +(or :py:attr:`DynamicCatalog.Bundle` consequently). For example, subclass of +:py:class:`CatalogBundle` for some concrete declarative catalog +``SomeCatalog`` could be reached as ``SomeCatalog.Bundle``. + +:py:class:`CatalogBundle` expects to get the list of its catalog providers as positional arguments and will limit the scope of created bundle to this list. +.. note:: + + Some notes about :py:class:`CatalogBundle` design. + + Design and syntax of :py:class:`CatalogBundle` was developed with the idea + of keeping full functionalities of type-hinting and introspection of + modern IDE's. This design came from some practical experience of using + :py:class:`CatalogBundle` and considered to be the most comfortable for + developer. + Example: .. image:: /images/catalogs/bundles.png diff --git a/docs/catalogs/declarative.rst b/docs/catalogs/declarative.rst new file mode 100644 index 00000000..52d369c4 --- /dev/null +++ b/docs/catalogs/declarative.rst @@ -0,0 +1,61 @@ +Declarative catalogs +-------------------- + +.. module:: dependency_injector.catalogs + +: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). + +Declarative catalogs have to extend base declarative catalog class - +:py:class:`dependency_injector.catalogs.DeclarativeCatalog`. + +Declarative catalog's providers have to be defined like catalog's class +attributes. Every provider in catalog has name. This name should follow +``some_provider`` convention, that is standard naming convention for +attribute names in Python. + +.. note:: + + Declarative catalogs have several features that could be useful + for some kind of operations on catalog's providers, please visit API + documentation for getting full list of features - + :py:class:`dependency_injector.catalogs.DeclarativeCatalog`. + +.. note:: + + It might be useful to add such + ``""":type: dependency_injector.providers.Provider -> Object1"""`` + docstrings just on the next line after provider's definition. It will + help code analyzing tools and IDE's to understand that variable above + contains some callable object, that returns particular instance as a + result of its call. + +Here is an simple example of defining declarative catalog with several +factories: + +.. image:: /images/catalogs/declarative.png + :width: 85% + :align: center + +.. literalinclude:: ../../examples/catalogs/declarative.py + :language: python + +Example of declarative catalogs inheritance: + +.. image:: /images/catalogs/declarative_inheritance.png + :width: 100% + :align: center + +.. literalinclude:: ../../examples/catalogs/declarative_inheritance.py + :language: python + +Example of declarative catalog's provider injections: + +.. image:: /images/catalogs/declarative_injections.png + :width: 100% + :align: center + +.. literalinclude:: ../../examples/catalogs/declarative_injections.py + :language: python diff --git a/docs/catalogs/dynamic.rst b/docs/catalogs/dynamic.rst new file mode 100644 index 00000000..22439464 --- /dev/null +++ b/docs/catalogs/dynamic.rst @@ -0,0 +1,29 @@ +Dynamic catalogs +---------------- + +.. module:: dependency_injector.catalogs + +: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). + +:py:class:`DeclarativeCatalog` and :py:class:`DynamicCatalog` have +100% API parity. + +Main difference between :py:class:`DeclarativeCatalog` and +:py:class:`DynamicCatalog` is that :py:class:`DeclarativeCatalog` acts on +class-level, while :py:class:`DynamicCatalog` do the same on +instance-level. + +Here is an simple example of defining dynamic catalog with several factories: + +.. literalinclude:: ../../examples/catalogs/dynamic.py + :language: python + +Next one example demonstrates creation and runtime filling of dynamic catalog: + +.. literalinclude:: ../../examples/catalogs/dynamic_runtime_creation.py + :language: python diff --git a/docs/catalogs/index.rst b/docs/catalogs/index.rst index cfc3d579..637d8352 100644 --- a/docs/catalogs/index.rst +++ b/docs/catalogs/index.rst @@ -16,10 +16,12 @@ Also, for both of these and some other cases, it might be useful to attach some init / shutdown functionality or something else, that deals with group of providers. +Catalogs module API docs - :py:mod:`dependency_injector.catalogs`. + .. toctree:: :maxdepth: 2 - writing - operating + declarative + dynamic bundles overriding diff --git a/docs/catalogs/operating.rst b/docs/catalogs/operating.rst deleted file mode 100644 index 1841c4a3..00000000 --- a/docs/catalogs/operating.rst +++ /dev/null @@ -1,30 +0,0 @@ -Operating with catalogs ------------------------ - -``di.DeclarativeCatalog`` has several features that could be useful for some -kind of operations on catalog's providers: - -- ``di.DeclarativeCatalog.providers`` is read-only attribute that contains - ``dict`` of all catalog providers, including providers that are inherited - from parent catalogs, where key is the name of provider and value is - provider itself. -- ``di.DeclarativeCatalog.cls_providers`` is read-only attribute contains - ``dict`` of current catalog providers, where key is the name of provider - and value is provider itself. -- ``di.DeclarativeCatalog.inherited_providers`` is read-only attribute - contains ``dict`` of all providers that are inherited from parent catalogs, - where key is the name of provider and value is provider itself. -- ``di.DeclarativeCatalog.filter(provider_type=di.Provider)`` is a class - method that could be used for filtering catalog providers by provider types - (for example, for getting all ``di.Factory`` providers). - ``di.DeclarativeCatalog.filter()`` method use - ``di.DeclarativeCatalog.providers``. - -Example: - -.. image:: /images/catalogs/operating_with_providers.png - :width: 100% - :align: center - -.. literalinclude:: ../../examples/catalogs/operating_with_providers.py - :language: python diff --git a/docs/catalogs/overriding.rst b/docs/catalogs/overriding.rst index feb6dfa5..7403d400 100644 --- a/docs/catalogs/overriding.rst +++ b/docs/catalogs/overriding.rst @@ -1,35 +1,49 @@ Overriding of catalogs ---------------------- +.. module:: dependency_injector.catalogs + Catalogs can be overridden by other catalogs. This, actually, means that all of the providers from overriding catalog will override providers with the same names in overridden catalog. -There are two ways to override catalog by another catalog: +There are two ways to override :py:class:`DeclarativeCatalog` with another +catalog: -- Use ``di.DeclarativeCatalog.override(AnotherCatalog)`` method. -- Use ``@di.override(AnotherCatalog)`` class decorator. +- Use :py:meth:`DeclarativeCatalog.override` method. +- Use :py:func:`override` class decorator. -Example of overriding catalog using ``di.DeclarativeCatalog.override()`` +Example of overriding catalog using :py:meth:`DeclarativeCatalog.override` method: -.. literalinclude:: ../../examples/catalogs/override.py +.. literalinclude:: ../../examples/catalogs/override_declarative.py :language: python -Example of overriding catalog using ``@di.override()`` decorator: +Example of overriding catalog using :py:func:`override` decorator: -.. literalinclude:: ../../examples/catalogs/override_decorator.py +.. literalinclude:: ../../examples/catalogs/override_declarative_decorator.py :language: python -Also there are several useful methods and properties that help to work with -catalog overridings: +Also there are several useful :py:class:`DeclarativeCatalog` methods and +properties that help to work with catalog overridings: -- ``di.DeclarativeCatalog.is_overridden`` - read-only, evaluated in runtime, - property that is set to True if catalog is overridden. -- ``di.DeclarativeCatalog.last_overriding`` - reference to the last overriding - catalog, if any. -- ``di.DeclarativeCatalog.overridden_by`` - tuple of all overriding catalogs. -- ``di.DeclarativeCatalog.reset_last_overriding()`` - reset last overriding - catalog. -- ``di.DeclarativeCatalog.reset_override()`` - reset all overridings for all - catalog providers. +- :py:attr:`DeclarativeCatalog.is_overridden` - read-only property that is set + to ``True`` if catalog is overridden. +- :py:attr:`DeclarativeCatalog.last_overriding` - read-only reference to + the last overriding catalog, if any. +- :py:attr:`DeclarativeCatalog.overridden_by` - tuple of all overriding + catalogs. +- :py:meth:`DeclarativeCatalog.reset_last_overriding()` - reset last + overriding catalog. +- :py:meth:`DeclarativeCatalog.reset_override()` - reset all overridings for + all catalog providers. + +:py:class:`DynamicCatalog` has exactly the same functionality, except of +:py:func:`override` decorator. Also :py:class:`DynamicCatalog` can override +:py:class:`DeclarativeCatalog` and vise versa. + +Example of overriding :py:class:`DeclarativeCatalog` by +:py:class:`DynamicCatalog`: + +.. literalinclude:: ../../examples/catalogs/override_declarative_by_dynamic.py + :language: python diff --git a/docs/catalogs/writing.rst b/docs/catalogs/writing.rst deleted file mode 100644 index c6ee929f..00000000 --- a/docs/catalogs/writing.rst +++ /dev/null @@ -1,25 +0,0 @@ -Writing catalogs ----------------- - -Catalogs have to extend base catalog class ``di.DeclarativeCatalog``. - -Providers have to be defined like catalog's attributes. Every provider in -catalog has name. This name should follow ``some_provider`` convention, -that is standard naming convention for attribute names in Python. - -.. note:: - - It might be useful to add such ``""":type: di.Provider -> Object1"""`` - docstrings just on the next line after provider's definition. It will - help code analyzing tools and IDE's to understand that variable above - contains some callable object, that returns particular instance as a - result of its call. - -Here is an simple example of catalog with several factories: - -.. image:: /images/catalogs/writing_catalogs.png - :width: 85% - :align: center - -.. literalinclude:: ../../examples/catalogs/writing_catalogs.py - :language: python diff --git a/docs/conf.py b/docs/conf.py index b4abe519..c225b114 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -28,7 +28,7 @@ import re # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = [] +extensions = ['sphinx.ext.autodoc'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/docs/images/catalogs/bundles.png b/docs/images/catalogs/bundles.png index 29ff4e48..f015088a 100644 Binary files a/docs/images/catalogs/bundles.png and b/docs/images/catalogs/bundles.png differ diff --git a/docs/images/catalogs/declarative.png b/docs/images/catalogs/declarative.png new file mode 100644 index 00000000..81d3740f Binary files /dev/null and b/docs/images/catalogs/declarative.png differ diff --git a/docs/images/catalogs/declarative_inheritance.png b/docs/images/catalogs/declarative_inheritance.png new file mode 100644 index 00000000..53dc40c1 Binary files /dev/null and b/docs/images/catalogs/declarative_inheritance.png differ diff --git a/docs/images/catalogs/declarative_injections.png b/docs/images/catalogs/declarative_injections.png new file mode 100644 index 00000000..2a487d32 Binary files /dev/null and b/docs/images/catalogs/declarative_injections.png differ diff --git a/docs/images/catalogs/operating_with_providers.png b/docs/images/catalogs/operating_with_providers.png deleted file mode 100644 index 759ce094..00000000 Binary files a/docs/images/catalogs/operating_with_providers.png and /dev/null differ diff --git a/docs/images/catalogs/writing_catalogs.png b/docs/images/catalogs/writing_catalogs.png deleted file mode 100644 index fdc84ec5..00000000 Binary files a/docs/images/catalogs/writing_catalogs.png and /dev/null differ diff --git a/docs/images/internals.png b/docs/images/internals.png index 1c595806..bd91b3f5 100644 Binary files a/docs/images/internals.png and b/docs/images/internals.png differ diff --git a/docs/images/providers/callable.png b/docs/images/providers/callable.png index 4e3c20a2..76271ad3 100644 Binary files a/docs/images/providers/callable.png and b/docs/images/providers/callable.png differ diff --git a/docs/images/providers/custom_provider.png b/docs/images/providers/custom_provider.png index 784c90bd..398e6bfa 100644 Binary files a/docs/images/providers/custom_provider.png and b/docs/images/providers/custom_provider.png differ diff --git a/docs/images/providers/external_dependency.png b/docs/images/providers/external_dependency.png index 28662c94..a02affbe 100644 Binary files a/docs/images/providers/external_dependency.png and b/docs/images/providers/external_dependency.png differ diff --git a/docs/images/providers/factory.png b/docs/images/providers/factory.png index 1b0dc8a0..86830b60 100644 Binary files a/docs/images/providers/factory.png and b/docs/images/providers/factory.png differ diff --git a/docs/images/providers/factory_attribute_injection.png b/docs/images/providers/factory_attribute_injection.png new file mode 100644 index 00000000..0edda7ab Binary files /dev/null and b/docs/images/providers/factory_attribute_injection.png differ diff --git a/docs/images/providers/factory_attribute_injections.png b/docs/images/providers/factory_attribute_injections.png index df963f0d..0edda7ab 100644 Binary files a/docs/images/providers/factory_attribute_injections.png and b/docs/images/providers/factory_attribute_injections.png differ diff --git a/docs/images/providers/factory_delegation.png b/docs/images/providers/factory_delegation.png index 28703c76..41192639 100644 Binary files a/docs/images/providers/factory_delegation.png and b/docs/images/providers/factory_delegation.png differ diff --git a/docs/images/providers/factory_init_injections.png b/docs/images/providers/factory_init_injections.png index b7a4f1fd..15909700 100644 Binary files a/docs/images/providers/factory_init_injections.png and b/docs/images/providers/factory_init_injections.png differ diff --git a/docs/images/providers/factory_init_injections_and_contexts.png b/docs/images/providers/factory_init_injections_and_contexts.png index 3945e923..8958edfe 100644 Binary files a/docs/images/providers/factory_init_injections_and_contexts.png and b/docs/images/providers/factory_init_injections_and_contexts.png differ diff --git a/docs/images/providers/factory_method_injections.png b/docs/images/providers/factory_method_injections.png index 0b0362b4..caab35a1 100644 Binary files a/docs/images/providers/factory_method_injections.png and b/docs/images/providers/factory_method_injections.png differ diff --git a/docs/images/providers/overriding_simple.png b/docs/images/providers/overriding_simple.png index a3d949da..5efb1e78 100644 Binary files a/docs/images/providers/overriding_simple.png and b/docs/images/providers/overriding_simple.png differ diff --git a/docs/images/providers/overriding_users_model.png b/docs/images/providers/overriding_users_model.png index f2763621..86c534b2 100644 Binary files a/docs/images/providers/overriding_users_model.png and b/docs/images/providers/overriding_users_model.png differ diff --git a/docs/images/providers/provider_override.png b/docs/images/providers/provider_override.png index 2a523605..19a5057b 100644 Binary files a/docs/images/providers/provider_override.png and b/docs/images/providers/provider_override.png differ diff --git a/docs/images/providers/singleton.png b/docs/images/providers/singleton.png index 595d359f..3cc3f598 100644 Binary files a/docs/images/providers/singleton.png and b/docs/images/providers/singleton.png differ diff --git a/docs/images/providers/singleton_internals.png b/docs/images/providers/singleton_internals.png index f05fb902..a327f390 100644 Binary files a/docs/images/providers/singleton_internals.png and b/docs/images/providers/singleton_internals.png differ diff --git a/docs/index.rst b/docs/index.rst index d4d648ed..bedcdd2a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -55,5 +55,6 @@ Contents providers/index catalogs/index advanced_usage/index + api/index main/feedback main/changelog diff --git a/docs/main/changelog.rst b/docs/main/changelog.rst index 6bcb2693..ec58572c 100644 --- a/docs/main/changelog.rst +++ b/docs/main/changelog.rst @@ -9,13 +9,31 @@ follows `Semantic versioning`_ Development version ------------------- -- Rename ``di.AbstractCatalog`` to ``di.DeclarativeCatalog`` +- No features. + +0.11.0 +------ +- Rename ``AbstractCatalog`` to ``DeclarativeCatalog`` (with backward compatibility). +- Rename ``catalog`` module to ``catalogs`` with backward compatibility. +- Implement dynamic binding of providers for ``DeclarativeCatalog``. +- Add ``DynamicCatalog``. +- Change restrictions for providers-to-catalogs bindings - provider could be + bound to several catalogs with different names. +- Restrict overriding of providers by themselves. +- Restrict overriding of catalogs by themselves. +- Make ``DeclarativeCatalog.last_overriding`` attribute to be ``None`` by + default. +- Make ``Provider.last_overriding`` attribute to be ``None`` by + default. +- Refactor catalogs and providers modules. +- Add API documentation +- Improve user's guides and examples. 0.10.5 ------ -- Add more representable implementation for ``di.AbstractCatalog`` and - ``di.AbstractCatalog.Bundle``. +- Add more representable implementation for ``AbstractCatalog`` and + ``AbstractCatalog.Bundle``. 0.10.4 ------ @@ -35,22 +53,22 @@ Development version 0.10.0 ------ -- Add functionality for creating ``di.AbstractCatalog`` provider bundles. -- Improve ``di.AbstractCatalog`` inheritance. -- Improve ``di.AbstractCatalog`` overriding. +- Add functionality for creating ``AbstractCatalog`` provider bundles. +- Improve ``AbstractCatalog`` inheritance. +- Improve ``AbstractCatalog`` overriding. - Add images for catalog "Writing catalogs" and "Operating with catalogs" examples. - Add functionality for using positional argument injections with - ``di.Factory``, ``di.Singleton``, ``di.Callable`` providers and - ``di.inject`` decorator. -- Add functionality for decorating classes with ``@di.inject``. -- Add ``di.Singleton.injections`` attribute that represents a tuple of all - ``di.Singleton`` injections (including args, kwargs, attributes and methods). -- Add ``di.Callable.injections`` attribute that represents a tuple of all - ``di.Callable`` injections (including args and kwargs). -- Add optimization for ``di.Injection.value`` property that will compute + ``Factory``, ``Singleton``, ``Callable`` providers and + ``inject`` decorator. +- Add functionality for decorating classes with ``@inject``. +- Add ``Singleton.injections`` attribute that represents a tuple of all + ``Singleton`` injections (including args, kwargs, attributes and methods). +- Add ``Callable.injections`` attribute that represents a tuple of all + ``Callable`` injections (including args and kwargs). +- Add optimization for ``Injection.value`` property that will compute type of injection once, instead of doing this on every call. -- Add ``di.VERSION`` constant for verification of currently installed version. +- Add ``VERSION`` constant for verification of currently installed version. - Add support of Python 3.5. - Add support of six 1.10.0. - Add minor refactorings and code style fixes. @@ -58,8 +76,8 @@ Development version 0.9.5 ----- - Change provider attributes scope to public. -- Add ``di.Factory.injections`` attribute that represents a tuple of all - ``di.Factory`` injections (including kwargs, attributes and methods). +- Add ``Factory.injections`` attribute that represents a tuple of all + ``Factory`` injections (including kwargs, attributes and methods). 0.9.4 ----- diff --git a/docs/main/installation.rst b/docs/main/installation.rst index 3c213111..1c2fafe8 100644 --- a/docs/main/installation.rst +++ b/docs/main/installation.rst @@ -12,7 +12,7 @@ framework can be installed from PyPi_: pip install dependency_injector # Installing particular version: - pip install dependency_injector==0.9.0 + pip install dependency_injector==0.11.0 Sources can be cloned from GitHub_: @@ -23,14 +23,14 @@ Sources can be cloned from GitHub_: Also all *Dependency Injector* releases can be downloaded from `GitHub releases page`_. -Verification of currently installed version could be done using ``di.VERSION`` -constant: +Verification of currently installed version could be done using +:py:obj:`dependency_injector.VERSION` constant: .. code-block:: bash - >>> import dependency_injector as di - >>> di.VERSION - '0.10.0' + >>> from dependency_injector import VERSION + >>> VERSION + '0.11.0' .. _PyPi: https://pypi.python.org/pypi/dependency_injector .. _GitHub: https://github.com/rmk135/dependency_injector diff --git a/docs/main/introduction.rst b/docs/main/introduction.rst index 5467f43e..ab0f1a57 100644 --- a/docs/main/introduction.rst +++ b/docs/main/introduction.rst @@ -57,27 +57,6 @@ framework: Main idea of *Dependency Injector* is to keep dependencies under control. -Shortcuts ---------- - -*Dependency Injector* recommends to use such kind of import shortcut: - -.. code-block:: python - - import dependency_injector as di - -- All *Dependency Injector* entities could be used just from top-level package - (like ``di.Factory``, ``di.inject``, ``di.AbstractCatalog`` and so on). -- Another one way is to use second level packages (like - ``di.providers.Factory``, ``di.injections.inject``, - ``di.catalog.AbstractCatalog`` and so on). It might be useful for improving - of readability in some cases. - -.. note:: - - ``import dependency_injector as di`` shortcut is used among current - documentation, images and examples. - Main entities ------------- @@ -91,18 +70,21 @@ interaction with each other. There are 3 main entities: - Providers. Providers are strategies of accesing objects. For example, - ``di.providers.Factory`` creates new instance of provided - class every time it is called. ``di.providers.Singleton`` creates - provided instance once and returns it on every next call. Providers - could be overridden by another providers. Base class is - - ``di.providers.Provider``. + :py:class:`dependency_injector.providers.Factory` creates new instance of + provided class every time it is called. + :py:class:`dependency_injector.providers.Singleton` creates provided + instance once and returns it on every next call. Providers could be + overridden by another providers. Base class is - + :py:class:`dependency_injector.providers.Provider`. - Injections. Injections are instructions for making dependency injections (there are several ways how they could be done). Injections are used mostly - by ``di.providers.Factory`` and ``di.providers.Singleton`` providers, but - these are not only cases. Base class is - ``di.injections.Injection``. + by :py:class:`dependency_injector.providers.Factory` and + :py:class:`dependency_injector.providers.Singleton` providers, but + these are not only cases. Base class is - + :py:class:`dependency_injector.injections.Injection`. - Catalogs. Catalogs are collections of providers. They are used for grouping of providers by some principles. Base class is - - ``di.catalog.AbstractCatalog``. + :py:class:`dependency_injector.catalogs.DeclarativeCatalog`. .. _SLOC: http://en.wikipedia.org/wiki/Source_lines_of_code diff --git a/docs/providers/callable.rst b/docs/providers/callable.rst index 0fcf0944..624c5f0d 100644 --- a/docs/providers/callable.rst +++ b/docs/providers/callable.rst @@ -1,40 +1,43 @@ Callable providers ------------------ -``di.Callable`` provider is a provider that wraps particular callable with +.. module:: dependency_injector.providers + +:py:class:`Callable` provider is a provider that wraps particular callable with some injections. Every call of this provider returns result of call of initial callable. Callable providers and injections ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -``di.Callable`` takes a various number of positional and keyword arguments -that are used as decorated callable injections. Every time, when -``di.Callable`` is called, positional and keyword argument injections would be -passed as an callable arguments. +:py:class:`Callable` takes a various number of positional and keyword +arguments that are used as decorated callable injections. Every time, when +:py:class:`Callable` is called, positional and keyword argument injections +would be passed as an callable arguments. Such behaviour is very similar to the standard Python ``functools.partial`` object, except of one thing: all injectable values are provided -*"as is"*, except of providers (subclasses of ``di.Provider``). Providers +*"as is"*, except of providers (subclasses of :py:class:`Provider`). Providers will be called every time, when injection needs to be done. For example, -if injectable value of injection is a ``di.Factory``, it will provide new one -instance (as a result of its call) every time, when injection needs to be done. +if injectable value of injection is a :py:class:`Factory`, it will provide +new one instance (as a result of its call) every time, when injection needs +to be done. -``di.Callable`` behaviour with context positional and keyword arguments is -very like a standard Python ``functools.partial``: +:py:class:`Callable` behaviour with context positional and keyword arguments +is very like a standard Python ``functools.partial``: -- Positional context arguments will be appended after ``di.Callable`` +- Positional context arguments will be appended after :py:class:`Callable` positional injections. -- Keyword context arguments have priority on ``di.Callable`` keyword +- Keyword context arguments have priority on :py:class:`Callable` keyword injections and will be merged over them. -Example that shows usage of ``di.Callable`` with positional argument +Example that shows usage of :py:class:`Callable` with positional argument injections: .. literalinclude:: ../../examples/providers/callable_args.py :language: python -Next one example shows usage of ``di.Callable`` with keyword argument +Next one example shows usage of :py:class:`Callable` with keyword argument injections: .. image:: /images/providers/callable.png @@ -47,13 +50,13 @@ injections: Callable providers delegation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -``di.Callable`` provider could be delegated to any other provider via any kind -of injection. Delegation of ``di.Callable`` providers is the same as -``di.Factory`` and ``di.Singleton`` providers delegation, please follow -*Factory providers delegation* section for example. +:py:class:`Callable` provider could be delegated to any other provider via any +kind of injection. Delegation of :py:class:`Callable` providers is the same as +:py:class:`Factory` and :py:class:`Singleton` providers delegation, please +follow *Factory providers delegation* section for example. -``di.Callable`` delegate could be created obviously using -``di.Delegate(di.Callable())`` or by calling ``di.Callable.delegate()`` method. +:py:class:`Callable` delegate could be created obviously using +``Delegate(Callable(...))`` or by calling ``Callable(...).delegate()`` method. Example: diff --git a/docs/providers/custom.rst b/docs/providers/custom.rst index 2d5374e8..effd87fa 100644 --- a/docs/providers/custom.rst +++ b/docs/providers/custom.rst @@ -1,27 +1,28 @@ -Writing custom providers +Writing of custom providers ------------------------ +.. module:: dependency_injector.providers + List of *Dependency Injector* providers could be widened with custom providers. Below are some tips and recommendations that have to be met: 1. Every custom provider has to extend base provider class - - ``di.Provider``. - 2. Cusom provider's ``__init__()`` could be overriden with only condition: - parent initializer (``di.Provider.__init__()``) has to be called. + :py:class:`Provider`. + 2. Cusom provider's ``__init__()`` could be overriden, but parent's + initializer (:py:meth:`Provider.__init__`) has to be called. 3. Providing strategy has to be implemented in custom provider's - ``_provide()`` method. All ``*args`` & ``**kwargs`` that will be - recieved by ``di.Provider.__call__()`` will be transefed to custom - provider's ``_provide()``. + :py:meth:`Provider._provide` method. All ``*args`` & ``**kwargs`` + that will be recieved by :py:meth:`Provider.__call__` will be + transefed to custom provider's :py:meth:`Provider._provide`. 4. If custom provider is based on some standard providers, it is better to use delegation of standard providers, then extending of them. 5. If custom provider defines any attributes, it is good to list them in ``__slots__`` attribute (as *Dependency Injector* does). It can save some memory. - 6. If custom provider deals with injections (e.g. ``di.Factory``, - ``di.Singleton`` providers), it is strongly recommended to be - consistent with ``di.Factory``, ``di.Singleton`` and ``di.Callable`` - providers style. + 6. If custom provider deals with injections, it is strongly recommended + to be consistent with :py:class:`Factory`, :py:class:`Singleton` and + :py:class:`Callable` providers style. Example: diff --git a/docs/providers/external_dependency.rst b/docs/providers/external_dependency.rst index e07e2679..71b7f4ad 100644 --- a/docs/providers/external_dependency.rst +++ b/docs/providers/external_dependency.rst @@ -1,7 +1,9 @@ External dependency providers ----------------------------- -``di.ExternalDependency`` provider can be useful for development of +.. module:: dependency_injector.providers + +:py:class:`ExternalDependency` provider can be useful for development of self-sufficient libraries / modules / applications that has required external dependencies. diff --git a/docs/providers/factory.rst b/docs/providers/factory.rst index 320e768b..c0097533 100644 --- a/docs/providers/factory.rst +++ b/docs/providers/factory.rst @@ -1,7 +1,10 @@ Factory providers ----------------- -``di.Factory`` provider creates new instance of specified class on every call. +.. module:: dependency_injector.providers + +:py:class:`Factory` provider creates new instance of specified class on every +call. Nothing could be better than brief example: @@ -15,21 +18,22 @@ Nothing could be better than brief example: Factory providers and __init__ injections ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -``di.Factory`` takes a various number of positional and keyword arguments that -are used as ``__init__()`` injections. Every time, when ``di.Factory`` -creates new one instance, positional and keyword argument injections would be -passed as an instance's arguments. +:py:class:`Factory` takes a various number of positional and keyword arguments +that are used as ``__init__()`` injections. Every time, when +:py:class:`Factory` creates new one instance, positional and keyword +argument injections would be passed as an instance's arguments. Such behaviour is very similar to the standard Python ``functools.partial`` object, except of one thing: all injectable values are provided -*"as is"*, except of providers (subclasses of ``di.Provider``). Providers +*"as is"*, except of providers (subclasses of :py:class:`Provider`). Providers will be called every time, when injection needs to be done. For example, -if injectable value of injection is a ``di.Factory``, it will provide new one -instance (as a result of its call) every time, when injection needs to be done. +if injectable value of injection is a :py:class:`Factory`, it will provide +new one instance (as a result of its call) every time, when injection needs +to be done. Example below is a little bit more complicated. It shows how to create -``di.Factory`` of particular class with ``__init__()`` argument injections -which injectable values are also provided by another factories: +:py:class:`Factory` of particular class with ``__init__()`` argument +injections which injectable values are also provided by another factories: .. note:: @@ -58,14 +62,14 @@ Example of usage keyword argument injections: Factory providers and __init__ injections priority ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Next example shows how ``di.Factory`` provider deals with positional and -keyword ``__init__()`` context arguments. In few words, ``di.Factory`` +Next example shows how :py:class:`Factory` provider deals with positional and +keyword ``__init__()`` context arguments. In few words, :py:class:`Factory` behaviour here is very like a standard Python ``functools.partial``: -- Positional context arguments will be appended after ``di.Factory`` +- Positional context arguments will be appended after :py:class:`Factory` positional injections. -- Keyword context arguments have priority on ``di.Factory`` keyword injections - and will be merged over them. +- Keyword context arguments have priority on :py:class:`Factory` keyword + injections and will be merged over them. So, please, follow the example below: @@ -82,34 +86,38 @@ Objects can take dependencies in different forms (some objects take init arguments, other use attributes setting or method calls). It affects how such objects are created and initialized. -``di.Factory`` provider takes various number of positional and keyword +:py:class:`Factory` provider takes various number of positional and keyword arguments, that define what kinds of dependency injections have to be used. -All of those instructions are defined in ``di.injections`` module and are -subclasses of ``di.injections.Injection`` (shortcut ``di.Injection``). There -are several types of injections that are used by ``di.Factory`` provider: +All of those instructions are defined in +:py:mod:`dependency_injector.injections` module and are subclasses of +:py:class:`dependency_injector.injections.Injection`. There are several types +of injections that are used by :py:class:`Factory` provider: -+ ``di.Arg`` - injection is done by passing injectable value in object's - ``__init__()`` method in time of object's creation as positional argument. - Takes injectable value only. -+ ``di.KwArg`` - injection is done by passing injectable value in object's - ``__init__()`` method in time of object's creation as keyword argument. - Takes keyword name of ``__init__()`` argument and injectable value. -+ ``di.Attribute`` - injection is done by setting specified attribute with - injectable value right after object's creation. Takes attribute's name - and injectable value. -+ ``di.Method`` - injection is done by calling of specified method with - injectable value right after object's creation and attribute injections - are done. Takes method name and injectable value. ++ :py:class:`dependency_injector.injections.Arg` - injection is done by + passing injectable value in object's ``__init__()`` method in time of + object's creation as positional argument. Takes injectable value only. ++ :py:class:`dependency_injector.injections.KwArg` - injection is done by + passing injectable value in object's ``__init__()`` method in time of + object's creation as keyword argument. Takes keyword name of + ``__init__()`` argument and injectable value. ++ :py:class:`dependency_injector.injections.Attribute` - injection is done + by setting specified attribute with injectable value right after + object's creation. Takes attribute's name and injectable value. ++ :py:class:`dependency_injector.injections.Method` - injection is done by + calling of specified method with injectable value right after object's + creation and attribute injections are done. Takes method name and + injectable value. -All ``di.Injection``'s injectable values are provided *"as is"*, except of -providers (subclasses of ``di.Provider``). Providers will be called every time, -when injection needs to be done. +All :py:class:`dependency_injector.injections.Injection`'s injectable values +are provided *"as is"*, except of providers (subclasses of +:py:class:`Provider`). Providers will be called every time, when injection +needs to be done. Factory providers and attribute injections ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Example below shows how to create ``di.Factory`` of particular class with +Example below shows how to create :py:class:`Factory` of particular class with attribute injections. Those injections are done by setting specified attributes with injectable values right after object's creation. @@ -123,10 +131,10 @@ Example: Factory providers and method injections ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Current example shows how to create ``di.Factory`` of particular class with -method injections. Those injections are done by calling of specified method -with injectable value right after object's creation and attribute injections -are done. +Current example shows how to create :py:class:`Factory` of particular class +with method injections. Those injections are done by calling of specified +method with injectable value right after object's creation and attribute +injections are done. Method injections are not very popular in Python due Python best practices (usage of public attributes instead of setter methods), but they may appear in @@ -142,21 +150,21 @@ Example: Factory providers delegation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -``di.Factory`` provider could be delegated to any other provider via any kind -of injection. As it was mentioned earlier, if ``di.Factory`` is injectable -value, it will be called every time when injection is done. ``di.Factory`` -delegation is performed by wrapping delegated ``di.Factory`` into special -provider type - ``di.Delegate``, that just returns wrapped ``di.Factory``. -Saying in other words, delegation of factories - is a way to inject factories -themselves, instead of results of their calls. - +:py:class:`Factory` provider could be delegated to any other provider via any +kind of injection. As it was mentioned earlier, if :py:class:`Factory` is +injectable value, it will be called every time when injection is done. +:py:class:`Factory` delegation is performed by wrapping delegated +:py:class:`Factory` into special provider type - :py:class:`Delegate`, that +just returns wrapped :py:class:`Factory`. Saying in other words, delegation +of factories - is a way to inject factories themselves, instead of results +of their calls. Actually, there are two ways of creating factory delegates: -+ ``di.Delegate(di.Factory(...))`` - obviously wrapping factory into - ``di.Delegate`` provider. -+ ``di.Factory(...).delegate()`` - calling factory ``delegate()`` method, that - returns delegate wrapper for current factory. ++ ``Delegate(Factory(...))`` - obviously wrapping factory into + :py:class:`Delegate` provider. ++ ``Factory(...).delegate()`` - calling factory :py:meth:`Factory.delegate` + method, that returns delegate wrapper for current factory. Example: diff --git a/docs/providers/index.rst b/docs/providers/index.rst index e932dc57..1f6a5514 100644 --- a/docs/providers/index.rst +++ b/docs/providers/index.rst @@ -4,17 +4,21 @@ Providers Providers are strategies of accessing objects. They describe how particular objects are provided. +Base providers class is - :py:class:`dependency_injector.providers.Provider` + Every provider is callable (implements ``__call__()``). Every call to provider instance returns provided result, according to the providing strategy of particular provider. Current documentation section consists from description of standard providers library and some useful information like overriding of providers and writing -custom providers. +of custom providers. All providers are validated in multithreading environment and considered to be thread safe. +Providers module API docs - :py:mod:`dependency_injector.providers` + .. toctree:: :maxdepth: 2 diff --git a/docs/providers/overriding.rst b/docs/providers/overriding.rst index 17c887d3..6848641c 100644 --- a/docs/providers/overriding.rst +++ b/docs/providers/overriding.rst @@ -1,6 +1,8 @@ Overriding of providers ----------------------- +.. module:: dependency_injector.providers + Every provider could be overridden by another provider. This gives opportunity to make system behaviour more flexible in some points. @@ -15,22 +17,22 @@ Provider overriding functionality has such interface: :width: 45% :align: center -+ ``di.Provider.override()`` - takes another provider that will be used ++ :py:meth:`Provider.override()` - takes another provider that will be used instead of current provider. This method could be called several times. In such case, last passed provider would be used as overriding one. -+ ``di.Provider.reset_override()`` - resets all overriding providers. Provider - starts to behave itself like usual. -+ ``di.Provider.is_overridden`` - bool, ``True`` if provider is overridden. ++ :py:meth:`Provider.reset_override()` - resets all overriding providers. + Provider starts to behave itself like usual. ++ :py:attr:`Provider.is_overridden` - bool, ``True`` if provider is overridden. .. note:: Actually, initial provider forms stack from overriding providers. There is some, not so common, but still usefull, functionality that could be used: - + ``di.Provider.last_overriding`` - always keeps reference to last + + :py:attr:`Provider.last_overriding` - always keeps reference to last overriding provider. - + ``di.Provider.reset_last_overriding()`` - remove last overriding provider - from stack of overriding providers. + + :py:meth:`Provider.reset_last_overriding()` - remove last overriding + provider from stack of overriding providers. Example: diff --git a/docs/providers/singleton.rst b/docs/providers/singleton.rst index c372e50d..70f575c7 100644 --- a/docs/providers/singleton.rst +++ b/docs/providers/singleton.rst @@ -1,8 +1,10 @@ Singleton providers ------------------- -``di.Singleton`` provider creates new instance of specified class on first call -and returns same instance on every next call. +.. module:: dependency_injector.providers + +:py:class:`Singleton` provider creates new instance of specified class on +first call and returns same instance on every next call. Example: @@ -16,9 +18,9 @@ Example: Singleton providers and injections ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -``di.Singleton`` providers use ``di.Factory`` providers for first creation of -specified class instance, so, all of the rules about injections are the same, -as for ``di.Factory`` providers. +:py:class:`Singleton` providers use :py:class:`Factory` providers for first +creation of specified class instance, so, all of the rules about injections +are the same, as for :py:class:`Factory` providers. .. image:: /images/providers/singleton_internals.png :width: 80% @@ -26,29 +28,29 @@ as for ``di.Factory`` providers. .. note:: - Due that ``di.Singleton`` provider creates specified class instance only on - the first call, all injections are done once, during the first call, also. - Every next call, while instance has been already created and memorized, no - injections are done, ``di.Singleton`` provider just returns memorized - earlier instance. + Due that :py:class:`Singleton` provider creates specified class instance + only on the first call, all injections are done once, during the first + call, also. Every next call, while instance has been already created + and memorized, no injections are done, :py:class:`Singleton` provider just + returns memorized earlier instance. This may cause some problems, for example, in case of trying to bind - ``di.Factory`` provider with ``di.Singleton`` provider (provided by - dependent ``di.Factory`` instance will be injected only once, during the - first call). Be aware that such behaviour was made with opened eyes and is - not a bug. + :py:class:`Factory` provider with :py:class:`Singleton` provider (provided + by dependent :py:class:`Factory` instance will be injected only once, + during the first call). Be aware that such behaviour was made with opened + eyes and is not a bug. - By the way, in such case, ``di.Delegate`` provider can be useful. It makes - possible to inject providers *as is*. Please check out full example in - *Providers delegation* section. + By the way, in such case, :py:class:`Delegate` provider can be useful. It + makes possible to inject providers *as is*. Please check out full example + in *Providers delegation* section. Singleton providers resetting ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Created and memorized by ``di.Singleton`` instance can be reset. Reset of -``di.Singleton``'s memorized instance is done by clearing reference to it. -Further lifecycle of memorized instance is out of ``di.Singleton`` provider's -control. +Created and memorized by :py:class:`Singleton` instance can be reset. Reset of +:py:class:`Singleton`'s memorized instance is done by clearing reference to +it. Further lifecycle of memorized instance is out of :py:class:`Singleton` +provider's control. Example: @@ -58,13 +60,13 @@ Example: Singleton providers delegation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -``di.Singleton`` provider could be delegated to any other provider via any -kind of injection. Delegation of ``di.Singleton`` providers is the same as -``di.Factory`` providers delegation, please follow -*Factory providers delegation* section for example. +:py:class:`Singleton` provider could be delegated to any other provider via +any kind of injection. Delegation of :py:class:`Singleton` providers is the +same as :py:class:`Factory` providers delegation, please follow *Factory +providers delegation* section for example. -``di.Singleton`` delegate could be created obviously using -``di.Delegate(di.Singleton())`` or by calling ``di.Singleton.delegate()`` +:py:class:`Singleton` delegate could be created obviously using +``Delegate(Singleton(...))`` or by calling ``Singleton(...).delegate()`` method. Example: diff --git a/docs/providers/static.rst b/docs/providers/static.rst index f4c3394e..66296bed 100644 --- a/docs/providers/static.rst +++ b/docs/providers/static.rst @@ -1,16 +1,19 @@ Static providers ---------------- +.. module:: dependency_injector.providers + Static providers are family of providers that return their values "as is". There are four types of static providers: - - ``di.Class`` - - ``di.Object`` - - ``di.Function`` - - ``di.Value`` + - :py:class:`Class` + - :py:class:`Object` + - :py:class:`Function` + - :py:class:`Value` -All of them have the same behaviour, but usage of anyone is predicted by -readability and providing object's type. +All of them have the same behaviour (inherited from +:py:class:`StaticProvider`), but usage of any is predicted by readability +and providing object's type. Example: diff --git a/examples/advanced_usage/config_provider.py b/examples/advanced_usage/config_provider.py index eb016f13..15060b88 100644 --- a/examples/advanced_usage/config_provider.py +++ b/examples/advanced_usage/config_provider.py @@ -1,6 +1,7 @@ """Config provider examples.""" -import dependency_injector as di +from dependency_injector import catalogs +from dependency_injector import providers class ObjectA(object): @@ -13,17 +14,17 @@ class ObjectA(object): self.timezone = timezone -class Catalog(di.AbstractCatalog): +class Catalog(catalogs.DeclarativeCatalog): """Catalog of providers.""" - config = di.Config() - """:type: di.Config""" + config = providers.Config() + """:type: providers.Config""" - object_a = di.Factory(ObjectA, - fee=config.FEE, - price=config.PRICE, - timezone=config.GLOBAL.TIMEZONE) - """:type: di.Provider -> ObjectA""" + object_a = providers.Factory(ObjectA, + fee=config.FEE, + price=config.PRICE, + timezone=config.GLOBAL.TIMEZONE) + """:type: providers.Provider -> ObjectA""" # Setting config value and making some tests. diff --git a/examples/advanced_usage/inject_flask.py b/examples/advanced_usage/inject_flask.py index 585edc8d..1b8580b4 100644 --- a/examples/advanced_usage/inject_flask.py +++ b/examples/advanced_usage/inject_flask.py @@ -1,22 +1,24 @@ -"""`@di.inject()` decorator and Flask view example.""" +"""`inject()` decorator and Flask view example.""" import sqlite3 import flask -import dependency_injector as di + +from dependency_injector import providers +from dependency_injector import injections -database = di.Singleton(sqlite3.connect, - ':memory:', - timeout=30, - detect_types=True, - isolation_level='EXCLUSIVE') +database = providers.Singleton(sqlite3.connect, + ':memory:', + timeout=30, + detect_types=True, + isolation_level='EXCLUSIVE') app = flask.Flask(__name__) @app.route('/') -@di.inject(database) -@di.inject(flask.request) +@injections.inject(database) +@injections.inject(flask.request) def hello(request, database): """Example Flask view.""" print request diff --git a/examples/advanced_usage/inject_flask_class_based.py b/examples/advanced_usage/inject_flask_class_based.py index 3908ef41..a0c2d293 100644 --- a/examples/advanced_usage/inject_flask_class_based.py +++ b/examples/advanced_usage/inject_flask_class_based.py @@ -1,22 +1,24 @@ -"""`@di.inject()` decorator with classes example.""" +"""`inject()` decorator with classes example.""" import sqlite3 import flask import flask.views -import dependency_injector as di + +from dependency_injector import providers +from dependency_injector import injections -database = di.Singleton(sqlite3.Connection, - database=':memory:', - timeout=30, - detect_types=True, - isolation_level='EXCLUSIVE') +database = providers.Singleton(sqlite3.Connection, + database=':memory:', + timeout=30, + detect_types=True, + isolation_level='EXCLUSIVE') app = flask.Flask(__name__) -@di.inject(database=database) -@di.inject(some_setting=777) +@injections.inject(database=database) +@injections.inject(some_setting=777) class HelloView(flask.views.View): """Example flask class-based view.""" diff --git a/examples/advanced_usage/inject_simple.py b/examples/advanced_usage/inject_simple.py index dce9c9fb..b3472e50 100644 --- a/examples/advanced_usage/inject_simple.py +++ b/examples/advanced_usage/inject_simple.py @@ -1,14 +1,15 @@ -"""`@di.inject()` decorator simple example.""" +"""`inject()` decorator simple example.""" -import dependency_injector as di +from dependency_injector import providers +from dependency_injector import injections -dependency_injector_factory = di.Factory(object) +dependency_injector_factory = providers.Factory(object) # Example of using `di.inject()` decorator keyword argument injections: -@di.inject(new_object=dependency_injector_factory) -@di.inject(some_setting=1334) +@injections.inject(new_object=dependency_injector_factory) +@injections.inject(some_setting=1334) def example_callback1(new_object, some_setting): """Example callback that does some asserts for input args.""" assert isinstance(new_object, object) @@ -16,7 +17,7 @@ def example_callback1(new_object, some_setting): # Example of using `di.inject()` decorator with positional argument injections: -@di.inject(dependency_injector_factory, 1334) +@injections.inject(dependency_injector_factory, 1334) def example_callback2(new_object, some_setting): """Example callback that does some asserts for input args.""" assert isinstance(new_object, object) diff --git a/examples/catalogs/bundles/catalogs.py b/examples/catalogs/bundles/catalogs.py index 2a9a7056..52e16861 100644 --- a/examples/catalogs/bundles/catalogs.py +++ b/examples/catalogs/bundles/catalogs.py @@ -1,38 +1,40 @@ """Catalog bundles example.""" -import dependency_injector as di +from dependency_injector import catalogs +from dependency_injector import providers +from dependency_injector import errors import services import views # Declaring services catalog: -class Services(di.DeclarativeCatalog): +class Services(catalogs.DeclarativeCatalog): """Example catalog of service providers.""" - users = di.Factory(services.UsersService) - """:type: di.Provider -> services.UsersService""" + users = providers.Factory(services.Users) + """:type: providers.Provider -> services.Users""" - auth = di.Factory(services.AuthService) - """:type: di.Provider -> services.AuthService""" + auth = providers.Factory(services.Auth) + """:type: providers.Provider -> services.Auth""" - photos = di.Factory(services.PhotosService) - """:type: di.Provider -> services.PhotosService""" + photos = providers.Factory(services.Photos) + """:type: providers.Provider -> services.Photos""" # Declaring views catalog: -class Views(di.DeclarativeCatalog): +class Views(catalogs.DeclarativeCatalog): """Example catalog of web views.""" - auth = di.Factory(views.AuthView, - services=Services.Bundle(Services.users, - Services.auth)) - """:type: di.Provider -> views.AuthView""" + auth = providers.Factory(views.Auth, + services=Services.Bundle(Services.users, + Services.auth)) + """:type: providers.Provider -> views.Auth""" - photos = di.Factory(views.PhotosView, - services=Services.Bundle(Services.users, - Services.photos)) - """:type: di.Provider -> views.PhotosView""" + photos = providers.Factory(views.Photos, + services=Services.Bundle(Services.users, + Services.photos)) + """:type: providers.Provider -> views.Photos""" # Creating example views: @@ -47,7 +49,7 @@ assert auth_view.services.users is Services.users assert auth_view.services.auth is Services.auth try: auth_view.services.photos -except di.Error: +except errors.Error: # `photos` service provider is not in scope of `auth_view` services bundle, # so `di.Error` will be raised. pass @@ -56,7 +58,7 @@ assert photos_view.services.users is Services.users assert photos_view.services.photos is Services.photos try: photos_view.services.auth -except di.Error as exception: +except errors.Error as exception: # `auth` service provider is not in scope of `photo_processing_view` # services bundle, so `di.Error` will be raised. pass diff --git a/examples/catalogs/bundles/services.py b/examples/catalogs/bundles/services.py index 91b9cae5..95f48b65 100644 --- a/examples/catalogs/bundles/services.py +++ b/examples/catalogs/bundles/services.py @@ -5,13 +5,13 @@ class BaseService(object): """Example base class of service.""" -class UsersService(BaseService): +class Users(BaseService): """Example users service.""" -class AuthService(BaseService): +class Auth(BaseService): """Example auth service.""" -class PhotosService(BaseService): +class Photos(BaseService): """Example photo service.""" diff --git a/examples/catalogs/bundles/views.py b/examples/catalogs/bundles/views.py index a3932d63..2be4dd03 100644 --- a/examples/catalogs/bundles/views.py +++ b/examples/catalogs/bundles/views.py @@ -7,15 +7,15 @@ class BaseWebView(object): def __init__(self, services): """Initializer. - :type services: catalogs.Services :param services: Bundle of service providers + :type services: catalogs.Services """ self.services = services -class AuthView(BaseWebView): +class Auth(BaseWebView): """Example auth web view.""" -class PhotosView(BaseWebView): +class Photos(BaseWebView): """Example photo processing web view.""" diff --git a/examples/catalogs/declarative.py b/examples/catalogs/declarative.py new file mode 100644 index 00000000..f11dbe35 --- /dev/null +++ b/examples/catalogs/declarative.py @@ -0,0 +1,24 @@ +"""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) + """:type: providers.Provider -> object""" + + factory2 = providers.Factory(object) + """:type: providers.Provider -> 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/operating_with_providers.py b/examples/catalogs/declarative_inheritance.py similarity index 60% rename from examples/catalogs/operating_with_providers.py rename to examples/catalogs/declarative_inheritance.py index ebbac118..d73b71a7 100644 --- a/examples/catalogs/operating_with_providers.py +++ b/examples/catalogs/declarative_inheritance.py @@ -1,20 +1,21 @@ -"""Operating with catalog providers example.""" +"""Declarative catalogs inheritance example.""" -import dependency_injector as di +from dependency_injector import catalogs +from dependency_injector import providers -class CatalogA(di.DeclarativeCatalog): +class CatalogA(catalogs.DeclarativeCatalog): """Example catalog A.""" - provider1 = di.Factory(object) - """:type: di.Provider -> object""" + provider1 = providers.Factory(object) + """:type: providers.Provider -> object""" class CatalogB(CatalogA): """Example catalog B.""" - provider2 = di.Singleton(object) - """:type: di.Provider -> object""" + provider2 = providers.Singleton(object) + """:type: providers.Provider -> object""" # Making some asserts for `providers` attribute: @@ -29,7 +30,3 @@ 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) - -# Making some asserts for `filter()` method: -assert CatalogB.filter(di.Factory) == dict(provider1=CatalogA.provider1) -assert CatalogB.filter(di.Singleton) == dict(provider2=CatalogB.provider2) diff --git a/examples/catalogs/declarative_injections.py b/examples/catalogs/declarative_injections.py new file mode 100644 index 00000000..0f277f3f --- /dev/null +++ b/examples/catalogs/declarative_injections.py @@ -0,0 +1,50 @@ +"""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:') + """:type: providers.Provider -> sqlite3.Connection""" + + users = providers.Factory(UsersService, + db=database) + """:type: providers.Provider -> UsersService""" + + auth = providers.Factory(AuthService, + db=database, + users_service=users) + """:type: providers.Provider -> AuthService""" + + +# 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/dynamic.py b/examples/catalogs/dynamic.py new file mode 100644 index 00000000..b89bc92c --- /dev/null +++ b/examples/catalogs/dynamic.py @@ -0,0 +1,18 @@ +"""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_runtime_creation.py b/examples/catalogs/dynamic_runtime_creation.py new file mode 100644 index 00000000..c42570b1 --- /dev/null +++ b/examples/catalogs/dynamic_runtime_creation.py @@ -0,0 +1,66 @@ +"""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.py b/examples/catalogs/override.py deleted file mode 100644 index 5f5648c4..00000000 --- a/examples/catalogs/override.py +++ /dev/null @@ -1,44 +0,0 @@ -"""Catalog overriding example.""" - -import collections -import dependency_injector as di - - -# Creating some example classes: -Object1 = collections.namedtuple('Object1', ['arg1', 'arg2']) -Object2 = collections.namedtuple('Object2', ['object1']) -ExtendedObject2 = collections.namedtuple('ExtendedObject2', []) - - -class Catalog(di.DeclarativeCatalog): - """Providers catalog.""" - - object1_factory = di.Factory(Object1, - arg1=1, - arg2=2) - """:type: di.Provider -> Object1""" - - object2_factory = di.Factory(Object2, - object1=object1_factory) - """:type: di.Provider -> Object2""" - - -class AnotherCatalog(di.DeclarativeCatalog): - """Another providers catalog.""" - - object2_factory = di.Factory(ExtendedObject2) - """:type: di.Provider -> 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 object2_1 is not object2_2 - -assert isinstance(object2_1, ExtendedObject2) -assert isinstance(object2_2, ExtendedObject2) diff --git a/examples/catalogs/override_declarative.py b/examples/catalogs/override_declarative.py new file mode 100644 index 00000000..825ffe91 --- /dev/null +++ b/examples/catalogs/override_declarative.py @@ -0,0 +1,48 @@ +"""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) + """:type: providers.Provider -> Object1""" + + object2_factory = providers.Factory(Object2, + object1=object1_factory) + """:type: providers.Provider -> Object2""" + + +class AnotherCatalog(catalogs.DeclarativeCatalog): + """Overriding catalog.""" + + object2_factory = providers.Factory(ExtendedObject2) + """:type: providers.Provider -> 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 new file mode 100644 index 00000000..8886a271 --- /dev/null +++ b/examples/catalogs/override_declarative_by_dynamic.py @@ -0,0 +1,43 @@ +"""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) + """:type: providers.Provider -> Object1""" + + object2_factory = providers.Factory(Object2, + object1=object1_factory) + """:type: providers.Provider -> Object2""" + + +# 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 new file mode 100644 index 00000000..2dcbc828 --- /dev/null +++ b/examples/catalogs/override_declarative_decorator.py @@ -0,0 +1,46 @@ +"""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) + """:type: providers.Provider -> Object1""" + + object2_factory = providers.Factory(Object2, + object1=object1_factory) + """:type: providers.Provider -> Object2""" + + +# Overriding `Catalog` with `AnotherCatalog`: +@catalogs.override(Catalog) +class AnotherCatalog(catalogs.DeclarativeCatalog): + """Overriding catalog.""" + + object2_factory = providers.Factory(ExtendedObject2) + """:type: providers.Provider -> 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) diff --git a/examples/catalogs/override_decorator.py b/examples/catalogs/override_decorator.py deleted file mode 100644 index 423d95e1..00000000 --- a/examples/catalogs/override_decorator.py +++ /dev/null @@ -1,43 +0,0 @@ -"""Catalog overriding using `@di.override()` decorator example.""" - -import collections -import dependency_injector as di - - -# Creating some example classes: -Object1 = collections.namedtuple('Object1', ['arg1', 'arg2']) -Object2 = collections.namedtuple('Object2', ['object1']) -ExtendedObject2 = collections.namedtuple('ExtendedObject2', []) - - -class Catalog(di.DeclarativeCatalog): - """Providers catalog.""" - - object1_factory = di.Factory(Object1, - arg1=1, - arg2=2) - """:type: di.Provider -> Object1""" - - object2_factory = di.Factory(Object2, - object1=object1_factory) - """:type: di.Provider -> Object2""" - - -# Overriding `Catalog` with `AnotherCatalog`: -@di.override(Catalog) -class AnotherCatalog(di.DeclarativeCatalog): - """Another providers catalog.""" - - object2_factory = di.Factory(ExtendedObject2) - """:type: di.Provider -> ExtendedObject2""" - - -# Creating some objects using overridden catalog: -object2_1 = Catalog.object2_factory() -object2_2 = Catalog.object2_factory() - -# Making some asserts: -assert object2_1 is not object2_2 - -assert isinstance(object2_1, ExtendedObject2) -assert isinstance(object2_2, ExtendedObject2) diff --git a/examples/catalogs/writing_catalogs.py b/examples/catalogs/writing_catalogs.py deleted file mode 100644 index ebb89746..00000000 --- a/examples/catalogs/writing_catalogs.py +++ /dev/null @@ -1,22 +0,0 @@ -"""Catalog example.""" - -import dependency_injector as di - - -class Catalog(di.DeclarativeCatalog): - """Providers catalog.""" - - factory1 = di.Factory(object) - """:type: di.Provider -> object""" - - factory2 = di.Factory(object) - """:type: di.Provider -> 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/concept.py b/examples/concept.py index af357c55..57c4cb68 100644 --- a/examples/concept.py +++ b/examples/concept.py @@ -1,7 +1,10 @@ """Concept example of `Dependency Injector`.""" import sqlite3 -import dependency_injector as di + +from dependency_injector import catalogs +from dependency_injector import providers +from dependency_injector import injections class UsersService(object): @@ -21,20 +24,20 @@ class AuthService(object): self.users_service = users_service -class Services(di.DeclarativeCatalog): +class Services(catalogs.DeclarativeCatalog): """Catalog of service providers.""" - database = di.Singleton(sqlite3.connect, ':memory:') - """:type: di.Provider -> sqlite3.Connection""" + database = providers.Singleton(sqlite3.connect, ':memory:') + """:type: providers.Provider -> sqlite3.Connection""" - users = di.Factory(UsersService, - db=database) - """:type: di.Provider -> UsersService""" + users = providers.Factory(UsersService, + db=database) + """:type: providers.Provider -> UsersService""" - auth = di.Factory(AuthService, - db=database, - users_service=users) - """:type: di.Provider -> AuthService""" + auth = providers.Factory(AuthService, + db=database, + users_service=users) + """:type: providers.Provider -> AuthService""" # Retrieving catalog providers: @@ -49,9 +52,9 @@ assert auth_service is not Services.auth() # Making some "inline" injections: -@di.inject(users_service=Services.users) -@di.inject(auth_service=Services.auth) -@di.inject(database=Services.database) +@injections.inject(users_service=Services.users) +@injections.inject(auth_service=Services.auth) +@injections.inject(database=Services.database) def example(users_service, auth_service, database): """Example callback.""" assert users_service.db is auth_service.db diff --git a/examples/providers/callable_args.py b/examples/providers/callable_args.py index 3be82b6f..9a854d5b 100644 --- a/examples/providers/callable_args.py +++ b/examples/providers/callable_args.py @@ -1,11 +1,11 @@ -"""`di.Callable` providers with positional arguments example.""" +"""`Callable` providers with positional arguments example.""" -import dependency_injector as di +from dependency_injector import providers # Creating even and odd filter providers: -even_filter = di.Callable(filter, lambda x: x % 2 == 0) -odd_filter = di.Callable(filter, lambda x: x % 2 != 0) +even_filter = providers.Callable(filter, lambda x: x % 2 == 0) +odd_filter = providers.Callable(filter, lambda x: x % 2 != 0) # Creating even and odd ranges using xrange() and filter providers: even_range = even_filter(xrange(1, 10)) diff --git a/examples/providers/callable_delegation.py b/examples/providers/callable_delegation.py index a4985982..4a59e004 100644 --- a/examples/providers/callable_delegation.py +++ b/examples/providers/callable_delegation.py @@ -1,13 +1,14 @@ -"""`di.Callable` providers delegation example.""" +"""`Callable` providers delegation example.""" import sys -import dependency_injector as di + +from dependency_injector import providers # Creating some callable provider and few delegates of it: -callable_provider = di.Callable(sys.exit) +callable_provider = providers.Callable(sys.exit) callable_provider_delegate1 = callable_provider.delegate() -callable_provider_delegate2 = di.Delegate(callable_provider) +callable_provider_delegate2 = providers.Delegate(callable_provider) # Making some asserts: assert callable_provider_delegate1() is callable_provider diff --git a/examples/providers/callable_kwargs.py b/examples/providers/callable_kwargs.py index 7119667d..0880940a 100644 --- a/examples/providers/callable_kwargs.py +++ b/examples/providers/callable_kwargs.py @@ -1,14 +1,16 @@ -"""`di.Callable` providers with keyword arguments example.""" +"""`Callable` providers with keyword arguments example.""" import passlib.hash -import dependency_injector as di + +from dependency_injector import providers + # Password hasher and verifier providers (hash function could be changed # anytime (for example, to sha512) without any changes in client's code): -password_hasher = di.Callable(passlib.hash.sha256_crypt.encrypt, - salt_size=16, - rounds=10000) -password_verifier = di.Callable(passlib.hash.sha256_crypt.verify) +password_hasher = providers.Callable(passlib.hash.sha256_crypt.encrypt, + salt_size=16, + rounds=10000) +password_verifier = providers.Callable(passlib.hash.sha256_crypt.verify) # Making some asserts: hashed_password = password_hasher('super secret') diff --git a/examples/providers/custom_factory.py b/examples/providers/custom_factory.py index 2264c736..7193fbaa 100644 --- a/examples/providers/custom_factory.py +++ b/examples/providers/custom_factory.py @@ -1,24 +1,33 @@ -"""Custom `di.Factory` example.""" +"""Custom `Factory` example.""" -import dependency_injector as di +from dependency_injector import providers class User(object): """Example class User.""" -class UsersFactory(di.Provider): +class UsersFactory(providers.Provider): """Example users factory.""" __slots__ = ('_factory',) def __init__(self): """Initializer.""" - self._factory = di.Factory(User) + self._factory = providers.Factory(User) super(UsersFactory, self).__init__() def _provide(self, *args, **kwargs): - """Return provided instance.""" + """Return provided instance. + + :param args: tuple of context positional arguments + :type args: tuple[object] + + :param kwargs: dictionary of context keyword arguments + :type kwargs: dict[str, object] + + :rtype: object + """ return self._factory(*args, **kwargs) diff --git a/examples/providers/external_dependency.py b/examples/providers/external_dependency.py index 683c265c..1aedd140 100644 --- a/examples/providers/external_dependency.py +++ b/examples/providers/external_dependency.py @@ -1,8 +1,9 @@ -"""`di.ExternalDependency` providers example.""" +"""`ExternalDependency` providers example.""" import sqlite3 import contextlib -import dependency_injector as di + +from dependency_injector import providers class UserService(object): @@ -14,7 +15,8 @@ class UserService(object): def __init__(self, database): """Initializer. - Database dependency need to be injected via init arg. + :param database: Database connection. + :type database: sqlite3.dbapi2.Connection """ self.database = database self.database.row_factory = sqlite3.dbapi2.Row @@ -43,18 +45,18 @@ class UserService(object): # Database and UserService providers: -database = di.ExternalDependency(instance_of=sqlite3.dbapi2.Connection) -users_service_factory = di.Factory(UserService, - database=database) +database = providers.ExternalDependency(instance_of=sqlite3.dbapi2.Connection) +users_service_factory = providers.Factory(UserService, + database=database) # Out of library's scope. # # Setting database provider: -database.provided_by(di.Singleton(sqlite3.dbapi2.Connection, - database=':memory:', - timeout=30, - detect_types=True, - isolation_level='EXCLUSIVE')) +database.provided_by(providers.Singleton(sqlite3.dbapi2.Connection, + database=':memory:', + timeout=30, + detect_types=True, + isolation_level='EXCLUSIVE')) # Creating UserService instance: users_service = users_service_factory() diff --git a/examples/providers/factory.py b/examples/providers/factory.py index c38ea954..1219bf0e 100644 --- a/examples/providers/factory.py +++ b/examples/providers/factory.py @@ -1,13 +1,13 @@ -"""`di.Factory` providers example.""" +"""`Factory` providers example.""" -import dependency_injector as di +from dependency_injector import providers class User(object): """Example class User.""" # Factory provider creates new instance of specified class on every call. -users_factory = di.Factory(User) +users_factory = providers.Factory(User) # Creating several User objects: user1 = users_factory() diff --git a/examples/providers/factory_attribute_injections.py b/examples/providers/factory_attribute_injections.py index ef46c3bb..a99a152c 100644 --- a/examples/providers/factory_attribute_injections.py +++ b/examples/providers/factory_attribute_injections.py @@ -1,6 +1,7 @@ -"""`di.Factory` providers with attribute injections example.""" +"""`Factory` providers with attribute injections example.""" -import dependency_injector as di +from dependency_injector import providers +from dependency_injector import injections class User(object): @@ -20,11 +21,13 @@ class CreditCard(object): """Example class CreditCard.""" # User, Photo and CreditCard factories: -credit_cards_factory = di.Factory(CreditCard) -photos_factory = di.Factory(Photo) -users_factory = di.Factory(User, - di.Attribute('main_photo', photos_factory), - di.Attribute('credit_card', credit_cards_factory)) +credit_cards_factory = providers.Factory(CreditCard) +photos_factory = providers.Factory(Photo) +users_factory = providers.Factory(User, + injections.Attribute('main_photo', + photos_factory), + injections.Attribute('credit_card', + credit_cards_factory)) # Creating several User objects: user1 = users_factory() diff --git a/examples/providers/factory_delegation.py b/examples/providers/factory_delegation.py index 5e9b12bc..b3c7773e 100644 --- a/examples/providers/factory_delegation.py +++ b/examples/providers/factory_delegation.py @@ -1,6 +1,6 @@ -"""`di.Factory` providers delegation example.""" +"""`Factory` providers delegation example.""" -import dependency_injector as di +from dependency_injector import providers class User(object): @@ -9,7 +9,7 @@ class User(object): def __init__(self, photos_factory): """Initializer. - :param photos_factory: (di.Factory) -> Photo + :param photos_factory: providers.Factory -> Photo """ self.photos_factory = photos_factory self._main_photo = None @@ -27,9 +27,9 @@ class Photo(object): """Example class Photo.""" # User and Photo factories: -photos_factory = di.Factory(Photo) -users_factory = di.Factory(User, - photos_factory=di.Delegate(photos_factory)) +photos_factory = providers.Factory(Photo) +users_factory = providers.Factory(User, + photos_factory=photos_factory.delegate()) # Creating several User objects: user1 = users_factory() diff --git a/examples/providers/factory_init_args.py b/examples/providers/factory_init_args.py index d3b66eff..ed259474 100644 --- a/examples/providers/factory_init_args.py +++ b/examples/providers/factory_init_args.py @@ -1,6 +1,6 @@ -"""`di.Factory` providers with init positional injections example.""" +"""`Factory` providers with init positional injections example.""" -import dependency_injector as di +from dependency_injector import providers class User(object): @@ -16,8 +16,8 @@ class Photo(object): """Example class Photo.""" # User and Photo factories: -photos_factory = di.Factory(Photo) -users_factory = di.Factory(User, photos_factory) +photos_factory = providers.Factory(Photo) +users_factory = providers.Factory(User, photos_factory) # Creating several User objects: user1 = users_factory() # Same as: user1 = User(Photo()) diff --git a/examples/providers/factory_init_injections_and_contexts.py b/examples/providers/factory_init_injections_and_contexts.py index 7917ba0b..10f503d3 100644 --- a/examples/providers/factory_init_injections_and_contexts.py +++ b/examples/providers/factory_init_injections_and_contexts.py @@ -1,6 +1,6 @@ -"""`di.Factory` providers with init injections priority example.""" +"""`Factory` providers with init injections priority example.""" -import dependency_injector as di +from dependency_injector import providers class User(object): @@ -30,11 +30,11 @@ class CreditCard(object): """Example class CreditCard.""" # User, Photo and CreditCard factories: -credit_cards_factory = di.Factory(CreditCard) -photos_factory = di.Factory(Photo) -users_factory = di.Factory(User, - main_photo=photos_factory, - credit_card=credit_cards_factory) +credit_cards_factory = providers.Factory(CreditCard) +photos_factory = providers.Factory(Photo) +users_factory = providers.Factory(User, + main_photo=photos_factory, + credit_card=credit_cards_factory) # Creating several User objects: user1 = users_factory(1) diff --git a/examples/providers/factory_init_kwargs.py b/examples/providers/factory_init_kwargs.py index 2159e335..85ba59c7 100644 --- a/examples/providers/factory_init_kwargs.py +++ b/examples/providers/factory_init_kwargs.py @@ -1,6 +1,6 @@ -"""`di.Factory` providers with init keyword injections example.""" +"""`Factory` providers with init keyword injections example.""" -import dependency_injector as di +from dependency_injector import providers class User(object): @@ -16,8 +16,8 @@ class Photo(object): """Example class Photo.""" # User and Photo factories: -photos_factory = di.Factory(Photo) -users_factory = di.Factory(User, main_photo=photos_factory) +photos_factory = providers.Factory(Photo) +users_factory = providers.Factory(User, main_photo=photos_factory) # Creating several User objects: user1 = users_factory() # Same as: user1 = User(main_photo=Photo()) diff --git a/examples/providers/factory_method_injections.py b/examples/providers/factory_method_injections.py index 53c0d6e6..9ce68cb4 100644 --- a/examples/providers/factory_method_injections.py +++ b/examples/providers/factory_method_injections.py @@ -1,6 +1,7 @@ -"""`di.Factory` providers with method injections example.""" +"""`Factory` providers with method injections example.""" -import dependency_injector as di +from dependency_injector import providers +from dependency_injector import injections class User(object): @@ -28,11 +29,13 @@ class CreditCard(object): """Example class CreditCard.""" # User, Photo and CreditCard factories: -credit_cards_factory = di.Factory(CreditCard) -photos_factory = di.Factory(Photo) -users_factory = di.Factory(User, - di.Method('set_main_photo', photos_factory), - di.Method('set_credit_card', credit_cards_factory)) +credit_cards_factory = providers.Factory(CreditCard) +photos_factory = providers.Factory(Photo) +users_factory = providers.Factory(User, + injections.Method('set_main_photo', + photos_factory), + injections.Method('set_credit_card', + credit_cards_factory)) # Creating several User objects: user1 = users_factory() diff --git a/examples/providers/overriding_simple.py b/examples/providers/overriding_simple.py index f99d0b64..4d99cf31 100644 --- a/examples/providers/overriding_simple.py +++ b/examples/providers/overriding_simple.py @@ -1,13 +1,13 @@ """Simple providers overriding example.""" -import dependency_injector as di +from dependency_injector import providers class User(object): """Example class User.""" # Users factory: -users_factory = di.Factory(User) +users_factory = providers.Factory(User) # Creating several User objects: user1 = users_factory() @@ -23,7 +23,7 @@ class SuperUser(User): """Example class SuperUser.""" # Overriding users factory: -users_factory.override(di.Factory(SuperUser)) +users_factory.override(providers.Factory(SuperUser)) # Creating some more User objects using overridden users factory: user3 = users_factory() diff --git a/examples/providers/overriding_users_model.py b/examples/providers/overriding_users_model.py index e3db9be2..6fdc7fed 100644 --- a/examples/providers/overriding_users_model.py +++ b/examples/providers/overriding_users_model.py @@ -1,6 +1,6 @@ """Overriding user's model example.""" -import dependency_injector as di +from dependency_injector import providers class User(object): @@ -26,8 +26,7 @@ class UserService(object): return self.user_cls(id=id, password='secret' + str(id)) # Users factory and UserService provider: -users_service = di.Factory(UserService, - user_cls=User) +users_service = providers.Factory(UserService, user_cls=User) # Getting several users and making some asserts: user1 = users_service().get_by_id(1) @@ -71,8 +70,8 @@ class ExtendedUserService(UserService): return user # Overriding users_service provider: -extended_users_service = di.Factory(ExtendedUserService, - user_cls=ExtendedUser) +extended_users_service = providers.Factory(ExtendedUserService, + user_cls=ExtendedUser) users_service.override(extended_users_service) # Getting few other users users and making some asserts: diff --git a/examples/providers/singleton.py b/examples/providers/singleton.py index 963d6c14..73b736ec 100644 --- a/examples/providers/singleton.py +++ b/examples/providers/singleton.py @@ -1,6 +1,6 @@ -"""`di.Singleton` providers example.""" +"""`Singleton` providers example.""" -import dependency_injector as di +from dependency_injector import providers class UserService(object): @@ -8,7 +8,7 @@ class UserService(object): # Singleton provider creates new instance of specified class on first call and # returns same instance on every next call. -users_service_provider = di.Singleton(UserService) +users_service_provider = providers.Singleton(UserService) # Retrieving several UserService objects: user_service1 = users_service_provider() diff --git a/examples/providers/singleton_delegation.py b/examples/providers/singleton_delegation.py index ebc9aa2e..f2342ad2 100644 --- a/examples/providers/singleton_delegation.py +++ b/examples/providers/singleton_delegation.py @@ -1,12 +1,12 @@ -"""`di.Singleton` providers delegation example.""" +"""`Singleton` providers delegation example.""" -import dependency_injector as di +from dependency_injector import providers # Some singleton provider and few delegates of it: -singleton_provider = di.Singleton(object) +singleton_provider = providers.Singleton(object) singleton_provider_delegate1 = singleton_provider.delegate() -singleton_provider_delegate2 = di.Delegate(singleton_provider) +singleton_provider_delegate2 = providers.Delegate(singleton_provider) # Making some asserts: assert singleton_provider_delegate1() is singleton_provider diff --git a/examples/providers/singleton_reseting.py b/examples/providers/singleton_reseting.py index cdc9e078..19c9b238 100644 --- a/examples/providers/singleton_reseting.py +++ b/examples/providers/singleton_reseting.py @@ -1,13 +1,13 @@ -"""`di.Singleton` providers resetting example.""" +"""`Singleton` providers resetting example.""" -import dependency_injector as di +from dependency_injector import providers class UserService(object): """Example class UserService.""" # Users service singleton provider: -users_service_provider = di.Singleton(UserService) +users_service_provider = providers.Singleton(UserService) # Retrieving several UserService objects: user_service1 = users_service_provider() diff --git a/examples/providers/static.py b/examples/providers/static.py index 69726eb6..7fa5460b 100644 --- a/examples/providers/static.py +++ b/examples/providers/static.py @@ -1,20 +1,20 @@ """Static providers example.""" -import dependency_injector as di +from dependency_injector import providers # Provides class - `object`: -cls_provider = di.Class(object) +cls_provider = providers.Class(object) assert cls_provider() is object # Provides object - `object()`: -object_provider = di.Object(object()) +object_provider = providers.Object(object()) assert isinstance(object_provider(), object) # Provides function - `len`: -function_provider = di.Function(len) +function_provider = providers.Function(len) assert function_provider() is len # Provides value - `123`: -value_provider = di.Value(123) +value_provider = providers.Value(123) assert value_provider() == 123 diff --git a/tests/__init__.py b/tests/__init__.py index 3a003995..72a7fbba 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1 +1,13 @@ """Dependency injector unittests.""" + +import unittest2 as unittest + +from dependency_injector import VERSION + + +class VersionTest(unittest.TestCase): + """Version constant tests.""" + + def test_version_follows_semantic_versioning(self): + """Test that version follows semantic versioning.""" + self.assertEquals(len(VERSION.split('.'))) diff --git a/tests/test_catalogs.py b/tests/test_catalogs.py index 648d18ed..3ba48e20 100644 --- a/tests/test_catalogs.py +++ b/tests/test_catalogs.py @@ -282,11 +282,9 @@ class DeclarativeCatalogTests(unittest.TestCase): self.assertIs(CatalogA.px, px) self.assertIs(CatalogA.get_provider('px'), px) - self.assertIs(CatalogA.catalog.px, px) self.assertIs(CatalogA.py, py) self.assertIs(CatalogA.get_provider('py'), py) - self.assertIs(CatalogA.catalog.py, py) del CatalogA.px del CatalogA.py @@ -300,11 +298,9 @@ class DeclarativeCatalogTests(unittest.TestCase): self.assertIs(CatalogB.px, px) self.assertIs(CatalogB.get_provider('px'), px) - self.assertIs(CatalogB.catalog.px, px) self.assertIs(CatalogB.py, py) self.assertIs(CatalogB.get_provider('py'), py) - self.assertIs(CatalogB.catalog.py, py) del CatalogB.px del CatalogB.py @@ -319,11 +315,9 @@ class DeclarativeCatalogTests(unittest.TestCase): self.assertIs(CatalogB.px, px) self.assertIs(CatalogB.get_provider('px'), px) - self.assertIs(CatalogB.catalog.px, px) self.assertIs(CatalogB.py, py) self.assertIs(CatalogB.get_provider('py'), py) - self.assertIs(CatalogB.catalog.py, py) del CatalogB.px del CatalogB.py @@ -480,6 +474,23 @@ class OverrideTests(unittest.TestCase): 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.Value(1), + p12=providers.Value(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.Value(1), @@ -512,8 +523,7 @@ class OverrideTests(unittest.TestCase): def test_last_overriding_on_not_overridden(self): """Test catalog last_overriding property on not overridden catalog.""" - with self.assertRaises(errors.Error): - CatalogA.last_overriding + self.assertIsNone(CatalogA.last_overriding) def test_reset_last_overriding(self): """Test resetting last overriding catalog.""" @@ -561,3 +571,14 @@ class OverrideTests(unittest.TestCase): self.assertFalse(CatalogA.p11.is_overridden) self.assertFalse(CatalogA.p12.is_overridden) + + +class CatalogModuleBackwardCompatibility(unittest.TestCase): + """Backward compatibility test of catalog module.""" + + def test_import_catalog(self): + """Test that module `catalog` is the same as `catalogs`.""" + from dependency_injector import catalog + from dependency_injector import catalogs + + self.assertIs(catalog, catalogs) diff --git a/tests/test_providers.py b/tests/test_providers.py index 38990ed5..e91eb5f0 100644 --- a/tests/test_providers.py +++ b/tests/test_providers.py @@ -90,13 +90,7 @@ class ProviderTests(unittest.TestCase): def test_last_overriding_of_not_overridden_provider(self): """Test getting last overriding from not overridden provider.""" - try: - self.provider.last_overriding - except errors.Error: - pass - else: - self.fail('Got en error in {}'.format( - str(self.test_last_overriding_of_not_overridden_provider))) + self.assertIsNone(self.provider.last_overriding) def test_reset_last_overriding(self): """Test reseting of last overriding provider.""" @@ -129,13 +123,7 @@ class ProviderTests(unittest.TestCase): self.provider.reset_override() self.assertFalse(self.provider.is_overridden) - try: - self.provider.last_overriding - except errors.Error: - pass - else: - self.fail('Got en error in {}'.format( - str(self.test_last_overriding_of_not_overridden_provider))) + self.assertIsNone(self.provider.last_overriding) class DelegateTests(unittest.TestCase): @@ -564,6 +552,35 @@ class SingletonTests(unittest.TestCase): self.assertIsInstance(instance1, object) self.assertIsInstance(instance2, object) + def test_provides_attr(self): + """Test provides attribute.""" + provider = providers.Singleton(Example) + self.assertIs(provider.provides, Example) + + def test_args_attr(self): + """Test args attribute.""" + provider = providers.Singleton(Example, 1, 2) + self.assertEquals(len(provider.args), 2) + + def test_kwargs_attr(self): + """Test kwargs attribute.""" + provider = providers.Singleton(Example, init_arg1=1, init_arg2=2) + self.assertEquals(len(provider.kwargs), 2) + + def test_attributes_attr(self): + """Test attributes attribute.""" + provider = providers.Singleton(Example, + injections.Attribute('attribute1', 1), + injections.Attribute('attribute2', 2)) + self.assertEquals(len(provider.attributes), 2) + + def test_methods_attr(self): + """Test methods attribute.""" + provider = providers.Singleton(Example, + injections.Method('method1', 1), + injections.Method('method2', 2)) + self.assertEquals(len(provider.methods), 2) + def test_injections(self): """Test getting a full list of injections using injections property.""" provider = providers.Singleton(Example,