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."""
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

View File

@ -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):

View File

@ -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