mirror of
https://github.com/ets-labs/python-dependency-injector.git
synced 2024-11-22 01:26:51 +03:00
Singleton reset context (#417)
* Add implementation and typing stubs * Make some refactoring and add tests * Pin ubuntu version to 18.04 * Add docs and example * Add changelog * Add container docs
This commit is contained in:
parent
e0b0a1e968
commit
2bf3601695
2
.github/workflows/tests-and-linters.yml
vendored
2
.github/workflows/tests-and-linters.yml
vendored
|
@ -6,7 +6,7 @@ jobs:
|
||||||
|
|
||||||
test-on-different-versions:
|
test-on-different-versions:
|
||||||
name: Run tests
|
name: Run tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-18.04
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: [2.7, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, pypy2, pypy3]
|
python-version: [2.7, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, pypy2, pypy3]
|
||||||
|
|
|
@ -18,6 +18,14 @@ Method ``.reset_singletons()`` also resets singletons in sub-containers: ``provi
|
||||||
:lines: 3-
|
:lines: 3-
|
||||||
:emphasize-lines: 21
|
:emphasize-lines: 21
|
||||||
|
|
||||||
|
You can use ``.reset_singletons()`` method with a context manager. Singletons will be reset on
|
||||||
|
both entering and exiting a context.
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/containers/reset_singletons_with.py
|
||||||
|
:language: python
|
||||||
|
:lines: 3-
|
||||||
|
:emphasize-lines: 14-15
|
||||||
|
|
||||||
See also: :ref:`singleton-provider`.
|
See also: :ref:`singleton-provider`.
|
||||||
|
|
||||||
.. disqus::
|
.. disqus::
|
||||||
|
|
|
@ -7,6 +7,12 @@ that were made in every particular version.
|
||||||
From version 0.7.6 *Dependency Injector* framework strictly
|
From version 0.7.6 *Dependency Injector* framework strictly
|
||||||
follows `Semantic versioning`_
|
follows `Semantic versioning`_
|
||||||
|
|
||||||
|
Development version
|
||||||
|
-------------------
|
||||||
|
- Implement context manager interface for resetting a singleton provider.
|
||||||
|
See issue: `#413 <https://github.com/ets-labs/python-dependency-injector/issues/413>`_.
|
||||||
|
Thanks to `@Arrowana <https://github.com/Arrowana>`_ for suggesting the improvement.
|
||||||
|
|
||||||
4.28.1
|
4.28.1
|
||||||
------
|
------
|
||||||
- Fix async mode mode exception handling issue in ``Dependency`` provider.
|
- Fix async mode mode exception handling issue in ``Dependency`` provider.
|
||||||
|
|
|
@ -20,13 +20,12 @@ returns it on the rest of the calls.
|
||||||
:language: python
|
:language: python
|
||||||
:lines: 3-
|
:lines: 3-
|
||||||
|
|
||||||
``Singleton`` provider handles an injection of the dependencies the same way like a
|
``Singleton`` provider handles dependencies injection the same way like a :ref:`factory-provider`.
|
||||||
:ref:`factory-provider`.
|
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
``Singleton`` provider does dependencies injection only when creates the object. When the object
|
``Singleton`` provider makes dependencies injection only when creates an object. When an object
|
||||||
is created and memorized ``Singleton`` provider just returns it without applying the injections.
|
is created and memorized ``Singleton`` provider just returns it without applying injections.
|
||||||
|
|
||||||
Specialization of the provided type and abstract singletons work the same like like for the
|
Specialization of the provided type and abstract singletons work the same like like for the
|
||||||
factories:
|
factories:
|
||||||
|
@ -56,6 +55,21 @@ provider.
|
||||||
Resetting of the memorized object clears the reference to it. Further object's lifecycle is
|
Resetting of the memorized object clears the reference to it. Further object's lifecycle is
|
||||||
managed by the garbage collector.
|
managed by the garbage collector.
|
||||||
|
|
||||||
|
You can use ``.reset()`` method with a context manager. Memorized instance will be reset on
|
||||||
|
both entering and exiting a context.
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/providers/singleton_resetting_with.py
|
||||||
|
:language: python
|
||||||
|
:lines: 3-
|
||||||
|
:emphasize-lines: 18-19
|
||||||
|
|
||||||
|
Context manager ``.reset()`` returns resetting singleton provider. You can use it for aliasing.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
with container.user_service.reset() as user_service:
|
||||||
|
...
|
||||||
|
|
||||||
Method ``.reset()`` resets only current provider. To reset all dependent singleton providers
|
Method ``.reset()`` resets only current provider. To reset all dependent singleton providers
|
||||||
call ``.full_reset()`` method.
|
call ``.full_reset()`` method.
|
||||||
|
|
||||||
|
@ -64,6 +78,13 @@ call ``.full_reset()`` method.
|
||||||
:lines: 3-
|
:lines: 3-
|
||||||
:emphasize-lines: 25
|
:emphasize-lines: 25
|
||||||
|
|
||||||
|
Method ``.full_reset()`` supports context manager interface like ``.reset()`` does.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
with container.user_service.full_reset() as user_service:
|
||||||
|
...
|
||||||
|
|
||||||
See also: :ref:`reset-container-singletons`.
|
See also: :ref:`reset-container-singletons`.
|
||||||
|
|
||||||
Using singleton with multiple threads
|
Using singleton with multiple threads
|
||||||
|
|
23
examples/containers/reset_singletons_with.py
Normal file
23
examples/containers/reset_singletons_with.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
"""Container reset singletons context manager example."""
|
||||||
|
|
||||||
|
from dependency_injector import containers, providers
|
||||||
|
|
||||||
|
|
||||||
|
class Container(containers.DeclarativeContainer):
|
||||||
|
|
||||||
|
service = providers.Singleton(object)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
container = Container()
|
||||||
|
|
||||||
|
service1 = container.service()
|
||||||
|
|
||||||
|
with container.reset_singletons():
|
||||||
|
service2 = container.service()
|
||||||
|
|
||||||
|
service3 = container.service()
|
||||||
|
|
||||||
|
assert service1 is not service2
|
||||||
|
assert service2 is not service3
|
||||||
|
assert service3 is not service1
|
27
examples/providers/singleton_resetting_with.py
Normal file
27
examples/providers/singleton_resetting_with.py
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
"""`Singleton` provider resetting context manager example."""
|
||||||
|
|
||||||
|
from dependency_injector import containers, providers
|
||||||
|
|
||||||
|
|
||||||
|
class UserService:
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class Container(containers.DeclarativeContainer):
|
||||||
|
|
||||||
|
user_service = providers.Singleton(UserService)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
container = Container()
|
||||||
|
|
||||||
|
user_service1 = container.user_service()
|
||||||
|
|
||||||
|
with container.user_service.reset():
|
||||||
|
user_service2 = container.user_service()
|
||||||
|
|
||||||
|
user_service3 = container.user_service()
|
||||||
|
|
||||||
|
assert user_service1 is not user_service2
|
||||||
|
assert user_service2 is not user_service3
|
||||||
|
assert user_service3 is not user_service1
|
File diff suppressed because it is too large
Load Diff
|
@ -1,4 +1,5 @@
|
||||||
from typing import (
|
from typing import (
|
||||||
|
Generic,
|
||||||
Type,
|
Type,
|
||||||
Dict,
|
Dict,
|
||||||
Tuple,
|
Tuple,
|
||||||
|
@ -20,6 +21,7 @@ from .providers import Provider, Self, ProviderParent
|
||||||
C_Base = TypeVar('C_Base', bound='Container')
|
C_Base = TypeVar('C_Base', bound='Container')
|
||||||
C = TypeVar('C', bound='DeclarativeContainer')
|
C = TypeVar('C', bound='DeclarativeContainer')
|
||||||
C_Overriding = TypeVar('C_Overriding', bound='DeclarativeContainer')
|
C_Overriding = TypeVar('C_Overriding', bound='DeclarativeContainer')
|
||||||
|
T = TypeVar('T')
|
||||||
TT = TypeVar('TT')
|
TT = TypeVar('TT')
|
||||||
|
|
||||||
|
|
||||||
|
@ -44,7 +46,7 @@ class Container:
|
||||||
def init_resources(self) -> Optional[Awaitable]: ...
|
def init_resources(self) -> Optional[Awaitable]: ...
|
||||||
def shutdown_resources(self) -> Optional[Awaitable]: ...
|
def shutdown_resources(self) -> Optional[Awaitable]: ...
|
||||||
def apply_container_providers_overridings(self) -> None: ...
|
def apply_container_providers_overridings(self) -> None: ...
|
||||||
def reset_singletons(self) -> None: ...
|
def reset_singletons(self) -> SingletonResetContext[C_Base]: ...
|
||||||
def check_dependencies(self) -> None: ...
|
def check_dependencies(self) -> None: ...
|
||||||
@overload
|
@overload
|
||||||
def resolve_provider_name(self, provider: Provider) -> str: ...
|
def resolve_provider_name(self, provider: Provider) -> str: ...
|
||||||
|
@ -72,6 +74,11 @@ class DeclarativeContainer(Container):
|
||||||
def __init__(self, **overriding_providers: Union[Provider, Any]) -> None: ...
|
def __init__(self, **overriding_providers: Union[Provider, Any]) -> None: ...
|
||||||
|
|
||||||
|
|
||||||
|
class SingletonResetContext(Generic[T]):
|
||||||
|
def __init__(self, container: T): ...
|
||||||
|
def __enter__(self) -> T: ...
|
||||||
|
def __exit__(self, *_: Any) -> None: ...
|
||||||
|
|
||||||
|
|
||||||
def override(container: Type[C]) -> _Callable[[Type[C_Overriding]], Type[C_Overriding]]: ...
|
def override(container: Type[C]) -> _Callable[[Type[C_Overriding]], Type[C_Overriding]]: ...
|
||||||
|
|
||||||
|
|
|
@ -304,6 +304,7 @@ class DynamicContainer(Container):
|
||||||
"""Reset container singletons."""
|
"""Reset container singletons."""
|
||||||
for provider in self.traverse(types=[providers.BaseSingleton]):
|
for provider in self.traverse(types=[providers.BaseSingleton]):
|
||||||
provider.reset()
|
provider.reset()
|
||||||
|
return SingletonResetContext(self)
|
||||||
|
|
||||||
def check_dependencies(self):
|
def check_dependencies(self):
|
||||||
"""Check if container dependencies are defined.
|
"""Check if container dependencies are defined.
|
||||||
|
@ -639,6 +640,18 @@ class DeclarativeContainer(Container):
|
||||||
provider.reset_override()
|
provider.reset_override()
|
||||||
|
|
||||||
|
|
||||||
|
class SingletonResetContext:
|
||||||
|
|
||||||
|
def __init__(self, container):
|
||||||
|
self._container = container
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
return self._container
|
||||||
|
|
||||||
|
def __exit__(self, *_):
|
||||||
|
self._container.reset_singletons()
|
||||||
|
|
||||||
|
|
||||||
def override(object container):
|
def override(object container):
|
||||||
""":py:class:`DeclarativeContainer` overriding decorator.
|
""":py:class:`DeclarativeContainer` overriding decorator.
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -54,11 +54,6 @@ cdef class DependenciesContainer(Object):
|
||||||
cpdef object _override_providers(self, object container)
|
cpdef object _override_providers(self, object container)
|
||||||
|
|
||||||
|
|
||||||
cdef class OverridingContext(object):
|
|
||||||
cdef Provider __overridden
|
|
||||||
cdef Provider __overriding
|
|
||||||
|
|
||||||
|
|
||||||
# Callable providers
|
# Callable providers
|
||||||
cdef class Callable(Provider):
|
cdef class Callable(Provider):
|
||||||
cdef object __provides
|
cdef object __provides
|
||||||
|
@ -292,6 +287,23 @@ cpdef tuple parse_named_injections(dict kwargs)
|
||||||
|
|
||||||
|
|
||||||
# Utils
|
# Utils
|
||||||
|
cdef class OverridingContext(object):
|
||||||
|
cdef Provider __overridden
|
||||||
|
cdef Provider __overriding
|
||||||
|
|
||||||
|
|
||||||
|
cdef class BaseSingletonResetContext(object):
|
||||||
|
cdef object __singleton
|
||||||
|
|
||||||
|
|
||||||
|
cdef class SingletonResetContext(BaseSingletonResetContext):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
cdef class SingletonFullResetContext(BaseSingletonResetContext):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
cdef object CLASS_TYPES
|
cdef object CLASS_TYPES
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -38,12 +38,8 @@ Injection = Any
|
||||||
ProviderParent = Union['Provider', Any]
|
ProviderParent = Union['Provider', Any]
|
||||||
T = TypeVar('T')
|
T = TypeVar('T')
|
||||||
TT = TypeVar('TT')
|
TT = TypeVar('TT')
|
||||||
|
P = TypeVar('P', bound='Provider')
|
||||||
|
BS = TypeVar('BS', bound='BaseSingleton')
|
||||||
class OverridingContext:
|
|
||||||
def __init__(self, overridden: Provider, overriding: Provider): ...
|
|
||||||
def __enter__(self) -> Provider: ...
|
|
||||||
def __exit__(self, *_: Any) -> None: ...
|
|
||||||
|
|
||||||
|
|
||||||
class Provider(Generic[T]):
|
class Provider(Generic[T]):
|
||||||
|
@ -62,7 +58,7 @@ class Provider(Generic[T]):
|
||||||
def overridden(self) -> Tuple[Provider]: ...
|
def overridden(self) -> Tuple[Provider]: ...
|
||||||
@property
|
@property
|
||||||
def last_overriding(self) -> Optional[Provider]: ...
|
def last_overriding(self) -> Optional[Provider]: ...
|
||||||
def override(self, provider: Union[Provider, Any]) -> OverridingContext: ...
|
def override(self, provider: Union[Provider, Any]) -> OverridingContext[P]: ...
|
||||||
def reset_last_overriding(self) -> None: ...
|
def reset_last_overriding(self) -> None: ...
|
||||||
def reset_override(self) -> None: ...
|
def reset_override(self) -> None: ...
|
||||||
def delegate(self) -> Provider: ...
|
def delegate(self) -> Provider: ...
|
||||||
|
@ -109,7 +105,7 @@ class Dependency(Provider[T]):
|
||||||
def default(self) -> Provider[T]: ...
|
def default(self) -> Provider[T]: ...
|
||||||
@property
|
@property
|
||||||
def is_defined(self) -> bool: ...
|
def is_defined(self) -> bool: ...
|
||||||
def provided_by(self, provider: Provider) -> OverridingContext: ...
|
def provided_by(self, provider: Provider) -> OverridingContext[P]: ...
|
||||||
@property
|
@property
|
||||||
def parent(self) -> Optional[ProviderParent]: ...
|
def parent(self) -> Optional[ProviderParent]: ...
|
||||||
@property
|
@property
|
||||||
|
@ -153,7 +149,7 @@ class DelegatedCallable(Callable[T]): ...
|
||||||
|
|
||||||
|
|
||||||
class AbstractCallable(Callable[T]):
|
class AbstractCallable(Callable[T]):
|
||||||
def override(self, provider: Callable) -> OverridingContext: ...
|
def override(self, provider: Callable) -> OverridingContext[P]: ...
|
||||||
|
|
||||||
|
|
||||||
class CallableDelegate(Delegate):
|
class CallableDelegate(Delegate):
|
||||||
|
@ -167,7 +163,7 @@ class DelegatedCoroutine(Coroutine[T]): ...
|
||||||
|
|
||||||
|
|
||||||
class AbstractCoroutine(Coroutine[T]):
|
class AbstractCoroutine(Coroutine[T]):
|
||||||
def override(self, provider: Coroutine) -> OverridingContext: ...
|
def override(self, provider: Coroutine) -> OverridingContext[P]: ...
|
||||||
|
|
||||||
|
|
||||||
class CoroutineDelegate(Delegate):
|
class CoroutineDelegate(Delegate):
|
||||||
|
@ -212,7 +208,7 @@ class Configuration(Object[Any]):
|
||||||
def __getitem__(self, item: Union[str, Provider]) -> ConfigurationOption: ...
|
def __getitem__(self, item: Union[str, Provider]) -> ConfigurationOption: ...
|
||||||
def get_name(self) -> str: ...
|
def get_name(self) -> str: ...
|
||||||
def get(self, selector: str) -> Any: ...
|
def get(self, selector: str) -> Any: ...
|
||||||
def set(self, selector: str, value: Any) -> OverridingContext: ...
|
def set(self, selector: str, value: Any) -> OverridingContext[P]: ...
|
||||||
def reset_cache(self) -> None: ...
|
def reset_cache(self) -> None: ...
|
||||||
def update(self, value: Any) -> None: ...
|
def update(self, value: Any) -> None: ...
|
||||||
def from_ini(self, filepath: Union[Path, str], required: bool = False) -> None: ...
|
def from_ini(self, filepath: Union[Path, str], required: bool = False) -> None: ...
|
||||||
|
@ -250,7 +246,7 @@ class DelegatedFactory(Factory[T]): ...
|
||||||
|
|
||||||
|
|
||||||
class AbstractFactory(Factory[T]):
|
class AbstractFactory(Factory[T]):
|
||||||
def override(self, provider: Factory) -> OverridingContext: ...
|
def override(self, provider: Factory) -> OverridingContext[P]: ...
|
||||||
|
|
||||||
|
|
||||||
class FactoryDelegate(Delegate):
|
class FactoryDelegate(Delegate):
|
||||||
|
@ -293,8 +289,8 @@ class BaseSingleton(Provider[T]):
|
||||||
def add_attributes(self, **kwargs: Injection) -> BaseSingleton[T]: ...
|
def add_attributes(self, **kwargs: Injection) -> BaseSingleton[T]: ...
|
||||||
def set_attributes(self, **kwargs: Injection) -> BaseSingleton[T]: ...
|
def set_attributes(self, **kwargs: Injection) -> BaseSingleton[T]: ...
|
||||||
def clear_attributes(self) -> BaseSingleton[T]: ...
|
def clear_attributes(self) -> BaseSingleton[T]: ...
|
||||||
def reset(self) -> None: ...
|
def reset(self) -> SingletonResetContext[BS]: ...
|
||||||
def full_reset(self) -> None: ...
|
def full_reset(self) -> SingletonFullResetContext[BS]: ...
|
||||||
|
|
||||||
|
|
||||||
class Singleton(BaseSingleton[T]): ...
|
class Singleton(BaseSingleton[T]): ...
|
||||||
|
@ -316,7 +312,7 @@ class DelegatedThreadLocalSingleton(ThreadLocalSingleton[T]): ...
|
||||||
|
|
||||||
|
|
||||||
class AbstractSingleton(BaseSingleton[T]):
|
class AbstractSingleton(BaseSingleton[T]):
|
||||||
def override(self, provider: BaseSingleton) -> OverridingContext: ...
|
def override(self, provider: BaseSingleton) -> OverridingContext[P]: ...
|
||||||
|
|
||||||
|
|
||||||
class SingletonDelegate(Delegate):
|
class SingletonDelegate(Delegate):
|
||||||
|
@ -414,6 +410,29 @@ class MethodCaller(Provider, ProvidedInstanceFluentInterface):
|
||||||
def __init__(self, provider: Provider, *args: Injection, **kwargs: Injection) -> None: ...
|
def __init__(self, provider: Provider, *args: Injection, **kwargs: Injection) -> None: ...
|
||||||
|
|
||||||
|
|
||||||
|
class OverridingContext(Generic[T]):
|
||||||
|
def __init__(self, overridden: Provider, overriding: Provider): ...
|
||||||
|
def __enter__(self) -> T: ...
|
||||||
|
def __exit__(self, *_: Any) -> None: ...
|
||||||
|
|
||||||
|
|
||||||
|
class BaseSingletonResetContext(Generic[T]):
|
||||||
|
def __init__(self, provider: T): ...
|
||||||
|
def __enter__(self) -> T: ...
|
||||||
|
def __exit__(self, *_: Any) -> None: ...
|
||||||
|
|
||||||
|
|
||||||
|
class SingletonResetContext(BaseSingletonResetContext):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class SingletonFullResetContext(BaseSingletonResetContext):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
CHILD_PROVIDERS: Tuple[Provider]
|
||||||
|
|
||||||
|
|
||||||
def is_provider(instance: Any) -> bool: ...
|
def is_provider(instance: Any) -> bool: ...
|
||||||
|
|
||||||
|
|
||||||
|
@ -444,6 +463,3 @@ if pydantic:
|
||||||
PydanticSettings = pydantic.BaseSettings
|
PydanticSettings = pydantic.BaseSettings
|
||||||
else:
|
else:
|
||||||
PydanticSettings = Any
|
PydanticSettings = Any
|
||||||
|
|
||||||
|
|
||||||
CHILD_PROVIDERS: Tuple[Provider]
|
|
||||||
|
|
|
@ -990,42 +990,6 @@ cdef class DependenciesContainer(Object):
|
||||||
provider.override(dependency_provider)
|
provider.override(dependency_provider)
|
||||||
|
|
||||||
|
|
||||||
cdef class OverridingContext(object):
|
|
||||||
"""Provider overriding context.
|
|
||||||
|
|
||||||
:py:class:`OverridingContext` is used by :py:meth:`Provider.override` for
|
|
||||||
implementing ``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()
|
|
||||||
|
|
||||||
|
|
||||||
cdef class Callable(Provider):
|
cdef class Callable(Provider):
|
||||||
r"""Callable provider calls wrapped callable on every call.
|
r"""Callable provider calls wrapped callable on every call.
|
||||||
|
|
||||||
|
@ -2632,11 +2596,12 @@ cdef class BaseSingleton(Provider):
|
||||||
def full_reset(self):
|
def full_reset(self):
|
||||||
"""Reset cached instance in current and all underlying singletons, if any.
|
"""Reset cached instance in current and all underlying singletons, if any.
|
||||||
|
|
||||||
:rtype: None
|
:rtype: :py:class:`SingletonFullResetContext`
|
||||||
"""
|
"""
|
||||||
self.reset()
|
self.reset()
|
||||||
for provider in self.traverse(types=[BaseSingleton]):
|
for provider in self.traverse(types=[BaseSingleton]):
|
||||||
provider.reset()
|
provider.reset()
|
||||||
|
return SingletonFullResetContext(self)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def related(self):
|
def related(self):
|
||||||
|
@ -2707,6 +2672,7 @@ cdef class Singleton(BaseSingleton):
|
||||||
if __is_future_or_coroutine(self.__storage):
|
if __is_future_or_coroutine(self.__storage):
|
||||||
asyncio.ensure_future(self.__storage).cancel()
|
asyncio.ensure_future(self.__storage).cancel()
|
||||||
self.__storage = None
|
self.__storage = None
|
||||||
|
return SingletonResetContext(self)
|
||||||
|
|
||||||
cpdef object _provide(self, tuple args, dict kwargs):
|
cpdef object _provide(self, tuple args, dict kwargs):
|
||||||
"""Return single instance."""
|
"""Return single instance."""
|
||||||
|
@ -2775,7 +2741,7 @@ cdef class ThreadSafeSingleton(BaseSingleton):
|
||||||
if __is_future_or_coroutine(self.__storage):
|
if __is_future_or_coroutine(self.__storage):
|
||||||
asyncio.ensure_future(self.__storage).cancel()
|
asyncio.ensure_future(self.__storage).cancel()
|
||||||
self.__storage = None
|
self.__storage = None
|
||||||
|
return SingletonResetContext(self)
|
||||||
|
|
||||||
cpdef object _provide(self, tuple args, dict kwargs):
|
cpdef object _provide(self, tuple args, dict kwargs):
|
||||||
"""Return single instance."""
|
"""Return single instance."""
|
||||||
|
@ -2853,10 +2819,18 @@ cdef class ThreadLocalSingleton(BaseSingleton):
|
||||||
|
|
||||||
:rtype: None
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
if __is_future_or_coroutine(self.__storage.instance):
|
try:
|
||||||
asyncio.ensure_future(self.__storage.instance).cancel()
|
instance = self.__storage.instance
|
||||||
|
except AttributeError:
|
||||||
|
return SingletonResetContext(self)
|
||||||
|
|
||||||
|
if __is_future_or_coroutine(instance):
|
||||||
|
asyncio.ensure_future(instance).cancel()
|
||||||
|
|
||||||
del self.__storage.instance
|
del self.__storage.instance
|
||||||
|
|
||||||
|
return SingletonResetContext(self)
|
||||||
|
|
||||||
cpdef object _provide(self, tuple args, dict kwargs):
|
cpdef object _provide(self, tuple args, dict kwargs):
|
||||||
"""Return single instance."""
|
"""Return single instance."""
|
||||||
cdef object instance
|
cdef object instance
|
||||||
|
@ -4188,6 +4162,67 @@ cpdef tuple parse_named_injections(dict kwargs):
|
||||||
return tuple(injections)
|
return tuple(injections)
|
||||||
|
|
||||||
|
|
||||||
|
cdef class OverridingContext(object):
|
||||||
|
"""Provider overriding context.
|
||||||
|
|
||||||
|
:py:class:`OverridingContext` is used by :py:meth:`Provider.override` for
|
||||||
|
implementing ``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()
|
||||||
|
|
||||||
|
|
||||||
|
cdef class BaseSingletonResetContext(object):
|
||||||
|
|
||||||
|
def __init__(self, Provider provider):
|
||||||
|
self.__singleton = provider
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
return self.__singleton
|
||||||
|
|
||||||
|
def __exit__(self, *_):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
|
cdef class SingletonResetContext(BaseSingletonResetContext):
|
||||||
|
|
||||||
|
def __exit__(self, *_):
|
||||||
|
return self.__singleton.reset()
|
||||||
|
|
||||||
|
|
||||||
|
cdef class SingletonFullResetContext(BaseSingletonResetContext):
|
||||||
|
|
||||||
|
def __exit__(self, *_):
|
||||||
|
return self.__singleton.full_reset()
|
||||||
|
|
||||||
|
|
||||||
CHILD_PROVIDERS = (Dependency, DependenciesContainer, Container)
|
CHILD_PROVIDERS = (Dependency, DependenciesContainer, Container)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -336,6 +336,36 @@ class DeclarativeContainerInstanceTests(unittest.TestCase):
|
||||||
self.assertIs(obj32, obj42)
|
self.assertIs(obj32, obj42)
|
||||||
self.assertIs(obj33, obj43)
|
self.assertIs(obj33, obj43)
|
||||||
|
|
||||||
|
def test_reset_singletons_context_manager(self):
|
||||||
|
class Item:
|
||||||
|
def __init__(self, dependency):
|
||||||
|
self.dependency = dependency
|
||||||
|
|
||||||
|
class Container(containers.DeclarativeContainer):
|
||||||
|
dependent = providers.Singleton(object)
|
||||||
|
singleton = providers.Singleton(Item, dependency=dependent)
|
||||||
|
|
||||||
|
container = Container()
|
||||||
|
|
||||||
|
instance1 = container.singleton()
|
||||||
|
with container.reset_singletons():
|
||||||
|
instance2 = container.singleton()
|
||||||
|
instance3 = container.singleton()
|
||||||
|
|
||||||
|
self.assertEqual(len({instance1, instance2, instance3}), 3)
|
||||||
|
self.assertEqual(
|
||||||
|
len({instance1.dependency, instance2.dependency, instance3.dependency}),
|
||||||
|
3,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_reset_singletons_context_manager_as_attribute(self):
|
||||||
|
container = containers.DeclarativeContainer()
|
||||||
|
|
||||||
|
with container.reset_singletons() as alias:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.assertIs(container, alias)
|
||||||
|
|
||||||
def test_check_dependencies(self):
|
def test_check_dependencies(self):
|
||||||
class SubContainer(containers.DeclarativeContainer):
|
class SubContainer(containers.DeclarativeContainer):
|
||||||
dependency = providers.Dependency()
|
dependency = providers.Dependency()
|
||||||
|
|
|
@ -370,6 +370,23 @@ class _BaseSingletonTestCase(object):
|
||||||
|
|
||||||
self.assertIsNot(instance1, instance2)
|
self.assertIsNot(instance1, instance2)
|
||||||
|
|
||||||
|
def test_reset_context_manager(self):
|
||||||
|
singleton = self.singleton_cls(object)
|
||||||
|
|
||||||
|
instance1 = singleton()
|
||||||
|
with singleton.reset():
|
||||||
|
instance2 = singleton()
|
||||||
|
instance3 = singleton()
|
||||||
|
self.assertEqual(len({instance1, instance2, instance3}), 3)
|
||||||
|
|
||||||
|
def test_reset_context_manager_as_attribute(self):
|
||||||
|
singleton = self.singleton_cls(object)
|
||||||
|
|
||||||
|
with singleton.reset() as alias:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.assertIs(singleton, alias)
|
||||||
|
|
||||||
def test_full_reset(self):
|
def test_full_reset(self):
|
||||||
dependent_singleton = providers.Singleton(object)
|
dependent_singleton = providers.Singleton(object)
|
||||||
provider = self.singleton_cls(dict, dependency=dependent_singleton)
|
provider = self.singleton_cls(dict, dependency=dependent_singleton)
|
||||||
|
@ -386,6 +403,33 @@ class _BaseSingletonTestCase(object):
|
||||||
self.assertIsNot(dependent_instance1, dependent_instance2)
|
self.assertIsNot(dependent_instance1, dependent_instance2)
|
||||||
self.assertIsNot(instance1, instance2)
|
self.assertIsNot(instance1, instance2)
|
||||||
|
|
||||||
|
def test_full_reset_context_manager(self):
|
||||||
|
class Item:
|
||||||
|
def __init__(self, dependency):
|
||||||
|
self.dependency = dependency
|
||||||
|
|
||||||
|
dependent_singleton = providers.Singleton(object)
|
||||||
|
singleton = self.singleton_cls(Item, dependency=dependent_singleton)
|
||||||
|
|
||||||
|
instance1 = singleton()
|
||||||
|
with singleton.full_reset():
|
||||||
|
instance2 = singleton()
|
||||||
|
instance3 = singleton()
|
||||||
|
|
||||||
|
self.assertEqual(len({instance1, instance2, instance3}), 3)
|
||||||
|
self.assertEqual(
|
||||||
|
len({instance1.dependency, instance2.dependency, instance3.dependency}),
|
||||||
|
3,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_full_reset_context_manager_as_attribute(self):
|
||||||
|
singleton = self.singleton_cls(object)
|
||||||
|
|
||||||
|
with singleton.full_reset() as alias:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.assertIs(singleton, alias)
|
||||||
|
|
||||||
|
|
||||||
class SingletonTests(_BaseSingletonTestCase, unittest.TestCase):
|
class SingletonTests(_BaseSingletonTestCase, unittest.TestCase):
|
||||||
|
|
||||||
|
@ -445,6 +489,16 @@ class ThreadLocalSingletonTests(_BaseSingletonTestCase, unittest.TestCase):
|
||||||
|
|
||||||
self.assertIsNot(instance1, instance2)
|
self.assertIsNot(instance1, instance2)
|
||||||
|
|
||||||
|
def test_reset_clean(self):
|
||||||
|
provider = providers.ThreadLocalSingleton(Example)
|
||||||
|
instance1 = provider()
|
||||||
|
|
||||||
|
provider.reset()
|
||||||
|
provider.reset()
|
||||||
|
|
||||||
|
instance2 = provider()
|
||||||
|
self.assertIsNot(instance1, instance2)
|
||||||
|
|
||||||
|
|
||||||
class DelegatedThreadLocalSingletonTests(_BaseSingletonTestCase,
|
class DelegatedThreadLocalSingletonTests(_BaseSingletonTestCase,
|
||||||
unittest.TestCase):
|
unittest.TestCase):
|
||||||
|
|
Loading…
Reference in New Issue
Block a user