beaver-db 0.16.7__py3-none-any.whl → 0.17.0__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.
Potentially problematic release.
This version of beaver-db might be problematic. Click here for more details.
- beaver/blobs.py +10 -10
- beaver/channels.py +8 -11
- beaver/cli.py +34 -0
- beaver/collections.py +19 -19
- beaver/core.py +58 -36
- beaver/dicts.py +13 -14
- beaver/lists.py +25 -25
- beaver/logs.py +10 -14
- beaver/queues.py +12 -12
- beaver/server.py +132 -0
- beaver/types.py +7 -0
- beaver/vectors.py +32 -30
- {beaver_db-0.16.7.dist-info → beaver_db-0.17.0.dist-info}/METADATA +12 -3
- beaver_db-0.17.0.dist-info/RECORD +18 -0
- beaver_db-0.17.0.dist-info/entry_points.txt +2 -0
- beaver_db-0.16.7.dist-info/RECORD +0 -15
- {beaver_db-0.16.7.dist-info → beaver_db-0.17.0.dist-info}/WHEEL +0 -0
- {beaver_db-0.16.7.dist-info → beaver_db-0.17.0.dist-info}/licenses/LICENSE +0 -0
beaver/dicts.py
CHANGED
|
@@ -3,15 +3,14 @@ import sqlite3
|
|
|
3
3
|
import time
|
|
4
4
|
from typing import Any, Iterator, Tuple, Type
|
|
5
5
|
|
|
6
|
-
from .types import JsonSerializable
|
|
7
|
-
|
|
6
|
+
from .types import JsonSerializable, IDatabase
|
|
8
7
|
|
|
9
8
|
class DictManager[T]:
|
|
10
9
|
"""A wrapper providing a Pythonic interface to a dictionary in the database."""
|
|
11
10
|
|
|
12
|
-
def __init__(self, name: str,
|
|
11
|
+
def __init__(self, name: str, db: IDatabase, model: Type[T] | None = None):
|
|
13
12
|
self._name = name
|
|
14
|
-
self.
|
|
13
|
+
self._db = db
|
|
15
14
|
self._model = model
|
|
16
15
|
|
|
17
16
|
def _serialize(self, value: T) -> str:
|
|
@@ -40,8 +39,8 @@ class DictManager[T]:
|
|
|
40
39
|
raise ValueError("ttl_seconds must be a positive integer.")
|
|
41
40
|
expires_at = time.time() + ttl_seconds
|
|
42
41
|
|
|
43
|
-
with self.
|
|
44
|
-
self.
|
|
42
|
+
with self._db.connection:
|
|
43
|
+
self._db.connection.execute(
|
|
45
44
|
"INSERT OR REPLACE INTO beaver_dicts (dict_name, key, value, expires_at) VALUES (?, ?, ?, ?)",
|
|
46
45
|
(self._name, key, self._serialize(value), expires_at),
|
|
47
46
|
)
|
|
@@ -55,7 +54,7 @@ class DictManager[T]:
|
|
|
55
54
|
|
|
56
55
|
def __getitem__(self, key: str) -> T:
|
|
57
56
|
"""Retrieves a value for a given key, raising KeyError if expired."""
|
|
58
|
-
cursor = self.
|
|
57
|
+
cursor = self._db.connection.cursor()
|
|
59
58
|
cursor.execute(
|
|
60
59
|
"SELECT value, expires_at FROM beaver_dicts WHERE dict_name = ? AND key = ?",
|
|
61
60
|
(self._name, key),
|
|
@@ -70,7 +69,7 @@ class DictManager[T]:
|
|
|
70
69
|
|
|
71
70
|
if expires_at is not None and time.time() > expires_at:
|
|
72
71
|
# Expired: delete the key and raise KeyError
|
|
73
|
-
with self.
|
|
72
|
+
with self._db.connection:
|
|
74
73
|
cursor.execute(
|
|
75
74
|
"DELETE FROM beaver_dicts WHERE dict_name = ? AND key = ?",
|
|
76
75
|
(self._name, key),
|
|
@@ -94,8 +93,8 @@ class DictManager[T]:
|
|
|
94
93
|
|
|
95
94
|
def __delitem__(self, key: str):
|
|
96
95
|
"""Deletes a key-value pair (e.g., `del my_dict[key]`)."""
|
|
97
|
-
with self.
|
|
98
|
-
cursor = self.
|
|
96
|
+
with self._db.connection:
|
|
97
|
+
cursor = self._db.connection.cursor()
|
|
99
98
|
cursor.execute(
|
|
100
99
|
"DELETE FROM beaver_dicts WHERE dict_name = ? AND key = ?",
|
|
101
100
|
(self._name, key),
|
|
@@ -105,7 +104,7 @@ class DictManager[T]:
|
|
|
105
104
|
|
|
106
105
|
def __len__(self) -> int:
|
|
107
106
|
"""Returns the number of items in the dictionary."""
|
|
108
|
-
cursor = self.
|
|
107
|
+
cursor = self._db.connection.cursor()
|
|
109
108
|
cursor.execute(
|
|
110
109
|
"SELECT COUNT(*) FROM beaver_dicts WHERE dict_name = ?", (self._name,)
|
|
111
110
|
)
|
|
@@ -119,7 +118,7 @@ class DictManager[T]:
|
|
|
119
118
|
|
|
120
119
|
def keys(self) -> Iterator[str]:
|
|
121
120
|
"""Returns an iterator over the dictionary's keys."""
|
|
122
|
-
cursor = self.
|
|
121
|
+
cursor = self._db.connection.cursor()
|
|
123
122
|
cursor.execute(
|
|
124
123
|
"SELECT key FROM beaver_dicts WHERE dict_name = ?", (self._name,)
|
|
125
124
|
)
|
|
@@ -129,7 +128,7 @@ class DictManager[T]:
|
|
|
129
128
|
|
|
130
129
|
def values(self) -> Iterator[T]:
|
|
131
130
|
"""Returns an iterator over the dictionary's values."""
|
|
132
|
-
cursor = self.
|
|
131
|
+
cursor = self._db.connection.cursor()
|
|
133
132
|
cursor.execute(
|
|
134
133
|
"SELECT value FROM beaver_dicts WHERE dict_name = ?", (self._name,)
|
|
135
134
|
)
|
|
@@ -139,7 +138,7 @@ class DictManager[T]:
|
|
|
139
138
|
|
|
140
139
|
def items(self) -> Iterator[Tuple[str, T]]:
|
|
141
140
|
"""Returns an iterator over the dictionary's items (key-value pairs)."""
|
|
142
|
-
cursor = self.
|
|
141
|
+
cursor = self._db.connection.cursor()
|
|
143
142
|
cursor.execute(
|
|
144
143
|
"SELECT key, value FROM beaver_dicts WHERE dict_name = ?", (self._name,)
|
|
145
144
|
)
|
beaver/lists.py
CHANGED
|
@@ -2,15 +2,15 @@ import json
|
|
|
2
2
|
import sqlite3
|
|
3
3
|
from typing import Any, Iterator, Type, Union
|
|
4
4
|
|
|
5
|
-
from .types import JsonSerializable
|
|
5
|
+
from .types import JsonSerializable, IDatabase
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class ListManager[T]:
|
|
9
9
|
"""A wrapper providing a Pythonic, full-featured interface to a list in the database."""
|
|
10
10
|
|
|
11
|
-
def __init__(self, name: str,
|
|
11
|
+
def __init__(self, name: str, db: IDatabase, model: Type[T] | None = None):
|
|
12
12
|
self._name = name
|
|
13
|
-
self.
|
|
13
|
+
self._db = db
|
|
14
14
|
self._model = model
|
|
15
15
|
|
|
16
16
|
def _serialize(self, value: T) -> str:
|
|
@@ -27,7 +27,7 @@ class ListManager[T]:
|
|
|
27
27
|
|
|
28
28
|
def __len__(self) -> int:
|
|
29
29
|
"""Returns the number of items in the list (e.g., `len(my_list)`)."""
|
|
30
|
-
cursor = self.
|
|
30
|
+
cursor = self._db.connection.cursor()
|
|
31
31
|
cursor.execute(
|
|
32
32
|
"SELECT COUNT(*) FROM beaver_lists WHERE list_name = ?", (self._name,)
|
|
33
33
|
)
|
|
@@ -40,7 +40,7 @@ class ListManager[T]:
|
|
|
40
40
|
Retrieves an item or slice from the list (e.g., `my_list[0]`, `my_list[1:3]`).
|
|
41
41
|
"""
|
|
42
42
|
if isinstance(key, slice):
|
|
43
|
-
with self.
|
|
43
|
+
with self._db.connection:
|
|
44
44
|
start, stop, step = key.indices(len(self))
|
|
45
45
|
if step != 1:
|
|
46
46
|
raise ValueError("Slicing with a step is not supported.")
|
|
@@ -49,7 +49,7 @@ class ListManager[T]:
|
|
|
49
49
|
if limit <= 0:
|
|
50
50
|
return []
|
|
51
51
|
|
|
52
|
-
cursor = self.
|
|
52
|
+
cursor = self._db.connection.cursor()
|
|
53
53
|
cursor.execute(
|
|
54
54
|
"SELECT item_value FROM beaver_lists WHERE list_name = ? ORDER BY item_order ASC LIMIT ? OFFSET ?",
|
|
55
55
|
(self._name, limit, start),
|
|
@@ -59,14 +59,14 @@ class ListManager[T]:
|
|
|
59
59
|
return results
|
|
60
60
|
|
|
61
61
|
elif isinstance(key, int):
|
|
62
|
-
with self.
|
|
62
|
+
with self._db.connection:
|
|
63
63
|
list_len = len(self)
|
|
64
64
|
if key < -list_len or key >= list_len:
|
|
65
65
|
raise IndexError("List index out of range.")
|
|
66
66
|
|
|
67
67
|
offset = key if key >= 0 else list_len + key
|
|
68
68
|
|
|
69
|
-
cursor = self.
|
|
69
|
+
cursor = self._db.connection.cursor()
|
|
70
70
|
cursor.execute(
|
|
71
71
|
"SELECT item_value FROM beaver_lists WHERE list_name = ? ORDER BY item_order ASC LIMIT 1 OFFSET ?",
|
|
72
72
|
(self._name, offset),
|
|
@@ -83,14 +83,14 @@ class ListManager[T]:
|
|
|
83
83
|
if not isinstance(key, int):
|
|
84
84
|
raise TypeError("List indices must be integers.")
|
|
85
85
|
|
|
86
|
-
with self.
|
|
86
|
+
with self._db.connection:
|
|
87
87
|
list_len = len(self)
|
|
88
88
|
if key < -list_len or key >= list_len:
|
|
89
89
|
raise IndexError("List index out of range.")
|
|
90
90
|
|
|
91
91
|
offset = key if key >= 0 else list_len + key
|
|
92
92
|
|
|
93
|
-
cursor = self.
|
|
93
|
+
cursor = self._db.connection.cursor()
|
|
94
94
|
# Find the rowid of the item to update
|
|
95
95
|
cursor.execute(
|
|
96
96
|
"SELECT rowid FROM beaver_lists WHERE list_name = ? ORDER BY item_order ASC LIMIT 1 OFFSET ?",
|
|
@@ -112,14 +112,14 @@ class ListManager[T]:
|
|
|
112
112
|
if not isinstance(key, int):
|
|
113
113
|
raise TypeError("List indices must be integers.")
|
|
114
114
|
|
|
115
|
-
with self.
|
|
115
|
+
with self._db.connection:
|
|
116
116
|
list_len = len(self)
|
|
117
117
|
if key < -list_len or key >= list_len:
|
|
118
118
|
raise IndexError("List index out of range.")
|
|
119
119
|
|
|
120
120
|
offset = key if key >= 0 else list_len + key
|
|
121
121
|
|
|
122
|
-
cursor = self.
|
|
122
|
+
cursor = self._db.connection.cursor()
|
|
123
123
|
# Find the rowid of the item to delete
|
|
124
124
|
cursor.execute(
|
|
125
125
|
"SELECT rowid FROM beaver_lists WHERE list_name = ? ORDER BY item_order ASC LIMIT 1 OFFSET ?",
|
|
@@ -135,7 +135,7 @@ class ListManager[T]:
|
|
|
135
135
|
|
|
136
136
|
def __iter__(self) -> Iterator[T]:
|
|
137
137
|
"""Returns an iterator for the list."""
|
|
138
|
-
cursor = self.
|
|
138
|
+
cursor = self._db.connection.cursor()
|
|
139
139
|
cursor.execute(
|
|
140
140
|
"SELECT item_value FROM beaver_lists WHERE list_name = ? ORDER BY item_order ASC",
|
|
141
141
|
(self._name,)
|
|
@@ -146,7 +146,7 @@ class ListManager[T]:
|
|
|
146
146
|
|
|
147
147
|
def __contains__(self, value: T) -> bool:
|
|
148
148
|
"""Checks for the existence of an item in the list (e.g., `'item' in my_list`)."""
|
|
149
|
-
cursor = self.
|
|
149
|
+
cursor = self._db.connection.cursor()
|
|
150
150
|
cursor.execute(
|
|
151
151
|
"SELECT 1 FROM beaver_lists WHERE list_name = ? AND item_value = ? LIMIT 1",
|
|
152
152
|
(self._name, self._serialize(value))
|
|
@@ -161,7 +161,7 @@ class ListManager[T]:
|
|
|
161
161
|
|
|
162
162
|
def _get_order_at_index(self, index: int) -> float:
|
|
163
163
|
"""Helper to get the float `item_order` at a specific index."""
|
|
164
|
-
cursor = self.
|
|
164
|
+
cursor = self._db.connection.cursor()
|
|
165
165
|
cursor.execute(
|
|
166
166
|
"SELECT item_order FROM beaver_lists WHERE list_name = ? ORDER BY item_order ASC LIMIT 1 OFFSET ?",
|
|
167
167
|
(self._name, index),
|
|
@@ -176,8 +176,8 @@ class ListManager[T]:
|
|
|
176
176
|
|
|
177
177
|
def push(self, value: T):
|
|
178
178
|
"""Pushes an item to the end of the list."""
|
|
179
|
-
with self.
|
|
180
|
-
cursor = self.
|
|
179
|
+
with self._db.connection:
|
|
180
|
+
cursor = self._db.connection.cursor()
|
|
181
181
|
cursor.execute(
|
|
182
182
|
"SELECT MAX(item_order) FROM beaver_lists WHERE list_name = ?",
|
|
183
183
|
(self._name,),
|
|
@@ -192,8 +192,8 @@ class ListManager[T]:
|
|
|
192
192
|
|
|
193
193
|
def prepend(self, value: T):
|
|
194
194
|
"""Prepends an item to the beginning of the list."""
|
|
195
|
-
with self.
|
|
196
|
-
cursor = self.
|
|
195
|
+
with self._db.connection:
|
|
196
|
+
cursor = self._db.connection.cursor()
|
|
197
197
|
cursor.execute(
|
|
198
198
|
"SELECT MIN(item_order) FROM beaver_lists WHERE list_name = ?",
|
|
199
199
|
(self._name,),
|
|
@@ -208,7 +208,7 @@ class ListManager[T]:
|
|
|
208
208
|
|
|
209
209
|
def insert(self, index: int, value: T):
|
|
210
210
|
"""Inserts an item at a specific index."""
|
|
211
|
-
with self.
|
|
211
|
+
with self._db.connection:
|
|
212
212
|
list_len = len(self)
|
|
213
213
|
if index <= 0:
|
|
214
214
|
self.prepend(value)
|
|
@@ -222,15 +222,15 @@ class ListManager[T]:
|
|
|
222
222
|
order_after = self._get_order_at_index(index)
|
|
223
223
|
new_order = order_before + (order_after - order_before) / 2.0
|
|
224
224
|
|
|
225
|
-
self.
|
|
225
|
+
self._db.connection.execute(
|
|
226
226
|
"INSERT INTO beaver_lists (list_name, item_order, item_value) VALUES (?, ?, ?)",
|
|
227
227
|
(self._name, new_order, self._serialize(value)),
|
|
228
228
|
)
|
|
229
229
|
|
|
230
230
|
def pop(self) -> T | None:
|
|
231
231
|
"""Removes and returns the last item from the list."""
|
|
232
|
-
with self.
|
|
233
|
-
cursor = self.
|
|
232
|
+
with self._db.connection:
|
|
233
|
+
cursor = self._db.connection.cursor()
|
|
234
234
|
cursor.execute(
|
|
235
235
|
"SELECT rowid, item_value FROM beaver_lists WHERE list_name = ? ORDER BY item_order DESC LIMIT 1",
|
|
236
236
|
(self._name,),
|
|
@@ -247,8 +247,8 @@ class ListManager[T]:
|
|
|
247
247
|
|
|
248
248
|
def deque(self) -> T | None:
|
|
249
249
|
"""Removes and returns the first item from the list."""
|
|
250
|
-
with self.
|
|
251
|
-
cursor = self.
|
|
250
|
+
with self._db.connection:
|
|
251
|
+
cursor = self._db.connection.cursor()
|
|
252
252
|
cursor.execute(
|
|
253
253
|
"SELECT rowid, item_value FROM beaver_lists WHERE list_name = ? ORDER BY item_order ASC LIMIT 1",
|
|
254
254
|
(self._name,),
|
beaver/logs.py
CHANGED
|
@@ -8,7 +8,7 @@ from datetime import datetime, timedelta, timezone
|
|
|
8
8
|
from queue import Empty, Queue
|
|
9
9
|
from typing import Any, AsyncIterator, Callable, Iterator, Type, TypeVar
|
|
10
10
|
|
|
11
|
-
from .types import JsonSerializable
|
|
11
|
+
from .types import JsonSerializable, IDatabase
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
# A special message object used to signal the iterator to gracefully shut down.
|
|
@@ -23,14 +23,14 @@ class LiveIterator[T,R]:
|
|
|
23
23
|
|
|
24
24
|
def __init__(
|
|
25
25
|
self,
|
|
26
|
-
|
|
26
|
+
db: IDatabase,
|
|
27
27
|
log_name: str,
|
|
28
28
|
window: timedelta,
|
|
29
29
|
period: timedelta,
|
|
30
30
|
aggregator: Callable[[list[T]], R],
|
|
31
31
|
deserializer: Callable[[str], T],
|
|
32
32
|
):
|
|
33
|
-
self.
|
|
33
|
+
self._db = db
|
|
34
34
|
self._log_name = log_name
|
|
35
35
|
self._window_duration_seconds = window.total_seconds()
|
|
36
36
|
self._sampling_period_seconds = period.total_seconds()
|
|
@@ -43,9 +43,7 @@ class LiveIterator[T,R]:
|
|
|
43
43
|
def _polling_loop(self):
|
|
44
44
|
"""The main loop for the background thread that queries and aggregates data."""
|
|
45
45
|
# Each thread needs its own database connection.
|
|
46
|
-
thread_conn =
|
|
47
|
-
thread_conn.row_factory = sqlite3.Row
|
|
48
|
-
|
|
46
|
+
thread_conn = self._db.connection
|
|
49
47
|
window_deque: collections.deque[tuple[float, T]] = collections.deque()
|
|
50
48
|
last_seen_timestamp = 0.0
|
|
51
49
|
|
|
@@ -179,13 +177,11 @@ class LogManager[T]:
|
|
|
179
177
|
def __init__(
|
|
180
178
|
self,
|
|
181
179
|
name: str,
|
|
182
|
-
|
|
183
|
-
db_path: str,
|
|
180
|
+
db,
|
|
184
181
|
model: Type[T] | None = None,
|
|
185
182
|
):
|
|
186
183
|
self._name = name
|
|
187
|
-
self.
|
|
188
|
-
self._db_path = db_path
|
|
184
|
+
self._db = db
|
|
189
185
|
self._model = model
|
|
190
186
|
|
|
191
187
|
def _serialize(self, value: T) -> str:
|
|
@@ -215,8 +211,8 @@ class LogManager[T]:
|
|
|
215
211
|
ts = timestamp or datetime.now(timezone.utc)
|
|
216
212
|
ts_float = ts.timestamp()
|
|
217
213
|
|
|
218
|
-
with self.
|
|
219
|
-
self.
|
|
214
|
+
with self._db.connection:
|
|
215
|
+
self._db.connection.execute(
|
|
220
216
|
"INSERT INTO beaver_logs (log_name, timestamp, data) VALUES (?, ?, ?)",
|
|
221
217
|
(self._name, ts_float, self._serialize(data)),
|
|
222
218
|
)
|
|
@@ -235,7 +231,7 @@ class LogManager[T]:
|
|
|
235
231
|
start_ts = start.timestamp()
|
|
236
232
|
end_ts = end.timestamp()
|
|
237
233
|
|
|
238
|
-
cursor = self.
|
|
234
|
+
cursor = self._db.connection.cursor()
|
|
239
235
|
cursor.execute(
|
|
240
236
|
"SELECT data FROM beaver_logs WHERE log_name = ? AND timestamp BETWEEN ? AND ? ORDER BY timestamp ASC",
|
|
241
237
|
(self._name, start_ts, end_ts),
|
|
@@ -265,7 +261,7 @@ class LogManager[T]:
|
|
|
265
261
|
An iterator that yields the results of the aggregator.
|
|
266
262
|
"""
|
|
267
263
|
return LiveIterator(
|
|
268
|
-
|
|
264
|
+
db=self._db,
|
|
269
265
|
log_name=self._name,
|
|
270
266
|
window=window,
|
|
271
267
|
period=period,
|
beaver/queues.py
CHANGED
|
@@ -4,7 +4,7 @@ import sqlite3
|
|
|
4
4
|
import time
|
|
5
5
|
from typing import Any, Literal, NamedTuple, Type, overload
|
|
6
6
|
|
|
7
|
-
from .types import JsonSerializable
|
|
7
|
+
from .types import JsonSerializable, IDatabase
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class QueueItem[T](NamedTuple):
|
|
@@ -50,9 +50,9 @@ class QueueManager[T]:
|
|
|
50
50
|
producer-consumer priority queue.
|
|
51
51
|
"""
|
|
52
52
|
|
|
53
|
-
def __init__(self, name: str,
|
|
53
|
+
def __init__(self, name: str, db: IDatabase, model: Type[T] | None = None):
|
|
54
54
|
self._name = name
|
|
55
|
-
self.
|
|
55
|
+
self._db = db
|
|
56
56
|
self._model = model
|
|
57
57
|
|
|
58
58
|
def _serialize(self, value: T) -> str:
|
|
@@ -77,8 +77,8 @@ class QueueManager[T]:
|
|
|
77
77
|
data: The JSON-serializable data to store.
|
|
78
78
|
priority: The priority of the item (lower numbers are higher priority).
|
|
79
79
|
"""
|
|
80
|
-
with self.
|
|
81
|
-
self.
|
|
80
|
+
with self._db.connection:
|
|
81
|
+
self._db.connection.execute(
|
|
82
82
|
"INSERT INTO beaver_priority_queues (queue_name, priority, timestamp, data) VALUES (?, ?, ?, ?)",
|
|
83
83
|
(self._name, priority, time.time(), self._serialize(data)),
|
|
84
84
|
)
|
|
@@ -88,8 +88,8 @@ class QueueManager[T]:
|
|
|
88
88
|
Performs a single, atomic attempt to retrieve and remove the
|
|
89
89
|
highest-priority item from the queue. Returns None if the queue is empty.
|
|
90
90
|
"""
|
|
91
|
-
with self.
|
|
92
|
-
cursor = self.
|
|
91
|
+
with self._db.connection:
|
|
92
|
+
cursor = self._db.connection.cursor()
|
|
93
93
|
cursor.execute(
|
|
94
94
|
"""
|
|
95
95
|
SELECT rowid, priority, timestamp, data
|
|
@@ -108,11 +108,11 @@ class QueueManager[T]:
|
|
|
108
108
|
rowid, priority, timestamp, data = result
|
|
109
109
|
|
|
110
110
|
if pop:
|
|
111
|
-
|
|
111
|
+
self._db.connection.execute("DELETE FROM beaver_priority_queues WHERE rowid = ?", (rowid,))
|
|
112
112
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
return QueueItem(
|
|
114
|
+
priority=priority, timestamp=timestamp, data=self._deserialize(data)
|
|
115
|
+
)
|
|
116
116
|
|
|
117
117
|
def peek(self) -> QueueItem[T] | None:
|
|
118
118
|
"""
|
|
@@ -169,7 +169,7 @@ class QueueManager[T]:
|
|
|
169
169
|
|
|
170
170
|
def __len__(self) -> int:
|
|
171
171
|
"""Returns the current number of items in the queue."""
|
|
172
|
-
cursor = self.
|
|
172
|
+
cursor = self._db.connection.cursor()
|
|
173
173
|
cursor.execute(
|
|
174
174
|
"SELECT COUNT(*) FROM beaver_priority_queues WHERE queue_name = ?",
|
|
175
175
|
(self._name,),
|
beaver/server.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
try:
|
|
2
|
+
from fastapi import FastAPI, HTTPException, Body
|
|
3
|
+
import uvicorn
|
|
4
|
+
except ImportError:
|
|
5
|
+
raise ImportError(
|
|
6
|
+
"FastAPI and Uvicorn are required to serve the database. "
|
|
7
|
+
'Please install them with `pip install "beaver-db[server]"`'
|
|
8
|
+
)
|
|
9
|
+
from typing import Any
|
|
10
|
+
from .core import BeaverDB
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def build(db: BeaverDB) -> FastAPI:
|
|
14
|
+
"""
|
|
15
|
+
Constructs a FastAPI application instance for a given BeaverDB instance.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
db: An active BeaverDB instance.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
A FastAPI application with all endpoints configured.
|
|
22
|
+
"""
|
|
23
|
+
app = FastAPI(
|
|
24
|
+
title="BeaverDB",
|
|
25
|
+
description="A RESTful API for a BeaverDB instance.",
|
|
26
|
+
version="0.1.0",
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# --- Dicts Endpoints ---
|
|
30
|
+
|
|
31
|
+
@app.get("/dicts/{name}")
|
|
32
|
+
def get_all_dict_items(name: str) -> dict:
|
|
33
|
+
"""Retrieves all key-value pairs in a dictionary."""
|
|
34
|
+
d = db.dict(name)
|
|
35
|
+
return {k: v for k, v in d.items()}
|
|
36
|
+
|
|
37
|
+
@app.get("/dicts/{name}/{key}")
|
|
38
|
+
def get_dict_item(name: str, key: str) -> Any:
|
|
39
|
+
"""Retrieves the value for a specific key."""
|
|
40
|
+
d = db.dict(name)
|
|
41
|
+
try:
|
|
42
|
+
return d[key]
|
|
43
|
+
except KeyError:
|
|
44
|
+
raise HTTPException(status_code=404, detail=f"Key '{key}' not found in dictionary '{name}'")
|
|
45
|
+
|
|
46
|
+
@app.post("/dicts/{name}/{key}")
|
|
47
|
+
def set_dict_item(name: str, key: str, value: Any = Body(...)):
|
|
48
|
+
"""Sets the value for a specific key."""
|
|
49
|
+
d = db.dict(name)
|
|
50
|
+
d[key] = value
|
|
51
|
+
return {"status": "ok"}
|
|
52
|
+
|
|
53
|
+
@app.delete("/dicts/{name}/{key}")
|
|
54
|
+
def delete_dict_item(name: str, key: str):
|
|
55
|
+
"""Deletes a key-value pair."""
|
|
56
|
+
d = db.dict(name)
|
|
57
|
+
try:
|
|
58
|
+
del d[key]
|
|
59
|
+
return {"status": "ok"}
|
|
60
|
+
except KeyError:
|
|
61
|
+
raise HTTPException(status_code=404, detail=f"Key '{key}' not found in dictionary '{name}'")
|
|
62
|
+
|
|
63
|
+
# --- Lists Endpoints ---
|
|
64
|
+
|
|
65
|
+
@app.get("/lists/{name}")
|
|
66
|
+
def get_list(name: str) -> list:
|
|
67
|
+
"""Retrieves all items in the list."""
|
|
68
|
+
l = db.list(name)
|
|
69
|
+
return l[:]
|
|
70
|
+
|
|
71
|
+
@app.get("/lists/{name}/{index}")
|
|
72
|
+
def get_list_item(name: str, index: int) -> Any:
|
|
73
|
+
"""Retrieves the item at a specific index."""
|
|
74
|
+
l = db.list(name)
|
|
75
|
+
try:
|
|
76
|
+
return l[index]
|
|
77
|
+
except IndexError:
|
|
78
|
+
raise HTTPException(status_code=404, detail=f"Index {index} out of bounds for list '{name}'")
|
|
79
|
+
|
|
80
|
+
@app.post("/lists/{name}")
|
|
81
|
+
def push_list_item(name: str, value: Any = Body(...)):
|
|
82
|
+
"""Adds an item to the end of the list."""
|
|
83
|
+
l = db.list(name)
|
|
84
|
+
l.push(value)
|
|
85
|
+
return {"status": "ok"}
|
|
86
|
+
|
|
87
|
+
@app.put("/lists/{name}/{index}")
|
|
88
|
+
def update_list_item(name: str, index: int, value: Any = Body(...)):
|
|
89
|
+
"""Updates the item at a specific index."""
|
|
90
|
+
l = db.list(name)
|
|
91
|
+
try:
|
|
92
|
+
l[index] = value
|
|
93
|
+
return {"status": "ok"}
|
|
94
|
+
except IndexError:
|
|
95
|
+
raise HTTPException(status_code=404, detail=f"Index {index} out of bounds for list '{name}'")
|
|
96
|
+
|
|
97
|
+
@app.delete("/lists/{name}/{index}")
|
|
98
|
+
def delete_list_item(name: str, index: int):
|
|
99
|
+
"""Deletes the item at a specific index."""
|
|
100
|
+
l = db.list(name)
|
|
101
|
+
try:
|
|
102
|
+
del l[index]
|
|
103
|
+
return {"status": "ok"}
|
|
104
|
+
except IndexError:
|
|
105
|
+
raise HTTPException(status_code=404, detail=f"Index {index} out of bounds for list '{name}'")
|
|
106
|
+
|
|
107
|
+
# TODO: Add endpoints for all BeaverDB modalities
|
|
108
|
+
# - Queues
|
|
109
|
+
# - Collections
|
|
110
|
+
# - Channels
|
|
111
|
+
# - Logs
|
|
112
|
+
# - Blobs
|
|
113
|
+
|
|
114
|
+
@app.get("/")
|
|
115
|
+
def read_root():
|
|
116
|
+
return {"message": "Welcome to the BeaverDB API"}
|
|
117
|
+
|
|
118
|
+
return app
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def serve(db_path: str, host: str, port: int):
|
|
122
|
+
"""
|
|
123
|
+
Initializes a BeaverDB instance and runs a Uvicorn server for it.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
db_path: The path to the SQLite database file.
|
|
127
|
+
host: The host to bind the server to.
|
|
128
|
+
port: The port to run the server on.
|
|
129
|
+
"""
|
|
130
|
+
db = BeaverDB(db_path)
|
|
131
|
+
app = build(db)
|
|
132
|
+
uvicorn.run(app, host=host, port=port)
|
beaver/types.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import json
|
|
2
|
+
import sqlite3
|
|
2
3
|
from typing import Protocol, Type, runtime_checkable, Self
|
|
3
4
|
|
|
4
5
|
|
|
@@ -49,3 +50,9 @@ def stub(msg: str):
|
|
|
49
50
|
raise TypeError(msg)
|
|
50
51
|
|
|
51
52
|
return Stub()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class IDatabase(Protocol):
|
|
56
|
+
@property
|
|
57
|
+
def connection(self) -> sqlite3.Connection:
|
|
58
|
+
...
|