moka-py 0.1.19__cp310-cp310-musllinux_1_2_armv7l.whl → 0.2.4__cp310-cp310-musllinux_1_2_armv7l.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 moka-py might be problematic. Click here for more details.
- moka_py/__init__.py +36 -12
- moka_py/__init__.pyi +31 -34
- moka_py/moka_py.cpython-310-arm-linux-gnueabihf.so +0 -0
- {moka_py-0.1.19.dist-info → moka_py-0.2.4.dist-info}/METADATA +88 -88
- moka_py-0.2.4.dist-info/RECORD +9 -0
- {moka_py-0.1.19.dist-info → moka_py-0.2.4.dist-info}/WHEEL +1 -1
- moka_py-0.1.19.dist-info/RECORD +0 -9
- {moka_py-0.1.19.dist-info → moka_py-0.2.4.dist-info}/licenses/LICENSE +0 -0
moka_py/__init__.py
CHANGED
|
@@ -1,32 +1,56 @@
|
|
|
1
1
|
import asyncio as _asyncio
|
|
2
|
-
from functools import
|
|
3
|
-
from
|
|
2
|
+
from functools import _make_key
|
|
3
|
+
from functools import wraps as _wraps
|
|
4
|
+
from typing import Any as _Any
|
|
4
5
|
|
|
6
|
+
from .moka_py import Moka
|
|
7
|
+
from .moka_py import get_version as _get_version
|
|
5
8
|
|
|
6
|
-
__all__ = ["
|
|
9
|
+
__all__ = ["VERSION", "Moka", "cached"]
|
|
7
10
|
|
|
8
11
|
VERSION = _get_version()
|
|
9
12
|
|
|
10
13
|
|
|
11
|
-
def cached(
|
|
14
|
+
def cached(
|
|
15
|
+
maxsize=128,
|
|
16
|
+
typed=False,
|
|
17
|
+
*,
|
|
18
|
+
ttl=None,
|
|
19
|
+
tti=None,
|
|
20
|
+
wait_concurrent=False,
|
|
21
|
+
policy="tiny_lfu",
|
|
22
|
+
):
|
|
23
|
+
"""Cache decorator for sync and async functions with TTL/TTI and optional concurrent-waiting.
|
|
24
|
+
|
|
25
|
+
- For sync functions: returns cached value if present, otherwise computes and stores it.
|
|
26
|
+
- For async functions: returns an awaitable; with wait_concurrent=True a single shared task is created per key
|
|
27
|
+
so concurrent awaiters share the same result or exception.
|
|
28
|
+
"""
|
|
12
29
|
cache = Moka(maxsize, ttl=ttl, tti=tti, policy=policy)
|
|
13
30
|
empty = object()
|
|
14
31
|
|
|
15
32
|
def dec(fn):
|
|
16
33
|
if _asyncio.iscoroutinefunction(fn):
|
|
17
|
-
if wait_concurrent:
|
|
18
|
-
raise NotImplementedError("wait_concurrent is not yet supported for async functions")
|
|
19
34
|
|
|
20
35
|
@_wraps(fn)
|
|
21
36
|
async def inner(*args, **kwargs):
|
|
22
37
|
key = _make_key(args, kwargs, typed)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
38
|
+
if wait_concurrent:
|
|
39
|
+
# Store a shared Task in cache while computation is in-flight
|
|
40
|
+
def init() -> _Any:
|
|
41
|
+
return _asyncio.create_task(fn(*args, **kwargs))
|
|
42
|
+
|
|
43
|
+
task = cache.get_with(key, init)
|
|
44
|
+
return await task
|
|
45
|
+
else:
|
|
46
|
+
maybe_value = cache.get(key, empty)
|
|
47
|
+
if maybe_value is not empty:
|
|
48
|
+
return maybe_value
|
|
49
|
+
value = await fn(*args, **kwargs)
|
|
50
|
+
cache.set(key, value)
|
|
51
|
+
return value
|
|
29
52
|
else:
|
|
53
|
+
|
|
30
54
|
@_wraps(fn)
|
|
31
55
|
def inner(*args, **kwargs):
|
|
32
56
|
key = _make_key(args, kwargs, typed)
|
moka_py/__init__.pyi
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
from
|
|
2
|
-
|
|
1
|
+
from collections.abc import Callable, Hashable
|
|
2
|
+
from typing import Any, Generic, Literal, TypeVar, overload
|
|
3
3
|
|
|
4
4
|
K = TypeVar("K", bound=Hashable)
|
|
5
5
|
V = TypeVar("V")
|
|
@@ -8,51 +8,48 @@ Fn = TypeVar("Fn", bound=Callable[..., Any])
|
|
|
8
8
|
Cause = Literal["explicit", "size", "expired", "replaced"]
|
|
9
9
|
Policy = Literal["tiny_lfu", "lru"]
|
|
10
10
|
|
|
11
|
-
|
|
12
11
|
class Moka(Generic[K, V]):
|
|
13
12
|
def __init__(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
self,
|
|
14
|
+
capacity: int,
|
|
15
|
+
ttl: int | float | None = None,
|
|
16
|
+
tti: int | float | None = None,
|
|
17
|
+
eviction_listener: Callable[[K, V, Cause], None] | None = None,
|
|
18
|
+
policy: Policy = "tiny_lfu",
|
|
20
19
|
): ...
|
|
21
|
-
|
|
22
20
|
def set(self, key: K, value: V) -> None: ...
|
|
23
|
-
|
|
24
21
|
@overload
|
|
25
|
-
def get(self, key: K, default: D) ->
|
|
26
|
-
|
|
22
|
+
def get(self, key: K, default: D) -> V | D: ...
|
|
27
23
|
@overload
|
|
28
|
-
def get(self, key: K, default:
|
|
29
|
-
|
|
24
|
+
def get(self, key: K, default: D | None = None) -> V | D | None: ...
|
|
30
25
|
def get_with(self, key: K, initializer: Callable[[], V]) -> V:
|
|
31
|
-
"""
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
`initializer`, and the others wait until the value is set.
|
|
26
|
+
"""Lookup or initialize a value for the key.
|
|
27
|
+
|
|
28
|
+
If multiple threads call `get_with` with the same key, only one calls `initializer`,
|
|
29
|
+
the others wait until the value is set.
|
|
36
30
|
"""
|
|
37
31
|
|
|
38
32
|
@overload
|
|
39
|
-
def remove(self, key: K, default: D) ->
|
|
40
|
-
|
|
33
|
+
def remove(self, key: K, default: D) -> V | D: ...
|
|
41
34
|
@overload
|
|
42
|
-
def remove(self, key: K, default:
|
|
43
|
-
|
|
35
|
+
def remove(self, key: K, default: D | None = None) -> V | D | None: ...
|
|
44
36
|
def clear(self) -> None: ...
|
|
45
|
-
|
|
46
37
|
def count(self) -> int: ...
|
|
47
38
|
|
|
48
|
-
|
|
49
39
|
def cached(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
40
|
+
maxsize: int = 128,
|
|
41
|
+
typed: bool = False,
|
|
42
|
+
*,
|
|
43
|
+
ttl: int | float | None = None,
|
|
44
|
+
tti: int | float | None = None,
|
|
45
|
+
wait_concurrent: bool = False,
|
|
46
|
+
policy: Policy = "tiny_lfu",
|
|
57
47
|
) -> Callable[[Fn], Fn]:
|
|
58
|
-
|
|
48
|
+
"""Decorator for caching function results in a thread-safe in-memory cache.
|
|
49
|
+
|
|
50
|
+
- If the decorated function is synchronous: returns the cached value or computes and stores it.
|
|
51
|
+
- If the decorated function is asynchronous: returns an awaitable which yields the cached result.
|
|
52
|
+
- If wait_concurrent=True: concurrent calls with the same arguments wait on a single in-flight computation.
|
|
53
|
+
For async functions this is implemented via a shared asyncio.Task; all awaiters receive the same result
|
|
54
|
+
or the same exception.
|
|
55
|
+
"""
|
|
Binary file
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: moka-py
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Classifier: Programming Language :: Python :: 3.9
|
|
5
5
|
Classifier: Programming Language :: Python :: 3.10
|
|
6
6
|
Classifier: Programming Language :: Python :: 3.11
|
|
@@ -21,37 +21,32 @@ Project-URL: Issues, https://github.com/deliro/moka-py/issues
|
|
|
21
21
|
|
|
22
22
|
# moka-py
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
**moka-py** is a Python binding for the highly efficient [Moka](https://github.com/moka-rs/moka) caching library written
|
|
27
|
-
in Rust. This library allows you to leverage the power of Moka's high-performance, feature-rich cache in your Python
|
|
28
|
-
projects.
|
|
24
|
+
**moka-py** is a Python binding to the [Moka](https://github.com/moka-rs/moka) cache written in Rust. It brings Moka’s high-performance, feature‑rich caching to Python.
|
|
29
25
|
|
|
30
26
|
## Features
|
|
31
27
|
|
|
32
|
-
- **Synchronous
|
|
33
|
-
- **TTL
|
|
34
|
-
- **TTI
|
|
35
|
-
- **Size-based
|
|
36
|
-
|
|
37
|
-
- **Concurrency:** Optimized for high-performance, concurrent access in multithreaded environments.
|
|
28
|
+
- **Synchronous cache:** Thread-safe in-memory caching for Python.
|
|
29
|
+
- **TTL:** Evicts entries after a configurable time to live (TTL).
|
|
30
|
+
- **TTI:** Evicts entries after a configurable time to idle (TTI).
|
|
31
|
+
- **Size-based eviction:** Removes items when capacity is exceeded using TinyLFU or LRU.
|
|
32
|
+
- **Concurrency:** Optimized for high-throughput, concurrent access.
|
|
38
33
|
- **Fully typed:** `mypy` and `pyright` friendly.
|
|
39
34
|
|
|
40
35
|
## Installation
|
|
41
36
|
|
|
42
|
-
|
|
37
|
+
Install with `uv`:
|
|
43
38
|
|
|
44
39
|
```bash
|
|
45
40
|
uv add moka-py
|
|
46
41
|
```
|
|
47
42
|
|
|
48
|
-
`poetry`:
|
|
43
|
+
Or with `poetry`:
|
|
49
44
|
|
|
50
45
|
```bash
|
|
51
46
|
poetry add moka-py
|
|
52
47
|
```
|
|
53
48
|
|
|
54
|
-
Or
|
|
49
|
+
Or with `pip`:
|
|
55
50
|
|
|
56
51
|
```bash
|
|
57
52
|
pip install moka-py
|
|
@@ -64,8 +59,8 @@ pip install moka-py
|
|
|
64
59
|
- [Usage](#usage)
|
|
65
60
|
- [Using moka_py.Moka](#using-moka_pymoka)
|
|
66
61
|
- [@cached decorator](#as-a-decorator)
|
|
67
|
-
- [
|
|
68
|
-
- [
|
|
62
|
+
- [Async support](#async-support)
|
|
63
|
+
- [Coalesce concurrent calls (wait_concurrent)](#coalesce-concurrent-calls-wait_concurrent)
|
|
69
64
|
- [Eviction listener](#eviction-listener)
|
|
70
65
|
- [Removing entries](#removing-entries)
|
|
71
66
|
- [How it works](#how-it-works)
|
|
@@ -82,16 +77,16 @@ from time import sleep
|
|
|
82
77
|
from moka_py import Moka
|
|
83
78
|
|
|
84
79
|
|
|
85
|
-
# Create a cache with a capacity of 100 entries, with a TTL of
|
|
86
|
-
# and a TTI of
|
|
87
|
-
# and are removed after
|
|
88
|
-
#
|
|
89
|
-
# Both TTL and TTI settings are optional. In the absence of an entry,
|
|
80
|
+
# Create a cache with a capacity of 100 entries, with a TTL of 10.0 seconds
|
|
81
|
+
# and a TTI of 0.1 seconds. Entries are always removed after 10 seconds
|
|
82
|
+
# and are removed after 0.1 seconds if there are no `get`s happened for this time.
|
|
83
|
+
#
|
|
84
|
+
# Both TTL and TTI settings are optional. In the absence of an entry,
|
|
90
85
|
# the corresponding policy will not expire it.
|
|
91
86
|
|
|
92
87
|
# The default eviction policy is "tiny_lfu" which is optimal for most workloads,
|
|
93
88
|
# but you can choose "lru" as well.
|
|
94
|
-
cache: Moka[str, list[int]] = Moka(capacity=100, ttl=
|
|
89
|
+
cache: Moka[str, list[int]] = Moka(capacity=100, ttl=10.0, tti=0.1, policy="lru")
|
|
95
90
|
|
|
96
91
|
# Insert a value.
|
|
97
92
|
cache.set("key", [3, 2, 1])
|
|
@@ -99,8 +94,8 @@ cache.set("key", [3, 2, 1])
|
|
|
99
94
|
# Retrieve the value.
|
|
100
95
|
assert cache.get("key") == [3, 2, 1]
|
|
101
96
|
|
|
102
|
-
# Wait for
|
|
103
|
-
sleep(
|
|
97
|
+
# Wait for 0.1+ seconds, and the entry will be automatically evicted.
|
|
98
|
+
sleep(0.12)
|
|
104
99
|
assert cache.get("key") is None
|
|
105
100
|
```
|
|
106
101
|
|
|
@@ -113,16 +108,21 @@ from time import sleep
|
|
|
113
108
|
from moka_py import cached
|
|
114
109
|
|
|
115
110
|
|
|
116
|
-
|
|
111
|
+
calls = []
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@cached(maxsize=1024, ttl=5.0, tti=0.05)
|
|
117
115
|
def f(x, y):
|
|
118
|
-
|
|
116
|
+
calls.append((x, y))
|
|
119
117
|
return x + y
|
|
120
118
|
|
|
121
119
|
|
|
122
|
-
f(1, 2) # calls computations
|
|
123
|
-
f(1, 2) # gets from the cache
|
|
124
|
-
|
|
125
|
-
|
|
120
|
+
assert f(1, 2) == 3 # calls computations
|
|
121
|
+
assert f(1, 2) == 3 # gets from the cache
|
|
122
|
+
assert len(calls) == 1
|
|
123
|
+
sleep(0.06)
|
|
124
|
+
assert f(1, 2) == 3 # calls computations again (since TTI has passed)
|
|
125
|
+
assert len(calls) == 2
|
|
126
126
|
```
|
|
127
127
|
|
|
128
128
|
### Async support
|
|
@@ -135,22 +135,27 @@ from time import perf_counter
|
|
|
135
135
|
from moka_py import cached
|
|
136
136
|
|
|
137
137
|
|
|
138
|
-
|
|
138
|
+
calls = []
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@cached(maxsize=1024, ttl=5.0, tti=0.1)
|
|
139
142
|
async def f(x, y):
|
|
140
|
-
|
|
141
|
-
await asyncio.sleep(
|
|
143
|
+
calls.append((x, y))
|
|
144
|
+
await asyncio.sleep(0.05)
|
|
142
145
|
return x + y
|
|
143
146
|
|
|
144
147
|
|
|
145
148
|
start = perf_counter()
|
|
146
149
|
assert asyncio.run(f(5, 6)) == 11
|
|
147
|
-
assert asyncio.run(f(5, 6)) == 11 #
|
|
148
|
-
|
|
150
|
+
assert asyncio.run(f(5, 6)) == 11 # from cache
|
|
151
|
+
elapsed = perf_counter() - start
|
|
152
|
+
assert elapsed < 0.2
|
|
153
|
+
assert len(calls) == 1
|
|
149
154
|
```
|
|
150
155
|
|
|
151
|
-
###
|
|
156
|
+
### Coalesce concurrent calls (wait_concurrent)
|
|
152
157
|
|
|
153
|
-
moka-py can synchronize threads on keys
|
|
158
|
+
`moka-py` can synchronize threads on keys
|
|
154
159
|
|
|
155
160
|
```python
|
|
156
161
|
import moka_py
|
|
@@ -166,7 +171,7 @@ calls = []
|
|
|
166
171
|
@moka_py.cached(ttl=5, wait_concurrent=True)
|
|
167
172
|
def get_user(id_: int) -> dict[str, Any]:
|
|
168
173
|
calls.append(id_)
|
|
169
|
-
sleep(0.
|
|
174
|
+
sleep(0.02) # simulate an HTTP request (short for tests)
|
|
170
175
|
return {
|
|
171
176
|
"id": id_,
|
|
172
177
|
"first_name": "Jack",
|
|
@@ -176,13 +181,11 @@ def get_user(id_: int) -> dict[str, Any]:
|
|
|
176
181
|
|
|
177
182
|
def process_request(path: str, user_id: int) -> None:
|
|
178
183
|
user = get_user(user_id)
|
|
179
|
-
print(f"user #{user_id} came to {path}, their info is {user}")
|
|
180
184
|
...
|
|
181
185
|
|
|
182
186
|
|
|
183
187
|
def charge_money(from_user_id: int, amount: Decimal) -> None:
|
|
184
188
|
user = get_user(from_user_id)
|
|
185
|
-
print(f"charging {amount} money from user #{from_user_id} ({user['first_name']} {user['last_name']})")
|
|
186
189
|
...
|
|
187
190
|
|
|
188
191
|
|
|
@@ -194,20 +197,23 @@ if __name__ == '__main__':
|
|
|
194
197
|
request_processing.join()
|
|
195
198
|
money_charging.join()
|
|
196
199
|
|
|
197
|
-
#
|
|
198
|
-
#
|
|
199
|
-
assert len(calls) == 1
|
|
200
|
+
# Only one call occurred. Without `wait_concurrent`, each thread would issue its own HTTP request
|
|
201
|
+
# before the cache entry is set.
|
|
202
|
+
assert len(calls) == 1
|
|
200
203
|
```
|
|
201
204
|
|
|
202
|
-
|
|
205
|
+
### Async wait_concurrent
|
|
206
|
+
|
|
207
|
+
When using `wait_concurrent=True` with async functions, `moka-py` creates a shared `asyncio.Task` per cache key. All
|
|
208
|
+
concurrent callers `await` the same task and receive the same result or exception. This eliminates duplicate in-flight
|
|
209
|
+
work for identical arguments.
|
|
203
210
|
|
|
204
211
|
### Eviction listener
|
|
205
212
|
|
|
206
|
-
moka-py supports
|
|
207
|
-
|
|
208
|
-
are passed as positional (not keyword).
|
|
213
|
+
`moka-py` supports an eviction listener, called whenever a key is removed.
|
|
214
|
+
The listener must be a three-argument function `(key, value, cause)` and uses positional arguments only.
|
|
209
215
|
|
|
210
|
-
|
|
216
|
+
Possible reasons:
|
|
211
217
|
|
|
212
218
|
1. `"expired"`: The entry's expiration timestamp has passed.
|
|
213
219
|
2. `"explicit"`: The entry was manually removed by the user (`.remove()` is called).
|
|
@@ -222,41 +228,37 @@ from time import sleep
|
|
|
222
228
|
|
|
223
229
|
|
|
224
230
|
def key_evicted(
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
231
|
+
k: str,
|
|
232
|
+
v: list[int],
|
|
233
|
+
cause: Literal["explicit", "size", "expired", "replaced"]
|
|
228
234
|
):
|
|
229
|
-
|
|
235
|
+
events.append((k, v, cause))
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
events: list[tuple[str, list[int], str]] = []
|
|
230
239
|
|
|
231
240
|
|
|
232
|
-
moka: Moka[str, list[int]] = Moka(2, eviction_listener=key_evicted, ttl=0.
|
|
241
|
+
moka: Moka[str, list[int]] = Moka(2, eviction_listener=key_evicted, ttl=0.5)
|
|
233
242
|
moka.set("hello", [1, 2, 3])
|
|
234
|
-
moka.set("hello", [3, 2, 1])
|
|
235
|
-
moka.set("foo", [4])
|
|
236
|
-
moka.set("
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
# entry hello:[3, 2, 1] was evicted. cause='expired'
|
|
244
|
-
# entry foo:[4] was evicted. cause='expired'
|
|
243
|
+
moka.set("hello", [3, 2, 1]) # replaced
|
|
244
|
+
moka.set("foo", [4]) # expired
|
|
245
|
+
moka.set("baz", "size")
|
|
246
|
+
moka.remove("foo") # explicit
|
|
247
|
+
sleep(1.0)
|
|
248
|
+
moka.get("anything") # this will trigger eviction for expired
|
|
249
|
+
|
|
250
|
+
causes = {c for _, _, c in events}
|
|
251
|
+
assert causes == {"size", "expired", "replaced", "explicit"}, events
|
|
245
252
|
```
|
|
246
253
|
|
|
247
|
-
>
|
|
248
|
-
> 1
|
|
249
|
-
|
|
250
|
-
>
|
|
251
|
-
moka-py method in any of the threads that call this method.
|
|
252
|
-
> 3. The listener must be fast. Since it's called only when you're interacting with `moka-py` (via `.get()` / `.set()` /
|
|
253
|
-
etc.), the listener will slow down these operations. It's terrible idea to do some sort of IO in the listener. If
|
|
254
|
-
you need so, run a `ThreadPoolExecutor` somewhere and call `.submit()` inside of the listener or commit an async
|
|
255
|
-
task via `asyncio.create_task()`
|
|
254
|
+
> IMPORTANT NOTES
|
|
255
|
+
> 1) The listener is not called just-in-time. `moka` has no background threads or tasks; it runs only during cache operations.
|
|
256
|
+
> 2) The listener must not raise exceptions. If it does, the exception may surface from any `moka-py` method on any thread.
|
|
257
|
+
> 3) Keep the listener fast. Heavy work (especially I/O) will slow `.get()`, `.set()`, etc. Offload via `ThreadPoolExecutor.submit()` or `asyncio.create_task()`
|
|
256
258
|
|
|
257
259
|
### Removing entries
|
|
258
260
|
|
|
259
|
-
|
|
261
|
+
Remove an entry with `Moka.remove(key)`. It returns the previous value if present; otherwise `None`.
|
|
260
262
|
|
|
261
263
|
```python
|
|
262
264
|
from moka_py import Moka
|
|
@@ -268,8 +270,7 @@ assert c.remove("hello") == "world"
|
|
|
268
270
|
assert c.get("hello") is None
|
|
269
271
|
```
|
|
270
272
|
|
|
271
|
-
|
|
272
|
-
value and `None` as the absence of a value. Use `Moka.remove(key, default=...)`:
|
|
273
|
+
If `None` is a valid cached value, distinguish it from absence using `Moka.remove(key, default=...)`:
|
|
273
274
|
|
|
274
275
|
```python
|
|
275
276
|
from moka_py import Moka
|
|
@@ -277,19 +278,18 @@ from moka_py import Moka
|
|
|
277
278
|
|
|
278
279
|
c = Moka(128)
|
|
279
280
|
c.set("hello", None)
|
|
280
|
-
assert c.remove("hello", default="WAS_NOT_SET") is None # None
|
|
281
|
+
assert c.remove("hello", default="WAS_NOT_SET") is None # None was set explicitly
|
|
281
282
|
|
|
282
|
-
# Now entry
|
|
283
|
-
assert c.remove("hello", default="WAS_NOT_SET") == "WAS_NOT_SET"
|
|
283
|
+
# Now the entry "hello" does not exist, so `default` is returned
|
|
284
|
+
assert c.remove("hello", default="WAS_NOT_SET") == "WAS_NOT_SET"
|
|
284
285
|
```
|
|
285
286
|
|
|
286
287
|
## How it works
|
|
287
288
|
|
|
288
|
-
`Moka`
|
|
289
|
-
(by [`
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
are still mutable:
|
|
289
|
+
`Moka` stores Python object references
|
|
290
|
+
(by [`Py_INCREF`](https://docs.python.org/3/c-api/refcounting.html#c.Py_INCREF)) and does not serialize or deserialize values.
|
|
291
|
+
You can use any Python object as a value and any hashable object as a key (`__hash__` is used).
|
|
292
|
+
Mutable objects remain mutable:
|
|
293
293
|
|
|
294
294
|
```python
|
|
295
295
|
from moka_py import Moka
|
|
@@ -305,8 +305,8 @@ assert my_list == [1, 2, 3, 4]
|
|
|
305
305
|
|
|
306
306
|
## Eviction policies
|
|
307
307
|
|
|
308
|
-
moka-py uses
|
|
309
|
-
|
|
308
|
+
`moka-py` uses TinyLFU by default, with an LRU option. Learn more in the
|
|
309
|
+
[Moka wiki](https://github.com/moka-rs/moka/wiki#admission-and-eviction-policies).
|
|
310
310
|
|
|
311
311
|
## Performance
|
|
312
312
|
|
|
@@ -330,5 +330,5 @@ test_bench_set[lru] 637.3014 (6.32) 644.4533 (5.92) 640.3
|
|
|
330
330
|
|
|
331
331
|
## License
|
|
332
332
|
|
|
333
|
-
moka-py is distributed under the [MIT license](LICENSE)
|
|
333
|
+
`moka-py` is distributed under the [MIT license](LICENSE).
|
|
334
334
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
moka_py-0.2.4.dist-info/METADATA,sha256=e-jmd2RcuG_uvXEOaMjJCkLE6QHQR5FZzkkvEFy6wfQ,11475
|
|
2
|
+
moka_py-0.2.4.dist-info/WHEEL,sha256=SBYB-KgAKBY8TnY5zERCkW8dJxVemh2GzdJfUPaGwHw,107
|
|
3
|
+
moka_py-0.2.4.dist-info/licenses/LICENSE,sha256=CUj5ca53JXgIACVKNEOFOlbMWtxY4RXXj9cELIv2R04,1069
|
|
4
|
+
moka_py.libs/libgcc_s-0366c7ba.so.1,sha256=QjFj5R7pVqB-p92h2JTCmlyfd3UKsVGW_Y_l3SqOAaU,2753157
|
|
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-310-arm-linux-gnueabihf.so,sha256=1Ww9i8gMQ6y3rVt-MAhzr7MToJLtekWfRv2xEfQ9-h0,559545
|
|
8
|
+
moka_py/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
moka_py-0.2.4.dist-info/RECORD,,
|
moka_py-0.1.19.dist-info/RECORD
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
moka_py-0.1.19.dist-info/METADATA,sha256=gywOXWmAzickgFshSpu0pcRFPM9NOIjVZR5j9xrKss0,12348
|
|
2
|
-
moka_py-0.1.19.dist-info/WHEEL,sha256=YZmFb3YQ5Iws9lY_kljqlKnJk4FmTOjb2rLVa1vIm68,107
|
|
3
|
-
moka_py-0.1.19.dist-info/licenses/LICENSE,sha256=CUj5ca53JXgIACVKNEOFOlbMWtxY4RXXj9cELIv2R04,1069
|
|
4
|
-
moka_py.libs/libgcc_s-0366c7ba.so.1,sha256=QjFj5R7pVqB-p92h2JTCmlyfd3UKsVGW_Y_l3SqOAaU,2753157
|
|
5
|
-
moka_py/__init__.py,sha256=Vm6tQsXweLtjD_QjBSWtJRquv9g7wq3zrQZJ7EBQGko,1572
|
|
6
|
-
moka_py/__init__.pyi,sha256=wfOnFVeRxogZpBwhUMWqlAVw_0yAa5xuCO28GJNTkGg,1777
|
|
7
|
-
moka_py/moka_py.cpython-310-arm-linux-gnueabihf.so,sha256=9Ld8FIhaHymufBCp4eYXVrJFvBwcQO8fUMfn9OW8bvM,559545
|
|
8
|
-
moka_py/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
-
moka_py-0.1.19.dist-info/RECORD,,
|
|
File without changes
|