mirror of
https://github.com/ets-labs/python-dependency-injector.git
synced 2024-11-22 01:26:51 +03:00
Implement singletons (need refactoring)
This commit is contained in:
parent
7502fa1e89
commit
322ba98f18
|
@ -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',
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
31
src/dependency_injector/providers/utils.h
Normal file
31
src/dependency_injector/providers/utils.h
Normal 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 */
|
|
@ -6,7 +6,7 @@ Powered by Cython.
|
|||
from .base cimport Provider
|
||||
|
||||
|
||||
cdef tuple CLASS_TYPES
|
||||
cdef public object CLASS_TYPES
|
||||
|
||||
|
||||
cdef class OverridingContext(object):
|
||||
|
|
|
@ -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
177
tests/performance/test.py
Normal 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()
|
|
@ -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))
|
||||
|
|
|
@ -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))
|
||||
|
|
Loading…
Reference in New Issue
Block a user