I was reading PEP-612 and it makes typing a decorator fairly tractable. Also, the example provided in the PEP makes it look pretty easy. This example is copied directly from the PEP:
from typing import ParamSpec, TypeVar
from collections.abc import Callable, Awaitable
P = ParamSpec("P")
R = TypeVar("R")
def add_logging(f: Callable[P, R]) -> Callable[P, Awaitable[R]]:
async def inner(*args: P.args, **kwargs: P.kwargs) -> R:
await log_to_database()
return f(*args, **kwargs)
return inner
@add_logging
def takes_int_str(x: int, y: str) -> int:
return x + 7
await takes_int_str(1, "A") # Accepted
await takes_int_str("B", 2) # Correctly rejected by the type checker
However, I found it non-trivial to properly type annotate a parametrizable decorator. Check the following MRE:
import functools
import inspect
from collections.abc import Callable, Coroutine
from typing import ParamSpec, TypeVar
P = ParamSpec("P")
R = TypeVar("R")
def tag(*names: str) -> ??:
"""This decorator just tags an async function with the provided name(s)."""
for name in names:
if not isinstance(name, str):
raise TypeError("tag name must be a string")
def outer(func: Callable[P, R]) -> Callable[P, Coroutine[R]]:
func.tags = names
if not inspect.iscoroutinefunction(func):
raise TypeError("tagged function must be an async function")
@functools.wraps(func)
async def inner(*args: P.args, **kwargs: P.kwargs) -> R:
result = await func(*args, **kwargs)
return result
return inner
return outer
I'm struggling with figuring out the return type of the tag
function. Also, I'm not 100% confident with the correctness of the typing of outer
and inner
nested functions. How do I type this properly?
P.S. I know, as of today, mypy 0.902 doesn't support this feature fully yet.