beaver-db 0.12.2__py3-none-any.whl → 0.13.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/__init__.py +2 -1
- beaver/core.py +18 -6
- beaver/dicts.py +38 -21
- beaver/lists.py +39 -23
- beaver/queues.py +37 -16
- beaver/types.py +39 -0
- {beaver_db-0.12.2.dist-info → beaver_db-0.13.0.dist-info}/METADATA +34 -1
- beaver_db-0.13.0.dist-info/RECORD +15 -0
- beaver_db-0.12.2.dist-info/RECORD +0 -14
- {beaver_db-0.12.2.dist-info → beaver_db-0.13.0.dist-info}/WHEEL +0 -0
- {beaver_db-0.12.2.dist-info → beaver_db-0.13.0.dist-info}/licenses/LICENSE +0 -0
- {beaver_db-0.12.2.dist-info → beaver_db-0.13.0.dist-info}/top_level.txt +0 -0
beaver/__init__.py
CHANGED
beaver/core.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import sqlite3
|
|
2
2
|
import threading
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
from .types import JsonSerializable
|
|
5
5
|
from .blobs import BlobManager
|
|
6
6
|
from .channels import ChannelManager
|
|
7
7
|
from .collections import CollectionManager
|
|
@@ -274,19 +274,31 @@ class BeaverDB:
|
|
|
274
274
|
|
|
275
275
|
return DictManager(name, self._conn)
|
|
276
276
|
|
|
277
|
-
def list(self, name: str) -> ListManager:
|
|
278
|
-
"""
|
|
277
|
+
def list[T](self, name: str, model: type[T] | None = None) -> ListManager[T]:
|
|
278
|
+
"""
|
|
279
|
+
Returns a wrapper object for interacting with a named list.
|
|
280
|
+
If model is defined, it should be a type used for automatic (de)serialization.
|
|
281
|
+
"""
|
|
279
282
|
if not isinstance(name, str) or not name:
|
|
280
283
|
raise TypeError("List name must be a non-empty string.")
|
|
281
284
|
|
|
285
|
+
if model and not isinstance(model, JsonSerializable):
|
|
286
|
+
raise TypeError("The model parameter must be a JsonSerializable class.")
|
|
287
|
+
|
|
282
288
|
return ListManager(name, self._conn)
|
|
283
289
|
|
|
284
|
-
def queue(self, name: str) -> QueueManager:
|
|
285
|
-
"""
|
|
290
|
+
def queue[T](self, name: str, model: type[T] | None = None) -> QueueManager[T]:
|
|
291
|
+
"""
|
|
292
|
+
Returns a wrapper object for interacting with a persistent priority queue.
|
|
293
|
+
If model is defined, it should be a type used for automatic (de)serialization.
|
|
294
|
+
"""
|
|
286
295
|
if not isinstance(name, str) or not name:
|
|
287
296
|
raise TypeError("Queue name must be a non-empty string.")
|
|
288
297
|
|
|
289
|
-
|
|
298
|
+
if model and not isinstance(model, JsonSerializable):
|
|
299
|
+
raise TypeError("The model parameter must be a JsonSerializable class.")
|
|
300
|
+
|
|
301
|
+
return QueueManager(name, self._conn, model)
|
|
290
302
|
|
|
291
303
|
def collection(self, name: str) -> CollectionManager:
|
|
292
304
|
"""
|
beaver/dicts.py
CHANGED
|
@@ -1,21 +1,38 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import sqlite3
|
|
3
|
-
import time
|
|
4
|
-
from typing import Any, Iterator, Tuple
|
|
3
|
+
import time
|
|
4
|
+
from typing import Any, Iterator, Tuple, Type
|
|
5
5
|
|
|
6
|
+
from .types import JsonSerializable
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
|
|
9
|
+
class DictManager[T]:
|
|
8
10
|
"""A wrapper providing a Pythonic interface to a dictionary in the database."""
|
|
9
11
|
|
|
10
|
-
def __init__(self, name: str, conn: sqlite3.Connection):
|
|
12
|
+
def __init__(self, name: str, conn: sqlite3.Connection, model: Type[T] | None = None):
|
|
11
13
|
self._name = name
|
|
12
14
|
self._conn = conn
|
|
15
|
+
self._model = model
|
|
16
|
+
|
|
17
|
+
def _serialize(self, value: T) -> str:
|
|
18
|
+
"""Serializes the given value to a JSON string."""
|
|
19
|
+
if isinstance(value, JsonSerializable):
|
|
20
|
+
return value.model_dump_json()
|
|
21
|
+
|
|
22
|
+
return json.dumps(value)
|
|
23
|
+
|
|
24
|
+
def _deserialize(self, value: str) -> T:
|
|
25
|
+
"""Deserializes a JSON string into the specified model or a generic object."""
|
|
26
|
+
if self._model:
|
|
27
|
+
return self._model.model_validate_json(value)
|
|
28
|
+
|
|
29
|
+
return json.loads(value)
|
|
13
30
|
|
|
14
|
-
def set(self, key: str, value:
|
|
31
|
+
def set(self, key: str, value: T, ttl_seconds: int | None = None):
|
|
15
32
|
"""Sets a value for a key, with an optional TTL."""
|
|
16
33
|
self.__setitem__(key, value, ttl_seconds=ttl_seconds)
|
|
17
34
|
|
|
18
|
-
def __setitem__(self, key: str, value:
|
|
35
|
+
def __setitem__(self, key: str, value: T, ttl_seconds: int | None = None):
|
|
19
36
|
"""Sets a value for a key (e.g., `my_dict[key] = value`)."""
|
|
20
37
|
expires_at = None
|
|
21
38
|
if ttl_seconds is not None:
|
|
@@ -26,17 +43,17 @@ class DictManager:
|
|
|
26
43
|
with self._conn:
|
|
27
44
|
self._conn.execute(
|
|
28
45
|
"INSERT OR REPLACE INTO beaver_dicts (dict_name, key, value, expires_at) VALUES (?, ?, ?, ?)",
|
|
29
|
-
(self._name, key,
|
|
46
|
+
(self._name, key, self._serialize(value), expires_at),
|
|
30
47
|
)
|
|
31
48
|
|
|
32
|
-
def get(self, key: str, default: Any = None) -> Any:
|
|
49
|
+
def get(self, key: str, default: Any = None) -> T | Any:
|
|
33
50
|
"""Gets a value for a key, with a default if it doesn't exist or is expired."""
|
|
34
51
|
try:
|
|
35
52
|
return self[key]
|
|
36
53
|
except KeyError:
|
|
37
54
|
return default
|
|
38
55
|
|
|
39
|
-
def __getitem__(self, key: str) ->
|
|
56
|
+
def __getitem__(self, key: str) -> T:
|
|
40
57
|
"""Retrieves a value for a given key, raising KeyError if expired."""
|
|
41
58
|
cursor = self._conn.cursor()
|
|
42
59
|
cursor.execute(
|
|
@@ -53,20 +70,20 @@ class DictManager:
|
|
|
53
70
|
|
|
54
71
|
if expires_at is not None and time.time() > expires_at:
|
|
55
72
|
# Expired: delete the key and raise KeyError
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
73
|
+
with self._conn:
|
|
74
|
+
cursor.execute(
|
|
75
|
+
"DELETE FROM beaver_dicts WHERE dict_name = ? AND key = ?",
|
|
76
|
+
(self._name, key),
|
|
77
|
+
)
|
|
61
78
|
cursor.close()
|
|
62
79
|
raise KeyError(
|
|
63
80
|
f"Key '{key}' not found in dictionary '{self._name}' (expired)"
|
|
64
81
|
)
|
|
65
82
|
|
|
66
83
|
cursor.close()
|
|
67
|
-
return
|
|
84
|
+
return self._deserialize(value)
|
|
68
85
|
|
|
69
|
-
def pop(self, key: str, default: Any = None):
|
|
86
|
+
def pop(self, key: str, default: Any = None) -> T | Any:
|
|
70
87
|
"""Deletes an item if it exists and returns its value."""
|
|
71
88
|
try:
|
|
72
89
|
value = self[key]
|
|
@@ -110,25 +127,25 @@ class DictManager:
|
|
|
110
127
|
yield row["key"]
|
|
111
128
|
cursor.close()
|
|
112
129
|
|
|
113
|
-
def values(self) -> Iterator[
|
|
130
|
+
def values(self) -> Iterator[T]:
|
|
114
131
|
"""Returns an iterator over the dictionary's values."""
|
|
115
132
|
cursor = self._conn.cursor()
|
|
116
133
|
cursor.execute(
|
|
117
134
|
"SELECT value FROM beaver_dicts WHERE dict_name = ?", (self._name,)
|
|
118
135
|
)
|
|
119
136
|
for row in cursor:
|
|
120
|
-
yield
|
|
137
|
+
yield self._deserialize(row["value"])
|
|
121
138
|
cursor.close()
|
|
122
139
|
|
|
123
|
-
def items(self) -> Iterator[Tuple[str,
|
|
140
|
+
def items(self) -> Iterator[Tuple[str, T]]:
|
|
124
141
|
"""Returns an iterator over the dictionary's items (key-value pairs)."""
|
|
125
142
|
cursor = self._conn.cursor()
|
|
126
143
|
cursor.execute(
|
|
127
144
|
"SELECT key, value FROM beaver_dicts WHERE dict_name = ?", (self._name,)
|
|
128
145
|
)
|
|
129
146
|
for row in cursor:
|
|
130
|
-
yield (row["key"],
|
|
147
|
+
yield (row["key"], self._deserialize(row["value"]))
|
|
131
148
|
cursor.close()
|
|
132
149
|
|
|
133
150
|
def __repr__(self) -> str:
|
|
134
|
-
return f"
|
|
151
|
+
return f"DictManager(name='{self._name}')"
|
beaver/lists.py
CHANGED
|
@@ -1,13 +1,29 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import sqlite3
|
|
3
|
-
from typing import Any,
|
|
3
|
+
from typing import Any, Iterator, Type, Union
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
from .types import JsonSerializable
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ListManager[T]:
|
|
6
9
|
"""A wrapper providing a Pythonic, full-featured interface to a list in the database."""
|
|
7
10
|
|
|
8
|
-
def __init__(self, name: str, conn: sqlite3.Connection):
|
|
11
|
+
def __init__(self, name: str, conn: sqlite3.Connection, model: Type[T] | None = None):
|
|
9
12
|
self._name = name
|
|
10
13
|
self._conn = conn
|
|
14
|
+
self._model = model
|
|
15
|
+
|
|
16
|
+
def _serialize(self, value: T) -> str:
|
|
17
|
+
"""Serializes the given value to a JSON string."""
|
|
18
|
+
if isinstance(value, JsonSerializable):
|
|
19
|
+
return value.model_dump_json()
|
|
20
|
+
return json.dumps(value)
|
|
21
|
+
|
|
22
|
+
def _deserialize(self, value: str) -> T:
|
|
23
|
+
"""Deserializes a JSON string into the specified model or a generic object."""
|
|
24
|
+
if self._model:
|
|
25
|
+
return self._model.model_validate_json(value)
|
|
26
|
+
return json.loads(value)
|
|
11
27
|
|
|
12
28
|
def __len__(self) -> int:
|
|
13
29
|
"""Returns the number of items in the list (e.g., `len(my_list)`)."""
|
|
@@ -19,7 +35,7 @@ class ListManager:
|
|
|
19
35
|
cursor.close()
|
|
20
36
|
return count
|
|
21
37
|
|
|
22
|
-
def __getitem__(self, key: Union[int, slice]) ->
|
|
38
|
+
def __getitem__(self, key: Union[int, slice]) -> T | list[T]:
|
|
23
39
|
"""
|
|
24
40
|
Retrieves an item or slice from the list (e.g., `my_list[0]`, `my_list[1:3]`).
|
|
25
41
|
"""
|
|
@@ -37,7 +53,7 @@ class ListManager:
|
|
|
37
53
|
"SELECT item_value FROM beaver_lists WHERE list_name = ? ORDER BY item_order ASC LIMIT ? OFFSET ?",
|
|
38
54
|
(self._name, limit, start),
|
|
39
55
|
)
|
|
40
|
-
results = [
|
|
56
|
+
results = [self._deserialize(row["item_value"]) for row in cursor.fetchall()]
|
|
41
57
|
cursor.close()
|
|
42
58
|
return results
|
|
43
59
|
|
|
@@ -55,12 +71,12 @@ class ListManager:
|
|
|
55
71
|
)
|
|
56
72
|
result = cursor.fetchone()
|
|
57
73
|
cursor.close()
|
|
58
|
-
return
|
|
74
|
+
return self._deserialize(result["item_value"])
|
|
59
75
|
|
|
60
76
|
else:
|
|
61
77
|
raise TypeError("List indices must be integers or slices.")
|
|
62
78
|
|
|
63
|
-
def __setitem__(self, key: int, value:
|
|
79
|
+
def __setitem__(self, key: int, value: T):
|
|
64
80
|
"""Sets the value of an item at a specific index (e.g., `my_list[0] = 'new'`)."""
|
|
65
81
|
if not isinstance(key, int):
|
|
66
82
|
raise TypeError("List indices must be integers.")
|
|
@@ -86,7 +102,7 @@ class ListManager:
|
|
|
86
102
|
# Update the value for that specific row
|
|
87
103
|
cursor.execute(
|
|
88
104
|
"UPDATE beaver_lists SET item_value = ? WHERE rowid = ?",
|
|
89
|
-
(
|
|
105
|
+
(self._serialize(value), rowid_to_update)
|
|
90
106
|
)
|
|
91
107
|
|
|
92
108
|
def __delitem__(self, key: int):
|
|
@@ -115,7 +131,7 @@ class ListManager:
|
|
|
115
131
|
# Delete that specific row
|
|
116
132
|
cursor.execute("DELETE FROM beaver_lists WHERE rowid = ?", (rowid_to_delete,))
|
|
117
133
|
|
|
118
|
-
def __iter__(self) -> Iterator[
|
|
134
|
+
def __iter__(self) -> Iterator[T]:
|
|
119
135
|
"""Returns an iterator for the list."""
|
|
120
136
|
cursor = self._conn.cursor()
|
|
121
137
|
cursor.execute(
|
|
@@ -123,15 +139,15 @@ class ListManager:
|
|
|
123
139
|
(self._name,)
|
|
124
140
|
)
|
|
125
141
|
for row in cursor:
|
|
126
|
-
yield
|
|
142
|
+
yield self._deserialize(row['item_value'])
|
|
127
143
|
cursor.close()
|
|
128
144
|
|
|
129
|
-
def __contains__(self, value:
|
|
145
|
+
def __contains__(self, value: T) -> bool:
|
|
130
146
|
"""Checks for the existence of an item in the list (e.g., `'item' in my_list`)."""
|
|
131
147
|
cursor = self._conn.cursor()
|
|
132
148
|
cursor.execute(
|
|
133
149
|
"SELECT 1 FROM beaver_lists WHERE list_name = ? AND item_value = ? LIMIT 1",
|
|
134
|
-
(self._name,
|
|
150
|
+
(self._name, self._serialize(value))
|
|
135
151
|
)
|
|
136
152
|
result = cursor.fetchone()
|
|
137
153
|
cursor.close()
|
|
@@ -139,7 +155,7 @@ class ListManager:
|
|
|
139
155
|
|
|
140
156
|
def __repr__(self) -> str:
|
|
141
157
|
"""Returns a developer-friendly representation of the object."""
|
|
142
|
-
return f"
|
|
158
|
+
return f"ListManager(name='{self._name}')"
|
|
143
159
|
|
|
144
160
|
def _get_order_at_index(self, index: int) -> float:
|
|
145
161
|
"""Helper to get the float `item_order` at a specific index."""
|
|
@@ -156,7 +172,7 @@ class ListManager:
|
|
|
156
172
|
|
|
157
173
|
raise IndexError(f"{index} out of range.")
|
|
158
174
|
|
|
159
|
-
def push(self, value:
|
|
175
|
+
def push(self, value: T):
|
|
160
176
|
"""Pushes an item to the end of the list."""
|
|
161
177
|
with self._conn:
|
|
162
178
|
cursor = self._conn.cursor()
|
|
@@ -169,10 +185,10 @@ class ListManager:
|
|
|
169
185
|
|
|
170
186
|
cursor.execute(
|
|
171
187
|
"INSERT INTO beaver_lists (list_name, item_order, item_value) VALUES (?, ?, ?)",
|
|
172
|
-
(self._name, new_order,
|
|
188
|
+
(self._name, new_order, self._serialize(value)),
|
|
173
189
|
)
|
|
174
190
|
|
|
175
|
-
def prepend(self, value:
|
|
191
|
+
def prepend(self, value: T):
|
|
176
192
|
"""Prepends an item to the beginning of the list."""
|
|
177
193
|
with self._conn:
|
|
178
194
|
cursor = self._conn.cursor()
|
|
@@ -185,10 +201,10 @@ class ListManager:
|
|
|
185
201
|
|
|
186
202
|
cursor.execute(
|
|
187
203
|
"INSERT INTO beaver_lists (list_name, item_order, item_value) VALUES (?, ?, ?)",
|
|
188
|
-
(self._name, new_order,
|
|
204
|
+
(self._name, new_order, self._serialize(value)),
|
|
189
205
|
)
|
|
190
206
|
|
|
191
|
-
def insert(self, index: int, value:
|
|
207
|
+
def insert(self, index: int, value: T):
|
|
192
208
|
"""Inserts an item at a specific index."""
|
|
193
209
|
list_len = len(self)
|
|
194
210
|
if index <= 0:
|
|
@@ -206,10 +222,10 @@ class ListManager:
|
|
|
206
222
|
with self._conn:
|
|
207
223
|
self._conn.execute(
|
|
208
224
|
"INSERT INTO beaver_lists (list_name, item_order, item_value) VALUES (?, ?, ?)",
|
|
209
|
-
(self._name, new_order,
|
|
225
|
+
(self._name, new_order, self._serialize(value)),
|
|
210
226
|
)
|
|
211
227
|
|
|
212
|
-
def pop(self) ->
|
|
228
|
+
def pop(self) -> T | None:
|
|
213
229
|
"""Removes and returns the last item from the list."""
|
|
214
230
|
with self._conn:
|
|
215
231
|
cursor = self._conn.cursor()
|
|
@@ -225,9 +241,9 @@ class ListManager:
|
|
|
225
241
|
cursor.execute(
|
|
226
242
|
"DELETE FROM beaver_lists WHERE rowid = ?", (rowid_to_delete,)
|
|
227
243
|
)
|
|
228
|
-
return
|
|
244
|
+
return self._deserialize(value_to_return)
|
|
229
245
|
|
|
230
|
-
def deque(self) ->
|
|
246
|
+
def deque(self) -> T | None:
|
|
231
247
|
"""Removes and returns the first item from the list."""
|
|
232
248
|
with self._conn:
|
|
233
249
|
cursor = self._conn.cursor()
|
|
@@ -243,4 +259,4 @@ class ListManager:
|
|
|
243
259
|
cursor.execute(
|
|
244
260
|
"DELETE FROM beaver_lists WHERE rowid = ?", (rowid_to_delete,)
|
|
245
261
|
)
|
|
246
|
-
return
|
|
262
|
+
return self._deserialize(value_to_return)
|
beaver/queues.py
CHANGED
|
@@ -1,25 +1,42 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import sqlite3
|
|
3
3
|
import time
|
|
4
|
-
from typing import Any, NamedTuple
|
|
4
|
+
from typing import Any, Literal, NamedTuple, Type, overload
|
|
5
5
|
|
|
6
|
+
from .types import JsonSerializable
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
|
|
9
|
+
class QueueItem[T](NamedTuple):
|
|
8
10
|
"""A data class representing a single item retrieved from the queue."""
|
|
9
11
|
|
|
10
12
|
priority: float
|
|
11
13
|
timestamp: float
|
|
12
|
-
data:
|
|
14
|
+
data: T
|
|
13
15
|
|
|
14
16
|
|
|
15
|
-
class QueueManager:
|
|
17
|
+
class QueueManager[T]:
|
|
16
18
|
"""A wrapper providing a Pythonic interface to a persistent priority queue."""
|
|
17
19
|
|
|
18
|
-
def __init__(self, name: str, conn: sqlite3.Connection):
|
|
20
|
+
def __init__(self, name: str, conn: sqlite3.Connection, model: Type[T] | None = None):
|
|
19
21
|
self._name = name
|
|
20
22
|
self._conn = conn
|
|
23
|
+
self._model = model
|
|
24
|
+
|
|
25
|
+
def _serialize(self, value: T) -> str:
|
|
26
|
+
"""Serializes the given value to a JSON string."""
|
|
27
|
+
if isinstance(value, JsonSerializable):
|
|
28
|
+
return value.model_dump_json()
|
|
29
|
+
|
|
30
|
+
return json.dumps(value)
|
|
31
|
+
|
|
32
|
+
def _deserialize(self, value: str) -> T:
|
|
33
|
+
"""Deserializes a JSON string into the specified model or a generic object."""
|
|
34
|
+
if self._model is not None:
|
|
35
|
+
return self._model.model_validate_json(value) # type: ignore
|
|
21
36
|
|
|
22
|
-
|
|
37
|
+
return json.loads(value)
|
|
38
|
+
|
|
39
|
+
def put(self, data: T, priority: float):
|
|
23
40
|
"""
|
|
24
41
|
Adds an item to the queue with a specific priority.
|
|
25
42
|
|
|
@@ -30,17 +47,18 @@ class QueueManager:
|
|
|
30
47
|
with self._conn:
|
|
31
48
|
self._conn.execute(
|
|
32
49
|
"INSERT INTO beaver_priority_queues (queue_name, priority, timestamp, data) VALUES (?, ?, ?, ?)",
|
|
33
|
-
(self._name, priority, time.time(),
|
|
50
|
+
(self._name, priority, time.time(), self._serialize(data)),
|
|
34
51
|
)
|
|
35
52
|
|
|
36
|
-
|
|
53
|
+
@overload
|
|
54
|
+
def get(self, safe:Literal[True]) -> QueueItem[T] | None: ...
|
|
55
|
+
@overload
|
|
56
|
+
def get(self) -> QueueItem[T]: ...
|
|
57
|
+
|
|
58
|
+
def get(self, safe:bool=False) -> QueueItem[T] | None:
|
|
37
59
|
"""
|
|
38
60
|
Atomically retrieves and removes the highest-priority item from the queue.
|
|
39
|
-
|
|
40
|
-
Returns:
|
|
41
|
-
A QueueItem containing the data and its metadata.
|
|
42
|
-
|
|
43
|
-
Raises IndexError if queue is empty.
|
|
61
|
+
If the queue is empty, returns None if safe is True, otherwise (the default) raises IndexError.
|
|
44
62
|
"""
|
|
45
63
|
with self._conn:
|
|
46
64
|
cursor = self._conn.cursor()
|
|
@@ -58,14 +76,17 @@ class QueueManager:
|
|
|
58
76
|
result = cursor.fetchone()
|
|
59
77
|
|
|
60
78
|
if result is None:
|
|
61
|
-
|
|
79
|
+
if safe:
|
|
80
|
+
return None
|
|
81
|
+
else:
|
|
82
|
+
raise IndexError("No item available.")
|
|
62
83
|
|
|
63
84
|
rowid, priority, timestamp, data = result
|
|
64
85
|
# Delete the retrieved item to ensure it's processed only once.
|
|
65
86
|
cursor.execute("DELETE FROM beaver_priority_queues WHERE rowid = ?", (rowid,))
|
|
66
87
|
|
|
67
88
|
return QueueItem(
|
|
68
|
-
priority=priority, timestamp=timestamp, data=
|
|
89
|
+
priority=priority, timestamp=timestamp, data=self._deserialize(data)
|
|
69
90
|
)
|
|
70
91
|
|
|
71
92
|
def __len__(self) -> int:
|
|
@@ -79,7 +100,7 @@ class QueueManager:
|
|
|
79
100
|
cursor.close()
|
|
80
101
|
return count
|
|
81
102
|
|
|
82
|
-
def
|
|
103
|
+
def __bool__(self) -> bool:
|
|
83
104
|
"""Returns True if the queue is not empty."""
|
|
84
105
|
return len(self) > 0
|
|
85
106
|
|
beaver/types.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Protocol, Type, runtime_checkable, Self
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@runtime_checkable
|
|
6
|
+
class JsonSerializable[T](Protocol):
|
|
7
|
+
"""
|
|
8
|
+
A protocol for objects that can be serialized to and from JSON,
|
|
9
|
+
compatible with Pydantic's `BaseModel`.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def model_dump_json(self) -> str:
|
|
13
|
+
"""Serializes the model to a JSON string."""
|
|
14
|
+
...
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
def model_validate_json(cls: Type[T], json_data: str | bytes) -> T:
|
|
18
|
+
"""Deserializes a JSON string into a model instance."""
|
|
19
|
+
...
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Model:
|
|
23
|
+
"""A lightweight base model that automatically provides JSON serialization."""
|
|
24
|
+
def __init__(self, **kwargs) -> None:
|
|
25
|
+
for k,v in kwargs.items():
|
|
26
|
+
setattr(self, k, v)
|
|
27
|
+
|
|
28
|
+
def model_dump_json(self) -> str:
|
|
29
|
+
"""Serializes the dataclass instance to a JSON string."""
|
|
30
|
+
return json.dumps(self.__dict__)
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def model_validate_json(cls, json_data: str | bytes) -> Self:
|
|
34
|
+
"""Deserializes a JSON string into a new instance of the dataclass."""
|
|
35
|
+
return cls(**json.loads(json_data))
|
|
36
|
+
|
|
37
|
+
def __repr__(self) -> str:
|
|
38
|
+
attrs = ", ".join(f"{k}={repr(v)}" for k,v in self.__dict__.items())
|
|
39
|
+
return f"{self.__class__.__name__}({attrs})"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: beaver-db
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.13.0
|
|
4
4
|
Summary: Fast, embedded, and multi-modal DB based on SQLite for AI-powered applications.
|
|
5
5
|
Requires-Python: >=3.13
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -219,6 +219,39 @@ attachments.put(
|
|
|
219
219
|
avatar = attachments.get("user_123_avatar.png")
|
|
220
220
|
```
|
|
221
221
|
|
|
222
|
+
|
|
223
|
+
## Type-Safe Data Models
|
|
224
|
+
|
|
225
|
+
For enhanced data integrity and a better developer experience, BeaverDB supports type-safe operations for dictionaries, lists, and queues. By associating a model with these data structures, you get automatic serialization and deserialization, complete with autocompletion in your editor.
|
|
226
|
+
|
|
227
|
+
This feature is designed to be flexible and works seamlessly with two kinds of models:
|
|
228
|
+
|
|
229
|
+
* **Pydantic Models**: If you're already using Pydantic, your `BaseModel` classes will work out of the box.
|
|
230
|
+
* **Lightweight `beaver.Model`**: For a zero-dependency solution, you can inherit from the built-in `beaver.Model` class, which is a standard Python class with serialization methods automatically included.
|
|
231
|
+
|
|
232
|
+
Here’s a quick example of how to use it:
|
|
233
|
+
|
|
234
|
+
```python
|
|
235
|
+
from beaver import BeaverDB, Model
|
|
236
|
+
|
|
237
|
+
# Inherit from beaver.Model for a lightweight, dependency-free model
|
|
238
|
+
class User(Model):
|
|
239
|
+
name: str
|
|
240
|
+
email: str
|
|
241
|
+
|
|
242
|
+
db = BeaverDB("user_data.db")
|
|
243
|
+
|
|
244
|
+
# Associate the User model with a dictionary
|
|
245
|
+
users = db.dict("user_profiles", model=User)
|
|
246
|
+
|
|
247
|
+
# BeaverDB now handles serialization automatically
|
|
248
|
+
users["alice"] = User(name="Alice", email="alice@example.com")
|
|
249
|
+
|
|
250
|
+
# The retrieved object is a proper instance of the User class
|
|
251
|
+
retrieved_user = users["alice"]
|
|
252
|
+
print(f"Retrieved: {retrieved_user.name}") # Your editor will provide autocompletion here
|
|
253
|
+
```
|
|
254
|
+
|
|
222
255
|
## More Examples
|
|
223
256
|
|
|
224
257
|
For more in-depth examples, check out the scripts in the `examples/` directory:
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
beaver/__init__.py,sha256=qyEzF1Os7w4b4Hijgz0Y0R4zTrRBrHIGT1mEkZFl2YM,101
|
|
2
|
+
beaver/blobs.py,sha256=5yVDzWyqi6Fur-2r0gaeIjEKj9fUPXb9hPulCTknJJI,3355
|
|
3
|
+
beaver/channels.py,sha256=jKL1sVLOe_Q_pP0q1-iceZbPe8FOi0EwqJtOMOe96f4,8675
|
|
4
|
+
beaver/collections.py,sha256=SZcaZnhcTpKb2OfpZOpFiVxh4-joYAJc6U98UeIhMuU,24247
|
|
5
|
+
beaver/core.py,sha256=xoWqmumhm969UPwzqb40Tl--KSqumMN4tyP_WVfu8GQ,11980
|
|
6
|
+
beaver/dicts.py,sha256=1BQ9A_cMkJ7l5ayWbDG-4Wi3WtQ-9BKd7Wj_CB7dGlU,5410
|
|
7
|
+
beaver/lists.py,sha256=0LT2XjuHs8pDgvW48kk_lfVc-Y-Ulmym0gcVWRESPtA,9708
|
|
8
|
+
beaver/queues.py,sha256=SFu2180ONotnZOcYp1Ld5d8kxzYxaOlgDdcr70ZoBL8,3641
|
|
9
|
+
beaver/types.py,sha256=65rDdj97EegghEkKCNjI67bPYtTTI_jyB-leHdIypx4,1249
|
|
10
|
+
beaver/vectors.py,sha256=j7RL2Y_xMAF2tPTi6E2LdJqZerSQXlnEQJOGZkefTsA,18358
|
|
11
|
+
beaver_db-0.13.0.dist-info/licenses/LICENSE,sha256=1xrIY5JnMk_QDQzsqmVzPIIyCgZAkWCC8kF2Ddo1UT0,1071
|
|
12
|
+
beaver_db-0.13.0.dist-info/METADATA,sha256=zGcAeV9HSyTaAuQO0YYEzB7SfqJeRVjR3tBoX3HPiaY,14928
|
|
13
|
+
beaver_db-0.13.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
14
|
+
beaver_db-0.13.0.dist-info/top_level.txt,sha256=FxA4XnX5Qm5VudEXCduFriqi4dQmDWpQ64d7g69VQKI,7
|
|
15
|
+
beaver_db-0.13.0.dist-info/RECORD,,
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
beaver/__init__.py,sha256=-z5Gj6YKMOswpJOOn5Gej8z5i6k3c0Xs00DIYLA-bMI,75
|
|
2
|
-
beaver/blobs.py,sha256=5yVDzWyqi6Fur-2r0gaeIjEKj9fUPXb9hPulCTknJJI,3355
|
|
3
|
-
beaver/channels.py,sha256=jKL1sVLOe_Q_pP0q1-iceZbPe8FOi0EwqJtOMOe96f4,8675
|
|
4
|
-
beaver/collections.py,sha256=SZcaZnhcTpKb2OfpZOpFiVxh4-joYAJc6U98UeIhMuU,24247
|
|
5
|
-
beaver/core.py,sha256=YAi5VAMsqS0JiydchjKVPbL0A5EL9m5f_gA3n5awnr0,11360
|
|
6
|
-
beaver/dicts.py,sha256=EFlrSr7oFVt0lfuEX-PsdHnJXKC6cTcgbD7Lh2gID8s,4805
|
|
7
|
-
beaver/lists.py,sha256=jFlDWwyaYycG0ZFVm58rMChefUaVZhaP1UeQ-hVo3Sg,9082
|
|
8
|
-
beaver/queues.py,sha256=WKpBzlXr9Hp_rOKEs_Y1Tjyj_hWx6ql1uBRKBV7rw8w,2780
|
|
9
|
-
beaver/vectors.py,sha256=j7RL2Y_xMAF2tPTi6E2LdJqZerSQXlnEQJOGZkefTsA,18358
|
|
10
|
-
beaver_db-0.12.2.dist-info/licenses/LICENSE,sha256=1xrIY5JnMk_QDQzsqmVzPIIyCgZAkWCC8kF2Ddo1UT0,1071
|
|
11
|
-
beaver_db-0.12.2.dist-info/METADATA,sha256=LqnC9Q2YgMtuPuOPOTLBYiTUB5a5u0Dxz3fxF8McmDU,13571
|
|
12
|
-
beaver_db-0.12.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
13
|
-
beaver_db-0.12.2.dist-info/top_level.txt,sha256=FxA4XnX5Qm5VudEXCduFriqi4dQmDWpQ64d7g69VQKI,7
|
|
14
|
-
beaver_db-0.12.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|