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 .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',

View File

@ -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

View File

@ -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):

View File

@ -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

View File

@ -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."""

View File

@ -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),

View File

@ -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."""