beaver-db 0.20.1__tar.gz → 0.20.2__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.
- {beaver_db-0.20.1 → beaver_db-0.20.2}/PKG-INFO +42 -4
- {beaver_db-0.20.1 → beaver_db-0.20.2}/README.md +41 -3
- {beaver_db-0.20.1 → beaver_db-0.20.2}/beaver/__init__.py +1 -1
- {beaver_db-0.20.1 → beaver_db-0.20.2}/beaver/blobs.py +60 -1
- beaver_db-0.20.2/beaver/client.py +360 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/beaver/collections.py +43 -1
- {beaver_db-0.20.1 → beaver_db-0.20.2}/beaver/dicts.py +51 -1
- {beaver_db-0.20.1 → beaver_db-0.20.2}/beaver/lists.py +49 -2
- {beaver_db-0.20.1 → beaver_db-0.20.2}/beaver/logs.py +72 -7
- {beaver_db-0.20.1 → beaver_db-0.20.2}/beaver/queues.py +82 -1
- {beaver_db-0.20.1 → beaver_db-0.20.2}/beaver/types.py +16 -2
- beaver_db-0.20.2/docs/_book/BeaverDB--The-Complete-Guide.pdf +0 -0
- beaver_db-0.20.2/docs/_book/dev-architecture.html +676 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/_book/dev-concurrency.html +12 -23
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/_book/dev-contributing.html +12 -23
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/_book/dev-search.html +12 -23
- beaver_db-0.20.2/docs/_book/guide-collections.html +683 -0
- beaver_db-0.20.2/docs/_book/guide-concurrency.html +669 -0
- beaver_db-0.20.2/docs/_book/guide-deployment.html +675 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/_book/guide-dicts-blobs.html +12 -23
- beaver_db-0.20.2/docs/_book/guide-lists-queues.html +671 -0
- beaver_db-0.20.2/docs/_book/guide-realtime.html +671 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/_book/index.html +16 -210
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/_book/quickstart.html +12 -23
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/_book/search.json +72 -80
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/_quarto.yml +4 -6
- beaver_db-0.20.2/issues/13-refactor-adopt-pydantic-deprecate-beavermodel-and-refactor-document-to-be-generic.md +83 -0
- beaver_db-0.20.2/issues/closed/12-add-dump-method-for-json-export-to-all-data-managers.md +195 -0
- {beaver_db-0.20.1/issues → beaver_db-0.20.2/issues/closed}/9-type-safe-wrappers-based-on-pydantic-compatible-models.md +1 -1
- {beaver_db-0.20.1 → beaver_db-0.20.2}/pyproject.toml +1 -1
- beaver_db-0.20.1/docs/_book/dev-architecture.html +0 -870
- beaver_db-0.20.1/docs/_book/guide-collections.html +0 -877
- beaver_db-0.20.1/docs/_book/guide-concurrency.html +0 -863
- beaver_db-0.20.1/docs/_book/guide-deployment.html +0 -869
- beaver_db-0.20.1/docs/_book/guide-lists-queues.html +0 -865
- beaver_db-0.20.1/docs/_book/guide-realtime.html +0 -865
- beaver_db-0.20.1/docs/_book/site_libs/bootstrap/bootstrap-dark-5cc7fd2e95cc32b4ed915ed1d7e2d366.min.css +0 -12
- beaver_db-0.20.1/docs/_book/site_libs/quarto-html/quarto-syntax-highlighting-dark-2b3e328b71be8d25427581baeb23079b.css +0 -219
- {beaver_db-0.20.1 → beaver_db-0.20.2}/.dockerignore +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/.gitignore +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/.python-version +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/LICENSE +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/beaver/channels.py +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/beaver/cli.py +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/beaver/core.py +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/beaver/locks.py +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/beaver/server.py +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/beaver/vectors.py +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/design.md +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/dockerfile +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/.gitignore +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/.quarto/cites/index.json +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/.quarto/idx/dev-architecture.qmd.json +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/.quarto/idx/dev-concurrency.qmd.json +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/.quarto/idx/dev-contributing.qmd.json +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/.quarto/idx/dev-search.qmd.json +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/.quarto/idx/guide-collections.qmd.json +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/.quarto/idx/guide-concurrency.qmd.json +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/.quarto/idx/guide-deployment.qmd.json +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/.quarto/idx/guide-dicts-blobs.qmd.json +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/.quarto/idx/guide-lists-queues.qmd.json +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/.quarto/idx/guide-realtime.qmd.json +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/.quarto/idx/index.qmd.json +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/.quarto/idx/quickstart.qmd.json +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/.quarto/project-cache/08c652dc55400ed7625e092b14557eff +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/.quarto/project-cache/78e7cce51608b23bc20ef50628d80ee9 +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/.quarto/project-cache/deno-kv-file +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/.quarto/project-cache/efb0c620c38ca7c31075f463c90f647b +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/.quarto/xref/1e84074e +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/.quarto/xref/41b07c8e +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/.quarto/xref/4a9bbebe +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/.quarto/xref/57337df9 +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/.quarto/xref/674e8fe6 +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/.quarto/xref/6a721ef9 +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/.quarto/xref/807a23ea +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/.quarto/xref/9c486562 +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/.quarto/xref/INDEX +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/.quarto/xref/b2f009dd +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/.quarto/xref/c40f73a8 +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/.quarto/xref/cf0735bd +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/.quarto/xref/d7f66f00 +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/.quarto/xref/f22e8d4c +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/.quarto/xref/f9d58622 +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/.quarto/xref/fa9f9ca6 +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/_book/logo.png +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/_book/site_libs/bootstrap/bootstrap-5d653dd4d7566aed16f42eb06e547978.min.css +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/_book/site_libs/bootstrap/bootstrap-icons.css +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/_book/site_libs/bootstrap/bootstrap-icons.woff +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/_book/site_libs/bootstrap/bootstrap.min.js +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/_book/site_libs/clipboard/clipboard.min.js +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/_book/site_libs/quarto-html/anchor.min.js +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/_book/site_libs/quarto-html/popper.min.js +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/_book/site_libs/quarto-html/quarto-syntax-highlighting-de070a7b0ab54f8780927367ac907214.css +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/_book/site_libs/quarto-html/quarto.js +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/_book/site_libs/quarto-html/tabsets/tabsets.js +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/_book/site_libs/quarto-html/tippy.css +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/_book/site_libs/quarto-html/tippy.umd.min.js +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/_book/site_libs/quarto-nav/quarto-nav.js +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/_book/site_libs/quarto-search/autocomplete.umd.js +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/_book/site_libs/quarto-search/fuse.min.js +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/_book/site_libs/quarto-search/quarto-search.js +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/cover.png +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/dev-architecture.qmd +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/dev-concurrency.qmd +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/dev-contributing.qmd +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/dev-search.qmd +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/guide-collections.qmd +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/guide-concurrency.qmd +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/guide-deployment.qmd +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/guide-dicts-blobs.qmd +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/guide-lists-queues.qmd +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/guide-realtime.qmd +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/index.qmd +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/logo.png +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/docs/quickstart.qmd +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/examples/async_pubsub.py +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/examples/blobs.py +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/examples/cache.py +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/examples/fts.py +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/examples/fuzzy.py +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/examples/general_test.py +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/examples/graph.py +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/examples/kvstore.py +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/examples/list.py +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/examples/locks.py +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/examples/logs.py +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/examples/pqueue.py +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/examples/producer_consumer.py +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/examples/publisher.py +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/examples/pubsub.py +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/examples/rerank.py +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/examples/stress_vectors.py +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/examples/subscriber.py +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/examples/textual_chat.css +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/examples/textual_chat.py +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/examples/type_hints.py +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/examples/vector.py +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/issues/2-comprehensive-async-wrappers.md +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/issues/6-drop-in-replacement-for-beaver-rest-server-client.md +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/issues/7-replace-faiss-with-simpler-linear-numpy-vectorial-search.md +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/issues/closed/1-refactor-vector-store-to-use-faiss.md +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/issues/closed/10-expose-dblock-functionality-on-all-high-level-data-managers.md +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/issues/closed/11-a-new-issue-for-gabriel.md +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/issues/closed/5-add-dblock-for-inter-process-synchronization.md +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/issues/closed/8-first-class-synchronization-primitive.md +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/logo.png +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/makefile +0 -0
- {beaver_db-0.20.1 → beaver_db-0.20.2}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: beaver-db
|
|
3
|
-
Version: 0.20.
|
|
3
|
+
Version: 0.20.2
|
|
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
|
|
@@ -71,6 +71,7 @@ Description-Content-Type: text/markdown
|
|
|
71
71
|
- **Built-in REST API Server (Optional)**: Instantly serve your database over a RESTful API with automatic OpenAPI documentation using FastAPI.
|
|
72
72
|
- **Full-Featured CLI Client (Optional)**: Interact with your database directly from the command line for administrative tasks and data exploration.
|
|
73
73
|
- **Optional Type-Safety:** Although the database is schemaless, you can use a minimalistic typing system for automatic serialization and deserialization that is Pydantic-compatible out of the box.
|
|
74
|
+
- **Data Export & Backups:** Dump any dictionary, list, collection, queue, blob, or log structure to a portable JSON file with a single `.dump()` command.
|
|
74
75
|
|
|
75
76
|
## How Beaver is Implemented
|
|
76
77
|
|
|
@@ -203,6 +204,43 @@ beaver client --database data.db dict app_config get theme
|
|
|
203
204
|
beaver client --database data.db list daily_tasks push "Review PRs"
|
|
204
205
|
```
|
|
205
206
|
|
|
207
|
+
## Data Export for Backups
|
|
208
|
+
|
|
209
|
+
All data structures (`dict`, `list`, `collection`, `queue`, `log`, and `blobs`) support a `.dump()` method for easy backups and migration. You can either write the data directly to a JSON file or get it as a Python dictionary.
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
import json
|
|
213
|
+
from beaver import BeaverDB
|
|
214
|
+
|
|
215
|
+
db = BeaverDB("my_app.db")
|
|
216
|
+
config = db.dict("app_config")
|
|
217
|
+
|
|
218
|
+
# Add some data
|
|
219
|
+
config["theme"] = "dark"
|
|
220
|
+
config["user_id"] = 456
|
|
221
|
+
|
|
222
|
+
# Dump the dictionary's contents to a JSON file
|
|
223
|
+
with open("config_backup.json", "w") as f:
|
|
224
|
+
config.dump(f)
|
|
225
|
+
|
|
226
|
+
# 'config_backup.json' now contains:
|
|
227
|
+
# {
|
|
228
|
+
# "metadata": {
|
|
229
|
+
# "type": "Dict",
|
|
230
|
+
# "name": "app_config",
|
|
231
|
+
# "count": 2,
|
|
232
|
+
# "dump_date": "2025-11-02T09:05:10.123456Z"
|
|
233
|
+
# },
|
|
234
|
+
# "items": [
|
|
235
|
+
# {"key": "theme", "value": "dark"},
|
|
236
|
+
# {"key": "user_id", "value": 456}
|
|
237
|
+
# ]
|
|
238
|
+
# }
|
|
239
|
+
|
|
240
|
+
# You can also get the dump as a Python object
|
|
241
|
+
dump_data = config.dump()
|
|
242
|
+
```
|
|
243
|
+
|
|
206
244
|
## Things You Can Build with Beaver
|
|
207
245
|
|
|
208
246
|
Here are a few ideas to inspire your next project, showcasing how to combine Beaver's features to build powerful local applications.
|
|
@@ -446,9 +484,9 @@ For more in-depth examples, check out the scripts in the `examples/` directory:
|
|
|
446
484
|
These are some of the features and improvements planned for future releases:
|
|
447
485
|
|
|
448
486
|
- **[Issue #2](https://github.com/syalia-srl/beaver/issues/2) Comprehensive async wrappers**: Extend the async support with on-demand wrappers for all data structures, not just channels.
|
|
449
|
-
- **[Issue #
|
|
450
|
-
- **[Issue #
|
|
451
|
-
- **[Issue #
|
|
487
|
+
- **[Issue #6](https://github.com/syalia-srl/beaver/issues/6) Drop-in replacement for Beaver REST server client**: Implement a `BeaverClient` class that acts as a drop-in replacement for `BeaverDB` but works against the REST API server.
|
|
488
|
+
- **[Issue #7](https://github.com/syalia-srl/beaver/issues/7) Replace `faiss` with simpler, linear `numpy` vectorial search**: Investigate removing the heavy `faiss` dependency in favor of a pure `numpy` implementation to improve installation simplicity, accepting a trade-off in search performance for O(1) installation.
|
|
489
|
+
- **[Issue #9](https://github.com/syalia-srl/beaver/issues/9) Type-safe wrappers based on Pydantic-compatible models**: Enhance the built-in `Model` to handle recursive and embedded types and provide Pydantic compatibility.
|
|
452
490
|
|
|
453
491
|
|
|
454
492
|
If you think of something that would make `beaver` more useful for your use case, please open an issue and/or submit a pull request.
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
- **Built-in REST API Server (Optional)**: Instantly serve your database over a RESTful API with automatic OpenAPI documentation using FastAPI.
|
|
47
47
|
- **Full-Featured CLI Client (Optional)**: Interact with your database directly from the command line for administrative tasks and data exploration.
|
|
48
48
|
- **Optional Type-Safety:** Although the database is schemaless, you can use a minimalistic typing system for automatic serialization and deserialization that is Pydantic-compatible out of the box.
|
|
49
|
+
- **Data Export & Backups:** Dump any dictionary, list, collection, queue, blob, or log structure to a portable JSON file with a single `.dump()` command.
|
|
49
50
|
|
|
50
51
|
## How Beaver is Implemented
|
|
51
52
|
|
|
@@ -178,6 +179,43 @@ beaver client --database data.db dict app_config get theme
|
|
|
178
179
|
beaver client --database data.db list daily_tasks push "Review PRs"
|
|
179
180
|
```
|
|
180
181
|
|
|
182
|
+
## Data Export for Backups
|
|
183
|
+
|
|
184
|
+
All data structures (`dict`, `list`, `collection`, `queue`, `log`, and `blobs`) support a `.dump()` method for easy backups and migration. You can either write the data directly to a JSON file or get it as a Python dictionary.
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
import json
|
|
188
|
+
from beaver import BeaverDB
|
|
189
|
+
|
|
190
|
+
db = BeaverDB("my_app.db")
|
|
191
|
+
config = db.dict("app_config")
|
|
192
|
+
|
|
193
|
+
# Add some data
|
|
194
|
+
config["theme"] = "dark"
|
|
195
|
+
config["user_id"] = 456
|
|
196
|
+
|
|
197
|
+
# Dump the dictionary's contents to a JSON file
|
|
198
|
+
with open("config_backup.json", "w") as f:
|
|
199
|
+
config.dump(f)
|
|
200
|
+
|
|
201
|
+
# 'config_backup.json' now contains:
|
|
202
|
+
# {
|
|
203
|
+
# "metadata": {
|
|
204
|
+
# "type": "Dict",
|
|
205
|
+
# "name": "app_config",
|
|
206
|
+
# "count": 2,
|
|
207
|
+
# "dump_date": "2025-11-02T09:05:10.123456Z"
|
|
208
|
+
# },
|
|
209
|
+
# "items": [
|
|
210
|
+
# {"key": "theme", "value": "dark"},
|
|
211
|
+
# {"key": "user_id", "value": 456}
|
|
212
|
+
# ]
|
|
213
|
+
# }
|
|
214
|
+
|
|
215
|
+
# You can also get the dump as a Python object
|
|
216
|
+
dump_data = config.dump()
|
|
217
|
+
```
|
|
218
|
+
|
|
181
219
|
## Things You Can Build with Beaver
|
|
182
220
|
|
|
183
221
|
Here are a few ideas to inspire your next project, showcasing how to combine Beaver's features to build powerful local applications.
|
|
@@ -421,9 +459,9 @@ For more in-depth examples, check out the scripts in the `examples/` directory:
|
|
|
421
459
|
These are some of the features and improvements planned for future releases:
|
|
422
460
|
|
|
423
461
|
- **[Issue #2](https://github.com/syalia-srl/beaver/issues/2) Comprehensive async wrappers**: Extend the async support with on-demand wrappers for all data structures, not just channels.
|
|
424
|
-
- **[Issue #
|
|
425
|
-
- **[Issue #
|
|
426
|
-
- **[Issue #
|
|
462
|
+
- **[Issue #6](https://github.com/syalia-srl/beaver/issues/6) Drop-in replacement for Beaver REST server client**: Implement a `BeaverClient` class that acts as a drop-in replacement for `BeaverDB` but works against the REST API server.
|
|
463
|
+
- **[Issue #7](https://github.com/syalia-srl/beaver/issues/7) Replace `faiss` with simpler, linear `numpy` vectorial search**: Investigate removing the heavy `faiss` dependency in favor of a pure `numpy` implementation to improve installation simplicity, accepting a trade-off in search performance for O(1) installation.
|
|
464
|
+
- **[Issue #9](https://github.com/syalia-srl/beaver/issues/9) Type-safe wrappers based on Pydantic-compatible models**: Enhance the built-in `Model` to handle recursive and embedded types and provide Pydantic compatibility.
|
|
427
465
|
|
|
428
466
|
|
|
429
467
|
If you think of something that would make `beaver` more useful for your use case, please open an issue and/or submit a pull request.
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
from datetime import datetime, timezone
|
|
1
3
|
import json
|
|
2
4
|
import sqlite3
|
|
3
|
-
from typing import Any, Dict, Iterator, NamedTuple, Optional, Type, TypeVar
|
|
5
|
+
from typing import IO, Any, Dict, Iterator, NamedTuple, Optional, Type, TypeVar
|
|
4
6
|
from .types import JsonSerializable, IDatabase
|
|
5
7
|
from .locks import LockManager
|
|
6
8
|
|
|
@@ -170,3 +172,60 @@ class BlobManager[M]:
|
|
|
170
172
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
171
173
|
"""Releases the lock when exiting a 'with' statement."""
|
|
172
174
|
self.release()
|
|
175
|
+
|
|
176
|
+
def _get_dump_object(self) -> dict:
|
|
177
|
+
"""Builds the JSON-compatible dump object."""
|
|
178
|
+
|
|
179
|
+
items_list = []
|
|
180
|
+
# __iter__ yields keys, so we get each blob
|
|
181
|
+
for key in self:
|
|
182
|
+
blob = self.get(key)
|
|
183
|
+
if blob:
|
|
184
|
+
metadata = blob.metadata
|
|
185
|
+
|
|
186
|
+
# Handle model instances in metadata
|
|
187
|
+
if self._model and isinstance(metadata, JsonSerializable):
|
|
188
|
+
metadata = json.loads(metadata.model_dump_json())
|
|
189
|
+
|
|
190
|
+
# Encode binary data to a base64 string
|
|
191
|
+
data_b64 = base64.b64encode(blob.data).decode('utf-8')
|
|
192
|
+
|
|
193
|
+
items_list.append({
|
|
194
|
+
"key": blob.key,
|
|
195
|
+
"metadata": metadata,
|
|
196
|
+
"data_b64": data_b64
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
metadata = {
|
|
200
|
+
"type": "BlobStore",
|
|
201
|
+
"name": self._name,
|
|
202
|
+
"count": len(items_list),
|
|
203
|
+
"dump_date": datetime.now(timezone.utc).isoformat()
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
"metadata": metadata,
|
|
208
|
+
"items": items_list
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
def dump(self, fp: IO[str] | None = None) -> dict | None:
|
|
212
|
+
"""
|
|
213
|
+
Dumps the entire contents of the blob store to a JSON-compatible
|
|
214
|
+
Python object or a file-like object.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
fp: A file-like object opened in text mode (e.g., with 'w').
|
|
218
|
+
If provided, the JSON dump will be written to this file.
|
|
219
|
+
If None (default), the dump will be returned as a dictionary.
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
A dictionary containing the dump if fp is None.
|
|
223
|
+
None if fp is provided.
|
|
224
|
+
"""
|
|
225
|
+
dump_object = self._get_dump_object()
|
|
226
|
+
|
|
227
|
+
if fp:
|
|
228
|
+
json.dump(dump_object, fp, indent=2)
|
|
229
|
+
return None
|
|
230
|
+
|
|
231
|
+
return dump_object
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module implements the BeaverClient, a drop-in replacement for the
|
|
3
|
+
BeaverDB class that interacts with a remote BeaverDB server over HTTP.
|
|
4
|
+
|
|
5
|
+
This implementation relies on the 'httpx' library.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import threading
|
|
9
|
+
from typing import Generic, Type, TypeVar, Optional, Any
|
|
10
|
+
import httpx
|
|
11
|
+
from .types import JsonSerializable
|
|
12
|
+
from .collections import Document
|
|
13
|
+
|
|
14
|
+
# --- Base Remote Manager ---
|
|
15
|
+
|
|
16
|
+
class RemoteManager:
|
|
17
|
+
"""Base class for all remote managers, holding the HTTP client."""
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
client: httpx.Client,
|
|
21
|
+
name: str,
|
|
22
|
+
model: Type | None = None
|
|
23
|
+
):
|
|
24
|
+
self._client = client
|
|
25
|
+
self._name = name
|
|
26
|
+
self._model = model
|
|
27
|
+
self._validate_model(model)
|
|
28
|
+
|
|
29
|
+
def _validate_model(self, model: Type | None):
|
|
30
|
+
"""Helper to validate the model, mirroring BeaverDB.core"""
|
|
31
|
+
if model and not isinstance(model, JsonSerializable):
|
|
32
|
+
# This check might need to be refined if Pydantic isn't a direct dependency
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
# --- Stub Remote Manager Implementations ---
|
|
36
|
+
|
|
37
|
+
class RemoteDictManager[T](RemoteManager):
|
|
38
|
+
"""
|
|
39
|
+
Manages a remote dictionary. All methods will make HTTP requests.
|
|
40
|
+
(This is a skeleton implementation)
|
|
41
|
+
"""
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
client: httpx.Client,
|
|
45
|
+
name: str,
|
|
46
|
+
model: Type[T] | None = None
|
|
47
|
+
):
|
|
48
|
+
super().__init__(client, name, model)
|
|
49
|
+
# Placeholder for manager-level locking, mirroring local implementation
|
|
50
|
+
self._lock = RemoteLockManager(client, f"__lock__dict__{name}", None)
|
|
51
|
+
|
|
52
|
+
def __setitem__(self, key: str, value: T):
|
|
53
|
+
raise NotImplementedError("This method will be implemented in a future milestone.")
|
|
54
|
+
|
|
55
|
+
def __getitem__(self, key: str) -> T:
|
|
56
|
+
raise NotImplementedError("This method will be implemented in a future milestone.")
|
|
57
|
+
|
|
58
|
+
# ... other dict methods (get, __delitem__, __len__, items, etc.) ...
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class RemoteListManager[T](RemoteManager):
|
|
62
|
+
"""
|
|
63
|
+
Manages a remote list. All methods will make HTTP requests.
|
|
64
|
+
(This is a skeleton implementation)
|
|
65
|
+
"""
|
|
66
|
+
def __init__(
|
|
67
|
+
self,
|
|
68
|
+
client: httpx.Client,
|
|
69
|
+
name: str,
|
|
70
|
+
model: Type[T] | None = None
|
|
71
|
+
):
|
|
72
|
+
super().__init__(client, name, model)
|
|
73
|
+
self._lock = RemoteLockManager(client, f"__lock__list__{name}", None)
|
|
74
|
+
|
|
75
|
+
def push(self, value: T):
|
|
76
|
+
raise NotImplementedError("This method will be implemented in a future milestone.")
|
|
77
|
+
|
|
78
|
+
# ... other list methods (pop, __getitem__, __setitem__, etc.) ...
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class RemoteQueueManager[T](RemoteManager):
|
|
82
|
+
"""
|
|
83
|
+
Manages a remote priority queue. All methods will make HTTP requests.
|
|
84
|
+
(This is a skeleton implementation)
|
|
85
|
+
"""
|
|
86
|
+
def __init__(
|
|
87
|
+
self,
|
|
88
|
+
client: httpx.Client,
|
|
89
|
+
name: str,
|
|
90
|
+
model: Type[T] | None = None
|
|
91
|
+
):
|
|
92
|
+
super().__init__(client, name, model)
|
|
93
|
+
self._lock = RemoteLockManager(client, f"__lock__queue__{name}", None)
|
|
94
|
+
|
|
95
|
+
def put(self, data: T, priority: float):
|
|
96
|
+
raise NotImplementedError("This method will be implemented in a future milestone.")
|
|
97
|
+
|
|
98
|
+
def get(self, block: bool = True, timeout: float | None = None):
|
|
99
|
+
raise NotImplementedError("This method will be implemented in a future milestone.")
|
|
100
|
+
|
|
101
|
+
# ... other queue methods (peek, __len__, etc.) ...
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class RemoteCollectionManager[D](RemoteManager):
|
|
105
|
+
"""
|
|
106
|
+
Manages a remote collection. All methods will make HTTP requests.
|
|
107
|
+
(This is a skeleton implementation)
|
|
108
|
+
"""
|
|
109
|
+
def __init__(
|
|
110
|
+
self,
|
|
111
|
+
client: httpx.Client,
|
|
112
|
+
name: str,
|
|
113
|
+
model: Type[D] | None = None
|
|
114
|
+
):
|
|
115
|
+
super().__init__(client, name, model)
|
|
116
|
+
self._lock = RemoteLockManager(client, f"__lock__collection__{name}", None)
|
|
117
|
+
|
|
118
|
+
def index(self, document: D, *, fts: bool | list[str] = True, fuzzy: bool = False):
|
|
119
|
+
raise NotImplementedError("This method will be implemented in a future milestone.")
|
|
120
|
+
|
|
121
|
+
def search(self, vector: list[float], top_k: int = 10):
|
|
122
|
+
raise NotImplementedError("This method will be implemented in a future milestone.")
|
|
123
|
+
|
|
124
|
+
# ... other collection methods (drop, match, connect, walk, etc.) ...
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class RemoteChannelManager[T](RemoteManager):
|
|
128
|
+
"""
|
|
129
|
+
Manages a remote pub/sub channel.
|
|
130
|
+
(This is a skeleton implementation)
|
|
131
|
+
"""
|
|
132
|
+
def publish(self, payload: T):
|
|
133
|
+
raise NotImplementedError("This method will be implemented in a future milestone.")
|
|
134
|
+
|
|
135
|
+
def subscribe(self):
|
|
136
|
+
raise NotImplementedError("This method will be implemented in a future milestone.")
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class RemoteBlobManager[M](RemoteManager):
|
|
140
|
+
"""
|
|
141
|
+
Manages a remote blob store. All methods will make HTTP requests.
|
|
142
|
+
(This is a skeleton implementation)
|
|
143
|
+
"""
|
|
144
|
+
def __init__(
|
|
145
|
+
self,
|
|
146
|
+
client: httpx.Client,
|
|
147
|
+
name: str,
|
|
148
|
+
model: Type[M] | None = None
|
|
149
|
+
):
|
|
150
|
+
super().__init__(client, name, model)
|
|
151
|
+
self._lock = RemoteLockManager(client, f"__lock__blob__{name}", None)
|
|
152
|
+
|
|
153
|
+
def put(self, key: str, data: bytes, metadata: Optional[M] = None):
|
|
154
|
+
raise NotImplementedError("This method will be implemented in a future milestone.")
|
|
155
|
+
|
|
156
|
+
def get(self, key: str):
|
|
157
|
+
raise NotImplementedError("This method will be implemented in a future milestone.")
|
|
158
|
+
|
|
159
|
+
# ... other blob methods (delete, __len__, etc.) ...
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class RemoteLogManager[T](RemoteManager):
|
|
163
|
+
"""
|
|
164
|
+
Manages a remote time-indexed log.
|
|
165
|
+
(This is a skeleton implementation)
|
|
166
|
+
"""
|
|
167
|
+
def log(self, data: T, timestamp: Any | None = None): # Using Any for datetime
|
|
168
|
+
raise NotImplementedError("This method will be implemented in a future milestone.")
|
|
169
|
+
|
|
170
|
+
def range(self, start: Any, end: Any): # Using Any for datetime
|
|
171
|
+
raise NotImplementedError("This method will be implemented in a future milestone.")
|
|
172
|
+
|
|
173
|
+
def live(self, window: Any, period: Any, aggregator: Any): # Using Any for timedelta/callable
|
|
174
|
+
raise NotImplementedError("This method will be implemented in a future milestone.")
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class RemoteLockManager(RemoteManager):
|
|
178
|
+
"""
|
|
179
|
+
Manages a remote inter-process lock.
|
|
180
|
+
(This is a skeleton implementation)
|
|
181
|
+
"""
|
|
182
|
+
def __init__(
|
|
183
|
+
self,
|
|
184
|
+
client: httpx.Client,
|
|
185
|
+
name: str,
|
|
186
|
+
model: Type | None = None,
|
|
187
|
+
timeout: float | None = None,
|
|
188
|
+
lock_ttl: float = 60.0,
|
|
189
|
+
poll_interval: float = 0.1,
|
|
190
|
+
):
|
|
191
|
+
super().__init__(client, name, model)
|
|
192
|
+
self._timeout = timeout
|
|
193
|
+
self._lock_ttl = lock_ttl
|
|
194
|
+
self._poll_interval = poll_interval
|
|
195
|
+
|
|
196
|
+
def acquire(self):
|
|
197
|
+
raise NotImplementedError("This method will be implemented in a future milestone.")
|
|
198
|
+
|
|
199
|
+
def release(self):
|
|
200
|
+
raise NotImplementedError("This method will be implemented in a future milestone.")
|
|
201
|
+
|
|
202
|
+
def __enter__(self):
|
|
203
|
+
self.acquire()
|
|
204
|
+
return self
|
|
205
|
+
|
|
206
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
207
|
+
self.release()
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
# --- The Main Client Class ---
|
|
211
|
+
|
|
212
|
+
class BeaverClient:
|
|
213
|
+
"""
|
|
214
|
+
A drop-in client for a remote BeaverDB server.
|
|
215
|
+
|
|
216
|
+
This class provides the same factory methods as the local BeaverDB class,
|
|
217
|
+
but all operations are performed over HTTP/WebSockets against a
|
|
218
|
+
server running 'beaver serve'.
|
|
219
|
+
"""
|
|
220
|
+
|
|
221
|
+
def __init__(self, base_url: str, **httpx_args):
|
|
222
|
+
"""
|
|
223
|
+
Initializes the client.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
base_url: The base URL of the BeaverDB server (e.g., "http://127.0.0.1:8000").
|
|
227
|
+
**httpx_args: Additional keyword arguments to pass to the httpx.Client
|
|
228
|
+
(e.g., headers, timeouts).
|
|
229
|
+
"""
|
|
230
|
+
self._client = httpx.Client(base_url=base_url, **httpx_args)
|
|
231
|
+
|
|
232
|
+
# Singleton managers for collections and channels, just like in BeaverDB.core
|
|
233
|
+
self._collections: dict[str, RemoteCollectionManager] = {}
|
|
234
|
+
self._collections_lock = threading.Lock()
|
|
235
|
+
self._channels: dict[str, RemoteChannelManager] = {}
|
|
236
|
+
self._channels_lock = threading.Lock()
|
|
237
|
+
|
|
238
|
+
def close(self):
|
|
239
|
+
"""Closes the underlying HTTP client session."""
|
|
240
|
+
self._client.close()
|
|
241
|
+
|
|
242
|
+
def __enter__(self):
|
|
243
|
+
return self
|
|
244
|
+
|
|
245
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
246
|
+
self.close()
|
|
247
|
+
|
|
248
|
+
def dict[T](self, name: str, model: type[T] | None = None) -> RemoteDictManager[T]:
|
|
249
|
+
"""
|
|
250
|
+
Returns a wrapper for interacting with a remote named dictionary.
|
|
251
|
+
If model is defined, it should be a type used for automatic (de)serialization.
|
|
252
|
+
"""
|
|
253
|
+
if not isinstance(name, str) or not name:
|
|
254
|
+
raise TypeError("Dictionary name must be a non-empty string.")
|
|
255
|
+
|
|
256
|
+
return RemoteDictManager(self._client, name, model)
|
|
257
|
+
|
|
258
|
+
def list[T](self, name: str, model: type[T] | None = None) -> RemoteListManager[T]:
|
|
259
|
+
"""
|
|
260
|
+
Returns a wrapper for interacting with a remote named list.
|
|
261
|
+
If model is defined, it should be a type used for automatic (de)serialization.
|
|
262
|
+
"""
|
|
263
|
+
if not isinstance(name, str) or not name:
|
|
264
|
+
raise TypeError("List name must be a non-empty string.")
|
|
265
|
+
|
|
266
|
+
return RemoteListManager(self._client, name, model)
|
|
267
|
+
|
|
268
|
+
def queue[T](self, name: str, model: type[T] | None = None) -> RemoteQueueManager[T]:
|
|
269
|
+
"""
|
|
270
|
+
Returns a wrapper for interacting with a remote persistent priority queue.
|
|
271
|
+
If model is defined, it should be a type used for automatic (de)serialization.
|
|
272
|
+
"""
|
|
273
|
+
if not isinstance(name, str) or not name:
|
|
274
|
+
raise TypeError("Queue name must be a non-empty string.")
|
|
275
|
+
|
|
276
|
+
return RemoteQueueManager(self._client, name, model)
|
|
277
|
+
|
|
278
|
+
def collection[D: Document](
|
|
279
|
+
self, name: str, model: Type[D] | None = None
|
|
280
|
+
) -> RemoteCollectionManager[D]:
|
|
281
|
+
"""
|
|
282
|
+
Returns a singleton wrapper for a remote document collection.
|
|
283
|
+
"""
|
|
284
|
+
if not isinstance(name, str) or not name:
|
|
285
|
+
raise TypeError("Collection name must be a non-empty string.")
|
|
286
|
+
|
|
287
|
+
with self._collections_lock:
|
|
288
|
+
if name not in self._collections:
|
|
289
|
+
self._collections[name] = RemoteCollectionManager(self._client, name, model)
|
|
290
|
+
return self._collections[name] # type: ignore
|
|
291
|
+
|
|
292
|
+
def channel[T](self, name: str, model: type[T] | None = None) -> RemoteChannelManager[T]:
|
|
293
|
+
"""
|
|
294
|
+
Returns a singleton wrapper for a remote pub/sub channel.
|
|
295
|
+
"""
|
|
296
|
+
if not isinstance(name, str) or not name:
|
|
297
|
+
raise ValueError("Channel name must be a non-empty string.")
|
|
298
|
+
|
|
299
|
+
with self._channels_lock:
|
|
300
|
+
if name not in self._channels:
|
|
301
|
+
self._channels[name] = RemoteChannelManager(self._client, name, model)
|
|
302
|
+
return self._channels[name]
|
|
303
|
+
|
|
304
|
+
def blobs[M](self, name: str, model: type[M] | None = None) -> RemoteBlobManager[M]:
|
|
305
|
+
"""Returns a wrapper for interacting with a remote blob store."""
|
|
306
|
+
if not isinstance(name, str) or not name:
|
|
307
|
+
raise TypeError("Blob store name must be a non-empty string.")
|
|
308
|
+
|
|
309
|
+
return RemoteBlobManager(self._client, name, model)
|
|
310
|
+
|
|
311
|
+
def log[T](self, name: str, model: type[T] | None = None) -> RemoteLogManager[T]:
|
|
312
|
+
"""
|
|
313
|
+
Returns a wrapper for interacting with a remote, time-indexed log.
|
|
314
|
+
If model is defined, it should be a type used for automatic (de)serialization.
|
|
315
|
+
"""
|
|
316
|
+
if not isinstance(name, str) or not name:
|
|
317
|
+
raise TypeError("Log name must be a non-empty string.")
|
|
318
|
+
|
|
319
|
+
return RemoteLogManager(self._client, name, model)
|
|
320
|
+
|
|
321
|
+
def lock(
|
|
322
|
+
self,
|
|
323
|
+
name: str,
|
|
324
|
+
timeout: float | None = None,
|
|
325
|
+
lock_ttl: float = 60.0,
|
|
326
|
+
poll_interval: float = 0.1,
|
|
327
|
+
) -> RemoteLockManager:
|
|
328
|
+
"""
|
|
329
|
+
Returns a wrapper for a remote inter-process lock.
|
|
330
|
+
"""
|
|
331
|
+
return RemoteLockManager(
|
|
332
|
+
self._client,
|
|
333
|
+
name,
|
|
334
|
+
model=None,
|
|
335
|
+
timeout=timeout,
|
|
336
|
+
lock_ttl=lock_ttl,
|
|
337
|
+
poll_interval=poll_interval,
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
# --- Async API ---
|
|
341
|
+
|
|
342
|
+
def as_async(self) -> "AsyncBeaverClient":
|
|
343
|
+
"""
|
|
344
|
+
Returns an async-compatible version of the client.
|
|
345
|
+
(This is a skeleton implementation)
|
|
346
|
+
"""
|
|
347
|
+
raise NotImplementedError("AsyncBeaverClient will be implemented in a future milestone.")
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
class AsyncBeaverClient:
|
|
351
|
+
"""
|
|
352
|
+
An async-compatible, drop-in client for a remote BeaverDB server.
|
|
353
|
+
(This is a skeleton implementation)
|
|
354
|
+
"""
|
|
355
|
+
def __init__(self, base_url: str, **httpx_args):
|
|
356
|
+
self._client = httpx.AsyncClient(base_url=base_url, **httpx_args)
|
|
357
|
+
# ... async-compatible locks and manager caches ...
|
|
358
|
+
|
|
359
|
+
async def close(self):
|
|
360
|
+
await self._client.aclose()
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
from datetime import datetime, timezone
|
|
1
2
|
import json
|
|
2
3
|
import sqlite3
|
|
3
4
|
import threading
|
|
4
5
|
import uuid
|
|
5
6
|
from enum import Enum
|
|
6
|
-
from typing import Any, Iterator, List, Literal, Tuple, Type, TypeVar, Optional
|
|
7
|
+
from typing import IO, Any, Iterator, List, Literal, Tuple, Type, TypeVar, Optional
|
|
7
8
|
from .types import Model, stub, IDatabase
|
|
8
9
|
from .locks import LockManager
|
|
9
10
|
|
|
@@ -679,6 +680,47 @@ class CollectionManager[D: Document]:
|
|
|
679
680
|
"""Releases the lock when exiting a 'with' statement."""
|
|
680
681
|
self.release()
|
|
681
682
|
|
|
683
|
+
def _get_dump_object(self) -> dict:
|
|
684
|
+
"""Builds the JSON-compatible dump object."""
|
|
685
|
+
|
|
686
|
+
# The __iter__ method yields Document instances.
|
|
687
|
+
# doc.to_dict(metadata_only=False) correctly serializes
|
|
688
|
+
# the Document (including embedding) to a plain dictionary.
|
|
689
|
+
items_list = [doc.to_dict(metadata_only=False) for doc in self]
|
|
690
|
+
|
|
691
|
+
metadata = {
|
|
692
|
+
"type": "Collection",
|
|
693
|
+
"name": self._name,
|
|
694
|
+
"count": len(items_list),
|
|
695
|
+
"dump_date": datetime.now(timezone.utc).isoformat()
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
return {
|
|
699
|
+
"metadata": metadata,
|
|
700
|
+
"items": items_list
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
def dump(self, fp: IO[str] | None = None) -> dict | None:
|
|
704
|
+
"""
|
|
705
|
+
Dumps the entire contents of the collection to a JSON-compatible
|
|
706
|
+
Python object or a file-like object.
|
|
707
|
+
|
|
708
|
+
Args:
|
|
709
|
+
fp: A file-like object opened in text mode (e.g., with 'w').
|
|
710
|
+
If provided, the JSON dump will be written to this file.
|
|
711
|
+
If None (default), the dump will be returned as a dictionary.
|
|
712
|
+
|
|
713
|
+
Returns:
|
|
714
|
+
A dictionary containing the dump if fp is None.
|
|
715
|
+
None if fp is provided.
|
|
716
|
+
"""
|
|
717
|
+
dump_object = self._get_dump_object()
|
|
718
|
+
|
|
719
|
+
if fp:
|
|
720
|
+
json.dump(dump_object, fp, indent=2)
|
|
721
|
+
return None
|
|
722
|
+
|
|
723
|
+
return dump_object
|
|
682
724
|
|
|
683
725
|
def rerank[D: Document](
|
|
684
726
|
*results: list[D], weights: list[float] | None = None, k: int = 60
|