beaver-db 0.19.2__tar.gz → 0.19.3__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.

Files changed (57) hide show
  1. {beaver_db-0.19.2 → beaver_db-0.19.3}/PKG-INFO +1 -1
  2. {beaver_db-0.19.2 → beaver_db-0.19.3}/beaver/__init__.py +1 -1
  3. {beaver_db-0.19.2 → beaver_db-0.19.3}/beaver/blobs.py +36 -1
  4. {beaver_db-0.19.2 → beaver_db-0.19.3}/beaver/collections.py +39 -3
  5. {beaver_db-0.19.2 → beaver_db-0.19.3}/beaver/dicts.py +36 -3
  6. {beaver_db-0.19.2 → beaver_db-0.19.3}/beaver/lists.py +37 -3
  7. {beaver_db-0.19.2 → beaver_db-0.19.3}/beaver/locks.py +20 -7
  8. {beaver_db-0.19.2 → beaver_db-0.19.3}/beaver/queues.py +38 -3
  9. beaver_db-0.19.3/issues/10-expose-dblock-functionality-on-all-high-level-data-managers.md +86 -0
  10. beaver_db-0.19.3/issues/closed/11-a-new-issue-for-gabriel.md +8 -0
  11. {beaver_db-0.19.2 → beaver_db-0.19.3}/pyproject.toml +1 -1
  12. {beaver_db-0.19.2 → beaver_db-0.19.3}/.dockerignore +0 -0
  13. {beaver_db-0.19.2 → beaver_db-0.19.3}/.gitignore +0 -0
  14. {beaver_db-0.19.2 → beaver_db-0.19.3}/.python-version +0 -0
  15. {beaver_db-0.19.2 → beaver_db-0.19.3}/LICENSE +0 -0
  16. {beaver_db-0.19.2 → beaver_db-0.19.3}/README.md +0 -0
  17. {beaver_db-0.19.2 → beaver_db-0.19.3}/beaver/channels.py +0 -0
  18. {beaver_db-0.19.2 → beaver_db-0.19.3}/beaver/cli.py +0 -0
  19. {beaver_db-0.19.2 → beaver_db-0.19.3}/beaver/core.py +0 -0
  20. {beaver_db-0.19.2 → beaver_db-0.19.3}/beaver/logs.py +0 -0
  21. {beaver_db-0.19.2 → beaver_db-0.19.3}/beaver/server.py +0 -0
  22. {beaver_db-0.19.2 → beaver_db-0.19.3}/beaver/types.py +0 -0
  23. {beaver_db-0.19.2 → beaver_db-0.19.3}/beaver/vectors.py +0 -0
  24. {beaver_db-0.19.2 → beaver_db-0.19.3}/design.md +0 -0
  25. {beaver_db-0.19.2 → beaver_db-0.19.3}/dockerfile +0 -0
  26. {beaver_db-0.19.2 → beaver_db-0.19.3}/examples/async_pubsub.py +0 -0
  27. {beaver_db-0.19.2 → beaver_db-0.19.3}/examples/blobs.py +0 -0
  28. {beaver_db-0.19.2 → beaver_db-0.19.3}/examples/cache.py +0 -0
  29. {beaver_db-0.19.2 → beaver_db-0.19.3}/examples/fts.py +0 -0
  30. {beaver_db-0.19.2 → beaver_db-0.19.3}/examples/fuzzy.py +0 -0
  31. {beaver_db-0.19.2 → beaver_db-0.19.3}/examples/general_test.py +0 -0
  32. {beaver_db-0.19.2 → beaver_db-0.19.3}/examples/graph.py +0 -0
  33. {beaver_db-0.19.2 → beaver_db-0.19.3}/examples/kvstore.py +0 -0
  34. {beaver_db-0.19.2 → beaver_db-0.19.3}/examples/list.py +0 -0
  35. {beaver_db-0.19.2 → beaver_db-0.19.3}/examples/locks.py +0 -0
  36. {beaver_db-0.19.2 → beaver_db-0.19.3}/examples/logs.py +0 -0
  37. {beaver_db-0.19.2 → beaver_db-0.19.3}/examples/pqueue.py +0 -0
  38. {beaver_db-0.19.2 → beaver_db-0.19.3}/examples/producer_consumer.py +0 -0
  39. {beaver_db-0.19.2 → beaver_db-0.19.3}/examples/publisher.py +0 -0
  40. {beaver_db-0.19.2 → beaver_db-0.19.3}/examples/pubsub.py +0 -0
  41. {beaver_db-0.19.2 → beaver_db-0.19.3}/examples/rerank.py +0 -0
  42. {beaver_db-0.19.2 → beaver_db-0.19.3}/examples/stress_vectors.py +0 -0
  43. {beaver_db-0.19.2 → beaver_db-0.19.3}/examples/subscriber.py +0 -0
  44. {beaver_db-0.19.2 → beaver_db-0.19.3}/examples/textual_chat.css +0 -0
  45. {beaver_db-0.19.2 → beaver_db-0.19.3}/examples/textual_chat.py +0 -0
  46. {beaver_db-0.19.2 → beaver_db-0.19.3}/examples/type_hints.py +0 -0
  47. {beaver_db-0.19.2 → beaver_db-0.19.3}/examples/vector.py +0 -0
  48. {beaver_db-0.19.2 → beaver_db-0.19.3}/issues/2-comprehensive-async-wrappers.md +0 -0
  49. {beaver_db-0.19.2 → beaver_db-0.19.3}/issues/6-drop-in-replacement-for-beaver-rest-server-client.md +0 -0
  50. {beaver_db-0.19.2 → beaver_db-0.19.3}/issues/7-replace-faiss-with-simpler-linear-numpy-vectorial-search.md +0 -0
  51. {beaver_db-0.19.2 → beaver_db-0.19.3}/issues/9-type-safe-wrappers-based-on-pydantic-compatible-models.md +0 -0
  52. {beaver_db-0.19.2 → beaver_db-0.19.3}/issues/closed/1-refactor-vector-store-to-use-faiss.md +0 -0
  53. {beaver_db-0.19.2 → beaver_db-0.19.3}/issues/closed/5-add-dblock-for-inter-process-synchronization.md +0 -0
  54. {beaver_db-0.19.2 → beaver_db-0.19.3}/issues/closed/8-first-class-synchronization-primitive.md +0 -0
  55. {beaver_db-0.19.2 → beaver_db-0.19.3}/logo.png +0 -0
  56. {beaver_db-0.19.2 → beaver_db-0.19.3}/makefile +0 -0
  57. {beaver_db-0.19.2 → beaver_db-0.19.3}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: beaver-db
