shelfdb 2.0.0__tar.gz → 2.0.0.dev0__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.
Files changed (39) hide show
  1. {shelfdb-2.0.0 → shelfdb-2.0.0.dev0}/PKG-INFO +1 -1
  2. {shelfdb-2.0.0 → shelfdb-2.0.0.dev0}/pyproject.toml +2 -2
  3. shelfdb-2.0.0.dev0/src/shelfdb/ai_skill/shelfdb-usage/SKILL.md +90 -0
  4. {shelfdb-2.0.0 → shelfdb-2.0.0.dev0}/src/shelfdb/ai_skill/shelfdb-usage/references/docs/embedded-mode.md +2 -3
  5. {shelfdb-2.0.0 → shelfdb-2.0.0.dev0}/src/shelfdb/ai_skill/shelfdb-usage/references/docs/server-mode.md +7 -10
  6. {shelfdb-2.0.0 → shelfdb-2.0.0.dev0}/src/shelfdb/ai_skill/shelfdb-usage/references/docs/transactions.md +4 -12
  7. {shelfdb-2.0.0 → shelfdb-2.0.0.dev0}/src/shelfdb/client/_impl.py +2 -24
  8. {shelfdb-2.0.0 → shelfdb-2.0.0.dev0}/src/shelfdb/server/rpc.py +3 -2
  9. {shelfdb-2.0.0 → shelfdb-2.0.0.dev0}/src/shelfdb/shelf/core.py +1 -7
  10. shelfdb-2.0.0/src/shelfdb/ai_skill/shelfdb-usage/SKILL.md +0 -115
  11. {shelfdb-2.0.0 → shelfdb-2.0.0.dev0}/LICENSE +0 -0
  12. {shelfdb-2.0.0 → shelfdb-2.0.0.dev0}/README.md +0 -0
  13. {shelfdb-2.0.0 → shelfdb-2.0.0.dev0}/src/shelfdb/__init__.py +0 -0
  14. {shelfdb-2.0.0 → shelfdb-2.0.0.dev0}/src/shelfdb/ai_skill/__init__.py +0 -0
  15. {shelfdb-2.0.0 → shelfdb-2.0.0.dev0}/src/shelfdb/ai_skill/shelfdb-usage/agents/openai.yaml +0 -0
  16. {shelfdb-2.0.0 → shelfdb-2.0.0.dev0}/src/shelfdb/ai_skill/shelfdb-usage/references/docs/api-reference.md +0 -0
  17. {shelfdb-2.0.0 → shelfdb-2.0.0.dev0}/src/shelfdb/ai_skill/shelfdb-usage/references/docs/getting-started.md +0 -0
  18. {shelfdb-2.0.0 → shelfdb-2.0.0.dev0}/src/shelfdb/ai_skill/shelfdb-usage/references/docs/index.md +0 -0
  19. {shelfdb-2.0.0 → shelfdb-2.0.0.dev0}/src/shelfdb/ai_skill/shelfdb-usage/references/docs/protocol.md +0 -0
  20. {shelfdb-2.0.0 → shelfdb-2.0.0.dev0}/src/shelfdb/ai_skill/shelfdb-usage/references/docs/query-model.md +0 -0
  21. {shelfdb-2.0.0 → shelfdb-2.0.0.dev0}/src/shelfdb/ai_skill/shelfdb-usage/references/docs/security.md +0 -0
  22. {shelfdb-2.0.0 → shelfdb-2.0.0.dev0}/src/shelfdb/cli.py +0 -0
  23. {shelfdb-2.0.0 → shelfdb-2.0.0.dev0}/src/shelfdb/client/__init__.py +0 -0
  24. {shelfdb-2.0.0 → shelfdb-2.0.0.dev0}/src/shelfdb/log.py +0 -0
  25. {shelfdb-2.0.0 → shelfdb-2.0.0.dev0}/src/shelfdb/protocol/__init__.py +0 -0
  26. {shelfdb-2.0.0 → shelfdb-2.0.0.dev0}/src/shelfdb/protocol/codec.py +0 -0
  27. {shelfdb-2.0.0 → shelfdb-2.0.0.dev0}/src/shelfdb/protocol/payload.py +0 -0
  28. {shelfdb-2.0.0 → shelfdb-2.0.0.dev0}/src/shelfdb/protocol/query.py +0 -0
  29. {shelfdb-2.0.0 → shelfdb-2.0.0.dev0}/src/shelfdb/protocol/schema.py +0 -0
  30. {shelfdb-2.0.0 → shelfdb-2.0.0.dev0}/src/shelfdb/server/__init__.py +0 -0
  31. {shelfdb-2.0.0 → shelfdb-2.0.0.dev0}/src/shelfdb/server/runtime.py +0 -0
  32. {shelfdb-2.0.0 → shelfdb-2.0.0.dev0}/src/shelfdb/shelf/__init__.py +0 -0
  33. {shelfdb-2.0.0 → shelfdb-2.0.0.dev0}/src/shelfdb/shelf/normalize.py +0 -0
  34. {shelfdb-2.0.0 → shelfdb-2.0.0.dev0}/src/shelfdb/shelf/query.py +0 -0
  35. {shelfdb-2.0.0 → shelfdb-2.0.0.dev0}/src/shelfdb/shelf/storage/__init__.py +0 -0
  36. {shelfdb-2.0.0 → shelfdb-2.0.0.dev0}/src/shelfdb/shelf/storage/lmdb.py +0 -0
  37. {shelfdb-2.0.0 → shelfdb-2.0.0.dev0}/src/shelfdb/util/__init__.py +0 -0
  38. {shelfdb-2.0.0 → shelfdb-2.0.0.dev0}/src/shelfdb/util/transport.py +0 -0
  39. {shelfdb-2.0.0 → shelfdb-2.0.0.dev0}/src/shelfdb/util/validation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shelfdb
3
- Version: 2.0.0
3
+ Version: 2.0.0.dev0
4
4
  Summary: A tiny documents database for Python
5
5
  Keywords: dict,json,database
6
6
  Author: Nitipit Nontasuwan
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "shelfdb"
7
- version = "2.0.0"
7
+ version = "2.0.0.dev0"
8
8
  description = "A tiny documents database for Python"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -38,7 +38,7 @@ dev = [
38
38
  "mkdocs>=1.6.1,<2",
39
39
  "mkdocs-shadcn>=0.10.4,<0.11",
40
40
  "mkdocstrings[python]>=0.29.0,<0.30",
41
- "pytest>=8.4.2,<10",
41
+ "pytest>=8.4.2,<9",
42
42
  ]
43
43
 
44
44
  [tool.uv.build-backend]
@@ -0,0 +1,90 @@
1
+ ---
2
+ name: shelfdb-usage
3
+ description: "ShelfDB developer workflow for running the local server and querying it from sync or async Python clients. Use when Codex needs to write or debug ShelfDB server-mode code, client queries, transactions, logging, or examples that call `shelfdb.connect`, `connect_async`, `.run()`, or `tx.commit()`."
4
+ ---
5
+
6
+ # ShelfDB usage
7
+
8
+ Prefer the simplest mode that fits the task.
9
+
10
+ ## Reference docs
11
+
12
+ - Read `references/docs/` for mirrored ShelfDB documentation when you need the full narrative docs.
13
+ - Prefer the most specific page for the task: `server-mode.md`, `query-model.md`, `transactions.md`, and `security.md` are the usual first reads.
14
+
15
+ ## Choose the mode
16
+
17
+ - Use embedded mode with `shelfdb.open(...)` when the process can access the LMDB directory directly.
18
+ - Use server mode only when a separate process or multi-client access is required.
19
+
20
+ ## Start the server
21
+
22
+ - Run `shelfdb` for the default TCP server on `127.0.0.1:17000` with the `db/` directory.
23
+ - Use `shelfdb --url tcp://HOST:PORT` or `shelfdb --url unix:///path/to/socket.sock` when an explicit transport is needed.
24
+ - Keep the server on loopback or a Unix socket. Treat the RPC layer as trusted-local only.
25
+
26
+ ## Connect from Python
27
+
28
+ - Use `shelfdb.connect(url)` in sync code.
29
+ - Use `await shelfdb.connect_async(url)` in async code.
30
+ - Await only `connect_async(...)`, `query.run()` in async code, and `tx.commit()`.
31
+
32
+ ## Build queries lazily
33
+
34
+ - Chain query methods and execute only with `.run()`.
35
+ - Reuse the same query object when you need to run the same pipeline again.
36
+ - Use the same query style in embedded and remote code.
37
+
38
+ Available operations:
39
+
40
+ - Read: `key`, `key_range`, `keys_in`, `filter`, `slice`, `first`, `count`
41
+ - Write: `put`, `put_many`, `update`, `replace`, `edit`, `delete`
42
+
43
+ ## Handle results correctly
44
+
45
+ - Local multi-item `.run()` returns a one-shot iterator of `['key', data]` items; wrap it in `list(...)` if you need reuse.
46
+ - Remote results are normalized into plain Python values.
47
+ - `first()` returns one item or `None`.
48
+ - `put_many()` returns `None`.
49
+
50
+ ## Respect batch and transaction behavior
51
+
52
+ - Pass iterables to `put_many()` and `keys_in()`, but assume the client materializes them before send.
53
+ - `keys_in()` keeps the requested key order and may repeat keys.
54
+ - Use `client.transaction(write=True)` for remote batches; queue queries with `.run()` and send them with `tx.commit()`.
55
+ - Use `db.transaction(write=True)` for embedded atomic writes; inside a local transaction, use `tx.shelf(...)`, not `db.shelf(...)`.
56
+
57
+ ## Safety checks
58
+
59
+ - Keep callable `filter()` and `edit()` functions simple and trusted; the protocol uses `dill`.
60
+ - Configure debug logs with `shelfdb.log.configure_logging("debug")` when diagnosing client or server behavior.
61
+ - Do not expose the server to untrusted networks.
62
+
63
+ ## Minimal patterns
64
+
65
+ ```python
66
+ import shelfdb
67
+
68
+ client = shelfdb.connect("tcp://127.0.0.1:17000")
69
+ note = client.shelf("note").key("note-1").first().run()
70
+ ```
71
+
72
+ ```python
73
+ import asyncio
74
+ import shelfdb
75
+
76
+
77
+ async def main():
78
+ client = await shelfdb.connect_async("unix:///tmp/shelfdb.sock")
79
+ rows = await client.shelf("note").keys_in(["note-1", "note-2"]).run()
80
+ print(rows)
81
+
82
+
83
+ asyncio.run(main())
84
+ ```
85
+
86
+ ```python
87
+ tx = client.transaction(write=True)
88
+ tx.shelf("note").put("note-1", {"title": "ShelfDB"}).run()
89
+ tx.commit()
90
+ ```
@@ -149,8 +149,7 @@ db.close()
149
149
  For consistent reads and atomic writes, use `with db.transaction(...) as tx:` and create
