Add support of positional args for Factory & Singleton providers
This commit is contained in:
parent
2a5ed703bf
commit
824ed4f3e9
|
@ -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',
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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."""
|
||||
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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."""
|
||||
|
||||
|
|
Loading…
Reference in New Issue