Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Differences in using retry with AsyncMock objects on Python 3.9 and Python 3.8 vs. newer Python #456

Open
MatthewFlamm opened this issue Apr 26, 2024 · 1 comment

Comments

@MatthewFlamm
Copy link

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
@MatthewFlamm
Copy link
Author

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant