diff --git a/src/dependency_injector/providers/__init__.py b/src/dependency_injector/providers/__init__.py index 2e235e8f..d01f77cd 100644 --- a/src/dependency_injector/providers/__init__.py +++ b/src/dependency_injector/providers/__init__.py @@ -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', diff --git a/src/dependency_injector/providers/callables.pyx b/src/dependency_injector/providers/callables.pyx index 370f6619..a2c963f1 100644 --- a/src/dependency_injector/providers/callables.pyx +++ b/src/dependency_injector/providers/callables.pyx @@ -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): diff --git a/src/dependency_injector/providers/factories.pyx b/src/dependency_injector/providers/factories.pyx index c54ee9ac..24c4a2bc 100644 --- a/src/dependency_injector/providers/factories.pyx +++ b/src/dependency_injector/providers/factories.pyx @@ -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. diff --git a/src/dependency_injector/providers/singletons.pxd b/src/dependency_injector/providers/singletons.pxd index fe494859..25f0b452 100644 --- a/src/dependency_injector/providers/singletons.pxd +++ b/src/dependency_injector/providers/singletons.pxd @@ -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): diff --git a/src/dependency_injector/providers/singletons.pyx b/src/dependency_injector/providers/singletons.pyx index 21b9b4e9..58e9c42a 100644 --- a/src/dependency_injector/providers/singletons.pyx +++ b/src/dependency_injector/providers/singletons.pyx @@ -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): diff --git a/src/dependency_injector/providers/utils.h b/src/dependency_injector/providers/utils.h new file mode 100644 index 00000000..00f08d98 --- /dev/null +++ b/src/dependency_injector/providers/utils.h @@ -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 */ diff --git a/src/dependency_injector/providers/utils.pxd b/src/dependency_injector/providers/utils.pxd index 13d9f049..989ce57d 100644 --- a/src/dependency_injector/providers/utils.pxd +++ b/src/dependency_injector/providers/utils.pxd @@ -6,7 +6,7 @@ Powered by Cython. from .base cimport Provider -cdef tuple CLASS_TYPES +cdef public object CLASS_TYPES cdef class OverridingContext(object): diff --git a/src/dependency_injector/providers/utils.pyx b/src/dependency_injector/providers/utils.pyx index 0eff832d..93b04105 100644 --- a/src/dependency_injector/providers/utils.pyx +++ b/src/dependency_injector/providers/utils.pyx @@ -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 diff --git a/tests/performance/test.py b/tests/performance/test.py new file mode 100644 index 00000000..20ac85fc --- /dev/null +++ b/tests/performance/test.py @@ -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() diff --git a/tests/unit/providers/test_callables.py b/tests/unit/providers/test_callables.py index e1d8ecad..f17c0e4d 100644 --- a/tests/unit/providers/test_callables.py +++ b/tests/unit/providers/test_callables.py @@ -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)) diff --git a/tests/unit/providers/test_singletons.py b/tests/unit/providers/test_singletons.py index f6050dd5..ca13052e 100644 --- a/tests/unit/providers/test_singletons.py +++ b/tests/unit/providers/test_singletons.py @@ -210,7 +210,7 @@ class SingletonTests(unittest.TestCase): provider = providers.Singleton(Example) self.assertEqual(repr(provider), - ''.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), - ''.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))