Add smarter providers representation

This commit is contained in:
Roman Mogilatov 2015-12-11 11:18:09 +02:00
parent 9ef7a961f6
commit 7b611820c5
3 changed files with 403 additions and 165 deletions

View File

@ -10,11 +10,13 @@ from .injections import _get_injectable_kwargs
from .utils import ensure_is_provider from .utils import ensure_is_provider
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 represent_provider
from .utils import GLOBAL_LOCK from .utils import GLOBAL_LOCK
from .errors import Error from .errors import Error
@six.python_2_unicode_compatible
class Provider(object): class Provider(object):
"""Base provider class. """Base provider class.
@ -150,7 +152,17 @@ class Provider(object):
""" """
return Delegate(self) return Delegate(self)
def __str__(self):
"""Return string representation of provider.
:rtype: str
"""
return represent_provider(provider=self, provides=None)
__repr__ = __str__
@six.python_2_unicode_compatible
class Delegate(Provider): class Delegate(Provider):
""":py:class:`Delegate` provider delegates another provider. """:py:class:`Delegate` provider delegates another provider.
@ -173,6 +185,11 @@ class Delegate(Provider):
:type delegated: :py:class:`Provider` :type delegated: :py:class:`Provider`
""" """
self.delegated = ensure_is_provider(delegated) self.delegated = ensure_is_provider(delegated)
"""Delegated provider.
:type: :py:class:`Provider`
"""
super(Delegate, self).__init__() super(Delegate, self).__init__()
def _provide(self, *args, **kwargs): def _provide(self, *args, **kwargs):
@ -188,7 +205,17 @@ class Delegate(Provider):
""" """
return self.delegated return self.delegated
def __str__(self):
"""Return string representation of provider.
:rtype: str
"""
return represent_provider(provider=self, provides=self.delegated)
__repr__ = __str__
@six.python_2_unicode_compatible
class Factory(Provider): class Factory(Provider):
""":py:class:`Factory` provider creates new instance on every call. """:py:class:`Factory` provider creates new instance on every call.
@ -301,7 +328,17 @@ class Factory(Provider):
return instance return instance
def __str__(self):
"""Return string representation of provider.
:rtype: str
"""
return represent_provider(provider=self, provides=self.provides)
__repr__ = __str__
@six.python_2_unicode_compatible
class Singleton(Provider): class Singleton(Provider):
""":py:class:`Singleton` provider returns same instance on every call. """:py:class:`Singleton` provider returns same instance on every call.
@ -425,7 +462,102 @@ class Singleton(Provider):
self.instance = self.factory(*args, **kwargs) self.instance = self.factory(*args, **kwargs)
return self.instance return self.instance
def __str__(self):
"""Return string representation of provider.
:rtype: str
"""
return represent_provider(provider=self, provides=self.provides)
__repr__ = __str__
@six.python_2_unicode_compatible
class Callable(Provider):
""":py:class:`Callable` provider calls wrapped callable on every call.
:py:class:`Callable` provider provides callable that is called on every
provider call with some predefined dependency injections.
:py:class:`Callable` syntax of passing injections is the same like
:py:class:`Factory` one:
.. code-block:: python
some_function = Callable(some_function, 'arg1', 'arg2', arg3=3, arg4=4)
result = some_function()
"""
__slots__ = ('callback', 'args', 'kwargs')
def __init__(self, callback, *args, **kwargs):
"""Initializer.
:param provides: Wrapped callable.
:type provides: callable
:param args: Tuple of injections.
:type args: tuple
:param kwargs: Dictionary of injections.
:type kwargs: dict
"""
if not callable(callback):
raise Error('Callable expected, got {0}'.format(str(callback)))
self.callback = callback
"""Provided callable.
:type: callable
"""
self.args = _parse_args_injections(args)
"""Tuple of positional argument injections.
:type: tuple[:py:class:`dependency_injector.injections.Arg`]
"""
self.kwargs = _parse_kwargs_injections(args, kwargs)
"""Tuple of keyword argument injections.
:type: tuple[:py:class:`dependency_injector.injections.KwArg`]
"""
super(Callable, self).__init__()
@property
def injections(self):
"""Read-only tuple of all injections.
:rtype: tuple[:py:class:`dependency_injector.injections.Injection`]
"""
return self.args + self.kwargs
def _provide(self, *args, **kwargs):
"""Return provided instance.
:param args: Tuple of context positional arguments.
:type args: tuple[object]
:param kwargs: Dictionary of context keyword arguments.
:type kwargs: dict[str, object]
:rtype: object
"""
return self.callback(*_get_injectable_args(args, self.args),
**_get_injectable_kwargs(kwargs, self.kwargs))
def __str__(self):
"""Return string representation of provider.
:rtype: str
"""
return represent_provider(provider=self, provides=self.callback)
__repr__ = __str__
@six.python_2_unicode_compatible
class ExternalDependency(Provider): class ExternalDependency(Provider):
""":py:class:`ExternalDependency` provider describes dependency interface. """:py:class:`ExternalDependency` provider describes dependency interface.
@ -490,11 +622,21 @@ class ExternalDependency(Provider):
""" """
return self.override(provider) return self.override(provider)
def __str__(self):
"""Return string representation of provider.
class StaticProvider(Provider): :rtype: str
""":py:class:`StaticProvider` returns provided instance "as is". """
return represent_provider(provider=self, provides=self.instance_of)
:py:class:`StaticProvider` is base implementation that provides exactly __repr__ = __str__
@six.python_2_unicode_compatible
class Static(Provider):
""":py:class:`Static` provider returns provided instance "as is".
:py:class:`Static` provider is base implementation that provides exactly
the same as it got on input. the same as it got on input.
""" """
@ -511,7 +653,7 @@ class StaticProvider(Provider):
:type: object :type: object
""" """
super(StaticProvider, self).__init__() super(Static, self).__init__()
def _provide(self, *args, **kwargs): def _provide(self, *args, **kwargs):
"""Return provided instance. """Return provided instance.
@ -526,8 +668,21 @@ class StaticProvider(Provider):
""" """
return self.provides return self.provides
def __str__(self):
"""Return string representation of provider.
class Class(StaticProvider): :rtype: str
"""
return represent_provider(provider=self, provides=self.provides)
__repr__ = __str__
StaticProvider = Static
# Backward compatibility for versions < 1.11.1
class Class(Static):
""":py:class:`Class` returns provided class "as is". """:py:class:`Class` returns provided class "as is".
.. code-block:: python .. code-block:: python
@ -537,7 +692,7 @@ class Class(StaticProvider):
""" """
class Object(StaticProvider): class Object(Static):
""":py:class:`Object` returns provided object "as is". """:py:class:`Object` returns provided object "as is".
.. code-block:: python .. code-block:: python
@ -547,7 +702,7 @@ class Object(StaticProvider):
""" """
class Function(StaticProvider): class Function(Static):
""":py:class:`Function` returns provided function "as is". """:py:class:`Function` returns provided function "as is".
.. code-block:: python .. code-block:: python
@ -557,7 +712,7 @@ class Function(StaticProvider):
""" """
class Value(StaticProvider): class Value(Static):
""":py:class:`Value` returns provided value "as is". """:py:class:`Value` returns provided value "as is".
.. code-block:: python .. code-block:: python
@ -567,80 +722,7 @@ class Value(StaticProvider):
""" """
class Callable(Provider): @six.python_2_unicode_compatible
""":py:class:`Callable` provider calls wrapped callable on every call.
:py:class:`Callable` provider provides callable that is called on every
provider call with some predefined dependency injections.
:py:class:`Callable` syntax of passing injections is the same like
:py:class:`Factory` one:
.. code-block:: python
some_function = Callable(some_function, 'arg1', 'arg2', arg3=3, arg4=4)
result = some_function()
"""
__slots__ = ('callback', 'args', 'kwargs')
def __init__(self, callback, *args, **kwargs):
"""Initializer.
:param callback: Wrapped callable.
:type callback: callable
:param args: Tuple of injections.
:type args: tuple
:param kwargs: Dictionary of injections.
:type kwargs: dict
"""
if not callable(callback):
raise Error('Callable expected, got {0}'.format(str(callback)))
self.callback = callback
"""Wrapped callable.
:type: callable
"""
self.args = _parse_args_injections(args)
"""Tuple of positional argument injections.
:type: tuple[:py:class:`dependency_injector.injections.Arg`]
"""
self.kwargs = _parse_kwargs_injections(args, kwargs)
"""Tuple of keyword argument injections.
:type: tuple[:py:class:`dependency_injector.injections.KwArg`]
"""
super(Callable, self).__init__()
@property
def injections(self):
"""Read-only tuple of all injections.
:rtype: tuple[:py:class:`dependency_injector.injections.Injection`]
"""
return self.args + self.kwargs
def _provide(self, *args, **kwargs):
"""Return provided instance.
:param args: Tuple of context positional arguments.
:type args: tuple[object]
:param kwargs: Dictionary of context keyword arguments.
:type kwargs: dict[str, object]
:rtype: object
"""
return self.callback(*_get_injectable_args(args, self.args),
**_get_injectable_kwargs(kwargs, self.kwargs))
class Config(Provider): class Config(Provider):
""":py:class:`Config` provider provide dict values. """:py:class:`Config` provider provide dict values.
@ -702,7 +784,17 @@ class Config(Provider):
""" """
self.value.update(value) self.value.update(value)
def __str__(self):
"""Return string representation of provider.
:rtype: str
"""
return represent_provider(provider=self, provides=self.value)
__repr__ = __str__
@six.python_2_unicode_compatible
class ChildConfig(Provider): class ChildConfig(Provider):
""":py:class:`ChildConfig` provider provides value from :py:class:`Config`. """:py:class:`ChildConfig` provider provides value from :py:class:`Config`.
@ -759,3 +851,13 @@ class ChildConfig(Provider):
:rtype: object :rtype: object
""" """
return self.root_config(self.parents) return self.root_config(self.parents)
def __str__(self):
"""Return string representation of provider.
:rtype: str
"""
return represent_provider(provider=self,
provides='.'.join(self.parents))
__repr__ = __str__

View File

@ -188,3 +188,22 @@ def ensure_is_catalog_bundle(instance):
raise Error('Expected catalog bundle instance, ' raise Error('Expected catalog bundle instance, '
'got {0}'.format(str(instance))) 'got {0}'.format(str(instance)))
return instance return instance
def represent_provider(provider, provides):
"""Return string representation of provider.
:param provider: Provider object
:type provider: :py:class:`dependency_injector.providers.Provider`
:param provides: Object that provider provides
:type provider: object
:return: String representation of provider
:rtype: str
"""
return '<{provider}({provides}) at {address}>'.format(
provider='.'.join((provider.__class__.__module__,
provider.__class__.__name__)),
provides=repr(provides) if provides is not None else '',
address=hex(id(provider)))

View File

@ -125,6 +125,12 @@ class ProviderTests(unittest.TestCase):
self.assertFalse(self.provider.is_overridden) self.assertFalse(self.provider.is_overridden)
self.assertIsNone(self.provider.last_overriding) self.assertIsNone(self.provider.last_overriding)
def test_repr(self):
"""Test representation of provider."""
self.assertEqual(repr(self.provider),
'<dependency_injector.providers.'
'Provider() at {0}>'.format(hex(id(self.provider))))
class DelegateTests(unittest.TestCase): class DelegateTests(unittest.TestCase):
"""Delegate test cases.""" """Delegate test cases."""
@ -150,6 +156,14 @@ class DelegateTests(unittest.TestCase):
self.assertIs(delegated1, self.delegated) self.assertIs(delegated1, self.delegated)
self.assertIs(delegated2, self.delegated) self.assertIs(delegated2, self.delegated)
def test_repr(self):
"""Test representation of provider."""
self.assertEqual(repr(self.delegate),
'<dependency_injector.providers.'
'Delegate({0}) at {1}>'.format(
repr(self.delegated),
hex(id(self.delegate))))
class FactoryTests(unittest.TestCase): class FactoryTests(unittest.TestCase):
"""Factory test cases.""" """Factory test cases."""
@ -356,6 +370,19 @@ class FactoryTests(unittest.TestCase):
injections.Method('method2', 6)) injections.Method('method2', 6))
self.assertEquals(len(provider.injections), 6) self.assertEquals(len(provider.injections), 6)
def test_repr(self):
"""Test representation of provider."""
provider = providers.Factory(Example,
injections.KwArg('init_arg1',
providers.Factory(dict)),
injections.KwArg('init_arg2',
providers.Factory(list)))
self.assertEqual(repr(provider),
'<dependency_injector.providers.'
'Factory({0}) at {1}>'.format(
repr(Example),
hex(id(provider))))
class SingletonTests(unittest.TestCase): class SingletonTests(unittest.TestCase):
"""Singleton test cases.""" """Singleton test cases."""
@ -606,89 +633,20 @@ class SingletonTests(unittest.TestCase):
self.assertIsNot(instance1, instance2) self.assertIsNot(instance1, instance2)
def test_repr(self):
class ExternalDependencyTests(unittest.TestCase): """Test representation of provider."""
"""ExternalDependency test cases.""" provider = providers.Singleton(Example,
injections.KwArg(
def setUp(self): 'init_arg1',
"""Set test cases environment up.""" providers.Factory(dict)),
self.provider = providers.ExternalDependency(instance_of=list) injections.KwArg(
'init_arg2',
def test_init_with_not_class(self): providers.Factory(list)))
"""Test creation with not a class.""" self.assertEqual(repr(provider),
self.assertRaises(errors.Error, providers.ExternalDependency, object()) '<dependency_injector.providers.'
'Singleton({0}) at {1}>'.format(
def test_is_provider(self): repr(Example),
"""Test `is_provider` check.""" hex(id(provider))))
self.assertTrue(utils.is_provider(self.provider))
def test_call_overridden(self):
"""Test call of overridden external dependency."""
self.provider.provided_by(providers.Factory(list))
self.assertIsInstance(self.provider(), list)
def test_call_overridden_but_not_instance_of(self):
"""Test call of overridden external dependency, but not instance of."""
self.provider.provided_by(providers.Factory(dict))
self.assertRaises(errors.Error, self.provider)
def test_call_not_overridden(self):
"""Test call of not satisfied external dependency."""
self.assertRaises(errors.Error, self.provider)
class StaticProvidersTests(unittest.TestCase):
"""Static providers test cases."""
def test_is_provider(self):
"""Test `is_provider` check."""
self.assertTrue(utils.is_provider(providers.Class(object)))
self.assertTrue(utils.is_provider(providers.Object(object())))
self.assertTrue(utils.is_provider(providers.Function(map)))
self.assertTrue(utils.is_provider(providers.Value(123)))
def test_call_class_provider(self):
"""Test Class provider call."""
self.assertIs(providers.Class(dict)(), dict)
def test_call_object_provider(self):
"""Test Object provider call."""
obj = object()
self.assertIs(providers.Object(obj)(), obj)
def test_call_function_provider(self):
"""Test Function provider call."""
self.assertIs(providers.Function(map)(), map)
def test_call_value_provider(self):
"""Test Value provider call."""
self.assertEqual(providers.Value(123)(), 123)
def test_call_overridden_class_provider(self):
"""Test overridden Class provider call."""
cls_provider = providers.Class(dict)
cls_provider.override(providers.Object(list))
self.assertIs(cls_provider(), list)
def test_call_overridden_object_provider(self):
"""Test overridden Object provider call."""
obj1 = object()
obj2 = object()
obj_provider = providers.Object(obj1)
obj_provider.override(providers.Object(obj2))
self.assertIs(obj_provider(), obj2)
def test_call_overridden_function_provider(self):
"""Test overridden Function provider call."""
function_provider = providers.Function(len)
function_provider.override(providers.Function(sum))
self.assertIs(function_provider(), sum)
def test_call_overridden_value_provider(self):
"""Test overridden Value provider call."""
value_provider = providers.Value(123)
value_provider.override(providers.Value(321))
self.assertEqual(value_provider(), 321)
class CallableTests(unittest.TestCase): class CallableTests(unittest.TestCase):
@ -780,6 +738,150 @@ class CallableTests(unittest.TestCase):
provider = providers.Callable(self.example, 1, 2, arg3=3, arg4=4) provider = providers.Callable(self.example, 1, 2, arg3=3, arg4=4)
self.assertEquals(len(provider.injections), 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),
'<dependency_injector.providers.'
'Callable({0}) at {1}>'.format(
repr(self.example),
hex(id(provider))))
class ExternalDependencyTests(unittest.TestCase):
"""ExternalDependency test cases."""
def setUp(self):
"""Set test cases environment up."""
self.provider = providers.ExternalDependency(instance_of=list)
def test_init_with_not_class(self):
"""Test creation with not a class."""
self.assertRaises(errors.Error, providers.ExternalDependency, object())
def test_is_provider(self):
"""Test `is_provider` check."""
self.assertTrue(utils.is_provider(self.provider))
def test_call_overridden(self):
"""Test call of overridden external dependency."""
self.provider.provided_by(providers.Factory(list))
self.assertIsInstance(self.provider(), list)
def test_call_overridden_but_not_instance_of(self):
"""Test call of overridden external dependency, but not instance of."""
self.provider.provided_by(providers.Factory(dict))
self.assertRaises(errors.Error, self.provider)
def test_call_not_overridden(self):
"""Test call of not satisfied external dependency."""
self.assertRaises(errors.Error, self.provider)
def test_repr(self):
"""Test representation of provider."""
self.assertEqual(repr(self.provider),
'<dependency_injector.providers.'
'ExternalDependency({0}) at {1}>'.format(
repr(list),
hex(id(self.provider))))
class StaticProvidersTests(unittest.TestCase):
"""Static providers test cases."""
def test_is_provider(self):
"""Test `is_provider` check."""
self.assertTrue(utils.is_provider(providers.Class(object)))
self.assertTrue(utils.is_provider(providers.Object(object())))
self.assertTrue(utils.is_provider(providers.Function(map)))
self.assertTrue(utils.is_provider(providers.Value(123)))
def test_call_class_provider(self):
"""Test Class provider call."""
self.assertIs(providers.Class(dict)(), dict)
def test_call_object_provider(self):
"""Test Object provider call."""
obj = object()
self.assertIs(providers.Object(obj)(), obj)
def test_call_function_provider(self):
"""Test Function provider call."""
self.assertIs(providers.Function(map)(), map)
def test_call_value_provider(self):
"""Test Value provider call."""
self.assertEqual(providers.Value(123)(), 123)
def test_call_overridden_class_provider(self):
"""Test overridden Class provider call."""
cls_provider = providers.Class(dict)
cls_provider.override(providers.Object(list))
self.assertIs(cls_provider(), list)
def test_call_overridden_object_provider(self):
"""Test overridden Object provider call."""
obj1 = object()
obj2 = object()
obj_provider = providers.Object(obj1)
obj_provider.override(providers.Object(obj2))
self.assertIs(obj_provider(), obj2)
def test_call_overridden_function_provider(self):
"""Test overridden Function provider call."""
function_provider = providers.Function(len)
function_provider.override(providers.Function(sum))
self.assertIs(function_provider(), sum)
def test_call_overridden_value_provider(self):
"""Test overridden Value provider call."""
value_provider = providers.Value(123)
value_provider.override(providers.Value(321))
self.assertEqual(value_provider(), 321)
def test_repr(self):
"""Test representation of provider."""
class_provider = providers.Class(object)
self.assertEqual(repr(class_provider),
'<dependency_injector.providers.'
'Class({0}) at {1}>'.format(
repr(object),
hex(id(class_provider))))
some_object = object()
object_provider = providers.Object(some_object)
self.assertEqual(repr(object_provider),
'<dependency_injector.providers.'
'Object({0}) at {1}>'.format(
repr(some_object),
hex(id(object_provider))))
function_provider = providers.Function(map)
self.assertEqual(repr(function_provider),
'<dependency_injector.providers.'
'Function({0}) at {1}>'.format(
repr(map),
hex(id(function_provider))))
value_provider = providers.Value(123)
self.assertEqual(repr(value_provider),
'<dependency_injector.providers.'
'Value({0}) at {1}>'.format(
repr(123),
hex(id(value_provider))))
class ConfigTests(unittest.TestCase): class ConfigTests(unittest.TestCase):
"""Config test cases.""" """Config test cases."""
@ -842,3 +944,18 @@ class ConfigTests(unittest.TestCase):
self.provider = providers.Config() self.provider = providers.Config()
category_setting = self.provider.category.setting category_setting = self.provider.category.setting
self.assertRaises(errors.Error, category_setting) self.assertRaises(errors.Error, category_setting)
def test_repr(self):
"""Test representation of provider."""
self.assertEqual(repr(self.provider),
'<dependency_injector.providers.'
'Config({0}) at {1}>'.format(
repr(self.initial_data),
hex(id(self.provider))))
category_setting = self.provider.category.setting
self.assertEqual(repr(category_setting),
'<dependency_injector.providers.'
'ChildConfig({0}) at {1}>'.format(
repr('.'.join(('category', 'setting'))),
hex(id(category_setting))))