From f3668ed815df052ccee55d1a56e15c213dd06c3d Mon Sep 17 00:00:00 2001 From: Roman Mogilatov Date: Fri, 11 Dec 2015 22:46:49 +0200 Subject: [PATCH] Make some refactorings for providers --- dependency_injector/providers.py | 485 ++++++++++++++----------------- docs/api/providers.rst | 1 + docs/main/changelog.rst | 3 + tests/test_providers.py | 65 ++++- 4 files changed, 278 insertions(+), 276 deletions(-) diff --git a/dependency_injector/providers.py b/dependency_injector/providers.py index c767e42b..9c2c6051 100644 --- a/dependency_injector/providers.py +++ b/dependency_injector/providers.py @@ -56,6 +56,12 @@ class Provider(object): assert provider is delegated All providers should extend this class. + + .. py:attribute:: overridden_by + + Tuple of overriding providers, if any. + + :type: tuple[:py:class:`Provider`] | None """ __IS_PROVIDER__ = True @@ -64,11 +70,6 @@ class Provider(object): def __init__(self): """Initializer.""" self.overridden_by = None - """Tuple of overriding providers, if any. - - :type: tuple[:py:class:`Provider`] | None - """ - super(Provider, self).__init__() def __call__(self, *args, **kwargs): @@ -174,6 +175,12 @@ class Delegate(Provider): delegated = delegate() assert provider is delegated + + .. py:attribute:: delegated + + Delegated provider. + + :type: :py:class:`Provider` """ __slots__ = ('delegated',) @@ -185,11 +192,6 @@ 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): @@ -216,7 +218,108 @@ class Delegate(Provider): @six.python_2_unicode_compatible -class Factory(Provider): +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 + + # simplified syntax for passing positional and keyword argument + # injections: + some_function = Callable(some_function, 'arg1', 'arg2', arg3=3, arg4=4) + + # extended (full) syntax for passing positional and keyword argument + # injections: + some_function = Callable(some_function, + injections.Arg(1), + injections.Arg(2), + injections.KwArg('some_arg', 3), + injections.KwArg('other_arg', 4)) + + .. 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`] + """ + + __slots__ = ('provides', 'args', 'kwargs') + + def __init__(self, provides, *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(provides): + raise Error('Provider{0} expected to get callable, ' + 'got {0}'.format('.'.join((self.__class__.__module__, + self.__class__.__name__)), + provides)) + + self.provides = provides + + self.args = _parse_args_injections(args) + self.kwargs = _parse_kwargs_injections(args, kwargs) + + 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.provides(*_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.provides) + + __repr__ = __str__ + + +class Factory(Callable): """:py:class:`Factory` provider creates new instance on every call. :py:class:`Factory` supports different syntaxes of passing injections: @@ -244,19 +347,49 @@ class Factory(Provider): some_arg1=1, some_arg2=2) some_object = factory() + + .. 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`] """ provided_type = None - """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 - """ - - __slots__ = ('provides', 'args', 'kwargs', 'attributes', 'methods') + __slots__ = ('attributes', 'methods') def __init__(self, provides, *args, **kwargs): """Initializer. @@ -271,41 +404,22 @@ class Factory(Provider): :param kwargs: Dictionary of injections. :type kwargs: dict """ - self.provides = self._ensure_provides_type(provides) - """Class or other callable that provides object for creation. - - :type: 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`] - """ + if (self.__class__.provided_type and + not issubclass(provides, self.__class__.provided_type)): + raise Error('{0} can provide only {1} instances'.format( + '.'.join((self.__class__.__module__, + self.__class__.__name__)), + self.__class__.provided_type)) self.attributes = tuple(injection for injection in args if is_attribute_injection(injection)) - """Tuple of attribute injections. - - :type: tuple[:py:class:`dependency_injector.injections.Attribute`] - """ self.methods = tuple(injection for injection in args if is_method_injection(injection)) - """Tuple of method injections. - :type: tuple[:py:class:`dependency_injector.injections.Method`] - """ - - super(Factory, self).__init__() + super(Factory, self).__init__(provides, *args, **kwargs) @property def injections(self): @@ -326,8 +440,8 @@ class Factory(Provider): :rtype: object """ - instance = self.provides(*_get_injectable_args(args, self.args), - **_get_injectable_kwargs(kwargs, self.kwargs)) + instance = super(Factory, self)._provide(*args, **kwargs) + for attribute in self.attributes: setattr(instance, attribute.name, attribute.value) for method in self.methods: @@ -335,49 +449,13 @@ class Factory(Provider): return instance - def _ensure_provides_type(self, provides): - """Check if provided type is valid type for this factory. - :param provides: Factory provided type - :type provides: type - - :raise: :py:exc:`dependency_injector.errors.Error` if ``provides`` - is not callable - :raise: :py:exc:`dependency_injector.errors.Error` if ``provides`` - doesn't meet factory provided type - - :return: validated ``provides`` - :rtype: type - """ - if not callable(provides): - raise Error('Factory provider expects to get callable, ' + - 'got {0} instead'.format(str(provides))) - if (self.__class__.provided_type and - not issubclass(provides, self.__class__.provided_type)): - raise Error('{0} can provide only {1} instances'.format( - '.'.join((self.__class__.__module__, - self.__class__.__name__)), - self.__class__.provided_type)) - return provides - - 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(Factory): """:py:class:`Singleton` provider returns same instance on every call. :py:class:`Singleton` provider creates instance once and return it on every - call. :py:class:`Singleton` uses :py:class:`Factory` for creation of - instance, so, please follow :py:class:`Factory` documentation to go inside - with injections syntax. + call. :py:class:`Singleton` extends :py:class:`Factory`, so, please follow + :py:class:`Factory` documentation to go inside with injections syntax. :py:class:`Singleton` is thread-safe and could be used in multithreading environment without any negative impact. @@ -392,9 +470,52 @@ class Singleton(Provider): some_arg2=2) some_object = singleton() + .. 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`] """ - __slots__ = ('instance', 'factory') + __slots__ = ('instance',) def __init__(self, provides, *args, **kwargs): """Initializer. @@ -410,66 +531,7 @@ class Singleton(Provider): :type kwargs: dict """ self.instance = None - """Read-only reference to singleton's instance. - - :type: object - """ - - self.factory = Factory(provides, *args, **kwargs) - """Singleton's factory object. - - :type: :py:class:`Factory` - """ - - super(Singleton, self).__init__() - - @property - def provides(self): - """Class or other callable that provides object for creation. - - :type: type | callable - """ - return self.factory.provides - - @property - def args(self): - """Tuple of positional argument injections. - - :type: tuple[:py:class:`dependency_injector.injections.Arg`] - """ - return self.factory.args - - @property - def kwargs(self): - """Tuple of keyword argument injections. - - :type: tuple[:py:class:`dependency_injector.injections.KwArg`] - """ - return self.factory.kwargs - - @property - def attributes(self): - """Tuple of attribute injections. - - :type: tuple[:py:class:`dependency_injector.injections.Attribute`] - """ - return self.factory.attributes - - @property - def methods(self): - """Tuple of method injections. - - :type: tuple[:py:class:`dependency_injector.injections.Method`] - """ - return self.factory.methods - - @property - def injections(self): - """Read-only tuple of all injections. - - :rtype: tuple[:py:class:`dependency_injector.injections.Injection`] - """ - return self.factory.injections + super(Singleton, self).__init__(provides, *args, **kwargs) def reset(self): """Reset cached instance, if any. @@ -491,103 +553,10 @@ class Singleton(Provider): """ with GLOBAL_LOCK: if not self.instance: - self.instance = self.factory(*args, **kwargs) + self.instance = super(Singleton, self)._provide(*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): @@ -604,6 +573,12 @@ class ExternalDependency(Provider): database_provider.override(Factory(sqlite3.connect, ':memory:')) database = database_provider() + + .. py:attribute:: instance_of + + Class of required dependency. + + :type: type """ __slots__ = ('instance_of',) @@ -614,10 +589,6 @@ class ExternalDependency(Provider): raise Error('ExternalDependency provider expects to get class, ' + 'got {0} instead'.format(str(instance_of))) self.instance_of = instance_of - """Class of required dependency. - - :type: type - """ super(ExternalDependency, self).__init__() def __call__(self, *args, **kwargs): @@ -670,6 +641,12 @@ class Static(Provider): :py:class:`Static` provider is base implementation that provides exactly the same as it got on input. + + .. py:attribute:: provides + + Value that have to be provided. + + :type: object """ __slots__ = ('provides',) @@ -681,10 +658,6 @@ class Static(Provider): :type provides: object """ self.provides = provides - """Value that have to be provided. - - :type: object - """ super(Static, self).__init__() def _provide(self, *args, **kwargs): @@ -847,17 +820,7 @@ class ChildConfig(Provider): :type root_config: :py:class:`Config` """ self.parents = parents - """Tuple of pieces of configuration option / section parent path. - - :type: tuple[str] - """ - self.root_config = root_config - """Root configuration object. - - :type: :py:class:`Config` - """ - super(ChildConfig, self).__init__() def __getattr__(self, item): diff --git a/docs/api/providers.rst b/docs/api/providers.rst index 4989b44e..b5f3ecc1 100644 --- a/docs/api/providers.rst +++ b/docs/api/providers.rst @@ -4,3 +4,4 @@ .. automodule:: dependency_injector.providers :members: :inherited-members: + :show-inheritance: diff --git a/docs/main/changelog.rst b/docs/main/changelog.rst index b791b1f6..23af0308 100644 --- a/docs/main/changelog.rst +++ b/docs/main/changelog.rst @@ -11,6 +11,9 @@ Development version ------------------- - Add possibility to validate ``Factory`` provided type on ``Factory`` initialization. +- Add possibility to validate ``Singleton`` provided type on ``Singleton`` + initialization. +- Make some refactorings for providers. 1.11.2 ------ diff --git a/tests/test_providers.py b/tests/test_providers.py index 3621958c..e29c2a3c 100644 --- a/tests/test_providers.py +++ b/tests/test_providers.py @@ -176,44 +176,44 @@ class FactoryTests(unittest.TestCase): """Test creation of provider with a callable.""" self.assertTrue(providers.Factory(credits)) + def test_init_with_not_callable(self): + """Test creation of provider with not a callable.""" + self.assertRaises(errors.Error, providers.Factory, 123) + def test_init_with_valid_provided_type(self): """Test creation with not valid provided type.""" - class ExampleFactory(providers.Factory): - """Example factory.""" + class ExampleProvider(providers.Factory): + """Example provider.""" provided_type = Example - example_factory = ExampleFactory(Example, 1, 2) + example_provider = ExampleProvider(Example, 1, 2) - self.assertIsInstance(example_factory(), Example) + self.assertIsInstance(example_provider(), Example) def test_init_with_valid_provided_subtype(self): """Test creation with not valid provided type.""" - class ExampleFactory(providers.Factory): - """Example factory.""" + class ExampleProvider(providers.Factory): + """Example provider.""" provided_type = Example class NewExampe(Example): """Example class subclass.""" - example_factory = ExampleFactory(NewExampe, 1, 2) + example_provider = ExampleProvider(NewExampe, 1, 2) - self.assertIsInstance(example_factory(), NewExampe) + self.assertIsInstance(example_provider(), NewExampe) def test_init_with_invalid_provided_type(self): """Test creation with not valid provided type.""" - class ExampleFactory(providers.Factory): - """Example factory.""" + class ExampleProvider(providers.Factory): + """Example provider.""" provided_type = Example with self.assertRaises(errors.Error): - ExampleFactory(list) - - def test_init_with_not_callable(self): - """Test creation of provider with not a callable.""" - self.assertRaises(errors.Error, providers.Factory, 123) + ExampleProvider(list) def test_call(self): """Test creation of new instances.""" @@ -434,6 +434,41 @@ class SingletonTests(unittest.TestCase): """Test creation of provider with not a callable.""" self.assertRaises(errors.Error, providers.Singleton, 123) + def test_init_with_valid_provided_type(self): + """Test creation with not valid provided type.""" + class ExampleProvider(providers.Singleton): + """Example provider.""" + + provided_type = Example + + example_provider = ExampleProvider(Example, 1, 2) + + self.assertIsInstance(example_provider(), Example) + + def test_init_with_valid_provided_subtype(self): + """Test creation with not valid provided type.""" + class ExampleProvider(providers.Singleton): + """Example provider.""" + + provided_type = Example + + class NewExampe(Example): + """Example class subclass.""" + + example_provider = ExampleProvider(NewExampe, 1, 2) + + self.assertIsInstance(example_provider(), NewExampe) + + def test_init_with_invalid_provided_type(self): + """Test creation with not valid provided type.""" + class ExampleProvider(providers.Singleton): + """Example provider.""" + + provided_type = Example + + with self.assertRaises(errors.Error): + ExampleProvider(list) + def test_call(self): """Test getting of instances.""" provider = providers.Singleton(Example)