mypy showing error "Untyped decorator makes function untyped" when using diskcache.Cache.memoize decorator on a typed function

754 Views Asked by At

I have a function that returns a list of dictionaries I want to cache using diskcache.Cache.memoize. However, when I run mypy type checks I get an Untyped decorator makes function "get_local_network_connections" untyped error. I'm not sure how to provide the typing hints to keep mypy happy. Here is an abbreviated version of my code:

import datetime
import pathlib
from diskcache import Cache

DEFAULT_CACHE_DIR = pathlib.Path.home() / ".cache" / "hub-cli"
cache = Cache(DEFAULT_CACHE_DIR)

@cache.memoize(typing=True, expire=datetime.timedelta(minutes=10).total_seconds())
def get_local_network_connections() -> list[dict[str, str]]:
    ....
1

There are 1 best solutions below

0
bluenote10 On

I often solve such problems by providing a properly typed wrapper for the underlying untyped decorator. In your example this could look something like:

from pathlib import Path
from typing import Callable, TypeVar
from diskcache import Cache
from typing_extensions import ParamSpec

# Hide the original decorator
_cache = Cache(Path.home() / ".cache" / "hub-cli")

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


# Create a typed decorator wrapper
def cached(f: Callable[P, R]) -> Callable[P, R]:
    # Forward decorating to the underlying decorator. This may require
    # a type ignore tag to satisfy mypy, but since this is the only point
    # where we have to suppress types that should be fine.
    return _cache.memoize(...)(f)  # type: ignore

# Now use the typed decorator
@cached
def some_func(x: SomeInput) -> SomeOutput:
    ...

Now mypy understands usages of some_func again, i.e., it can detect wrong calls, and understands its return type, as if there was no decorator.

If the underlying untyped decorator actually messes with the input/output types, it may be possible to adapt the wrapper accordingly, see PEP 612 for details on ParamSpec usage.

Wrapping the decorator can have the benefit that commonly passed arguments can be moved into the wrapper. If usages use different parametrizations though, this have to be re-implemented as arguments in the wrapper.