150
150
  transaction-scoped queries with `tx.shelf(...)`.
151
151
 
152
- Inside the block, embedded `.run()` calls execute immediately and update `tx.result`. Remote
153
- client transactions queue their steps until `tx.commit()` or a clean `with` / `async with` exit
154
- stores the commit result on `tx.result`.
152
+ Inside the block, embedded `.run()` calls execute immediately. Remote client transactions queue
153
+ their steps until `tx.commit()`.
155
154
 
156
155
  See [Transactions](transactions.md) for the full behavior and examples.
@@ -147,21 +147,18 @@ Remote transaction queries queue their steps when you call `.run()`, unlike embe
147
147
  where `.run()` executes immediately inside the `with` block.
148
148
 
149
149
  !!! info "Information:"
150
- Call `tx.commit()` to send the batch, or use `with client.transaction(...) as tx:` / `async with`
151
- to auto-commit and store the commit result on `tx.result`.
150
+ Call `tx.commit()` to send the batch.
152
151
 
153
152
  ```python
154
- with client.transaction(write=True) as tx:
155
- tx.shelf("note").put("note-1", {"title": "ShelfDB"}).run()
156
- tx.shelf("user").put("user-1", {"name": "alice"}).run()
157
-
158
- print(tx.result)
153
+ tx = client.transaction(write=True)
154
+ tx.shelf("note").put("note-1", {"title": "ShelfDB"}).run()
155
+ tx.shelf("user").put("user-1", {"name": "alice"}).run()
156
+ tx.commit()
159
157
  ```
