Implement lazy initialization and improve copying for Resource provider

This commit is contained in:
Roman Mogylatov 2021-03-09 08:26:02 -05:00
parent 17ecd6c8c0
commit f180f493fa
6 changed files with 3354 additions and 3037 deletions

View File

@ -1339,12 +1339,12 @@ struct __pyx_obj_19dependency_injector_9providers_Dict {
*
*
* cdef class Resource(Provider): # <<<<<<<<<<<<<<
* cdef object __initializer
* cdef object __provides
* cdef bint __initialized
*/
struct __pyx_obj_19dependency_injector_9providers_Resource {
struct __pyx_obj_19dependency_injector_9providers_Provider __pyx_base;
PyObject *__pyx___initializer;
PyObject *__pyx___provides;
int __pyx___initialized;
PyObject *__pyx___shutdowner;
PyObject *__pyx___resource;
@ -2102,7 +2102,7 @@ static struct __pyx_vtabstruct_19dependency_injector_9providers_Dict *__pyx_vtab
*
*
* cdef class Resource(Provider): # <<<<<<<<<<<<<<
* cdef object __initializer
* cdef object __provides
* cdef bint __initialized
*/

File diff suppressed because it is too large Load Diff

View File

@ -203,7 +203,7 @@ cdef class Dict(Provider):
cdef class Resource(Provider):
cdef object __initializer
cdef object __provides
cdef bint __initialized
cdef object __shutdowner
cdef object __resource

View File

@ -351,19 +351,20 @@ class Dict(Provider[_Dict]):
class Resource(Provider[T]):
@overload
def __init__(self, initializer: Type[resources.Resource[T]], *args: Injection, **kwargs: Injection) -> None: ...
def __init__(self, provides: Optional[Type[resources.Resource[T]]] = None, *args: Injection, **kwargs: Injection) -> None: ...
@overload
def __init__(self, initializer: Type[resources.AsyncResource[T]], *args: Injection, **kwargs: Injection) -> None: ...
def __init__(self, provides: Optional[Type[resources.AsyncResource[T]]] = None, *args: Injection, **kwargs: Injection) -> None: ...
@overload
def __init__(self, initializer: _Callable[..., _Iterator[T]], *args: Injection, **kwargs: Injection) -> None: ...
def __init__(self, provides: Optional[_Callable[..., _Iterator[T]]] = None, *args: Injection, **kwargs: Injection) -> None: ...
@overload
def __init__(self, initializer: _Callable[..., _AsyncIterator[T]], *args: Injection, **kwargs: Injection) -> None: ...
def __init__(self, provides: Optional[_Callable[..., _AsyncIterator[T]]] = None, *args: Injection, **kwargs: Injection) -> None: ...
@overload
def __init__(self, initializer: _Callable[..., _Coroutine[Injection, Injection, T]], *args: Injection, **kwargs: Injection) -> None: ...
def __init__(self, provides: Optional[_Callable[..., _Coroutine[Injection, Injection, T]]] = None, *args: Injection, **kwargs: Injection) -> None: ...
@overload
def __init__(self, initializer: _Callable[..., T], *args: Injection, **kwargs: Injection) -> None: ...
def __init__(self, provides: Optional[_Callable[..., T]] = None, *args: Injection, **kwargs: Injection) -> None: ...
@property
def initializer(self) -> _Callable[..., Any]: ...
def provides(self) -> Optional[_Callable[..., Any]]: ...
def set_provides(self, provides: Optional[Any]) -> Resource[T]: ...
@property
def args(self) -> Tuple[Injection]: ...
def add_args(self, *args: Injection) -> Resource[T]: ...

View File

@ -3218,8 +3218,10 @@ cdef class Dict(Provider):
cdef class Resource(Provider):
"""Resource provider provides a component with initialization and shutdown."""
def __init__(self, initializer, *args, **kwargs):
self.__initializer = initializer
def __init__(self, provides=None, *args, **kwargs):
self.__provides = None
self.set_provides(provides)
self.__initialized = False
self.__resource = None
self.__shutdowner = None
@ -3243,11 +3245,11 @@ cdef class Resource(Provider):
if self.__initialized:
raise Error('Can not copy initialized resource')
copied = self.__class__(
self.__initializer,
*deepcopy(self.args, memo),
**deepcopy(self.kwargs, memo),
)
copied = _memorized_duplicate(self, memo)
copied.set_provides(_copy_if_provider(self.provides, memo))
copied.set_args(*deepcopy(self.args, memo))
copied.set_kwargs(**deepcopy(self.kwargs, memo))
self._copy_overridings(copied, memo)
return copied
@ -3257,12 +3259,17 @@ cdef class Resource(Provider):
:rtype: str
"""
return represent_provider(provider=self, provides=self.__initializer)
return represent_provider(provider=self, provides=self.provides)
@property
def initializer(self):
"""Return initializer."""
return self.__initializer
def provides(self):
"""Return provider's provides."""
return self.__provides
def set_provides(self, provides):
"""Set provider's provides."""
self.__provides = provides
return self
@property
def args(self):
@ -3387,7 +3394,7 @@ cdef class Resource(Provider):
@property
def related(self):
"""Return related providers generator."""
yield from filter(is_provider, [self.__initializer])
yield from filter(is_provider, [self.provides])
yield from filter(is_provider, self.args)
yield from filter(is_provider, self.kwargs.values())
yield from super().related
@ -3396,8 +3403,8 @@ cdef class Resource(Provider):
if self.__initialized:
return self.__resource
if self._is_resource_subclass(self.__initializer):
initializer = self.__initializer()
if self._is_resource_subclass(self.__provides):
initializer = self.__provides()
self.__resource = __call(
initializer.init,
args,
@ -3408,8 +3415,8 @@ cdef class Resource(Provider):
self.__kwargs_len,
)
self.__shutdowner = initializer.shutdown
elif self._is_async_resource_subclass(self.__initializer):
initializer = self.__initializer()
elif self._is_async_resource_subclass(self.__provides):
initializer = self.__provides()
async_init = __call(
initializer.init,
args,
@ -3421,9 +3428,9 @@ cdef class Resource(Provider):
)
self.__initialized = True
return self._create_init_future(async_init, initializer.shutdown)
elif inspect.isgeneratorfunction(self.__initializer):
elif inspect.isgeneratorfunction(self.__provides):
initializer = __call(
self.__initializer,
self.__provides,
args,
self.__args,
self.__args_len,
@ -3433,9 +3440,9 @@ cdef class Resource(Provider):
)
self.__resource = next(initializer)
self.__shutdowner = initializer.send
elif iscoroutinefunction(self.__initializer):
elif iscoroutinefunction(self.__provides):
initializer = __call(
self.__initializer,
self.__provides,
args,
self.__args,
self.__args_len,
@ -3445,9 +3452,9 @@ cdef class Resource(Provider):
)
self.__initialized = True
return self._create_init_future(initializer)
elif isasyncgenfunction(self.__initializer):
elif isasyncgenfunction(self.__provides):
initializer = __call(
self.__initializer,
self.__provides,
args,
self.__args,
self.__args_len,
@ -3457,9 +3464,9 @@ cdef class Resource(Provider):
)
self.__initialized = True
return self._create_async_gen_init_future(initializer)
elif callable(self.__initializer):
elif callable(self.__provides):
self.__resource = __call(
self.__initializer,
self.__provides,
args,
self.__args,
self.__args_len,
@ -4506,6 +4513,12 @@ cpdef object _memorized_duplicate(object instance, dict memo):
return copied
cpdef object _copy_if_provider(object instance, dict memo):
if not is_provider(instance):
return instance
return deepcopy(instance, memo)
cpdef str _class_qualname(object instance):
name = getattr(instance.__class__, '__qualname__', None)
if not name:

View File

@ -29,6 +29,16 @@ class ResourceTests(unittest.TestCase):
def test_is_provider(self):
self.assertTrue(providers.is_provider(providers.Resource(init_fn)))
def test_init_optional_provides(self):
provider = providers.Resource()
provider.set_provides(init_fn)
self.assertIs(provider.provides, init_fn)
self.assertEqual(provider(), (tuple(), dict()))
def test_set_provides_returns_self(self):
provider = providers.Resource()
self.assertIs(provider.set_provides(init_fn), provider)
def test_provided_instance_provider(self):
provider = providers.Resource(init_fn)
self.assertIsInstance(provider.provided, providers.ProvidedInstance)