pythonpython-asynciocontextmanager

How to assign yield value to variable


I was trying to create a dynamic decorator function for both sync and async function and it is working fine, but I cannot use yield value further.

def retry(f) -> Any:
    @contextmanager
    def retry_context(*args):
        # response = False
        i = 0
        response_ok = False

        retries = DEFAULT_RETRIES
        status_retries = DEFAULT_STATUS_RETRIES

        retry_count = 0
        while i < retries and response_ok is False:
            retry_count = retry_count+1
            yield response
            i += 1
            if response.status_code not in status_retries:
                response_ok = True
                logger.debug("Response {}".format(response))
        if not response_ok:
            logger.warning("Response ERROR: {}".format(response))
        return response


    def wrapper(*args, **kwargs) -> Any:
        with retry_context(*args):
            return f(*args, **kwargs)

    async def async_wrapper(*args, **kwargs) -> Any:
        with retry_context(*args):
            return await f(*args, **kwargs)

    if asyncio.iscoroutinefunction(f):
        return async_wrapper
    return wrapper

I have seen many other answers for assigning value to yield but none is solving my issue, as I also want to use it further. Any other solution is also welcome.


Solution

  • The Context Manager protocol (i.e., the with statement ant its associated special methods and helper functions in the contextlib module), or "async context manager" as well, in this case - is not designed and cannot be made to work as a "retry" mechanism by itself.

    That means: the context manager, either a generator decorated with the contextlib.contextmanager decorator, or a full-fledged class with an __enter__ and __exit__ methods will only, and can only by the way the language works, pass control to the body of the with statement a single time. For a contextmanager decorated generator, that means it must run a yield expression a single time in its lifetime.

    You should either use an outer while loop with a differently designed class, or a ready-made retry library to achieve your goals there.

    As for a nice 3rd party library implementing retries with a lot of options and care for edgecases, you can try "tenacity": https://github.com/jd/tenacity