diff --git a/dependency_injector/__init__.py b/dependency_injector/__init__.py index 56b6ac35..927246f5 100644 --- a/dependency_injector/__init__.py +++ b/dependency_injector/__init__.py @@ -8,15 +8,18 @@ from .catalogs import override from .providers import Provider from .providers import Delegate +from .providers import Callable +from .providers import DelegatedCallable from .providers import Factory +from .providers import DelegatedFactory from .providers import Singleton +from .providers import DelegatedSingleton from .providers import ExternalDependency from .providers import StaticProvider from .providers import Class from .providers import Object from .providers import Function from .providers import Value -from .providers import Callable from .providers import Config from .injections import Injection @@ -28,6 +31,7 @@ from .injections import inject from .utils import is_provider from .utils import ensure_is_provider +from .utils import is_delegated_provider from .utils import is_injection from .utils import ensure_is_injection from .utils import is_arg_injection @@ -65,15 +69,18 @@ __all__ = ( # Providers 'Provider', 'Delegate', + 'Callable', + 'DelegatedCallable', 'Factory', + 'DelegatedFactory', 'Singleton', + 'DelegatedSingleton', 'ExternalDependency', 'StaticProvider', 'Class', 'Object', 'Function', 'Value', - 'Callable', 'Config', # Injections @@ -87,6 +94,7 @@ __all__ = ( # Utils 'is_provider', 'ensure_is_provider', + 'is_delegated_provider', 'is_injection', 'ensure_is_injection', 'is_arg_injection', diff --git a/dependency_injector/injections.py b/dependency_injector/injections.py index f2309479..3d71af48 100644 --- a/dependency_injector/injections.py +++ b/dependency_injector/injections.py @@ -6,6 +6,7 @@ import itertools import six from .utils import is_provider +from .utils import is_delegated_provider from .utils import is_injection from .utils import is_arg_injection from .utils import is_kwarg_injection @@ -32,15 +33,17 @@ class Injection(object): :type: object | :py:class:`dependency_injector.providers.Provider` - .. py:attribute:: injectable_is_provider + .. py:attribute:: call_injectable - Flag that is set to ``True`` if injectable value is provider. + Flag that is set to ``True`` if it is needed to call injectable. + + Injectable needs to be called if it is not delegated provider. :type: bool """ __IS_INJECTION__ = True - __slots__ = ('injectable', 'injectable_is_provider') + __slots__ = ('injectable', 'call_injectable') def __init__(self, injectable): """Initializer. @@ -51,20 +54,21 @@ class Injection(object): :py:class:`dependency_injector.providers.Provider` """ self.injectable = injectable - self.injectable_is_provider = is_provider(injectable) + self.call_injectable = (is_provider(injectable) and + not is_delegated_provider(injectable)) super(Injection, self).__init__() @property def value(self): """Read-only property that represents injectable value. - Injectable values are provided "as is", except of providers - (subclasses of :py:class:`dependency_injector.providers.Provider`). - Providers will be called every time, when injection needs to be done. + Injectable values and delegated providers are provided "as is". + Other providers will be called every time, when injection needs to + be done. :rtype: object """ - if self.injectable_is_provider: + if self.call_injectable: return self.injectable() return self.injectable diff --git a/dependency_injector/providers.py b/dependency_injector/providers.py index 73440d5d..46a5f14e 100644 --- a/dependency_injector/providers.py +++ b/dependency_injector/providers.py @@ -319,6 +319,34 @@ class Callable(Provider): __repr__ = __str__ +class DelegatedCallable(Callable): + """:py:class:`DelegatedCallable` is a delegated :py:class:`Callable`. + + :py:class:`DelegatedCallable` is a :py:class:`Callable`, that is injected + "as is". + + .. 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`] + """ + + __IS_DELEGATED__ = True + + class Factory(Callable): """:py:class:`Factory` provider creates new instance on every call. @@ -448,6 +476,54 @@ class Factory(Callable): return instance +class DelegatedFactory(Factory): + """:py:class:`DelegatedFactory` is a delegated :py:class:`Factory`. + + :py:class:`DelegatedFactory` is a :py:class:`Factory`, that is injected + "as is". + + .. 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`] + """ + + __IS_DELEGATED__ = True + + class Singleton(Factory): """:py:class:`Singleton` provider returns same instance on every call. @@ -556,6 +632,60 @@ class Singleton(Factory): return self.instance +class DelegatedSingleton(Singleton): + """:py:class:`DelegatedSingleton` is a delegated :py:class:`Singleton`. + + :py:class:`DelegatedSingleton` is a :py:class:`Singleton`, that is injected + "as is". + + .. 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`] + """ + + __IS_DELEGATED__ = True + + @six.python_2_unicode_compatible class ExternalDependency(Provider): """:py:class:`ExternalDependency` provider describes dependency interface. diff --git a/dependency_injector/utils.py b/dependency_injector/utils.py index 01bb957d..569fcf71 100644 --- a/dependency_injector/utils.py +++ b/dependency_injector/utils.py @@ -44,6 +44,19 @@ def ensure_is_provider(instance): return instance +def is_delegated_provider(instance): + """Check if instance is delegated provider instance. + + :param instance: Instance to be checked. + :type instance: object + + :rtype: bool + """ + return (is_provider(instance) and + hasattr(instance, '__IS_DELEGATED__') and + getattr(instance, '__IS_DELEGATED__') is True) + + def is_injection(instance): """Check if instance is injection instance. diff --git a/docs/main/changelog.rst b/docs/main/changelog.rst index 680eb421..bd2fb032 100644 --- a/docs/main/changelog.rst +++ b/docs/main/changelog.rst @@ -9,7 +9,9 @@ follows `Semantic versioning`_ Development version ------------------- -- No features. +- Add ``DelegatedCallable`` provider. +- Add ``DelegatedFactory`` provider. +- Add ``DelegatedSingleton`` provider. 1.12.0 ------ diff --git a/docs/providers/callable.rst b/docs/providers/callable.rst index 7bc6227d..0a813640 100644 --- a/docs/providers/callable.rst +++ b/docs/providers/callable.rst @@ -16,12 +16,17 @@ arguments that are used as decorated callable injections. Every time, when would be passed as an callable arguments. 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 :py:class:`Provider`). Providers -will be called every time, when injection needs to be done. For example, -if injectable value of injection is a :py:class:`Factory`, it will provide -new one instance (as a result of its call) every time, when injection needs -to be done. +object with several more things: + ++ All providers (instances of :py:class:`Provider`) are called every time + when injection needs to be done. ++ Providers could be injected "as is" (delegated), if it is defined obviously. + Check out `Callable providers delegation`_. ++ All other injectable values are provided *"as is"* + +For example, if injectable value of injection is a :py:class:`Factory`, it +will provide new one instance (as a result of its call) every time, when +injection needs to be done. :py:class:`Callable` behaviour with context positional and keyword arguments is very like a standard Python ``functools.partial``: @@ -47,6 +52,8 @@ injections: .. literalinclude:: ../../examples/providers/callable_kwargs.py :language: python +.. _callable_delegation: + Callable providers delegation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -62,3 +69,12 @@ Example: .. literalinclude:: ../../examples/providers/callable_delegation.py :language: python + +Alternative way of doing :py:class:`Callable` delegation is an usage of +:py:class:`DelegatedCallable`. :py:class:`DelegatedCallable` is a +:py:class:`Callable` that is always injected "as is". + +Example: + +.. literalinclude:: ../../examples/providers/delegated_callable.py + :language: python diff --git a/docs/providers/factory.rst b/docs/providers/factory.rst index b5e250d1..818cbf1b 100644 --- a/docs/providers/factory.rst +++ b/docs/providers/factory.rst @@ -24,12 +24,17 @@ that are used as ``__init__()`` injections. Every time, when argument injections would be passed as an instance's arguments. 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 :py:class:`Provider`). Providers -will be called every time, when injection needs to be done. For example, -if injectable value of injection is a :py:class:`Factory`, it will provide -new one instance (as a result of its call) every time, when injection needs -to be done. +object with several more things: + ++ All providers (instances of :py:class:`Provider`) are called every time + when injection needs to be done. ++ Providers could be injected "as is" (delegated), if it is defined obviously. + Check out `Factory providers delegation`_. ++ All other injectable values are provided *"as is"* + +For example, if injectable value of injection is a :py:class:`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 :py:class:`Factory` of particular class with ``__init__()`` argument @@ -175,6 +180,15 @@ Example: .. literalinclude:: ../../examples/providers/factory_delegation.py :language: python +Alternative way of doing :py:class:`Factory` delegation is an usage of +:py:class:`DelegatedFactory`. :py:class:`DelegatedFactory` is a +:py:class:`Factory` that is always injected "as is". + +Example: + +.. literalinclude:: ../../examples/providers/delegated_factory.py + :language: python + Factory providers specialization ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/providers/index.rst b/docs/providers/index.rst index 1f6a5514..0d57268a 100644 --- a/docs/providers/index.rst +++ b/docs/providers/index.rst @@ -24,8 +24,8 @@ Providers module API docs - :py:mod:`dependency_injector.providers` factory singleton - static callable external_dependency + static overriding custom diff --git a/docs/providers/singleton.rst b/docs/providers/singleton.rst index b005a379..db24ccfd 100644 --- a/docs/providers/singleton.rst +++ b/docs/providers/singleton.rst @@ -36,9 +36,10 @@ provider. during the first call). Be aware that such behaviour was made with opened eyes and is not a bug. - By the way, in such case, :py:class:`Delegate` provider can be useful. It - makes possible to inject providers *as is*. Please check out full example - in *Providers delegation* section. + By the way, in such case, :py:class:`Delegate` or + :py:class:`DelegatedSingleton` provider can be useful + . It makes possible to inject providers *as is*. Please check out + `Singleton providers delegation`_ section. Singleton providers resetting ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -70,6 +71,15 @@ Example: .. literalinclude:: ../../examples/providers/singleton_delegation.py :language: python +Alternative way of doing :py:class:`Singleton` delegation is an usage of +:py:class:`DelegatedSingleton`. :py:class:`DelegatedSingleton` is a +:py:class:`Singleton` that is always injected "as is". + +Example: + +.. literalinclude:: ../../examples/providers/delegated_singleton.py + :language: python + Singleton providers specialization ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/examples/providers/delegated_callable.py b/examples/providers/delegated_callable.py new file mode 100644 index 00000000..c4ee9546 --- /dev/null +++ b/examples/providers/delegated_callable.py @@ -0,0 +1,22 @@ +"""`DelegatedCallable` providers example.""" + +from dependency_injector import providers + + +def command1(config): + """Some example command.""" + return config['some_value'] * 5 + + +def command2(command1): + """Some example command.""" + return command1() / 2 + +# Creating callable providers for commands: +command1_provider = providers.DelegatedCallable(command1, + config={'some_value': 4}) +command2_provider = providers.DelegatedCallable(command2, + command1=command1_provider) + +# Making some asserts: +assert command2_provider() == 10 diff --git a/examples/providers/delegated_factory.py b/examples/providers/delegated_factory.py new file mode 100644 index 00000000..03f6801f --- /dev/null +++ b/examples/providers/delegated_factory.py @@ -0,0 +1,46 @@ +"""`DelegatedFactory` providers example.""" + +from dependency_injector import providers + + +class User(object): + """Example class User.""" + + def __init__(self, photos_factory): + """Initializer. + + :param photos_factory: providers.Factory -> Photo + """ + self.photos_factory = photos_factory + self._main_photo = None + super(User, self).__init__() + + @property + def main_photo(self): + """Return user's main photo.""" + if not self._main_photo: + self._main_photo = self.photos_factory() + return self._main_photo + + +class Photo(object): + """Example class Photo.""" + +# User and Photo factories: +photos_factory = providers.DelegatedFactory(Photo) +users_factory = providers.DelegatedFactory(User, + photos_factory=photos_factory) + +# Creating several User objects: +user1 = users_factory() +user2 = users_factory() + +# 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/delegated_singleton.py b/examples/providers/delegated_singleton.py new file mode 100644 index 00000000..5e7a5d74 --- /dev/null +++ b/examples/providers/delegated_singleton.py @@ -0,0 +1,18 @@ +"""`DelegatedSingleton` providers example.""" + +from dependency_injector import providers + + +# Some delegated singleton provider: +singleton_provider = providers.DelegatedSingleton(object) +registry = providers.DelegatedSingleton(dict, + object1=singleton_provider, + object2=singleton_provider) + +# Getting several references to singleton object: +registry = registry() +singleton_object1 = registry['object1']() +singleton_object2 = registry['object2']() + +# Making some asserts: +assert singleton_object1 is singleton_object2 diff --git a/tests/test_providers.py b/tests/test_providers.py index e29c2a3c..82761606 100644 --- a/tests/test_providers.py +++ b/tests/test_providers.py @@ -165,6 +165,136 @@ class DelegateTests(unittest.TestCase): hex(id(self.delegate)))) +class CallableTests(unittest.TestCase): + """Callable test cases.""" + + def example(self, arg1, arg2, arg3, arg4): + """Example callback.""" + return arg1, arg2, arg3, arg4 + + def test_init_with_callable(self): + """Test creation of provider with a callable.""" + self.assertTrue(providers.Callable(self.example)) + + def test_init_with_not_callable(self): + """Test creation of provider with not a callable.""" + self.assertRaises(errors.Error, providers.Callable, 123) + + def test_call(self): + """Test call.""" + provider = providers.Callable(lambda: True) + self.assertTrue(provider()) + + def test_call_with_positional_args(self): + """Test call with positional args. + + New simplified syntax. + """ + provider = providers.Callable(self.example, 1, 2, 3, 4) + self.assertTupleEqual(provider(), (1, 2, 3, 4)) + + def test_call_with_keyword_args(self): + """Test call with keyword args. + + New simplified syntax. + """ + provider = providers.Callable(self.example, + arg1=1, + arg2=2, + arg3=3, + arg4=4) + self.assertTupleEqual(provider(), (1, 2, 3, 4)) + + def test_call_with_positional_and_keyword_args(self): + """Test call with positional and keyword args. + + Simplified syntax of positional and keyword arg injections. + """ + provider = providers.Callable(self.example, 1, 2, arg3=3, arg4=4) + self.assertTupleEqual(provider(), (1, 2, 3, 4)) + + def test_call_with_positional_and_keyword_args_extended_syntax(self): + """Test call with positional and keyword args. + + Extended syntax of positional and keyword arg injections. + """ + provider = providers.Callable(self.example, + injections.Arg(1), + injections.Arg(2), + injections.KwArg('arg3', 3), + injections.KwArg('arg4', 4)) + self.assertTupleEqual(provider(), (1, 2, 3, 4)) + + def test_call_with_context_args(self): + """Test call with context args.""" + provider = providers.Callable(self.example, 1, 2) + self.assertTupleEqual(provider(3, 4), (1, 2, 3, 4)) + + def test_call_with_context_kwargs(self): + """Test call with context kwargs.""" + provider = providers.Callable(self.example, + injections.KwArg('arg1', 1)) + self.assertTupleEqual(provider(arg2=2, arg3=3, arg4=4), (1, 2, 3, 4)) + + def test_call_with_context_args_and_kwargs(self): + """Test call with context args and kwargs.""" + provider = providers.Callable(self.example, 1) + self.assertTupleEqual(provider(2, arg3=3, arg4=4), (1, 2, 3, 4)) + + def test_call_overridden(self): + """Test creation of new instances on overridden provider.""" + provider = providers.Callable(self.example) + provider.override(providers.Value((4, 3, 2, 1))) + provider.override(providers.Value((1, 2, 3, 4))) + + self.assertTupleEqual(provider(), (1, 2, 3, 4)) + + def test_injections(self): + """Test getting a full list of injections using injections property.""" + provider = providers.Callable(self.example, 1, 2, arg3=3, arg4=4) + self.assertEquals(len(provider.injections), 4) + + def test_repr(self): + """Test representation of provider.""" + provider = providers.Callable(self.example, + injections.KwArg( + 'arg1', + providers.Factory(dict)), + injections.KwArg( + 'arg2', + providers.Factory(list)), + injections.KwArg( + 'arg3', + providers.Factory(set)), + injections.KwArg( + 'arg4', + providers.Factory(tuple))) + self.assertEqual(repr(provider), + ''.format( + repr(self.example), + hex(id(provider)))) + + +class DelegatedCallableTests(unittest.TestCase): + """DelegatedCallable test cases.""" + + def test_inheritance(self): + """Test inheritance.""" + self.assertIsInstance(providers.DelegatedCallable(len), + providers.Callable) + + def test_is_provider(self): + """Test is_provider.""" + self.assertTrue(utils.is_provider(providers.DelegatedCallable(len))) + + def test_is_delegated_provider(self): + """Test is_delegated_provider.""" + self.assertTrue(utils.is_delegated_provider( + providers.DelegatedCallable(len))) + self.assertFalse(utils.is_delegated_provider(providers.Callable(len))) + + class FactoryTests(unittest.TestCase): """Factory test cases.""" @@ -419,6 +549,26 @@ class FactoryTests(unittest.TestCase): hex(id(provider)))) +class DelegatedFactoryTests(unittest.TestCase): + """DelegatedFactory test cases.""" + + def test_inheritance(self): + """Test inheritance.""" + self.assertIsInstance(providers.DelegatedFactory(object), + providers.Factory) + + def test_is_provider(self): + """Test is_provider.""" + self.assertTrue(utils.is_provider(providers.DelegatedFactory(object))) + + def test_is_delegated_provider(self): + """Test is_delegated_provider.""" + self.assertTrue(utils.is_delegated_provider( + providers.DelegatedFactory(object))) + self.assertFalse(utils.is_delegated_provider( + providers.Factory(object))) + + class SingletonTests(unittest.TestCase): """Singleton test cases.""" @@ -719,115 +869,25 @@ class SingletonTests(unittest.TestCase): hex(id(provider)))) -class CallableTests(unittest.TestCase): - """Callable test cases.""" +class DelegatedSingletonTests(unittest.TestCase): + """DelegatedSingleton test cases.""" - def example(self, arg1, arg2, arg3, arg4): - """Example callback.""" - return arg1, arg2, arg3, arg4 + def test_inheritance(self): + """Test inheritance.""" + self.assertIsInstance(providers.DelegatedSingleton(object), + providers.Singleton) - def test_init_with_callable(self): - """Test creation of provider with a callable.""" - self.assertTrue(providers.Callable(self.example)) + def test_is_provider(self): + """Test is_provider.""" + self.assertTrue(utils.is_provider( + providers.DelegatedSingleton(object))) - def test_init_with_not_callable(self): - """Test creation of provider with not a callable.""" - self.assertRaises(errors.Error, providers.Callable, 123) - - def test_call(self): - """Test call.""" - provider = providers.Callable(lambda: True) - self.assertTrue(provider()) - - def test_call_with_positional_args(self): - """Test call with positional args. - - New simplified syntax. - """ - provider = providers.Callable(self.example, 1, 2, 3, 4) - self.assertTupleEqual(provider(), (1, 2, 3, 4)) - - def test_call_with_keyword_args(self): - """Test call with keyword args. - - New simplified syntax. - """ - provider = providers.Callable(self.example, - arg1=1, - arg2=2, - arg3=3, - arg4=4) - self.assertTupleEqual(provider(), (1, 2, 3, 4)) - - def test_call_with_positional_and_keyword_args(self): - """Test call with positional and keyword args. - - Simplified syntax of positional and keyword arg injections. - """ - provider = providers.Callable(self.example, 1, 2, arg3=3, arg4=4) - self.assertTupleEqual(provider(), (1, 2, 3, 4)) - - def test_call_with_positional_and_keyword_args_extended_syntax(self): - """Test call with positional and keyword args. - - Extended syntax of positional and keyword arg injections. - """ - provider = providers.Callable(self.example, - injections.Arg(1), - injections.Arg(2), - injections.KwArg('arg3', 3), - injections.KwArg('arg4', 4)) - self.assertTupleEqual(provider(), (1, 2, 3, 4)) - - def test_call_with_context_args(self): - """Test call with context args.""" - provider = providers.Callable(self.example, 1, 2) - self.assertTupleEqual(provider(3, 4), (1, 2, 3, 4)) - - def test_call_with_context_kwargs(self): - """Test call with context kwargs.""" - provider = providers.Callable(self.example, - injections.KwArg('arg1', 1)) - self.assertTupleEqual(provider(arg2=2, arg3=3, arg4=4), (1, 2, 3, 4)) - - def test_call_with_context_args_and_kwargs(self): - """Test call with context args and kwargs.""" - provider = providers.Callable(self.example, 1) - self.assertTupleEqual(provider(2, arg3=3, arg4=4), (1, 2, 3, 4)) - - def test_call_overridden(self): - """Test creation of new instances on overridden provider.""" - provider = providers.Callable(self.example) - provider.override(providers.Value((4, 3, 2, 1))) - provider.override(providers.Value((1, 2, 3, 4))) - - self.assertTupleEqual(provider(), (1, 2, 3, 4)) - - def test_injections(self): - """Test getting a full list of injections using injections property.""" - provider = providers.Callable(self.example, 1, 2, arg3=3, arg4=4) - self.assertEquals(len(provider.injections), 4) - - def test_repr(self): - """Test representation of provider.""" - provider = providers.Callable(self.example, - injections.KwArg( - 'arg1', - providers.Factory(dict)), - injections.KwArg( - 'arg2', - providers.Factory(list)), - injections.KwArg( - 'arg3', - providers.Factory(set)), - injections.KwArg( - 'arg4', - providers.Factory(tuple))) - self.assertEqual(repr(provider), - ''.format( - repr(self.example), - hex(id(provider)))) + def test_is_delegated_provider(self): + """Test is_delegated_provider.""" + self.assertTrue(utils.is_delegated_provider( + providers.DelegatedSingleton(object))) + self.assertFalse(utils.is_delegated_provider( + providers.Singleton(object))) class ExternalDependencyTests(unittest.TestCase):