Add support of positional args for Factory & Singleton providers

This commit is contained in:
Roman Mogilatov 2015-10-14 14:30:01 +03:00
parent 2a5ed703bf
commit 824ed4f3e9
7 changed files with 150 additions and 38 deletions

View File

@ -18,6 +18,7 @@ from .providers import Callable
from .providers import Config from .providers import Config
from .injections import Injection from .injections import Injection
from .injections import Arg
from .injections import KwArg from .injections import KwArg
from .injections import Attribute from .injections import Attribute
from .injections import Method from .injections import Method
@ -27,6 +28,7 @@ from .utils import is_provider
from .utils import ensure_is_provider from .utils import ensure_is_provider
from .utils import is_injection from .utils import is_injection
from .utils import ensure_is_injection from .utils import ensure_is_injection
from .utils import is_arg_injection
from .utils import is_kwarg_injection from .utils import is_kwarg_injection
from .utils import is_attribute_injection from .utils import is_attribute_injection
from .utils import is_method_injection from .utils import is_method_injection
@ -59,6 +61,7 @@ __all__ = (
# Injections # Injections
'Injection', 'Injection',
'Arg',
'KwArg', 'KwArg',
'Attribute', 'Attribute',
'Method', 'Method',
@ -69,6 +72,7 @@ __all__ = (
'ensure_is_provider', 'ensure_is_provider',
'is_injection', 'is_injection',
'ensure_is_injection', 'ensure_is_injection',
'is_arg_injection',
'is_kwarg_injection', 'is_kwarg_injection',
'is_attribute_injection', 'is_attribute_injection',
'is_method_injection', 'is_method_injection',

View File

@ -21,11 +21,10 @@ class Injection(object):
"""Base injection class.""" """Base injection class."""
__IS_INJECTION__ = True __IS_INJECTION__ = True
__slots__ = ('name', 'injectable', 'is_provider') __slots__ = ('injectable', 'is_provider')
def __init__(self, name, injectable): def __init__(self, injectable):
"""Initializer.""" """Initializer."""
self.name = name
self.injectable = injectable self.injectable = injectable
self.is_provider = is_provider(injectable) self.is_provider = is_provider(injectable)
@ -37,19 +36,36 @@ class Injection(object):
return self.injectable 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.""" """Keyword argument injection."""
__IS_KWARG_INJECTION__ = True __IS_KWARG_INJECTION__ = True
class Attribute(Injection): class Attribute(NamedInjection):
"""Attribute injection.""" """Attribute injection."""
__IS_ATTRIBUTE_INJECTION__ = True __IS_ATTRIBUTE_INJECTION__ = True
class Method(Injection): class Method(NamedInjection):
"""Method injection.""" """Method injection."""
__IS_METHOD_INJECTION__ = True __IS_METHOD_INJECTION__ = True

View File

@ -2,12 +2,16 @@
import six import six
from .injections import Arg
from .injections import KwArg from .injections import KwArg
from .utils import ensure_is_provider 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_kwarg_injection
from .utils import is_attribute_injection from .utils import is_attribute_injection
from .utils import is_method_injection from .utils import is_method_injection
from .utils import get_injectable_args
from .utils import get_injectable_kwargs from .utils import get_injectable_kwargs
from .utils import GLOBAL_LOCK from .utils import GLOBAL_LOCK
@ -104,33 +108,35 @@ class Factory(Provider):
Factory provider creates new instance of specified class on every call. 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.""" """Initializer."""
if not callable(provides): if not callable(provides):
raise Error('Factory provider expects to get callable, ' + raise Error('Factory provider expects to get callable, ' +
'got {0} instead'.format(str(provides))) 'got {0} instead'.format(str(provides)))
self.provides = 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 self.kwargs = tuple(injection
for injection in injections for injection in args
if is_kwarg_injection(injection)) if is_kwarg_injection(injection))
if kwargs: if kwargs:
self.kwargs += tuple(KwArg(name, value) self.kwargs += tuple(KwArg(name, value)
for name, value in six.iteritems(kwargs)) for name, value in six.iteritems(kwargs))
self.attributes = tuple(injection self.attributes = tuple(injection
for injection in injections for injection in args
if is_attribute_injection(injection)) if is_attribute_injection(injection))
self.methods = tuple(injection self.methods = tuple(injection
for injection in injections for injection in args
if is_method_injection(injection)) if is_method_injection(injection))
super(Factory, self).__init__() super(Factory, self).__init__()
def _provide(self, *args, **kwargs): def _provide(self, *args, **kwargs):
"""Return provided instance.""" """Return provided instance."""
instance = self.provides(*args, instance = self.provides(*get_injectable_args(args, self.args),
**get_injectable_kwargs(kwargs, **get_injectable_kwargs(kwargs, self.kwargs))
self.kwargs))
for attribute in self.attributes: for attribute in self.attributes:
setattr(instance, attribute.name, attribute.value) setattr(instance, attribute.name, attribute.value)
for method in self.methods: for method in self.methods:
@ -141,7 +147,7 @@ class Factory(Provider):
@property @property
def injections(self): def injections(self):
"""Return tuple of all injections.""" """Return tuple of all injections."""
return self.kwargs + self.attributes + self.methods return self.args + self.kwargs + self.attributes + self.methods
class Singleton(Provider): class Singleton(Provider):
@ -152,10 +158,10 @@ class Singleton(Provider):
__slots__ = ('instance', 'factory') __slots__ = ('instance', 'factory')
def __init__(self, provides, *injections, **kwargs): def __init__(self, provides, *args, **kwargs):
"""Initializer.""" """Initializer."""
self.instance = None self.instance = None
self.factory = Factory(provides, *injections, **kwargs) self.factory = Factory(provides, *args, **kwargs)
super(Singleton, self).__init__() super(Singleton, self).__init__()
def _provide(self, *args, **kwargs): def _provide(self, *args, **kwargs):

View File

@ -1,6 +1,7 @@
"""Utils module.""" """Utils module."""
import threading import threading
import itertools
import six import six
@ -41,6 +42,12 @@ def ensure_is_injection(instance):
return 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): def is_kwarg_injection(instance):
"""Check if instance is keyword argument injection instance.""" """Check if instance is keyword argument injection instance."""
return (not isinstance(instance, six.class_types) and return (not isinstance(instance, six.class_types) and
@ -82,9 +89,14 @@ def ensure_is_catalog_bundle(instance):
return instance return instance
def get_injectable_kwargs(kwargs, injections): def get_injectable_args(context_args, arg_injections):
"""Return dictionary of kwargs, patched with injections.""" """Return tuple of positional args, patched with injections."""
init_kwargs = dict(((injection.name, injection.value) return itertools.chain((arg.value for arg in arg_injections), context_args)
for injection in injections))
init_kwargs.update(kwargs)
return init_kwargs 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

View File

@ -9,18 +9,17 @@ class InjectionTests(unittest.TestCase):
def test_init(self): def test_init(self):
"""Test Injection creation and initialization.""" """Test Injection creation and initialization."""
injection = di.Injection('some_arg_name', 'some_value') injection = di.Injection('some_value')
self.assertEqual(injection.name, 'some_arg_name')
self.assertEqual(injection.injectable, 'some_value') self.assertEqual(injection.injectable, 'some_value')
def test_value_with_scalar_injectable(self): def test_value_with_scalar_injectable(self):
"""Test Injection value property with scalar value.""" """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') self.assertEqual(injection.value, 'some_value')
def test_value_with_provider_injectable(self): def test_value_with_provider_injectable(self):
"""Test Injection value property with provider.""" """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) self.assertIsInstance(injection.value, object)
def test_value_with_catalog_bundle_injectable(self): def test_value_with_catalog_bundle_injectable(self):
@ -35,6 +34,15 @@ class InjectionTests(unittest.TestCase):
self.assertIsInstance(injection.value, TestCatalog.Bundle) 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): class KwArgTests(unittest.TestCase):
"""Keyword arg injection test cases.""" """Keyword arg injection test cases."""

