Had a complete headache trying to figure out how a decorator as a class can maintain the possible async properties of a method. The solution is actually very simple. When called, use inspect.iscoroutinefunction to check whether it is a coroutine, and return again an async method!

The example adds given paths to a registry,

import inspect
from functools import wraps

paths_registry = []

class route(object):
    def __init__(self, path: str, **kwargs) -> None:
        self._path = path

    def __call__(self, fn):
        paths_registry.append(
            {
                "path": self._path,
            }
        )

        @wraps(fn)
        def decorated(*args, **kwargs):
            return fn(*args, **kwargs)

        @wraps(fn)
        async def asyncdecorated(*args, **kwargs):
            return await fn(*args, **kwargs)

        if inspect.iscoroutinefunction(fn):
            return asyncdecorated
        return decorated

A method with this decorator would look like:

@route('hello')
async def hello():
    print("haai")

and the method with decorator is still a coroutine:

>>> inspect.iscoroutinefunction(hello)
True