# -*- coding: utf-8 -*-1# This program is free software; you can redistribute it and/or modify2# it under the terms of the GNU General Public License as published by3# the Free Software Foundation; either version 2 of the License, or4# (at your option) any later version.5#6# This program is distributed in the hope that it will be useful,7# but WITHOUT ANY WARRANTY; without even the implied warranty of8# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9# GNU General Public License for more details.10#11# You should have received a copy of the GNU General Public License12# along with this program; if not, write to the Free Software13# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,14# MA 02110-1301, USA.15#16# Author: Mauro Soria1718from __future__ import annotations1920import threading2122from functools import wraps23from time import time24from typing import Any, Callable, TypeVar25from typing_extensions import ParamSpec2627_lock = threading.Lock()28_cache: dict[int, tuple[float, Any]] = {}29_cache_lock = threading.Lock()3031# https://mypy.readthedocs.io/en/stable/generics.html#declaring-decorators32P = ParamSpec("P")33T = TypeVar("T")343536def cached(timeout: int | float = 100) -> Callable[..., Any]:37def _cached(func: Callable[P, T]) -> Callable[P, T]:38@wraps(func)39def with_caching(*args: P.args, **kwargs: P.kwargs) -> T:40key = id(func)41for arg in args:42key += id(arg)43for k, v in kwargs.items():44key += id(k) + id(v)4546# If it was cached and the cache timeout hasn't been reached47if key in _cache and time() - _cache[key][0] < timeout:48return _cache[key][1]4950with _cache_lock:51result = func(*args, **kwargs)52_cache[key] = (time(), result)5354return result5556return with_caching5758return _cached596061def locked(func: Callable[P, T]) -> Callable[P, T]:62def with_locking(*args: P.args, **kwargs: P.kwargs) -> T:63with _lock:64return func(*args, **kwargs)6566return with_locking676869