160
158
 
161
- `tx.commit()` returns the last queued query result, or `None` for an empty transaction, and stores
162
- that value on `tx.result`.
159
+ `tx.commit()` returns the last queued query result, or `None` for an empty transaction.
163
160
 
164
- For async code, use `async with` or `await tx.commit()` instead.
161
+ For async code, use `await tx.commit()` instead.
165
162
 
166
163
  Transaction queries must belong to the transaction they are queued into.
167
164
 
@@ -5,8 +5,8 @@ ShelfDB supports database transactions for two main purposes:
5
5
  - **consistent reads** with a stable snapshot
6
6
  - **atomic writes** that either all commit or all roll back
7
7
 
8
- Embedded transactions execute each query when you call `.run()` inside the `with` block and keep the latest result on `tx.result`.
9
- Client transactions queue each query on `.run()` and store the commit result on `tx.result` when you call `tx.commit()` or use `with` / `async with`.
8
+ Embedded transactions execute each query when you call `.run()` inside the `with` block.
9
+ Client transactions queue each query on `.run()` and execute the batch later with `tx.commit()`.
10
10
 
11
11
  ## Read transactions
12
12
 
@@ -56,7 +56,6 @@ except RuntimeError:
56
56
  ```
57
57
 
58
58
  After that error, the update is not committed.
59
- `tx.result` still reflects the last successful query result from inside the aborted block.
60
59
 
61
60
  ## Read your own writes
62
61
 
@@ -114,15 +113,8 @@ tx.shelf("note").put("note-1", {"title": "hello"}).run()
114
113
  tx.commit()
115
114
  ```
116
115
 
117
- `tx.commit()` returns the last queued query result, or `None` for an empty transaction, and stores that value on `tx.result`.
116
+ `tx.commit()` returns the last queued query result, or `None` for an empty transaction.
118
117
 
119
- You can also write:
120
-
121
- ```python
122
- with client.transaction(write=True) as tx:
123
- tx.shelf("note").put("note-1", {"title": "hello"}).run()
124
- ```
125
-
126
- For async code, use `async with` or `await tx.commit()` instead.
118
+ For async code, use `await tx.commit()` instead.
127
119
 
128
120
  See [Server Mode](server-mode.md) for connection setup and the shared remote query model.
@@ -174,7 +174,6 @@ class AsyncClientTransaction:
174
174
  self._write = write
175
175
  self._txs: list[TransactionShelfRequest] = []
176
176
  self._ran = False
177
- self.result = None
178
177
 
179
178
  def _enqueue(self, query: AsyncTransactionQuery):
180
179
  if self._ran:
@@ -195,23 +194,13 @@ class AsyncClientTransaction:
195
194
 
196
195
  return AsyncTransactionQuery(self, require_shelf_name(shelf_name))
197
196
 
198
- async def __aenter__(self):
199
- return self
200
-
201
- async def __aexit__(self, exc_type, exc, tb):
202
- if exc_type is None and not self._ran:
203
- await self.commit()
204
- return False
205
-
206
197
  async def commit(self):
207
198
  if self._ran:
208
199
  raise RuntimeError("Transaction already ran.")
209
200
 
210
201
  self._ran = True
211
202
  payload = make_transaction_request(self._write, self._txs)
212
- result = await self._client._request(payload)
213
- self.result = result
214
- return result
203
+ return await self._client._request(payload)
215
204
 
216
205
  async def run(self):
217
206
  return await self.commit()
@@ -320,7 +309,6 @@ class SyncClientTransaction:
320
309
  self._write = write
321
310
  self._txs: list[TransactionShelfRequest] = []
322
311
  self._ran = False
323
- self.result = None
324
312
 
