From 10e76f65d7b0fe56c088640e1c89b9315b9dd0a0 Mon Sep 17 00:00:00 2001 From: Roman Mogilatov Date: Fri, 11 Dec 2015 16:01:07 +0200 Subject: [PATCH 1/3] Add validation of provided type for Factory provider --- dependency_injector/providers.py | 40 ++++++++++++++++++++++++++++---- docs/main/changelog.rst | 3 ++- tests/test_providers.py | 35 ++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 5 deletions(-) diff --git a/dependency_injector/providers.py b/dependency_injector/providers.py index f53ac14d..c767e42b 100644 --- a/dependency_injector/providers.py +++ b/dependency_injector/providers.py @@ -246,6 +246,16 @@ class Factory(Provider): some_object = factory() """ + provided_type = None + """Provided type. + + If provided type is defined, :py:class:`Factory` checks that + :py:attr:`Factory.provides` is subclass of + :py:attr:`Factory.provided_type`. + + :type: type | None + """ + __slots__ = ('provides', 'args', 'kwargs', 'attributes', 'methods') def __init__(self, provides, *args, **kwargs): @@ -261,10 +271,7 @@ class Factory(Provider): :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 + self.provides = self._ensure_provides_type(provides) """Class or other callable that provides object for creation. :type: type | callable @@ -328,6 +335,31 @@ class Factory(Provider): return instance + def _ensure_provides_type(self, provides): + """Check if provided type is valid type for this factory. + + :param provides: Factory provided type + :type provides: type + + :raise: :py:exc:`dependency_injector.errors.Error` if ``provides`` + is not callable + :raise: :py:exc:`dependency_injector.errors.Error` if ``provides`` + doesn't meet factory provided type + + :return: validated ``provides`` + :rtype: type + """ + if not callable(provides): + raise Error('Factory provider expects to get callable, ' + + 'got {0} instead'.format(str(provides))) + if (self.__class__.provided_type and + not issubclass(provides, self.__class__.provided_type)): + raise Error('{0} can provide only {1} instances'.format( + '.'.join((self.__class__.__module__, + self.__class__.__name__)), + self.__class__.provided_type)) + return provides + def __str__(self): """Return string representation of provider. diff --git a/docs/main/changelog.rst b/docs/main/changelog.rst index 75a24c5b..b791b1f6 100644 --- a/docs/main/changelog.rst +++ b/docs/main/changelog.rst @@ -9,7 +9,8 @@ follows `Semantic versioning`_ Development version ------------------- -- No features. +- Add possibility to validate ``Factory`` provided type on ``Factory`` + initialization. 1.11.2 ------ diff --git a/tests/test_providers.py b/tests/test_providers.py index 98cf9f20..3621958c 100644 --- a/tests/test_providers.py +++ b/tests/test_providers.py @@ -176,6 +176,41 @@ class FactoryTests(unittest.TestCase): """Test creation of provider with a callable.""" self.assertTrue(providers.Factory(credits)) + def test_init_with_valid_provided_type(self): + """Test creation with not valid provided type.""" + class ExampleFactory(providers.Factory): + """Example factory.""" + + provided_type = Example + + example_factory = ExampleFactory(Example, 1, 2) + + self.assertIsInstance(example_factory(), Example) + + def test_init_with_valid_provided_subtype(self): + """Test creation with not valid provided type.""" + class ExampleFactory(providers.Factory): + """Example factory.""" + + provided_type = Example + + class NewExampe(Example): + """Example class subclass.""" + + example_factory = ExampleFactory(NewExampe, 1, 2) + + self.assertIsInstance(example_factory(), NewExampe) + + def test_init_with_invalid_provided_type(self): + """Test creation with not valid provided type.""" + class ExampleFactory(providers.Factory): + """Example factory.""" + + provided_type = Example + + with self.assertRaises(errors.Error): + ExampleFactory(list) + def test_init_with_not_callable(self): """Test creation of provider with not a callable.""" self.assertRaises(errors.Error, providers.Factory, 123) From f3668ed815df052ccee55d1a56e15c213dd06c3d Mon Sep 17 00:00:00 2001 From: Roman Mogilatov Date: Fri, 11 Dec 2015 22:46:49 +0200 Subject: [PATCH 2/3] Make some refactorings for providers --- dependency_injector/providers.py | 485 ++++++++++++++----------------- docs/api/providers.rst | 1 + docs/main/changelog.rst | 3 + tests/test_providers.py | 65 ++++- 4 files changed, 278 insertions(+), 276 deletions(-) diff --git a/dependency_injector/providers.py b/dependency_injector/providers.py index c767e42b..9c2c6051 100644 --- a/dependency_injector/providers.py +++ b/dependency_injector/providers.py @@ -56,6 +56,12 @@ class Provider(object): assert provider is delegated All providers should extend this class. + + .. py:attribute:: overridden_by + + Tuple of overriding providers, if any. + + :type: tuple[:py:class:`Provider`] | None """ __IS_PROVIDER__ = True @@ -64,11 +70,6 @@ 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): @@ -174,6 +175,12 @@ class Delegate(Provider): delegated = delegate() assert provider is delegated + + .. py:attribute:: delegated + + Delegated provider. + + :type: :py:class:`Provider` """ __slots__ = ('delegated',) @@ -185,11 +192,6 @@ class Delegate(Provider): :type delegated: :py:class:`Provider` """ self.delegated = ensure_is_provider(delegated) - """Delegated provider. - - :type: :py:class:`Provider` - """ - super(Delegate, self).__init__() def _provide(self, *args, **kwargs): @@ -216,7 +218,108 @@ class Delegate(Provider): @six.python_2_unicode_compatible -class Factory(Provider): +class Callable(Provider): + """:py:class:`Callable` provider calls wrapped callable on every call. + + :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 + + # simplified syntax for passing positional and keyword argument + # injections: + some_function = Callable(some_function, 'arg1', 'arg2', arg3=3, arg4=4) + + # extended (full) syntax for passing positional and keyword argument + # injections: + some_function = Callable(some_function, + injections.Arg(1), + injections.Arg(2), + injections.KwArg('some_arg', 3), + injections.KwArg('other_arg', 4)) + + .. py:attribute:: provides + + Provided callable. + + :type: callable + + .. py:attribute:: args + + Tuple of positional argument injections. + + :type: tuple[:py:class:`dependency_injector.injections.Arg`] + + .. py:attribute:: kwargs + + Tuple of keyword argument injections. + + :type: tuple[:py:class:`dependency_injector.injections.KwArg`] + """ + + __slots__ = ('provides', 'args', 'kwargs') + + def __init__(self, provides, *args, **kwargs): + """Initializer. + + :param provides: Wrapped callable. + :type provides: callable + + :param args: Tuple of injections. + :type args: tuple + + :param kwargs: Dictionary of injections. + :type kwargs: dict + """ + if not callable(provides): + raise Error('Provider{0} expected to get callable, ' + 'got {0}'.format('.'.join((self.__class__.__module__, + self.__class__.__name__)), + provides)) + + self.provides = provides + + self.args = _parse_args_injections(args) + self.kwargs = _parse_kwargs_injections(args, kwargs) + + super(Callable, 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 + + 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.provides(*_get_injectable_args(args, self.args), + **_get_injectable_kwargs(kwargs, self.kwargs)) + + def __str__(self): + """Return string representation of provider. + + :rtype: str + """ + return represent_provider(provider=self, provides=self.provides) + + __repr__ = __str__ + + +class Factory(Callable): """:py:class:`Factory` provider creates new instance on every call. :py:class:`Factory` supports different syntaxes of passing injections: @@ -244,19 +347,49 @@ class Factory(Provider): some_arg1=1, some_arg2=2) some_object = factory() + + .. py:attribute:: provided_type + + If provided type is defined, :py:class:`Factory` checks that + :py:attr:`Factory.provides` is subclass of + :py:attr:`Factory.provided_type`. + + :type: type | None + + .. py:attribute:: provides + + Class or other callable that provides object. + + :type: type | callable + + .. py:attribute:: args + + Tuple of positional argument injections. + + :type: tuple[:py:class:`dependency_injector.injections.Arg`] + + .. py:attribute:: kwargs + + Tuple of keyword argument injections. + + :type: tuple[:py:class:`dependency_injector.injections.KwArg`] + + .. py:attribute:: attributes + + Tuple of attribute injections. + + :type: tuple[:py:class:`dependency_injector.injections.Attribute`] + + .. py:attribute:: methods + + Tuple of method injections. + + :type: tuple[:py:class:`dependency_injector.injections.Method`] """ provided_type = None - """Provided type. - If provided type is defined, :py:class:`Factory` checks that - :py:attr:`Factory.provides` is subclass of - :py:attr:`Factory.provided_type`. - - :type: type | None - """ - - __slots__ = ('provides', 'args', 'kwargs', 'attributes', 'methods') + __slots__ = ('attributes', 'methods') def __init__(self, provides, *args, **kwargs): """Initializer. @@ -271,41 +404,22 @@ class Factory(Provider): :param kwargs: Dictionary of injections. :type kwargs: dict """ - self.provides = self._ensure_provides_type(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`] - """ + if (self.__class__.provided_type and + not issubclass(provides, self.__class__.provided_type)): + raise Error('{0} can provide only {1} instances'.format( + '.'.join((self.__class__.__module__, + self.__class__.__name__)), + self.__class__.provided_type)) 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__() + super(Factory, self).__init__(provides, *args, **kwargs) @property def injections(self): @@ -326,8 +440,8 @@ class Factory(Provider): :rtype: object """ - instance = self.provides(*_get_injectable_args(args, self.args), - **_get_injectable_kwargs(kwargs, self.kwargs)) + instance = super(Factory, self)._provide(*args, **kwargs) + for attribute in self.attributes: setattr(instance, attribute.name, attribute.value) for method in self.methods: @@ -335,49 +449,13 @@ class Factory(Provider): return instance - def _ensure_provides_type(self, provides): - """Check if provided type is valid type for this factory. - :param provides: Factory provided type - :type provides: type - - :raise: :py:exc:`dependency_injector.errors.Error` if ``provides`` - is not callable - :raise: :py:exc:`dependency_injector.errors.Error` if ``provides`` - doesn't meet factory provided type - - :return: validated ``provides`` - :rtype: type - """ - if not callable(provides): - raise Error('Factory provider expects to get callable, ' + - 'got {0} instead'.format(str(provides))) - if (self.__class__.provided_type and - not issubclass(provides, self.__class__.provided_type)): - raise Error('{0} can provide only {1} instances'.format( - '.'.join((self.__class__.__module__, - self.__class__.__name__)), - self.__class__.provided_type)) - return provides - - def __str__(self): - """Return string representation of provider. - - :rtype: str - """ - return represent_provider(provider=self, provides=self.provides) - - __repr__ = __str__ - - -@six.python_2_unicode_compatible -class Singleton(Provider): +class Singleton(Factory): """: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. + call. :py:class:`Singleton` extends :py:class:`Factory`, 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. @@ -392,9 +470,52 @@ class Singleton(Provider): some_arg2=2) some_object = singleton() + .. py:attribute:: provided_type + + If provided type is defined, :py:class:`Factory` checks that + :py:attr:`Factory.provides` is subclass of + :py:attr:`Factory.provided_type`. + + :type: type | None + + .. py:attribute:: instance + + Read-only reference to singleton's instance. + + :type: object + + .. py:attribute:: provides + + Class or other callable that provides object. + + :type: type | callable + + .. py:attribute:: args + + Tuple of positional argument injections. + + :type: tuple[:py:class:`dependency_injector.injections.Arg`] + + .. py:attribute:: kwargs + + Tuple of keyword argument injections. + + :type: tuple[:py:class:`dependency_injector.injections.KwArg`] + + .. py:attribute:: attributes + + Tuple of attribute injections. + + :type: tuple[:py:class:`dependency_injector.injections.Attribute`] + + .. py:attribute:: methods + + Tuple of method injections. + + :type: tuple[:py:class:`dependency_injector.injections.Method`] """ - __slots__ = ('instance', 'factory') + __slots__ = ('instance',) def __init__(self, provides, *args, **kwargs): """Initializer. @@ -410,66 +531,7 @@ class Singleton(Provider): :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 + super(Singleton, self).__init__(provides, *args, **kwargs) def reset(self): """Reset cached instance, if any. @@ -491,103 +553,10 @@ class Singleton(Provider): """ with GLOBAL_LOCK: if not self.instance: - self.instance = self.factory(*args, **kwargs) + self.instance = super(Singleton, self)._provide(*args, + **kwargs) return self.instance - def __str__(self): - """Return string representation of provider. - - :rtype: str - """ - return represent_provider(provider=self, provides=self.provides) - - __repr__ = __str__ - - -@six.python_2_unicode_compatible -class Callable(Provider): - """:py:class:`Callable` provider calls wrapped callable on every call. - - :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. - - :param provides: Wrapped callable. - :type provides: 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 - """Provided callable. - - :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): - """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)) - - def __str__(self): - """Return string representation of provider. - - :rtype: str - """ - return represent_provider(provider=self, provides=self.callback) - - __repr__ = __str__ - @six.python_2_unicode_compatible class ExternalDependency(Provider): @@ -604,6 +573,12 @@ class ExternalDependency(Provider): database_provider.override(Factory(sqlite3.connect, ':memory:')) database = database_provider() + + .. py:attribute:: instance_of + + Class of required dependency. + + :type: type """ __slots__ = ('instance_of',) @@ -614,10 +589,6 @@ 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): @@ -670,6 +641,12 @@ class Static(Provider): :py:class:`Static` provider is base implementation that provides exactly the same as it got on input. + + .. py:attribute:: provides + + Value that have to be provided. + + :type: object """ __slots__ = ('provides',) @@ -681,10 +658,6 @@ class Static(Provider): :type provides: object """ self.provides = provides - """Value that have to be provided. - - :type: object - """ super(Static, self).__init__() def _provide(self, *args, **kwargs): @@ -847,17 +820,7 @@ class ChildConfig(Provider): :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): diff --git a/docs/api/providers.rst b/docs/api/providers.rst index 4989b44e..b5f3ecc1 100644 --- a/docs/api/providers.rst +++ b/docs/api/providers.rst @@ -4,3 +4,4 @@ .. automodule:: dependency_injector.providers :members: :inherited-members: + :show-inheritance: diff --git a/docs/main/changelog.rst b/docs/main/changelog.rst index b791b1f6..23af0308 100644 --- a/docs/main/changelog.rst +++ b/docs/main/changelog.rst @@ -11,6 +11,9 @@ Development version ------------------- - Add possibility to validate ``Factory`` provided type on ``Factory`` initialization. +- Add possibility to validate ``Singleton`` provided type on ``Singleton`` + initialization. +- Make some refactorings for providers. 1.11.2 ------ diff --git a/tests/test_providers.py b/tests/test_providers.py index 3621958c..e29c2a3c 100644 --- a/tests/test_providers.py +++ b/tests/test_providers.py @@ -176,44 +176,44 @@ class FactoryTests(unittest.TestCase): """Test creation of provider with a callable.""" self.assertTrue(providers.Factory(credits)) + def test_init_with_not_callable(self): + """Test creation of provider with not a callable.""" + self.assertRaises(errors.Error, providers.Factory, 123) + def test_init_with_valid_provided_type(self): """Test creation with not valid provided type.""" - class ExampleFactory(providers.Factory): - """Example factory.""" + class ExampleProvider(providers.Factory): + """Example provider.""" provided_type = Example - example_factory = ExampleFactory(Example, 1, 2) + example_provider = ExampleProvider(Example, 1, 2) - self.assertIsInstance(example_factory(), Example) + self.assertIsInstance(example_provider(), Example) def test_init_with_valid_provided_subtype(self): """Test creation with not valid provided type.""" - class ExampleFactory(providers.Factory): - """Example factory.""" + class ExampleProvider(providers.Factory): + """Example provider.""" provided_type = Example class NewExampe(Example): """Example class subclass.""" - example_factory = ExampleFactory(NewExampe, 1, 2) + example_provider = ExampleProvider(NewExampe, 1, 2) - self.assertIsInstance(example_factory(), NewExampe) + self.assertIsInstance(example_provider(), NewExampe) def test_init_with_invalid_provided_type(self): """Test creation with not valid provided type.""" - class ExampleFactory(providers.Factory): - """Example factory.""" + class ExampleProvider(providers.Factory): + """Example provider.""" provided_type = Example with self.assertRaises(errors.Error): - ExampleFactory(list) - - def test_init_with_not_callable(self): - """Test creation of provider with not a callable.""" - self.assertRaises(errors.Error, providers.Factory, 123) + ExampleProvider(list) def test_call(self): """Test creation of new instances.""" @@ -434,6 +434,41 @@ class SingletonTests(unittest.TestCase): """Test creation of provider with not a callable.""" self.assertRaises(errors.Error, providers.Singleton, 123) + def test_init_with_valid_provided_type(self): + """Test creation with not valid provided type.""" + class ExampleProvider(providers.Singleton): + """Example provider.""" + + provided_type = Example + + example_provider = ExampleProvider(Example, 1, 2) + + self.assertIsInstance(example_provider(), Example) + + def test_init_with_valid_provided_subtype(self): + """Test creation with not valid provided type.""" + class ExampleProvider(providers.Singleton): + """Example provider.""" + + provided_type = Example + + class NewExampe(Example): + """Example class subclass.""" + + example_provider = ExampleProvider(NewExampe, 1, 2) + + self.assertIsInstance(example_provider(), NewExampe) + + def test_init_with_invalid_provided_type(self): + """Test creation with not valid provided type.""" + class ExampleProvider(providers.Singleton): + """Example provider.""" + + provided_type = Example + + with self.assertRaises(errors.Error): + ExampleProvider(list) + def test_call(self): """Test getting of instances.""" provider = providers.Singleton(Example) From 80a329d480b2ad799b954151a74d18fbfc2dc53f Mon Sep 17 00:00:00 2001 From: Roman Mogilatov Date: Sun, 13 Dec 2015 14:22:59 +0200 Subject: [PATCH 3/3] Add docs about Factory and Singleton provides specialization --- docs/images/providers/singleton_internals.png | Bin 26376 -> 0 bytes docs/main/changelog.rst | 6 +-- docs/providers/factory.rst | 11 ++++++ docs/providers/singleton.rst | 21 +++++++---- examples/providers/factory_provided_type.py | 35 ++++++++++++++++++ examples/providers/singleton_provided_type.py | 35 ++++++++++++++++++ 6 files changed, 97 insertions(+), 11 deletions(-) delete mode 100644 docs/images/providers/singleton_internals.png create mode 100644 examples/providers/factory_provided_type.py create mode 100644 examples/providers/singleton_provided_type.py diff --git a/docs/images/providers/singleton_internals.png b/docs/images/providers/singleton_internals.png deleted file mode 100644 index a327f390050e4cb5ab19575c9e9515529c5758c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26376 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 diff --git a/docs/main/changelog.rst b/docs/main/changelog.rst index 23af0308..9b05a400 100644 --- a/docs/main/changelog.rst +++ b/docs/main/changelog.rst @@ -9,10 +9,8 @@ follows `Semantic versioning`_ Development version ------------------- -- Add possibility to validate ``Factory`` provided type on ``Factory`` - initialization. -- Add possibility to validate ``Singleton`` provided type on ``Singleton`` - initialization. +- Add possibility to specialize ``Factory`` provided type. +- Add possibility to specialize ``Singleton`` provided type. - Make some refactorings for providers. 1.11.2 diff --git a/docs/providers/factory.rst b/docs/providers/factory.rst index 4879317d..a811a15c 100644 --- a/docs/providers/factory.rst +++ b/docs/providers/factory.rst @@ -174,3 +174,14 @@ Example: .. literalinclude:: ../../examples/providers/factory_delegation.py :language: python + +Factory providers specialization +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:py:class:`Factory` provider could be specialized for any kind of needs via +declaring its subclasses. + +One of such features is a limitation to :py:class:`Factory` provided type: + +.. literalinclude:: ../../examples/providers/factory_provided_type.py + :language: python diff --git a/docs/providers/singleton.rst b/docs/providers/singleton.rst index 4e6f59b2..47316503 100644 --- a/docs/providers/singleton.rst +++ b/docs/providers/singleton.rst @@ -18,13 +18,9 @@ Example: Singleton providers and injections ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -: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% - :align: center +:py:class:`Singleton` provider extends :py:class:`Factory` provider, so, all +of the rules about injections are the same, as for :py:class:`Factory` +provider. .. note:: @@ -73,3 +69,14 @@ Example: .. literalinclude:: ../../examples/providers/singleton_delegation.py :language: python + +Singleton providers specialization +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:py:class:`Singleton` provider could be specialized for any kind of needs via +declaring its subclasses. + +One of such features is a limitation to :py:class:`Singleton` provided type: + +.. literalinclude:: ../../examples/providers/singleton_provided_type.py + :language: python diff --git a/examples/providers/factory_provided_type.py b/examples/providers/factory_provided_type.py new file mode 100644 index 00000000..4a7ca8f0 --- /dev/null +++ b/examples/providers/factory_provided_type.py @@ -0,0 +1,35 @@ +"""`Factory` specialization for limitation to provided type example.""" + +from dependency_injector import providers +from dependency_injector import errors + + +class BaseService(object): + """Base service class.""" + + +class UsersService(BaseService): + """Users service.""" + + +class PhotosService(BaseService): + """Photos service.""" + + +class ServiceProvider(providers.Factory): + """Service provider.""" + + provided_type = BaseService + + +# Creating several service providers with BaseService instances: +users_service_provider = ServiceProvider(UsersService) +photos_service_provider = ServiceProvider(PhotosService) + +# Trying to create service provider with not a BaseService instance: +try: + some_service_provider = ServiceProvider(object) +except errors.Error as exception: + print exception + # __main__.ServiceProvider can provide only + # instances diff --git a/examples/providers/singleton_provided_type.py b/examples/providers/singleton_provided_type.py new file mode 100644 index 00000000..17628cd3 --- /dev/null +++ b/examples/providers/singleton_provided_type.py @@ -0,0 +1,35 @@ +"""`Singleton` specialization for limitation to provided type example.""" + +from dependency_injector import providers +from dependency_injector import errors + + +class BaseService(object): + """Base service class.""" + + +class UsersService(BaseService): + """Users service.""" + + +class PhotosService(BaseService): + """Photos service.""" + + +class ServiceProvider(providers.Singleton): + """Service provider.""" + + provided_type = BaseService + + +# Creating several service providers with BaseService instances: +users_service_provider = ServiceProvider(UsersService) +photos_service_provider = ServiceProvider(PhotosService) + +# Trying to create service provider with not a BaseService instance: +try: + some_service_provider = ServiceProvider(object) +except errors.Error as exception: + print exception + # __main__.ServiceProvider can provide only + # instances