如何使装饰器与异步函数一起工作?

Har*_*rvs 5 python api async-await python-asyncio

通用问题:

我有一个装饰器@some_package.decorator期望函数包装为返回类型T

### runs
@some_package.decorator
def my_func(param) -> int:
    return len(param)

### I also want to be able to wrap an async function
@some_package.decorator
async def my_func(param):
    await asyncio.sleep(1)
    return len(param)
Run Code Online (Sandbox Code Playgroud)

然而,该函数可以是syncor async,因此可能返回coroutine[Any]

我怎样才能使装饰器await具有功能

我的具体用例:我有一些代码在以下情况下运行良好sync=True

import uplink
class MyAPI(Consumer):
        def __init__(
        self,
        password: str,
        sync: bool = True,
        auto_auth: bool = True,
        **kwargs
    ):
        self.sync = sync
        self.client = None

        if not self.sync:
            self.client = AiohttpClient()

        # initialise the super class
        super(GLinet, self).__init__(client=self.client, **kwargs)

        if auto_auth:
                self._login(password)

    @uplink.returns.json(key="token")
    @uplink.post("router/login")
    def _login(self, pwd: uplink.Field):
        """fetches token"""
Run Code Online (Sandbox Code Playgroud)

然而,当sync=False我使用的 API 使得 _login() 和 async 函数。因此,装饰器uplink.returns.json会感到不安,因为它期望它包装的函数能够证明可以解析的响应,但它却收到了一个 co 例程。

如何使uplink.returns.json接受成为异步函数并等待它可以解析的返回值。

我尝试过这样的事情

def dec(fn):
    if asyncio.iscoroutinefunction(fn):
        @returns.json
        async def wrapper(*args, **kwargs):
            print("wrapping async function")
            print(fn)
            return await fn()
        return wrapper
    else:
        @returns.json
        def wrapper(*args, **kwargs):
            print("wrapping sync function")
            print(fn)
            return fn()
        return wrapper
Run Code Online (Sandbox Code Playgroud)

有些人开发了自己的装饰器,可以接受两种类型的功能,但我还没有发现有人修改过另一个装饰器

def dec(fn):
    if asyncio.iscoroutinefunction(fn):
        @wraps(fn)
        async def wrapper(*args, **kwargs):
            print(fn, args, kwargs)  # <function foo at 0x10952d598> () {}
            await asyncio.sleep(5)
            print("done with wrapper, going to call fn")
            return await fn()
        return wrapper
    else:
        @wraps(fn)
        def wrapper(*args, **kwargs):
            print(fn, args, kwargs)  # <function bar at 0x108fb5a60> () {}
            time.sleep(5)
            print("done with wrapper, going to call fn")
            return fn()
        return wrapper
Run Code Online (Sandbox Code Playgroud)