diff --git a/dependency_injector/injections.py b/dependency_injector/injections.py index 8fa440cd..420dc931 100644 --- a/dependency_injector/injections.py +++ b/dependency_injector/injections.py @@ -20,6 +20,7 @@ else: # pragma: no cover _OBJECT_INIT = None +@six.python_2_unicode_compatible class Injection(object): """Base injection class. @@ -27,7 +28,7 @@ class Injection(object): """ __IS_INJECTION__ = True - __slots__ = ('injectable', 'is_provider') + __slots__ = ('injectable', 'injectable_is_provider') def __init__(self, injectable): """Initializer. @@ -43,7 +44,7 @@ class Injection(object): :type: object | :py:class:`dependency_injector.providers.Provider` """ - self.is_provider = is_provider(injectable) + self.injectable_is_provider = is_provider(injectable) """Flag that is set to ``True`` if injectable value is provider. :type: bool @@ -61,20 +62,22 @@ class Injection(object): :rtype: object """ - if self.is_provider: + if self.injectable_is_provider: return self.injectable() return self.injectable + def __str__(self): + """Return string representation of provider. -class _NamedInjection(Injection): - """Base class of named injections.""" + :rtype: str + """ + return '<{injection}({injectable}) at {address}>'.format( + injection='.'.join((self.__class__.__module__, + self.__class__.__name__)), + injectable=repr(self.injectable), + address=hex(id(self))) - __slots__ = ('name',) - - def __init__(self, name, injectable): - """Initializer.""" - self.name = name - super(_NamedInjection, self).__init__(injectable) + __repr__ = __str__ class Arg(Injection): @@ -83,21 +86,79 @@ class Arg(Injection): __IS_ARG_INJECTION__ = True +@six.python_2_unicode_compatible +class _NamedInjection(Injection): + """Base class of named injections.""" + + __slots__ = ('name',) + + def __init__(self, name, injectable): + """Initializer. + + :param name: Injection target's name. + :type name: str + + :param injectable: Injectable value, could be provider or any + other object. + :type injectable: object | + :py:class:`dependency_injector.providers.Provider` + """ + self.name = name + """Injection target's name (keyword argument, attribute, method). + + :type: str + """ + + super(_NamedInjection, self).__init__(injectable) + + def __str__(self): + """Return string representation of provider. + + :rtype: str + """ + return '<{injection}({name}, {injectable}) at {address}>'.format( + name=repr(self.name), + injection='.'.join((self.__class__.__module__, + self.__class__.__name__)), + injectable=repr(self.injectable), + address=hex(id(self))) + + __repr__ = __str__ + + class KwArg(_NamedInjection): """Keyword argument injection.""" + name = None + """Keyword argument's name. + + :type: str + """ + __IS_KWARG_INJECTION__ = True class Attribute(_NamedInjection): """Attribute injection.""" + name = None + """Attribute's name. + + :type: str + """ + __IS_ATTRIBUTE_INJECTION__ = True class Method(_NamedInjection): """Method injection.""" + name = None + """Method's name. + + :type: str + """ + __IS_METHOD_INJECTION__ = True diff --git a/dependency_injector/providers.py b/dependency_injector/providers.py index 5c25536b..f53ac14d 100644 --- a/dependency_injector/providers.py +++ b/dependency_injector/providers.py @@ -10,11 +10,13 @@ from .injections import _get_injectable_kwargs from .utils import ensure_is_provider from .utils import is_attribute_injection from .utils import is_method_injection +from .utils import represent_provider from .utils import GLOBAL_LOCK from .errors import Error +@six.python_2_unicode_compatible class Provider(object): """Base provider class. @@ -150,7 +152,17 @@ class Provider(object): """ 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): """:py:class:`Delegate` provider delegates another provider. @@ -173,6 +185,11 @@ class Delegate(Provider): :type delegated: :py:class:`Provider` """ self.delegated = ensure_is_provider(delegated) + """Delegated provider. + + :type: :py:class:`Provider` + """ + super(Delegate, self).__init__() def _provide(self, *args, **kwargs): @@ -188,7 +205,17 @@ class Delegate(Provider): """ 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): """:py:class:`Factory` provider creates new instance on every call. @@ -301,7 +328,17 @@ class Factory(Provider): 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): """:py:class:`Singleton` provider returns same instance on every call. @@ -425,7 +462,102 @@ class Singleton(Provider): self.instance = self.factory(*args, **kwargs) 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): """:py:class:`ExternalDependency` provider describes dependency interface. @@ -490,11 +622,21 @@ class ExternalDependency(Provider): """ return self.override(provider) + def __str__(self): + """Return string representation of provider. -class StaticProvider(Provider): - """:py:class:`StaticProvider` returns provided instance "as is". + :rtype: str + """ + 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. """ @@ -511,7 +653,7 @@ class StaticProvider(Provider): :type: object """ - super(StaticProvider, self).__init__() + super(Static, self).__init__() def _provide(self, *args, **kwargs): """Return provided instance. @@ -526,8 +668,21 @@ class StaticProvider(Provider): """ 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". .. code-block:: python @@ -537,7 +692,7 @@ class Class(StaticProvider): """ -class Object(StaticProvider): +class Object(Static): """:py:class:`Object` returns provided object "as is". .. code-block:: python @@ -547,7 +702,7 @@ class Object(StaticProvider): """ -class Function(StaticProvider): +class Function(Static): """:py:class:`Function` returns provided function "as is". .. code-block:: python @@ -557,7 +712,7 @@ class Function(StaticProvider): """ -class Value(StaticProvider): +class Value(Static): """:py:class:`Value` returns provided value "as is". .. code-block:: python @@ -567,80 +722,7 @@ class Value(StaticProvider): """ -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 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)) - - +@six.python_2_unicode_compatible class Config(Provider): """:py:class:`Config` provider provide dict values. @@ -702,7 +784,17 @@ class Config(Provider): """ 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): """:py:class:`ChildConfig` provider provides value from :py:class:`Config`. @@ -759,3 +851,13 @@ class ChildConfig(Provider): :rtype: object """ 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__ diff --git a/dependency_injector/utils.py b/dependency_injector/utils.py index 14caf4b1..01bb957d 100644 --- a/dependency_injector/utils.py +++ b/dependency_injector/utils.py @@ -188,3 +188,22 @@ def ensure_is_catalog_bundle(instance): raise Error('Expected catalog bundle instance, ' 'got {0}'.format(str(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))) diff --git a/docs/main/changelog.rst b/docs/main/changelog.rst index 7465f231..b4228e51 100644 --- a/docs/main/changelog.rst +++ b/docs/main/changelog.rst @@ -9,7 +9,8 @@ follows `Semantic versioning`_ Development version ------------------- -- No features. +- Improve representation of providers and injections. + 1.11.1 ------ diff --git a/tests/test_injections.py b/tests/test_injections.py index ea8b4418..ac68f541 100644 --- a/tests/test_injections.py +++ b/tests/test_injections.py @@ -37,6 +37,16 @@ class InjectionTests(unittest.TestCase): self.assertIsInstance(injection.value, TestCatalog.Bundle) + def test_repr(self): + """Test Injection representation.""" + provider = providers.Factory(object) + injection = injections.Injection(provider) + self.assertEqual( + repr(injection), + ''.format( + repr(provider), + hex(id(injection)))) + class ArgTests(unittest.TestCase): """Positional arg injection test cases.""" @@ -46,6 +56,16 @@ class ArgTests(unittest.TestCase): injection = injections.Arg('some_value') self.assertEqual(injection.injectable, 'some_value') + def test_repr(self): + """Test Arg representation.""" + provider = providers.Factory(object) + injection = injections.Arg(provider) + self.assertEqual( + repr(injection), + ''.format( + repr(provider), + hex(id(injection)))) + class KwArgTests(unittest.TestCase): """Keyword arg injection test cases.""" @@ -56,6 +76,17 @@ class KwArgTests(unittest.TestCase): self.assertEqual(injection.name, 'some_arg_name') self.assertEqual(injection.injectable, 'some_value') + def test_repr(self): + """Test KwArg representation.""" + provider = providers.Factory(object) + injection = injections.KwArg('name', provider) + self.assertEqual( + repr(injection), + ''.format( + repr('name'), + repr(provider), + hex(id(injection)))) + class AttributeTests(unittest.TestCase): """Attribute injection test cases.""" @@ -66,6 +97,18 @@ class AttributeTests(unittest.TestCase): self.assertEqual(injection.name, 'some_arg_name') self.assertEqual(injection.injectable, 'some_value') + def test_repr(self): + """Test Attribute representation.""" + provider = providers.Factory(object) + injection = injections.Attribute('name', provider) + self.assertEqual( + repr(injection), + ''.format( + repr('name'), + repr(provider), + hex(id(injection)))) + class MethodTests(unittest.TestCase): """Method injection test cases.""" @@ -76,6 +119,17 @@ class MethodTests(unittest.TestCase): self.assertEqual(injection.name, 'some_arg_name') self.assertEqual(injection.injectable, 'some_value') + def test_repr(self): + """Test Method representation.""" + provider = providers.Factory(object) + injection = injections.Method('name', provider) + self.assertEqual( + repr(injection), + ''.format( + repr('name'), + repr(provider), + hex(id(injection)))) + class InjectTests(unittest.TestCase): """Inject decorator test cases.""" diff --git a/tests/test_providers.py b/tests/test_providers.py index e91eb5f0..98cf9f20 100644 --- a/tests/test_providers.py +++ b/tests/test_providers.py @@ -125,6 +125,12 @@ class ProviderTests(unittest.TestCase): self.assertFalse(self.provider.is_overridden) self.assertIsNone(self.provider.last_overriding) + def test_repr(self): + """Test representation of provider.""" + self.assertEqual(repr(self.provider), + ''.format(hex(id(self.provider)))) + class DelegateTests(unittest.TestCase): """Delegate test cases.""" @@ -150,6 +156,14 @@ class DelegateTests(unittest.TestCase): self.assertIs(delegated1, self.delegated) self.assertIs(delegated2, self.delegated) + def test_repr(self): + """Test representation of provider.""" + self.assertEqual(repr(self.delegate), + ''.format( + repr(self.delegated), + hex(id(self.delegate)))) + class FactoryTests(unittest.TestCase): """Factory test cases.""" @@ -356,6 +370,19 @@ class FactoryTests(unittest.TestCase): injections.Method('method2', 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), + ''.format( + repr(Example), + hex(id(provider)))) + class SingletonTests(unittest.TestCase): """Singleton test cases.""" @@ -606,89 +633,20 @@ class SingletonTests(unittest.TestCase): self.assertIsNot(instance1, instance2) - -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) - - -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.""" + provider = providers.Singleton(Example, + injections.KwArg( + 'init_arg1', + providers.Factory(dict)), + injections.KwArg( + 'init_arg2', + providers.Factory(list))) + self.assertEqual(repr(provider), + ''.format( + repr(Example), + hex(id(provider)))) class CallableTests(unittest.TestCase): @@ -780,6 +738,150 @@ class CallableTests(unittest.TestCase): 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 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), + ''.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), + ''.format( + repr(object), + hex(id(class_provider)))) + + some_object = object() + object_provider = providers.Object(some_object) + self.assertEqual(repr(object_provider), + ''.format( + repr(some_object), + hex(id(object_provider)))) + + function_provider = providers.Function(map) + self.assertEqual(repr(function_provider), + ''.format( + repr(map), + hex(id(function_provider)))) + + value_provider = providers.Value(123) + self.assertEqual(repr(value_provider), + ''.format( + repr(123), + hex(id(value_provider)))) + class ConfigTests(unittest.TestCase): """Config test cases.""" @@ -842,3 +944,18 @@ class ConfigTests(unittest.TestCase): self.provider = providers.Config() category_setting = self.provider.category.setting self.assertRaises(errors.Error, category_setting) + + def test_repr(self): + """Test representation of provider.""" + self.assertEqual(repr(self.provider), + ''.format( + repr(self.initial_data), + hex(id(self.provider)))) + + category_setting = self.provider.category.setting + self.assertEqual(repr(category_setting), + ''.format( + repr('.'.join(('category', 'setting'))), + hex(id(category_setting))))