moka-py 0.2.4__cp314-cp314-musllinux_1_2_aarch64.whl → 0.3.0__cp314-cp314-musllinux_1_2_aarch64.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.
- moka_py/__init__.py +2 -1
- moka_py/__init__.pyi +14 -2
- moka_py/moka_py.cpython-314-aarch64-linux-musl.so +0 -0
- {moka_py-0.2.4.dist-info → moka_py-0.3.0.dist-info}/METADATA +124 -12
- moka_py-0.3.0.dist-info/RECORD +9 -0
- {moka_py-0.2.4.dist-info → moka_py-0.3.0.dist-info}/WHEEL +1 -1
- moka_py.libs/libgcc_s-0bf60adc.so.1 +0 -0
- moka_py-0.2.4.dist-info/RECORD +0 -9
- moka_py.libs/libgcc_s-39080030.so.1 +0 -0
- {moka_py-0.2.4.dist-info → moka_py-0.3.0.dist-info}/licenses/LICENSE +0 -0
moka_py/__init__.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import asyncio as _asyncio
|
|
2
|
+
import inspect as _inspect
|
|
2
3
|
from functools import _make_key
|
|
3
4
|
from functools import wraps as _wraps
|
|
4
5
|
from typing import Any as _Any
|
|
@@ -30,7 +31,7 @@ def cached(
|
|
|
30
31
|
empty = object()
|
|
31
32
|
|
|
32
33
|
def dec(fn):
|
|
33
|
-
if
|
|
34
|
+
if _inspect.iscoroutinefunction(fn):
|
|
34
35
|
|
|
35
36
|
@_wraps(fn)
|
|
36
37
|
async def inner(*args, **kwargs):
|
moka_py/__init__.pyi
CHANGED
|
@@ -17,12 +17,24 @@ class Moka(Generic[K, V]):
|
|
|
17
17
|
eviction_listener: Callable[[K, V, Cause], None] | None = None,
|
|
18
18
|
policy: Policy = "tiny_lfu",
|
|
19
19
|
): ...
|
|
20
|
-
def set(
|
|
20
|
+
def set(
|
|
21
|
+
self,
|
|
22
|
+
key: K,
|
|
23
|
+
value: V,
|
|
24
|
+
ttl: int | float | None = None,
|
|
25
|
+
tti: int | float | None = None,
|
|
26
|
+
) -> None: ...
|
|
21
27
|
@overload
|
|
22
28
|
def get(self, key: K, default: D) -> V | D: ...
|
|
23
29
|
@overload
|
|
24
30
|
def get(self, key: K, default: D | None = None) -> V | D | None: ...
|
|
25
|
-
def get_with(
|
|
31
|
+
def get_with(
|
|
32
|
+
self,
|
|
33
|
+
key: K,
|
|
34
|
+
initializer: Callable[[], V],
|
|
35
|
+
ttl: int | float | None = None,
|
|
36
|
+
tti: int | float | None = None,
|
|
37
|
+
) -> V:
|
|
26
38
|
"""Lookup or initialize a value for the key.
|
|
27
39
|
|
|
28
40
|
If multiple threads call `get_with` with the same key, only one calls `initializer`,
|
|
Binary file
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: moka-py
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Classifier: Programming Language :: Python :: 3.9
|
|
5
5
|
Classifier: Programming Language :: Python :: 3.10
|
|
6
6
|
Classifier: Programming Language :: Python :: 3.11
|
|
@@ -16,8 +16,8 @@ Summary: A high performance caching library for Python written in Rust
|
|
|
16
16
|
Requires-Python: >=3.9
|
|
17
17
|
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
18
18
|
Project-URL: Homepage, https://github.com/deliro/moka-py
|
|
19
|
-
Project-URL: Repository, https://github.com/deliro/moka-py
|
|
20
19
|
Project-URL: Issues, https://github.com/deliro/moka-py/issues
|
|
20
|
+
Project-URL: Repository, https://github.com/deliro/moka-py
|
|
21
21
|
|
|
22
22
|
# moka-py
|
|
23
23
|
|
|
@@ -28,6 +28,7 @@ Project-URL: Issues, https://github.com/deliro/moka-py/issues
|
|
|
28
28
|
- **Synchronous cache:** Thread-safe in-memory caching for Python.
|
|
29
29
|
- **TTL:** Evicts entries after a configurable time to live (TTL).
|
|
30
30
|
- **TTI:** Evicts entries after a configurable time to idle (TTI).
|
|
31
|
+
- **Per-entry TTL / TTI:** Override the cache-wide TTL or TTI on individual entries.
|
|
31
32
|
- **Size-based eviction:** Removes items when capacity is exceeded using TinyLFU or LRU.
|
|
32
33
|
- **Concurrency:** Optimized for high-throughput, concurrent access.
|
|
33
34
|
- **Fully typed:** `mypy` and `pyright` friendly.
|
|
@@ -58,6 +59,7 @@ pip install moka-py
|
|
|
58
59
|
- [Features](#features)
|
|
59
60
|
- [Usage](#usage)
|
|
60
61
|
- [Using moka_py.Moka](#using-moka_pymoka)
|
|
62
|
+
- [Per-entry TTL / TTI](#per-entry-ttl--tti)
|
|
61
63
|
- [@cached decorator](#as-a-decorator)
|
|
62
64
|
- [Async support](#async-support)
|
|
63
65
|
- [Coalesce concurrent calls (wait_concurrent)](#coalesce-concurrent-calls-wait_concurrent)
|
|
@@ -99,6 +101,112 @@ sleep(0.12)
|
|
|
99
101
|
assert cache.get("key") is None
|
|
100
102
|
```
|
|
101
103
|
|
|
104
|
+
### Per-entry TTL / TTI
|
|
105
|
+
|
|
106
|
+
By default, TTL and TTI are set once for the entire cache. You can also set them
|
|
107
|
+
per entry by passing `ttl` and/or `tti` to `set()` or `get_with()`:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from time import sleep
|
|
111
|
+
from moka_py import Moka
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
cache = Moka(100)
|
|
115
|
+
|
|
116
|
+
cache.set("short-lived", "value", ttl=0.5)
|
|
117
|
+
cache.set("session", {"user": "alice"}, ttl=3600.0)
|
|
118
|
+
cache.set("idle-sensitive", "value", tti=1.0)
|
|
119
|
+
cache.set("both", "value", ttl=60.0, tti=5.0)
|
|
120
|
+
|
|
121
|
+
# Entries without per-entry ttl/tti never expire (unless the cache has global settings).
|
|
122
|
+
cache.set("permanent", "value")
|
|
123
|
+
|
|
124
|
+
sleep(0.6)
|
|
125
|
+
assert cache.get("short-lived") is None # expired after 0.5s
|
|
126
|
+
assert cache.get("session") is not None # still alive
|
|
127
|
+
assert cache.get("permanent") is not None
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
`get_with()` accepts the same parameters:
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
from moka_py import Moka
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
cache = Moka(100)
|
|
137
|
+
|
|
138
|
+
value = cache.get_with("key", lambda: "computed", ttl=30.0)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
#### Concurrent `get_with` with different TTL / TTI
|
|
142
|
+
|
|
143
|
+
`get_with()` guarantees that only **one** thread executes the initializer for a given key (stampede protection).
|
|
144
|
+
When multiple threads call `get_with()` for the same key concurrently with **different** `ttl`/`tti` values,
|
|
145
|
+
the thread that wins the race runs its initializer — and its `ttl`/`tti` values are stored with the entry.
|
|
146
|
+
All other threads receive the same cached value and their `ttl`/`tti` parameters are **silently ignored**.
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
import threading
|
|
150
|
+
from moka_py import Moka
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
cache = Moka(100)
|
|
154
|
+
|
|
155
|
+
# Thread A: get_with("k", compute, ttl=1.0)
|
|
156
|
+
# Thread B: get_with("k", compute, ttl=60.0)
|
|
157
|
+
#
|
|
158
|
+
# If thread A wins, the entry expires in 1 second.
|
|
159
|
+
# If thread B wins, the entry expires in 60 seconds.
|
|
160
|
+
# The loser's ttl is discarded — it is NOT merged or compared.
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
#### Interaction with cache-wide TTL / TTI
|
|
164
|
+
|
|
165
|
+
When the cache is constructed with global `ttl` or `tti` **and** an entry specifies its own, the entry
|
|
166
|
+
expires at whichever deadline comes **first**.
|
|
167
|
+
|
|
168
|
+
> **WARNING**
|
|
169
|
+
>
|
|
170
|
+
> Per-entry TTL / TTI can only make an entry expire **sooner** than the cache-wide
|
|
171
|
+
> policy, not later. This is a technical limitation of the underlying
|
|
172
|
+
> [Moka](https://github.com/moka-rs/moka) library: global and per-entry expiration
|
|
173
|
+
> are evaluated independently, and the earliest deadline wins.
|
|
174
|
+
>
|
|
175
|
+
> If you need entries with different lifetimes that can **exceed** a common default,
|
|
176
|
+
> do not set global `ttl`/`tti` on the cache. Use per-entry values exclusively instead.
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
from moka_py import Moka
|
|
180
|
+
|
|
181
|
+
# Do this:
|
|
182
|
+
cache = Moka(1000)
|
|
183
|
+
cache.set("short", "v", ttl=60.0)
|
|
184
|
+
cache.set("long", "v", ttl=300.0) # works as expected
|
|
185
|
+
|
|
186
|
+
# NOT this — "long" will still expire in 60 s:
|
|
187
|
+
cache = Moka(1000, ttl=60.0)
|
|
188
|
+
cache.set("long", "v", ttl=300.0) # capped at 60 s by the global policy
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
from time import sleep
|
|
193
|
+
from moka_py import Moka
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
# Global TTL of 10 seconds.
|
|
197
|
+
cache = Moka(100, ttl=10.0)
|
|
198
|
+
|
|
199
|
+
# This entry will expire in 0.5 s (per-entry TTL wins, it is shorter).
|
|
200
|
+
cache.set("fast", "value", ttl=0.5)
|
|
201
|
+
|
|
202
|
+
# This entry keeps the global 10 s TTL (per-entry TTL=20 s is longer, so global wins).
|
|
203
|
+
cache.set("slow", "value", ttl=20.0)
|
|
204
|
+
|
|
205
|
+
sleep(0.6)
|
|
206
|
+
assert cache.get("fast") is None
|
|
207
|
+
assert cache.get("slow") is not None
|
|
208
|
+
```
|
|
209
|
+
|
|
102
210
|
### As a decorator
|
|
103
211
|
|
|
104
212
|
moka-py can be used as a drop-in replacement for `@lru_cache()` with TTL + TTI support:
|
|
@@ -255,6 +363,10 @@ assert causes == {"size", "expired", "replaced", "explicit"}, events
|
|
|
255
363
|
> 1) The listener is not called just-in-time. `moka` has no background threads or tasks; it runs only during cache operations.
|
|
256
364
|
> 2) The listener must not raise exceptions. If it does, the exception may surface from any `moka-py` method on any thread.
|
|
257
365
|
> 3) Keep the listener fast. Heavy work (especially I/O) will slow `.get()`, `.set()`, etc. Offload via `ThreadPoolExecutor.submit()` or `asyncio.create_task()`
|
|
366
|
+
> 4) **Per-entry TTL / TTI and the eviction listener.** Per-entry expiry fires the
|
|
367
|
+
> listener with `"expired"` just like global TTL/TTI does. The notification is
|
|
368
|
+
> delivered lazily during subsequent cache operations (e.g. `get`, `set`) after
|
|
369
|
+
> the per-entry deadline passes — it is not instant.
|
|
258
370
|
|
|
259
371
|
### Removing entries
|
|
260
372
|
|
|
@@ -310,21 +422,21 @@ assert my_list == [1, 2, 3, 4]
|
|
|
310
422
|
|
|
311
423
|
## Performance
|
|
312
424
|
|
|
313
|
-
*Measured using MacBook Pro
|
|
425
|
+
*Measured using MacBook Pro 14-inch, Nov 2024 with Apple M4 Pro processor and 24GiB RAM*
|
|
314
426
|
|
|
315
427
|
```
|
|
316
428
|
-------------------------------------------------------------------------------------------- benchmark: 9 tests -------------------------------------------------------------------------------------------
|
|
317
429
|
Name (time in ns) Min Max Mean StdDev Median IQR Outliers OPS (Mops/s) Rounds Iterations
|
|
318
430
|
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
319
|
-
test_bench_remove
|
|
320
|
-
test_bench_get[lru-False]
|
|
321
|
-
test_bench_get[tiny_lfu-False]
|
|
322
|
-
test_bench_get[lru-True]
|
|
323
|
-
test_bench_get[tiny_lfu-True]
|
|
324
|
-
test_bench_get_with
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
test_bench_set[lru]
|
|
431
|
+
test_bench_remove 68.1140 (1.0) 68.2812 (1.0) 68.1806 (1.0) 0.0671 (1.0) 68.1621 (1.0) 0.1000 (1.0) 1;0 14.6669 (1.0) 5 10000000
|
|
432
|
+
test_bench_get[lru-False] 77.5126 (1.14) 78.2797 (1.15) 77.7823 (1.14) 0.2947 (4.39) 77.6792 (1.14) 0.2913 (2.91) 1;0 12.8564 (0.88) 5 10000000
|
|
433
|
+
test_bench_get[tiny_lfu-False] 78.0985 (1.15) 78.8168 (1.15) 78.4920 (1.15) 0.2678 (3.99) 78.4868 (1.15) 0.3429 (3.43) 2;0 12.7401 (0.87) 5 10000000
|
|
434
|
+
test_bench_get[lru-True] 89.1512 (1.31) 89.6459 (1.31) 89.4480 (1.31) 0.1910 (2.85) 89.5190 (1.31) 0.2458 (2.46) 2;0 11.1797 (0.76) 5 10000000
|
|
435
|
+
test_bench_get[tiny_lfu-True] 91.4891 (1.34) 91.9214 (1.35) 91.6827 (1.34) 0.1867 (2.78) 91.7339 (1.35) 0.3141 (3.14) 2;0 10.9072 (0.74) 5 10000000
|
|
436
|
+
test_bench_get_with 137.0672 (2.01) 137.8738 (2.02) 137.4143 (2.02) 0.3182 (4.74) 137.2839 (2.01) 0.4530 (4.53) 2;0 7.2773 (0.50) 5 10000000
|
|
437
|
+
test_bench_set_str_key 354.1709 (5.20) 355.5768 (5.21) 354.9073 (5.21) 0.5631 (8.39) 355.0415 (5.21) 0.8900 (8.90) 2;0 2.8176 (0.19) 5 1408297
|
|
438
|
+
test_bench_set[tiny_lfu] 355.6927 (5.22) 356.9633 (5.23) 356.3647 (5.23) 0.5645 (8.41) 356.4059 (5.23) 1.0390 (10.40) 2;0 2.8061 (0.19) 5 1405450
|
|
439
|
+
test_bench_set[lru] 388.7005 (5.71) 389.5825 (5.71) 389.1170 (5.71) 0.3837 (5.72) 389.0796 (5.71) 0.6915 (6.92) 2;0 2.5699 (0.18) 5 1295615
|
|
328
440
|
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
329
441
|
```
|
|
330
442
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
moka_py/__init__.py,sha256=WMkLJ34xVZ9eEnWsnfkOYxM_cWbo3IztW_3gUvmJ2V0,2318
|
|
2
|
+
moka_py/__init__.pyi,sha256=1VMXf8_CKuik1EGAJzJ04kN0BgSZyPlxz7ceSKsTO5w,2297
|
|
3
|
+
moka_py/moka_py.cpython-314-aarch64-linux-musl.so,sha256=M6VVLCU-1100wiOZBfM5fV0Ofzky8fov1sLXI6Id26c,723625
|
|
4
|
+
moka_py/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
moka_py-0.3.0.dist-info/METADATA,sha256=cbEf4W_gAb5iDndFWBTdVZ_ApdxrhoGdGsAaxDFUZKU,15095
|
|
6
|
+
moka_py-0.3.0.dist-info/WHEEL,sha256=wfa3QoSp_ZyLpGod_VyrFzSaMcf2FS-0HgJ4XxFA7a8,109
|
|
7
|
+
moka_py-0.3.0.dist-info/licenses/LICENSE,sha256=CUj5ca53JXgIACVKNEOFOlbMWtxY4RXXj9cELIv2R04,1069
|
|
8
|
+
moka_py.libs/libgcc_s-0bf60adc.so.1,sha256=9fhX5YpiwtHdsiLMLz77Pw9jjIEGriteV7UOUrrwvpk,527681
|
|
9
|
+
moka_py-0.3.0.dist-info/RECORD,,
|
|
Binary file
|
moka_py-0.2.4.dist-info/RECORD
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
moka_py-0.2.4.dist-info/METADATA,sha256=e-jmd2RcuG_uvXEOaMjJCkLE6QHQR5FZzkkvEFy6wfQ,11475
|
|
2
|
-
moka_py-0.2.4.dist-info/WHEEL,sha256=3dJIuQYj1xffw_5TULgTDxRmdRwYLHfh8CpBkgcXhHU,108
|
|
3
|
-
moka_py-0.2.4.dist-info/licenses/LICENSE,sha256=CUj5ca53JXgIACVKNEOFOlbMWtxY4RXXj9cELIv2R04,1069
|
|
4
|
-
moka_py.libs/libgcc_s-39080030.so.1,sha256=fIO6GHOh8Ft9CR0Geu7wSUb9Xnl122iTtrxQQ9TAkTQ,789673
|
|
5
|
-
moka_py/__init__.py,sha256=yb0K713ikrp4a6raS0p16W_9CVX17ReTil-M8-rmjv0,2291
|
|
6
|
-
moka_py/__init__.pyi,sha256=_mBsBklSY575Gxl-StCX7mv4QGugyh_Z47Rxnz0XlaE,2075
|
|
7
|
-
moka_py/moka_py.cpython-314-aarch64-linux-musl.so,sha256=9j33ov1lpNrOOTf2Nruba3QNfEibSXKgitB2BoOVYog,723609
|
|
8
|
-
moka_py/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
-
moka_py-0.2.4.dist-info/RECORD,,
|
|
Binary file
|
|
File without changes
|