3
- Version: 0.19.2
3
+ Version: 0.19.3
4
4
  Summary: Fast, embedded, and multi-modal DB based on SQLite for AI-powered applications.
5
5
  License-File: LICENSE
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -2,4 +2,4 @@ from .core import BeaverDB
2
2
  from .types import Model
3
3
  from .collections import Document, WalkDirection
4
4
 
5
- __version__ = "0.19.2"
5
+ __version__ = "0.19.3"
@@ -1,8 +1,8 @@
1
1
  import json
2
2
  import sqlite3
3
3
  from typing import Any, Dict, Iterator, NamedTuple, Optional, Type, TypeVar
4
-
5
4
  from .types import JsonSerializable, IDatabase
5
+ from .locks import LockManager
6
6
 
7
7
 
8
8
  class Blob[M](NamedTuple):
@@ -16,10 +16,13 @@ class Blob[M](NamedTuple):
16
16
  class BlobManager[M]:
17
17
  """A wrapper providing a Pythonic interface to a blob store in the database."""
18
18
 
19
+ # In beaver/blobs.py, inside class BlobManager[M]:
19
20
  def __init__(self, name: str, db: IDatabase, model: Type[M] | None = None):
20
21
  self._name = name
21
22
  self._db = db
22
23
  self._model = model
24
+ lock_name = f"__lock__blob__{name}"
25
+ self._lock = LockManager(db, lock_name)
23
26
 
24
27
  def _serialize(self, value: M) -> str | None:
25
28
  """Serializes the given value to a JSON string."""
@@ -135,3 +138,35 @@ class BlobManager[M]:
135
138
 
136
139
  def __repr__(self) -> str:
137
140
  return f"BlobManager(name='{self._name}')"
