tigrbl_engine_memqueue 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,40 @@
1
+ from __future__ import annotations
2
+
3
+ from tigrbl.engine.registry import register_engine
4
+
5
+ from .queuehub import QueueHub
6
+ from .session import QueueSession, AsyncQueueSession
7
+
8
+
9
+ def register() -> None:
10
+ register_engine(kind="memqueue", build=build_memqueue, capabilities=capabilities)
11
+
12
+
13
+ def capabilities() -> dict:
14
+ return {
15
+ "engine": "memqueue",
16
+ "transactional": False,
17
+ "async_native": True,
18
+ "persistence": "process",
19
+ "features": {"fifo", "lifo", "priority_optional", "named_queues"},
20
+ }
21
+
22
+
23
+ def build_memqueue(*, mapping=None, spec=None, dsn=None, **_) -> tuple[object, object]:
24
+ mapping = dict(mapping or {})
25
+ async_ = bool(getattr(spec, "async_", False))
26
+
27
+ mode = str(mapping.get("mode", "fifo")).lower()
28
+ max_items = int(mapping.get("max_items", 100_000))
29
+ namespace = str(mapping.get("namespace", "default"))
30
+
31
+ engine = QueueHub(mode=mode, max_items=max_items, namespace=namespace)
32
+
33
+ if async_:
34
+ def sessionmaker():
35
+ return AsyncQueueSession(engine)
36
+ else:
37
+ def sessionmaker():
38
+ return QueueSession(engine)
39
+
40
+ return engine, sessionmaker
@@ -0,0 +1,135 @@
1
+ from __future__ import annotations
2
+
3
+ import heapq
4
+ from collections import deque
5
+ from dataclasses import dataclass
6
+ from threading import Condition, RLock
7
+ from time import monotonic
8
+ from typing import Any
9
+
10
+
11
+ @dataclass
12
+ class _Q:
13
+ mode: str
14
+ max_items: int
15
+ lock: RLock
16
+ cv: Condition
17
+ # one of these is used depending on mode
18
+ dq: deque[Any] | None = None
19
+ heap: list[tuple[int, float, Any]] | None = None
20
+ seq: int = 0
21
+ closed: bool = False
22
+
23
+
24
+ class QueueHub:
25
+ """Named in-process queues with blocking get/put.
26
+
27
+ Modes:
28
+ - fifo: deque append/pop left
29
+ - lifo: deque append/pop
30
+ - priority: heapq of (priority, t, item) (lower priority value first)
31
+ """
32
+
33
+ def __init__(self, *, mode: str = "fifo", max_items: int = 100_000, namespace: str = "default") -> None:
34
+ mode = (mode or "fifo").lower()
35
+ if mode not in {"fifo", "lifo", "priority"}:
36
+ raise ValueError("mode must be fifo|lifo|priority")
37
+ if max_items <= 0:
38
+ raise ValueError("max_items must be > 0")
39
+ self.mode = mode
40
+ self.max_items = int(max_items)
41
+ self.namespace = namespace
42
+ self._lock = RLock()
43
+ self._qs: dict[str, _Q] = {}
44
+
45
+ def _q(self, name: str) -> _Q:
46
+ k = name or "default"
47
+ with self._lock:
48
+ q = self._qs.get(k)
49
+ if q is None:
50
+ lock = RLock()
51
+ cv = Condition(lock)
52
+ if self.mode == "priority":
53
+ q = _Q(mode=self.mode, max_items=self.max_items, lock=lock, cv=cv, heap=[])
54
+ else:
55
+ q = _Q(mode=self.mode, max_items=self.max_items, lock=lock, cv=cv, dq=deque())
56
+ self._qs[k] = q
57
+ return q
58
+
59
+ def close(self, name: str = "default") -> None:
60
+ q = self._q(name)
61
+ with q.lock:
62
+ q.closed = True
63
+ q.cv.notify_all()
64
+
65
+ def put(self, name: str, item: Any, *, priority: int = 0, timeout: float | None = None) -> bool:
66
+ q = self._q(name)
67
+ deadline = None if timeout is None else (monotonic() + max(0.0, float(timeout)))
68
+ with q.lock:
69
+ while not q.closed and self.size(name) >= q.max_items:
70
+ if deadline is None:
71
+ q.cv.wait()
72
+ else:
73
+ remain = deadline - monotonic()
74
+ if remain <= 0:
75
+ return False
76
+ q.cv.wait(remain)
77
+ if q.closed:
78
+ return False
79
+ if q.mode == "priority":
80
+ assert q.heap is not None
81
+ q.seq += 1
82
+ heapq.heappush(q.heap, (int(priority), monotonic(), item))
83
+ else:
84
+ assert q.dq is not None
85
+ q.dq.append(item)
86
+ q.cv.notify()
87
+ return True
88
+
89
+ def get(self, name: str, *, timeout: float | None = None) -> Any | None:
90
+ q = self._q(name)
91
+ deadline = None if timeout is None else (monotonic() + max(0.0, float(timeout)))
92
+ with q.lock:
93
+ while not q.closed and self.size(name) == 0:
94
+ if deadline is None:
95
+ q.cv.wait()
96
+ else:
97
+ remain = deadline - monotonic()
98
+ if remain <= 0:
99
+ return None
100
+ q.cv.wait(remain)
101
+ if self.size(name) == 0:
102
+ return None
103
+ if q.mode == "priority":
104
+ assert q.heap is not None
105
+ _pri, _t, item = heapq.heappop(q.heap)
106
+ elif q.mode == "lifo":
107
+ assert q.dq is not None
108
+ item = q.dq.pop()
109
+ else: # fifo
110
+ assert q.dq is not None
111
+ item = q.dq.popleft()
112
+ q.cv.notify()
113
+ return item
114
+
115
+ def drain(self, name: str, *, max_items: int | None = None) -> list[Any]:
116
+ q = self._q(name)
117
+ out: list[Any] = []
118
+ with q.lock:
119
+ n = self.size(name)
120
+ if max_items is not None:
121
+ n = min(n, int(max_items))
122
+ for _ in range(n):
123
+ item = self.get(name, timeout=0.0)
124
+ if item is None:
125
+ break
126
+ out.append(item)
127
+ return out
128
+
129
+ def size(self, name: str) -> int:
130
+ q = self._q(name)
131
+ if q.mode == "priority":
132
+ assert q.heap is not None
133
+ return len(q.heap)
134
+ assert q.dq is not None
135
+ return len(q.dq)
@@ -0,0 +1,43 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from .queuehub import QueueHub
6
+
7
+
8
+ class QueueSession:
9
+ def __init__(self, engine: QueueHub) -> None:
10
+ self._engine = engine
11
+ self._closed = False
12
+
13
+ def close(self) -> None:
14
+ self._closed = True
15
+
16
+ def put(self, item: Any, *, name: str = "default", priority: int = 0, timeout: float | None = None) -> bool:
17
+ self._require_open()
18
+ return self._engine.put(name, item, priority=priority, timeout=timeout)
19
+
20
+ def get(self, *, name: str = "default", timeout: float | None = None) -> Any | None:
21
+ self._require_open()
22
+ return self._engine.get(name, timeout=timeout)
23
+
24
+ def drain(self, *, name: str = "default", max_items: int | None = None) -> list[Any]:
25
+ self._require_open()
26
+ return self._engine.drain(name, max_items=max_items)
27
+
28
+ def qsize(self, *, name: str = "default") -> int:
29
+ self._require_open()
30
+ return self._engine.size(name)
31
+
32
+ def close_queue(self, *, name: str = "default") -> None:
33
+ self._require_open()
34
+ self._engine.close(name)
35
+
36
+ def _require_open(self) -> None:
37
+ if self._closed:
38
+ raise RuntimeError("session is closed")
39
+
40
+
41
+ class AsyncQueueSession(QueueSession):
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_memqueue
3
+ Version: 0.1.10.dev1
4
+ Requires-Python: <3.14,>=3.10
5
+ Requires-Dist: tigrbl
@@ -0,0 +1,8 @@
1
+ tigrbl_engine_memqueue/__init__.py,sha256=tXbRXsO0NE_UV1kIHiZTTQQH0fj0U2KoxxNusu_gzrM,48
2
+ tigrbl_engine_memqueue/plugin.py,sha256=hkchl1DctA3LvQ8sYj9Suot1Sh-RpCxvjkRT3p6OfBc,1149
3
+ tigrbl_engine_memqueue/queuehub.py,sha256=H39pCkbUuMBLyvpgszJA8QVYV9Lv1g8AhhOfR7DU8Xs,4513
4
+ tigrbl_engine_memqueue/session.py,sha256=iT-wK5BMgDC1l-dRXn3vf8WhEgHx03s53C_P_RvIffk,1346
5
+ tigrbl_engine_memqueue-0.1.10.dev1.dist-info/METADATA,sha256=aO1Vy2SxOI4il8r3CDEkOTxY1qWjrRLFy9Yrs2YNpIM,124
6
+ tigrbl_engine_memqueue-0.1.10.dev1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
7
+ tigrbl_engine_memqueue-0.1.10.dev1.dist-info/entry_points.txt,sha256=zaihZDVkzYBeQoWHkTfCz9ndnTghQ6rPBlyzGCtc8xw,66
8
+ tigrbl_engine_memqueue-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
+ memqueue = tigrbl_engine_memqueue.plugin:register