beaver-db 0.17.4__tar.gz → 0.17.6__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.17.4 → beaver_db-0.17.6}/PKG-INFO +86 -28
- {beaver_db-0.17.4 → beaver_db-0.17.6}/README.md +85 -27
- beaver_db-0.17.6/beaver/server.py +339 -0
- {beaver_db-0.17.4 → beaver_db-0.17.6}/pyproject.toml +1 -1
- beaver_db-0.17.4/beaver/server.py +0 -132
- {beaver_db-0.17.4 → beaver_db-0.17.6}/.gitignore +0 -0
- {beaver_db-0.17.4 → beaver_db-0.17.6}/.python-version +0 -0
- {beaver_db-0.17.4 → beaver_db-0.17.6}/LICENSE +0 -0
- {beaver_db-0.17.4 → beaver_db-0.17.6}/beaver/__init__.py +0 -0
- {beaver_db-0.17.4 → beaver_db-0.17.6}/beaver/blobs.py +0 -0
- {beaver_db-0.17.4 → beaver_db-0.17.6}/beaver/channels.py +0 -0
- {beaver_db-0.17.4 → beaver_db-0.17.6}/beaver/cli.py +0 -0
- {beaver_db-0.17.4 → beaver_db-0.17.6}/beaver/collections.py +0 -0
- {beaver_db-0.17.4 → beaver_db-0.17.6}/beaver/core.py +0 -0
- {beaver_db-0.17.4 → beaver_db-0.17.6}/beaver/dicts.py +0 -0
- {beaver_db-0.17.4 → beaver_db-0.17.6}/beaver/lists.py +0 -0
- {beaver_db-0.17.4 → beaver_db-0.17.6}/beaver/logs.py +0 -0
- {beaver_db-0.17.4 → beaver_db-0.17.6}/beaver/queues.py +0 -0
- {beaver_db-0.17.4 → beaver_db-0.17.6}/beaver/types.py +0 -0
- {beaver_db-0.17.4 → beaver_db-0.17.6}/beaver/vectors.py +0 -0
- {beaver_db-0.17.4 → beaver_db-0.17.6}/design.md +0 -0
- {beaver_db-0.17.4 → beaver_db-0.17.6}/examples/async_pubsub.py +0 -0
- {beaver_db-0.17.4 → beaver_db-0.17.6}/examples/blobs.py +0 -0
- {beaver_db-0.17.4 → beaver_db-0.17.6}/examples/cache.py +0 -0
- {beaver_db-0.17.4 → beaver_db-0.17.6}/examples/fts.py +0 -0
- {beaver_db-0.17.4 → beaver_db-0.17.6}/examples/fuzzy.py +0 -0
- {beaver_db-0.17.4 → beaver_db-0.17.6}/examples/general_test.py +0 -0
- {beaver_db-0.17.4 → beaver_db-0.17.6}/examples/graph.py +0 -0
- {beaver_db-0.17.4 → beaver_db-0.17.6}/examples/kvstore.py +0 -0
- {beaver_db-0.17.4 → beaver_db-0.17.6}/examples/list.py +0 -0
- {beaver_db-0.17.4 → beaver_db-0.17.6}/examples/logs.py +0 -0
- {beaver_db-0.17.4 → beaver_db-0.17.6}/examples/pqueue.py +0 -0
- {beaver_db-0.17.4 → beaver_db-0.17.6}/examples/producer_consumer.py +0 -0
- {beaver_db-0.17.4 → beaver_db-0.17.6}/examples/publisher.py +0 -0
- {beaver_db-0.17.4 → beaver_db-0.17.6}/examples/pubsub.py +0 -0
- {beaver_db-0.17.4 → beaver_db-0.17.6}/examples/rerank.py +0 -0
- {beaver_db-0.17.4 → beaver_db-0.17.6}/examples/stress_vectors.py +0 -0
- {beaver_db-0.17.4 → beaver_db-0.17.6}/examples/subscriber.py +0 -0
- {beaver_db-0.17.4 → beaver_db-0.17.6}/examples/textual_chat.css +0 -0
- {beaver_db-0.17.4 → beaver_db-0.17.6}/examples/textual_chat.py +0 -0
- {beaver_db-0.17.4 → beaver_db-0.17.6}/examples/type_hints.py +0 -0
- {beaver_db-0.17.4 → beaver_db-0.17.6}/examples/vector.py +0 -0
- {beaver_db-0.17.4 → beaver_db-0.17.6}/makefile +0 -0
- {beaver_db-0.17.4 → beaver_db-0.17.6}/roadmap.md +0 -0
- {beaver_db-0.17.4 → beaver_db-0.17.6}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: beaver-db
|
|
3
|
-
Version: 0.17.
|
|
3
|
+
Version: 0.17.6
|
|
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
|
|
@@ -42,7 +42,7 @@ A fast, single-file, multi-modal database for Python, built with the standard `s
|
|
|
42
42
|
|
|
43
43
|
`beaver` is built with a minimalistic philosophy for small, local use cases where a full-blown database server would be overkill.
|
|
44
44
|
|
|
45
|
-
- **Minimalistic**: The core library has zero external dependencies. Vector search
|
|
45
|
+
- **Minimalistic**: The core library has zero external dependencies. Vector search, the REST server, and the CLI, which require external libraries, are available as optional features.
|
|
46
46
|
- **Schemaless**: Flexible data storage without rigid schemas across all modalities.
|
|
47
47
|
- **Synchronous, Multi-Process, and Thread-Safe**: Designed for simplicity and safety in multi-threaded and multi-process environments.
|
|
48
48
|
- **Built for Local Applications**: Perfect for local AI tools, RAG prototypes, chatbots, and desktop utilities that need persistent, structured data without network overhead.
|
|
@@ -61,6 +61,8 @@ A fast, single-file, multi-modal database for Python, built with the standard `s
|
|
|
61
61
|
- **Full-Text and Fuzzy Search**: Automatically index and search through document metadata using SQLite's powerful FTS5 engine, enhanced with optional fuzzy search for typo-tolerant matching.
|
|
62
62
|
- **Knowledge Graph**: Create relationships between documents and traverse the graph to find neighbors or perform multi-hop walks.
|
|
63
63
|
- **Single-File & Portable**: All data is stored in a single SQLite file, making it incredibly easy to move, back up, or embed in your application.
|
|
64
|
+
- **Built-in REST API Server (Optional)**: Instantly serve your database over a RESTful API with automatic OpenAPI documentation using FastAPI.
|
|
65
|
+
- **Full-Featured CLI Client (Optional)**: Interact with your database directly from the command line for administrative tasks and data exploration.
|
|
64
66
|
- **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.
|
|
65
67
|
|
|
66
68
|
## How Beaver is Implemented
|
|
@@ -79,7 +81,6 @@ The vector store in BeaverDB is designed for high performance and reliability, u
|
|
|
79
81
|
|
|
80
82
|
This hybrid approach allows BeaverDB to provide a vector search experience that is both fast and durable, without sacrificing the single-file, embedded philosophy of the library.
|
|
81
83
|
|
|
82
|
-
|
|
83
84
|
## Installation
|
|
84
85
|
|
|
85
86
|
Install the core, dependency-free library:
|
|
@@ -88,10 +89,17 @@ Install the core, dependency-free library:
|
|
|
88
89
|
pip install beaver-db
|
|
89
90
|
```
|
|
90
91
|
|
|
91
|
-
|
|
92
|
+
To include optional features, you can install them as extras:
|
|
92
93
|
|
|
93
94
|
```bash
|
|
95
|
+
# For vector search capabilities
|
|
94
96
|
pip install "beaver-db[faiss]"
|
|
97
|
+
|
|
98
|
+
# For the REST API server and CLI
|
|
99
|
+
pip install "beaver-db[server,cli]"
|
|
100
|
+
|
|
101
|
+
# To install all optional features at once
|
|
102
|
+
pip install "beaver-db[full]"
|
|
95
103
|
```
|
|
96
104
|
|
|
97
105
|
## Quickstart
|
|
@@ -131,6 +139,53 @@ print(f"FTS Result: '{top_doc.content}'")
|
|
|
131
139
|
db.close()
|
|
132
140
|
```
|
|
133
141
|
|
|
142
|
+
## Built-in Server and CLI
|
|
143
|
+
|
|
144
|
+
Beaver comes with a built-in REST API server and a full-featured command-line client, allowing you to interact with your database without writing any code.
|
|
145
|
+
|
|
146
|
+
### REST API Server
|
|
147
|
+
|
|
148
|
+
You can instantly expose all of your database's functionality over a RESTful API. This is perfect for building quick prototypes, microservices, or for interacting with your data from other languages.
|
|
149
|
+
|
|
150
|
+
**1. Start the server**
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
# Start the server for your database file
|
|
154
|
+
beaver serve --database data.db --port 8000
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
This starts a `FastAPI` server. You can now access the interactive API documentation at `http://127.0.0.1:8000/docs`.
|
|
158
|
+
|
|
159
|
+
**2. Interact with the API**
|
|
160
|
+
|
|
161
|
+
Here are a couple of examples using `curl`:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
# Set a value in the 'app_config' dictionary
|
|
165
|
+
curl -X PUT http://127.0.0.1:8000/dicts/app_config/api_key
|
|
166
|
+
-H "Content-Type: application/json"
|
|
167
|
+
-d '"your-secret-api-key"'
|
|
168
|
+
|
|
169
|
+
# Get the value back
|
|
170
|
+
curl http://127.0.0.1:8000/dicts/app_config/api_key
|
|
171
|
+
# Output: "your-secret-api-key"
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Command-Line Client
|
|
175
|
+
|
|
176
|
+
The CLI client allows you to call any BeaverDB method directly from your terminal.
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
# Set a value in a dictionary
|
|
180
|
+
beaver client --database data.db dict app_config set theme light
|
|
181
|
+
|
|
182
|
+
# Get the value back
|
|
183
|
+
beaver client --database data.db dict app_config get theme
|
|
184
|
+
|
|
185
|
+
# Push an item to a list
|
|
186
|
+
beaver client --database data.db list daily_tasks push "Review PRs"
|
|
187
|
+
```
|
|
188
|
+
|
|
134
189
|
## Things You Can Build with Beaver
|
|
135
190
|
|
|
136
191
|
Here are a few ideas to inspire your next project, showcasing how to combine Beaver's features to build powerful local applications.
|
|
@@ -282,8 +337,10 @@ For enhanced data integrity and a better developer experience, BeaverDB supports
|
|
|
282
337
|
|
|
283
338
|
This feature is designed to be flexible and works seamlessly with two kinds of models:
|
|
284
339
|
|
|
285
|
-
|
|
286
|
-
|
|
340
|
+
- **Pydantic Models**: If you're already using Pydantic, your `BaseModel` classes will work out of the box.
|
|
341
|
+
|
|
342
|
+
- **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.
|
|
343
|
+
|
|
287
344
|
|
|
288
345
|
Here’s a quick example of how to use it:
|
|
289
346
|
|
|
@@ -305,7 +362,8 @@ users["alice"] = User(name="Alice", email="alice@example.com")
|
|
|
305
362
|
|
|
306
363
|
# The retrieved object is a proper instance of the User class
|
|
307
364
|
retrieved_user = users["alice"]
|
|
308
|
-
|
|
365
|
+
# Your editor will provide autocompletion here
|
|
366
|
+
print(f"Retrieved: {retrieved_user.name}")
|
|
309
367
|
```
|
|
310
368
|
|
|
311
369
|
In the same way you can have typed message payloads in `db.channel`, typed metadata in `db.blobs`, and custom document types in `db.collection`, as well as custom types in lists and queues.
|
|
@@ -316,25 +374,25 @@ Basically everywhere you can store or get some object in BeaverDB, you can use a
|
|
|
316
374
|
|
|
317
375
|
For more in-depth examples, check out the scripts in the `examples/` directory:
|
|
318
376
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
377
|
+
- [`async_pubsub.py`](examples/async_pubsub.py): A demonstration of the asynchronous wrapper for the publish/subscribe system.
|
|
378
|
+
- [`blobs.py`](examples/blobs.py): Demonstrates how to store and retrieve binary data in the database.
|
|
379
|
+
- [`cache.py`](examples/cache.py): A practical example of using a dictionary with TTL as a cache for API calls.
|
|
380
|
+
- [`fts.py`](examples/fts.py): A detailed look at full-text search, including targeted searches on specific metadata fields.
|
|
381
|
+
- [`fuzzy.py`](examples/fuzzy.py): Demonstrates fuzzy search capabilities for text search.
|
|
382
|
+
- [`general_test.py`](examples/general_test.py): A general-purpose test to run all operations randomly which allows testing long-running processes and synchronicity issues.
|
|
383
|
+
- [`graph.py`](examples/graph.py): Shows how to create relationships between documents and perform multi-hop graph traversals.
|
|
384
|
+
- [`kvstore.py`](examples/kvstore.py): A comprehensive demo of the namespaced dictionary feature.
|
|
385
|
+
- [`list.py`](examples/list.py): Shows the full capabilities of the persistent list, including slicing and in-place updates.
|
|
386
|
+
- [`logs.py`](examples/logs.py): A short example showing how to build a realtime dashboard with the logging feature.
|
|
387
|
+
- [`pqueue.py`](examples/pqueue.py): A practical example of using the persistent priority queue for task management.
|
|
388
|
+
- [`producer_consumer.py`](examples/producer_consumer.py): A demonstration of the distributed task queue system in a multi-process environment.
|
|
389
|
+
- [`publisher.py`](examples/publisher.p) and [`subscriber.py`](examples/subscriber.py): A pair of examples demonstrating inter-process message passing with the publish/subscribe system.
|
|
390
|
+
- [`pubsub.py`](examples/pubsub.py): A demonstration of the synchronous, thread-safe publish/subscribe system in a single process.
|
|
391
|
+
- [`rerank.py`](examples/rerank.py): Shows how to combine results from vector and text search for more refined results.
|
|
392
|
+
- [`stress_vectors.py`](examples/stress_vectors.py): A stress test for the vector search functionality.
|
|
393
|
+
- [`textual_chat.py`](examples/textual_chat.py): A chat application built with `textual` and `beaver` to illustrate the use of several primitives (lists, dicts, and channels) at the same time.
|
|
394
|
+
- [`type_hints.py`](examples/type_hints.py): Shows how to use type hints with `beaver` to get better IDE support and type safety.
|
|
395
|
+
- [`vector.py`](examples/vector.py): Demonstrates how to index and search vector embeddings, including upserts.
|
|
338
396
|
|
|
339
397
|
## Roadmap
|
|
340
398
|
|
|
@@ -342,7 +400,7 @@ For more in-depth examples, check out the scripts in the `examples/` directory:
|
|
|
342
400
|
|
|
343
401
|
These are some of the features and improvements planned for future releases:
|
|
344
402
|
|
|
345
|
-
|
|
403
|
+
- **Async API**: Extend the async support with on-demand wrappers for all features besides channels.
|
|
346
404
|
|
|
347
405
|
Check out the [roadmap](roadmap.md) for a detailed list of upcoming features and design ideas.
|
|
348
406
|
|
|
@@ -350,4 +408,4 @@ If you think of something that would make `beaver` more useful for your use case
|
|
|
350
408
|
|
|
351
409
|
## License
|
|
352
410
|
|
|
353
|
-
This project is licensed under the MIT License.
|
|
411
|
+
This project is licensed under the MIT License.
|
|
@@ -17,7 +17,7 @@ A fast, single-file, multi-modal database for Python, built with the standard `s
|
|
|
17
17
|
|
|
18
18
|
`beaver` is built with a minimalistic philosophy for small, local use cases where a full-blown database server would be overkill.
|
|
19
19
|
|
|
20
|
-
- **Minimalistic**: The core library has zero external dependencies. Vector search
|
|
20
|
+
- **Minimalistic**: The core library has zero external dependencies. Vector search, the REST server, and the CLI, which require external libraries, are available as optional features.
|
|
21
21
|
- **Schemaless**: Flexible data storage without rigid schemas across all modalities.
|
|
22
22
|
- **Synchronous, Multi-Process, and Thread-Safe**: Designed for simplicity and safety in multi-threaded and multi-process environments.
|
|
23
23
|
- **Built for Local Applications**: Perfect for local AI tools, RAG prototypes, chatbots, and desktop utilities that need persistent, structured data without network overhead.
|
|
@@ -36,6 +36,8 @@ A fast, single-file, multi-modal database for Python, built with the standard `s
|
|
|
36
36
|
- **Full-Text and Fuzzy Search**: Automatically index and search through document metadata using SQLite's powerful FTS5 engine, enhanced with optional fuzzy search for typo-tolerant matching.
|
|
37
37
|
- **Knowledge Graph**: Create relationships between documents and traverse the graph to find neighbors or perform multi-hop walks.
|
|
38
38
|
- **Single-File & Portable**: All data is stored in a single SQLite file, making it incredibly easy to move, back up, or embed in your application.
|
|
39
|
+
- **Built-in REST API Server (Optional)**: Instantly serve your database over a RESTful API with automatic OpenAPI documentation using FastAPI.
|
|
40
|
+
- **Full-Featured CLI Client (Optional)**: Interact with your database directly from the command line for administrative tasks and data exploration.
|
|
39
41
|
- **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.
|
|
40
42
|
|
|
41
43
|
## How Beaver is Implemented
|
|
@@ -54,7 +56,6 @@ The vector store in BeaverDB is designed for high performance and reliability, u
|
|
|
54
56
|
|
|
55
57
|
This hybrid approach allows BeaverDB to provide a vector search experience that is both fast and durable, without sacrificing the single-file, embedded philosophy of the library.
|
|
56
58
|
|
|
57
|
-
|
|
58
59
|
## Installation
|
|
59
60
|
|
|
60
61
|
Install the core, dependency-free library:
|
|
@@ -63,10 +64,17 @@ Install the core, dependency-free library:
|
|
|
63
64
|
pip install beaver-db
|
|
64
65
|
```
|
|
65
66
|
|
|
66
|
-
|
|
67
|
+
To include optional features, you can install them as extras:
|
|
67
68
|
|
|
68
69
|
```bash
|
|
70
|
+
# For vector search capabilities
|
|
69
71
|
pip install "beaver-db[faiss]"
|
|
72
|
+
|
|
73
|
+
# For the REST API server and CLI
|
|
74
|
+
pip install "beaver-db[server,cli]"
|
|
75
|
+
|
|
76
|
+
# To install all optional features at once
|
|
77
|
+
pip install "beaver-db[full]"
|
|
70
78
|
```
|
|
71
79
|
|
|
72
80
|
## Quickstart
|
|
@@ -106,6 +114,53 @@ print(f"FTS Result: '{top_doc.content}'")
|
|
|
106
114
|
db.close()
|
|
107
115
|
```
|
|
108
116
|
|
|
117
|
+
## Built-in Server and CLI
|
|
118
|
+
|
|
119
|
+
Beaver comes with a built-in REST API server and a full-featured command-line client, allowing you to interact with your database without writing any code.
|
|
120
|
+
|
|
121
|
+
### REST API Server
|
|
122
|
+
|
|
123
|
+
You can instantly expose all of your database's functionality over a RESTful API. This is perfect for building quick prototypes, microservices, or for interacting with your data from other languages.
|
|
124
|
+
|
|
125
|
+
**1. Start the server**
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
# Start the server for your database file
|
|
129
|
+
beaver serve --database data.db --port 8000
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
This starts a `FastAPI` server. You can now access the interactive API documentation at `http://127.0.0.1:8000/docs`.
|
|
133
|
+
|
|
134
|
+
**2. Interact with the API**
|
|
135
|
+
|
|
136
|
+
Here are a couple of examples using `curl`:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
# Set a value in the 'app_config' dictionary
|
|
140
|
+
curl -X PUT http://127.0.0.1:8000/dicts/app_config/api_key
|
|
141
|
+
-H "Content-Type: application/json"
|
|
142
|
+
-d '"your-secret-api-key"'
|
|
143
|
+
|
|
144
|
+
# Get the value back
|
|
145
|
+
curl http://127.0.0.1:8000/dicts/app_config/api_key
|
|
146
|
+
# Output: "your-secret-api-key"
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Command-Line Client
|
|
150
|
+
|
|
151
|
+
The CLI client allows you to call any BeaverDB method directly from your terminal.
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
# Set a value in a dictionary
|
|
155
|
+
beaver client --database data.db dict app_config set theme light
|
|
156
|
+
|
|
157
|
+
# Get the value back
|
|
158
|
+
beaver client --database data.db dict app_config get theme
|
|
159
|
+
|
|
160
|
+
# Push an item to a list
|
|
161
|
+
beaver client --database data.db list daily_tasks push "Review PRs"
|
|
162
|
+
```
|
|
163
|
+
|
|
109
164
|
## Things You Can Build with Beaver
|
|
110
165
|
|
|
111
166
|
Here are a few ideas to inspire your next project, showcasing how to combine Beaver's features to build powerful local applications.
|
|
@@ -257,8 +312,10 @@ For enhanced data integrity and a better developer experience, BeaverDB supports
|
|
|
257
312
|
|
|
258
313
|
This feature is designed to be flexible and works seamlessly with two kinds of models:
|
|
259
314
|
|
|
260
|
-
|
|
261
|
-
|
|
315
|
+
- **Pydantic Models**: If you're already using Pydantic, your `BaseModel` classes will work out of the box.
|
|
316
|
+
|
|
317
|
+
- **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.
|
|
318
|
+
|
|
262
319
|
|
|
263
320
|
Here’s a quick example of how to use it:
|
|
264
321
|
|
|
@@ -280,7 +337,8 @@ users["alice"] = User(name="Alice", email="alice@example.com")
|
|
|
280
337
|
|
|
281
338
|
# The retrieved object is a proper instance of the User class
|
|
282
339
|
retrieved_user = users["alice"]
|
|
283
|
-
|
|
340
|
+
# Your editor will provide autocompletion here
|
|
341
|
+
print(f"Retrieved: {retrieved_user.name}")
|
|
284
342
|
```
|
|
285
343
|
|
|
286
344
|
In the same way you can have typed message payloads in `db.channel`, typed metadata in `db.blobs`, and custom document types in `db.collection`, as well as custom types in lists and queues.
|
|
@@ -291,25 +349,25 @@ Basically everywhere you can store or get some object in BeaverDB, you can use a
|
|
|
291
349
|
|
|
292
350
|
For more in-depth examples, check out the scripts in the `examples/` directory:
|
|
293
351
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
352
|
+
- [`async_pubsub.py`](examples/async_pubsub.py): A demonstration of the asynchronous wrapper for the publish/subscribe system.
|
|
353
|
+
- [`blobs.py`](examples/blobs.py): Demonstrates how to store and retrieve binary data in the database.
|
|
354
|
+
- [`cache.py`](examples/cache.py): A practical example of using a dictionary with TTL as a cache for API calls.
|
|
355
|
+
- [`fts.py`](examples/fts.py): A detailed look at full-text search, including targeted searches on specific metadata fields.
|
|
356
|
+
- [`fuzzy.py`](examples/fuzzy.py): Demonstrates fuzzy search capabilities for text search.
|
|
357
|
+
- [`general_test.py`](examples/general_test.py): A general-purpose test to run all operations randomly which allows testing long-running processes and synchronicity issues.
|
|
358
|
+
- [`graph.py`](examples/graph.py): Shows how to create relationships between documents and perform multi-hop graph traversals.
|
|
359
|
+
- [`kvstore.py`](examples/kvstore.py): A comprehensive demo of the namespaced dictionary feature.
|
|
360
|
+
- [`list.py`](examples/list.py): Shows the full capabilities of the persistent list, including slicing and in-place updates.
|
|
361
|
+
- [`logs.py`](examples/logs.py): A short example showing how to build a realtime dashboard with the logging feature.
|
|
362
|
+
- [`pqueue.py`](examples/pqueue.py): A practical example of using the persistent priority queue for task management.
|
|
363
|
+
- [`producer_consumer.py`](examples/producer_consumer.py): A demonstration of the distributed task queue system in a multi-process environment.
|
|
364
|
+
- [`publisher.py`](examples/publisher.p) and [`subscriber.py`](examples/subscriber.py): A pair of examples demonstrating inter-process message passing with the publish/subscribe system.
|
|
365
|
+
- [`pubsub.py`](examples/pubsub.py): A demonstration of the synchronous, thread-safe publish/subscribe system in a single process.
|
|
366
|
+
- [`rerank.py`](examples/rerank.py): Shows how to combine results from vector and text search for more refined results.
|
|
367
|
+
- [`stress_vectors.py`](examples/stress_vectors.py): A stress test for the vector search functionality.
|
|
368
|
+
- [`textual_chat.py`](examples/textual_chat.py): A chat application built with `textual` and `beaver` to illustrate the use of several primitives (lists, dicts, and channels) at the same time.
|
|
369
|
+
- [`type_hints.py`](examples/type_hints.py): Shows how to use type hints with `beaver` to get better IDE support and type safety.
|
|
370
|
+
- [`vector.py`](examples/vector.py): Demonstrates how to index and search vector embeddings, including upserts.
|
|
313
371
|
|
|
314
372
|
## Roadmap
|
|
315
373
|
|
|
@@ -317,7 +375,7 @@ For more in-depth examples, check out the scripts in the `examples/` directory:
|
|
|
317
375
|
|
|
318
376
|
These are some of the features and improvements planned for future releases:
|
|
319
377
|
|
|
320
|
-
|
|
378
|
+
- **Async API**: Extend the async support with on-demand wrappers for all features besides channels.
|
|
321
379
|
|
|
322
380
|
Check out the [roadmap](roadmap.md) for a detailed list of upcoming features and design ideas.
|
|
323
381
|
|
|
@@ -325,4 +383,4 @@ If you think of something that would make `beaver` more useful for your use case
|
|
|
325
383
|
|
|
326
384
|
## License
|
|
327
385
|
|
|
328
|
-
This project is licensed under the MIT License.
|
|
386
|
+
This project is licensed under the MIT License.
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
try:
|
|
2
|
+
from typing import Any, Optional, List
|
|
3
|
+
import json
|
|
4
|
+
from datetime import datetime, timedelta, timezone
|
|
5
|
+
from fastapi import FastAPI, HTTPException, Body, UploadFile, File, Form, Response, WebSocket, WebSocketDisconnect
|
|
6
|
+
import uvicorn
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
except ImportError:
|
|
9
|
+
raise ImportError("Please install server dependencies with: pip install \"beaver-db[server]\"")
|
|
10
|
+
|
|
11
|
+
from .core import BeaverDB
|
|
12
|
+
from .collections import Document, WalkDirection
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# --- Pydantic Models for Collections ---
|
|
16
|
+
|
|
17
|
+
class IndexRequest(BaseModel):
|
|
18
|
+
id: Optional[str] = None
|
|
19
|
+
embedding: Optional[List[float]] = None
|
|
20
|
+
metadata: dict = Field(default_factory=dict)
|
|
21
|
+
fts: bool = True
|
|
22
|
+
fuzzy: bool = False
|
|
23
|
+
|
|
24
|
+
class SearchRequest(BaseModel):
|
|
25
|
+
vector: List[float]
|
|
26
|
+
top_k: int = 10
|
|
27
|
+
|
|
28
|
+
class MatchRequest(BaseModel):
|
|
29
|
+
query: str
|
|
30
|
+
on: Optional[List[str]] = None
|
|
31
|
+
top_k: int = 10
|
|
32
|
+
fuzziness: int = 0
|
|
33
|
+
|
|
34
|
+
class ConnectRequest(BaseModel):
|
|
35
|
+
source_id: str
|
|
36
|
+
target_id: str
|
|
37
|
+
label: str
|
|
38
|
+
metadata: Optional[dict] = None
|
|
39
|
+
|
|
40
|
+
class WalkRequest(BaseModel):
|
|
41
|
+
labels: List[str]
|
|
42
|
+
depth: int
|
|
43
|
+
direction: WalkDirection = WalkDirection.OUTGOING
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def build(db: BeaverDB) -> FastAPI:
|
|
47
|
+
"""Constructs a FastAPI instance for a given BeaverDB."""
|
|
48
|
+
app = FastAPI(title="BeaverDB Server")
|
|
49
|
+
|
|
50
|
+
# --- Dicts Endpoints ---
|
|
51
|
+
|
|
52
|
+
@app.get("/dicts/{name}/{key}", tags=["Dicts"])
|
|
53
|
+
def get_dict_item(name: str, key: str) -> Any:
|
|
54
|
+
"""Retrieves the value for a specific key."""
|
|
55
|
+
d = db.dict(name)
|
|
56
|
+
value = d.get(key)
|
|
57
|
+
if value is None:
|
|
58
|
+
raise HTTPException(status_code=404, detail=f"Key '{key}' not found in dictionary '{name}'")
|
|
59
|
+
return value
|
|
60
|
+
|
|
61
|
+
@app.put("/dicts/{name}/{key}", tags=["Dicts"])
|
|
62
|
+
def set_dict_item(name: str, key: str, value: Any = Body(...)):
|
|
63
|
+
"""Sets or updates the value for a specific key."""
|
|
64
|
+
d = db.dict(name)
|
|
65
|
+
d[key] = value
|
|
66
|
+
return {"status": "ok"}
|
|
67
|
+
|
|
68
|
+
@app.delete("/dicts/{name}/{key}", tags=["Dicts"])
|
|
69
|
+
def delete_dict_item(name: str, key: str):
|
|
70
|
+
"""Deletes a key-value pair."""
|
|
71
|
+
d = db.dict(name)
|
|
72
|
+
try:
|
|
73
|
+
del d[key]
|
|
74
|
+
return {"status": "ok"}
|
|
75
|
+
except KeyError:
|
|
76
|
+
raise HTTPException(status_code=404, detail=f"Key '{key}' not found in dictionary '{name}'")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# --- Lists Endpoints ---
|
|
80
|
+
|
|
81
|
+
@app.get("/lists/{name}", tags=["Lists"])
|
|
82
|
+
def get_list(name: str) -> list:
|
|
83
|
+
"""Retrieves all items in the list."""
|
|
84
|
+
l = db.list(name)
|
|
85
|
+
return l[:]
|
|
86
|
+
|
|
87
|
+
@app.get("/lists/{name}/{index}", tags=["Lists"])
|
|
88
|
+
def get_list_item(name: str, index: int) -> Any:
|
|
89
|
+
"""Retrieves the item at a specific index."""
|
|
90
|
+
l = db.list(name)
|
|
91
|
+
try:
|
|
92
|
+
return l[index]
|
|
93
|
+
except IndexError:
|
|
94
|
+
raise HTTPException(status_code=404, detail=f"Index {index} out of bounds for list '{name}'")
|
|
95
|
+
|
|
96
|
+
@app.post("/lists/{name}", tags=["Lists"])
|
|
97
|
+
def push_list_item(name: str, value: Any = Body(...)):
|
|
98
|
+
"""Adds an item to the end of the list."""
|
|
99
|
+
l = db.list(name)
|
|
100
|
+
l.push(value)
|
|
101
|
+
return {"status": "ok"}
|
|
102
|
+
|
|
103
|
+
@app.put("/lists/{name}/{index}", tags=["Lists"])
|
|
104
|
+
def update_list_item(name: str, index: int, value: Any = Body(...)):
|
|
105
|
+
"""Updates the item at a specific index."""
|
|
106
|
+
l = db.list(name)
|
|
107
|
+
try:
|
|
108
|
+
l[index] = value
|
|
109
|
+
return {"status": "ok"}
|
|
110
|
+
except IndexError:
|
|
111
|
+
raise HTTPException(status_code=404, detail=f"Index {index} out of bounds for list '{name}'")
|
|
112
|
+
|
|
113
|
+
@app.delete("/lists/{name}/{index}", tags=["Lists"])
|
|
114
|
+
def delete_list_item(name: str, index: int):
|
|
115
|
+
"""Deletes the item at a specific index."""
|
|
116
|
+
l = db.list(name)
|
|
117
|
+
try:
|
|
118
|
+
del l[index]
|
|
119
|
+
return {"status": "ok"}
|
|
120
|
+
except IndexError:
|
|
121
|
+
raise HTTPException(status_code=404, detail=f"Index {index} out of bounds for list '{name}'")
|
|
122
|
+
|
|
123
|
+
# --- Queues Endpoints ---
|
|
124
|
+
|
|
125
|
+
@app.get("/queues/{name}/peek", tags=["Queues"])
|
|
126
|
+
def peek_queue_item(name: str) -> Any:
|
|
127
|
+
"""Retrieves the highest-priority item from the queue without removing it."""
|
|
128
|
+
q = db.queue(name)
|
|
129
|
+
item = q.peek()
|
|
130
|
+
if item is None:
|
|
131
|
+
raise HTTPException(status_code=404, detail=f"Queue '{name}' is empty")
|
|
132
|
+
return item
|
|
133
|
+
|
|
134
|
+
@app.post("/queues/{name}/put", tags=["Queues"])
|
|
135
|
+
def put_queue_item(name: str, data: Any = Body(...), priority: float = Body(...)):
|
|
136
|
+
"""Adds an item to the queue with a specific priority."""
|
|
137
|
+
q = db.queue(name)
|
|
138
|
+
q.put(data=data, priority=priority)
|
|
139
|
+
return {"status": "ok"}
|
|
140
|
+
|
|
141
|
+
@app.delete("/queues/{name}/get", tags=["Queues"])
|
|
142
|
+
def get_queue_item(name: str, timeout: float = 5.0) -> Any:
|
|
143
|
+
"""
|
|
144
|
+
Atomically retrieves and removes the highest-priority item from the queue,
|
|
145
|
+
blocking until an item is available or the timeout is reached.
|
|
146
|
+
"""
|
|
147
|
+
q = db.queue(name)
|
|
148
|
+
try:
|
|
149
|
+
item = q.get(block=True, timeout=timeout)
|
|
150
|
+
return item
|
|
151
|
+
except TimeoutError:
|
|
152
|
+
raise HTTPException(status_code=408, detail=f"Request timed out after {timeout}s waiting for an item in queue '{name}'")
|
|
153
|
+
except IndexError:
|
|
154
|
+
# This case is less likely with block=True but good to handle
|
|
155
|
+
raise HTTPException(status_code=404, detail=f"Queue '{name}' is empty")
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
# --- Blobs Endpoints ---
|
|
159
|
+
|
|
160
|
+
@app.get("/blobs/{name}/{key}", response_class=Response, tags=["Blobs"])
|
|
161
|
+
def get_blob(name: str, key: str):
|
|
162
|
+
"""Retrieves a blob as a binary file."""
|
|
163
|
+
blobs = db.blobs(name)
|
|
164
|
+
blob = blobs.get(key)
|
|
165
|
+
if blob is None:
|
|
166
|
+
raise HTTPException(status_code=404, detail=f"Blob with key '{key}' not found in store '{name}'")
|
|
167
|
+
# Return the raw bytes with a generic binary content type
|
|
168
|
+
return Response(content=blob.data, media_type="application/octet-stream")
|
|
169
|
+
|
|
170
|
+
@app.put("/blobs/{name}/{key}", tags=["Blobs"])
|
|
171
|
+
async def put_blob(name: str, key: str, data: UploadFile = File(...), metadata: Optional[str] = Form(None)):
|
|
172
|
+
"""Stores a blob (binary file) with optional JSON metadata."""
|
|
173
|
+
blobs = db.blobs(name)
|
|
174
|
+
file_bytes = await data.read()
|
|
175
|
+
|
|
176
|
+
meta_dict = None
|
|
177
|
+
if metadata:
|
|
178
|
+
try:
|
|
179
|
+
meta_dict = json.loads(metadata)
|
|
180
|
+
except json.JSONDecodeError:
|
|
181
|
+
raise HTTPException(status_code=400, detail="Invalid JSON format for metadata.")
|
|
182
|
+
|
|
183
|
+
blobs.put(key=key, data=file_bytes, metadata=meta_dict)
|
|
184
|
+
return {"status": "ok"}
|
|
185
|
+
|
|
186
|
+
@app.delete("/blobs/{name}/{key}", tags=["Blobs"])
|
|
187
|
+
def delete_blob(name: str, key: str):
|
|
188
|
+
"""Deletes a blob from the store."""
|
|
189
|
+
blobs = db.blobs(name)
|
|
190
|
+
try:
|
|
191
|
+
blobs.delete(key)
|
|
192
|
+
return {"status": "ok"}
|
|
193
|
+
except KeyError:
|
|
194
|
+
raise HTTPException(status_code=404, detail=f"Blob with key '{key}' not found in store '{name}'")
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
# --- Logs Endpoints ---
|
|
198
|
+
|
|
199
|
+
@app.post("/logs/{name}", tags=["Logs"])
|
|
200
|
+
def create_log_entry(name: str, data: Any = Body(...)):
|
|
201
|
+
"""Adds a new entry to the log."""
|
|
202
|
+
log = db.log(name)
|
|
203
|
+
log.log(data)
|
|
204
|
+
return {"status": "ok"}
|
|
205
|
+
|
|
206
|
+
@app.get("/logs/{name}/range", tags=["Logs"])
|
|
207
|
+
def get_log_range(name: str, start: datetime, end: datetime) -> list:
|
|
208
|
+
"""Retrieves log entries within a specific time window."""
|
|
209
|
+
log = db.log(name)
|
|
210
|
+
# Ensure datetimes are timezone-aware (UTC) for correct comparison
|
|
211
|
+
start_utc = start.astimezone(timezone.utc) if start.tzinfo else start.replace(tzinfo=timezone.utc)
|
|
212
|
+
end_utc = end.astimezone(timezone.utc) if end.tzinfo else end.replace(tzinfo=timezone.utc)
|
|
213
|
+
return log.range(start=start_utc, end=end_utc)
|
|
214
|
+
|
|
215
|
+
@app.websocket("/logs/{name}/live", name="Logs")
|
|
216
|
+
async def live_log_feed(websocket: WebSocket, name: str, window_seconds: int = 5, period_seconds: int = 1):
|
|
217
|
+
"""Streams live, aggregated log data over a WebSocket."""
|
|
218
|
+
await websocket.accept()
|
|
219
|
+
|
|
220
|
+
async_logs = db.log(name).as_async()
|
|
221
|
+
|
|
222
|
+
# This simple aggregator function runs in the background and returns a
|
|
223
|
+
# JSON-serializable summary of the data in the current window.
|
|
224
|
+
def simple_aggregator(window):
|
|
225
|
+
return {"count": len(window), "latest_timestamp": window[-1]["timestamp"] if window else None}
|
|
226
|
+
|
|
227
|
+
live_stream = async_logs.live(
|
|
228
|
+
window=timedelta(seconds=window_seconds),
|
|
229
|
+
period=timedelta(seconds=period_seconds),
|
|
230
|
+
aggregator=simple_aggregator,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
try:
|
|
234
|
+
async for summary in live_stream:
|
|
235
|
+
await websocket.send_json(summary)
|
|
236
|
+
except WebSocketDisconnect:
|
|
237
|
+
print(f"Client disconnected from log '{name}' live feed.")
|
|
238
|
+
finally:
|
|
239
|
+
# Cleanly close the underlying iterator and its background thread.
|
|
240
|
+
live_stream.close()
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
# --- Channels Endpoints ---
|
|
244
|
+
|
|
245
|
+
@app.post("/channels/{name}/publish", tags=["Channels"])
|
|
246
|
+
def publish_to_channel(name: str, payload: Any = Body(...)):
|
|
247
|
+
"""Publishes a message to the specified channel."""
|
|
248
|
+
channel = db.channel(name)
|
|
249
|
+
channel.publish(payload)
|
|
250
|
+
return {"status": "ok"}
|
|
251
|
+
|
|
252
|
+
@app.websocket("/channels/{name}/subscribe", name="Channels")
|
|
253
|
+
async def subscribe_to_channel(websocket: WebSocket, name: str):
|
|
254
|
+
"""Subscribes to a channel and streams messages over a WebSocket."""
|
|
255
|
+
await websocket.accept()
|
|
256
|
+
|
|
257
|
+
async_channel = db.channel(name).as_async()
|
|
258
|
+
|
|
259
|
+
try:
|
|
260
|
+
async with async_channel.subscribe() as listener:
|
|
261
|
+
async for message in listener.listen():
|
|
262
|
+
await websocket.send_json(message)
|
|
263
|
+
except WebSocketDisconnect:
|
|
264
|
+
print(f"Client disconnected from channel '{name}' subscription.")
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
# --- Collections Endpoints ---
|
|
268
|
+
|
|
269
|
+
@app.get("/collections/{name}", tags=["Collections"])
|
|
270
|
+
def get_all_documents(name: str) -> List[dict]:
|
|
271
|
+
"""Retrieves all documents in the collection."""
|
|
272
|
+
collection = db.collection(name)
|
|
273
|
+
return [doc.model_dump() for doc in collection]
|
|
274
|
+
|
|
275
|
+
@app.post("/collections/{name}/index", tags=["Collections"])
|
|
276
|
+
def index_document(name: str, req: IndexRequest):
|
|
277
|
+
"""Indexes a document in the specified collection."""
|
|
278
|
+
collection = db.collection(name)
|
|
279
|
+
doc = Document(id=req.id, embedding=req.embedding, **req.metadata)
|
|
280
|
+
try:
|
|
281
|
+
collection.index(doc, fts=req.fts, fuzzy=req.fuzzy)
|
|
282
|
+
return {"status": "ok", "id": doc.id}
|
|
283
|
+
except TypeError as e:
|
|
284
|
+
if "faiss" in str(e):
|
|
285
|
+
raise HTTPException(status_code=501, detail="Vector indexing requires the '[faiss]' extra. Install with: pip install \"beaver-db[faiss]\"")
|
|
286
|
+
raise e
|
|
287
|
+
|
|
288
|
+
@app.post("/collections/{name}/search", tags=["Collections"])
|
|
289
|
+
def search_collection(name: str, req: SearchRequest) -> List[dict]:
|
|
290
|
+
"""Performs a vector search on the collection."""
|
|
291
|
+
collection = db.collection(name)
|
|
292
|
+
try:
|
|
293
|
+
results = collection.search(vector=req.vector, top_k=req.top_k)
|
|
294
|
+
return [{"document": doc.model_dump(), "distance": dist} for doc, dist in results]
|
|
295
|
+
except TypeError as e:
|
|
296
|
+
if "faiss" in str(e):
|
|
297
|
+
raise HTTPException(status_code=501, detail="Vector search requires the '[faiss]' extra. Install with: pip install \"beaver-db[faiss]\"")
|
|
298
|
+
raise e
|
|
299
|
+
|
|
300
|
+
@app.post("/collections/{name}/match", tags=["Collections"])
|
|
301
|
+
def match_collection(name: str, req: MatchRequest) -> List[dict]:
|
|
302
|
+
"""Performs a full-text or fuzzy search on the collection."""
|
|
303
|
+
collection = db.collection(name)
|
|
304
|
+
results = collection.match(query=req.query, on=req.on, top_k=req.top_k, fuzziness=req.fuzziness)
|
|
305
|
+
return [{"document": doc.model_dump(), "score": score} for doc, score in results]
|
|
306
|
+
|
|
307
|
+
@app.post("/collections/{name}/connect", tags=["Collections"])
|
|
308
|
+
def connect_documents(name: str, req: ConnectRequest):
|
|
309
|
+
"""Creates a directed edge between two documents."""
|
|
310
|
+
collection = db.collection(name)
|
|
311
|
+
source_doc = Document(id=req.source_id)
|
|
312
|
+
target_doc = Document(id=req.target_id)
|
|
313
|
+
collection.connect(source=source_doc, target=target_doc, label=req.label, metadata=req.metadata)
|
|
314
|
+
return {"status": "ok"}
|
|
315
|
+
|
|
316
|
+
@app.get("/collections/{name}/{doc_id}/neighbors", tags=["Collections"])
|
|
317
|
+
def get_neighbors(name: str, doc_id: str, label: Optional[str] = None) -> List[dict]:
|
|
318
|
+
"""Retrieves the neighboring documents for a given document."""
|
|
319
|
+
collection = db.collection(name)
|
|
320
|
+
doc = Document(id=doc_id)
|
|
321
|
+
neighbors = collection.neighbors(doc, label=label)
|
|
322
|
+
return [n.model_dump() for n in neighbors]
|
|
323
|
+
|
|
324
|
+
@app.post("/collections/{name}/{doc_id}/walk", tags=["Collections"])
|
|
325
|
+
def walk_graph(name: str, doc_id: str, req: WalkRequest) -> List[dict]:
|
|
326
|
+
"""Performs a graph traversal (BFS) from a starting document."""
|
|
327
|
+
collection = db.collection(name)
|
|
328
|
+
source_doc = Document(id=doc_id)
|
|
329
|
+
results = collection.walk(source=source_doc, labels=req.labels, depth=req.depth, direction=req.direction)
|
|
330
|
+
return [doc.model_dump() for doc in results]
|
|
331
|
+
|
|
332
|
+
return app
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def serve(db_path: str, host: str, port: int):
|
|
336
|
+
"""Initializes and runs the Uvicorn server."""
|
|
337
|
+
db = BeaverDB(db_path)
|
|
338
|
+
app = build(db)
|
|
339
|
+
uvicorn.run(app, host=host, port=port)
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
try:
|
|
2
|
-
from fastapi import FastAPI, HTTPException, Body
|
|
3
|
-
import uvicorn
|
|
4
|
-
except ImportError:
|
|
5
|
-
raise ImportError(
|
|
6
|
-
"FastAPI and Uvicorn are required to serve the database. "
|
|
7
|
-
'Please install them with `pip install "beaver-db[server]"`'
|
|
8
|
-
)
|
|
9
|
-
from typing import Any
|
|
10
|
-
from .core import BeaverDB
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def build(db: BeaverDB) -> FastAPI:
|
|
14
|
-
"""
|
|
15
|
-
Constructs a FastAPI application instance for a given BeaverDB instance.
|
|
16
|
-
|
|
17
|
-
Args:
|
|
18
|
-
db: An active BeaverDB instance.
|
|
19
|
-
|
|
20
|
-
Returns:
|
|
21
|
-
A FastAPI application with all endpoints configured.
|
|
22
|
-
"""
|
|
23
|
-
app = FastAPI(
|
|
24
|
-
title="BeaverDB",
|
|
25
|
-
description="A RESTful API for a BeaverDB instance.",
|
|
26
|
-
version="0.1.0",
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
# --- Dicts Endpoints ---
|
|
30
|
-
|
|
31
|
-
@app.get("/dicts/{name}")
|
|
32
|
-
def get_all_dict_items(name: str) -> dict:
|
|
33
|
-
"""Retrieves all key-value pairs in a dictionary."""
|
|
34
|
-
d = db.dict(name)
|
|
35
|
-
return {k: v for k, v in d.items()}
|
|
36
|
-
|
|
37
|
-
@app.get("/dicts/{name}/{key}")
|
|
38
|
-
def get_dict_item(name: str, key: str) -> Any:
|
|
39
|
-
"""Retrieves the value for a specific key."""
|
|
40
|
-
d = db.dict(name)
|
|
41
|
-
try:
|
|
42
|
-
return d[key]
|
|
43
|
-
except KeyError:
|
|
44
|
-
raise HTTPException(status_code=404, detail=f"Key '{key}' not found in dictionary '{name}'")
|
|
45
|
-
|
|
46
|
-
@app.post("/dicts/{name}/{key}")
|
|
47
|
-
def set_dict_item(name: str, key: str, value: Any = Body(...)):
|
|
48
|
-
"""Sets the value for a specific key."""
|
|
49
|
-
d = db.dict(name)
|
|
50
|
-
d[key] = value
|
|
51
|
-
return {"status": "ok"}
|
|
52
|
-
|
|
53
|
-
@app.delete("/dicts/{name}/{key}")
|
|
54
|
-
def delete_dict_item(name: str, key: str):
|
|
55
|
-
"""Deletes a key-value pair."""
|
|
56
|
-
d = db.dict(name)
|
|
57
|
-
try:
|
|
58
|
-
del d[key]
|
|
59
|
-
return {"status": "ok"}
|
|
60
|
-
except KeyError:
|
|
61
|
-
raise HTTPException(status_code=404, detail=f"Key '{key}' not found in dictionary '{name}'")
|
|
62
|
-
|
|
63
|
-
# --- Lists Endpoints ---
|
|
64
|
-
|
|
65
|
-
@app.get("/lists/{name}")
|
|
66
|
-
def get_list(name: str) -> list:
|
|
67
|
-
"""Retrieves all items in the list."""
|
|
68
|
-
l = db.list(name)
|
|
69
|
-
return l[:]
|
|
70
|
-
|
|
71
|
-
@app.get("/lists/{name}/{index}")
|
|
72
|
-
def get_list_item(name: str, index: int) -> Any:
|
|
73
|
-
"""Retrieves the item at a specific index."""
|
|
74
|
-
l = db.list(name)
|
|
75
|
-
try:
|
|
76
|
-
return l[index]
|
|
77
|
-
except IndexError:
|
|
78
|
-
raise HTTPException(status_code=404, detail=f"Index {index} out of bounds for list '{name}'")
|
|
79
|
-
|
|
80
|
-
@app.post("/lists/{name}")
|
|
81
|
-
def push_list_item(name: str, value: Any = Body(...)):
|
|
82
|
-
"""Adds an item to the end of the list."""
|
|
83
|
-
l = db.list(name)
|
|
84
|
-
l.push(value)
|
|
85
|
-
return {"status": "ok"}
|
|
86
|
-
|
|
87
|
-
@app.put("/lists/{name}/{index}")
|
|
88
|
-
def update_list_item(name: str, index: int, value: Any = Body(...)):
|
|
89
|
-
"""Updates the item at a specific index."""
|
|
90
|
-
l = db.list(name)
|
|
91
|
-
try:
|
|
92
|
-
l[index] = value
|
|
93
|
-
return {"status": "ok"}
|
|
94
|
-
except IndexError:
|
|
95
|
-
raise HTTPException(status_code=404, detail=f"Index {index} out of bounds for list '{name}'")
|
|
96
|
-
|
|
97
|
-
@app.delete("/lists/{name}/{index}")
|
|
98
|
-
def delete_list_item(name: str, index: int):
|
|
99
|
-
"""Deletes the item at a specific index."""
|
|
100
|
-
l = db.list(name)
|
|
101
|
-
try:
|
|
102
|
-
del l[index]
|
|
103
|
-
return {"status": "ok"}
|
|
104
|
-
except IndexError:
|
|
105
|
-
raise HTTPException(status_code=404, detail=f"Index {index} out of bounds for list '{name}'")
|
|
106
|
-
|
|
107
|
-
# TODO: Add endpoints for all BeaverDB modalities
|
|
108
|
-
# - Queues
|
|
109
|
-
# - Collections
|
|
110
|
-
# - Channels
|
|
111
|
-
# - Logs
|
|
112
|
-
# - Blobs
|
|
113
|
-
|
|
114
|
-
@app.get("/")
|
|
115
|
-
def read_root():
|
|
116
|
-
return {"message": "Welcome to the BeaverDB API"}
|
|
117
|
-
|
|
118
|
-
return app
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
def serve(db_path: str, host: str, port: int):
|
|
122
|
-
"""
|
|
123
|
-
Initializes a BeaverDB instance and runs a Uvicorn server for it.
|
|
124
|
-
|
|
125
|
-
Args:
|
|
126
|
-
db_path: The path to the SQLite database file.
|
|
127
|
-
host: The host to bind the server to.
|
|
128
|
-
port: The port to run the server on.
|
|
129
|
-
"""
|
|
130
|
-
db = BeaverDB(db_path)
|
|
131
|
-
app = build(db)
|
|
132
|
-
uvicorn.run(app, host=host, port=port)
|
|
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
|
|
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
|