Merge branch 'release/4.10.2' into master

This commit is contained in:
Roman Mogylatov 2021-01-19 17:51:00 -05:00
commit 9f7dbe89f6
6 changed files with 4316 additions and 3352 deletions

View File

@ -7,6 +7,20 @@ 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
-------------------
- Fix a bug in the ``Configuration`` provider: strict mode didn't work when provider
is overridden by ``None``.
See issue: `#358#issuecomment-761607432 <https://github.com/ets-labs/python-dependency-injector/issues/358#issuecomment-761607432>`_.
Many thanks to `Stefano Frazzetto <https://github.com/StefanoFrazzetto>`_ for reporting the issue.
4.10.2
------
- Fix a bug in ``Resource`` that cause failure when async resource depends on
another async resource.
See issue `#361 <https://github.com/ets-labs/python-dependency-injector/issues/361>`_.
Thanks `@kolypto <https://github.com/kolypto>`_ for the bug report.
4.10.1 4.10.1
------ ------
- Fix a Python 3.9 specific bug in ``wiring`` module: introspection doesn't work for - Fix a Python 3.9 specific bug in ``wiring`` module: introspection doesn't work for

View File

@ -1,6 +1,6 @@
"""Top-level package.""" """Top-level package."""
__version__ = '4.10.1' __version__ = '4.10.2'
"""Version number. """Version number.
:type: str :type: str

File diff suppressed because it is too large Load Diff

View File

@ -1471,12 +1471,14 @@ cdef class Configuration(Object):
:return: Option value. :return: Option value.
:rtype: Any :rtype: Any
""" """
keys = selector.split('.')
value = self.__call__() value = self.__call__()
if value is None: if value is None:
if self.__strict or required:
raise Error('Undefined configuration option "{0}.{1}"'.format(self.__name, selector))
return None return None
keys = selector.split('.')
while len(keys) > 0: while len(keys) > 0:
key = keys.pop(0) key = keys.pop(0)
value = value.get(key, self.UNDEFINED) value = value.get(key, self.UNDEFINED)
@ -1500,9 +1502,9 @@ cdef class Configuration(Object):
:return: Overriding context. :return: Overriding context.
:rtype: :py:class:`OverridingContext` :rtype: :py:class:`OverridingContext`
""" """
keys = selector.split('.')
original_value = current_value = deepcopy(self.__call__()) original_value = current_value = deepcopy(self.__call__())
keys = selector.split('.')
while len(keys) > 0: while len(keys) > 0:
key = keys.pop(0) key = keys.pop(0)
if len(keys) == 0: if len(keys) == 0:
@ -2967,7 +2969,7 @@ cdef class Resource(Provider):
self.__kwargs_len, self.__kwargs_len,
) )
self.__initialized = True self.__initialized = True
return self._create_init_future(initializer.__anext__(), initializer.asend) return self._create_async_gen_init_future(initializer)
elif callable(self.__initializer): elif callable(self.__initializer):
self.__resource = __call( self.__resource = __call(
self.__initializer, self.__initializer,
@ -2995,6 +2997,18 @@ cdef class Resource(Provider):
return future return future
def _create_async_gen_init_future(self, initializer):
if inspect.isasyncgen(initializer):
return self._create_init_future(initializer.__anext__(), initializer.asend)
future = asyncio.Future()
create_initializer = asyncio.ensure_future(initializer)
create_initializer.add_done_callback(functools.partial(self._async_create_gen_callback, future))
self.__resource = future
return future
def _async_init_callback(self, initializer, shutdowner=None): def _async_init_callback(self, initializer, shutdowner=None):
try: try:
resource = initializer.result() resource = initializer.result()
@ -3005,6 +3019,14 @@ cdef class Resource(Provider):
self.__resource = resource self.__resource = resource
self.__shutdowner = shutdowner self.__shutdowner = shutdowner
def _async_create_gen_callback(self, future, initializer_future):
initializer = initializer_future.result()
init_future = self._create_init_future(initializer.__anext__(), initializer.asend)
init_future.add_done_callback(functools.partial(self._async_trigger_result, future))
def _async_trigger_result(self, future, future_result):
future.set_result(future_result.result())
def _create_shutdown_future(self, shutdown_future): def _create_shutdown_future(self, shutdown_future):
future = asyncio.Future() future = asyncio.Future()
shutdown_future = asyncio.ensure_future(shutdown_future) shutdown_future = asyncio.ensure_future(shutdown_future)

View File

@ -214,6 +214,12 @@ class ConfigTests(unittest.TestCase):
with self.assertRaisesRegex(errors.Error, 'Undefined configuration option "config.a"'): with self.assertRaisesRegex(errors.Error, 'Undefined configuration option "config.a"'):
self.config.a() self.config.a()
def test_value_of_undefined_option_with_root_none_in_strict_mode(self):
self.config = providers.Configuration(strict=True)
self.config.override(None)
with self.assertRaisesRegex(errors.Error, 'Undefined configuration option "config.a"'):
self.config.a()
def test_value_of_defined_none_option_in_strict_mode(self): def test_value_of_defined_none_option_in_strict_mode(self):
self.config = providers.Configuration(strict=True) self.config = providers.Configuration(strict=True)
self.config.from_dict({'a': None}) self.config.from_dict({'a': None})

View File

@ -461,6 +461,43 @@ class AsyncResourceTest(AsyncTestCase):
self.assertFalse(provider.initialized) self.assertFalse(provider.initialized)
self.assertTrue(provider.is_async_mode_enabled()) self.assertTrue(provider.is_async_mode_enabled())
def test_init_with_dependency_to_other_resource(self):
# See: https://github.com/ets-labs/python-dependency-injector/issues/361
async def init_db_connection(db_url: str):
await asyncio.sleep(0.001)
yield {'connection': 'ok', 'url': db_url}
async def init_user_session(db):
await asyncio.sleep(0.001)
yield {'session': 'ok', 'db': db}
class Container(containers.DeclarativeContainer):
config = providers.Configuration()
db_connection = providers.Resource(
init_db_connection,
db_url=config.db_url,
)
user_session = providers.Resource(
init_user_session,
db=db_connection
)
async def main():
container = Container(config={'db_url': 'postgres://...'})
try:
return await container.user_session()
finally:
await container.shutdown_resources()
result = self._run(main())
self.assertEqual(
result,
{'session': 'ok', 'db': {'connection': 'ok', 'url': 'postgres://...'}},
)
def test_init_and_shutdown_methods(self): def test_init_and_shutdown_methods(self):
async def _init(): async def _init():
await asyncio.sleep(0.001) await asyncio.sleep(0.001)