Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Python decorator for backoff retries in class

I have a class named Client which tries to connect to a server via Websockets. I tried to make a decorator to retry if it couldn’t connect to the server but I get the error

ValueError: a coroutine was expected, got <__main__.Client object at 0x000001BE86D71930>

The code is this:

def retry(fnc):
    def f_retry(self):
        tries = 2
        delay = 2
        backoff = 2
        while tries > 1:
            try:
                return self.main()
            except Exception as e:
                msg = "%s, Retrying in %d seconds..." % (str(e), delay)
                print(msg)
                time.sleep(delay)
                tries -= 1
                delay *= backoff
        return self.main()

    return f_retry

class Client():
    def __init__(self):
        self.name= None
        
    @retry
    async def main(self):
        try : 
            async with websockets.connect(
                'ws://localhost:8000',
            ) as ws:
                logging.info("Connected to the server")
        except Exception as e:
            print(e)

if __name__ == '__main__':
    client = Client()
    asyncio.run(client.main())

This algorithm is inspired from This other question

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

>Solution :

Because you are decorating an async function, you need to make your wrapper async and await the result of the function:

import asyncio
from functools import wrap
def retry(fnc):
    @wraps(fnc)
    async def f_retry(*args, **kwargs):
        tries = 2
        delay = 2
        backoff = 2
        while tries > 1:
            try:
                return await fnc(*args, **kwargs)
            except Exception as e:
                msg = "%s, Retrying in %d seconds..." % (str(e), delay)
                print(msg)
                await asyncio.sleep(delay)
                tries -= 1
                delay *= backoff
        return await fnc(*args, **kwargs)

    return f_retry

I’ve fixed a few more things:

  1. Use functools.wraps
    functools.wraps helps decorators behave more like the functions they wrap.
  2. Make it care about the argument
    Instead of just using self.main, this is now more versatile, can be used for methods and functions with any arguments, and any name.
  3. Fix RecursionError
    Previously, it would call self.main, which was itself. Now it calls the passed function.
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading