ttl-cache-decorator 0.1.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ttl-cache-decorator might be problematic. Click here for more details.

ttl_cache/__init__.py ADDED
@@ -0,0 +1 @@
1
+ from ._ttl_lru import ttl_lru_cache, TTLCachedFunction # noqa
ttl_cache/_ttl_lru.py ADDED
@@ -0,0 +1,83 @@
1
+ import functools
2
+ import random
3
+ import time
4
+ from collections.abc import Callable
5
+ from functools import lru_cache, wraps
6
+ from typing import Any, Protocol, cast
7
+
8
+
9
+ class TTLCachedFunction[**P, R](Protocol):
10
+ """Same signature as the wrapped function, plus ``lru_cache``'s helpers."""
11
+
12
+ def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R: ...
13
+ def cache_info(self) -> functools._CacheInfo: ...
14
+ def cache_clear(self) -> None: ...
15
+
16
+
17
+ def ttl_lru_cache[**P, R](
18
+ ttl: float = 60,
19
+ jitter: float = 0,
20
+ maxsize: int | None = 128,
21
+ typed: bool = False,
22
+ ) -> Callable[[Callable[P, R]], TTLCachedFunction[P, R]]:
23
+ """A decorator that works like ``lru_cache`` but with a time to live (ttl).
24
+
25
+ Under the hood it still uses ``lru_cache``, but the cached call receives an
26
+ extra positional-only ``time_bucket`` argument::
27
+
28
+ time_bucket = int((time.monotonic() + offset) // ttl)
29
+
30
+ An entry stays "alive" while the bucket value is unchanged and expires when
31
+ the bucket rolls over. ``offset`` is drawn once per decorated function from
32
+ ``random.uniform(0, jitter)``, so different functions (or processes) expire
33
+ at different moments instead of all at once.
34
+
35
+ Args:
36
+ ttl: Time to live in seconds. Defaults to 60.
37
+ jitter: Max random offset in seconds applied to the bucket boundaries.
38
+ Defaults to 0.
39
+ maxsize: Max size of the cache, or ``None`` for unbounded. Defaults to 128.
40
+ typed: Whether argument types are part of the cache key. Defaults to False.
41
+
42
+ Returns:
43
+ A decorator that wraps a function in a TTL-aware LRU cache. The wrapped
44
+ function keeps its signature and exposes ``cache_info`` and
45
+ ``cache_clear``.
46
+
47
+ Raises:
48
+ ValueError: If ``ttl`` is not positive or ``jitter`` is negative.
49
+
50
+ Example:
51
+ >>> @ttl_lru_cache(ttl=30, jitter=5)
52
+ ... def fetch(key: str) -> str:
53
+ ... return key.upper()
54
+ """
55
+ if ttl <= 0:
56
+ raise ValueError(f"ttl must be positive, got {ttl!r}")
57
+ if jitter < 0:
58
+ raise ValueError(f"jitter must be non-negative, got {jitter!r}")
59
+
60
+ def decorator(func: Callable[P, R]) -> TTLCachedFunction[P, R]:
61
+ offset = random.uniform(0, jitter)
62
+
63
+ @lru_cache(maxsize=maxsize, typed=typed)
64
+ def cached_with_time(_time_bucket: int, /, *args: Any, **kwargs: Any) -> R:
65
+ return func(*args, **kwargs)
66
+
67
+ # lru_cache types its arguments as Hashable, which P.args/P.kwargs can't
68
+ # be proven to be. Unhashable arguments still raise TypeError at call
69
+ # time, exactly as with plain lru_cache.
70
+ call_cached = cast(Callable[..., R], cached_with_time)
71
+
72
+ @wraps(func)
73
+ def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
74
+ time_bucket = int((time.monotonic() + offset) // ttl)
75
+ return call_cached(time_bucket, *args, **kwargs)
76
+
77
+ wrapper.__dict__.update(
78
+ cache_info=cached_with_time.cache_info,
79
+ cache_clear=cached_with_time.cache_clear,
80
+ )
81
+ return cast(TTLCachedFunction[P, R], wrapper)
82
+
83
+ return decorator
@@ -0,0 +1,58 @@
1
+ Metadata-Version: 2.5
2
+ Name: ttl-cache-decorator
3
+ Version: 0.1.2
4
+ Summary: LRU Cache Decorator with a Time to Live
5
+ Project-URL: Changelog, https://github.com/Johandielangman/ttl-cache-decorator/blob/main/CHANGELOG.md
6
+ Project-URL: Documentation, https://johandielangman.github.io/ttl-cache-decorator/
7
+ Project-URL: Issues, https://github.com/Johandielangman/ttl-cache-decorator/issues
8
+ Project-URL: Source, https://github.com/Johandielangman/ttl-cache-decorator
9
+ Author-email: Johandielangman <johan@datatreehouse.org>
10
+ License-Expression: MIT
11
+ License-File: LICENSE.txt
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Operating System :: MacOS
14
+ Classifier: Operating System :: Microsoft :: Windows
15
+ Classifier: Operating System :: POSIX :: Linux
16
+ Classifier: Programming Language :: Python
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Programming Language :: Python :: 3.15
21
+ Classifier: Programming Language :: Python :: Implementation :: CPython
22
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
23
+ Requires-Python: >=3.12
24
+ Description-Content-Type: text/markdown
25
+
26
+ # TTL Cache
27
+
28
+ [![PyPI - Version](https://img.shields.io/pypi/v/ttl-cache-decorator.svg)](https://pypi.org/project/ttl-cache-decorator)
29
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/ttl-cache-decorator.svg)](https://pypi.org/project/ttl-cache-decorator)
30
+
31
+ An LRU cache decorator with a time to live — like `functools.lru_cache`, but entries expire after a configurable number of seconds.
32
+
33
+ ## Installation
34
+
35
+ ```console
36
+ pip install ttl-cache-decorator
37
+ ```
38
+
39
+ ## Usage
40
+
41
+ ```python
42
+ from ttl_cache import ttl_lru_cache
43
+
44
+
45
+ @ttl_lru_cache(ttl=30)
46
+ def fetch_user(user_id: int) -> dict:
47
+ return {"id": user_id, "name": "Ada"}
48
+
49
+
50
+ fetch_user(1) # Calls the function
51
+ fetch_user(1) # Cache hit
52
+ # ... 30 seconds later ...
53
+ fetch_user(1) # Expired — calls the function again
54
+ ```
55
+
56
+ ## License
57
+
58
+ `ttl-cache-decorator` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.
@@ -0,0 +1,6 @@
1
+ ttl_cache/__init__.py,sha256=2oTIuMAlfWiXoSNXjXX6n0tVkjjj8ewKR0YOhCYcYFI,63
2
+ ttl_cache/_ttl_lru.py,sha256=3_aSt8sfby6TeDB67816W4i7Al9HtMzpQRrYi4hjXHs,3121
3
+ ttl_cache_decorator-0.1.2.dist-info/METADATA,sha256=mdxWNUiKY4MhPSnxwYISgzPIIAnAxubMTu5ibXZHtps,2103
4
+ ttl_cache_decorator-0.1.2.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
5
+ ttl_cache_decorator-0.1.2.dist-info/licenses/LICENSE.txt,sha256=9XrdCLKFXDpgek58eydUna_ucPUJuR75euMJp0_6f0I,1103
6
+ ttl_cache_decorator-0.1.2.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.33.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,18 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026-present Johandielangman <jghanekom2@gmail.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
6
+ associated documentation files (the "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
9
+ following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all copies or substantial
12
+ portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
15
+ LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
16
+ EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
18
+ USE OR OTHER DEALINGS IN THE SOFTWARE.