Coverage for expiring_lru_cache/__init__.py: 100%
30 statements
« prev ^ index » next coverage.py v7.2.7, created at 2024-06-30 12:03 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2024-06-30 12:03 +0000
1"""Core functionality."""
3import functools
4import logging
5from datetime import datetime, timedelta
6from typing import Any, Callable, Optional, Union
8from expiring_lru_cache.version import __version__ # noqa: F401
10logging.getLogger("expiring_lru_cache").addHandler(logging.NullHandler())
12MINUTES = 60
13HOURS = 60 * MINUTES
14DAYS = 24 * HOURS
17# Bug in type hints for `functools.lru_cache` + mypy
18# https://github.com/python/mypy/issues/5858#issuecomment-932773127
19def _init_cache(
20 func: Callable, expires_after: Optional[int], *args: Any, **kwargs: Any
21) -> Callable:
22 """Initialize cached function by wrapping it in `functools.lru_cache`."""
23 cached_func = functools.lru_cache(*args, **kwargs)(func)
24 if expires_after:
25 setattr(
26 cached_func,
27 "__expires_at",
28 datetime.now() + timedelta(seconds=expires_after),
29 )
30 return cached_func
33def _expired(cached_func: Callable) -> bool:
34 """Check if cached function is expired."""
35 if hasattr(cached_func, "__expires_at"):
36 return getattr(cached_func, "__expires_at") < datetime.now()
37 return False
40def lru_cache(
41 expires_after: Optional[int] = None,
42 *lru_args: Union[int, bool],
43 **lru_kwargs: Union[int, bool],
44) -> Callable:
45 """
46 LRU caching with expiration period.
48 Acts as a drop-in replacement of `functools.lru_cache`. Arguments valid for
49 `functools.lru_cache` can also be passed.
50 :param expires_after: number of seconds after which to invalidate cache - `None`
51 will never invalidate based on time. Convenience variables `MINUTES`, `HOURS`
52 and `DAYS` are available (using `lru_cache(expires_after=2 * DAYS)`)
53 :param args: `functools.lru_cache`'s positional arguments
54 :param kwargs: `functools.lru_cache`'s keyword arguments
55 :return: cached function
56 """
58 def decorate(func: Callable) -> Callable:
59 cached_func = _init_cache(func, expires_after, *lru_args, **lru_kwargs)
61 @functools.wraps(func)
62 def wrapper(*args: Union[int, bool], **kwargs: Union[int, bool]) -> Callable:
63 nonlocal cached_func
64 if _expired(cached_func):
65 logging.debug("Resetting cache")
66 cached_func = _init_cache(func, expires_after, *lru_args, **lru_kwargs)
67 return cached_func(*args, **kwargs)
69 wrapper.cache_info = lambda: cached_func.cache_info()
70 return wrapper
72 return decorate