Implement singletons (need refactoring)

This commit is contained in:
Roman Mogilatov 2016-11-06 17:14:12 +02:00
parent 7502fa1e89
commit 322ba98f18
11 changed files with 502 additions and 28 deletions

View File

@ -34,7 +34,6 @@ from .injections import (
NamedInjection,
)
from .utils import (
GLOBAL_LOCK,
OverridingContext,
is_provider,
ensure_is_provider,
@ -71,7 +70,6 @@ __all__ = (
'PositionalInjection',
'NamedInjection',
'GLOBAL_LOCK',
'OverridingContext',
'is_provider',
'ensure_is_provider',

View File

@ -12,7 +12,7 @@ from .injections cimport (
parse_positional_injections,
parse_named_injections,
)
from .utils import represent_provider
from .utils cimport represent_provider
cdef class Callable(Provider):

View File

@ -11,7 +11,7 @@ from .injections cimport (
NamedInjection,
parse_named_injections,
)
from .utils import represent_provider
from .utils cimport represent_provider
cdef class Factory(Provider):
@ -90,7 +90,6 @@ cdef class Factory(Provider):
super(Factory, self).__init__()
def __str__(self):
"""Return string representation of provider.

View File

@ -4,22 +4,39 @@ Powered by Cython.
"""
from .base cimport Provider
from .factories cimport Factory
cdef class BaseSingleton(Provider):
pass
cdef Factory __instantiator
cdef class Singleton(BaseSingleton):
pass
cdef object __storage
cpdef object _provide(self, tuple args, dict kwargs)
cdef inline object __provide(self, tuple args, dict kwargs):
if self.__storage is None:
self.__storage = self.__instantiator.__provide(args, kwargs)
return self.__storage
cdef class DelegatedSingleton(Singleton):
pass
cdef class ThreadSafeSingleton(Singleton):
pass
cdef class ThreadSafeSingleton(BaseSingleton):
cdef object __storage
cdef object __lock
cpdef object _provide(self, tuple args, dict kwargs)
cdef inline object __provide(self, tuple args, dict kwargs):
with self.__lock:
if self.__storage is None:
self.__storage = self.__instantiator.__provide(args, kwargs)
return self.__storage
cdef class DelegatedThreadSafeSingleton(ThreadSafeSingleton):
@ -27,7 +44,20 @@ cdef class DelegatedThreadSafeSingleton(ThreadSafeSingleton):
cdef class ThreadLocalSingleton(BaseSingleton):
pass
cdef object __storage
cpdef object _provide(self, tuple args, dict kwargs)
cdef inline object __provide(self, tuple args, dict kwargs):
cdef object instance
try:
instance = self.__storage.instance
except AttributeError:
instance = self.__instantiator.__provide(args, kwargs)
self.__storage.instance = instance
finally:
return instance
cdef class DelegatedThreadLocalSingleton(ThreadLocalSingleton):

View File

@ -3,23 +3,243 @@
Powered by Cython.
"""
import threading
from dependency_injector.errors import Error
from .base cimport Provider
from .factories cimport Factory
from .utils cimport represent_provider
GLOBAL_LOCK = threading.RLock()
"""Global reentrant lock.
:type: :py:class:`threading.RLock`
"""
cdef class BaseSingleton(Provider):
pass
"""Base class of singleton providers."""
provided_type = None
def __init__(self, provides, *args, **kwargs):
"""Initializer.
:param provides: Provided type.
:type provides: type
:param args: Tuple of positional argument injections.
:type args: tuple[object]
:param kwargs: Dictionary of context keyword argument injections.
:type kwargs: dict[str, object]
"""
if (self.__class__.provided_type and
not issubclass(provides, self.__class__.provided_type)):
raise Error('{0} can provide only {1} instances'.format(
self.__class__, self.__class__.provided_type))
self.__instantiator = Factory(provides, *args, **kwargs)
super(Provider, self).__init__()
def __str__(self):
"""Return string representation of provider.
:rtype: str
"""
return represent_provider(provider=self,
provides=self.__instantiator.cls)
@property
def cls(self):
"""Return provided type."""
return self.__instantiator.cls
@property
def args(self):
"""Return positional argument injections."""
return self.__instantiator.args
def add_args(self, *args):
"""Add __init__ postional argument injections.
:param args: Tuple of injections.
:type args: tuple
:return: Reference ``self``
"""
self.__instantiator.add_args(*args)
return self
def set_args(self, *args):
"""Set __init__ postional argument injections.
Existing __init__ positional argument injections are dropped.
:param args: Tuple of injections.
:type args: tuple
:return: Reference ``self``
"""
self.__instantiator.set_args(*args)
return self
def clear_args(self):
"""Drop __init__ postional argument injections.
:return: Reference ``self``
"""
self.__instantiator.clear_args()
return self
@property
def kwargs(self):
"""Return keyword argument injections."""
return self.__instantiator.kwargs
def add_kwargs(self, **kwargs):
"""Add __init__ keyword argument injections.
:param kwargs: Dictionary of injections.
:type kwargs: dict
:return: Reference ``self``
"""
self.__instantiator.add_kwargs(**kwargs)
return self
def set_kwargs(self, **kwargs):
"""Set __init__ keyword argument injections.
Existing __init__ keyword argument injections are dropped.
:param kwargs: Dictionary of injections.
:type kwargs: dict
:return: Reference ``self``
"""
self.__instantiator.set_kwargs(**kwargs)
return self
def clear_kwargs(self):
"""Drop __init__ keyword argument injections.
:return: Reference ``self``
"""
self.__instantiator.clear_kwargs()
return self
@property
def attributes(self):
"""Return attribute injections."""
return self.__instantiator.attributes
def add_attributes(self, **kwargs):
"""Add attribute injections.
:param args: Tuple of injections.
:type args: tuple
:return: Reference ``self``
"""
self.__instantiator.add_attributes(**kwargs)
return self
def set_attributes(self, **kwargs):
"""Set attribute injections.
Existing attribute injections are dropped.
:param args: Tuple of injections.
:type args: tuple
:return: Reference ``self``
"""
self.__instantiator.set_attributes(**kwargs)
return self
def clear_attributes(self):
"""Drop attribute injections.
:return: Reference ``self``
"""
self.__instantiator.clear_attributes()
return self
def reset(self):
"""Reset cached instance, if any.
:rtype: None
"""
raise NotImplementedError()
cdef class Singleton(BaseSingleton):
pass
"""Singleton provider."""
def __init__(self, provides, *args, **kwargs):
"""Initializer.
:param provides: Provided type.
:type provides: type
:param args: Tuple of positional argument injections.
:type args: tuple[object]
:param kwargs: Dictionary of context keyword argument injections.
:type kwargs: dict[str, object]
"""
self.__storage = None
super(Singleton, self).__init__(provides, *args, **kwargs)
def reset(self):
"""Reset cached instance, if any.
:rtype: None
"""
self.__storage = None
cpdef object _provide(self, tuple args, dict kwargs):
"""Return single instance."""
return self.__provide(args, kwargs)
cdef class DelegatedSingleton(Singleton):
__IS_DELEGATED__ = True
cdef class ThreadSafeSingleton(Singleton):
pass
cdef class ThreadSafeSingleton(BaseSingleton):
"""Thread-safe singleton provider."""
def __init__(self, provides, *args, **kwargs):
"""Initializer.
:param provides: Provided type.
:type provides: type
:param args: Tuple of positional argument injections.
:type args: tuple[object]
:param kwargs: Dictionary of context keyword argument injections.
:type kwargs: dict[str, object]
"""
self.__storage = None
self.__lock = GLOBAL_LOCK
super(ThreadSafeSingleton, self).__init__(provides, *args, **kwargs)
def reset(self):
"""Reset cached instance, if any.
:rtype: None
"""
self.__storage = None
cpdef object _provide(self, tuple args, dict kwargs):
"""Return single instance."""
return self.__provide(args, kwargs)
cdef class DelegatedThreadSafeSingleton(ThreadSafeSingleton):
@ -27,7 +247,33 @@ cdef class DelegatedThreadSafeSingleton(ThreadSafeSingleton):
cdef class ThreadLocalSingleton(BaseSingleton):
pass
"""Thread local singleton provider."""
def __init__(self, provides, *args, **kwargs):
"""Initializer.
:param provides: Provided type.
:type provides: type
:param args: Tuple of positional argument injections.
:type args: tuple[object]
:param kwargs: Dictionary of context keyword argument injections.
:type kwargs: dict[str, object]
"""
self.__storage = threading.local()
super(ThreadLocalSingleton, self).__init__(provides, *args, **kwargs)
def reset(self):
"""Reset cached instance, if any.
:rtype: None
"""
self.__storage.instance = None
cpdef object _provide(self, tuple args, dict kwargs):
"""Return single instance."""
return self.__provide(args, kwargs)
cdef class DelegatedThreadLocalSingleton(ThreadLocalSingleton):

View File

@ -0,0 +1,31 @@
/* Generated by Cython 0.25.1 */
#ifndef __PYX_HAVE__dependency_injector__providers__utils
#define __PYX_HAVE__dependency_injector__providers__utils
#ifndef __PYX_HAVE_API__dependency_injector__providers__utils
#ifndef __PYX_EXTERN_C
#ifdef __cplusplus
#define __PYX_EXTERN_C extern "C"
#else
#define __PYX_EXTERN_C extern
#endif
#endif
#ifndef DL_IMPORT
#define DL_IMPORT(_T) _T
#endif
__PYX_EXTERN_C DL_IMPORT(PyObject) *__pyx_v_19dependency_injector_9providers_5utils_CLASS_TYPES;
#endif /* !__PYX_HAVE_API__dependency_injector__providers__utils */
#if PY_MAJOR_VERSION < 3
PyMODINIT_FUNC initutils(void);
#else
PyMODINIT_FUNC PyInit_utils(void);
#endif
#endif /* !__PYX_HAVE__dependency_injector__providers__utils */

View File

@ -6,7 +6,7 @@ Powered by Cython.
from .base cimport Provider
cdef tuple CLASS_TYPES
cdef public object CLASS_TYPES
cdef class OverridingContext(object):

View File

@ -13,13 +13,6 @@ from dependency_injector.errors import Error
from .base cimport Provider
GLOBAL_LOCK = threading.RLock()
"""Global reentrant lock.
:type: :py:class:`threading.RLock`
"""
cdef tuple CLASS_TYPES
if sys.version_info[0] == 3: # pragma: no cover
CLASS_TYPES = (type,)
else: # pragma: no cover

177
tests/performance/test.py Normal file
View File

@ -0,0 +1,177 @@
"""Test providers performance."""
import time
import gc
import dependency_injector.providers
class Tester(object):
"""Performance tester for provider module implementations."""
def __init__(self, provider_modules, duration_factor):
"""Initializer."""
self.provider_modules = provider_modules
self.tests = [getattr(self, name)
for name in dir(self)
if name.startswith('test')]
self.total_time = 0
self.duration_factor = duration_factor
def run(self):
"""Run all tests for all provider modules."""
for module in self.provider_modules:
print('\n')
print('Running tests for module - "{module}":'
.format(module=module.__name__))
gc.disable()
for test in self.tests:
start_time = time.time()
test(module)
self.total_time = time.time() - start_time
print('Test "{test}" took - {time}'
.format(test=test.__name__,
time=self.total_time))
gc.collect()
gc.enable()
print('\n')
# def test_simple_object(self, providers):
# """Test simple object's creation."""
# class Test(object):
# pass
#
# for x in xrange(int(5000000 * self.duration_factor)):
# Test()
#
# def test_simple_object_factory(self, providers):
# """Test simple object's factory."""
# class Test(object):
# pass
#
# test_factory = providers.Factory(Test)
# for x in xrange(int(5000000 * self.duration_factor)):
# test_factory()
#
# def test_3_ctx_positional_injections(self, providers):
# """Test factory with 3 context positional injections."""
# class Test(object):
# def __init__(self, a, b, c):
# pass
#
# for x in xrange(int(5000000 * self.duration_factor)):
# Test(1, 2, 3)
#
# def test_factory_3_ctx_positional_injections(self, providers):
# """Test factory with 3 context positional injections."""
# class Test(object):
# def __init__(self, a, b, c):
# pass
#
# test_factory = providers.Factory(Test)
# for x in xrange(int(5000000 * self.duration_factor)):
# test_factory(1, 2, 3)
def test_3_kw_injections(self, providers):
"""Test 3 keyword argument injections."""
class A(object):
pass
class B(object):
pass
class C(object):
pass
class Test(object):
def __init__(self, a, b, c):
pass
for x in xrange(int(5000000 * self.duration_factor)):
Test(a=A(), b=B(), c=C())
def test_factory_3_factory_kw_injections(self, providers):
"""Test factory with 3 keyword argument injections via factories."""
class A(object):
pass
class B(object):
pass
class C(object):
pass
class Test(object):
def __init__(self, a, b, c):
pass
a_factory = providers.Factory(A)
b_factory = providers.Factory(B)
c_factory = providers.Factory(C)
test_factory = providers.Factory(Test,
a=a_factory,
b=b_factory,
c=c_factory)
for x in xrange(int(5000000 * self.duration_factor)):
test_factory()
def test_factory_subcls_3_factory_subcls_kw_injections(self, providers):
"""Test factory with 3 keyword argument injections via factories."""
class MyFactory(providers.Factory):
pass
class A(object):
pass
class B(object):
pass
class C(object):
pass
class Test(object):
def __init__(self, a, b, c):
pass
a_factory = MyFactory(A)
b_factory = MyFactory(B)
c_factory = MyFactory(C)
test_factory = MyFactory(Test,
a=a_factory,
b=b_factory,
c=c_factory)
for x in xrange(int(5000000 * self.duration_factor)):
test_factory()
# def test_singleton(self, providers):
# """Test factory with 3 keyword argument injections via factories."""
# class Test(object):
# def __init__(self):
# pass
#
# test_factory = providers.Singleton(Test)
# for x in xrange(int(5000000 * self.duration_factor)):
# test_factory()
#
# def test_singleton_subcls(self, providers):
# """Test factory with 3 keyword argument injections via factories."""
# class MySingleton(providers.Singleton):
# pass
#
# class Test(object):
# pass
#
# test_factory = MySingleton(Test)
# for x in xrange(int(5000000 * self.duration_factor)):
# test_factory()
if __name__ == '__main__':
tester = Tester(
provider_modules=[
dependency_injector.providers,
],
duration_factor=1)
tester.run()

View File

@ -88,4 +88,4 @@ class DelegatedCallableTests(unittest.TestCase):
def test_is_delegated_provider(self):
provider = providers.DelegatedCallable(len)
self.assertIs(provider.provide_injection(), provider)
self.assertTrue(providers.is_delegated(provider))

View File

@ -210,7 +210,7 @@ class SingletonTests(unittest.TestCase):
provider = providers.Singleton(Example)
self.assertEqual(repr(provider),
'<dependency_injector.providers.creational.'
'<dependency_injector.providers.singletons.'
'Singleton({0}) at {1}>'.format(
repr(Example),
hex(id(provider))))
@ -228,7 +228,7 @@ class DelegatedSingletonTests(unittest.TestCase):
def test_is_delegated_provider(self):
provider = providers.DelegatedSingleton(object)
self.assertIs(provider.provide_injection(), provider)
self.assertTrue(providers.is_delegated(provider))
class ThreadLocalSingletonTests(unittest.TestCase):
@ -425,7 +425,7 @@ class ThreadLocalSingletonTests(unittest.TestCase):
provider = providers.ThreadLocalSingleton(Example)
self.assertEqual(repr(provider),
'<dependency_injector.providers.creational.'
'<dependency_injector.providers.singletons.'
'ThreadLocalSingleton({0}) at {1}>'.format(
repr(Example),
hex(id(provider))))
@ -444,4 +444,4 @@ class DelegatedThreadLocalSingletonTests(unittest.TestCase):
def test_is_delegated_provider(self):
provider = providers.DelegatedThreadLocalSingleton(object)
self.assertIs(provider.provide_injection(), provider)
self.assertTrue(providers.is_delegated(provider))