Skip to content

How to properly annotate a decorator that decorates both regular functions and async functions? #2142

@MapleCCC

Description

@MapleCCC

I am writing a decorator that caches the result of the decorated function. Should be a trivial use case. The decorator supports both regular function and async function. However, I don't seem to be able to find a way to add type annotations that can make Pyright happy.

My original code was like this:

import inspect
from collections.abc import Callable
from functools import wraps
from typing import TypeVar

from typing_extensions import ParamSpec

P = ParamSpec("P")
R = TypeVar("R")

def cache(func: Callable[P, R]) -> Callable[P, R]:

    if inspect.iscoroutinefunction(func):

        @wraps(func)
        async def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
            result = await func(*args, **kwargs)
            # Omitted the code to add the result to cache
            return result

    else:

        @wraps(func)
        def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
            result = func(*args, **kwargs)
            # Ommitted the code to add the result to cache
            return result

    return wrapper

Pyright complained that the the type R is not awaitable, so I make some changes to comply:

import inspect
from collections.abc import Awaitable, Callable
from functools import wraps
from typing import TypeVar, Union

from typing_extensions import ParamSpec

P = ParamSpec("P")
R = TypeVar("R")


def cache(
    func: Union[Callable[P, R], Callable[P, Awaitable[R]]]
) -> Union[Callable[P, R], Callable[P, Awaitable[R]]]:

    if inspect.iscoroutinefunction(func):

        @wraps(func)
        async def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
            result = await func(*args, **kwargs)
            # Omitted the code to add the result to cache
            return result

    else:

        @wraps(func)
        def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
            result = func(*args, **kwargs)
            # Ommitted the code to add the result to cache
            return result

    return wrapper

Pyright still complained that the type R is not awaitable. It seems the problem originates from that Pyright didn't narrow the type of func after the inspect.iscoroutinefunction call?

I don't know how to fix the type annotations here. I find adding type annotations in situations involving higher order functions quite tricky.

Metadata

Metadata

Assignees

No one assigned

    Labels

    as designedNot a bug, working as intended

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions