From 0e5ec6956ece20514cda3f7f5b59552d6be82b41 Mon Sep 17 00:00:00 2001 From: Roman Mogilatov Date: Mon, 8 Feb 2016 00:29:41 +0200 Subject: [PATCH] Optimize provider calls --- dependency_injector/injections.py | 24 ++++------ dependency_injector/providers.py | 79 ++++++++++++++++++++++--------- tox.ini | 8 ++-- 3 files changed, 69 insertions(+), 42 deletions(-) diff --git a/dependency_injector/injections.py b/dependency_injector/injections.py index 3d71af48..99ea48e1 100644 --- a/dependency_injector/injections.py +++ b/dependency_injector/injections.py @@ -1,7 +1,6 @@ """Injections module.""" import sys -import itertools import six @@ -247,8 +246,14 @@ def inject(*args, **kwargs): @six.wraps(callback) def decorated(*args, **kwargs): """Decorated with dependency injection callback.""" - return callback(*_get_injectable_args(args, decorated.args), - **_get_injectable_kwargs(kwargs, decorated.kwargs)) + if decorated.args: + args = tuple(arg.value for arg in decorated.args) + args + + for kwarg in decorated.kwargs: + if kwarg.name not in kwargs: + kwargs[kwarg.name] = kwarg.value + + return callback(*args, **kwargs) decorated.args = arg_injections decorated.kwargs = kwarg_injections @@ -274,16 +279,3 @@ def _parse_kwargs_injections(args, kwargs): kwarg_injections += tuple(KwArg(name, value) for name, value in six.iteritems(kwargs)) return kwarg_injections - - -def _get_injectable_args(context_args, arg_injections): - """Return tuple of positional arguments, patched with injections.""" - return itertools.chain((arg.value for arg in arg_injections), context_args) - - -def _get_injectable_kwargs(context_kwargs, kwarg_injections): - """Return dictionary of keyword arguments, patched with injections.""" - injectable_kwargs = dict((kwarg.name, kwarg.value) - for kwarg in kwarg_injections) - injectable_kwargs.update(context_kwargs) - return injectable_kwargs diff --git a/dependency_injector/providers.py b/dependency_injector/providers.py index 46a5f14e..5917b439 100644 --- a/dependency_injector/providers.py +++ b/dependency_injector/providers.py @@ -4,8 +4,6 @@ import six from .injections import _parse_args_injections from .injections import _parse_kwargs_injections -from .injections import _get_injectable_args -from .injections import _get_injectable_kwargs from .utils import ensure_is_provider from .utils import is_attribute_injection @@ -62,18 +60,10 @@ class Provider(object): Tuple of overriding providers, if any. :type: tuple[:py:class:`Provider`] | None - """ - __IS_PROVIDER__ = True - __slots__ = ('overridden_by',) + .. py:method:: __call__ - def __init__(self): - """Initializer.""" - self.overridden_by = None - super(Provider, self).__init__() - - def __call__(self, *args, **kwargs): - """Return provided instance. + Return provided instance. Implementation of current method adds ``callable`` functionality for providers API and it should be common for all provider's subclasses. @@ -81,10 +71,22 @@ class Provider(object): common for all providers. Implementation of particular providing strategy should be done in :py:meth:`Provider._provide` of :py:class:`Provider` subclass. - """ - if self.overridden_by: - return self.last_overriding(*args, **kwargs) - return self._provide(*args, **kwargs) + + :return: Provided instance + :rtype: object + """ + + __IS_PROVIDER__ = True + __OPTIMIZED_CALLS__ = True + __slots__ = ('overridden_by', '__call__') + + def __init__(self): + """Initializer.""" + self.overridden_by = None + super(Provider, self).__init__() + # Enable __call__() / _provide() optimization + if self.__class__.__OPTIMIZED_CALLS__: + self.__call__ = self._provide def _provide(self, *args, **kwargs): """Providing strategy implementation. @@ -95,6 +97,10 @@ class Provider(object): """ raise NotImplementedError() + def _call_last_overriding(self, *args, **kwargs): + """Call last overriding provider and return result.""" + return self.last_overriding(*args, **kwargs) + @property def is_overridden(self): """Read-only property that is set to ``True`` if provider is overridden. @@ -127,6 +133,10 @@ class Provider(object): else: self.overridden_by += (ensure_is_provider(provider),) + # Disable __call__() / _provide() optimization + if self.__class__.__OPTIMIZED_CALLS__: + self.__call__ = self._call_last_overriding + def reset_last_overriding(self): """Reset last overriding provider. @@ -139,6 +149,11 @@ class Provider(object): raise Error('Provider {0} is not overridden'.format(str(self))) self.overridden_by = self.overridden_by[:-1] + if not self.is_overridden: + # Enable __call__() / _provide() optimization + if self.__class__.__OPTIMIZED_CALLS__: + self.__call__ = self._provide + def reset_override(self): """Reset all overriding providers. @@ -146,6 +161,10 @@ class Provider(object): """ self.overridden_by = None + # Enable __call__() / _provide() optimization + if self.__class__.__OPTIMIZED_CALLS__: + self.__call__ = self._provide + def delegate(self): """Return provider's delegate. @@ -306,8 +325,14 @@ class Callable(Provider): :rtype: object """ - return self.provides(*_get_injectable_args(args, self.args), - **_get_injectable_kwargs(kwargs, self.kwargs)) + if self.args: + args = tuple(arg.value for arg in self.args) + args + + for kwarg in self.kwargs: + if kwarg.name not in kwargs: + kwargs[kwarg.name] = kwarg.value + + return self.provides(*args, **kwargs) def __str__(self): """Return string representation of provider. @@ -466,7 +491,14 @@ class Factory(Callable): :rtype: object """ - instance = super(Factory, self)._provide(*args, **kwargs) + if self.args: + args = tuple(arg.value for arg in self.args) + args + + for kwarg in self.kwargs: + if kwarg.name not in kwargs: + kwargs[kwarg.name] = kwarg.value + + instance = self.provides(*args, **kwargs) for attribute in self.attributes: setattr(instance, attribute.name, attribute.value) @@ -625,10 +657,12 @@ class Singleton(Factory): :rtype: object """ + if self.instance: + return self.instance + with GLOBAL_LOCK: - if not self.instance: - self.instance = super(Singleton, self)._provide(*args, - **kwargs) + self.instance = super(Singleton, self)._provide(*args, **kwargs) + return self.instance @@ -709,6 +743,7 @@ class ExternalDependency(Provider): :type: type """ + __OPTIMIZED_CALLS__ = False __slots__ = ('instance_of',) def __init__(self, instance_of): diff --git a/tox.ini b/tox.ini index 0e54905e..bd6cb072 100644 --- a/tox.ini +++ b/tox.ini @@ -20,8 +20,8 @@ commands= coverage run --rcfile=./.coveragerc -m unittest2 discover tests [] coverage html --rcfile=./.coveragerc - flake8 --max-complexity=8 dependency_injector/ - flake8 --max-complexity=8 examples/ + flake8 --max-complexity=10 dependency_injector/ + flake8 --max-complexity=10 examples/ pep257 dependency_injector/ pep257 examples/ @@ -50,8 +50,8 @@ basepython=python2.7 deps= flake8 commands= - flake8 --max-complexity=8 dependency_injector/ - flake8 --max-complexity=8 examples/ + flake8 --max-complexity=10 dependency_injector/ + flake8 --max-complexity=10 examples/ [testenv:pep257] basepython=python2.7