325
313
  def _enqueue(self, query: SyncTransactionQuery):
326
314
  if self._ran:
@@ -341,23 +329,13 @@ class SyncClientTransaction:
341
329
 
342
330
  return SyncTransactionQuery(self, require_shelf_name(shelf_name))
343
331
 
344
- def __enter__(self):
345
- return self
346
-
347
- def __exit__(self, exc_type, exc, tb):
348
- if exc_type is None and not self._ran:
349
- self.commit()
350
- return False
351
-
352
332
  def commit(self):
353
333
  if self._ran:
354
334
  raise RuntimeError("Transaction already ran.")
355
335
 
356
336
  self._ran = True
357
337
  payload = make_transaction_request(self._write, self._txs)
358
- result = self._client._request(payload)
359
- self.result = result
360
- return result
338
+ return self._client._request(payload)
361
339
 
362
340
  def run(self):
363
341
  return self.commit()
@@ -11,14 +11,15 @@ def run_query_request(db, shelf, queries):
11
11
 
12
12
  def run_transaction_request(db, write, txs):
13
13
  """Execute one transaction request and return its last result."""
14
+ last_result = None
14
15
  with db.transaction(write=write) as tx:
15
16
  for tx_payload in txs:
16
17
  match tx_payload:
17
18
  case {"shelf": shelf, "queries": queries}:
18
- replay_queries(tx.shelf(shelf), queries).run()
19
+ last_result = replay_queries(tx.shelf(shelf), queries).run()
19
20
  case _:
20
21
  raise ValueError("Transaction payload item is invalid.")
21
- return tx.result
22
+ return last_result
22
23
 
23
24
 
24
25
  def run_request(db, payload):
@@ -34,7 +34,6 @@ class Transaction:
34
34
  self._db = db
35
35
  self.txn = txn
36
36
  self.write = write
37
- self.result = None
38
37
 
39
38
  def shelf(self, shelf_name: str) -> "ShelfQuery":
40
39
  """Create one query builder bound to this active transaction."""
@@ -322,12 +321,7 @@ class ShelfQuery(QueryBuilderMixin):
322
321
  with self._db.transaction(write=True):
323
322
  return self._run_queries()
324
323
 
325
- result = self._run_queries()
326
- if self._tx_context is not None:
327
- if isinstance(result, Iterator):
328
- result = list(result)
329
- self._tx_context.result = result
330
- return result
324
+ return self._run_queries()
331
325
 
332
326
  def _has_write_step(self) -> bool:
333
327
  return any(query.get("write") is True for query in self.queries)
