rust-py-cache 0.1.1__cp310-abi3-win_amd64.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.
- rust_py_cache/__init__.py +22 -0
- rust_py_cache/_rust_py_cache.pyd +0 -0
- rust_py_cache/decorators.py +64 -0
- rust_py_cache-0.1.1.dist-info/METADATA +136 -0
- rust_py_cache-0.1.1.dist-info/RECORD +7 -0
- rust_py_cache-0.1.1.dist-info/WHEEL +4 -0
- rust_py_cache-0.1.1.dist-info/sboms/rust_py_cache.cyclonedx.json +1190 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""rust-py-cache — An ultra-fast local cache for Python, powered by Rust."""
|
|
2
|
+
|
|
3
|
+
# O módulo nativo (_rust_py_cache) é compilado pelo Rust/maturin e fica DENTRO
|
|
4
|
+
# deste pacote. O core (set/get/delete/exists/keys/stats/cleanup_expired) vem do
|
|
5
|
+
# Rust; aqui só adicionamos açúcar Python — o decorator `cached` — via subclasse.
|
|
6
|
+
from ._rust_py_cache import Cache as _RustCache, hello
|
|
7
|
+
from .decorators import cached as _cached
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Cache(_RustCache):
|
|
11
|
+
"""Cache local em memória (core em Rust) + decorator `cached` em Python.
|
|
12
|
+
|
|
13
|
+
Herda todos os métodos do core nativo e adiciona `@cache.cached(...)`. A
|
|
14
|
+
herança só é possível porque o `#[pyclass(subclass)]` libera o tipo nativo
|
|
15
|
+
para ser estendido no Python.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
cached = _cached
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
__all__ = ["Cache", "hello"]
|
|
22
|
+
__version__ = "0.1.1"
|
|
Binary file
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Decorator de memoização — Etapa 17.
|
|
2
|
+
|
|
3
|
+
Fornece `Cache.cached`, um decorator que guarda o retorno de uma função no cache.
|
|
4
|
+
Fica em Python (não no core Rust) porque mexe com `*args/**kwargs`, geração de
|
|
5
|
+
chave e `functools.wraps` — tudo muito mais natural aqui.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import functools
|
|
9
|
+
|
|
10
|
+
# Sentinela para distinguir "ausente no cache" de "valor None legítimo".
|
|
11
|
+
# `cache.get(key, _MISSING)` devolve `_MISSING` só quando a chave não existe.
|
|
12
|
+
_MISSING = object()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _default_key(func, args, kwargs):
|
|
16
|
+
"""Chave determinística a partir da função e dos argumentos.
|
|
17
|
+
|
|
18
|
+
Usa `repr` dos argumentos — simples e legível. Pressupõe argumentos
|
|
19
|
+
repr-áveis e estáveis (o caso comum: números, strings, tuplas). Para casos
|
|
20
|
+
exóticos, o usuário pode passar `key=...` explícito.
|
|
21
|
+
"""
|
|
22
|
+
parts = [func.__module__ or "", func.__qualname__, repr(args)]
|
|
23
|
+
if kwargs:
|
|
24
|
+
parts.append(repr(sorted(kwargs.items())))
|
|
25
|
+
return "|".join(parts)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def cached(self, ttl=None, key=None):
|
|
29
|
+
"""Decorator: memoiza o retorno da função neste cache.
|
|
30
|
+
|
|
31
|
+
Parâmetros:
|
|
32
|
+
ttl: validade em segundos (igual a `set`); `None` = sem expiração.
|
|
33
|
+
key: opcional. String fixa, ou callable `key(*args, **kwargs) -> str`.
|
|
34
|
+
Se omitido, a chave é derivada de função + argumentos.
|
|
35
|
+
|
|
36
|
+
Uso:
|
|
37
|
+
@cache.cached(ttl=60)
|
|
38
|
+
def soma(a, b):
|
|
39
|
+
return a + b
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def decorator(func):
|
|
43
|
+
@functools.wraps(func)
|
|
44
|
+
def wrapper(*args, **kwargs):
|
|
45
|
+
if key is None:
|
|
46
|
+
cache_key = _default_key(func, args, kwargs)
|
|
47
|
+
elif callable(key):
|
|
48
|
+
cache_key = key(*args, **kwargs)
|
|
49
|
+
else:
|
|
50
|
+
cache_key = key
|
|
51
|
+
|
|
52
|
+
hit = self.get(cache_key, _MISSING)
|
|
53
|
+
if hit is not _MISSING:
|
|
54
|
+
return hit
|
|
55
|
+
|
|
56
|
+
result = func(*args, **kwargs)
|
|
57
|
+
self.set(cache_key, result, ttl=ttl)
|
|
58
|
+
return result
|
|
59
|
+
|
|
60
|
+
# Expõe a chave-base para inspeção/invalidação manual em testes.
|
|
61
|
+
wrapper.__wrapped__ = func
|
|
62
|
+
return wrapper
|
|
63
|
+
|
|
64
|
+
return decorator
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rust-py-cache
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Classifier: Programming Language :: Rust
|
|
5
|
+
Classifier: Programming Language :: Python :: 3
|
|
6
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
9
|
+
Classifier: Topic :: Database
|
|
10
|
+
Classifier: Topic :: System :: Distributed Computing
|
|
11
|
+
Summary: An ultra-fast local cache for Python, powered by Rust.
|
|
12
|
+
Keywords: cache,rust,pyo3,ttl,performance
|
|
13
|
+
Author-email: Roberto Lima <robertolima.izphera@gmail.com>
|
|
14
|
+
License: MIT
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
17
|
+
Project-URL: Homepage, https://github.com/robertolima/rust-py-cache
|
|
18
|
+
|
|
19
|
+
# rust-py-cache
|
|
20
|
+
|
|
21
|
+
> **An ultra-fast local cache for Python, powered by Rust.**
|
|
22
|
+
|
|
23
|
+
A local, in-memory, thread-safe cache with TTL, lazy expiration, and metrics. The
|
|
24
|
+
core is written in Rust (PyO3 + maturin) on top of a concurrent `DashMap`; the
|
|
25
|
+
Python API is minimal. Think of it as a "mini Redis" living **inside** your Python
|
|
26
|
+
process.
|
|
27
|
+
|
|
28
|
+
[](https://pypi.org/project/rust-py-cache/)
|
|
29
|
+
[](https://pypi.org/project/rust-py-cache/)
|
|
30
|
+
[](./LICENSE)
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install rust-py-cache
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
To work on it locally (requires Rust + maturin):
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
python -m venv .venv && source .venv/bin/activate
|
|
42
|
+
pip install maturin pytest
|
|
43
|
+
maturin develop # compiles the Rust core and installs into the venv
|
|
44
|
+
pytest # runs the tests
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Usage
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from rust_py_cache import Cache
|
|
51
|
+
|
|
52
|
+
cache = Cache()
|
|
53
|
+
|
|
54
|
+
cache.set("user:1", {"name": "Roberto"}, ttl=60) # ttl in seconds
|
|
55
|
+
user = cache.get("user:1") # {"name": "Roberto"}
|
|
56
|
+
cache.get("missing", default=0) # 0
|
|
57
|
+
|
|
58
|
+
cache.exists("user:1") # True (honors TTL)
|
|
59
|
+
cache.delete("user:1") # True if removed, False if absent
|
|
60
|
+
cache.len() # approximate size
|
|
61
|
+
cache.keys() # list of keys
|
|
62
|
+
cache.cleanup_expired() # remove expired entries; returns the count
|
|
63
|
+
cache.clear() # remove everything (keeps counters)
|
|
64
|
+
cache.stats() # {'hits', 'misses', 'sets', 'deletes', 'expired', 'size'}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Memoization decorator
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
@cache.cached(ttl=60)
|
|
71
|
+
def add(a, b):
|
|
72
|
+
return a + b
|
|
73
|
+
|
|
74
|
+
add(2, 3) # runs and caches
|
|
75
|
+
add(2, 3) # served from cache
|
|
76
|
+
|
|
77
|
+
# custom key (fixed string or callable):
|
|
78
|
+
@cache.cached(ttl=300, key=lambda user_id: f"user:{user_id}")
|
|
79
|
+
def load_user(user_id):
|
|
80
|
+
...
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
See full examples under [`examples/`](./examples) (FastAPI and Django).
|
|
84
|
+
|
|
85
|
+
## API
|
|
86
|
+
|
|
87
|
+
| Method | Description |
|
|
88
|
+
|---|---|
|
|
89
|
+
| `set(key, value, ttl=None)` | Store a value. `ttl` in seconds (`int`/`float`); `None` = no expiration; `ttl <= 0` → `ValueError`. Overwrites. |
|
|
90
|
+
| `get(key, default=None)` | The value, or `default` if missing/expired (expired entries are removed). |
|
|
91
|
+
| `delete(key)` | `True` if removed, `False` if it didn't exist. |
|
|
92
|
+
| `exists(key)` | `True`/`False`, honoring TTL. |
|
|
93
|
+
| `keys()` | List of keys (may include expired-but-not-yet-collected ones). |
|
|
94
|
+
| `len()` / `len(cache)` | Approximate size. |
|
|
95
|
+
| `clear()` | Remove everything (does not reset counters). |
|
|
96
|
+
| `cleanup_expired()` | Remove expired entries; returns how many. |
|
|
97
|
+
| `stats()` | `dict` with `hits, misses, sets, deletes, expired, size`. |
|
|
98
|
+
| `@cache.cached(ttl=None, key=None)` | Memoization decorator. |
|
|
99
|
+
|
|
100
|
+
## How it works
|
|
101
|
+
|
|
102
|
+
- **Serialization:** in the MVP, values are serialized with `pickle` (on the Python
|
|
103
|
+
side, via PyO3) and stored as opaque bytes (`Vec<u8>`) in the Rust core.
|
|
104
|
+
- **Concurrency:** `DashMap` (a HashMap with per-shard locks) plus `AtomicU64`
|
|
105
|
+
counters, with no global lock on the hot path. Thread-safe, no busy loop.
|
|
106
|
+
- **TTL:** expiration is **lazy** — an expired key is removed when accessed
|
|
107
|
+
(`get`/`exists`) or via `cleanup_expired()`. There is no background thread in the MVP.
|
|
108
|
+
|
|
109
|
+
## Limitations
|
|
110
|
+
|
|
111
|
+
- The cache is **process-local**: multiple workers = multiple independent caches.
|
|
112
|
+
- It does **not** replace Redis for distributed caching.
|
|
113
|
+
- Data is **lost** when the process restarts.
|
|
114
|
+
- `pickle` must **not** be used to deserialize untrusted data.
|
|
115
|
+
- Lazy TTL: expired items may linger until accessed or until `cleanup_expired()`.
|
|
116
|
+
|
|
117
|
+
## Development
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
cargo test # Rust core tests
|
|
121
|
+
maturin develop # rebuild and install
|
|
122
|
+
pytest # Python tests
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
> If `maturin develop` complains about both `VIRTUAL_ENV` and `CONDA_PREFIX` being
|
|
126
|
+
> set, run `conda deactivate` first, or use `env -u CONDA_PREFIX maturin develop`.
|
|
127
|
+
|
|
128
|
+
## Roadmap
|
|
129
|
+
|
|
130
|
+
Stages and next steps (LRU/LFU eviction, background expiration, configurable
|
|
131
|
+
serializer, namespaces, etc.) are in [ROADMAP.md](./ROADMAP.md).
|
|
132
|
+
|
|
133
|
+
## License
|
|
134
|
+
|
|
135
|
+
MIT
|
|
136
|
+
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
rust_py_cache/__init__.py,sha256=ik8zJVOuYg8BrHSP2psDcyOJc6vqUyTLfaflD2UQ9l0,834
|
|
2
|
+
rust_py_cache/_rust_py_cache.pyd,sha256=0L6i6vgdBVDi-10ZsIo3cuG7MyrqK6tYCPFekQ6JAvE,331264
|
|
3
|
+
rust_py_cache/decorators.py,sha256=fqH-JQTwSilH-m7te4n9GvPQUZgVoGYZh--ne3_GU_E,2174
|
|
4
|
+
rust_py_cache-0.1.1.dist-info/METADATA,sha256=jHZu9sabDZ6heDUaB-SmAFI1OUACESNJmO65YL1EutA,5028
|
|
5
|
+
rust_py_cache-0.1.1.dist-info/WHEEL,sha256=_qjkWZs5yrFgc7wGyX42BWq6PiACrY0XI82VSrZ_57E,96
|
|
6
|
+
rust_py_cache-0.1.1.dist-info/sboms/rust_py_cache.cyclonedx.json,sha256=W1bqYnXOSg9GnNzf65_nwMPvsJKmgB_iuTANf58XjhA,36745
|
|
7
|
+
rust_py_cache-0.1.1.dist-info/RECORD,,
|