View File

@ -181,14 +181,12 @@ class FactoryTests(unittest.TestCase):
self.assertIsInstance(instance1, self.Example) self.assertIsInstance(instance1, self.Example)
self.assertIsInstance(instance2, self.Example) self.assertIsInstance(instance2, self.Example)
def test_call_with_init_args_simplified_syntax(self): def test_call_with_init_positional_args(self):
"""Test creation of new instances with init args injections. """Test creation of new instances with init positional args.
New simplified syntax. New simplified syntax.
""" """
provider = di.Factory(self.Example, provider = di.Factory(self.Example, 'i1', 'i2')
init_arg1='i1',
init_arg2='i2')
instance1 = provider() instance1 = provider()
instance2 = provider() instance2 = provider()
@ -203,10 +201,53 @@ class FactoryTests(unittest.TestCase):
self.assertIsInstance(instance1, self.Example) self.assertIsInstance(instance1, self.Example)
self.assertIsInstance(instance2, self.Example) self.assertIsInstance(instance2, self.Example)
def test_call_with_init_args_old_syntax(self): def test_call_with_init_keyword_args(self):
"""Test creation of new instances with init args injections.""" """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, provider = di.Factory(self.Example,
di.KwArg('init_arg1', 'i1'), di.Arg('i1'),
di.KwArg('init_arg2', 'i2')) di.KwArg('init_arg2', 'i2'))
instance1 = provider() instance1 = provider()
@ -300,7 +341,7 @@ class FactoryTests(unittest.TestCase):
def test_injections(self): def test_injections(self):
"""Test getting a full list of injections using Factory.injections.""" """Test getting a full list of injections using Factory.injections."""
provider = di.Factory(self.Example, provider = di.Factory(self.Example,
di.KwArg('init_arg1', 1), di.Arg(1),
di.KwArg('init_arg2', 2), di.KwArg('init_arg2', 2),
di.Attribute('attribute1', 3), di.Attribute('attribute1', 3),
di.Attribute('attribute2', 4), di.Attribute('attribute2', 4),

View File

@ -68,10 +68,11 @@ class IsInjectionTests(unittest.TestCase):
def test_with_instance(self): def test_with_instance(self):
"""Test with instance.""" """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): def test_with_subclass_instances(self):
"""Test with subclass instances.""" """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.KwArg('name', 'value')))
self.assertTrue(di.is_injection(di.Attribute('name', 'value'))) self.assertTrue(di.is_injection(di.Attribute('name', 'value')))
self.assertTrue(di.is_injection(di.Method('name', 'value'))) self.assertTrue(di.is_injection(di.Method('name', 'value')))
@ -94,7 +95,7 @@ class EnsureIsInjectionTests(unittest.TestCase):
def test_with_instance(self): def test_with_instance(self):
"""Test with instance.""" """Test with instance."""
injection = di.Injection('name', 'value') injection = di.Injection('value')
self.assertIs(di.ensure_is_injection(injection), injection) self.assertIs(di.ensure_is_injection(injection), injection)
def test_with_class(self): def test_with_class(self):
@ -110,6 +111,30 @@ class EnsureIsInjectionTests(unittest.TestCase):
self.assertRaises(di.Error, di.ensure_is_injection, object()) 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): class IsKwArgInjectionTests(unittest.TestCase):
"""`is_kwarg_injection()` test cases.""" """`is_kwarg_injection()` test cases."""