@@ -1,115 +0,0 @@
1
- ---
2
- name: shelfdb-usage
3
- description: "ShelfDB developer workflow for embedded and server-mode usage, including local transactions, client queries, logging, and examples that call `shelfdb.open`, `connect`, `connect_async`, `.run()`, or `tx.commit()`."
4
- ---
5
-
6
- # ShelfDB usage
7
-
8
- Prefer embedded mode first; use server mode only when you need a separate process or multiple trusted local clients.
9
-
10
- ## Reference docs
11
-
12
- - Read `references/docs/` for mirrored ShelfDB documentation when you need the full narrative docs.
13
- - Prefer the most specific page for the task: `embedded-mode.md`, `server-mode.md`, `transactions.md`, `query-model.md`, and `security.md` are the usual first reads.
14
-
15
- ## Choose the mode
16
-
17
- - Use `shelfdb.open(path)` when the Python process can access the LMDB directory directly.
18
- - Use server mode when you need a process boundary, a remote client, or shared access from several trusted local processes.
19
- - Keep server mode on loopback or a Unix socket. Treat the RPC layer as trusted-local only.
20
-
21
- ## Start the server
22
-
23
- - Run `shelfdb` for the default TCP server on `127.0.0.1:17000` with the `db` directory.
24
- - Use `shelfdb --url tcp://HOST:PORT` or `shelfdb --url unix:///path/to/socket.sock` when an explicit transport is needed.
25
- - Use `shelfdb --db ./data/app-db` to choose a different database path.
26
- - Use `shelfdb --log-level debug` when you need server-side diagnostics.
27
-
28
- ## Connect from Python
29
-
30
- - Use `shelfdb.connect(url)` in sync code.
31
- - Use `await shelfdb.connect_async(url)` in async code.
32
- - Await only `connect_async(...)`, remote query `.run()` calls, and `tx.commit()`.
33
- - Embedded transactions are sync `with db.transaction(...)` blocks, and their `.run()` calls execute immediately inside the block.
34
- - Remote transaction queries like `tx.shelf(...).run()` only queue work and return `None`; remote transactions also support `with` / `async with`, and `tx.commit()` returns the last queued result and stores it on `tx.result`.
35
-
36
- ## Build queries lazily
37
-
38
- - Chain query methods and execute only with `.run()`.
39
- - Reuse the same query object when you need to run the same pipeline again.
40
- - Use the same query style in embedded and remote code.
41
-
42
- Available operations:
43
-
44
- - Read: `key`, `key_range`, `keys_in`, `filter`, `slice`, `first`, `count`
45
- - Write: `put`, `put_many`, `update`, `replace`, `edit`, `delete`
46
-
47
- Selection rules:
48
-
49
- - Call `keys_in()` on the base shelf before chaining other selection steps.
50
- - `replace()`, `update()`, and `edit()` require an existing selection.
51
- - `delete()` is the mutator that is safe on a missing key.
52
-
53
- ## Handle results correctly
54
-
55
- - Local multi-item `.run()` returns a one-shot iterator of `['key', data]` items; wrap it in `list(...)` if you need reuse.
56
- - Remote results are normalized into plain Python values.
57
- - `first()` returns one item or `None`.
58
- - `put_many()` returns `None`.
59
- - In transactions, `tx.result` stores the latest result; for remote transactions, `tx.commit()` returns the last queued result or `None`.
60
-
61
- ## Respect batch and transaction behavior
62
-
63
- - Pass iterables to `put_many()` and `keys_in()`, but assume the client materializes them before send.
64
- - `keys_in()` keeps the requested key order and may repeat keys.
65
- - Use `db.transaction()` when you need a consistent snapshot.
66
- - Use `db.transaction(write=True)` when you need several local queries to commit together.
67
- - Single embedded write queries are already atomic by default. Inside a local transaction, use `tx.shelf(...)`, not `db.shelf(...)`.
68
- - Use `client.transaction(write=True)` for remote batches; queue queries with `.run()` and send them with `tx.commit()` or `await tx.commit()`.
69
- - Local transaction blocks commit on success and roll back on error.
70
- - Nested local transactions are not supported.
71
-
72
- ## Safety checks
73
-
74
- - Keep callable `filter()` and `edit()` functions simple and trusted; the protocol uses `dill`.
75
- - Configure debug logs with `shelfdb.log.configure_logging("debug")` when diagnosing client or server behavior.
76
- - Do not expose the server to untrusted networks.
77
-
78
- ## Minimal patterns
79
-
80
- ```python
81
- import shelfdb
82
-
83
- db = shelfdb.open("db")
84
- note = db.shelf("note").key("note-1").first().run()
85
- ```
86
-
87
- ```python
88
- import shelfdb
89
-
90
- client = shelfdb.connect("tcp://127.0.0.1:17000")
91
- note = client.shelf("note").key("note-1").first().run()
92
- ```
93
-
94
- ```python
95
- import asyncio
96
- import shelfdb
97
-
98
-
99
- async def main():
100
- client = await shelfdb.connect_async("unix:///tmp/shelfdb.sock")
101
- rows = await client.shelf("note").keys_in(["note-1", "note-2"]).run()
102
- print(rows)
103
-
104
-
105
- asyncio.run(main())
106
- ```
107
-
108
- ```python
109
- import shelfdb
110
-
111
- client = shelfdb.connect("tcp://127.0.0.1:17000")
112
- tx = client.transaction(write=True)
113
- tx.shelf("note").put("note-1", {"title": "ShelfDB"}).run()
114
- tx.commit()
115
- ```
File without changes
File without changes
File without changes
File without changes