tenacity
tenacity copied to clipboard
Differences in using `retry` with `AsyncMock` objects on Python 3.9 and Python 3.8 vs. newer Python
I'm trying to write a test using AsyncMock object with a side_effect
error that is then wrapped with tenacity.retry
. In Python 3.10+, my tests work, but in Python 3.8 and Python 3.9 the side effect is not swallowed by retry
. See below for a MRE.
from unittest.mock import AsyncMock
from tenacity import retry
async def test_mock_retry():
amock = AsyncMock()
amock.side_effect = [ValueError, None]
retry_func = retry(amock)
await retry_func()
assert amock.call_count == 2
This passes on Python 3.10, Python 3.11, and Python 3.12 but fails on Python 3.8 and Python 3.9 for me.
traceback from Python 3.9:
_______________________________ test_mock_retry ________________________________
async def test_mock_retry():
amock = AsyncMock()
amock.side_effect = [ValueError, None]
retry_func = retry(amock)
> await retry_func()
tests/test_async_mock.py:10:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <AsyncMock id='140142953906240'>, args = (), kwargs = {}, _call = call()
effect = <list_iterator object at 0x7f7593046850>, result = <class 'ValueError'>
async def _execute_mock_call(self, /, *args, **kwargs):
# This is nearly just like super(), except for special handling
# of coroutines
_call = _Call((args, kwargs), two=True)
self.await_count += 1
self.await_args = _call
self.await_args_list.append(_call)
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
raise effect
elif not _callable(effect):
try:
result = next(effect)
except StopIteration:
# It is impossible to propogate a StopIteration
# through coroutines because of PEP 479
raise StopAsyncIteration
if _is_exception(result):
> raise result
E ValueError
/usr/local/lib/python3.9/unittest/mock.py:2162: ValueError
This is a workaround that works for my use case:
from unittest.mock import AsyncMock
from tenacity import retry
async def test_mock_retry():
amock = AsyncMock()
amock.side_effect = [ValueError, None]
async def wrap_amock(*args, **kwargs):
return await amock(*args, **kwargs)
retry_func = retry(wrap_amock)
await retry_func()
assert amock.call_count == 2