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 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 _asyncio.iscoroutinefunction(fn):
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(self, key: K, value: V) -> None: ...
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(self, key: K, initializer: Callable[[], V]) -> V:
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`,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: moka-py
3
- Version: 0.2.4
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 2021 with Apple M1 Pro processor and 16GiB RAM*
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 100.8775 (1.0) 108.9191 (1.0) 102.6757 (1.0) 3.4992 (34.54) 101.0640 (1.0) 2.4234 (15.49) 1;1 9.7394 (1.0) 5 10000000
320
- test_bench_get[lru-False] 112.8452 (1.12) 113.0924 (1.04) 112.9415 (1.10) 0.1013 (1.0) 112.9176 (1.12) 0.1565 (1.0) 1;0 8.8541 (0.91) 5 10000000
321
- test_bench_get[tiny_lfu-False] 135.0147 (1.34) 135.6069 (1.25) 135.2916 (1.32) 0.2246 (2.22) 135.2849 (1.34) 0.3164 (2.02) 2;0 7.3914 (0.76) 5 10000000
322
- test_bench_get[lru-True] 135.1628 (1.34) 135.7813 (1.25) 135.4712 (1.32) 0.2231 (2.20) 135.4765 (1.34) 0.2477 (1.58) 2;0 7.3816 (0.76) 5 10000000
323
- test_bench_get[tiny_lfu-True] 135.2461 (1.34) 135.6612 (1.25) 135.4463 (1.32) 0.1802 (1.78) 135.4026 (1.34) 0.3192 (2.04) 2;0 7.3830 (0.76) 5 10000000
324
- test_bench_get_with 290.5307 (2.88) 291.0418 (2.67) 290.8393 (2.83) 0.1893 (1.87) 290.8867 (2.88) 0.1873 (1.20) 2;0 3.4383 (0.35) 5 10000000
325
- test_bench_set[tiny_lfu] 515.7514 (5.11) 518.6080 (4.76) 517.4876 (5.04) 1.1196 (11.05) 517.6572 (5.12) 1.5465 (9.88) 2;0 1.9324 (0.20) 5 1912971
326
- test_bench_set_str_key 516.1032 (5.12) 533.7330 (4.90) 525.7461 (5.12) 6.3386 (62.57) 526.8491 (5.21) 6.1052 (39.01) 2;0 1.9021 (0.20) 5 1918471
327
- test_bench_set[lru] 637.3014 (6.32) 644.4533 (5.92) 640.3571 (6.24) 2.8981 (28.61) 639.8821 (6.33) 4.6131 (29.48) 2;0 1.5616 (0.16) 5 1581738
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,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: maturin (1.9.6)
2
+ Generator: maturin (1.11.5)
3
3
  Root-Is-Purelib: false
4
4
  Tag: cp314-cp314-musllinux_1_2_aarch64
Binary file
@@ -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