1

I'm trying to cache the results of a method that performs an HTTP query. The method receives the endpoint (a string) and a payload (a dictionary). The dictionary is not hashable, so it cannot be used with the standard functools.cache.

I tried to follow the path shown in Using @functools.lru_cache with dictionary arguments, and it is working. But pylance type checker is complaining quite a lot, and I don't know how to get a clean linting.

The code used for testing this issue is:

from functools import cache, wraps
from typing import Any, Collection, Hashable, Mapping

from immutabledict import immutabledict


def deep_freeze(thing: Any) -> Any:
    if thing is None or isinstance(thing, str):
        return thing
    if isinstance(thing, Mapping):
        return immutabledict({k: deep_freeze(v) for k, v in thing.items()})
    if isinstance(thing, Collection):
        return tuple(deep_freeze(i) for i in thing)
    if not isinstance(thing, Hashable):
        raise TypeError(f"Unfreezable type: '{type(thing)}'")

    return thing


def deep_freeze_args(func):

    @wraps(func)
    def wrapped(*args, **kwargs):
        return func(*deep_freeze(args), **deep_freeze(kwargs))

    for cache_func in ["cache_info", "cache_clear", "cache_parameters"]:
        if hasattr(func, cache_func):
            setattr(wrapped, cache_func, getattr(func, cache_func))

    return wrapped


@deep_freeze_args
@cache
def func_a(data):
    return data


if __name__ == "__main__":
    ext_data = {"a": "b", "c": 3}
    func_a(ext_data)

deep_freeze freezes recursively all the data found, using immutabledict library to freeze the mappings and tuples for other collections.

deep_freeze_args is the decorator que freezes the arguments before handling the result to the next level, that should be the cache.

func_a is the test method, the one that contain the HTTP call. For testing, an empty method works equally.

The linter complains at the parameter in the last line:

Argument of type "dict[str, Unknown]" cannot be assigned to parameter "args" of type "Hashable" in function "__call__"
  "dict[str, Unknown]" is incompatible with protocol "Hashable"
    "__hash__" is an incompatible type
      Type "None" cannot be assigned to type "(self: dict[str, Unknown]) -> int"

Any advise on how to get a clean linting?

Poshi
  • 5,332
  • 3
  • 15
  • 32
  • It seems to be a bug in pyright/pylance/mypy. Try adding `# type: ignore` to last line: `func_a(ext_data) # type: ignore` This works. – CreepyRaccoon Oct 14 '22 at 14:46
  • Another option is to disable Pylance and try other lints like _Flake8_ – CreepyRaccoon Oct 14 '22 at 14:48
  • I'm already using other linters. Just trying to clean those too. Ignoring the check is an option, but I would prefer to get it sorted, not ignored. If going thru the ignore path, every user that uses that library will have to add the ignore comment in their code, as this is a public facing function :-( – Poshi Oct 14 '22 at 14:52

0 Answers0