From 824ed4f3e99f0c18819bc211196b908ddd5cd5e2 Mon Sep 17 00:00:00 2001 From: Roman Mogilatov Date: Wed, 14 Oct 2015 14:30:01 +0300 Subject: [PATCH 1/4] Add support of positional args for Factory & Singleton providers --- dependency_injector/__init__.py | 4 +++ dependency_injector/injections.py | 28 +++++++++++---- dependency_injector/providers.py | 28 +++++++++------ dependency_injector/utils.py | 24 +++++++++---- tests/test_injections.py | 16 ++++++--- tests/test_providers.py | 59 ++++++++++++++++++++++++++----- tests/test_utils.py | 29 +++++++++++++-- 7 files changed, 150 insertions(+), 38 deletions(-) diff --git a/dependency_injector/__init__.py b/dependency_injector/__init__.py index 5e38585a..9da694d8 100644 --- a/dependency_injector/__init__.py +++ b/dependency_injector/__init__.py @@ -18,6 +18,7 @@ from .providers import Callable from .providers import Config from .injections import Injection +from .injections import Arg from .injections import KwArg from .injections import Attribute from .injections import Method @@ -27,6 +28,7 @@ from .utils import is_provider from .utils import ensure_is_provider from .utils import is_injection from .utils import ensure_is_injection +from .utils import is_arg_injection from .utils import is_kwarg_injection from .utils import is_attribute_injection from .utils import is_method_injection @@ -59,6 +61,7 @@ __all__ = ( # Injections 'Injection', + 'Arg', 'KwArg', 'Attribute', 'Method', @@ -69,6 +72,7 @@ __all__ = ( 'ensure_is_provider', 'is_injection', 'ensure_is_injection', + 'is_arg_injection', 'is_kwarg_injection', 'is_attribute_injection', 'is_method_injection', diff --git a/dependency_injector/injections.py b/dependency_injector/injections.py index a7a29084..2806b7c0 100644 --- a/dependency_injector/injections.py +++ b/dependency_injector/injections.py @@ -21,11 +21,10 @@ class Injection(object): """Base injection class.""" __IS_INJECTION__ = True - __slots__ = ('name', 'injectable', 'is_provider') + __slots__ = ('injectable', 'is_provider') - def __init__(self, name, injectable): + def __init__(self, injectable): """Initializer.""" - self.name = name self.injectable = injectable self.is_provider = is_provider(injectable) @@ -37,19 +36,36 @@ class Injection(object): return self.injectable -class KwArg(Injection): +class NamedInjection(Injection): + """Base class of named injections.""" + + __slots__ = ('name',) + + def __init__(self, name, injectable): + """Initializer.""" + self.name = name + super(NamedInjection, self).__init__(injectable) + + +class Arg(Injection): + """Positional argument injection.""" + + __IS_ARG_INJECTION__ = True + + +class KwArg(NamedInjection): """Keyword argument injection.""" __IS_KWARG_INJECTION__ = True -class Attribute(Injection): +class Attribute(NamedInjection): """Attribute injection.""" __IS_ATTRIBUTE_INJECTION__ = True -class Method(Injection): +class Method(NamedInjection): """Method injection.""" __IS_METHOD_INJECTION__ = True diff --git a/dependency_injector/providers.py b/dependency_injector/providers.py index b510bd6b..71643749 100644 --- a/dependency_injector/providers.py +++ b/dependency_injector/providers.py @@ -2,12 +2,16 @@ import six +from .injections import Arg from .injections import KwArg from .utils import ensure_is_provider +from .utils import is_injection +from .utils import is_arg_injection from .utils import is_kwarg_injection from .utils import is_attribute_injection from .utils import is_method_injection +from .utils import get_injectable_args from .utils import get_injectable_kwargs from .utils import GLOBAL_LOCK @@ -104,33 +108,35 @@ class Factory(Provider): Factory provider creates new instance of specified class on every call. """ - __slots__ = ('provides', 'kwargs', 'attributes', 'methods') + __slots__ = ('provides', 'args', 'kwargs', 'attributes', 'methods') - def __init__(self, provides, *injections, **kwargs): + def __init__(self, provides, *args, **kwargs): """Initializer.""" if not callable(provides): raise Error('Factory provider expects to get callable, ' + 'got {0} instead'.format(str(provides))) self.provides = provides + self.args = tuple(Arg(arg) if not is_injection(arg) else arg + for arg in args + if not is_injection(arg) or is_arg_injection(arg)) self.kwargs = tuple(injection - for injection in injections + for injection in args if is_kwarg_injection(injection)) if kwargs: self.kwargs += tuple(KwArg(name, value) for name, value in six.iteritems(kwargs)) self.attributes = tuple(injection - for injection in injections + for injection in args if is_attribute_injection(injection)) self.methods = tuple(injection - for injection in injections + for injection in args if is_method_injection(injection)) super(Factory, self).__init__() def _provide(self, *args, **kwargs): """Return provided instance.""" - instance = self.provides(*args, - **get_injectable_kwargs(kwargs, - self.kwargs)) + instance = self.provides(*get_injectable_args(args, self.args), + **get_injectable_kwargs(kwargs, self.kwargs)) for attribute in self.attributes: setattr(instance, attribute.name, attribute.value) for method in self.methods: @@ -141,7 +147,7 @@ class Factory(Provider): @property def injections(self): """Return tuple of all injections.""" - return self.kwargs + self.attributes + self.methods + return self.args + self.kwargs + self.attributes + self.methods class Singleton(Provider): @@ -152,10 +158,10 @@ class Singleton(Provider): __slots__ = ('instance', 'factory') - def __init__(self, provides, *injections, **kwargs): + def __init__(self, provides, *args, **kwargs): """Initializer.""" self.instance = None - self.factory = Factory(provides, *injections, **kwargs) + self.factory = Factory(provides, *args, **kwargs) super(Singleton, self).__init__() def _provide(self, *args, **kwargs): diff --git a/dependency_injector/utils.py b/dependency_injector/utils.py index 29547b9c..007b70b0 100644 --- a/dependency_injector/utils.py +++ b/dependency_injector/utils.py @@ -1,6 +1,7 @@ """Utils module.""" import threading +import itertools import six @@ -41,6 +42,12 @@ def ensure_is_injection(instance): return instance +def is_arg_injection(instance): + """Check if instance is positional argument injection instance.""" + return (not isinstance(instance, six.class_types) and + getattr(instance, '__IS_ARG_INJECTION__', False) is True) + + def is_kwarg_injection(instance): """Check if instance is keyword argument injection instance.""" return (not isinstance(instance, six.class_types) and @@ -82,9 +89,14 @@ def ensure_is_catalog_bundle(instance): return instance -def get_injectable_kwargs(kwargs, injections): - """Return dictionary of kwargs, patched with injections.""" - init_kwargs = dict(((injection.name, injection.value) - for injection in injections)) - init_kwargs.update(kwargs) - return init_kwargs +def get_injectable_args(context_args, arg_injections): + """Return tuple of positional args, patched with injections.""" + return itertools.chain((arg.value for arg in arg_injections), context_args) + + +def get_injectable_kwargs(context_kwargs, kwarg_injections): + """Return dictionary of keyword args, patched with injections.""" + kwargs = dict((kwarg.name, kwarg.value) + for kwarg in kwarg_injections) + kwargs.update(context_kwargs) + return kwargs diff --git a/tests/test_injections.py b/tests/test_injections.py index c96567b7..2226a2fc 100644 --- a/tests/test_injections.py +++ b/tests/test_injections.py @@ -9,18 +9,17 @@ class InjectionTests(unittest.TestCase): def test_init(self): """Test Injection creation and initialization.""" - injection = di.Injection('some_arg_name', 'some_value') - self.assertEqual(injection.name, 'some_arg_name') + injection = di.Injection('some_value') self.assertEqual(injection.injectable, 'some_value') def test_value_with_scalar_injectable(self): """Test Injection value property with scalar value.""" - injection = di.Injection('some_arg_name', 'some_value') + injection = di.Injection('some_value') self.assertEqual(injection.value, 'some_value') def test_value_with_provider_injectable(self): """Test Injection value property with provider.""" - injection = di.Injection('some_arg_name', di.Factory(object)) + injection = di.Injection(di.Factory(object)) self.assertIsInstance(injection.value, object) def test_value_with_catalog_bundle_injectable(self): @@ -35,6 +34,15 @@ class InjectionTests(unittest.TestCase): self.assertIsInstance(injection.value, TestCatalog.Bundle) +class ArgTests(unittest.TestCase): + """Positional arg injection test cases.""" + + def test_init(self): + """Test Arg creation and initialization.""" + injection = di.Arg('some_value') + self.assertEqual(injection.injectable, 'some_value') + + class KwArgTests(unittest.TestCase): """Keyword arg injection test cases.""" diff --git a/tests/test_providers.py b/tests/test_providers.py index 35ced2f9..9591fc43 100644 --- a/tests/test_providers.py +++ b/tests/test_providers.py @@ -181,14 +181,12 @@ class FactoryTests(unittest.TestCase): self.assertIsInstance(instance1, self.Example) self.assertIsInstance(instance2, self.Example) - def test_call_with_init_args_simplified_syntax(self): - """Test creation of new instances with init args injections. + def test_call_with_init_positional_args(self): + """Test creation of new instances with init positional args. New simplified syntax. """ - provider = di.Factory(self.Example, - init_arg1='i1', - init_arg2='i2') + provider = di.Factory(self.Example, 'i1', 'i2') instance1 = provider() instance2 = provider() @@ -203,10 +201,53 @@ class FactoryTests(unittest.TestCase): self.assertIsInstance(instance1, self.Example) self.assertIsInstance(instance2, self.Example) - def test_call_with_init_args_old_syntax(self): - """Test creation of new instances with init args injections.""" + def test_call_with_init_keyword_args(self): + """Test creation of new instances with init keyword args. + + New simplified syntax. + """ + provider = di.Factory(self.Example, init_arg1='i1', init_arg2='i2') + + instance1 = provider() + instance2 = provider() + + self.assertEqual(instance1.init_arg1, 'i1') + self.assertEqual(instance1.init_arg2, 'i2') + + self.assertEqual(instance2.init_arg1, 'i1') + self.assertEqual(instance2.init_arg2, 'i2') + + self.assertIsNot(instance1, instance2) + self.assertIsInstance(instance1, self.Example) + self.assertIsInstance(instance2, self.Example) + + def test_call_with_init_positional_and_keyword_args(self): + """Test creation of new instances with init positional and keyword args. + + Simplified syntax of positional and keyword arg injections. + """ + provider = di.Factory(self.Example,'i1', init_arg2='i2') + + instance1 = provider() + instance2 = provider() + + self.assertEqual(instance1.init_arg1, 'i1') + self.assertEqual(instance1.init_arg2, 'i2') + + self.assertEqual(instance2.init_arg1, 'i1') + self.assertEqual(instance2.init_arg2, 'i2') + + self.assertIsNot(instance1, instance2) + self.assertIsInstance(instance1, self.Example) + self.assertIsInstance(instance2, self.Example) + + def test_call_with_init_positional_and_keyword_args_extended_syntax(self): + """Test creation of new instances with init positional and keyword args. + + Extended syntax of positional and keyword arg injections. + """ provider = di.Factory(self.Example, - di.KwArg('init_arg1', 'i1'), + di.Arg('i1'), di.KwArg('init_arg2', 'i2')) instance1 = provider() @@ -300,7 +341,7 @@ class FactoryTests(unittest.TestCase): def test_injections(self): """Test getting a full list of injections using Factory.injections.""" provider = di.Factory(self.Example, - di.KwArg('init_arg1', 1), + di.Arg(1), di.KwArg('init_arg2', 2), di.Attribute('attribute1', 3), di.Attribute('attribute2', 4), diff --git a/tests/test_utils.py b/tests/test_utils.py index 61705ff1..275989f0 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -68,10 +68,11 @@ class IsInjectionTests(unittest.TestCase): def test_with_instance(self): """Test with instance.""" - self.assertTrue(di.is_injection(di.Injection('name', 'value'))) + self.assertTrue(di.is_injection(di.Injection('value'))) def test_with_subclass_instances(self): """Test with subclass instances.""" + self.assertTrue(di.is_injection(di.Arg('value'))) self.assertTrue(di.is_injection(di.KwArg('name', 'value'))) self.assertTrue(di.is_injection(di.Attribute('name', 'value'))) self.assertTrue(di.is_injection(di.Method('name', 'value'))) @@ -94,7 +95,7 @@ class EnsureIsInjectionTests(unittest.TestCase): def test_with_instance(self): """Test with instance.""" - injection = di.Injection('name', 'value') + injection = di.Injection('value') self.assertIs(di.ensure_is_injection(injection), injection) def test_with_class(self): @@ -110,6 +111,30 @@ class EnsureIsInjectionTests(unittest.TestCase): self.assertRaises(di.Error, di.ensure_is_injection, object()) +class IsArgInjectionTests(unittest.TestCase): + """`is_arg_injection()` test cases.""" + + def test_with_instance(self): + """Test with instance.""" + self.assertTrue(di.is_arg_injection(di.Arg('value'))) + + def test_with_class(self): + """Test with class.""" + self.assertFalse(di.is_arg_injection(di.Arg)) + + def test_with_parent_class(self): + """Test with parent class.""" + self.assertFalse(di.is_arg_injection(di.Injection)) + + def test_with_string(self): + """Test with string.""" + self.assertFalse(di.is_arg_injection('some_string')) + + def test_with_object(self): + """Test with object.""" + self.assertFalse(di.is_arg_injection(object())) + + class IsKwArgInjectionTests(unittest.TestCase): """`is_kwarg_injection()` test cases.""" From b61e3ee00721dfc3248175b56a61af6d19eca0d2 Mon Sep 17 00:00:00 2001 From: Roman Mogilatov Date: Mon, 19 Oct 2015 14:53:38 +0300 Subject: [PATCH 2/4] Fix test of Injection.value with catalog bundle --- tests/test_injections.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_injections.py b/tests/test_injections.py index 2226a2fc..dd8573bd 100644 --- a/tests/test_injections.py +++ b/tests/test_injections.py @@ -28,8 +28,7 @@ class InjectionTests(unittest.TestCase): """Test catalog.""" provider = di.Provider() - injection = di.Injection('some_arg_name', - TestCatalog.Bundle(TestCatalog.provider)) + injection = di.Injection(TestCatalog.Bundle(TestCatalog.provider)) self.assertIsInstance(injection.value, TestCatalog.Bundle) From 9b4a7bd28ca1e2caed8018307414b061d09bc9fb Mon Sep 17 00:00:00 2001 From: Roman Mogilatov Date: Mon, 19 Oct 2015 15:20:25 +0300 Subject: [PATCH 3/4] Add additional unittests for full validation of Singleton provider --- dependency_injector/providers.py | 5 + tests/test_providers.py | 298 +++++++++++++++++++++++++------ 2 files changed, 246 insertions(+), 57 deletions(-) diff --git a/dependency_injector/providers.py b/dependency_injector/providers.py index 71643749..ed7d2145 100644 --- a/dependency_injector/providers.py +++ b/dependency_injector/providers.py @@ -175,6 +175,11 @@ class Singleton(Provider): """Reset instance.""" self.instance = None + @property + def injections(self): + """Return tuple of all injections.""" + return self.factory.injections + class ExternalDependency(Provider): """External dependency provider. diff --git a/tests/test_providers.py b/tests/test_providers.py index 9591fc43..9887f952 100644 --- a/tests/test_providers.py +++ b/tests/test_providers.py @@ -4,6 +4,34 @@ import unittest2 as unittest import dependency_injector as di +class Example(object): + """Example class for Factory provider tests.""" + + def __init__(self, init_arg1=None, init_arg2=None): + """Initializer. + + :param init_arg1: + :param init_arg2: + :return: + """ + self.init_arg1 = init_arg1 + self.init_arg2 = init_arg2 + + self.attribute1 = None + self.attribute2 = None + + self.method1_value = None + self.method2_value = None + + def method1(self, value): + """Setter method 1.""" + self.method1_value = value + + def method2(self, value): + """Setter method 2.""" + self.method2_value = value + + class ProviderTests(unittest.TestCase): """Provider test cases.""" @@ -132,36 +160,9 @@ class DelegateTests(unittest.TestCase): class FactoryTests(unittest.TestCase): """Factory test cases.""" - class Example(object): - """Example class for Factory provider tests.""" - - def __init__(self, init_arg1=None, init_arg2=None): - """Initializer. - - :param init_arg1: - :param init_arg2: - :return: - """ - self.init_arg1 = init_arg1 - self.init_arg2 = init_arg2 - - self.attribute1 = None - self.attribute2 = None - - self.method1_value = None - self.method2_value = None - - def method1(self, value): - """Setter method 1.""" - self.method1_value = value - - def method2(self, value): - """Setter method 2.""" - self.method2_value = value - def test_is_provider(self): """Test `is_provider` check.""" - self.assertTrue(di.is_provider(di.Factory(self.Example))) + self.assertTrue(di.is_provider(di.Factory(Example))) def test_init_with_callable(self): """Test creation of provider with a callable.""" @@ -173,20 +174,20 @@ class FactoryTests(unittest.TestCase): def test_call(self): """Test creation of new instances.""" - provider = di.Factory(self.Example) + provider = di.Factory(Example) instance1 = provider() instance2 = provider() self.assertIsNot(instance1, instance2) - self.assertIsInstance(instance1, self.Example) - self.assertIsInstance(instance2, self.Example) + self.assertIsInstance(instance1, Example) + self.assertIsInstance(instance2, Example) def test_call_with_init_positional_args(self): """Test creation of new instances with init positional args. New simplified syntax. """ - provider = di.Factory(self.Example, 'i1', 'i2') + provider = di.Factory(Example, 'i1', 'i2') instance1 = provider() instance2 = provider() @@ -198,15 +199,15 @@ class FactoryTests(unittest.TestCase): self.assertEqual(instance2.init_arg2, 'i2') self.assertIsNot(instance1, instance2) - self.assertIsInstance(instance1, self.Example) - self.assertIsInstance(instance2, self.Example) + self.assertIsInstance(instance1, Example) + self.assertIsInstance(instance2, Example) def test_call_with_init_keyword_args(self): """Test creation of new instances with init keyword args. New simplified syntax. """ - provider = di.Factory(self.Example, init_arg1='i1', init_arg2='i2') + provider = di.Factory(Example, init_arg1='i1', init_arg2='i2') instance1 = provider() instance2 = provider() @@ -218,15 +219,15 @@ class FactoryTests(unittest.TestCase): self.assertEqual(instance2.init_arg2, 'i2') self.assertIsNot(instance1, instance2) - self.assertIsInstance(instance1, self.Example) - self.assertIsInstance(instance2, self.Example) + self.assertIsInstance(instance1, Example) + self.assertIsInstance(instance2, Example) def test_call_with_init_positional_and_keyword_args(self): """Test creation of new instances with init positional and keyword args. Simplified syntax of positional and keyword arg injections. """ - provider = di.Factory(self.Example,'i1', init_arg2='i2') + provider = di.Factory(Example, 'i1', init_arg2='i2') instance1 = provider() instance2 = provider() @@ -238,15 +239,15 @@ class FactoryTests(unittest.TestCase): self.assertEqual(instance2.init_arg2, 'i2') self.assertIsNot(instance1, instance2) - self.assertIsInstance(instance1, self.Example) - self.assertIsInstance(instance2, self.Example) + self.assertIsInstance(instance1, Example) + self.assertIsInstance(instance2, Example) def test_call_with_init_positional_and_keyword_args_extended_syntax(self): """Test creation of new instances with init positional and keyword args. Extended syntax of positional and keyword arg injections. """ - provider = di.Factory(self.Example, + provider = di.Factory(Example, di.Arg('i1'), di.KwArg('init_arg2', 'i2')) @@ -260,12 +261,12 @@ class FactoryTests(unittest.TestCase): self.assertEqual(instance2.init_arg2, 'i2') self.assertIsNot(instance1, instance2) - self.assertIsInstance(instance1, self.Example) - self.assertIsInstance(instance2, self.Example) + self.assertIsInstance(instance1, Example) + self.assertIsInstance(instance2, Example) def test_call_with_attributes(self): """Test creation of new instances with attribute injections.""" - provider = di.Factory(self.Example, + provider = di.Factory(Example, di.Attribute('attribute1', 'a1'), di.Attribute('attribute2', 'a2')) @@ -279,12 +280,12 @@ class FactoryTests(unittest.TestCase): self.assertEqual(instance2.attribute2, 'a2') self.assertIsNot(instance1, instance2) - self.assertIsInstance(instance1, self.Example) - self.assertIsInstance(instance2, self.Example) + self.assertIsInstance(instance1, Example) + self.assertIsInstance(instance2, Example) def test_call_with_methods(self): """Test creation of new instances with method injections.""" - provider = di.Factory(self.Example, + provider = di.Factory(Example, di.Method('method1', 'm1'), di.Method('method2', 'm2')) @@ -298,12 +299,12 @@ class FactoryTests(unittest.TestCase): self.assertEqual(instance2.method2_value, 'm2') self.assertIsNot(instance1, instance2) - self.assertIsInstance(instance1, self.Example) - self.assertIsInstance(instance2, self.Example) + self.assertIsInstance(instance1, Example) + self.assertIsInstance(instance2, Example) def test_call_with_context_args(self): """Test creation of new instances with context args.""" - provider = di.Factory(self.Example) + provider = di.Factory(Example) instance = provider(11, 22) self.assertEqual(instance.init_arg1, 11) @@ -311,7 +312,7 @@ class FactoryTests(unittest.TestCase): def test_call_with_context_kwargs(self): """Test creation of new instances with context kwargs.""" - provider = di.Factory(self.Example, + provider = di.Factory(Example, di.KwArg('init_arg1', 1)) instance1 = provider(init_arg2=22) @@ -324,7 +325,7 @@ class FactoryTests(unittest.TestCase): def test_call_overridden(self): """Test creation of new instances on overridden provider.""" - provider = di.Factory(self.Example) + provider = di.Factory(Example) overriding_provider1 = di.Factory(dict) overriding_provider2 = di.Factory(list) @@ -339,8 +340,8 @@ class FactoryTests(unittest.TestCase): self.assertIsInstance(instance2, list) def test_injections(self): - """Test getting a full list of injections using Factory.injections.""" - provider = di.Factory(self.Example, + """Test getting a full list of injections using injections property.""" + provider = di.Factory(Example, di.Arg(1), di.KwArg('init_arg2', 2), di.Attribute('attribute1', 3), @@ -356,16 +357,199 @@ class FactoryTests(unittest.TestCase): class SingletonTests(unittest.TestCase): """Singleton test cases.""" + def test_is_provider(self): + """Test `is_provider` check.""" + self.assertTrue(di.is_provider(di.Singleton(Example))) + + def test_init_with_callable(self): + """Test creation of provider with a callable.""" + self.assertTrue(di.Singleton(credits)) + + def test_init_with_not_callable(self): + """Test creation of provider with not a callable.""" + self.assertRaises(di.Error, di.Singleton, 123) + def test_call(self): - """Test creation and returning of single object.""" - provider = di.Singleton(object) + """Test getting of instances.""" + provider = di.Singleton(Example) + instance1 = provider() + instance2 = provider() + + self.assertIs(instance1, instance2) + self.assertIsInstance(instance1, Example) + self.assertIsInstance(instance2, Example) + + def test_call_with_init_positional_args(self): + """Test getting of instances with init positional args. + + New simplified syntax. + """ + provider = di.Singleton(Example, 'i1', 'i2') instance1 = provider() instance2 = provider() + self.assertEqual(instance1.init_arg1, 'i1') + self.assertEqual(instance1.init_arg2, 'i2') + + self.assertEqual(instance2.init_arg1, 'i1') + self.assertEqual(instance2.init_arg2, 'i2') + + self.assertIs(instance1, instance2) + self.assertIsInstance(instance1, Example) + self.assertIsInstance(instance2, Example) + + def test_call_with_init_keyword_args(self): + """Test getting of instances with init keyword args. + + New simplified syntax. + """ + provider = di.Singleton(Example, init_arg1='i1', init_arg2='i2') + + instance1 = provider() + instance2 = provider() + + self.assertEqual(instance1.init_arg1, 'i1') + self.assertEqual(instance1.init_arg2, 'i2') + + self.assertEqual(instance2.init_arg1, 'i1') + self.assertEqual(instance2.init_arg2, 'i2') + + self.assertIs(instance1, instance2) + self.assertIsInstance(instance1, Example) + self.assertIsInstance(instance2, Example) + + def test_call_with_init_positional_and_keyword_args(self): + """Test getting of instances with init positional and keyword args. + + Simplified syntax of positional and keyword arg injections. + """ + provider = di.Singleton(Example, 'i1', init_arg2='i2') + + instance1 = provider() + instance2 = provider() + + self.assertEqual(instance1.init_arg1, 'i1') + self.assertEqual(instance1.init_arg2, 'i2') + + self.assertEqual(instance2.init_arg1, 'i1') + self.assertEqual(instance2.init_arg2, 'i2') + + self.assertIs(instance1, instance2) + self.assertIsInstance(instance1, Example) + self.assertIsInstance(instance2, Example) + + def test_call_with_init_positional_and_keyword_args_extended_syntax(self): + """Test getting of instances with init positional and keyword args. + + Extended syntax of positional and keyword arg injections. + """ + provider = di.Singleton(Example, + di.Arg('i1'), + di.KwArg('init_arg2', 'i2')) + + instance1 = provider() + instance2 = provider() + + self.assertEqual(instance1.init_arg1, 'i1') + self.assertEqual(instance1.init_arg2, 'i2') + + self.assertEqual(instance2.init_arg1, 'i1') + self.assertEqual(instance2.init_arg2, 'i2') + + self.assertIs(instance1, instance2) + self.assertIsInstance(instance1, Example) + self.assertIsInstance(instance2, Example) + + def test_call_with_attributes(self): + """Test getting of instances with attribute injections.""" + provider = di.Singleton(Example, + di.Attribute('attribute1', 'a1'), + di.Attribute('attribute2', 'a2')) + + instance1 = provider() + instance2 = provider() + + self.assertEqual(instance1.attribute1, 'a1') + self.assertEqual(instance1.attribute2, 'a2') + + self.assertEqual(instance2.attribute1, 'a1') + self.assertEqual(instance2.attribute2, 'a2') + + self.assertIs(instance1, instance2) + self.assertIsInstance(instance1, Example) + self.assertIsInstance(instance2, Example) + + def test_call_with_methods(self): + """Test getting of instances with method injections.""" + provider = di.Singleton(Example, + di.Method('method1', 'm1'), + di.Method('method2', 'm2')) + + instance1 = provider() + instance2 = provider() + + self.assertEqual(instance1.method1_value, 'm1') + self.assertEqual(instance1.method2_value, 'm2') + + self.assertEqual(instance2.method1_value, 'm1') + self.assertEqual(instance2.method2_value, 'm2') + + self.assertIs(instance1, instance2) + self.assertIsInstance(instance1, Example) + self.assertIsInstance(instance2, Example) + + def test_call_with_context_args(self): + """Test getting of instances with context args.""" + provider = di.Singleton(Example) + instance = provider(11, 22) + + self.assertEqual(instance.init_arg1, 11) + self.assertEqual(instance.init_arg2, 22) + + def test_call_with_context_kwargs(self): + """Test getting of instances with context kwargs.""" + provider = di.Singleton(Example, + di.KwArg('init_arg1', 1)) + + instance1 = provider(init_arg2=22) + self.assertEqual(instance1.init_arg1, 1) + self.assertEqual(instance1.init_arg2, 22) + + # Instance is created earlier + instance1 = provider(init_arg1=11, init_arg2=22) + self.assertEqual(instance1.init_arg1, 1) + self.assertEqual(instance1.init_arg2, 22) + + def test_call_overridden(self): + """Test getting of instances on overridden provider.""" + provider = di.Singleton(Example) + overriding_provider1 = di.Singleton(dict) + overriding_provider2 = di.Singleton(object) + + provider.override(overriding_provider1) + provider.override(overriding_provider2) + + instance1 = provider() + instance2 = provider() + + self.assertIs(instance1, instance2) self.assertIsInstance(instance1, object) self.assertIsInstance(instance2, object) - self.assertIs(instance1, instance2) + + def test_injections(self): + """Test getting a full list of injections using injections property.""" + provider = di.Singleton(Example, + di.Arg(1), + di.KwArg('init_arg2', 2), + di.Attribute('attribute1', 3), + di.Attribute('attribute2', 4), + di.Method('method1', 5), + di.Method('method2', 6)) + + injections = provider.injections + + self.assertEquals(len(injections), 6) def test_reset(self): """Test creation and reset of single object.""" From cc29d68337a27425796554e37eb18cb70522b163 Mon Sep 17 00:00:00 2001 From: Roman Mogilatov Date: Mon, 19 Oct 2015 17:26:59 +0300 Subject: [PATCH 4/4] Update Factory docs about positional and keyword arguments --- docs/main/changelog.rst | 10 +-- docs/providers/factory.rst | 62 ++++++++++++------- examples/providers/factory_init_args.py | 34 ++++++++++ .../factory_init_injections_and_contexts.py | 3 +- ...t_injections.py => factory_init_kwargs.py} | 5 +- 5 files changed, 82 insertions(+), 32 deletions(-) create mode 100644 examples/providers/factory_init_args.py rename examples/providers/{factory_init_injections.py => factory_init_kwargs.py} (83%) diff --git a/docs/main/changelog.rst b/docs/main/changelog.rst index 20c321eb..c3890dda 100644 --- a/docs/main/changelog.rst +++ b/docs/main/changelog.rst @@ -11,15 +11,17 @@ follows `Semantic versioning`_ Development version ------------------- -- Add functionality for decorating classes with ``@di.inject``. - Add functionality for creating ``di.AbstractCatalog`` provider bundles. -- Add enhancement for ``di.AbstractCatalog`` inheritance. +- Enhance ``di.AbstractCatalog`` inheritance. - Add images for catalog "Writing catalogs" and "Operating with catalogs" examples. -- Add support of Python 3.5. -- Add support of six 1.10.0. +- Add functionality for using positional argument injections with + ``di.Factory`` and ``di.Singleton`` providers. - Add optimization for ``di.Injection.value`` property that will compute type of injection once, instead of doing this on every call. +- Add functionality for decorating classes with ``@di.inject``. +- Add support of Python 3.5. +- Add support of six 1.10.0. - Add minor refactorings and code style fixes. 0.9.5 diff --git a/docs/providers/factory.rst b/docs/providers/factory.rst index 5f9c6109..320e768b 100644 --- a/docs/providers/factory.rst +++ b/docs/providers/factory.rst @@ -15,46 +15,57 @@ Nothing could be better than brief example: Factory providers and __init__ injections ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -``di.Factory`` takes a various number of keyword arguments that are -transformed into keyword argument injections. Every time, when ``di.Factory`` -creates new one instance, keyword argument injections would be passed as an -instance's keyword arguments. +``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. -All 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. For example, if injectable value of keyword argument injection is a -``di.Factory``, it will provide new one instance (as a result of its call) as -an injectable value every time, when injection needs to be done. +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 +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. Example below is a little bit more complicated. It shows how to create -``di.Factory`` of particular class with ``__init__`` keyword argument -injections which injectable values are also provided by another factories: +``di.Factory`` of particular class with ``__init__()`` argument injections +which injectable values are also provided by another factories: .. note:: - Current keyword argument injections syntax (in an example below) is a - **simplified one**. Full syntax and other types of injections could be - found in sections below. + Current positional and keyword argument injections syntax (in the examples + below) is a **simplified one** version of full syntax. Examples of full + syntax and other types of injections could be found in sections below. - While keyword argument injections may be the best way of passing - injections, current simplified syntax might be the preferable one and - could be widely used. + While positional / keyword argument injections may be the best way of + passing injections, current simplified syntax might be the preferable one + and could be widely used. .. image:: /images/providers/factory_init_injections.png :width: 90% :align: center -.. literalinclude:: ../../examples/providers/factory_init_injections.py +Example of usage positional argument injections: + +.. literalinclude:: ../../examples/providers/factory_init_args.py + :language: python + +Example of usage keyword argument injections: + +.. literalinclude:: ../../examples/providers/factory_init_kwargs.py :language: python 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`` -provider fully passes positional context arguments to class's ``__init__`` -method, but keyword context arguments have priority on predefined keyword -argument injections. +keyword ``__init__()`` context arguments. In few words, ``di.Factory`` +behaviour here is very like a standard Python ``functools.partial``: + +- Positional context arguments will be appended after ``di.Factory`` + positional injections. +- Keyword context arguments have priority on ``di.Factory`` keyword injections + and will be merged over them. So, please, follow the example below: @@ -67,7 +78,7 @@ So, please, follow the example below: Factory providers and other types of injections ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Objects can take dependencies in different forms(some objects take init +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. @@ -78,8 +89,11 @@ 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: ++ ``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 via keyword argument. + ``__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 diff --git a/examples/providers/factory_init_args.py b/examples/providers/factory_init_args.py new file mode 100644 index 00000000..d3b66eff --- /dev/null +++ b/examples/providers/factory_init_args.py @@ -0,0 +1,34 @@ +"""`di.Factory` providers with init positional injections example.""" + +import dependency_injector as di + + +class User(object): + """Example class User.""" + + def __init__(self, main_photo): + """Initializer.""" + self.main_photo = main_photo + super(User, self).__init__() + + +class Photo(object): + """Example class Photo.""" + +# User and Photo factories: +photos_factory = di.Factory(Photo) +users_factory = di.Factory(User, photos_factory) + +# Creating several User objects: +user1 = users_factory() # Same as: user1 = User(Photo()) +user2 = users_factory() # Same as: user2 = User(Photo()) + +# Making some asserts: +assert isinstance(user1, User) +assert isinstance(user1.main_photo, Photo) + +assert isinstance(user2, User) +assert isinstance(user2.main_photo, Photo) + +assert user1 is not user2 +assert user1.main_photo is not user2.main_photo diff --git a/examples/providers/factory_init_injections_and_contexts.py b/examples/providers/factory_init_injections_and_contexts.py index 6bc196d3..7917ba0b 100644 --- a/examples/providers/factory_init_injections_and_contexts.py +++ b/examples/providers/factory_init_injections_and_contexts.py @@ -62,7 +62,8 @@ assert user1.credit_card is not user2.credit_card main_photo_mock = Photo() credit_card_mock = CreditCard() -user3 = users_factory(3, main_photo=main_photo_mock, +user3 = users_factory(3, + main_photo=main_photo_mock, credit_card=credit_card_mock) assert user3.id == 3 diff --git a/examples/providers/factory_init_injections.py b/examples/providers/factory_init_kwargs.py similarity index 83% rename from examples/providers/factory_init_injections.py rename to examples/providers/factory_init_kwargs.py index 1649c9ec..2159e335 100644 --- a/examples/providers/factory_init_injections.py +++ b/examples/providers/factory_init_kwargs.py @@ -1,4 +1,4 @@ -"""`di.Factory` providers with init injections example.""" +"""`di.Factory` providers with init keyword injections example.""" import dependency_injector as di @@ -17,8 +17,7 @@ class Photo(object): # User and Photo factories: photos_factory = di.Factory(Photo) -users_factory = di.Factory(User, - main_photo=photos_factory) +users_factory = di.Factory(User, main_photo=photos_factory) # Creating several User objects: user1 = users_factory() # Same as: user1 = User(main_photo=Photo())