How to define a three-layer nested Python asynchronous decorator

hello everyone.
This is sync three-layer nested python decorator.

from typing import Callable

def condition_retry(condition: Callable) -> Callable:
    count = 0
    def outer(func: Callable):
        def wrapper(*args, **kwargs):
            nonlocal count
            if count > 0:
                print(f"{func.__name__} start.")
            rt = func(*args, **kwargs)
            if condition(rt):
                count += 1
                print(f"{func.__name__} retry {count} times.")
                return wrapper(*args, **kwargs)
            count = 0
            return rt
        return wrapper
    return outer

@condition_retry(lambda x: x!="a")
def func(a):
    print(a)
    return a

func("a")

How to define an asynchronous decorator with the same version?

This is a final result. successful!

import asyncio
from typing import Callable


def condition_retry(condition: Callable) -> Callable:
    count = 0
    def outer(func: Callable):
        async def wrapper(*args, **kwargs):
            nonlocal count
            if count > 0:
                print(f"{func.__name__} start.")
            rt = await func(*args, **kwargs)
            if condition(rt):
                count += 1
                print(f"{func.__name__} retry {count} times.")
                return await wrapper(*args, **kwargs)
            count = 0
            return rt
        return wrapper
    return outer

@condition_retry(lambda x: x!="a")
async def func(a):
    print(a)
    return a

asyncio.run(func("a"))

I assume that def func here is intended to become asynchronous both before and after decoration. If that’s the case, the most important functions are func itself, and wrapper which is returned by the decorator. Here’s how it goes:

  1. You call condition_retry, passing it an argument. Whatever it returns is a decorator.
  2. You construct func by calling the decorator (here called “outer”) and taking whatever it returns.
  3. The decorator works by constructing another function (here called “wrapper”), which will call the original function.

The actual construction and decoration work is entirely synchronous (no awaiting anything), so the only two functions that will have any asynchronicity will be async def func(a) and async def wrapper(*,**) - they’re the two that do most of the work. That would then also mean you’ll want to await func(*,**) and possibly return await wrapper(*,**) for the recursion, although it’s also possible you may want to rewrite that as a while loop instead.

Thanks. I get it.