diff --git a/README.rst b/README.rst index a95be39e..c2de8d1d 100644 --- a/README.rst +++ b/README.rst @@ -16,6 +16,9 @@ dependency injection pattern in formal, pretty, Pythonic way. + Documentation. + Semantic versioning. +*Dependency Injector* providers are implemented as C extension types using +Cython. + Status ------ diff --git a/docs/api/index.rst b/docs/api/index.rst index 1f1df940..132cdd5d 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -7,5 +7,4 @@ API Documentation top_level providers containers - utils errors diff --git a/docs/api/providers.rst b/docs/api/providers.rst index c86ca0c3..b5f3ecc1 100644 --- a/docs/api/providers.rst +++ b/docs/api/providers.rst @@ -1,10 +1,6 @@ ``dependency_injector.providers`` --------------------------------- -.. image:: /images/providers/providers_class_diagram.png - :width: 100% - :align: center - .. automodule:: dependency_injector.providers :members: :inherited-members: diff --git a/docs/containers/index.rst b/docs/containers/index.rst index 55e06f17..93bee78a 100644 --- a/docs/containers/index.rst +++ b/docs/containers/index.rst @@ -17,7 +17,7 @@ Also, for both of these and some other cases, it might be useful to attach some init / shutdown functionality or something else, that deals with group of providers. -Containers module API docs - :py:mod:`dependency_injector.containers`. +Containers package API docs - :py:mod:`dependency_injector.containers`. .. toctree:: :maxdepth: 2 diff --git a/docs/images/providers/providers_class_diagram.png b/docs/images/providers/providers_class_diagram.png deleted file mode 100644 index c4a0cb72..00000000 Binary files a/docs/images/providers/providers_class_diagram.png and /dev/null differ diff --git a/docs/index.rst b/docs/index.rst index 534e291b..11bfe188 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -8,22 +8,33 @@ Dependency Injector --- Dependency injection microframework for Python :description: Dependency Injector is a dependency injection microframework for Python. It was designed to be unified, developer-friendly tool that helps to implement dependency injection pattern in - formal, pretty, Pythonic way. + formal, pretty, Pythonic way. Dependency Injector framework + key features are: Easy, smart, pythonic style; Obvious, clear + structure; Extensibility and flexibility; High performance; + Memory efficiency; Thread safety; Documentation; Semantic + versioning; Dependency Injector providers are implemented as + C extension types using Cython. + +.. _index: *Dependency Injector* is a dependency injection microframework for Python. It was designed to be unified, developer-friendly tool that helps to implement -dependency injection pattern in formal, pretty, Pythonic way. +dependency injection pattern in formal, pretty, Pythonic way. *Dependency Injector* framework key features are: + Easy, smart, pythonic style. + Obvious, clear structure. + Extensibility and flexibility. ++ High performance. + Memory efficiency. + Thread safety. + Documentation. + Semantic versioning. +*Dependency Injector* providers are implemented as C extension types using +Cython. + Status ------ diff --git a/docs/introduction/di_in_python.rst b/docs/introduction/di_in_python.rst index c7cf6b91..8dc370f3 100644 --- a/docs/introduction/di_in_python.rst +++ b/docs/introduction/di_in_python.rst @@ -121,7 +121,7 @@ code for specification of dependencies. Nevertheless, this disadvantage could be easily avoided by creating inversion of control container (IoC container). Example of creation of several inversion of control containers (IoC containers) -using *Dependency Injector*: +using :doc:`Dependency Injector <../index>`: .. literalinclude:: ../../examples/miniapps/engines_cars/example_ioc_containers.py :language: python diff --git a/docs/main/changelog.rst b/docs/main/changelog.rst index b94cbbda..1ef7253b 100644 --- a/docs/main/changelog.rst +++ b/docs/main/changelog.rst @@ -9,16 +9,40 @@ follows `Semantic versioning`_ Development version ------------------- -- Add ``dependency_injector.injections`` module (C extension). -- Transfer ``dependency_injector.utils`` module to Cython (C extension). -- Transfer ``dependency_injector.errors`` module to Cython (C extension). -- Remove ``@inject`` decorator. -- Add makefile (``clean``, ``test``, ``build``, ``install``, ``uninstall`` - & ``publish`` commands). -- Update repository structure: +- **Providers** - - Sources are moved under ``src``. - - Tests are moved under ``tests/unit``. + 1. All providers from ``dependency_injector.providers`` package are + implemented as C extension types using Cython. + 2. Add ``BaseSingleton`` super class for all singleton providers. + 3. Make ``Singleton`` provider not thread-safe. It makes performance of + ``Singleton`` provider 10x times faster. + 4. Add ``ThreadSafeSingleton`` provider - thread-safe version of + ``Singleton`` provider. + 5. Add ``ThreadLocalSingleton`` provider - ``Singleton`` provider that uses + thread-local storage. + 6. Remove ``provides`` attribute from ``Factory`` and ``Singleton`` + providers. + 7. Add ``set_args()`` and ``clear_args()`` methods for ``Callable``, + ``Factory`` and ``Singleton`` providers. + +- **Containers** + + 1. Module ``dependency_injector.containers`` was splitted into submodules + without any functional changes. + +- **Utils** + + 1. Module ``dependency_injector.utils`` is splitted into + ``dependency_injector.containers`` and ``dependency_injector.providers``. + +- **Miscellaneous** + + 1. Remove ``@inject`` decorator. + 2. Add makefile (``clean``, ``test``, ``build``, ``install``, ``uninstall`` + & ``publish`` commands). + 3. Update repository structure: + 1. Sources are moved under ``src/`` folder. + 2. Tests are moved under ``tests/unit/`` folder. .. - No features. diff --git a/docs/main/installation.rst b/docs/main/installation.rst index 1a3e0cd1..abfff964 100644 --- a/docs/main/installation.rst +++ b/docs/main/installation.rst @@ -12,8 +12,13 @@ framework can be installed from PyPi_: pip install dependency_injector # Installing particular version: - pip install dependency_injector==2.0.0 + pip install dependency-injector==3.0.0 +.. note:: + Some components of *Dependency Injector* are implemented as C extension types. + *Dependency Injector* is distributed as an archive with a source code, so + C compiler and Python header files are required for the installation. + Sources can be cloned from GitHub_: .. code-block:: bash @@ -30,7 +35,7 @@ Verification of currently installed version could be done using >>> from dependency_injector import VERSION >>> VERSION - '2.0.0' + '3.0.0' .. _PyPi: https://pypi.python.org/pypi/dependency_injector .. _GitHub: https://github.com/ets-labs/python-dependency-injector diff --git a/docs/providers/index.rst b/docs/providers/index.rst index 621959e3..a4fdc4ab 100644 --- a/docs/providers/index.rst +++ b/docs/providers/index.rst @@ -12,16 +12,7 @@ Current documentation section consists from description of standard providers library and some useful information like overriding of providers and writing of custom providers. -All providers are validated in multithreading environment and considered to -be thread safe. - -+ Base providers class is: :py:class:`dependency_injector.providers.Provider` -+ Providers module API docs: :py:mod:`dependency_injector.providers` -+ Providers class diagram: - -.. image:: /images/providers/providers_class_diagram.png - :width: 100% - :align: center +Providers package API docs - :py:mod:`dependency_injector.providers` .. toctree:: :maxdepth: 2 diff --git a/docs/providers/singleton.rst b/docs/providers/singleton.rst index 6e0a88a6..81573319 100644 --- a/docs/providers/singleton.rst +++ b/docs/providers/singleton.rst @@ -21,7 +21,7 @@ Singleton providers resetting Created and memorized by :py:class:`Singleton` instance can be reset. Reset of :py:class:`Singleton`'s memorized instance is done by clearing reference to -it. Further lifecycle of memorized instance is out of :py:class:`Singleton` +it. Further lifecycle of memorized instance is out of :py:class:`Singleton` provider's control and dependes on garbage collection strategy. Example: @@ -33,15 +33,15 @@ Example: Singleton providers and injections ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -:py:class:`Singleton` provider extends :py:class:`Factory` provider, so, all -of the rules about injections are the same, as for :py:class:`Factory` -provider. +:py:class:`Singleton` provider has same interface as :py:class:`Factory` +provider, so, all of the rules about injections are the same, as for +:py:class:`Factory` provider. .. note:: Due that :py:class:`Singleton` provider creates specified class instance only on the first call, all injections are done once, during the first - call, also. Every next call, while instance has been already created + call. Every next call, while instance has been already created and memorized, no injections are done, :py:class:`Singleton` provider just returns memorized earlier instance. @@ -81,10 +81,12 @@ Specialization of :py:class:`Singleton` providers is the same as Singleton providers and multi-threading ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -:py:class:`Singleton` provider is thread-safe and could be used in -multi-threading applications without any negative impact. Race condition on -singleton's initialization is escaped by using a global reentrant mutex - -:py:obj:`dependency_injector.utils.GLOBAL_LOCK`. +:py:class:`Singleton` provider is NOT thread-safe and should be used in +multi-threading applications with manually controlled locking. + +:py:class:`ThreadSafeSingleton` is a thread-safe version of +:py:class:`Singleton` and could be used in multi-threading applications +without any additional locking. Also there could be a need to use thread-scoped singletons and there is a special provider for such case - :py:class:`ThreadLocalSingleton`. diff --git a/setup.py b/setup.py index 0e386e4c..929d6d1d 100644 --- a/setup.py +++ b/setup.py @@ -47,6 +47,22 @@ setup(name='dependency-injector', '': 'src', }, ext_modules=[ + Extension('dependency_injector.providers.base', + ['src/dependency_injector/providers/base.c'], + define_macros=defined_macros, + extra_compile_args=['-O2']), + Extension('dependency_injector.providers.callables', + ['src/dependency_injector/providers/callables.c'], + define_macros=defined_macros, + extra_compile_args=['-O2']), + Extension('dependency_injector.providers.factories', + ['src/dependency_injector/providers/factories.c'], + define_macros=defined_macros, + extra_compile_args=['-O2']), + Extension('dependency_injector.providers.singletons', + ['src/dependency_injector/providers/singletons.c'], + define_macros=defined_macros, + extra_compile_args=['-O2']), Extension('dependency_injector.providers.injections', ['src/dependency_injector/providers/injections.c'], define_macros=defined_macros, diff --git a/src/dependency_injector/containers/utils.py b/src/dependency_injector/containers/utils.py index 62b6f74f..fcce8722 100644 --- a/src/dependency_injector/containers/utils.py +++ b/src/dependency_injector/containers/utils.py @@ -1,20 +1,12 @@ """Dependency injector container utils.""" -import copy as _copy -import types import six +from dependency_injector.providers import deepcopy from dependency_injector.errors import Error -if six.PY2: # pragma: no cover - _copy._deepcopy_dispatch[types.MethodType] = \ - lambda obj, memo: type(obj)(obj.im_func, - _copy.deepcopy(obj.im_self, memo), - obj.im_class) - - def is_container(instance): """Check if instance is container instance. @@ -74,11 +66,6 @@ def copy(container): return _decorator -def deepcopy(instance, memo=None): - """Make full copy of instance.""" - return _copy.deepcopy(instance, memo) - - def _check_provider_type(cls, provider): if not isinstance(provider, cls.provider_type): raise Error('{0} can contain only {1} ' diff --git a/src/dependency_injector/providers/__init__.py b/src/dependency_injector/providers/__init__.py index 688e87eb..fb4d475a 100644 --- a/src/dependency_injector/providers/__init__.py +++ b/src/dependency_injector/providers/__init__.py @@ -2,46 +2,51 @@ from .base import ( Provider, - Delegate, Object, + Delegate, ExternalDependency, OverridingContext, - override, ) -from .callable import ( +from .callables import ( Callable, DelegatedCallable, ) -from .creational import ( +from .factories import ( Factory, DelegatedFactory, +) +from .singletons import ( + BaseSingleton, + Singleton, DelegatedSingleton, + + ThreadSafeSingleton, + DelegatedThreadSafeSingleton, + ThreadLocalSingleton, DelegatedThreadLocalSingleton, ) -from .utils import ( - GLOBAL_LOCK, - is_provider, - ensure_is_provider, - is_delegated, - represent_provider, -) from .injections import ( Injection, PositionalInjection, NamedInjection, ) +from .utils import ( + is_provider, + ensure_is_provider, + is_delegated, + represent_provider, + deepcopy, +) __all__ = ( 'Provider', - 'Delegate', 'Object', + 'Delegate', 'ExternalDependency', - 'OverridingContext', - 'override', 'Callable', 'DelegatedCallable', @@ -49,19 +54,24 @@ __all__ = ( 'Factory', 'DelegatedFactory', + 'BaseSingleton', + 'Singleton', 'DelegatedSingleton', + 'ThreadSafeSingleton', + 'DelegatedThreadSafeSingleton', + 'ThreadLocalSingleton', 'DelegatedThreadLocalSingleton', - 'GLOBAL_LOCK', + 'Injection', + 'PositionalInjection', + 'NamedInjection', + 'is_provider', 'ensure_is_provider', 'is_delegated', 'represent_provider', - - 'Injection', - 'PositionalInjection', - 'NamedInjection', + 'deepcopy', ) diff --git a/src/dependency_injector/providers/base.pxd b/src/dependency_injector/providers/base.pxd new file mode 100644 index 00000000..29f895c3 --- /dev/null +++ b/src/dependency_injector/providers/base.pxd @@ -0,0 +1,31 @@ +"""Dependency injector base providers. + +Powered by Cython. +""" + + +cdef class Provider(object): + cdef tuple __overridden + cdef int __overridden_len + + cpdef object _provide(self, tuple args, dict kwargs) + cpdef object _call_last_overriding(self, tuple args, dict kwargs) + + +cdef class Object(Provider): + cdef object __provides + + cpdef object _provide(self, tuple args, dict kwargs) + + +cdef class Delegate(Object): + pass + + +cdef class ExternalDependency(Provider): + cdef type __instance_of + + +cdef class OverridingContext(object): + cdef Provider __overridden + cdef Provider __overriding diff --git a/src/dependency_injector/providers/base.py b/src/dependency_injector/providers/base.pyx similarity index 58% rename from src/dependency_injector/providers/base.py rename to src/dependency_injector/providers/base.pyx index c46fba97..1d072a5a 100644 --- a/src/dependency_injector/providers/base.py +++ b/src/dependency_injector/providers/base.pyx @@ -1,17 +1,22 @@ -"""Dependency injector base providers.""" +"""Dependency injector base providers. -import six +Powered by Cython. +""" + +cimport cython from dependency_injector.errors import Error -from .utils import ( + +from .utils cimport ( + CLASS_TYPES, is_provider, ensure_is_provider, represent_provider, + deepcopy, ) -@six.python_2_unicode_compatible -class Provider(object): +cdef class Provider(object): """Base provider class. :py:class:`Provider` is callable (implements ``__call__`` method). Every @@ -59,38 +64,53 @@ class Provider(object): """ __IS_PROVIDER__ = True - __OPTIMIZED_CALLS__ = True - __slots__ = ('overridden', 'provide', '__call__') def __init__(self): """Initializer.""" - self.overridden = tuple() + self.__overridden = tuple() + self.__overridden_len = 0 super(Provider, self).__init__() - # Enable __call__() / _provide() optimization - if self.__class__.__OPTIMIZED_CALLS__: - self.__call__ = self.provide = self._provide - def _provide(self, *args, **kwargs): - """Providing strategy implementation. + def __call__(self, *args, **kwargs): + """Return provided object. - Abstract protected method that implements providing strategy of - particular provider. Current method is called every time when not - overridden provider is called. Need to be overridden in subclasses. + Callable interface implementation. """ - raise NotImplementedError() + if self.__overridden_len != 0: + return self._call_last_overriding(args, kwargs) + return self._provide(args, kwargs) - def _call_last_overriding(self, *args, **kwargs): - """Call last overriding provider and return result.""" - return (self.overridden[-1](*args, **kwargs) - if self.overridden - else None) + def __deepcopy__(self, memo): + """Create and return full copy of provider.""" + copied = memo.get(id(self)) + if copied is not None: + return copied - def provide_injection(self): - """Injection strategy implementation. + copied = self.__class__() - :rtype: object + for overriding_provider in self.overridden: + copied.override(deepcopy(overriding_provider, memo)) + + return copied + + def __str__(self): + """Return string representation of provider. + + :rtype: str """ - return self.provide() + return represent_provider(provider=self, provides=None) + + def __repr__(self): + """Return string representation of provider. + + :rtype: str + """ + return self.__str__() + + @property + def overridden(self): + """Return tuple of overriding providers.""" + return self.__overridden def override(self, provider): """Override provider with another provider. @@ -100,8 +120,8 @@ class Provider(object): :raise: :py:exc:`dependency_injector.errors.Error` - :return: Overriding provider. - :rtype: :py:class:`Provider` + :return: Overriding context. + :rtype: :py:class:`OverridingContext` """ if provider is self: raise Error('Provider {0} could not be overridden ' @@ -110,14 +130,13 @@ class Provider(object): if not is_provider(provider): provider = Object(provider) - self.overridden += (ensure_is_provider(provider),) - - # Disable __call__() / _provide() optimization - if self.__class__.__OPTIMIZED_CALLS__: - self.__call__ = self.provide = self._call_last_overriding + self.__overridden += (provider,) + self.__overridden_len += 1 return OverridingContext(self, provider) + @cython.boundscheck(False) + @cython.wraparound(False) def reset_last_overriding(self): """Reset last overriding provider. @@ -126,26 +145,19 @@ class Provider(object): :rtype: None """ - if not self.overridden: + if self.__overridden_len == 0: raise Error('Provider {0} is not overridden'.format(str(self))) - self.overridden = self.overridden[:-1] - - if not self.overridden: - # Enable __call__() / _provide() optimization - if self.__class__.__OPTIMIZED_CALLS__: - self.__call__ = self.provide = self._provide + self.__overridden = self.__overridden[:self.__overridden_len - 1] + self.__overridden_len -= 1 def reset_override(self): """Reset all overriding providers. :rtype: None """ - self.overridden = tuple() - - # Enable __call__() / _provide() optimization - if self.__class__.__OPTIMIZED_CALLS__: - self.__call__ = self.provide = self._provide + self.__overridden = tuple() + self.__overridden_len = 0 def delegate(self): """Return provider's delegate. @@ -154,73 +166,25 @@ class Provider(object): """ return Delegate(self) - def __str__(self): - """Return string representation of provider. + cpdef object _provide(self, tuple args, dict kwargs): + """Providing strategy implementation. - :rtype: str + Abstract protected method that implements providing strategy of + particular provider. Current method is called every time when not + overridden provider is called. Need to be overridden in subclasses. """ - return represent_provider(provider=self, provides=None) + raise NotImplementedError() - __repr__ = __str__ + @cython.boundscheck(False) + @cython.wraparound(False) + cpdef object _call_last_overriding(self, tuple args, dict kwargs): + """Call last overriding provider and return result.""" + return self.__overridden[self.__overridden_len - 1](*args, + **kwargs) -@six.python_2_unicode_compatible -class Delegate(Provider): - """:py:class:`Delegate` provider delegates another provider. - - .. code-block:: python - - provider = Factory(object) - delegate = Delegate(provider) - - delegated = delegate() - - assert provider is delegated - - .. py:attribute:: delegated - - Delegated provider. - - :type: :py:class:`Provider` - """ - - __slots__ = ('delegated',) - - def __init__(self, delegated): - """Initializer. - - :provider delegated: Delegated provider. - :type delegated: :py:class:`Provider` - """ - self.delegated = ensure_is_provider(delegated) - super(Delegate, self).__init__() - - def _provide(self, *args, **kwargs): - """Return provided instance. - - :param args: Tuple of context positional arguments. - :type args: tuple[object] - - :param kwargs: Dictionary of context keyword arguments. - :type kwargs: dict[str, object] - - :rtype: object - """ - return self.delegated - - def __str__(self): - """Return string representation of provider. - - :rtype: str - """ - return represent_provider(provider=self, provides=self.delegated) - - __repr__ = __str__ - - -@six.python_2_unicode_compatible -class Object(Provider): - """:py:class:`Object` provider returns provided instance "as is". +cdef class Object(Provider): + """Object provider returns provided instance "as is". .. py:attribute:: provides @@ -229,18 +193,43 @@ class Object(Provider): :type: object """ - __slots__ = ('provides',) - def __init__(self, provides): """Initializer. :param provides: Value that have to be provided. :type provides: object """ - self.provides = provides + self.__provides = provides super(Object, self).__init__() - def _provide(self, *args, **kwargs): + def __deepcopy__(self, memo): + """Create and return full copy of provider.""" + copied = memo.get(id(self)) + if copied is not None: + return copied + + copied = self.__class__(deepcopy(self.__provides, memo)) + + for overriding_provider in self.overridden: + copied.override(deepcopy(overriding_provider, memo)) + + return copied + + def __str__(self): + """Return string representation of provider. + + :rtype: str + """ + return represent_provider(provider=self, provides=self.__provides) + + def __repr__(self): + """Return string representation of provider. + + :rtype: str + """ + return self.__str__() + + cpdef object _provide(self, tuple args, dict kwargs): """Return provided instance. :param args: Tuple of context positional arguments. @@ -251,20 +240,29 @@ class Object(Provider): :rtype: object """ - return self.provides + return self.__provides - def __str__(self): - """Return string representation of provider. - :rtype: str +cdef class Delegate(Object): + """Delegate provider returns provider "as is". + + .. py:attribute:: provides + + Value that have to be provided. + + :type: object + """ + + def __init__(self, provides): + """Initializer. + + :param provides: Value that have to be provided. + :type provides: object """ - return represent_provider(provider=self, provides=self.provides) - - __repr__ = __str__ + super(Delegate, self).__init__(ensure_is_provider(provides)) -@six.python_2_unicode_compatible -class ExternalDependency(Provider): +cdef class ExternalDependency(Provider): """:py:class:`ExternalDependency` provider describes dependency interface. This provider is used for description of dependency interface. That might @@ -286,18 +284,24 @@ class ExternalDependency(Provider): :type: type """ - __OPTIMIZED_CALLS__ = False - __slots__ = ('instance_of',) - - def __init__(self, instance_of): + def __init__(self, type instance_of): """Initializer.""" - if not isinstance(instance_of, six.class_types): - raise Error('ExternalDependency provider expects to get class, ' + - 'got {0} instead'.format(str(instance_of))) - self.instance_of = instance_of - self.provide = self.__call__ + self.__instance_of = instance_of super(ExternalDependency, self).__init__() + def __deepcopy__(self, memo): + """Create and return full copy of provider.""" + copied = memo.get(id(self)) + if copied is not None: + return copied + + copied = self.__class__(self.__instance_of) + + for overriding_provider in self.overridden: + copied.override(deepcopy(overriding_provider, memo)) + + return copied + def __call__(self, *args, **kwargs): """Return provided instance. @@ -311,10 +315,12 @@ class ExternalDependency(Provider): :rtype: object """ - if not self.overridden: + cdef object instance + + if self.__overridden_len == 0: raise Error('Dependency is not defined') - instance = self._call_last_overriding(*args, **kwargs) + instance = self._call_last_overriding(args, kwargs) if not isinstance(instance, self.instance_of): raise Error('{0} is not an '.format(instance) + @@ -322,6 +328,25 @@ class ExternalDependency(Provider): return instance + def __str__(self): + """Return string representation of provider. + + :rtype: str + """ + return represent_provider(provider=self, provides=self.__instance_of) + + def __repr__(self): + """Return string representation of provider. + + :rtype: str + """ + return self.__str__() + + @property + def instance_of(self): + """Return class of required dependency.""" + return self.__instance_of + def provided_by(self, provider): """Set external dependency provider. @@ -332,17 +357,8 @@ class ExternalDependency(Provider): """ return self.override(provider) - def __str__(self): - """Return string representation of provider. - :rtype: str - """ - return represent_provider(provider=self, provides=self.instance_of) - - __repr__ = __str__ - - -class OverridingContext(object): +cdef class OverridingContext(object): """Provider overriding context. :py:class:`OverridingContext` is used by :py:meth:`Provider.override` for @@ -356,7 +372,7 @@ class OverridingContext(object): assert not provider.overridden """ - def __init__(self, overridden, overriding): + def __init__(self, Provider overridden, Provider overriding): """Initializer. :param overridden: Overridden provider. @@ -365,52 +381,14 @@ class OverridingContext(object): :param overriding: Overriding provider. :type overriding: :py:class:`Provider` """ - self.overridden = overridden - self.overriding = overriding + self.__overridden = overridden + self.__overriding = overriding + super(OverridingContext, self).__init__() def __enter__(self): """Do nothing.""" - return self.overriding + return self.__overriding def __exit__(self, *_): """Exit overriding context.""" - self.overridden.reset_last_overriding() - - -def override(overridden): - """Decorator for overriding providers. - - This decorator overrides ``overridden`` provider by decorated one. - - .. code-block:: python - - @Factory - class SomeClass(object): - pass - - - @override(SomeClass) - @Factory - class ExtendedSomeClass(SomeClass.cls): - pass - - :param overridden: Provider that should be overridden. - :type overridden: :py:class:`Provider` - - :return: Overriding provider. - :rtype: :py:class:`Provider` - """ - def decorator(overriding): - overridden.override(overriding) - return overriding - return decorator - - -def _parse_positional_injections(args): - return tuple(arg if is_provider(arg) else Object(arg) - for arg in args) - - -def _parse_keyword_injections(kwargs): - return dict((name, arg if is_provider(arg) else Object(arg)) - for name, arg in six.iteritems(kwargs)) + self.__overridden.reset_last_overriding() diff --git a/src/dependency_injector/providers/callable.py b/src/dependency_injector/providers/callable.py deleted file mode 100644 index 50b042e5..00000000 --- a/src/dependency_injector/providers/callable.py +++ /dev/null @@ -1,127 +0,0 @@ -"""Dependency injector callable providers.""" - -import six - -from dependency_injector.providers.base import ( - Provider, - _parse_positional_injections, - _parse_keyword_injections, -) -from .utils import represent_provider -from dependency_injector.errors import Error - - -@six.python_2_unicode_compatible -class Callable(Provider): - r""":py:class:`Callable` provider calls wrapped callable on every call. - - :py:class:`Callable` supports positional and keyword argument injections: - - .. code-block:: python - - some_function = Callable(some_function, - 'positional_arg1', 'positional_arg2', - keyword_argument1=3, keyword_argument=4) - - # or - - some_function = Callable(some_function) \ - .add_args('positional_arg1', 'positional_arg2') \ - .add_kwargs(keyword_argument1=3, keyword_argument=4) - - # or - - some_function = Callable(some_function) - some_function.add_args('positional_arg1', 'positional_arg2') - some_function.add_kwargs(keyword_argument1=3, keyword_argument=4) - """ - - __slots__ = ('provides', 'args', 'kwargs') - - def __init__(self, provides, *args, **kwargs): - """Initializer. - - :param provides: Wrapped callable. - :type provides: callable - """ - if not callable(provides): - raise Error('Provider {0} expected to get callable, ' - 'got {0}'.format('.'.join((self.__class__.__module__, - self.__class__.__name__)), - provides)) - - self.provides = provides - - self.args = tuple() - self.kwargs = dict() - - self.add_args(*args) - self.add_kwargs(**kwargs) - - super(Callable, self).__init__() - - def add_args(self, *args): - """Add postional argument injections. - - :param args: Tuple of injections. - :type args: tuple - - :return: Reference ``self`` - """ - self.args += _parse_positional_injections(args) - return self - - def add_kwargs(self, **kwargs): - """Add keyword argument injections. - - :param kwargs: Dictionary of injections. - :type kwargs: dict - - :return: Reference ``self`` - """ - self.kwargs.update(_parse_keyword_injections(kwargs)) - return self - - def _provide(self, *args, **kwargs): - """Return provided instance. - - :param args: Tuple of context positional arguments. - :type args: tuple[object] - - :param kwargs: Dictionary of context keyword arguments. - :type kwargs: dict[str, object] - - :rtype: object - """ - if self.args: - args = tuple(arg.provide_injection() for arg in self.args) + args - - for name, arg in six.iteritems(self.kwargs): - if name not in kwargs: - kwargs[name] = arg.provide_injection() - - return self.provides(*args, **kwargs) - - def __str__(self): - """Return string representation of provider. - - :rtype: str - """ - return represent_provider(provider=self, provides=self.provides) - - __repr__ = __str__ - - -class DelegatedCallable(Callable): - """:py:class:`DelegatedCallable` is a delegated :py:class:`Callable`. - - :py:class:`DelegatedCallable` is a :py:class:`Callable`, that is injected - "as is". - """ - - def provide_injection(self): - """Injection strategy implementation. - - :rtype: object - """ - return self diff --git a/src/dependency_injector/providers/callables.pxd b/src/dependency_injector/providers/callables.pxd new file mode 100644 index 00000000..f529526b --- /dev/null +++ b/src/dependency_injector/providers/callables.pxd @@ -0,0 +1,42 @@ +"""Dependency injector callable providers. + +Powered by Cython. +""" + +from .base cimport Provider +from .injections cimport ( + PositionalInjection, + NamedInjection, + __provide_positional_args, + __provide_keyword_args, +) + + + +cdef class Callable(Provider): + cdef object __provides + + cdef tuple __args + cdef int __args_len + + cdef tuple __kwargs + cdef int __kwargs_len + + cpdef object _provide(self, tuple args, dict kwargs) + + cdef inline object __provide(self, tuple args, dict kwargs): + cdef tuple positional_args + cdef dict keyword_args + + positional_args = __provide_positional_args(args, + self.__args, + self.__args_len) + keyword_args = __provide_keyword_args(kwargs, + self.__kwargs, + self.__kwargs_len) + + return self.__provides(*positional_args, **keyword_args) + + +cdef class DelegatedCallable(Callable): + pass diff --git a/src/dependency_injector/providers/callables.pyx b/src/dependency_injector/providers/callables.pyx new file mode 100644 index 00000000..c3eef390 --- /dev/null +++ b/src/dependency_injector/providers/callables.pyx @@ -0,0 +1,208 @@ +"""Dependency injector callable providers. + +Powered by Cython. +""" + +from dependency_injector.errors import Error + +from .base cimport Provider +from .injections cimport ( + PositionalInjection, + NamedInjection, + parse_positional_injections, + parse_named_injections, +) +from .utils cimport ( + represent_provider, + deepcopy, +) + + +cdef class Callable(Provider): + r"""Callable provider calls wrapped callable on every call. + + Callable supports positional and keyword argument injections: + + .. code-block:: python + + some_function = Callable(some_function, + 'positional_arg1', 'positional_arg2', + keyword_argument1=3, keyword_argument=4) + + # or + + some_function = Callable(some_function) \ + .add_args('positional_arg1', 'positional_arg2') \ + .add_kwargs(keyword_argument1=3, keyword_argument=4) + + # or + + some_function = Callable(some_function) + some_function.add_args('positional_arg1', 'positional_arg2') + some_function.add_kwargs(keyword_argument1=3, keyword_argument=4) + """ + + def __init__(self, provides, *args, **kwargs): + """Initializer. + + :param provides: Wrapped callable. + :type provides: callable + + :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 not callable(provides): + raise Error('Provider {0} expected to get callable, ' + 'got {0}'.format('.'.join((self.__class__.__module__, + self.__class__.__name__)), + provides)) + self.__provides = provides + + self.__args = tuple() + self.__args_len = 0 + self.set_args(*args) + + self.__kwargs = tuple() + self.__kwargs_len = 0 + self.set_kwargs(**kwargs) + + super(Callable, self).__init__() + + def __deepcopy__(self, memo): + """Create and return full copy of provider.""" + copied = memo.get(id(self)) + if copied is not None: + return copied + + copied = self.__class__(self.provides, + *deepcopy(self.args, memo), + **deepcopy(self.kwargs, memo)) + + for overriding_provider in self.overridden: + copied.override(deepcopy(overriding_provider, memo)) + + return copied + + def __str__(self): + """Return string representation of provider. + + :rtype: str + """ + return represent_provider(provider=self, provides=self.__provides) + + @property + def provides(self): + """Return wrapped callable.""" + return self.__provides + + @property + def args(self): + """Return positional argument injections.""" + cdef int index + cdef PositionalInjection arg + cdef list args + + args = list() + for index in range(self.__args_len): + arg = self.__args[index] + args.append(arg.__value) + return tuple(args) + + def add_args(self, *args): + """Add postional argument injections. + + :param args: Tuple of injections. + :type args: tuple + + :return: Reference ``self`` + """ + self.__args += parse_positional_injections(args) + self.__args_len = len(self.__args) + return self + + def set_args(self, *args): + """Set postional argument injections. + + Existing positional argument injections are dropped. + + :param args: Tuple of injections. + :type args: tuple + + :return: Reference ``self`` + """ + self.__args = parse_positional_injections(args) + self.__args_len = len(self.__args) + return self + + def clear_args(self): + """Drop postional argument injections. + + :return: Reference ``self`` + """ + self.__args = tuple() + self.__args_len = len(self.__args) + return self + + @property + def kwargs(self): + """Return keyword argument injections.""" + cdef int index + cdef NamedInjection kwarg + cdef dict kwargs + + kwargs = dict() + for index in range(self.__kwargs_len): + kwarg = self.__kwargs[index] + kwargs[kwarg.__name] = kwarg.__value + return kwargs + + def add_kwargs(self, **kwargs): + """Add keyword argument injections. + + :param kwargs: Dictionary of injections. + :type kwargs: dict + + :return: Reference ``self`` + """ + self.__kwargs += parse_named_injections(kwargs) + self.__kwargs_len = len(self.__kwargs) + return self + + def set_kwargs(self, **kwargs): + """Set keyword argument injections. + + Existing keyword argument injections are dropped. + + :param kwargs: Dictionary of injections. + :type kwargs: dict + + :return: Reference ``self`` + """ + self.__kwargs = parse_named_injections(kwargs) + self.__kwargs_len = len(self.__kwargs) + return self + + def clear_kwargs(self): + """Drop keyword argument injections. + + :return: Reference ``self`` + """ + self.__kwargs = tuple() + self.__kwargs_len = len(self.__kwargs) + return self + + cpdef object _provide(self, tuple args, dict kwargs): + """Return result of provided callable's call.""" + return self.__provide(args, kwargs) + + +cdef class DelegatedCallable(Callable): + """Callable that is injected "as is". + + DelegatedCallable is a :py:class:`Callable`, that is injected "as is". + """ + + __IS_DELEGATED__ = True diff --git a/src/dependency_injector/providers/creational.py b/src/dependency_injector/providers/creational.py deleted file mode 100644 index c37fd786..00000000 --- a/src/dependency_injector/providers/creational.py +++ /dev/null @@ -1,342 +0,0 @@ -"""Dependency injector creational providers.""" - -import threading - -import six - -from dependency_injector.providers.callable import Callable -from dependency_injector.providers.base import _parse_keyword_injections -from .utils import GLOBAL_LOCK -from dependency_injector.errors import Error - - -class Factory(Callable): - r""":py:class:`Factory` provider creates new instance on every call. - - :py:class:`Factory` supports positional & keyword argument injections, - as well as attribute injections. - - Positional and keyword argument injections could be defined like this: - - .. code-block:: python - - factory = Factory(SomeClass, - 'positional_arg1', 'positional_arg2', - keyword_argument1=3, keyword_argument=4) - - # or - - factory = Factory(SomeClass) \ - .add_args('positional_arg1', 'positional_arg2') \ - .add_kwargs(keyword_argument1=3, keyword_argument=4) - - # or - - factory = Factory(SomeClass) - factory.add_args('positional_arg1', 'positional_arg2') - factory.add_kwargs(keyword_argument1=3, keyword_argument=4) - - - Attribute injections are defined by using :py:meth:`Factory.attributes`: - - .. code-block:: python - - factory = Factory(SomeClass) \ - .add_attributes(attribute1=1, attribute2=2) - - Retrieving of provided instance can be performed via calling - :py:class:`Factory` object: - - .. code-block:: python - - factory = Factory(SomeClass) - some_object = factory() - - .. py:attribute:: provided_type - - If provided type is defined, provider checks that providing class is - its subclass. - - :type: type | None - - .. py:attribute:: cls - - Class that provides object. - Alias for :py:attr:`provides`. - - :type: type - """ - - provided_type = None - - __slots__ = ('cls', 'attributes') - - def __init__(self, provides, *args, **kwargs): - """Initializer. - - :param provides: Class or other callable that provides object - for creation. - :type provides: type | callable - """ - 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.attributes = dict() - - super(Factory, self).__init__(provides, *args, **kwargs) - - self.cls = self.provides - - def add_attributes(self, **kwargs): - """Add attribute injections. - - :param kwargs: Dictionary of injections. - :type kwargs: dict - - :return: Reference ``self`` - """ - self.attributes.update(_parse_keyword_injections(kwargs)) - return self - - def _provide(self, *args, **kwargs): - """Return provided instance. - - :param args: Tuple of context positional arguments. - :type args: tuple[object] - - :param kwargs: Dictionary of context keyword arguments. - :type kwargs: dict[str, object] - - :rtype: object - """ - instance = super(Factory, self)._provide(*args, **kwargs) - - for name, arg in six.iteritems(self.attributes): - setattr(instance, name, arg.provide_injection()) - - return instance - - -class DelegatedFactory(Factory): - """:py:class:`Factory` that is injected "as is". - - .. py:attribute:: provided_type - - If provided type is defined, provider checks that providing class is - its subclass. - - :type: type | None - - .. py:attribute:: cls - - Class that provides object. - Alias for :py:attr:`provides`. - - :type: type - """ - - def provide_injection(self): - """Injection strategy implementation. - - :rtype: object - """ - return self - - -class Singleton(Factory): - """:py:class:`Singleton` provider returns same instance on every call. - - :py:class:`Singleton` provider creates instance once and returns it on - every call. :py:class:`Singleton` extends :py:class:`Factory`, so, please - follow :py:class:`Factory` documentation for getting familiar with - injections syntax. - - :py:class:`Singleton` is thread-safe and could be used in multithreading - environment without any negative impact. - - Retrieving of provided instance can be performed via calling - :py:class:`Singleton` object: - - .. code-block:: python - - singleton = Singleton(SomeClass) - some_object = singleton() - - .. py:attribute:: provided_type - - If provided type is defined, provider checks that providing class is - its subclass. - - :type: type | None - - .. py:attribute:: cls - - Class that provides object. - Alias for :py:attr:`provides`. - - :type: type - """ - - __slots__ = ('instance',) - - def __init__(self, provides, *args, **kwargs): - """Initializer. - - :param provides: Class or other callable that provides object - for creation. - :type provides: type | callable - """ - self.instance = None - super(Singleton, self).__init__(provides, *args, **kwargs) - - def reset(self): - """Reset cached instance, if any. - - :rtype: None - """ - self.instance = None - - def _provide(self, *args, **kwargs): - """Return provided instance. - - :param args: Tuple of context positional arguments. - :type args: tuple[object] - - :param kwargs: Dictionary of context keyword arguments. - :type kwargs: dict[str, object] - - :rtype: object - """ - with GLOBAL_LOCK: - if self.instance is None: - self.instance = super(Singleton, self)._provide(*args, - **kwargs) - return self.instance - - -class DelegatedSingleton(Singleton): - """:py:class:`Singleton` that is injected "as is". - - .. py:attribute:: provided_type - - If provided type is defined, provider checks that providing class is - its subclass. - - :type: type | None - - .. py:attribute:: cls - - Class that provides object. - Alias for :py:attr:`provides`. - - :type: type - """ - - def provide_injection(self): - """Injection strategy implementation. - - :rtype: object - """ - return self - - -class ThreadLocalSingleton(Factory): - """:py:class:`ThreadLocalSingleton` is singleton based on thread locals. - - :py:class:`ThreadLocalSingleton` provider creates instance once for each - thread and returns it on every call. :py:class:`ThreadLocalSingleton` - extends :py:class:`Factory`, so, please follow :py:class:`Factory` - documentation for getting familiar with injections syntax. - - :py:class:`ThreadLocalSingleton` is thread-safe and could be used in - multithreading environment without any negative impact. - - Retrieving of provided instance can be performed via calling - :py:class:`ThreadLocalSingleton` object: - - .. code-block:: python - - singleton = ThreadLocalSingleton(SomeClass) - some_object = singleton() - - .. py:attribute:: provided_type - - If provided type is defined, provider checks that providing class is - its subclass. - - :type: type | None - - .. py:attribute:: cls - - Class that provides object. - Alias for :py:attr:`provides`. - - :type: type - """ - - __slots__ = ('local_storage',) - - def __init__(self, provides, *args, **kwargs): - """Initializer. - - :param provides: Class or other callable that provides object - for creation. - :type provides: type | callable - """ - self.local_storage = threading.local() - super(ThreadLocalSingleton, self).__init__(provides, *args, **kwargs) - - def reset(self): - """Reset cached instance, if any. - - :rtype: None - """ - self.local_storage.instance = None - - def _provide(self, *args, **kwargs): - """Return provided instance. - - :param args: Tuple of context positional arguments. - :type args: tuple[object] - - :param kwargs: Dictionary of context keyword arguments. - :type kwargs: dict[str, object] - - :rtype: object - """ - try: - instance = self.local_storage.instance - except AttributeError: - instance = super(ThreadLocalSingleton, self)._provide(*args, - **kwargs) - self.local_storage.instance = instance - finally: - return instance - - -class DelegatedThreadLocalSingleton(ThreadLocalSingleton): - """:py:class:`ThreadLocalSingleton` that is injected "as is". - - .. py:attribute:: provided_type - - If provided type is defined, provider checks that providing class is - its subclass. - - :type: type | None - - .. py:attribute:: cls - - Class that provides object. - Alias for :py:attr:`provides`. - - :type: type - """ - - def provide_injection(self): - """Injection strategy implementation. - - :rtype: object - """ - return self diff --git a/src/dependency_injector/providers/factories.pxd b/src/dependency_injector/providers/factories.pxd new file mode 100644 index 00000000..89399e17 --- /dev/null +++ b/src/dependency_injector/providers/factories.pxd @@ -0,0 +1,33 @@ +"""Dependency injector factory providers. + +Powered by Cython. +""" + +from .base cimport Provider +from .callables cimport Callable +from .injections cimport __inject_attributes + + +cdef class Factory(Provider): + cdef Callable __instantiator + + cdef tuple __attributes + cdef int __attributes_len + + cpdef object _provide(self, tuple args, dict kwargs) + + cdef inline object __provide(self, tuple args, dict kwargs): + cdef object instance + + instance = self.__instantiator.__provide(args, kwargs) + + if self.__attributes_len > 0: + __inject_attributes(instance, + self.__attributes, + self.__attributes_len) + + return instance + + +cdef class DelegatedFactory(Factory): + pass diff --git a/src/dependency_injector/providers/factories.pyx b/src/dependency_injector/providers/factories.pyx new file mode 100644 index 00000000..d5c3f4c3 --- /dev/null +++ b/src/dependency_injector/providers/factories.pyx @@ -0,0 +1,270 @@ +"""Dependency injector factory providers. + +Powered by Cython. +""" + +from dependency_injector.errors import Error + +from .base cimport Provider +from .callables cimport Callable +from .injections cimport ( + NamedInjection, + parse_named_injections, +) +from .utils cimport ( + represent_provider, + deepcopy, +) + + +cdef class Factory(Provider): + r"""Factory provider creates new instance on every call. + + :py:class:`Factory` supports positional & keyword argument injections, + as well as attribute injections. + + Positional and keyword argument injections could be defined like this: + + .. code-block:: python + + factory = Factory(SomeClass, + 'positional_arg1', 'positional_arg2', + keyword_argument1=3, keyword_argument=4) + + # or + + factory = Factory(SomeClass) \ + .add_args('positional_arg1', 'positional_arg2') \ + .add_kwargs(keyword_argument1=3, keyword_argument=4) + + # or + + factory = Factory(SomeClass) + factory.add_args('positional_arg1', 'positional_arg2') + factory.add_kwargs(keyword_argument1=3, keyword_argument=4) + + + Attribute injections are defined by using :py:meth:`Factory.attributes`: + + .. code-block:: python + + factory = Factory(SomeClass) \ + .add_attributes(attribute1=1, attribute2=2) + + Retrieving of provided instance can be performed via calling + :py:class:`Factory` object: + + .. code-block:: python + + factory = Factory(SomeClass) + some_object = factory() + + .. py:attribute:: provided_type + + If provided type is defined, provider checks that providing class is + its subclass. + + :type: type | None + """ + + 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 = Callable(provides, *args, **kwargs) + + self.__attributes = tuple() + self.__attributes_len = 0 + + super(Factory, self).__init__() + + def __deepcopy__(self, memo): + """Create and return full copy of provider.""" + copied = memo.get(id(self)) + if copied is not None: + return copied + + copied = self.__class__(self.cls, + *deepcopy(self.args, memo), + **deepcopy(self.kwargs, memo)) + copied.set_attributes(**deepcopy(self.attributes, memo)) + + for overriding_provider in self.overridden: + copied.override(deepcopy(overriding_provider, memo)) + + return copied + + def __str__(self): + """Return string representation of provider. + + :rtype: str + """ + return represent_provider(provider=self, + provides=self.__instantiator.provides) + + @property + def cls(self): + """Return provided type.""" + return self.__instantiator.provides + + @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.""" + cdef int index + cdef NamedInjection attribute + cdef dict attributes + + attributes = dict() + for index in range(self.__attributes_len): + attribute = self.__attributes[index] + attributes[attribute.__name] = attribute.__value + return attributes + + def add_attributes(self, **kwargs): + """Add attribute injections. + + :param args: Tuple of injections. + :type args: tuple + + :return: Reference ``self`` + """ + self.__attributes += parse_named_injections(kwargs) + self.__attributes_len = len(self.__attributes) + 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.__attributes = parse_named_injections(kwargs) + self.__attributes_len = len(self.__attributes) + return self + + def clear_attributes(self): + """Drop attribute injections. + + :return: Reference ``self`` + """ + self.__attributes = tuple() + self.__attributes_len = len(self.__attributes) + return self + + cpdef object _provide(self, tuple args, dict kwargs): + """Return new instance.""" + return self.__provide(args, kwargs) + + +cdef class DelegatedFactory(Factory): + """Factory that is injected "as is". + + .. py:attribute:: provided_type + + If provided type is defined, provider checks that providing class is + its subclass. + + :type: type | None + + .. py:attribute:: cls + + Class that provides object. + Alias for :py:attr:`provides`. + + :type: type + """ + + __IS_DELEGATED__ = True diff --git a/src/dependency_injector/providers/injections.pxd b/src/dependency_injector/providers/injections.pxd index 38682403..dcf1ef93 100644 --- a/src/dependency_injector/providers/injections.pxd +++ b/src/dependency_injector/providers/injections.pxd @@ -6,7 +6,7 @@ Powered by Cython. cimport cython -cdef class Injection: +cdef class Injection(object): pass @@ -40,9 +40,9 @@ cdef class NamedInjection(Injection): @cython.boundscheck(False) @cython.wraparound(False) -cdef inline tuple __provide_positional_args(tuple inj_args, - int inj_args_len, - tuple args): +cdef inline tuple __provide_positional_args(tuple args, + tuple inj_args, + int inj_args_len): cdef int index cdef list positional_args cdef PositionalInjection injection @@ -53,29 +53,48 @@ cdef inline tuple __provide_positional_args(tuple inj_args, positional_args = list() for index in range(inj_args_len): injection = inj_args[index] - positional_args.append(injection.get_value()) + positional_args.append(injection.__get_value()) positional_args.extend(args) - return positional_args + return tuple(positional_args) @cython.boundscheck(False) @cython.wraparound(False) -cdef inline dict __provide_keyword_args(tuple inj_kwargs, - int inj_kwargs_len, - dict kwargs): +cdef inline dict __provide_keyword_args(dict kwargs, + tuple inj_kwargs, + int inj_kwargs_len): cdef int index + cdef object name cdef NamedInjection kw_injection - if inj_kwargs_len == 0: - return kwargs - - for index in range(inj_kwargs_len): - kw_injection = inj_kwargs[index] - kwargs[kw_injection.get_name()] = kw_injection.get_value() + if len(kwargs) == 0: + for index in range(inj_kwargs_len): + kw_injection = inj_kwargs[index] + name = kw_injection.__get_name() + kwargs[name] = kw_injection.__get_value() + else: + for index in range(inj_kwargs_len): + kw_injection = inj_kwargs[index] + name = kw_injection.__get_name() + if name not in kwargs: + kwargs[name] = kw_injection.__get_value() return kwargs +@cython.boundscheck(False) +@cython.wraparound(False) +cdef inline object __inject_attributes(object instance, + tuple attributes, + int attributes_len): + cdef NamedInjection attr_injection + for index in range(attributes_len): + attr_injection = attributes[index] + setattr(instance, + attr_injection.__get_name(), + attr_injection.__get_value()) + + cpdef tuple parse_positional_injections(tuple args) cpdef tuple parse_named_injections(dict kwargs) diff --git a/src/dependency_injector/providers/injections.pyx b/src/dependency_injector/providers/injections.pyx index 39e41593..41248901 100644 --- a/src/dependency_injector/providers/injections.pyx +++ b/src/dependency_injector/providers/injections.pyx @@ -8,10 +8,11 @@ cimport cython from .utils cimport ( is_provider, is_delegated, + deepcopy, ) -cdef class Injection: +cdef class Injection(object): """Abstract injection class.""" @@ -23,7 +24,16 @@ cdef class PositionalInjection(Injection): self.__value = value self.__is_provider = is_provider(value) self.__is_delegated = is_delegated(value) - self.__call = self.__is_provider == 1 and self.__is_delegated == 0 + self.__call = (self.__is_provider == 1 and + self.__is_delegated == 0) + super(PositionalInjection, self).__init__() + + def __deepcopy__(self, memo): + """Create and return full copy of provider.""" + copied = memo.get(id(self)) + if copied is not None: + return copied + return self.__class__(deepcopy(self.__value, memo)) def get_value(self): """Return injection value.""" @@ -43,7 +53,17 @@ cdef class NamedInjection(Injection): self.__value = value self.__is_provider = is_provider(value) self.__is_delegated = is_delegated(value) - self.__call = self.__is_provider == 1 and self.__is_delegated == 0 + self.__call = (self.__is_provider == 1 and + self.__is_delegated == 0) + super(NamedInjection, self).__init__() + + def __deepcopy__(self, memo): + """Create and return full copy of provider.""" + copied = memo.get(id(self)) + if copied is not None: + return copied + return self.__class__(deepcopy(self.__name, memo), + deepcopy(self.__value, memo)) def get_name(self): """Return injection value.""" diff --git a/src/dependency_injector/providers/singletons.pxd b/src/dependency_injector/providers/singletons.pxd new file mode 100644 index 00000000..25f0b452 --- /dev/null +++ b/src/dependency_injector/providers/singletons.pxd @@ -0,0 +1,64 @@ +"""Dependency injector singleton providers. + +Powered by Cython. +""" + +from .base cimport Provider +from .factories cimport Factory + + +cdef class BaseSingleton(Provider): + cdef Factory __instantiator + + +cdef class Singleton(BaseSingleton): + 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(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): + pass + + +cdef class ThreadLocalSingleton(BaseSingleton): + 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): + pass diff --git a/src/dependency_injector/providers/singletons.pyx b/src/dependency_injector/providers/singletons.pyx new file mode 100644 index 00000000..44480e33 --- /dev/null +++ b/src/dependency_injector/providers/singletons.pyx @@ -0,0 +1,393 @@ +"""Dependency injector singleton providers. + +Powered by Cython. +""" + +import threading + +from dependency_injector.errors import Error + +from .base cimport Provider +from .factories cimport Factory +from .utils cimport ( + represent_provider, + deepcopy, +) + + +GLOBAL_LOCK = threading.RLock() +"""Global reentrant lock. + +:type: :py:class:`threading.RLock` +""" + + +cdef class BaseSingleton(Provider): + """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(BaseSingleton, self).__init__() + + def __str__(self): + """Return string representation of provider. + + :rtype: str + """ + return represent_provider(provider=self, + provides=self.__instantiator.cls) + + def __deepcopy__(self, memo): + """Create and return full copy of provider.""" + copied = memo.get(id(self)) + if copied is not None: + return copied + + copied = self.__class__(self.cls, + *deepcopy(self.args, memo), + **deepcopy(self.kwargs, memo)) + copied.set_attributes(**deepcopy(self.attributes, memo)) + + for overriding_provider in self.overridden: + copied.override(deepcopy(overriding_provider, memo)) + + return copied + + @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): + """Singleton provider returns same instance on every call. + + :py:class:`Singleton` provider creates instance once and returns it on + every call. :py:class:`Singleton` extends :py:class:`Factory`, so, please + follow :py:class:`Factory` documentation for getting familiar with + injections syntax. + + Retrieving of provided instance can be performed via calling + :py:class:`Singleton` object: + + .. code-block:: python + + singleton = Singleton(SomeClass) + some_object = singleton() + + .. py:attribute:: provided_type + + If provided type is defined, provider checks that providing class is + its subclass. + + :type: type | None + + .. py:attribute:: cls + + Class that provides object. + Alias for :py:attr:`provides`. + + :type: type + """ + + 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): + """Delegated singleton is injected "as is". + + .. py:attribute:: provided_type + + If provided type is defined, provider checks that providing class is + its subclass. + + :type: type | None + + .. py:attribute:: cls + + Class that provides object. + Alias for :py:attr:`provides`. + + :type: type + """ + + __IS_DELEGATED__ = True + + +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): + """Delegated thread-safe singleton is injected "as is". + + .. py:attribute:: provided_type + + If provided type is defined, provider checks that providing class is + its subclass. + + :type: type | None + + .. py:attribute:: cls + + Class that provides object. + Alias for :py:attr:`provides`. + + :type: type + """ + + __IS_DELEGATED__ = True + + +cdef class ThreadLocalSingleton(BaseSingleton): + """Thread-local singleton provides single objects in scope of thread. + + .. py:attribute:: provided_type + + If provided type is defined, provider checks that providing class is + its subclass. + + :type: type | None + + .. py:attribute:: cls + + Class that provides object. + Alias for :py:attr:`provides`. + + :type: type + """ + + 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): + """Delegated thread-local singleton is injected "as is". + + .. py:attribute:: provided_type + + If provided type is defined, provider checks that providing class is + its subclass. + + :type: type | None + + .. py:attribute:: cls + + Class that provides object. + Alias for :py:attr:`provides`. + + :type: type + """ + + __IS_DELEGATED__ = True 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 58fa0299..84faf5ba 100644 --- a/src/dependency_injector/providers/utils.pxd +++ b/src/dependency_injector/providers/utils.pxd @@ -3,7 +3,11 @@ Powered by Cython. """ +cdef public object CLASS_TYPES + + cpdef bint is_provider(object instance) cpdef object ensure_is_provider(object instance) cpdef bint is_delegated(object instance) cpdef str represent_provider(object provider, object provides) +cpdef object deepcopy(object instance, dict memo=*) diff --git a/src/dependency_injector/providers/utils.pyx b/src/dependency_injector/providers/utils.pyx index 5d008cd5..1940ffc6 100644 --- a/src/dependency_injector/providers/utils.pyx +++ b/src/dependency_injector/providers/utils.pyx @@ -3,23 +3,23 @@ Powered by Cython. """ +import copy import sys import types - import threading from dependency_injector.errors import Error -GLOBAL_LOCK = threading.RLock() -"""Global reentrant lock. - -:type: :py:class:`threading.RLock` -""" if sys.version_info[0] == 3: # pragma: no cover - _CLASS_TYPES = (type,) + CLASS_TYPES = (type,) else: # pragma: no cover - _CLASS_TYPES = (type, types.ClassType) + CLASS_TYPES = (type, types.ClassType) + + copy._deepcopy_dispatch[types.MethodType] = \ + lambda obj, memo: type(obj)(obj.im_func, + copy.deepcopy(obj.im_self, memo), + obj.im_class) cpdef bint is_provider(object instance): @@ -30,7 +30,7 @@ cpdef bint is_provider(object instance): :rtype: bool """ - return (not isinstance(instance, _CLASS_TYPES) and + return (not isinstance(instance, CLASS_TYPES) and getattr(instance, '__IS_PROVIDER__', False) is True) @@ -59,7 +59,7 @@ cpdef bint is_delegated(object instance): :rtype: bool """ - return (not isinstance(instance, _CLASS_TYPES) and + return (not isinstance(instance, CLASS_TYPES) and getattr(instance, '__IS_DELEGATED__', False) is True) @@ -80,3 +80,7 @@ cpdef str represent_provider(object provider, object provides): provider.__class__.__name__)), provides=repr(provides) if provides is not None else '', address=hex(id(provider))) + +cpdef object deepcopy(object instance, dict memo=None): + """Return full copy of provider or container with providers.""" + return copy.deepcopy(instance, memo) 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_base.py b/tests/unit/providers/test_base.py index 68e284fe..e7ca8167 100644 --- a/tests/unit/providers/test_base.py +++ b/tests/unit/providers/test_base.py @@ -80,23 +80,113 @@ class ProviderTests(unittest.TestCase): self.assertEqual(self.provider.overridden, tuple()) + def test_deepcopy(self): + provider = providers.Provider() + + provider_copy = providers.deepcopy(provider) + + self.assertIsNot(provider, provider_copy) + self.assertIsInstance(provider, providers.Provider) + + def test_deepcopy_from_memo(self): + provider = providers.Provider() + provider_copy_memo = providers.Provider() + + provider_copy = providers.deepcopy( + provider, memo={id(provider): provider_copy_memo}) + + self.assertIs(provider_copy, provider_copy_memo) + + def test_deepcopy_overridden(self): + provider = providers.Provider() + overriding_provider = providers.Provider() + + provider.override(overriding_provider) + + provider_copy = providers.deepcopy(provider) + overriding_provider_copy = provider_copy.overridden[0] + + self.assertIsNot(provider, provider_copy) + self.assertIsInstance(provider, providers.Provider) + + self.assertIsNot(overriding_provider, overriding_provider_copy) + self.assertIsInstance(overriding_provider_copy, providers.Provider) + def test_repr(self): self.assertEqual(repr(self.provider), ''.format(hex(id(self.provider)))) +class ObjectProviderTests(unittest.TestCase): + + def test_is_provider(self): + self.assertTrue(providers.is_provider(providers.Object(object()))) + + def test_call_object_provider(self): + obj = object() + self.assertIs(providers.Object(obj)(), obj) + + def test_call_overridden_object_provider(self): + obj1 = object() + obj2 = object() + provider = providers.Object(obj1) + provider.override(providers.Object(obj2)) + self.assertIs(provider(), obj2) + + def test_deepcopy(self): + provider = providers.Object(1) + + provider_copy = providers.deepcopy(provider) + + self.assertIsNot(provider, provider_copy) + self.assertIsInstance(provider, providers.Object) + + def test_deepcopy_from_memo(self): + provider = providers.Object(1) + provider_copy_memo = providers.Provider() + + provider_copy = providers.deepcopy( + provider, memo={id(provider): provider_copy_memo}) + + self.assertIs(provider_copy, provider_copy_memo) + + def test_deepcopy_overridden(self): + provider = providers.Object(1) + overriding_provider = providers.Provider() + + provider.override(overriding_provider) + + provider_copy = providers.deepcopy(provider) + overriding_provider_copy = provider_copy.overridden[0] + + self.assertIsNot(provider, provider_copy) + self.assertIsInstance(provider, providers.Object) + + self.assertIsNot(overriding_provider, overriding_provider_copy) + self.assertIsInstance(overriding_provider_copy, providers.Provider) + + def test_repr(self): + some_object = object() + provider = providers.Object(some_object) + self.assertEqual(repr(provider), + ''.format( + repr(some_object), + hex(id(provider)))) + + class DelegateTests(unittest.TestCase): def setUp(self): self.delegated = providers.Provider() - self.delegate = providers.Delegate(delegated=self.delegated) + self.delegate = providers.Delegate(self.delegated) def test_is_provider(self): self.assertTrue(providers.is_provider(self.delegate)) def test_init_with_not_provider(self): - self.assertRaises(errors.Error, providers.Delegate, delegated=object()) + self.assertRaises(errors.Error, providers.Delegate, object()) def test_call(self): delegated1 = self.delegate() @@ -119,7 +209,7 @@ class ExternalDependencyTests(unittest.TestCase): self.provider = providers.ExternalDependency(instance_of=list) def test_init_with_not_class(self): - self.assertRaises(errors.Error, providers.ExternalDependency, object()) + self.assertRaises(TypeError, providers.ExternalDependency, object()) def test_is_provider(self): self.assertTrue(providers.is_provider(self.provider)) @@ -135,6 +225,38 @@ class ExternalDependencyTests(unittest.TestCase): def test_call_not_overridden(self): self.assertRaises(errors.Error, self.provider) + def test_deepcopy(self): + provider = providers.ExternalDependency(int) + + provider_copy = providers.deepcopy(provider) + + self.assertIsNot(provider, provider_copy) + self.assertIsInstance(provider, providers.ExternalDependency) + + def test_deepcopy_from_memo(self): + provider = providers.ExternalDependency(int) + provider_copy_memo = providers.Provider() + + provider_copy = providers.deepcopy( + provider, memo={id(provider): provider_copy_memo}) + + self.assertIs(provider_copy, provider_copy_memo) + + def test_deepcopy_overridden(self): + provider = providers.ExternalDependency(int) + overriding_provider = providers.Provider() + + provider.override(overriding_provider) + + provider_copy = providers.deepcopy(provider) + overriding_provider_copy = provider_copy.overridden[0] + + self.assertIsNot(provider, provider_copy) + self.assertIsInstance(provider, providers.ExternalDependency) + + self.assertIsNot(overriding_provider, overriding_provider_copy) + self.assertIsInstance(overriding_provider_copy, providers.Provider) + def test_repr(self): self.assertEqual(repr(self.provider), ''.format( - repr(self.example), - hex(id(provider)))) - - -class DelegatedCallableTests(unittest.TestCase): - - def test_inheritance(self): - self.assertIsInstance(providers.DelegatedCallable(len), - providers.Callable) - - def test_is_provider(self): - self.assertTrue( - providers.is_provider(providers.DelegatedCallable(len))) - - def test_is_delegated_provider(self): - provider = providers.DelegatedCallable(len) - self.assertIs(provider.provide_injection(), provider) diff --git a/tests/unit/providers/test_callables.py b/tests/unit/providers/test_callables.py new file mode 100644 index 00000000..717b8397 --- /dev/null +++ b/tests/unit/providers/test_callables.py @@ -0,0 +1,191 @@ +"""Dependency injector callable providers unit tests.""" + +import unittest2 as unittest + +from dependency_injector import ( + providers, + errors, +) + + +class CallableTests(unittest.TestCase): + + def example(self, arg1, arg2, arg3, arg4): + return arg1, arg2, arg3, arg4 + + def test_init_with_callable(self): + self.assertTrue(providers.Callable(self.example)) + + def test_init_with_not_callable(self): + self.assertRaises(errors.Error, providers.Callable, 123) + + def test_call(self): + provider = providers.Callable(lambda: True) + self.assertTrue(provider()) + + def test_call_with_positional_args(self): + provider = providers.Callable(self.example, + 1, 2, 3, 4) + self.assertTupleEqual(provider(), (1, 2, 3, 4)) + + def test_call_with_keyword_args(self): + provider = providers.Callable(self.example, + arg1=1, arg2=2, arg3=3, arg4=4) + self.assertTupleEqual(provider(), (1, 2, 3, 4)) + + def test_call_with_positional_and_keyword_args(self): + provider = providers.Callable(self.example, + 1, 2, + arg3=3, arg4=4) + self.assertTupleEqual(provider(), (1, 2, 3, 4)) + + def test_call_with_context_args(self): + provider = providers.Callable(self.example, 1, 2) + self.assertTupleEqual(provider(3, 4), (1, 2, 3, 4)) + + def test_call_with_context_kwargs(self): + provider = providers.Callable(self.example, arg1=1) + self.assertTupleEqual(provider(arg2=2, arg3=3, arg4=4), (1, 2, 3, 4)) + + def test_call_with_context_args_and_kwargs(self): + provider = providers.Callable(self.example, 1) + self.assertTupleEqual(provider(2, arg3=3, arg4=4), (1, 2, 3, 4)) + + def test_fluent_interface(self): + provider = providers.Singleton(self.example) \ + .add_args(1, 2) \ + .add_kwargs(arg3=3, arg4=4) + + self.assertTupleEqual(provider(), (1, 2, 3, 4)) + + def test_set_args(self): + provider = providers.Callable(self.example) \ + .add_args(1, 2) \ + .set_args(3, 4) + self.assertEqual(provider.args, tuple([3, 4])) + + def test_set_kwargs(self): + provider = providers.Callable(self.example) \ + .add_kwargs(init_arg3=3, init_arg4=4) \ + .set_kwargs(init_arg3=4, init_arg4=5) + self.assertEqual(provider.kwargs, dict(init_arg3=4, init_arg4=5)) + + def test_clear_args(self): + provider = providers.Callable(self.example) \ + .add_args(1, 2) \ + .clear_args() + self.assertEqual(provider.args, tuple()) + + def test_clear_kwargs(self): + provider = providers.Callable(self.example) \ + .add_kwargs(init_arg3=3, init_arg4=4) \ + .clear_kwargs() + self.assertEqual(provider.kwargs, dict()) + + def test_call_overridden(self): + provider = providers.Callable(self.example) + + provider.override(providers.Object((4, 3, 2, 1))) + provider.override(providers.Object((1, 2, 3, 4))) + + self.assertTupleEqual(provider(), (1, 2, 3, 4)) + + def test_deepcopy(self): + provider = providers.Callable(self.example) + + provider_copy = providers.deepcopy(provider) + + self.assertIsNot(provider, provider_copy) + self.assertIs(provider.provides, provider_copy.provides) + self.assertIsInstance(provider, providers.Callable) + + def test_deepcopy_from_memo(self): + provider = providers.Callable(self.example) + provider_copy_memo = providers.Callable(self.example) + + provider_copy = providers.deepcopy( + provider, memo={id(provider): provider_copy_memo}) + + self.assertIs(provider_copy, provider_copy_memo) + + def test_deepcopy_args(self): + provider = providers.Callable(self.example) + dependent_provider1 = providers.Callable(list) + dependent_provider2 = providers.Callable(dict) + + provider.add_args(dependent_provider1, dependent_provider2) + + provider_copy = providers.deepcopy(provider) + dependent_provider_copy1 = provider_copy.args[0] + dependent_provider_copy2 = provider_copy.args[1] + + self.assertNotEqual(provider.args, provider_copy.args) + + self.assertIs(dependent_provider1.provides, + dependent_provider_copy1.provides) + self.assertIsNot(dependent_provider1, dependent_provider_copy1) + + self.assertIs(dependent_provider2.provides, + dependent_provider_copy2.provides) + self.assertIsNot(dependent_provider2, dependent_provider_copy2) + + def test_deepcopy_kwargs(self): + provider = providers.Callable(self.example) + dependent_provider1 = providers.Callable(list) + dependent_provider2 = providers.Callable(dict) + + provider.add_kwargs(a1=dependent_provider1, a2=dependent_provider2) + + provider_copy = providers.deepcopy(provider) + dependent_provider_copy1 = provider_copy.kwargs['a1'] + dependent_provider_copy2 = provider_copy.kwargs['a2'] + + self.assertNotEqual(provider.kwargs, provider_copy.kwargs) + + self.assertIs(dependent_provider1.provides, + dependent_provider_copy1.provides) + self.assertIsNot(dependent_provider1, dependent_provider_copy1) + + self.assertIs(dependent_provider2.provides, + dependent_provider_copy2.provides) + self.assertIsNot(dependent_provider2, dependent_provider_copy2) + + def test_deepcopy_overridden(self): + provider = providers.Callable(self.example) + object_provider = providers.Object(object()) + + provider.override(object_provider) + + provider_copy = providers.deepcopy(provider) + object_provider_copy = provider_copy.overridden[0] + + self.assertIsNot(provider, provider_copy) + self.assertIs(provider.provides, provider_copy.provides) + self.assertIsInstance(provider, providers.Callable) + + self.assertIsNot(object_provider, object_provider_copy) + self.assertIsInstance(object_provider_copy, providers.Object) + + def test_repr(self): + provider = providers.Callable(self.example) + + self.assertEqual(repr(provider), + ''.format( + repr(self.example), + hex(id(provider)))) + + +class DelegatedCallableTests(unittest.TestCase): + + def test_inheritance(self): + self.assertIsInstance(providers.DelegatedCallable(len), + providers.Callable) + + def test_is_provider(self): + self.assertTrue( + providers.is_provider(providers.DelegatedCallable(len))) + + def test_is_delegated_provider(self): + provider = providers.DelegatedCallable(len) + self.assertTrue(providers.is_delegated(provider)) diff --git a/tests/unit/providers/test_creational.py b/tests/unit/providers/test_creational.py deleted file mode 100644 index ed13215e..00000000 --- a/tests/unit/providers/test_creational.py +++ /dev/null @@ -1,662 +0,0 @@ -"""Dependency injector creational providers unit tests.""" - -import unittest2 as unittest - -from dependency_injector import ( - providers, - errors, -) - - -class Example(object): - - def __init__(self, init_arg1=None, init_arg2=None, init_arg3=None, - init_arg4=None): - self.init_arg1 = init_arg1 - self.init_arg2 = init_arg2 - self.init_arg3 = init_arg3 - self.init_arg4 = init_arg4 - - self.attribute1 = None - self.attribute2 = None - - -class FactoryTests(unittest.TestCase): - - def test_is_provider(self): - self.assertTrue(providers.is_provider(providers.Factory(Example))) - - def test_init_with_callable(self): - self.assertTrue(providers.Factory(credits)) - - def test_init_with_not_callable(self): - self.assertRaises(errors.Error, providers.Factory, 123) - - def test_init_with_valid_provided_type(self): - class ExampleProvider(providers.Factory): - provided_type = Example - - example_provider = ExampleProvider(Example, 1, 2) - - self.assertIsInstance(example_provider(), Example) - - def test_init_with_valid_provided_subtype(self): - class ExampleProvider(providers.Factory): - provided_type = Example - - class NewExampe(Example): - pass - - example_provider = ExampleProvider(NewExampe, 1, 2) - - self.assertIsInstance(example_provider(), NewExampe) - - def test_init_with_invalid_provided_type(self): - class ExampleProvider(providers.Factory): - provided_type = Example - - with self.assertRaises(errors.Error): - ExampleProvider(list) - - def test_call(self): - provider = providers.Factory(Example) - - instance1 = provider() - instance2 = provider() - - self.assertIsNot(instance1, instance2) - self.assertIsInstance(instance1, Example) - self.assertIsInstance(instance2, Example) - - def test_call_with_init_positional_args(self): - provider = providers.Factory(Example, 'i1', 'i2') - - instance1 = provider() - instance2 = provider() - - self.assertEqual(instance1.init_arg1, 'i1') - self.assertEqual(instance1.init_arg2, 'i2') - - self.assertEqual(instance2.init_arg1, 'i1') - self.assertEqual(instance2.init_arg2, 'i2') - - self.assertIsNot(instance1, instance2) - self.assertIsInstance(instance1, Example) - self.assertIsInstance(instance2, Example) - - def test_call_with_init_keyword_args(self): - provider = providers.Factory(Example, init_arg1='i1', init_arg2='i2') - - instance1 = provider() - instance2 = provider() - - self.assertEqual(instance1.init_arg1, 'i1') - self.assertEqual(instance1.init_arg2, 'i2') - - self.assertEqual(instance2.init_arg1, 'i1') - self.assertEqual(instance2.init_arg2, 'i2') - - self.assertIsNot(instance1, instance2) - self.assertIsInstance(instance1, Example) - self.assertIsInstance(instance2, Example) - - def test_call_with_init_positional_and_keyword_args(self): - provider = providers.Factory(Example, 'i1', init_arg2='i2') - - instance1 = provider() - instance2 = provider() - - self.assertEqual(instance1.init_arg1, 'i1') - self.assertEqual(instance1.init_arg2, 'i2') - - self.assertEqual(instance2.init_arg1, 'i1') - self.assertEqual(instance2.init_arg2, 'i2') - - self.assertIsNot(instance1, instance2) - self.assertIsInstance(instance1, Example) - self.assertIsInstance(instance2, Example) - - def test_call_with_attributes(self): - provider = providers.Factory(Example) - provider.add_attributes(attribute1='a1', attribute2='a2') - - instance1 = provider() - instance2 = provider() - - self.assertEqual(instance1.attribute1, 'a1') - self.assertEqual(instance1.attribute2, 'a2') - - self.assertEqual(instance2.attribute1, 'a1') - self.assertEqual(instance2.attribute2, 'a2') - - self.assertIsNot(instance1, instance2) - self.assertIsInstance(instance1, Example) - self.assertIsInstance(instance2, Example) - - def test_call_with_context_args(self): - provider = providers.Factory(Example, 11, 22) - - instance = provider(33, 44) - - self.assertEqual(instance.init_arg1, 11) - self.assertEqual(instance.init_arg2, 22) - self.assertEqual(instance.init_arg3, 33) - self.assertEqual(instance.init_arg4, 44) - - def test_call_with_context_kwargs(self): - provider = providers.Factory(Example, init_arg1=1) - - instance1 = provider(init_arg2=22) - self.assertEqual(instance1.init_arg1, 1) - self.assertEqual(instance1.init_arg2, 22) - - instance2 = provider(init_arg1=11, init_arg2=22) - self.assertEqual(instance2.init_arg1, 11) - self.assertEqual(instance2.init_arg2, 22) - - def test_call_with_context_args_and_kwargs(self): - provider = providers.Factory(Example, 11) - - instance = provider(22, init_arg3=33, init_arg4=44) - - self.assertEqual(instance.init_arg1, 11) - self.assertEqual(instance.init_arg2, 22) - self.assertEqual(instance.init_arg3, 33) - self.assertEqual(instance.init_arg4, 44) - - def test_fluent_interface(self): - provider = providers.Factory(Example) \ - .add_args(1, 2) \ - .add_kwargs(init_arg3=3, init_arg4=4) \ - .add_attributes(attribute1=5, attribute2=6) - - instance = provider() - - self.assertEqual(instance.init_arg1, 1) - self.assertEqual(instance.init_arg2, 2) - self.assertEqual(instance.init_arg3, 3) - self.assertEqual(instance.init_arg4, 4) - self.assertEqual(instance.attribute1, 5) - self.assertEqual(instance.attribute2, 6) - - def test_call_overridden(self): - provider = providers.Factory(Example) - overriding_provider1 = providers.Factory(dict) - overriding_provider2 = providers.Factory(list) - - provider.override(overriding_provider1) - provider.override(overriding_provider2) - - instance1 = provider() - instance2 = provider() - - self.assertIsNot(instance1, instance2) - self.assertIsInstance(instance1, list) - self.assertIsInstance(instance2, list) - - def test_repr(self): - provider = providers.Factory(Example) - - self.assertEqual(repr(provider), - ''.format( - repr(Example), - hex(id(provider)))) - - -class DelegatedFactoryTests(unittest.TestCase): - - def test_inheritance(self): - self.assertIsInstance(providers.DelegatedFactory(object), - providers.Factory) - - def test_is_provider(self): - self.assertTrue( - providers.is_provider(providers.DelegatedFactory(object))) - - def test_is_delegated_provider(self): - provider = providers.DelegatedFactory(object) - self.assertIs(provider.provide_injection(), provider) - - -class SingletonTests(unittest.TestCase): - - def test_is_provider(self): - self.assertTrue(providers.is_provider(providers.Singleton(Example))) - - def test_init_with_callable(self): - self.assertTrue(providers.Singleton(credits)) - - def test_init_with_not_callable(self): - self.assertRaises(errors.Error, providers.Singleton, 123) - - def test_init_with_valid_provided_type(self): - class ExampleProvider(providers.Singleton): - provided_type = Example - - example_provider = ExampleProvider(Example, 1, 2) - - self.assertIsInstance(example_provider(), Example) - - def test_init_with_valid_provided_subtype(self): - class ExampleProvider(providers.Singleton): - provided_type = Example - - class NewExampe(Example): - pass - - example_provider = ExampleProvider(NewExampe, 1, 2) - - self.assertIsInstance(example_provider(), NewExampe) - - def test_init_with_invalid_provided_type(self): - class ExampleProvider(providers.Singleton): - provided_type = Example - - with self.assertRaises(errors.Error): - ExampleProvider(list) - - def test_call(self): - provider = providers.Singleton(Example) - - instance1 = provider() - instance2 = provider() - - self.assertIs(instance1, instance2) - self.assertIsInstance(instance1, Example) - self.assertIsInstance(instance2, Example) - - def test_call_with_init_positional_args(self): - provider = providers.Singleton(Example, 'i1', 'i2') - - instance1 = provider() - instance2 = provider() - - self.assertEqual(instance1.init_arg1, 'i1') - self.assertEqual(instance1.init_arg2, 'i2') - - self.assertEqual(instance2.init_arg1, 'i1') - self.assertEqual(instance2.init_arg2, 'i2') - - self.assertIs(instance1, instance2) - self.assertIsInstance(instance1, Example) - self.assertIsInstance(instance2, Example) - - def test_call_with_init_keyword_args(self): - provider = providers.Singleton(Example, init_arg1='i1', init_arg2='i2') - - instance1 = provider() - instance2 = provider() - - self.assertEqual(instance1.init_arg1, 'i1') - self.assertEqual(instance1.init_arg2, 'i2') - - self.assertEqual(instance2.init_arg1, 'i1') - self.assertEqual(instance2.init_arg2, 'i2') - - self.assertIs(instance1, instance2) - self.assertIsInstance(instance1, Example) - self.assertIsInstance(instance2, Example) - - def test_call_with_init_positional_and_keyword_args(self): - provider = providers.Singleton(Example, 'i1', init_arg2='i2') - - instance1 = provider() - instance2 = provider() - - self.assertEqual(instance1.init_arg1, 'i1') - self.assertEqual(instance1.init_arg2, 'i2') - - self.assertEqual(instance2.init_arg1, 'i1') - self.assertEqual(instance2.init_arg2, 'i2') - - self.assertIs(instance1, instance2) - self.assertIsInstance(instance1, Example) - self.assertIsInstance(instance2, Example) - - def test_call_with_attributes(self): - provider = providers.Singleton(Example) - provider.add_attributes(attribute1='a1', attribute2='a2') - - instance1 = provider() - instance2 = provider() - - self.assertEqual(instance1.attribute1, 'a1') - self.assertEqual(instance1.attribute2, 'a2') - - self.assertEqual(instance2.attribute1, 'a1') - self.assertEqual(instance2.attribute2, 'a2') - - self.assertIs(instance1, instance2) - self.assertIsInstance(instance1, Example) - self.assertIsInstance(instance2, Example) - - def test_call_with_context_args(self): - provider = providers.Singleton(Example) - - instance = provider(11, 22) - - self.assertEqual(instance.init_arg1, 11) - self.assertEqual(instance.init_arg2, 22) - - def test_call_with_context_kwargs(self): - provider = providers.Singleton(Example, init_arg1=1) - - instance1 = provider(init_arg2=22) - self.assertEqual(instance1.init_arg1, 1) - self.assertEqual(instance1.init_arg2, 22) - - # Instance is created earlier - instance1 = provider(init_arg1=11, init_arg2=22) - self.assertEqual(instance1.init_arg1, 1) - self.assertEqual(instance1.init_arg2, 22) - - def test_call_with_context_args_and_kwargs(self): - provider = providers.Singleton(Example, 11) - - instance = provider(22, init_arg3=33, init_arg4=44) - - self.assertEqual(instance.init_arg1, 11) - self.assertEqual(instance.init_arg2, 22) - self.assertEqual(instance.init_arg3, 33) - self.assertEqual(instance.init_arg4, 44) - - def test_fluent_interface(self): - provider = providers.Singleton(Example) \ - .add_args(1, 2) \ - .add_kwargs(init_arg3=3, init_arg4=4) \ - .add_attributes(attribute1=5, attribute2=6) - - instance = provider() - - self.assertEqual(instance.init_arg1, 1) - self.assertEqual(instance.init_arg2, 2) - self.assertEqual(instance.init_arg3, 3) - self.assertEqual(instance.init_arg4, 4) - self.assertEqual(instance.attribute1, 5) - self.assertEqual(instance.attribute2, 6) - - def test_call_overridden(self): - provider = providers.Singleton(Example) - overriding_provider1 = providers.Singleton(dict) - overriding_provider2 = providers.Singleton(object) - - provider.override(overriding_provider1) - provider.override(overriding_provider2) - - instance1 = provider() - instance2 = provider() - - self.assertIs(instance1, instance2) - self.assertIsInstance(instance1, object) - self.assertIsInstance(instance2, object) - - def test_reset(self): - provider = providers.Singleton(object) - - instance1 = provider() - self.assertIsInstance(instance1, object) - - provider.reset() - - instance2 = provider() - self.assertIsInstance(instance1, object) - - self.assertIsNot(instance1, instance2) - - def test_repr(self): - provider = providers.Singleton(Example) - - self.assertEqual(repr(provider), - ''.format( - repr(Example), - hex(id(provider)))) - - -class DelegatedSingletonTests(unittest.TestCase): - - def test_inheritance(self): - self.assertIsInstance(providers.DelegatedSingleton(object), - providers.Singleton) - - def test_is_provider(self): - self.assertTrue( - providers.is_provider(providers.DelegatedSingleton(object))) - - def test_is_delegated_provider(self): - provider = providers.DelegatedSingleton(object) - self.assertIs(provider.provide_injection(), provider) - - -class ThreadLocalSingletonTests(unittest.TestCase): - - def test_is_provider(self): - self.assertTrue( - providers.is_provider(providers.ThreadLocalSingleton(Example))) - - def test_init_with_callable(self): - self.assertTrue(providers.ThreadLocalSingleton(credits)) - - def test_init_with_not_callable(self): - self.assertRaises(errors.Error, providers.ThreadLocalSingleton, 123) - - def test_init_with_valid_provided_type(self): - class ExampleProvider(providers.ThreadLocalSingleton): - provided_type = Example - - example_provider = ExampleProvider(Example, 1, 2) - - self.assertIsInstance(example_provider(), Example) - - def test_init_with_valid_provided_subtype(self): - class ExampleProvider(providers.ThreadLocalSingleton): - provided_type = Example - - class NewExampe(Example): - pass - - example_provider = ExampleProvider(NewExampe, 1, 2) - - self.assertIsInstance(example_provider(), NewExampe) - - def test_init_with_invalid_provided_type(self): - class ExampleProvider(providers.ThreadLocalSingleton): - provided_type = Example - - with self.assertRaises(errors.Error): - ExampleProvider(list) - - def test_call(self): - provider = providers.ThreadLocalSingleton(Example) - - instance1 = provider() - instance2 = provider() - - self.assertIs(instance1, instance2) - self.assertIsInstance(instance1, Example) - self.assertIsInstance(instance2, Example) - - def test_call_with_init_positional_args(self): - provider = providers.ThreadLocalSingleton(Example, 'i1', 'i2') - - instance1 = provider() - instance2 = provider() - - self.assertEqual(instance1.init_arg1, 'i1') - self.assertEqual(instance1.init_arg2, 'i2') - - self.assertEqual(instance2.init_arg1, 'i1') - self.assertEqual(instance2.init_arg2, 'i2') - - self.assertIs(instance1, instance2) - self.assertIsInstance(instance1, Example) - self.assertIsInstance(instance2, Example) - - def test_call_with_init_keyword_args(self): - provider = providers.ThreadLocalSingleton(Example, - init_arg1='i1', - init_arg2='i2') - - instance1 = provider() - instance2 = provider() - - self.assertEqual(instance1.init_arg1, 'i1') - self.assertEqual(instance1.init_arg2, 'i2') - - self.assertEqual(instance2.init_arg1, 'i1') - self.assertEqual(instance2.init_arg2, 'i2') - - self.assertIs(instance1, instance2) - self.assertIsInstance(instance1, Example) - self.assertIsInstance(instance2, Example) - - def test_call_with_init_positional_and_keyword_args(self): - provider = providers.ThreadLocalSingleton(Example, - 'i1', - init_arg2='i2') - - instance1 = provider() - instance2 = provider() - - self.assertEqual(instance1.init_arg1, 'i1') - self.assertEqual(instance1.init_arg2, 'i2') - - self.assertEqual(instance2.init_arg1, 'i1') - self.assertEqual(instance2.init_arg2, 'i2') - - self.assertIs(instance1, instance2) - self.assertIsInstance(instance1, Example) - self.assertIsInstance(instance2, Example) - - def test_call_with_attributes(self): - provider = providers.ThreadLocalSingleton(Example) - provider.add_attributes(attribute1='a1', attribute2='a2') - - instance1 = provider() - instance2 = provider() - - self.assertEqual(instance1.attribute1, 'a1') - self.assertEqual(instance1.attribute2, 'a2') - - self.assertEqual(instance2.attribute1, 'a1') - self.assertEqual(instance2.attribute2, 'a2') - - self.assertIs(instance1, instance2) - self.assertIsInstance(instance1, Example) - self.assertIsInstance(instance2, Example) - - def test_call_with_context_args(self): - provider = providers.ThreadLocalSingleton(Example) - - instance = provider(11, 22) - - self.assertEqual(instance.init_arg1, 11) - self.assertEqual(instance.init_arg2, 22) - - def test_call_with_context_kwargs(self): - provider = providers.ThreadLocalSingleton(Example, init_arg1=1) - - instance1 = provider(init_arg2=22) - self.assertEqual(instance1.init_arg1, 1) - self.assertEqual(instance1.init_arg2, 22) - - # Instance is created earlier - instance1 = provider(init_arg1=11, init_arg2=22) - self.assertEqual(instance1.init_arg1, 1) - self.assertEqual(instance1.init_arg2, 22) - - def test_call_with_context_args_and_kwargs(self): - provider = providers.ThreadLocalSingleton(Example, 11) - - instance = provider(22, init_arg3=33, init_arg4=44) - - self.assertEqual(instance.init_arg1, 11) - self.assertEqual(instance.init_arg2, 22) - self.assertEqual(instance.init_arg3, 33) - self.assertEqual(instance.init_arg4, 44) - - def test_fluent_interface(self): - provider = providers.ThreadLocalSingleton(Example) \ - .add_args(1, 2) \ - .add_kwargs(init_arg3=3, init_arg4=4) \ - .add_attributes(attribute1=5, attribute2=6) - - instance = provider() - - self.assertEqual(instance.init_arg1, 1) - self.assertEqual(instance.init_arg2, 2) - self.assertEqual(instance.init_arg3, 3) - self.assertEqual(instance.init_arg4, 4) - self.assertEqual(instance.attribute1, 5) - self.assertEqual(instance.attribute2, 6) - - def test_call_overridden(self): - provider = providers.ThreadLocalSingleton(Example) - overriding_provider1 = providers.ThreadLocalSingleton(dict) - overriding_provider2 = providers.ThreadLocalSingleton(object) - - provider.override(overriding_provider1) - provider.override(overriding_provider2) - - instance1 = provider() - instance2 = provider() - - self.assertIs(instance1, instance2) - self.assertIsInstance(instance1, object) - self.assertIsInstance(instance2, object) - - def test_reset(self): - provider = providers.ThreadLocalSingleton(object) - - instance1 = provider() - self.assertIsInstance(instance1, object) - - provider.reset() - - instance2 = provider() - self.assertIsInstance(instance1, object) - - self.assertIsNot(instance1, instance2) - - def test_repr(self): - provider = providers.ThreadLocalSingleton(Example) - - self.assertEqual(repr(provider), - ''.format( - repr(Example), - hex(id(provider)))) - - -class DelegatedThreadLocalSingletonTests(unittest.TestCase): - - def test_inheritance(self): - self.assertIsInstance(providers.DelegatedThreadLocalSingleton(object), - providers.ThreadLocalSingleton) - - def test_is_provider(self): - self.assertTrue( - providers.is_provider( - providers.DelegatedThreadLocalSingleton(object))) - - def test_is_delegated_provider(self): - provider = providers.DelegatedThreadLocalSingleton(object) - self.assertIs(provider.provide_injection(), provider) - - -class FactoryAsDecoratorTests(unittest.TestCase): - - def test_decoration_and_overriding(self): - @providers.Factory - class AuthService(object): - pass - - @providers.override(AuthService) - @providers.Factory - class ExtAuthService(AuthService.cls): - pass - - auth_service = AuthService() - - self.assertIsInstance(auth_service, ExtAuthService.cls) diff --git a/tests/unit/providers/test_factories.py b/tests/unit/providers/test_factories.py new file mode 100644 index 00000000..42af696b --- /dev/null +++ b/tests/unit/providers/test_factories.py @@ -0,0 +1,346 @@ +"""Dependency injector factory providers unit tests.""" + +import unittest2 as unittest + +from dependency_injector import ( + providers, + errors, +) + + +class Example(object): + + def __init__(self, init_arg1=None, init_arg2=None, init_arg3=None, + init_arg4=None): + self.init_arg1 = init_arg1 + self.init_arg2 = init_arg2 + self.init_arg3 = init_arg3 + self.init_arg4 = init_arg4 + + self.attribute1 = None + self.attribute2 = None + + +class FactoryTests(unittest.TestCase): + + def test_is_provider(self): + self.assertTrue(providers.is_provider(providers.Factory(Example))) + + def test_init_with_callable(self): + self.assertTrue(providers.Factory(credits)) + + def test_init_with_not_callable(self): + self.assertRaises(errors.Error, providers.Factory, 123) + + def test_init_with_valid_provided_type(self): + class ExampleProvider(providers.Factory): + provided_type = Example + + example_provider = ExampleProvider(Example, 1, 2) + + self.assertIsInstance(example_provider(), Example) + + def test_init_with_valid_provided_subtype(self): + class ExampleProvider(providers.Factory): + provided_type = Example + + class NewExampe(Example): + pass + + example_provider = ExampleProvider(NewExampe, 1, 2) + + self.assertIsInstance(example_provider(), NewExampe) + + def test_init_with_invalid_provided_type(self): + class ExampleProvider(providers.Factory): + provided_type = Example + + with self.assertRaises(errors.Error): + ExampleProvider(list) + + def test_call(self): + provider = providers.Factory(Example) + + instance1 = provider() + instance2 = provider() + + self.assertIsNot(instance1, instance2) + self.assertIsInstance(instance1, Example) + self.assertIsInstance(instance2, Example) + + def test_call_with_init_positional_args(self): + provider = providers.Factory(Example, 'i1', 'i2') + + instance1 = provider() + instance2 = provider() + + self.assertEqual(instance1.init_arg1, 'i1') + self.assertEqual(instance1.init_arg2, 'i2') + + self.assertEqual(instance2.init_arg1, 'i1') + self.assertEqual(instance2.init_arg2, 'i2') + + self.assertIsNot(instance1, instance2) + self.assertIsInstance(instance1, Example) + self.assertIsInstance(instance2, Example) + + def test_call_with_init_keyword_args(self): + provider = providers.Factory(Example, init_arg1='i1', init_arg2='i2') + + instance1 = provider() + instance2 = provider() + + self.assertEqual(instance1.init_arg1, 'i1') + self.assertEqual(instance1.init_arg2, 'i2') + + self.assertEqual(instance2.init_arg1, 'i1') + self.assertEqual(instance2.init_arg2, 'i2') + + self.assertIsNot(instance1, instance2) + self.assertIsInstance(instance1, Example) + self.assertIsInstance(instance2, Example) + + def test_call_with_init_positional_and_keyword_args(self): + provider = providers.Factory(Example, 'i1', init_arg2='i2') + + instance1 = provider() + instance2 = provider() + + self.assertEqual(instance1.init_arg1, 'i1') + self.assertEqual(instance1.init_arg2, 'i2') + + self.assertEqual(instance2.init_arg1, 'i1') + self.assertEqual(instance2.init_arg2, 'i2') + + self.assertIsNot(instance1, instance2) + self.assertIsInstance(instance1, Example) + self.assertIsInstance(instance2, Example) + + def test_call_with_attributes(self): + provider = providers.Factory(Example) + provider.add_attributes(attribute1='a1', attribute2='a2') + + instance1 = provider() + instance2 = provider() + + self.assertEqual(instance1.attribute1, 'a1') + self.assertEqual(instance1.attribute2, 'a2') + + self.assertEqual(instance2.attribute1, 'a1') + self.assertEqual(instance2.attribute2, 'a2') + + self.assertIsNot(instance1, instance2) + self.assertIsInstance(instance1, Example) + self.assertIsInstance(instance2, Example) + + def test_call_with_context_args(self): + provider = providers.Factory(Example, 11, 22) + + instance = provider(33, 44) + + self.assertEqual(instance.init_arg1, 11) + self.assertEqual(instance.init_arg2, 22) + self.assertEqual(instance.init_arg3, 33) + self.assertEqual(instance.init_arg4, 44) + + def test_call_with_context_kwargs(self): + provider = providers.Factory(Example, init_arg1=1) + + instance1 = provider(init_arg2=22) + self.assertEqual(instance1.init_arg1, 1) + self.assertEqual(instance1.init_arg2, 22) + + instance2 = provider(init_arg1=11, init_arg2=22) + self.assertEqual(instance2.init_arg1, 11) + self.assertEqual(instance2.init_arg2, 22) + + def test_call_with_context_args_and_kwargs(self): + provider = providers.Factory(Example, 11) + + instance = provider(22, init_arg3=33, init_arg4=44) + + self.assertEqual(instance.init_arg1, 11) + self.assertEqual(instance.init_arg2, 22) + self.assertEqual(instance.init_arg3, 33) + self.assertEqual(instance.init_arg4, 44) + + def test_fluent_interface(self): + provider = providers.Factory(Example) \ + .add_args(1, 2) \ + .add_kwargs(init_arg3=3, init_arg4=4) \ + .add_attributes(attribute1=5, attribute2=6) + + instance = provider() + + self.assertEqual(instance.init_arg1, 1) + self.assertEqual(instance.init_arg2, 2) + self.assertEqual(instance.init_arg3, 3) + self.assertEqual(instance.init_arg4, 4) + self.assertEqual(instance.attribute1, 5) + self.assertEqual(instance.attribute2, 6) + + def test_set_args(self): + provider = providers.Factory(Example) \ + .add_args(1, 2) \ + .set_args(3, 4) + self.assertEqual(provider.args, tuple([3, 4])) + + def test_set_kwargs(self): + provider = providers.Factory(Example) \ + .add_kwargs(init_arg3=3, init_arg4=4) \ + .set_kwargs(init_arg3=4, init_arg4=5) + self.assertEqual(provider.kwargs, dict(init_arg3=4, init_arg4=5)) + + def test_set_attributes(self): + provider = providers.Factory(Example) \ + .add_attributes(attribute1=5, attribute2=6) \ + .set_attributes(attribute1=6, attribute2=7) + self.assertEqual(provider.attributes, dict(attribute1=6, attribute2=7)) + + def test_clear_args(self): + provider = providers.Factory(Example) \ + .add_args(1, 2) \ + .clear_args() + self.assertEqual(provider.args, tuple()) + + def test_clear_kwargs(self): + provider = providers.Factory(Example) \ + .add_kwargs(init_arg3=3, init_arg4=4) \ + .clear_kwargs() + self.assertEqual(provider.kwargs, dict()) + + def test_clear_attributes(self): + provider = providers.Factory(Example) \ + .add_attributes(attribute1=5, attribute2=6) \ + .clear_attributes() + self.assertEqual(provider.attributes, dict()) + + def test_call_overridden(self): + provider = providers.Factory(Example) + overriding_provider1 = providers.Factory(dict) + overriding_provider2 = providers.Factory(list) + + provider.override(overriding_provider1) + provider.override(overriding_provider2) + + instance1 = provider() + instance2 = provider() + + self.assertIsNot(instance1, instance2) + self.assertIsInstance(instance1, list) + self.assertIsInstance(instance2, list) + + def test_deepcopy(self): + provider = providers.Factory(Example) + + provider_copy = providers.deepcopy(provider) + + self.assertIsNot(provider, provider_copy) + self.assertIs(provider.cls, provider_copy.cls) + self.assertIsInstance(provider, providers.Factory) + + def test_deepcopy_from_memo(self): + provider = providers.Factory(Example) + provider_copy_memo = providers.Factory(Example) + + provider_copy = providers.deepcopy( + provider, memo={id(provider): provider_copy_memo}) + + self.assertIs(provider_copy, provider_copy_memo) + + def test_deepcopy_args(self): + provider = providers.Factory(Example) + dependent_provider1 = providers.Factory(list) + dependent_provider2 = providers.Factory(dict) + + provider.add_args(dependent_provider1, dependent_provider2) + + provider_copy = providers.deepcopy(provider) + dependent_provider_copy1 = provider_copy.args[0] + dependent_provider_copy2 = provider_copy.args[1] + + self.assertNotEqual(provider.args, provider_copy.args) + + self.assertIs(dependent_provider1.cls, dependent_provider_copy1.cls) + self.assertIsNot(dependent_provider1, dependent_provider_copy1) + + self.assertIs(dependent_provider2.cls, dependent_provider_copy2.cls) + self.assertIsNot(dependent_provider2, dependent_provider_copy2) + + def test_deepcopy_kwargs(self): + provider = providers.Factory(Example) + dependent_provider1 = providers.Factory(list) + dependent_provider2 = providers.Factory(dict) + + provider.add_kwargs(a1=dependent_provider1, a2=dependent_provider2) + + provider_copy = providers.deepcopy(provider) + dependent_provider_copy1 = provider_copy.kwargs['a1'] + dependent_provider_copy2 = provider_copy.kwargs['a2'] + + self.assertNotEqual(provider.kwargs, provider_copy.kwargs) + + self.assertIs(dependent_provider1.cls, dependent_provider_copy1.cls) + self.assertIsNot(dependent_provider1, dependent_provider_copy1) + + self.assertIs(dependent_provider2.cls, dependent_provider_copy2.cls) + self.assertIsNot(dependent_provider2, dependent_provider_copy2) + + def test_deepcopy_attributes(self): + provider = providers.Factory(Example) + dependent_provider1 = providers.Factory(list) + dependent_provider2 = providers.Factory(dict) + + provider.add_attributes(a1=dependent_provider1, a2=dependent_provider2) + + provider_copy = providers.deepcopy(provider) + dependent_provider_copy1 = provider_copy.attributes['a1'] + dependent_provider_copy2 = provider_copy.attributes['a2'] + + self.assertNotEqual(provider.attributes, provider_copy.attributes) + + self.assertIs(dependent_provider1.cls, dependent_provider_copy1.cls) + self.assertIsNot(dependent_provider1, dependent_provider_copy1) + + self.assertIs(dependent_provider2.cls, dependent_provider_copy2.cls) + self.assertIsNot(dependent_provider2, dependent_provider_copy2) + + def test_deepcopy_overridden(self): + provider = providers.Factory(Example) + object_provider = providers.Object(object()) + + provider.override(object_provider) + + provider_copy = providers.deepcopy(provider) + object_provider_copy = provider_copy.overridden[0] + + self.assertIsNot(provider, provider_copy) + self.assertIs(provider.cls, provider_copy.cls) + self.assertIsInstance(provider, providers.Factory) + + self.assertIsNot(object_provider, object_provider_copy) + self.assertIsInstance(object_provider_copy, providers.Object) + + def test_repr(self): + provider = providers.Factory(Example) + + self.assertEqual(repr(provider), + ''.format( + repr(Example), + hex(id(provider)))) + + +class DelegatedFactoryTests(unittest.TestCase): + + def test_inheritance(self): + self.assertIsInstance(providers.DelegatedFactory(object), + providers.Factory) + + def test_is_provider(self): + self.assertTrue( + providers.is_provider(providers.DelegatedFactory(object))) + + def test_is_delegated_provider(self): + self.assertTrue( + providers.is_delegated(providers.DelegatedFactory(object))) diff --git a/tests/unit/providers/test_injections.py b/tests/unit/providers/test_injections.py index ea2a4924..c1514abe 100644 --- a/tests/unit/providers/test_injections.py +++ b/tests/unit/providers/test_injections.py @@ -25,6 +25,33 @@ class PositionalInjectionTests(unittest.TestCase): self.assertIs(type(obj2), object) self.assertIsNot(obj1, obj2) + def test_get_original_value(self): + provider = providers.Factory(object) + injection = providers.PositionalInjection(provider) + self.assertIs(injection.get_original_value(), provider) + + def test_deepcopy(self): + provider = providers.Factory(object) + injection = providers.PositionalInjection(provider) + + injection_copy = providers.deepcopy(injection) + + self.assertIsNot(injection_copy, injection) + self.assertIsNot(injection_copy.get_original_value(), + injection.get_original_value()) + + def test_deepcopy_memo(self): + provider = providers.Factory(object) + injection = providers.PositionalInjection(provider) + injection_copy_orig = providers.PositionalInjection(provider) + + injection_copy = providers.deepcopy( + injection, {id(injection): injection_copy_orig}) + + self.assertIs(injection_copy, injection_copy_orig) + self.assertIs(injection_copy.get_original_value(), + injection.get_original_value()) + class NamedInjectionTests(unittest.TestCase): @@ -50,3 +77,30 @@ class NamedInjectionTests(unittest.TestCase): self.assertIs(type(obj1), object) self.assertIs(type(obj2), object) self.assertIsNot(obj1, obj2) + + def test_get_original_value(self): + provider = providers.Factory(object) + injection = providers.NamedInjection('name', provider) + self.assertIs(injection.get_original_value(), provider) + + def test_deepcopy(self): + provider = providers.Factory(object) + injection = providers.NamedInjection('name', provider) + + injection_copy = providers.deepcopy(injection) + + self.assertIsNot(injection_copy, injection) + self.assertIsNot(injection_copy.get_original_value(), + injection.get_original_value()) + + def test_deepcopy_memo(self): + provider = providers.Factory(object) + injection = providers.NamedInjection('name', provider) + injection_copy_orig = providers.NamedInjection('name', provider) + + injection_copy = providers.deepcopy( + injection, {id(injection): injection_copy_orig}) + + self.assertIs(injection_copy, injection_copy_orig) + self.assertIs(injection_copy.get_original_value(), + injection.get_original_value()) diff --git a/tests/unit/providers/test_singletons.py b/tests/unit/providers/test_singletons.py new file mode 100644 index 00000000..549e7590 --- /dev/null +++ b/tests/unit/providers/test_singletons.py @@ -0,0 +1,434 @@ +"""Dependency injector singleton providers unit tests.""" + +import unittest2 as unittest + +from dependency_injector import ( + providers, + errors, +) + + +class Example(object): + + def __init__(self, init_arg1=None, init_arg2=None, init_arg3=None, + init_arg4=None): + self.init_arg1 = init_arg1 + self.init_arg2 = init_arg2 + self.init_arg3 = init_arg3 + self.init_arg4 = init_arg4 + + self.attribute1 = None + self.attribute2 = None + + +class _BaseSingletonTestCase(object): + + singleton_cls = None + + def test_is_provider(self): + self.assertTrue(providers.is_provider(self.singleton_cls(Example))) + + def test_init_with_callable(self): + self.assertTrue(self.singleton_cls(credits)) + + def test_init_with_not_callable(self): + self.assertRaises(errors.Error, self.singleton_cls, 123) + + def test_init_with_valid_provided_type(self): + class ExampleProvider(self.singleton_cls): + provided_type = Example + + example_provider = ExampleProvider(Example, 1, 2) + + self.assertIsInstance(example_provider(), Example) + + def test_init_with_valid_provided_subtype(self): + class ExampleProvider(self.singleton_cls): + provided_type = Example + + class NewExampe(Example): + pass + + example_provider = ExampleProvider(NewExampe, 1, 2) + + self.assertIsInstance(example_provider(), NewExampe) + + def test_init_with_invalid_provided_type(self): + class ExampleProvider(self.singleton_cls): + provided_type = Example + + with self.assertRaises(errors.Error): + ExampleProvider(list) + + def test_call(self): + provider = self.singleton_cls(Example) + + instance1 = provider() + instance2 = provider() + + self.assertIs(instance1, instance2) + self.assertIsInstance(instance1, Example) + self.assertIsInstance(instance2, Example) + + def test_call_with_init_positional_args(self): + provider = self.singleton_cls(Example, 'i1', 'i2') + + instance1 = provider() + instance2 = provider() + + self.assertEqual(instance1.init_arg1, 'i1') + self.assertEqual(instance1.init_arg2, 'i2') + + self.assertEqual(instance2.init_arg1, 'i1') + self.assertEqual(instance2.init_arg2, 'i2') + + self.assertIs(instance1, instance2) + self.assertIsInstance(instance1, Example) + self.assertIsInstance(instance2, Example) + + def test_call_with_init_keyword_args(self): + provider = self.singleton_cls(Example, init_arg1='i1', init_arg2='i2') + + instance1 = provider() + instance2 = provider() + + self.assertEqual(instance1.init_arg1, 'i1') + self.assertEqual(instance1.init_arg2, 'i2') + + self.assertEqual(instance2.init_arg1, 'i1') + self.assertEqual(instance2.init_arg2, 'i2') + + self.assertIs(instance1, instance2) + self.assertIsInstance(instance1, Example) + self.assertIsInstance(instance2, Example) + + def test_call_with_init_positional_and_keyword_args(self): + provider = self.singleton_cls(Example, 'i1', init_arg2='i2') + + instance1 = provider() + instance2 = provider() + + self.assertEqual(instance1.init_arg1, 'i1') + self.assertEqual(instance1.init_arg2, 'i2') + + self.assertEqual(instance2.init_arg1, 'i1') + self.assertEqual(instance2.init_arg2, 'i2') + + self.assertIs(instance1, instance2) + self.assertIsInstance(instance1, Example) + self.assertIsInstance(instance2, Example) + + def test_call_with_attributes(self): + provider = self.singleton_cls(Example) + provider.add_attributes(attribute1='a1', attribute2='a2') + + instance1 = provider() + instance2 = provider() + + self.assertEqual(instance1.attribute1, 'a1') + self.assertEqual(instance1.attribute2, 'a2') + + self.assertEqual(instance2.attribute1, 'a1') + self.assertEqual(instance2.attribute2, 'a2') + + self.assertIs(instance1, instance2) + self.assertIsInstance(instance1, Example) + self.assertIsInstance(instance2, Example) + + def test_call_with_context_args(self): + provider = self.singleton_cls(Example) + + instance = provider(11, 22) + + self.assertEqual(instance.init_arg1, 11) + self.assertEqual(instance.init_arg2, 22) + + def test_call_with_context_kwargs(self): + provider = self.singleton_cls(Example, init_arg1=1) + + instance1 = provider(init_arg2=22) + self.assertEqual(instance1.init_arg1, 1) + self.assertEqual(instance1.init_arg2, 22) + + # Instance is created earlier + instance1 = provider(init_arg1=11, init_arg2=22) + self.assertEqual(instance1.init_arg1, 1) + self.assertEqual(instance1.init_arg2, 22) + + def test_call_with_context_args_and_kwargs(self): + provider = self.singleton_cls(Example, 11) + + instance = provider(22, init_arg3=33, init_arg4=44) + + self.assertEqual(instance.init_arg1, 11) + self.assertEqual(instance.init_arg2, 22) + self.assertEqual(instance.init_arg3, 33) + self.assertEqual(instance.init_arg4, 44) + + def test_fluent_interface(self): + provider = self.singleton_cls(Example) \ + .add_args(1, 2) \ + .add_kwargs(init_arg3=3, init_arg4=4) \ + .add_attributes(attribute1=5, attribute2=6) + + instance = provider() + + self.assertEqual(instance.init_arg1, 1) + self.assertEqual(instance.init_arg2, 2) + self.assertEqual(instance.init_arg3, 3) + self.assertEqual(instance.init_arg4, 4) + self.assertEqual(instance.attribute1, 5) + self.assertEqual(instance.attribute2, 6) + + def test_set_args(self): + provider = self.singleton_cls(Example) \ + .add_args(1, 2) \ + .set_args(3, 4) + self.assertEqual(provider.args, tuple([3, 4])) + + def test_set_kwargs(self): + provider = self.singleton_cls(Example) \ + .add_kwargs(init_arg3=3, init_arg4=4) \ + .set_kwargs(init_arg3=4, init_arg4=5) + self.assertEqual(provider.kwargs, dict(init_arg3=4, init_arg4=5)) + + def test_set_attributes(self): + provider = self.singleton_cls(Example) \ + .add_attributes(attribute1=5, attribute2=6) \ + .set_attributes(attribute1=6, attribute2=7) + self.assertEqual(provider.attributes, dict(attribute1=6, attribute2=7)) + + def test_clear_args(self): + provider = self.singleton_cls(Example) \ + .add_args(1, 2) \ + .clear_args() + self.assertEqual(provider.args, tuple()) + + def test_clear_kwargs(self): + provider = self.singleton_cls(Example) \ + .add_kwargs(init_arg3=3, init_arg4=4) \ + .clear_kwargs() + self.assertEqual(provider.kwargs, dict()) + + def test_clear_attributes(self): + provider = self.singleton_cls(Example) \ + .add_attributes(attribute1=5, attribute2=6) \ + .clear_attributes() + self.assertEqual(provider.attributes, dict()) + + def test_call_overridden(self): + provider = self.singleton_cls(Example) + overriding_provider1 = self.singleton_cls(dict) + overriding_provider2 = self.singleton_cls(list) + + provider.override(overriding_provider1) + provider.override(overriding_provider2) + + instance1 = provider() + instance2 = provider() + + self.assertIs(instance1, instance2) + self.assertIsInstance(instance1, list) + self.assertIsInstance(instance2, list) + + def test_deepcopy(self): + provider = self.singleton_cls(Example) + + provider_copy = providers.deepcopy(provider) + + self.assertIsNot(provider, provider_copy) + self.assertIs(provider.cls, provider_copy.cls) + self.assertIsInstance(provider, self.singleton_cls) + + def test_deepcopy_from_memo(self): + provider = self.singleton_cls(Example) + provider_copy_memo = self.singleton_cls(Example) + + provider_copy = providers.deepcopy( + provider, memo={id(provider): provider_copy_memo}) + + self.assertIs(provider_copy, provider_copy_memo) + + def test_deepcopy_args(self): + provider = self.singleton_cls(Example) + dependent_provider1 = self.singleton_cls(list) + dependent_provider2 = self.singleton_cls(dict) + + provider.add_args(dependent_provider1, dependent_provider2) + + provider_copy = providers.deepcopy(provider) + dependent_provider_copy1 = provider_copy.args[0] + dependent_provider_copy2 = provider_copy.args[1] + + self.assertNotEqual(provider.args, provider_copy.args) + + self.assertIs(dependent_provider1.cls, dependent_provider_copy1.cls) + self.assertIsNot(dependent_provider1, dependent_provider_copy1) + + self.assertIs(dependent_provider2.cls, dependent_provider_copy2.cls) + self.assertIsNot(dependent_provider2, dependent_provider_copy2) + + def test_deepcopy_kwargs(self): + provider = self.singleton_cls(Example) + dependent_provider1 = self.singleton_cls(list) + dependent_provider2 = self.singleton_cls(dict) + + provider.add_kwargs(a1=dependent_provider1, a2=dependent_provider2) + + provider_copy = providers.deepcopy(provider) + dependent_provider_copy1 = provider_copy.kwargs['a1'] + dependent_provider_copy2 = provider_copy.kwargs['a2'] + + self.assertNotEqual(provider.kwargs, provider_copy.kwargs) + + self.assertIs(dependent_provider1.cls, dependent_provider_copy1.cls) + self.assertIsNot(dependent_provider1, dependent_provider_copy1) + + self.assertIs(dependent_provider2.cls, dependent_provider_copy2.cls) + self.assertIsNot(dependent_provider2, dependent_provider_copy2) + + def test_deepcopy_attributes(self): + provider = self.singleton_cls(Example) + dependent_provider1 = self.singleton_cls(list) + dependent_provider2 = self.singleton_cls(dict) + + provider.add_attributes(a1=dependent_provider1, a2=dependent_provider2) + + provider_copy = providers.deepcopy(provider) + dependent_provider_copy1 = provider_copy.attributes['a1'] + dependent_provider_copy2 = provider_copy.attributes['a2'] + + self.assertNotEqual(provider.attributes, provider_copy.attributes) + + self.assertIs(dependent_provider1.cls, dependent_provider_copy1.cls) + self.assertIsNot(dependent_provider1, dependent_provider_copy1) + + self.assertIs(dependent_provider2.cls, dependent_provider_copy2.cls) + self.assertIsNot(dependent_provider2, dependent_provider_copy2) + + def test_deepcopy_overridden(self): + provider = self.singleton_cls(Example) + object_provider = providers.Object(object()) + + provider.override(object_provider) + + provider_copy = providers.deepcopy(provider) + object_provider_copy = provider_copy.overridden[0] + + self.assertIsNot(provider, provider_copy) + self.assertIs(provider.cls, provider_copy.cls) + self.assertIsInstance(provider, self.singleton_cls) + + self.assertIsNot(object_provider, object_provider_copy) + self.assertIsInstance(object_provider_copy, providers.Object) + + def test_reset(self): + provider = self.singleton_cls(object) + + instance1 = provider() + self.assertIsInstance(instance1, object) + + provider.reset() + + instance2 = provider() + self.assertIsInstance(instance1, object) + + self.assertIsNot(instance1, instance2) + + +class SingletonTests(_BaseSingletonTestCase, unittest.TestCase): + + singleton_cls = providers.Singleton + + def test_repr(self): + provider = self.singleton_cls(Example) + + self.assertEqual(repr(provider), + ''.format( + repr(Example), + hex(id(provider)))) + + +class DelegatedSingletonTests(_BaseSingletonTestCase, unittest.TestCase): + + singleton_cls = providers.DelegatedSingleton + + def test_is_delegated_provider(self): + provider = self.singleton_cls(object) + self.assertTrue(providers.is_delegated(provider)) + + def test_repr(self): + provider = self.singleton_cls(Example) + + self.assertEqual(repr(provider), + ''.format( + repr(Example), + hex(id(provider)))) + + +class ThreadLocalSingletonTests(_BaseSingletonTestCase, unittest.TestCase): + + singleton_cls = providers.ThreadLocalSingleton + + def test_repr(self): + provider = providers.ThreadLocalSingleton(Example) + + self.assertEqual(repr(provider), + ''.format( + repr(Example), + hex(id(provider)))) + + +class DelegatedThreadLocalSingletonTests(_BaseSingletonTestCase, + unittest.TestCase): + + singleton_cls = providers.DelegatedThreadLocalSingleton + + def test_is_delegated_provider(self): + provider = self.singleton_cls(object) + self.assertTrue(providers.is_delegated(provider)) + + def test_repr(self): + provider = self.singleton_cls(Example) + + self.assertEqual(repr(provider), + ''.format( + repr(Example), + hex(id(provider)))) + + +class ThreadSafeSingletonTests(_BaseSingletonTestCase, unittest.TestCase): + + singleton_cls = providers.ThreadSafeSingleton + + def test_repr(self): + provider = self.singleton_cls(Example) + + self.assertEqual(repr(provider), + ''.format( + repr(Example), + hex(id(provider)))) + + +class DelegatedThreadSafeSingletonTests(_BaseSingletonTestCase, + unittest.TestCase): + + singleton_cls = providers.DelegatedThreadSafeSingleton + + def test_is_delegated_provider(self): + provider = self.singleton_cls(object) + self.assertTrue(providers.is_delegated(provider)) + + def test_repr(self): + provider = self.singleton_cls(Example) + + self.assertEqual(repr(provider), + ''.format( + repr(Example), + hex(id(provider)))) diff --git a/tests/unit/providers/test_static.py b/tests/unit/providers/test_static.py deleted file mode 100644 index 48bf170f..00000000 --- a/tests/unit/providers/test_static.py +++ /dev/null @@ -1,31 +0,0 @@ -"""Dependency injector static providers unit tests.""" - -import unittest2 as unittest - -from dependency_injector import providers - - -class ObjectProviderTests(unittest.TestCase): - - def test_is_provider(self): - self.assertTrue(providers.is_provider(providers.Object(object()))) - - def test_call_object_provider(self): - obj = object() - self.assertIs(providers.Object(obj)(), obj) - - def test_call_overridden_object_provider(self): - obj1 = object() - obj2 = object() - provider = providers.Object(obj1) - provider.override(providers.Object(obj2)) - self.assertIs(provider(), obj2) - - def test_repr(self): - some_object = object() - provider = providers.Object(some_object) - self.assertEqual(repr(provider), - ''.format( - repr(some_object), - hex(id(provider))))