tigrbl_engine_memlru 0.1.10.dev1__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.
@@ -0,0 +1,2 @@
1
+ __all__ = ["__version__"]
2
+ __version__ = "0.1.0"
@@ -0,0 +1,102 @@
1
+ from __future__ import annotations
2
+
3
+ from collections import OrderedDict
4
+ from dataclasses import dataclass
5
+ from threading import RLock
6
+ from time import monotonic
7
+ from typing import Any
8
+
9
+
10
+ @dataclass
11
+ class _Entry:
12
+ value: Any
13
+ cost: float
14
+ t: float
15
+
16
+
17
+ class LRUCache:
18
+ """LRU cache with optional cost budget.
19
+
20
+ Eviction:
21
+ - Always enforces max_items.
22
+ - If max_cost set, evicts LRU until total_cost <= max_cost.
23
+ """
24
+
25
+ def __init__(
26
+ self,
27
+ *,
28
+ max_items: int = 100_000,
29
+ max_cost: float | None = None,
30
+ default_cost: float = 1.0,
31
+ namespace: str = "default",
32
+ ) -> None:
33
+ if max_items <= 0:
34
+ raise ValueError("max_items must be > 0")
35
+ if max_cost is not None and max_cost <= 0:
36
+ raise ValueError("max_cost must be > 0 if set")
37
+ if default_cost <= 0:
38
+ raise ValueError("default_cost must be > 0")
39
+ self.max_items = int(max_items)
40
+ self.max_cost = float(max_cost) if max_cost is not None else None
41
+ self.default_cost = float(default_cost)
42
+ self.namespace = namespace
43
+
44
+ self._lock = RLock()
45
+ self._od: OrderedDict[str, _Entry] = OrderedDict()
46
+ self._total_cost = 0.0
47
+
48
+ def get(self, key: str, default: Any = None) -> Any:
49
+ with self._lock:
50
+ e = self._od.get(key)
51
+ if e is None:
52
+ return default
53
+ self._od.move_to_end(key, last=True)
54
+ return e.value
55
+
56
+ def set(self, key: str, value: Any, *, cost: float | None = None) -> None:
57
+ if cost is None:
58
+ cost = self.default_cost
59
+ c = float(cost)
60
+ if c <= 0:
61
+ raise ValueError("cost must be > 0")
62
+ now = monotonic()
63
+ with self._lock:
64
+ old = self._od.pop(key, None)
65
+ if old is not None:
66
+ self._total_cost -= old.cost
67
+ self._od[key] = _Entry(value=value, cost=c, t=now)
68
+ self._od.move_to_end(key, last=True)
69
+ self._total_cost += c
70
+ self._evict()
71
+
72
+ def delete(self, key: str) -> bool:
73
+ with self._lock:
74
+ e = self._od.pop(key, None)
75
+ if e is None:
76
+ return False
77
+ self._total_cost -= e.cost
78
+ return True
79
+
80
+ def clear(self) -> None:
81
+ with self._lock:
82
+ self._od.clear()
83
+ self._total_cost = 0.0
84
+
85
+ def stats(self) -> dict[str, Any]:
86
+ with self._lock:
87
+ return {
88
+ "size": len(self._od),
89
+ "max_items": self.max_items,
90
+ "total_cost": self._total_cost,
91
+ "max_cost": self.max_cost,
92
+ "default_cost": self.default_cost,
93
+ }
94
+
95
+ def _evict(self) -> None:
96
+ while len(self._od) > self.max_items:
97
+ k, e = self._od.popitem(last=False)
98
+ self._total_cost -= e.cost
99
+ if self.max_cost is not None:
100
+ while self._total_cost > self.max_cost and self._od:
101
+ k, e = self._od.popitem(last=False)
102
+ self._total_cost -= e.cost
@@ -0,0 +1,41 @@
1
+ from __future__ import annotations
2
+
3
+ from tigrbl.engine.registry import register_engine
4
+
5
+ from .lru import LRUCache
6
+ from .session import LRUSession, AsyncLRUSession
7
+
8
+
9
+ def register() -> None:
10
+ register_engine(kind="memlru", build=build_memlru, capabilities=capabilities)
11
+
12
+
13
+ def capabilities() -> dict:
14
+ return {
15
+ "engine": "memlru",
16
+ "transactional": False,
17
+ "async_native": True,
18
+ "persistence": "process",
19
+ "features": {"lru", "cost_based_optional"},
20
+ }
21
+
22
+
23
+ def build_memlru(*, mapping=None, spec=None, dsn=None, **_) -> tuple[object, object]:
24
+ mapping = dict(mapping or {})
25
+ async_ = bool(getattr(spec, "async_", False))
26
+
27
+ max_items = int(mapping.get("max_items", 100_000))
28
+ max_cost = float(mapping.get("max_cost", 0.0)) or None
29
+ default_cost = float(mapping.get("default_cost", 1.0))
30
+ namespace = str(mapping.get("namespace", "default"))
31
+
32
+ engine = LRUCache(max_items=max_items, max_cost=max_cost, default_cost=default_cost, namespace=namespace)
33
+
34
+ if async_:
35
+ def sessionmaker():
36
+ return AsyncLRUSession(engine)
37
+ else:
38
+ def sessionmaker():
39
+ return LRUSession(engine)
40
+
41
+ return engine, sessionmaker
@@ -0,0 +1,43 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from .lru import LRUCache
6
+
7
+
8
+ class LRUSession:
9
+ def __init__(self, engine: LRUCache) -> None:
10
+ self._engine = engine
11
+ self._closed = False
12
+
13
+ def close(self) -> None:
14
+ self._closed = True
15
+
16
+ def get(self, key: str, default: Any = None) -> Any:
17
+ self._require_open()
18
+ return self._engine.get(key, default)
19
+
20
+ def set(self, key: str, value: Any, *, cost: float | None = None) -> None:
21
+ self._require_open()
22
+ self._engine.set(key, value, cost=cost)
23
+
24
+ def delete(self, key: str) -> bool:
25
+ self._require_open()
26
+ return self._engine.delete(key)
27
+
28
+ def clear(self) -> None:
29
+ self._require_open()
30
+ self._engine.clear()
31
+
32
+ def stats(self) -> dict:
33
+ self._require_open()
34
+ return self._engine.stats()
35
+
36
+ def _require_open(self) -> None:
37
+ if self._closed:
38
+ raise RuntimeError("session is closed")
39
+
40
+
41
+ class AsyncLRUSession(LRUSession):
42
+ async def close(self) -> None: # type: ignore[override]
43
+ super().close()
@@ -0,0 +1,5 @@
1
+ Metadata-Version: 2.4
2
+ Name: tigrbl_engine_memlru
3
+ Version: 0.1.10.dev1
4
+ Requires-Python: <3.14,>=3.10
5
+ Requires-Dist: tigrbl
@@ -0,0 +1,8 @@
1
+ tigrbl_engine_memlru/__init__.py,sha256=tXbRXsO0NE_UV1kIHiZTTQQH0fj0U2KoxxNusu_gzrM,48
2
+ tigrbl_engine_memlru/lru.py,sha256=CXVhMR9Aj2TrY3HIU2l0Y6mvIgSCSvycCM2oPBTmX2k,3097
3
+ tigrbl_engine_memlru/plugin.py,sha256=Fj_iH3GPiqYKmrgKWsWNv7TZ9485_J3j7kWCLtVhrxM,1206
4
+ tigrbl_engine_memlru/session.py,sha256=rOjMIiwcHsDx0Gpd3BoXIuPaSjL8jFEf0kGQWEFCVkk,1093
5
+ tigrbl_engine_memlru-0.1.10.dev1.dist-info/METADATA,sha256=OsvJfWbX6IBbh-NjXdLNZhHGv7lZogs4MXPjC0tD2ts,122
6
+ tigrbl_engine_memlru-0.1.10.dev1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
7
+ tigrbl_engine_memlru-0.1.10.dev1.dist-info/entry_points.txt,sha256=QhuLp8RxZvrSi9HoLKbMmy2mzwiQBpGyyArDf60LPsQ,62
8
+ tigrbl_engine_memlru-0.1.10.dev1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [tigrbl.engine]
2
+ memlru = tigrbl_engine_memlru.plugin:register