Singleton.full_reset() (#391)

* Improve .traverse() typing stubs

* Fix container.reset_singletons()

* Add implementation, tests, and typing stubs

* Add docs and example

* Update changelog
This commit is contained in:
Roman Mogylatov 2021-02-05 18:17:44 -05:00 committed by GitHub
parent 78f623c05b
commit 2fe0e00cef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 2959 additions and 2679 deletions

View File

@ -7,6 +7,13 @@ 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
-------------------
- Add ``singleton.full_reset()`` method to reset all underlying singleton providers.
- Fix ``container.reset_singleton()`` to reset all provider types, not only ``Singleton``.
- Improve ``container.traverse(types=[...])`` and ``provider.traverse(types=[...])`` typing stubs
to return ``types`` -typed iterator.
4.18.0 4.18.0
------ ------
- Add ``container.reset_singleton()`` method to reset container singletons. - Add ``container.reset_singleton()`` method to reset container singletons.

View File

@ -54,6 +54,14 @@ 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.
Method ``.reset()`` resets only current provider. To reset all dependent singleton providers
call ``.full_reset()`` method.
.. literalinclude:: ../../examples/providers/singleton_full_resetting.py
:language: python
:lines: 3-
:emphasize-lines: 25
Using singleton with multiple threads Using singleton with multiple threads
------------------------------------- -------------------------------------

View File

@ -0,0 +1,31 @@
"""`Singleton` provider full resetting example."""
from dependency_injector import containers, providers
class Database:
...
class UserService:
def __init__(self, db: Database):
self.db = db
class Container(containers.DeclarativeContainer):
database = providers.Singleton(Database)
user_service = providers.Singleton(UserService, db=database)
if __name__ == '__main__':
container = Container()
user_service1 = container.user_service()
container.user_service.full_reset()
user_service2 = container.user_service()
assert user_service2 is not user_service1
assert user_service2.db is not user_service1.db

View File

@ -9,15 +9,15 @@ class UserService:
class Container(containers.DeclarativeContainer): class Container(containers.DeclarativeContainer):
user_service_provider = providers.Singleton(UserService) user_service = providers.Singleton(UserService)
if __name__ == '__main__': if __name__ == '__main__':
container = Container() container = Container()
user_service1 = container.user_service_provider() user_service1 = container.user_service()
container.user_service_provider.reset() container.user_service.reset()
user_service2 = container.user_service_provider() user_service2 = container.user_service()
assert user_service2 is not user_service1 assert user_service2 is not user_service1

View File

@ -1620,7 +1620,6 @@ static const char __pyx_k_shutdown[] = "shutdown";
static const char __pyx_k_sub_memo[] = "sub_memo"; static const char __pyx_k_sub_memo[] = "sub_memo";
static const char __pyx_k_traverse[] = "traverse"; static const char __pyx_k_traverse[] = "traverse";
static const char __pyx_k_Container[] = "Container"; static const char __pyx_k_Container[] = "Container";
static const char __pyx_k_Singleton[] = "Singleton";
static const char __pyx_k_container[] = "container"; static const char __pyx_k_container[] = "container";
static const char __pyx_k_decorator[] = "_decorator"; static const char __pyx_k_decorator[] = "_decorator";
static const char __pyx_k_iteritems[] = "iteritems"; static const char __pyx_k_iteritems[] = "iteritems";
@ -1639,6 +1638,7 @@ static const char __pyx_k_isawaitable[] = "isawaitable";
static const char __pyx_k_IS_CONTAINER[] = "__IS_CONTAINER__"; static const char __pyx_k_IS_CONTAINER[] = "__IS_CONTAINER__";
static const char __pyx_k_dependencies[] = "dependencies"; static const char __pyx_k_dependencies[] = "dependencies";
static const char __pyx_k_version_info[] = "version_info"; static const char __pyx_k_version_info[] = "version_info";
static const char __pyx_k_BaseSingleton[] = "BaseSingleton";
static const char __pyx_k_add_metaclass[] = "add_metaclass"; static const char __pyx_k_add_metaclass[] = "add_metaclass";
static const char __pyx_k_all_providers[] = "all_providers"; static const char __pyx_k_all_providers[] = "all_providers";
static const char __pyx_k_asyncio_tasks[] = "asyncio.tasks"; static const char __pyx_k_asyncio_tasks[] = "asyncio.tasks";
@ -1724,6 +1724,7 @@ static const char __pyx_k_Declarative_inversion_of_control_2[] = "Declarative in
static PyObject *__pyx_kp_s_0_can_contain_only_1_instances; static PyObject *__pyx_kp_s_0_can_contain_only_1_instances;
static PyObject *__pyx_kp_s_Abstract_container; static PyObject *__pyx_kp_s_Abstract_container;
static PyObject *__pyx_n_s_AttributeError; static PyObject *__pyx_n_s_AttributeError;
static PyObject *__pyx_n_s_BaseSingleton;
static PyObject *__pyx_n_s_Container; static PyObject *__pyx_n_s_Container;
static PyObject *__pyx_kp_s_Container_0_could_not_be_overrid; static PyObject *__pyx_kp_s_Container_0_could_not_be_overrid;
static PyObject *__pyx_kp_s_Container_0_could_not_be_overrid_2; static PyObject *__pyx_kp_s_Container_0_could_not_be_overrid_2;
@ -1771,7 +1772,6 @@ static PyObject *__pyx_n_s_NotImplementedError;
static PyObject *__pyx_n_s_Object; static PyObject *__pyx_n_s_Object;
static PyObject *__pyx_n_s_Provider; static PyObject *__pyx_n_s_Provider;
static PyObject *__pyx_n_s_Resource; static PyObject *__pyx_n_s_Resource;
static PyObject *__pyx_n_s_Singleton;
static PyObject *__pyx_kp_s_Wiring_requires_Python_3_6_or_ab; static PyObject *__pyx_kp_s_Wiring_requires_Python_3_6_or_ab;
static PyObject *__pyx_n_s__11; static PyObject *__pyx_n_s__11;
static PyObject *__pyx_n_s_add_metaclass; static PyObject *__pyx_n_s_add_metaclass;
@ -6370,13 +6370,13 @@ static PyObject *__pyx_pf_19dependency_injector_10containers_16DynamicContainer_
* provider.apply_overridings() * provider.apply_overridings()
* *
* def reset_singletons(self): # <<<<<<<<<<<<<< * def reset_singletons(self): # <<<<<<<<<<<<<<
* """Reset all container singletons.""" * """Reset container singletons."""
* for provider in self.traverse(types=[providers.Singleton]): * for provider in self.traverse(types=[providers.BaseSingleton]):
*/ */
/* Python wrapper */ /* Python wrapper */
static PyObject *__pyx_pw_19dependency_injector_10containers_16DynamicContainer_34reset_singletons(PyObject *__pyx_self, PyObject *__pyx_v_self); /*proto*/ static PyObject *__pyx_pw_19dependency_injector_10containers_16DynamicContainer_34reset_singletons(PyObject *__pyx_self, PyObject *__pyx_v_self); /*proto*/
static char __pyx_doc_19dependency_injector_10containers_16DynamicContainer_33reset_singletons[] = "Reset all container singletons."; static char __pyx_doc_19dependency_injector_10containers_16DynamicContainer_33reset_singletons[] = "Reset container singletons.";
static PyMethodDef __pyx_mdef_19dependency_injector_10containers_16DynamicContainer_34reset_singletons = {"reset_singletons", (PyCFunction)__pyx_pw_19dependency_injector_10containers_16DynamicContainer_34reset_singletons, METH_O, __pyx_doc_19dependency_injector_10containers_16DynamicContainer_33reset_singletons}; static PyMethodDef __pyx_mdef_19dependency_injector_10containers_16DynamicContainer_34reset_singletons = {"reset_singletons", (PyCFunction)__pyx_pw_19dependency_injector_10containers_16DynamicContainer_34reset_singletons, METH_O, __pyx_doc_19dependency_injector_10containers_16DynamicContainer_33reset_singletons};
static PyObject *__pyx_pw_19dependency_injector_10containers_16DynamicContainer_34reset_singletons(PyObject *__pyx_self, PyObject *__pyx_v_self) { static PyObject *__pyx_pw_19dependency_injector_10containers_16DynamicContainer_34reset_singletons(PyObject *__pyx_self, PyObject *__pyx_v_self) {
PyObject *__pyx_r = 0; PyObject *__pyx_r = 0;
@ -6406,8 +6406,8 @@ static PyObject *__pyx_pf_19dependency_injector_10containers_16DynamicContainer_
/* "dependency_injector/containers.pyx":276 /* "dependency_injector/containers.pyx":276
* def reset_singletons(self): * def reset_singletons(self):
* """Reset all container singletons.""" * """Reset container singletons."""
* for provider in self.traverse(types=[providers.Singleton]): # <<<<<<<<<<<<<< * for provider in self.traverse(types=[providers.BaseSingleton]): # <<<<<<<<<<<<<<
* provider.reset() * provider.reset()
* *
*/ */
@ -6417,7 +6417,7 @@ static PyObject *__pyx_pf_19dependency_injector_10containers_16DynamicContainer_
__Pyx_GOTREF(__pyx_t_2); __Pyx_GOTREF(__pyx_t_2);
__Pyx_GetModuleGlobalName(__pyx_t_3, __pyx_n_s_providers); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 276, __pyx_L1_error) __Pyx_GetModuleGlobalName(__pyx_t_3, __pyx_n_s_providers); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 276, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_3); __Pyx_GOTREF(__pyx_t_3);
__pyx_t_4 = __Pyx_PyObject_GetAttrStr(__pyx_t_3, __pyx_n_s_Singleton); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 276, __pyx_L1_error) __pyx_t_4 = __Pyx_PyObject_GetAttrStr(__pyx_t_3, __pyx_n_s_BaseSingleton); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 276, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_4); __Pyx_GOTREF(__pyx_t_4);
__Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
__pyx_t_3 = PyList_New(1); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 276, __pyx_L1_error) __pyx_t_3 = PyList_New(1); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 276, __pyx_L1_error)
@ -6475,8 +6475,8 @@ static PyObject *__pyx_pf_19dependency_injector_10containers_16DynamicContainer_
__pyx_t_3 = 0; __pyx_t_3 = 0;
/* "dependency_injector/containers.pyx":277 /* "dependency_injector/containers.pyx":277
* """Reset all container singletons.""" * """Reset container singletons."""
* for provider in self.traverse(types=[providers.Singleton]): * for provider in self.traverse(types=[providers.BaseSingleton]):
* provider.reset() # <<<<<<<<<<<<<< * provider.reset() # <<<<<<<<<<<<<<
* *
* *
@ -6502,8 +6502,8 @@ static PyObject *__pyx_pf_19dependency_injector_10containers_16DynamicContainer_
/* "dependency_injector/containers.pyx":276 /* "dependency_injector/containers.pyx":276
* def reset_singletons(self): * def reset_singletons(self):
* """Reset all container singletons.""" * """Reset container singletons."""
* for provider in self.traverse(types=[providers.Singleton]): # <<<<<<<<<<<<<< * for provider in self.traverse(types=[providers.BaseSingleton]): # <<<<<<<<<<<<<<
* provider.reset() * provider.reset()
* *
*/ */
@ -6514,8 +6514,8 @@ static PyObject *__pyx_pf_19dependency_injector_10containers_16DynamicContainer_
* provider.apply_overridings() * provider.apply_overridings()
* *
* def reset_singletons(self): # <<<<<<<<<<<<<< * def reset_singletons(self): # <<<<<<<<<<<<<<
* """Reset all container singletons.""" * """Reset container singletons."""
* for provider in self.traverse(types=[providers.Singleton]): * for provider in self.traverse(types=[providers.BaseSingleton]):
*/ */
/* function exit code */ /* function exit code */
@ -12153,6 +12153,7 @@ static __Pyx_StringTabEntry __pyx_string_tab[] = {
{&__pyx_kp_s_0_can_contain_only_1_instances, __pyx_k_0_can_contain_only_1_instances, sizeof(__pyx_k_0_can_contain_only_1_instances), 0, 0, 1, 0}, {&__pyx_kp_s_0_can_contain_only_1_instances, __pyx_k_0_can_contain_only_1_instances, sizeof(__pyx_k_0_can_contain_only_1_instances), 0, 0, 1, 0},
{&__pyx_kp_s_Abstract_container, __pyx_k_Abstract_container, sizeof(__pyx_k_Abstract_container), 0, 0, 1, 0}, {&__pyx_kp_s_Abstract_container, __pyx_k_Abstract_container, sizeof(__pyx_k_Abstract_container), 0, 0, 1, 0},
{&__pyx_n_s_AttributeError, __pyx_k_AttributeError, sizeof(__pyx_k_AttributeError), 0, 0, 1, 1}, {&__pyx_n_s_AttributeError, __pyx_k_AttributeError, sizeof(__pyx_k_AttributeError), 0, 0, 1, 1},
{&__pyx_n_s_BaseSingleton, __pyx_k_BaseSingleton, sizeof(__pyx_k_BaseSingleton), 0, 0, 1, 1},
{&__pyx_n_s_Container, __pyx_k_Container, sizeof(__pyx_k_Container), 0, 0, 1, 1}, {&__pyx_n_s_Container, __pyx_k_Container, sizeof(__pyx_k_Container), 0, 0, 1, 1},
{&__pyx_kp_s_Container_0_could_not_be_overrid, __pyx_k_Container_0_could_not_be_overrid, sizeof(__pyx_k_Container_0_could_not_be_overrid), 0, 0, 1, 0}, {&__pyx_kp_s_Container_0_could_not_be_overrid, __pyx_k_Container_0_could_not_be_overrid, sizeof(__pyx_k_Container_0_could_not_be_overrid), 0, 0, 1, 0},
{&__pyx_kp_s_Container_0_could_not_be_overrid_2, __pyx_k_Container_0_could_not_be_overrid_2, sizeof(__pyx_k_Container_0_could_not_be_overrid_2), 0, 0, 1, 0}, {&__pyx_kp_s_Container_0_could_not_be_overrid_2, __pyx_k_Container_0_could_not_be_overrid_2, sizeof(__pyx_k_Container_0_could_not_be_overrid_2), 0, 0, 1, 0},
@ -12200,7 +12201,6 @@ static __Pyx_StringTabEntry __pyx_string_tab[] = {
{&__pyx_n_s_Object, __pyx_k_Object, sizeof(__pyx_k_Object), 0, 0, 1, 1}, {&__pyx_n_s_Object, __pyx_k_Object, sizeof(__pyx_k_Object), 0, 0, 1, 1},
{&__pyx_n_s_Provider, __pyx_k_Provider, sizeof(__pyx_k_Provider), 0, 0, 1, 1}, {&__pyx_n_s_Provider, __pyx_k_Provider, sizeof(__pyx_k_Provider), 0, 0, 1, 1},
{&__pyx_n_s_Resource, __pyx_k_Resource, sizeof(__pyx_k_Resource), 0, 0, 1, 1}, {&__pyx_n_s_Resource, __pyx_k_Resource, sizeof(__pyx_k_Resource), 0, 0, 1, 1},
{&__pyx_n_s_Singleton, __pyx_k_Singleton, sizeof(__pyx_k_Singleton), 0, 0, 1, 1},
{&__pyx_kp_s_Wiring_requires_Python_3_6_or_ab, __pyx_k_Wiring_requires_Python_3_6_or_ab, sizeof(__pyx_k_Wiring_requires_Python_3_6_or_ab), 0, 0, 1, 0}, {&__pyx_kp_s_Wiring_requires_Python_3_6_or_ab, __pyx_k_Wiring_requires_Python_3_6_or_ab, sizeof(__pyx_k_Wiring_requires_Python_3_6_or_ab), 0, 0, 1, 0},
{&__pyx_n_s__11, __pyx_k__11, sizeof(__pyx_k__11), 0, 0, 1, 1}, {&__pyx_n_s__11, __pyx_k__11, sizeof(__pyx_k__11), 0, 0, 1, 1},
{&__pyx_n_s_add_metaclass, __pyx_k_add_metaclass, sizeof(__pyx_k_add_metaclass), 0, 0, 1, 1}, {&__pyx_n_s_add_metaclass, __pyx_k_add_metaclass, sizeof(__pyx_k_add_metaclass), 0, 0, 1, 1},
@ -12648,8 +12648,8 @@ static CYTHON_SMALL_CODE int __Pyx_InitCachedConstants(void) {
* provider.apply_overridings() * provider.apply_overridings()
* *
* def reset_singletons(self): # <<<<<<<<<<<<<< * def reset_singletons(self): # <<<<<<<<<<<<<<
* """Reset all container singletons.""" * """Reset container singletons."""
* for provider in self.traverse(types=[providers.Singleton]): * for provider in self.traverse(types=[providers.BaseSingleton]):
*/ */
__pyx_tuple__52 = PyTuple_Pack(2, __pyx_n_s_self_2, __pyx_n_s_provider); if (unlikely(!__pyx_tuple__52)) __PYX_ERR(0, 274, __pyx_L1_error) __pyx_tuple__52 = PyTuple_Pack(2, __pyx_n_s_self_2, __pyx_n_s_provider); if (unlikely(!__pyx_tuple__52)) __PYX_ERR(0, 274, __pyx_L1_error)
__Pyx_GOTREF(__pyx_tuple__52); __Pyx_GOTREF(__pyx_tuple__52);
@ -13645,8 +13645,8 @@ if (!__Pyx_RefNanny) {
* provider.apply_overridings() * provider.apply_overridings()
* *
* def reset_singletons(self): # <<<<<<<<<<<<<< * def reset_singletons(self): # <<<<<<<<<<<<<<
* """Reset all container singletons.""" * """Reset container singletons."""
* for provider in self.traverse(types=[providers.Singleton]): * for provider in self.traverse(types=[providers.BaseSingleton]):
*/ */
__pyx_t_10 = __Pyx_CyFunction_New(&__pyx_mdef_19dependency_injector_10containers_16DynamicContainer_34reset_singletons, 0, __pyx_n_s_DynamicContainer_reset_singleton, NULL, __pyx_n_s_dependency_injector_containers, __pyx_d, ((PyObject *)__pyx_codeobj__53)); if (unlikely(!__pyx_t_10)) __PYX_ERR(0, 274, __pyx_L1_error) __pyx_t_10 = __Pyx_CyFunction_New(&__pyx_mdef_19dependency_injector_10containers_16DynamicContainer_34reset_singletons, 0, __pyx_n_s_DynamicContainer_reset_singleton, NULL, __pyx_n_s_dependency_injector_containers, __pyx_d, ((PyObject *)__pyx_codeobj__53)); if (unlikely(!__pyx_t_10)) __PYX_ERR(0, 274, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_10); __Pyx_GOTREF(__pyx_t_10);

View File

@ -21,6 +21,7 @@ from .providers import Provider
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')
TT = TypeVar('TT')
class Container: class Container:
@ -46,10 +47,10 @@ class Container:
def apply_container_providers_overridings(self) -> None: ... def apply_container_providers_overridings(self) -> None: ...
def reset_singletons(self) -> None: ... def reset_singletons(self) -> None: ...
@overload @overload
def traverse(self, types: Optional[Sequence[Type]] = None) -> Iterator[Provider]: ... def traverse(self, types: Optional[Sequence[TT]] = None) -> Iterator[TT]: ...
@classmethod @classmethod
@overload @overload
def traverse(cls, types: Optional[Sequence[Type]] = None) -> Iterator[Provider]: ... def traverse(cls, types: Optional[Sequence[TT]] = None) -> Iterator[TT]: ...
class DynamicContainer(Container): ... class DynamicContainer(Container): ...

View File

@ -272,8 +272,8 @@ class DynamicContainer(Container):
provider.apply_overridings() provider.apply_overridings()
def reset_singletons(self): def reset_singletons(self):
"""Reset all container singletons.""" """Reset container singletons."""
for provider in self.traverse(types=[providers.Singleton]): for provider in self.traverse(types=[providers.BaseSingleton]):
provider.reset() provider.reset()

File diff suppressed because it is too large Load Diff

View File

@ -36,6 +36,7 @@ from . import resources
Injection = Any Injection = Any
T = TypeVar('T') T = TypeVar('T')
TT = TypeVar('TT')
class OverridingContext: class OverridingContext:
@ -76,7 +77,7 @@ class Provider(Generic[T]):
def is_async_mode_undefined(self) -> bool: ... def is_async_mode_undefined(self) -> bool: ...
@property @property
def related(self) -> _Iterator[Provider]: ... def related(self) -> _Iterator[Provider]: ...
def traverse(self, types: Optional[_Iterable[Type]] = None) -> _Iterator[Provider]: ... def traverse(self, types: Optional[_Iterable[TT]] = None) -> _Iterator[TT]: ...
def _copy_overridings(self, copied: Provider, memo: Optional[_Dict[Any, Any]]) -> None: ... def _copy_overridings(self, copied: Provider, memo: Optional[_Dict[Any, Any]]) -> None: ...
@ -266,6 +267,7 @@ class BaseSingleton(Provider[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) -> None: ...
def full_reset(self) -> None: ...
class Singleton(BaseSingleton[T]): ... class Singleton(BaseSingleton[T]): ...

View File

@ -2468,6 +2468,15 @@ cdef class BaseSingleton(Provider):
""" """
raise NotImplementedError() raise NotImplementedError()
def full_reset(self):
"""Reset cached instance in current and all underlying singletons, if any.
:rtype: None
"""
self.reset()
for provider in self.traverse(types=[BaseSingleton]):
provider.reset()
@property @property
def related(self): def related(self):
"""Return related providers generator.""" """Return related providers generator."""

View File

@ -355,6 +355,37 @@ class _BaseSingletonTestCase(object):
self.assertIsNot(instance1, instance2) self.assertIsNot(instance1, instance2)
def test_reset_with_singleton(self):
dependent_singleton = providers.Singleton(object)
provider = self.singleton_cls(dict, dependency=dependent_singleton)
dependent_instance = dependent_singleton()
instance1 = provider()
self.assertIs(instance1['dependency'], dependent_instance)
provider.reset()
instance2 = provider()
self.assertIs(instance1['dependency'], dependent_instance)
self.assertIsNot(instance1, instance2)
def test_full_reset(self):
dependent_singleton = providers.Singleton(object)
provider = self.singleton_cls(dict, dependency=dependent_singleton)
dependent_instance1 = dependent_singleton()
instance1 = provider()
self.assertIs(instance1['dependency'], dependent_instance1)
provider.full_reset()
dependent_instance2 = dependent_singleton()
instance2 = provider()
self.assertIsNot(instance2['dependency'], dependent_instance1)
self.assertIsNot(dependent_instance1, dependent_instance2)
self.assertIsNot(instance1, instance2)
class SingletonTests(_BaseSingletonTestCase, unittest.TestCase): class SingletonTests(_BaseSingletonTestCase, unittest.TestCase):