Optimize provider calls

This commit is contained in:
Roman Mogilatov 2016-02-08 00:29:41 +02:00
parent f0f5822d14
commit 0e5ec6956e
3 changed files with 69 additions and 42 deletions

View File

@ -1,7 +1,6 @@
"""Injections module.""" """Injections module."""
import sys import sys
import itertools
import six import six
@ -247,8 +246,14 @@ def inject(*args, **kwargs):
@six.wraps(callback) @six.wraps(callback)
def decorated(*args, **kwargs): def decorated(*args, **kwargs):
"""Decorated with dependency injection callback.""" """Decorated with dependency injection callback."""
return callback(*_get_injectable_args(args, decorated.args), if decorated.args:
**_get_injectable_kwargs(kwargs, decorated.kwargs)) 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.args = arg_injections
decorated.kwargs = kwarg_injections decorated.kwargs = kwarg_injections
@ -274,16 +279,3 @@ def _parse_kwargs_injections(args, kwargs):
kwarg_injections += tuple(KwArg(name, value) kwarg_injections += tuple(KwArg(name, value)
for name, value in six.iteritems(kwargs)) for name, value in six.iteritems(kwargs))
return kwarg_injections 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

View File

@ -4,8 +4,6 @@ import six
from .injections import _parse_args_injections from .injections import _parse_args_injections
from .injections import _parse_kwargs_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 ensure_is_provider
from .utils import is_attribute_injection from .utils import is_attribute_injection
@ -62,18 +60,10 @@ class Provider(object):
Tuple of overriding providers, if any. Tuple of overriding providers, if any.
:type: tuple[:py:class:`Provider`] | None :type: tuple[:py:class:`Provider`] | None
"""
__IS_PROVIDER__ = True .. py:method:: __call__
__slots__ = ('overridden_by',)
def __init__(self): Return provided instance.
"""Initializer."""
self.overridden_by = None
super(Provider, self).__init__()
def __call__(self, *args, **kwargs):
"""Return provided instance.
Implementation of current method adds ``callable`` functionality for Implementation of current method adds ``callable`` functionality for
providers API and it should be common for all provider's subclasses. 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 common for all providers. Implementation of particular providing
strategy should be done in :py:meth:`Provider._provide` of strategy should be done in :py:meth:`Provider._provide` of
:py:class:`Provider` subclass. :py:class:`Provider` subclass.
:return: Provided instance
:rtype: object
""" """
if self.overridden_by:
return self.last_overriding(*args, **kwargs) __IS_PROVIDER__ = True
return self._provide(*args, **kwargs) __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): def _provide(self, *args, **kwargs):
"""Providing strategy implementation. """Providing strategy implementation.
@ -95,6 +97,10 @@ class Provider(object):
""" """
raise NotImplementedError() raise NotImplementedError()
def _call_last_overriding(self, *args, **kwargs):
"""Call last overriding provider and return result."""
return self.last_overriding(*args, **kwargs)
@property @property
def is_overridden(self): def is_overridden(self):
"""Read-only property that is set to ``True`` if provider is overridden. """Read-only property that is set to ``True`` if provider is overridden.
@ -127,6 +133,10 @@ class Provider(object):
else: else:
self.overridden_by += (ensure_is_provider(provider),) 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): def reset_last_overriding(self):
"""Reset last overriding provider. """Reset last overriding provider.
@ -139,6 +149,11 @@ class Provider(object):
raise Error('Provider {0} is not overridden'.format(str(self))) raise Error('Provider {0} is not overridden'.format(str(self)))
self.overridden_by = self.overridden_by[:-1] 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): def reset_override(self):
"""Reset all overriding providers. """Reset all overriding providers.
@ -146,6 +161,10 @@ class Provider(object):
""" """
self.overridden_by = None self.overridden_by = None
# Enable __call__() / _provide() optimization
if self.__class__.__OPTIMIZED_CALLS__:
self.__call__ = self._provide
def delegate(self): def delegate(self):
"""Return provider's delegate. """Return provider's delegate.
@ -306,8 +325,14 @@ class Callable(Provider):
:rtype: object :rtype: object
""" """
return self.provides(*_get_injectable_args(args, self.args), if self.args:
**_get_injectable_kwargs(kwargs, self.kwargs)) 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): def __str__(self):
"""Return string representation of provider. """Return string representation of provider.
@ -466,7 +491,14 @@ class Factory(Callable):
:rtype: object :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: for attribute in self.attributes:
setattr(instance, attribute.name, attribute.value) setattr(instance, attribute.name, attribute.value)
@ -625,10 +657,12 @@ class Singleton(Factory):
:rtype: object :rtype: object
""" """
if self.instance:
return self.instance
with GLOBAL_LOCK: 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 return self.instance
@ -709,6 +743,7 @@ class ExternalDependency(Provider):
:type: type :type: type
""" """
__OPTIMIZED_CALLS__ = False
__slots__ = ('instance_of',) __slots__ = ('instance_of',)
def __init__(self, instance_of): def __init__(self, instance_of):

View File

@ -20,8 +20,8 @@ commands=
coverage run --rcfile=./.coveragerc -m unittest2 discover tests [] coverage run --rcfile=./.coveragerc -m unittest2 discover tests []
coverage html --rcfile=./.coveragerc coverage html --rcfile=./.coveragerc
flake8 --max-complexity=8 dependency_injector/ flake8 --max-complexity=10 dependency_injector/
flake8 --max-complexity=8 examples/ flake8 --max-complexity=10 examples/
pep257 dependency_injector/ pep257 dependency_injector/
pep257 examples/ pep257 examples/
@ -50,8 +50,8 @@ basepython=python2.7
deps= deps=
flake8 flake8
commands= commands=
flake8 --max-complexity=8 dependency_injector/ flake8 --max-complexity=10 dependency_injector/
flake8 --max-complexity=8 examples/ flake8 --max-complexity=10 examples/
[testenv:pep257] [testenv:pep257]
basepython=python2.7 basepython=python2.7