beaver-db 0.12.0__tar.gz → 0.13.0__tar.gz

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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: beaver-db
3
- Version: 0.12.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
@@ -9,8 +9,6 @@ Requires-Dist: faiss-cpu>=1.12.0
9
9
  Requires-Dist: numpy>=2.3.3
10
10
  Dynamic: license-file
11
11
 
12
- Of course, here is a rewritten README to explain the vector store uses a high performance FAISS-based implementation with in-memory and persistent indices, with an added small section on how is this implemented to explain the basic ideas behind the implementation of beaver.
13
-
14
12
  # beaver 🦫
15
13
 
16
14
  A fast, single-file, multi-modal database for Python, built with the standard `sqlite3` library.
@@ -221,6 +219,39 @@ attachments.put(
221
219
  avatar = attachments.get("user_123_avatar.png")
222
220
  ```
223
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
+
224
255
  ## More Examples
225
256
 
226
257
  For more in-depth examples, check out the scripts in the `examples/` directory:
@@ -1,5 +1,3 @@
1
- Of course, here is a rewritten README to explain the vector store uses a high performance FAISS-based implementation with in-memory and persistent indices, with an added small section on how is this implemented to explain the basic ideas behind the implementation of beaver.
2
-
3
1
  # beaver 🦫
4
2
 
5
3
  A fast, single-file, multi-modal database for Python, built with the standard `sqlite3` library.
@@ -210,6 +208,39 @@ attachments.put(
210
208
  avatar = attachments.get("user_123_avatar.png")
211
209
  ```
212
210
 
211
+
212
+ ## Type-Safe Data Models
213
+
214
+ 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.
215
+
216
+ This feature is designed to be flexible and works seamlessly with two kinds of models:
217
+
218
+ * **Pydantic Models**: If you're already using Pydantic, your `BaseModel` classes will work out of the box.
219
+ * **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.
220
+
221
+ Here’s a quick example of how to use it:
222
+
223
+ ```python
224
+ from beaver import BeaverDB, Model
225
+
226
+ # Inherit from beaver.Model for a lightweight, dependency-free model
227
+ class User(Model):
228
+ name: str
229
+ email: str
230
+
231
+ db = BeaverDB("user_data.db")
232
+
233
+ # Associate the User model with a dictionary
234
+ users = db.dict("user_profiles", model=User)
235
+
236
+ # BeaverDB now handles serialization automatically
237
+ users["alice"] = User(name="Alice", email="alice@example.com")
238
+
239
+ # The retrieved object is a proper instance of the User class
240
+ retrieved_user = users["alice"]
241
+ print(f"Retrieved: {retrieved_user.name}") # Your editor will provide autocompletion here
242
+ ```
243
+
213
244
  ## More Examples
214
245
 
215
246
  For more in-depth examples, check out the scripts in the `examples/` directory:
@@ -0,0 +1,3 @@
1
+ from .core import BeaverDB
2
+ from .types import Model
3
+ from .collections import Document, WalkDirection
@@ -560,6 +560,17 @@ class CollectionManager:
560
560
  for row in rows
561
561
  ]
562
562
 
563
+ def __len__(self) -> int:
564
+ """Returns the number of documents in the collection."""
565
+ cursor = self._conn.cursor()
566
+ cursor.execute(
567
+ "SELECT COUNT(*) FROM beaver_collections WHERE collection = ?",
568
+ (self._name,),
569
+ )
570
+ count = cursor.fetchone()[0]
571
+ cursor.close()
572
+ return count
573
+
563
574
 
564
575
  def rerank(
565
576
  *results: list[Document],
@@ -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
- """Returns a wrapper object for interacting with a named list."""
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
- """Returns a wrapper object for interacting with a persistent priority queue."""
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
- return QueueManager(name, self._conn)
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
  """
@@ -1,21 +1,38 @@
1
1
  import json
2
2
  import sqlite3
3
- import time # Add this import
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
- class DictManager:
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: Any, ttl_seconds: int | None = None):
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: Any, ttl_seconds: int | None = None):
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, json.dumps(value), expires_at),
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) -> Any:
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
- cursor.execute(
57
- "DELETE FROM beaver_dicts WHERE dict_name = ? AND key = ?",
58
- (self._name, key),
59
- )
60
- self._conn.commit()
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 json.loads(value)
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[Any]:
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 json.loads(row["value"])
137
+ yield self._deserialize(row["value"])
121
138
  cursor.close()
122
139
 
123
- def items(self) -> Iterator[Tuple[str, Any]]:
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"], json.loads(row["value"]))
147
+ yield (row["key"], self._deserialize(row["value"]))
131
148
  cursor.close()
132
149
 
133
150
  def __repr__(self) -> str:
134
- return f"DictWrapper(name='{self._name}')"
151
+ return f"DictManager(name='{self._name}')"
@@ -1,13 +1,29 @@
1
1
  import json
2
2
  import sqlite3
3
- from typing import Any, Union, Iterator
3
+ from typing import Any, Iterator, Type, Union
4
4
 
5
- class ListManager:
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]) -> Any:
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 = [json.loads(row["item_value"]) for row in cursor.fetchall()]
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 json.loads(result["item_value"]) if result else None
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: Any):
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
- (json.dumps(value), rowid_to_update)
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[Any]:
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 json.loads(row['item_value'])
142
+ yield self._deserialize(row['item_value'])
127
143
  cursor.close()
128
144
 
129
- def __contains__(self, value: Any) -> bool:
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, json.dumps(value))
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"ListWrapper(name='{self._name}')"
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: Any):
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, json.dumps(value)),
188
+ (self._name, new_order, self._serialize(value)),
173
189
  )
174
190
 
175
- def prepend(self, value: Any):
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, json.dumps(value)),
204
+ (self._name, new_order, self._serialize(value)),
189
205
  )
190
206
 
191
- def insert(self, index: int, value: Any):
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, json.dumps(value)),
225
+ (self._name, new_order, self._serialize(value)),
210
226
  )
211
227
 
212
- def pop(self) -> Any:
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 json.loads(value_to_return)
244
+ return self._deserialize(value_to_return)
229
245
 
230
- def deque(self) -> Any:
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 json.loads(value_to_return)
262
+ return self._deserialize(value_to_return)
@@ -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
- class QueueItem(NamedTuple):
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: Any
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
- def put(self, data: Any, priority: float):
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(), json.dumps(data)),
50
+ (self._name, priority, time.time(), self._serialize(data)),
34
51
  )
35
52
 
36
- def get(self) -> QueueItem:
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
- raise IndexError("Queue is empty")
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=json.loads(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 __nonzero__(self) -> bool:
103
+ def __bool__(self) -> bool:
83
104
  """Returns True if the queue is not empty."""
84
105
  return len(self) > 0
85
106
 
@@ -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.12.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
@@ -9,8 +9,6 @@ Requires-Dist: faiss-cpu>=1.12.0
9
9
  Requires-Dist: numpy>=2.3.3
10
10
  Dynamic: license-file
11
11
 
12
- Of course, here is a rewritten README to explain the vector store uses a high performance FAISS-based implementation with in-memory and persistent indices, with an added small section on how is this implemented to explain the basic ideas behind the implementation of beaver.
13
-
14
12
  # beaver 🦫
15
13
 
16
14
  A fast, single-file, multi-modal database for Python, built with the standard `sqlite3` library.
@@ -221,6 +219,39 @@ attachments.put(
221
219
  avatar = attachments.get("user_123_avatar.png")
222
220
  ```
223
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
+
224
255
  ## More Examples
225
256
 
226
257
  For more in-depth examples, check out the scripts in the `examples/` directory:
@@ -9,6 +9,7 @@ beaver/core.py
9
9
  beaver/dicts.py
10
10
  beaver/lists.py
11
11
  beaver/queues.py
12
+ beaver/types.py
12
13
  beaver/vectors.py
13
14
  beaver_db.egg-info/PKG-INFO
14
15
  beaver_db.egg-info/SOURCES.txt
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "beaver-db"
3
- version = "0.12.0"
3
+ version = "0.13.0"
4
4
  description = "Fast, embedded, and multi-modal DB based on SQLite for AI-powered applications."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -1,2 +0,0 @@
1
- from .core import BeaverDB
2
- from .collections import Document, WalkDirection
File without changes
File without changes
File without changes
File without changes