From 7b61464c939f9bd761aa3be15ecb0742e702b6c3 Mon Sep 17 00:00:00 2001 From: Roman Mogilatov Date: Tue, 8 Nov 2016 12:46:11 +0200 Subject: [PATCH] Make providers stable --- setup.py | 4 - src/dependency_injector/containers/utils.py | 15 +- src/dependency_injector/providers/__init__.py | 21 +- src/dependency_injector/providers/base.pxd | 19 ++ src/dependency_injector/providers/base.pyx | 231 +++++++++++++++++- .../providers/callables.pyx | 28 ++- .../providers/factories.pyx | 25 +- .../providers/injections.pxd | 2 +- .../providers/injections.pyx | 20 +- .../providers/singletons.pyx | 121 ++++++++- src/dependency_injector/providers/static.pxd | 19 -- src/dependency_injector/providers/static.pyx | 165 ------------- src/dependency_injector/providers/utils.pxd | 9 +- src/dependency_injector/providers/utils.pyx | 46 +--- tests/unit/providers/test_base.py | 83 +++++++ tests/unit/providers/test_static.py | 91 ------- 16 files changed, 531 insertions(+), 368 deletions(-) delete mode 100644 src/dependency_injector/providers/static.pxd delete mode 100644 src/dependency_injector/providers/static.pyx delete mode 100644 tests/unit/providers/test_static.py diff --git a/setup.py b/setup.py index 636a77df..929d6d1d 100644 --- a/setup.py +++ b/setup.py @@ -63,10 +63,6 @@ setup(name='dependency-injector', ['src/dependency_injector/providers/singletons.c'], define_macros=defined_macros, extra_compile_args=['-O2']), - Extension('dependency_injector.providers.static', - ['src/dependency_injector/providers/static.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 d01f77cd..fb4d475a 100644 --- a/src/dependency_injector/providers/__init__.py +++ b/src/dependency_injector/providers/__init__.py @@ -2,6 +2,10 @@ from .base import ( Provider, + Object, + Delegate, + ExternalDependency, + OverridingContext, ) from .callables import ( Callable, @@ -23,27 +27,26 @@ from .singletons import ( ThreadLocalSingleton, DelegatedThreadLocalSingleton, ) -from .static import ( - Object, - Delegate, - ExternalDependency, -) from .injections import ( Injection, PositionalInjection, NamedInjection, ) from .utils import ( - OverridingContext, is_provider, ensure_is_provider, is_delegated, represent_provider, + deepcopy, ) __all__ = ( 'Provider', + 'Object', + 'Delegate', + 'ExternalDependency', + 'OverridingContext', 'Callable', 'DelegatedCallable', @@ -62,17 +65,13 @@ __all__ = ( 'ThreadLocalSingleton', 'DelegatedThreadLocalSingleton', - 'Object', - 'Delegate', - 'ExternalDependency', - 'Injection', 'PositionalInjection', 'NamedInjection', - 'OverridingContext', 'is_provider', 'ensure_is_provider', 'is_delegated', 'represent_provider', + 'deepcopy', ) diff --git a/src/dependency_injector/providers/base.pxd b/src/dependency_injector/providers/base.pxd index b3c65a7f..29f895c3 100644 --- a/src/dependency_injector/providers/base.pxd +++ b/src/dependency_injector/providers/base.pxd @@ -10,3 +10,22 @@ cdef class Provider(object): 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.pyx b/src/dependency_injector/providers/base.pyx index eb4e2859..a3e70854 100644 --- a/src/dependency_injector/providers/base.pyx +++ b/src/dependency_injector/providers/base.pyx @@ -7,15 +7,12 @@ cimport cython from dependency_injector.errors import Error -from .static cimport ( - Object, - Delegate, -) from .utils cimport ( + CLASS_TYPES, is_provider, ensure_is_provider, represent_provider, - OverridingContext, + deepcopy, ) @@ -83,6 +80,19 @@ cdef class Provider(object): return self._call_last_overriding(args, kwargs) return self._provide(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__() + + for overriding_provider in self.overridden: + copied.override(deepcopy(overriding_provider, memo)) + + return copied + def __str__(self): """Return string representation of provider. @@ -173,3 +183,214 @@ cdef class Provider(object): return None return self.__overridden[self.__overridden_len - 1](*args, **kwargs) + + +cdef class Object(Provider): + """Object provider returns provided instance "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 + """ + self.__provides = provides + super(Object, 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__(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. + :type args: tuple[object] + + :param kwargs: Dictionary of context keyword arguments. + :type kwargs: dict[str, object] + + :rtype: object + """ + return self.__provides + + +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 + """ + super(Delegate, self).__init__(ensure_is_provider(provides)) + + +cdef class ExternalDependency(Provider): + """:py:class:`ExternalDependency` provider describes dependency interface. + + This provider is used for description of dependency interface. That might + be useful when dependency could be provided in the client's code only, + but it's interface is known. Such situations could happen when required + dependency has non-determenistic list of dependencies itself. + + .. code-block:: python + + database_provider = ExternalDependency(sqlite3.dbapi2.Connection) + database_provider.override(Factory(sqlite3.connect, ':memory:')) + + database = database_provider() + + .. py:attribute:: instance_of + + Class of required dependency. + + :type: type + """ + + def __init__(self, type instance_of): + """Initializer.""" + 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. + + :param args: Tuple of context positional arguments. + :type args: tuple[object] + + :param kwargs: Dictionary of context keyword arguments. + :type kwargs: dict[str, object] + + :raise: :py:exc:`dependency_injector.errors.Error` + + :rtype: object + """ + cdef object instance + + if self.__overridden_len == 0: + raise Error('Dependency is not defined') + + instance = self._call_last_overriding(args, kwargs) + + if not isinstance(instance, self.instance_of): + raise Error('{0} is not an '.format(instance) + + 'instance of {0}'.format(self.instance_of)) + + 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. + + :param provider: Provider that provides required dependency. + :type provider: :py:class:`Provider` + + :rtype: None + """ + return self.override(provider) + + +cdef class OverridingContext(object): + """Provider overriding context. + + :py:class:`OverridingContext` is used by :py:meth:`Provider.override` for + implemeting ``with`` contexts. When :py:class:`OverridingContext` is + closed, overriding that was created in this context is dropped also. + + .. code-block:: python + + with provider.override(another_provider): + assert provider.overridden + assert not provider.overridden + """ + + def __init__(self, Provider overridden, Provider overriding): + """Initializer. + + :param overridden: Overridden provider. + :type overridden: :py:class:`Provider` + + :param overriding: Overriding provider. + :type overriding: :py:class:`Provider` + """ + self.__overridden = overridden + self.__overriding = overriding + super(OverridingContext, self).__init__() + + def __enter__(self): + """Do nothing.""" + return self.__overriding + + def __exit__(self, *_): + """Exit overriding context.""" + self.__overridden.reset_last_overriding() diff --git a/src/dependency_injector/providers/callables.pyx b/src/dependency_injector/providers/callables.pyx index a2c963f1..fc895363 100644 --- a/src/dependency_injector/providers/callables.pyx +++ b/src/dependency_injector/providers/callables.pyx @@ -12,7 +12,10 @@ from .injections cimport ( parse_positional_injections, parse_named_injections, ) -from .utils cimport represent_provider +from .utils cimport ( + represent_provider, + deepcopy, +) cdef class Callable(Provider): @@ -68,6 +71,21 @@ cdef class Callable(Provider): 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.__provies, + *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. @@ -132,13 +150,13 @@ cdef class Callable(Provider): def kwargs(self): """Return keyword argument injections.""" cdef int index - cdef NamedInjection arg + cdef NamedInjection kwarg cdef dict kwargs kwargs = dict() - for index in range(self.__args_len): - arg = self.__args[index] - kwargs[arg.__name] = arg.__value + for index in range(self.__kwargs_len): + kwarg = self.__kwargs[index] + kwargs[kwarg.__name] = kwarg.__value return kwargs def add_kwargs(self, **kwargs): diff --git a/src/dependency_injector/providers/factories.pyx b/src/dependency_injector/providers/factories.pyx index 24c4a2bc..d5c3f4c3 100644 --- a/src/dependency_injector/providers/factories.pyx +++ b/src/dependency_injector/providers/factories.pyx @@ -11,7 +11,10 @@ from .injections cimport ( NamedInjection, parse_named_injections, ) -from .utils cimport represent_provider +from .utils cimport ( + represent_provider, + deepcopy, +) cdef class Factory(Provider): @@ -90,6 +93,22 @@ cdef class Factory(Provider): 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. @@ -185,8 +204,8 @@ cdef class Factory(Provider): cdef dict attributes attributes = dict() - for index in range(self.__args_len): - attribute = self.__args[index] + for index in range(self.__attributes_len): + attribute = self.__attributes[index] attributes[attribute.__name] = attribute.__value return attributes diff --git a/src/dependency_injector/providers/injections.pxd b/src/dependency_injector/providers/injections.pxd index 87a243a2..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 diff --git a/src/dependency_injector/providers/injections.pyx b/src/dependency_injector/providers/injections.pyx index 6b6a71dc..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.""" @@ -25,6 +26,14 @@ cdef class PositionalInjection(Injection): self.__is_delegated = is_delegated(value) 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.""" @@ -46,6 +55,15 @@ cdef class NamedInjection(Injection): self.__is_delegated = is_delegated(value) 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.pyx b/src/dependency_injector/providers/singletons.pyx index 58e9c42a..44480e33 100644 --- a/src/dependency_injector/providers/singletons.pyx +++ b/src/dependency_injector/providers/singletons.pyx @@ -9,7 +9,10 @@ from dependency_injector.errors import Error from .base cimport Provider from .factories cimport Factory -from .utils cimport represent_provider +from .utils cimport ( + represent_provider, + deepcopy, +) GLOBAL_LOCK = threading.RLock() @@ -43,7 +46,7 @@ cdef class BaseSingleton(Provider): self.__instantiator = Factory(provides, *args, **kwargs) - super(Provider, self).__init__() + super(BaseSingleton, self).__init__() def __str__(self): """Return string representation of provider. @@ -53,6 +56,22 @@ cdef class BaseSingleton(Provider): 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.""" @@ -178,7 +197,35 @@ cdef class BaseSingleton(Provider): cdef class Singleton(BaseSingleton): - """Singleton provider.""" + """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. @@ -208,6 +255,23 @@ cdef class Singleton(BaseSingleton): 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 @@ -243,11 +307,43 @@ cdef class ThreadSafeSingleton(BaseSingleton): 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 provider.""" + """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. @@ -277,4 +373,21 @@ cdef class ThreadLocalSingleton(BaseSingleton): 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/static.pxd b/src/dependency_injector/providers/static.pxd deleted file mode 100644 index 01403205..00000000 --- a/src/dependency_injector/providers/static.pxd +++ /dev/null @@ -1,19 +0,0 @@ -"""Dependency injector static providers. - -Powered by Cython. -""" - -from .base cimport Provider - - -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 diff --git a/src/dependency_injector/providers/static.pyx b/src/dependency_injector/providers/static.pyx deleted file mode 100644 index aed1b69a..00000000 --- a/src/dependency_injector/providers/static.pyx +++ /dev/null @@ -1,165 +0,0 @@ -"""Dependency injector static providers. - -Powered by Cython. -""" - -from dependency_injector.errors import Error - -from .base cimport Provider -from .utils cimport ( - ensure_is_provider, - represent_provider, - CLASS_TYPES, -) - - -cdef class Object(Provider): - """Object provider returns provided instance "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 - """ - self.__provides = provides - super(Object, self).__init__() - - 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. - :type args: tuple[object] - - :param kwargs: Dictionary of context keyword arguments. - :type kwargs: dict[str, object] - - :rtype: object - """ - return self.__provides - - -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 - """ - super(Delegate, self).__init__(ensure_is_provider(provides)) - - -cdef class ExternalDependency(Provider): - """:py:class:`ExternalDependency` provider describes dependency interface. - - This provider is used for description of dependency interface. That might - be useful when dependency could be provided in the client's code only, - but it's interface is known. Such situations could happen when required - dependency has non-determenistic list of dependencies itself. - - .. code-block:: python - - database_provider = ExternalDependency(sqlite3.dbapi2.Connection) - database_provider.override(Factory(sqlite3.connect, ':memory:')) - - database = database_provider() - - .. py:attribute:: instance_of - - Class of required dependency. - - :type: type - """ - - def __init__(self, type instance_of): - """Initializer.""" - if not isinstance(instance_of, CLASS_TYPES): - raise Error('ExternalDependency provider expects to get class, ' + - 'got {0} instead'.format(str(instance_of))) - self.__instance_of = instance_of - super(ExternalDependency, self).__init__() - - def __call__(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] - - :raise: :py:exc:`dependency_injector.errors.Error` - - :rtype: object - """ - cdef object instance - - if self.__overridden_len == 0: - raise Error('Dependency is not defined') - - instance = self._call_last_overriding(args, kwargs) - - if not isinstance(instance, self.instance_of): - raise Error('{0} is not an '.format(instance) + - 'instance of {0}'.format(self.instance_of)) - - 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. - - :param provider: Provider that provides required dependency. - :type provider: :py:class:`Provider` - - :rtype: None - """ - return self.override(provider) diff --git a/src/dependency_injector/providers/utils.pxd b/src/dependency_injector/providers/utils.pxd index 989ce57d..84faf5ba 100644 --- a/src/dependency_injector/providers/utils.pxd +++ b/src/dependency_injector/providers/utils.pxd @@ -3,18 +3,11 @@ Powered by Cython. """ -from .base cimport Provider - - cdef public object CLASS_TYPES -cdef class OverridingContext(object): - cdef Provider __overridden - cdef Provider __overriding - - 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 93b04105..1940ffc6 100644 --- a/src/dependency_injector/providers/utils.pyx +++ b/src/dependency_injector/providers/utils.pyx @@ -3,55 +3,23 @@ Powered by Cython. """ +import copy import sys import types - import threading from dependency_injector.errors import Error -from .base cimport Provider - if sys.version_info[0] == 3: # pragma: no cover CLASS_TYPES = (type,) else: # pragma: no cover CLASS_TYPES = (type, types.ClassType) - -cdef class OverridingContext(object): - """Provider overriding context. - - :py:class:`OverridingContext` is used by :py:meth:`Provider.override` for - implemeting ``with`` contexts. When :py:class:`OverridingContext` is - closed, overriding that was created in this context is dropped also. - - .. code-block:: python - - with provider.override(another_provider): - assert provider.overridden - assert not provider.overridden - """ - - def __init__(self, Provider overridden, Provider overriding): - """Initializer. - - :param overridden: Overridden provider. - :type overridden: :py:class:`Provider` - - :param overriding: Overriding provider. - :type overriding: :py:class:`Provider` - """ - self.__overridden = overridden - self.__overriding = overriding - - def __enter__(self): - """Do nothing.""" - return self.__overriding - - def __exit__(self, *_): - """Exit overriding context.""" - self.__overridden.reset_last_overriding() + 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): @@ -112,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/unit/providers/test_base.py b/tests/unit/providers/test_base.py index 5ad03dbc..e475ceb6 100644 --- a/tests/unit/providers/test_base.py +++ b/tests/unit/providers/test_base.py @@ -84,3 +84,86 @@ class ProviderTests(unittest.TestCase): 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_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(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, object()) + + def test_call(self): + delegated1 = self.delegate() + delegated2 = self.delegate() + + self.assertIs(delegated1, self.delegated) + self.assertIs(delegated2, self.delegated) + + def test_repr(self): + self.assertEqual(repr(self.delegate), + ''.format( + repr(self.delegated), + hex(id(self.delegate)))) + + +class ExternalDependencyTests(unittest.TestCase): + + def setUp(self): + self.provider = providers.ExternalDependency(instance_of=list) + + def test_init_with_not_class(self): + self.assertRaises(TypeError, providers.ExternalDependency, object()) + + def test_is_provider(self): + self.assertTrue(providers.is_provider(self.provider)) + + def test_call_overridden(self): + self.provider.provided_by(providers.Factory(list)) + self.assertIsInstance(self.provider(), list) + + def test_call_overridden_but_not_instance_of(self): + self.provider.provided_by(providers.Factory(dict)) + self.assertRaises(errors.Error, self.provider) + + def test_call_not_overridden(self): + self.assertRaises(errors.Error, self.provider) + + def test_repr(self): + self.assertEqual(repr(self.provider), + ''.format( + repr(list), + hex(id(self.provider)))) diff --git a/tests/unit/providers/test_static.py b/tests/unit/providers/test_static.py deleted file mode 100644 index 47e568b0..00000000 --- a/tests/unit/providers/test_static.py +++ /dev/null @@ -1,91 +0,0 @@ -"""Dependency injector static providers unit tests.""" - -import unittest2 as unittest - -from dependency_injector import ( - providers, - errors, -) - - -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)))) - - -class DelegateTests(unittest.TestCase): - - def setUp(self): - self.delegated = providers.Provider() - 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, object()) - - def test_call(self): - delegated1 = self.delegate() - delegated2 = self.delegate() - - self.assertIs(delegated1, self.delegated) - self.assertIs(delegated2, self.delegated) - - def test_repr(self): - self.assertEqual(repr(self.delegate), - ''.format( - repr(self.delegated), - hex(id(self.delegate)))) - - -class ExternalDependencyTests(unittest.TestCase): - - def setUp(self): - self.provider = providers.ExternalDependency(instance_of=list) - - def test_init_with_not_class(self): - self.assertRaises(TypeError, providers.ExternalDependency, object()) - - def test_is_provider(self): - self.assertTrue(providers.is_provider(self.provider)) - - def test_call_overridden(self): - self.provider.provided_by(providers.Factory(list)) - self.assertIsInstance(self.provider(), list) - - def test_call_overridden_but_not_instance_of(self): - self.provider.provided_by(providers.Factory(dict)) - self.assertRaises(errors.Error, self.provider) - - def test_call_not_overridden(self): - self.assertRaises(errors.Error, self.provider) - - def test_repr(self): - self.assertEqual(repr(self.provider), - ''.format( - repr(list), - hex(id(self.provider))))