141
+
142
+ def acquire(
143
+ self,
144
+ timeout: Optional[float] = None,
145
+ lock_ttl: Optional[float] = None,
146
+ poll_interval: Optional[float] = None,
147
+ ) -> "BlobManager[M]":
148
+ """
149
+ Acquires an inter-process lock on this blob store, blocking until acquired.
150
+
151
+ Parameters override the default settings of the underlying LockManager.
152
+ """
153
+ self._lock.acquire(
154
+ timeout=timeout,
155
+ lock_ttl=lock_ttl,
156
+ poll_interval=poll_interval
157
+ )
158
+ return self
159
+
160
+ def release(self):
161
+ """
162
+ Releases the inter-process lock on this blob store.
163
+ """
164
+ self._lock.release()
165
+
166
+ def __enter__(self) -> "BlobManager[M]":
167
+ """Acquires the lock upon entering a 'with' statement."""
168
+ return self.acquire()
169
+
170
+ def __exit__(self, exc_type, exc_val, exc_tb):
171
+ """Releases the lock when exiting a 'with' statement."""
172
+ self.release()
@@ -3,15 +3,16 @@ import sqlite3
3
3
  import threading
4
4
  import uuid
5
5
  from enum import Enum
6
- from typing import Any, Iterator, List, Literal, Tuple, Type, TypeVar
6
+ from typing import Any, Iterator, List, Literal, Tuple, Type, TypeVar, Optional
7
7
  from .types import Model, stub, IDatabase
8
+ from .locks import LockManager
8
9
 
9
10
  try:
10
11
  import numpy as np
11
12
  from .vectors import VectorIndex
12
13
  except ImportError:
13
- np = stub("This feature requires to install beaver-db[faiss]")()
14
- VectorIndex = stub("This feature requires to install beaver-db[faiss]")
14
+ np = stub("This feature requires to install beaver-db[vector]")()
15
+ VectorIndex = stub("This feature requires to install beaver-db[vector]")
15
16
 
16
17
  # --- Fuzzy Search Helper Functions ---
17
18
 
@@ -135,6 +136,9 @@ class CollectionManager[D: Document]:
135
136
  self._compaction_lock = threading.Lock()
136
137
  self._compaction_thread: threading.Thread | None = None
137
138
 
139
+ lock_name = f"__lock__collection__{name}"
140
+ self._lock = LockManager(db, lock_name)
141
+
138
142
  def _flatten_metadata(self, metadata: dict, prefix: str = "") -> dict[str, Any]:
139
143
  """Flattens a nested dictionary for indexing."""
140
144
  flat_dict = {}
@@ -643,6 +647,38 @@ class CollectionManager[D: Document]:
643
647
  cursor.close()
644
648
  return count
645
649
 
650
+ def acquire(
651
+ self,
652
+ timeout: Optional[float] = None,
653
+ lock_ttl: Optional[float] = None,
654
+ poll_interval: Optional[float] = None,
655
+ ) -> "CollectionManager[D]":
656
+ """
657
+ Acquires an inter-process lock on this collection, blocking until acquired.
658
+ This guarantees exclusive access for multi-step atomic operations
659
+ (e.g., index + connect).
660
+
661
+ Parameters override the default settings of the underlying LockManager.
662
+ """
663
+ self._lock.acquire(
664
+ timeout=timeout, lock_ttl=lock_ttl, poll_interval=poll_interval
665
+ )
666
+ return self
667
+
668
+ def release(self):
669
+ """
670
+ Releases the inter-process lock on this collection.
671
+ """
672
+ self._lock.release()
673
+
674
+ def __enter__(self) -> "CollectionManager[D]":
675
+ """Acquires the lock upon entering a 'with' statement."""
676
+ return self.acquire()
677
+
678
+ def __exit__(self, exc_type, exc_val, exc_tb):
679
+ """Releases the lock when exiting a 'with' statement."""
680
+ self.release()
681
+
646
682
 
647
683
  def rerank[D: Document](
648
684
  *results: list[D], weights: list[float] | None = None, k: int = 60
@@ -1,9 +1,9 @@
1
1
  import json
2
2
  import sqlite3
3
3
  import time
4
- from typing import Any, Iterator, Tuple, Type
5
-
4
+ from typing import Any, Iterator, Tuple, Type, Optional
6
5
  from .types import JsonSerializable, IDatabase
6
+ from .locks import LockManager
7
7
 
8
8
  class DictManager[T]:
9
9
  """A wrapper providing a Pythonic interface to a dictionary in the database."""
@@ -12,6 +12,8 @@ class DictManager[T]:
12
12
  self._name = name
13
13
  self._db = db
14
14
  self._model = model
15
+ lock_name = f"__lock__dict__{name}"
16
+ self._lock = LockManager(db, lock_name)
15
17
 
16
18
  def _serialize(self, value: T) -> str:
17
19
  """Serializes the given value to a JSON string."""
@@ -147,4 +149,35 @@ class DictManager[T]:
147
149
  cursor.close()
148
150
 
149
151
  def __repr__(self) -> str:
150
- return f"DictManager(name='{self._name}')"
152
+ return f"DictManager(name='{self._name}')"
153
+
154
+ def acquire(
155
+ self,
156
+ timeout: Optional[float] = None,
157
+ lock_ttl: Optional[float] = None,
158
+ poll_interval: Optional[float] = None,
159
+ ) -> "DictManager[T]":
160
+ """
161
+ Acquires an inter-process lock on this dictionary, blocking until acquired.
162
+ Parameters override the default settings of the underlying LockManager.
163
+ """
164
+ self._lock.acquire(
165
+ timeout=timeout,
166
+ lock_ttl=lock_ttl,
167
+ poll_interval=poll_interval
168
+ )
169
+ return self
170
+
171
+ def release(self):
172
+ """
173
+ Releases the inter-process lock on this dictionary.
174
+ """
175
+ self._lock.release()
176
+
177
+ def __enter__(self) -> "DictManager[T]":
178
+ """Acquires the lock upon entering a 'with' statement."""
179
+ return self.acquire()
180
+
181
+ def __exit__(self, exc_type, exc_val, exc_tb):
182
+ """Releases the lock when exiting a 'with' statement."""
183
+ self.release()
@@ -1,8 +1,8 @@
1
1
  import json
2
2
  import sqlite3
3
- from typing import Any, Iterator, Type, Union
4
-
3
+ from typing import Any, Iterator, Type, Union, Optional
5
4
  from .types import JsonSerializable, IDatabase
5
+ from .locks import LockManager
6
6
 
7
7
 
8
8
  class ListManager[T]:
@@ -12,6 +12,8 @@ class ListManager[T]:
12
12
  self._name = name
13
13
  self._db = db
14
14
  self._model = model
15
+ lock_name = f"__lock__list__{name}"
16
+ self._lock = LockManager(db, lock_name)
15
17
 
16
18
  def _serialize(self, value: T) -> str:
17
19
  """Serializes the given value to a JSON string."""
@@ -261,4 +263,36 @@ class ListManager[T]:
261
263
  cursor.execute(
262
264
  "DELETE FROM beaver_lists WHERE rowid = ?", (rowid_to_delete,)
263
265
  )
264
- return self._deserialize(value_to_return)
266
+ return self._deserialize(value_to_return)
267
+
268
+ def acquire(
269
+ self,
270
+ timeout: Optional[float] = None,
271
+ lock_ttl: Optional[float] = None,
272
+ poll_interval: Optional[float] = None,
273
+ ) -> "ListManager[T]":
274
+ """
275
+ Acquires an inter-process lock on this list, blocking until acquired.
276
+
277
+ Parameters override the default settings of the underlying LockManager.
278
+ """
279
+ self._lock.acquire(
280
+ timeout=timeout,
281
+ lock_ttl=lock_ttl,
282
+ poll_interval=poll_interval
283
+ )
284
+ return self
285
+
286
+ def release(self):
287
+ """
288
+ Releases the inter-process lock on this list, allowing the next waiting process access.
289
+ """
290
+ self._lock.release()
291
+
292
+ def __enter__(self) -> "ListManager[T]":
293
+ """Acquires the lock upon entering a 'with' statement."""
294
+ return self.acquire()
295
+
296
+ def __exit__(self, exc_type, exc_val, exc_tb):
297
+ """Releases the lock when exiting a 'with' statement."""
298
+ self.release()
@@ -55,7 +55,11 @@ class LockManager:
55
55
  self._waiter_id = f"pid:{os.getpid()}:id:{uuid.uuid4()}"
56
56
  self._acquired = False # State to track if this instance holds the lock
57
57
 
58
- def acquire(self) -> "LockManager":
58
+ def acquire(self,
59
+ timeout: float|None = None,
60
+ lock_ttl: float |None = None,
61
+ poll_interval: float |None = None,
62
+ ) -> "LockManager":
59
63
  """
60
64
  Blocks until the lock is acquired or the timeout expires.
61
65
 
@@ -66,9 +70,18 @@ class LockManager:
66
70
  # This instance already holds the lock
67
71
  return self
68
72
 
73
+ if timeout is None:
74
+ timeout = self._timeout
75
+
76
+ if lock_ttl is None:
77
+ lock_ttl = self._lock_ttl
78
+
79
+ if poll_interval is None:
80
+ poll_interval = self._poll_interval
81
+
69
82
  start_time = time.time()
70
83
  requested_at = time.time()
71
- expires_at = requested_at + self._lock_ttl
84
+ expires_at = requested_at + lock_ttl
72
85
 
73
86
  conn = self._db.connection
74
87
 
@@ -113,19 +126,19 @@ class LockManager:
113
126
  return self
114
127
 
115
128
  # 5. Check for timeout
116
- if self._timeout is not None:
117
- if (time.time() - start_time) > self._timeout:
129
+ if timeout is not None:
130
+ if (time.time() - start_time) > timeout:
118
131
  # We timed out. Remove ourselves from the queue and raise.
119
132
  self._release_from_queue()
120
133
  raise TimeoutError(
121
- f"Failed to acquire lock '{self._lock_name}' within {self._timeout}s."
134
+ f"Failed to acquire lock '{self._lock_name}' within {timeout}s."
122
135
  )
123
136
 
124
137
  # 6. Wait politely before polling again
125
138
  # Add +/- 10% jitter to the poll interval to avoid thundering herd
126
- jitter = self._poll_interval * 0.1
139
+ jitter = poll_interval * 0.1
127
140
  sleep_time = random.uniform(
128
- self._poll_interval - jitter, self._poll_interval + jitter
141
+ poll_interval - jitter, poll_interval + jitter
129
142
  )
130
143
  time.sleep(sleep_time)
131
144
 
@@ -2,10 +2,9 @@ import asyncio
2
2
  import json
3
3
  import sqlite3
4
4
  import time
5
- from typing import Any, Literal, NamedTuple, Type, overload
6
-
5
+ from typing import Any, Literal, NamedTuple, Type, overload, Optional
7
6
  from .types import JsonSerializable, IDatabase
8
-
7
+ from .locks import LockManager
9
8
 
10
9
  class QueueItem[T](NamedTuple):
11
10
  """A data class representing a single item retrieved from the queue."""
@@ -54,6 +53,8 @@ class QueueManager[T]:
54
53
  self._name = name
55
54
  self._db = db
56
55
  self._model = model
56
+ lock_name = f"__lock__queue__{name}"
57
+ self._lock = LockManager(db, lock_name)
57
58
 
58
59
  def _serialize(self, value: T) -> str:
59
60
  """Serializes the given value to a JSON string."""
@@ -184,3 +185,37 @@ class QueueManager[T]:
184
185
 
185
186
  def __repr__(self) -> str:
186
187
  return f"QueueManager(name='{self._name}')"
188
+
189
+ def acquire(
190
+ self,
191
+ timeout: Optional[float] = None,
192
+ lock_ttl: Optional[float] = None,
193
+ poll_interval: Optional[float] = None,
194
+ ) -> "QueueManager[T]":
195
+ """
196
+ Acquires an inter-process lock on this queue, blocking until acquired.
197
+ This ensures that a sequence of operations (e.g., batch-getting tasks)
198
+ is performed atomically without interruption from other processes.
199
+
200
+ Parameters override the default settings of the underlying LockManager.
201
+ """
202
+ self._lock.acquire(
203
+ timeout=timeout,
204
+ lock_ttl=lock_ttl,
205
+ poll_interval=poll_interval
206
+ )
207
+ return self
208
+
209
+ def release(self):
210
+ """
211
+ Releases the inter-process lock on this queue.
212
+ """
213
+ self._lock.release()
214
+
215
+ def __enter__(self) -> "QueueManager[T]":
216
+ """Acquires the lock upon entering a 'with' statement."""
217
+ return self.acquire()
218
+
219
+ def __exit__(self, exc_type, exc_val, exc_tb):
220
+ """Releases the lock when exiting a 'with' statement."""
221
+ self.release()
@@ -0,0 +1,86 @@
1
+ ---
2
+ number: 10
3
+ title: "Expose `db.lock()` functionality on all high-level data managers"
4
+ state: open
5
+ labels:
6
+ ---
7
+
8
+ ### 1. Concept
9
+
10
+ This feature proposes adding explicit, inter-process synchronization to all core state-mutating data managers: `DictManager`, `ListManager`, `CollectionManager`, **`QueueManager`**, and `BlobManager`.
11
+
12
+ This will be achieved by:
13
+
14
+ 1. Making each manager class a **Python context manager** (implementing `__enter__` and `__exit__`).
15
+ 2. Adding public `acquire()` and `release()` methods to each manager.
16
+ 3. Internally, these methods will be thin wrappers around the existing `db.lock()` primitive, using a reserved naming convention (e.g., `__lock__dict__<name>`).
17
+
18
+ This will allow users to perform complex, multi-step operations on a single data structure as a process-safe, atomic unit.
19
+
20
+ ### 2. Use Cases
21
+
22
+ * **Atomic Read-Modify-Write (Dicts/Blobs):** Guarantee that a process can safely read, modify, and write a value back without another process interfering.
23
+ * **Batch Task Processing (Queues):** Allow a worker to **atomically retrieve a batch of multiple tasks** from the queue (e.g., calling `q.get()` five times) without another worker jumping in and stealing tasks in the middle of the batch operation.
24
+ * **List Manipulation (Lists):** Safely perform multi-step modifications like checking list length, popping the last item, and prepending a new item without race conditions.
25
+ * **Consistent State for Collections (Collections):** A process could lock a collection to perform a series of related operations (e.g., `index`, `drop`, and `connect`) and ensure that other processes only see the final, consistent state.
26
+
27
+ ### 3. Proposed API
28
+
29
+ The primary change would be enabling a `with` statement directly on the manager object.
30
+
31
+ ```python
32
+ db = BeaverDB("my.db")
33
+
34
+ # Example for a Dictionary (Atomic Read-Modify-Write)
35
+ try:
36
+ with db.dict("config", timeout=5) as config:
37
+ count = config.get("counter", 0)
38
+ count += 1
39
+ config["counter"] = count
40
+ except TimeoutError:
41
+ print("Could not acquire lock for config.")
42
+
43
+ # Example for a Queue (Atomic Batch Processing)
44
+ try:
45
+ with db.queue("tasks", timeout=10) as q:
46
+ task1 = q.get()
47
+ task2 = q.get()
48
+ print(f"Retrieved two tasks atomically: {task1} and {task2}")
49
+ except IndexError:
50
+ print("Queue did not have two tasks.")
51
+ except TimeoutError:
52
+ print("Could not acquire lock for queue.")
53
+ ```
54
+
55
+ ### 4. Implementation Design (High-Level)
56
+
57
+ **Manager `__init__` Modification:**
58
+
59
+ * The `__init__` method for all targeted managers (`DictManager`, `ListManager`, `CollectionManager`, **`QueueManager`**, and `BlobManager`) will be updated.
60
+ * Each manager will initialize and store an internal `LockManager` instance, which is provided by `db.lock()`.
61
+ * The lock name will use a standardized internal format based on the manager type and the manager's name, e.g., `f"__lock__queue__{self._name}"`.
62
+
63
+ **Lock Methods Implementation:**
64
+
65
+ * `acquire(self, timeout=None, ...)`: Calls `self._lock.acquire(timeout=timeout, ...)` internally.
66
+ * `release(self)`: Calls `self._lock.release()` internally.
67
+ * `__enter__(self)`: Calls `self.acquire()`.
68
+ * `__exit__(self, exc_type, exc_val, exc_tb)`: Calls `self.release()`.
69
+
70
+ **Targeted Manager Classes:**
71
+
72
+ * `DictManager` (`beaver/dicts.py`)
73
+ * `ListManager` (`beaver/lists.py`)
74
+ * `QueueManager` (`beaver/queues.py`)
75
+ * `CollectionManager` (`beaver/collections.py`)
76
+ * `BlobManager` (`beaver/blobs.py`)
77
+
78
+ **Exclusions:** `ChannelManager` and `LogManager` will **not** be modified, as their core write operations are already atomic, and imposing a lock would create an unnecessary performance bottleneck for their high-concurrency design.
79
+
80
+ ### 5. Alignment with Philosophy
81
+
82
+ This feature aligns with `beaver-db`'s core principles:
83
+
84
+ * **Simplicity and Pythonic API:** Provides the most intuitive `with` statement syntax for concurrency control.
85
+ * **Developer Experience:** Solves complex multi-process problems (like batch race conditions) with a simple, high-level primitive.
86
+ * **Synchronous Core:** Builds entirely on the existing, dependency-free `db.lock()` primitive, maintaining the project's architectural integrity.
@@ -0,0 +1,8 @@
1
+ ---
2
+ number: 11
3
+ title: "A new issue for Gabriel"
4
+ state: closed
5
+ labels:
6
+ ---
7
+
8
+ This is a test issue.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "beaver-db"
3
- version = "0.19.2"
3
+ version = "0.19.3"
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"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes