zexus 1.7.1 → 1.7.2

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 (159) hide show
  1. package/README.md +3 -3
  2. package/package.json +1 -1
  3. package/src/__init__.py +7 -0
  4. package/src/zexus/__init__.py +1 -1
  5. package/src/zexus/__pycache__/__init__.cpython-312.pyc +0 -0
  6. package/src/zexus/__pycache__/capability_system.cpython-312.pyc +0 -0
  7. package/src/zexus/__pycache__/debug_sanitizer.cpython-312.pyc +0 -0
  8. package/src/zexus/__pycache__/environment.cpython-312.pyc +0 -0
  9. package/src/zexus/__pycache__/error_reporter.cpython-312.pyc +0 -0
  10. package/src/zexus/__pycache__/input_validation.cpython-312.pyc +0 -0
  11. package/src/zexus/__pycache__/lexer.cpython-312.pyc +0 -0
  12. package/src/zexus/__pycache__/module_cache.cpython-312.pyc +0 -0
  13. package/src/zexus/__pycache__/module_manager.cpython-312.pyc +0 -0
  14. package/src/zexus/__pycache__/object.cpython-312.pyc +0 -0
  15. package/src/zexus/__pycache__/security.cpython-312.pyc +0 -0
  16. package/src/zexus/__pycache__/security_enforcement.cpython-312.pyc +0 -0
  17. package/src/zexus/__pycache__/syntax_validator.cpython-312.pyc +0 -0
  18. package/src/zexus/__pycache__/zexus_ast.cpython-312.pyc +0 -0
  19. package/src/zexus/__pycache__/zexus_token.cpython-312.pyc +0 -0
  20. package/src/zexus/access_control_system/__pycache__/__init__.cpython-312.pyc +0 -0
  21. package/src/zexus/access_control_system/__pycache__/access_control.cpython-312.pyc +0 -0
  22. package/src/zexus/advanced_types.py +17 -2
  23. package/src/zexus/blockchain/__init__.py +411 -0
  24. package/src/zexus/blockchain/accelerator.py +1160 -0
  25. package/src/zexus/blockchain/chain.py +660 -0
  26. package/src/zexus/blockchain/consensus.py +821 -0
  27. package/src/zexus/blockchain/contract_vm.py +1019 -0
  28. package/src/zexus/blockchain/crypto.py +79 -14
  29. package/src/zexus/blockchain/events.py +526 -0
  30. package/src/zexus/blockchain/loadtest.py +721 -0
  31. package/src/zexus/blockchain/monitoring.py +350 -0
  32. package/src/zexus/blockchain/mpt.py +716 -0
  33. package/src/zexus/blockchain/multichain.py +951 -0
  34. package/src/zexus/blockchain/multiprocess_executor.py +338 -0
  35. package/src/zexus/blockchain/network.py +886 -0
  36. package/src/zexus/blockchain/node.py +666 -0
  37. package/src/zexus/blockchain/rpc.py +1203 -0
  38. package/src/zexus/blockchain/rust_bridge.py +421 -0
  39. package/src/zexus/blockchain/storage.py +423 -0
  40. package/src/zexus/blockchain/tokens.py +750 -0
  41. package/src/zexus/blockchain/upgradeable.py +1004 -0
  42. package/src/zexus/blockchain/verification.py +1602 -0
  43. package/src/zexus/blockchain/wallet.py +621 -0
  44. package/src/zexus/cli/__pycache__/main.cpython-312.pyc +0 -0
  45. package/src/zexus/cli/main.py +300 -20
  46. package/src/zexus/cli/zpm.py +1 -1
  47. package/src/zexus/compiler/__pycache__/bytecode.cpython-312.pyc +0 -0
  48. package/src/zexus/compiler/__pycache__/lexer.cpython-312.pyc +0 -0
  49. package/src/zexus/compiler/__pycache__/parser.cpython-312.pyc +0 -0
  50. package/src/zexus/compiler/__pycache__/semantic.cpython-312.pyc +0 -0
  51. package/src/zexus/compiler/__pycache__/zexus_ast.cpython-312.pyc +0 -0
  52. package/src/zexus/compiler/lexer.py +10 -5
  53. package/src/zexus/concurrency_system.py +79 -0
  54. package/src/zexus/config.py +54 -0
  55. package/src/zexus/crypto_bridge.py +244 -8
  56. package/src/zexus/dap/__init__.py +10 -0
  57. package/src/zexus/dap/__main__.py +4 -0
  58. package/src/zexus/dap/dap_server.py +391 -0
  59. package/src/zexus/dap/debug_engine.py +298 -0
  60. package/src/zexus/environment.py +10 -1
  61. package/src/zexus/evaluator/__pycache__/bytecode_compiler.cpython-312.pyc +0 -0
  62. package/src/zexus/evaluator/__pycache__/core.cpython-312.pyc +0 -0
  63. package/src/zexus/evaluator/__pycache__/expressions.cpython-312.pyc +0 -0
  64. package/src/zexus/evaluator/__pycache__/functions.cpython-312.pyc +0 -0
  65. package/src/zexus/evaluator/__pycache__/resource_limiter.cpython-312.pyc +0 -0
  66. package/src/zexus/evaluator/__pycache__/statements.cpython-312.pyc +0 -0
  67. package/src/zexus/evaluator/__pycache__/unified_execution.cpython-312.pyc +0 -0
  68. package/src/zexus/evaluator/__pycache__/utils.cpython-312.pyc +0 -0
  69. package/src/zexus/evaluator/bytecode_compiler.py +441 -37
  70. package/src/zexus/evaluator/core.py +560 -49
  71. package/src/zexus/evaluator/expressions.py +122 -49
  72. package/src/zexus/evaluator/functions.py +417 -16
  73. package/src/zexus/evaluator/statements.py +521 -118
  74. package/src/zexus/evaluator/unified_execution.py +573 -72
  75. package/src/zexus/evaluator/utils.py +14 -2
  76. package/src/zexus/event_loop.py +186 -0
  77. package/src/zexus/lexer.py +742 -486
  78. package/src/zexus/lsp/__init__.py +1 -1
  79. package/src/zexus/lsp/definition_provider.py +163 -9
  80. package/src/zexus/lsp/server.py +22 -8
  81. package/src/zexus/lsp/symbol_provider.py +182 -9
  82. package/src/zexus/module_cache.py +237 -9
  83. package/src/zexus/object.py +64 -6
  84. package/src/zexus/parser/__pycache__/parser.cpython-312.pyc +0 -0
  85. package/src/zexus/parser/__pycache__/strategy_context.cpython-312.pyc +0 -0
  86. package/src/zexus/parser/__pycache__/strategy_structural.cpython-312.pyc +0 -0
  87. package/src/zexus/parser/parser.py +786 -285
  88. package/src/zexus/parser/strategy_context.py +407 -66
  89. package/src/zexus/parser/strategy_structural.py +117 -19
  90. package/src/zexus/persistence.py +15 -1
  91. package/src/zexus/renderer/__init__.py +15 -0
  92. package/src/zexus/renderer/__pycache__/__init__.cpython-312.pyc +0 -0
  93. package/src/zexus/renderer/__pycache__/backend.cpython-312.pyc +0 -0
  94. package/src/zexus/renderer/__pycache__/canvas.cpython-312.pyc +0 -0
  95. package/src/zexus/renderer/__pycache__/color_system.cpython-312.pyc +0 -0
  96. package/src/zexus/renderer/__pycache__/layout.cpython-312.pyc +0 -0
  97. package/src/zexus/renderer/__pycache__/main_renderer.cpython-312.pyc +0 -0
  98. package/src/zexus/renderer/__pycache__/painter.cpython-312.pyc +0 -0
  99. package/src/zexus/renderer/tk_backend.py +208 -0
  100. package/src/zexus/renderer/web_backend.py +260 -0
  101. package/src/zexus/runtime/__pycache__/__init__.cpython-312.pyc +0 -0
  102. package/src/zexus/runtime/__pycache__/async_runtime.cpython-312.pyc +0 -0
  103. package/src/zexus/runtime/__pycache__/load_manager.cpython-312.pyc +0 -0
  104. package/src/zexus/runtime/file_flags.py +137 -0
  105. package/src/zexus/safety/__pycache__/__init__.cpython-312.pyc +0 -0
  106. package/src/zexus/safety/__pycache__/memory_safety.cpython-312.pyc +0 -0
  107. package/src/zexus/security.py +424 -34
  108. package/src/zexus/stdlib/fs.py +23 -18
  109. package/src/zexus/stdlib/http.py +289 -186
  110. package/src/zexus/stdlib/sockets.py +207 -163
  111. package/src/zexus/stdlib/websockets.py +282 -0
  112. package/src/zexus/stdlib_integration.py +369 -2
  113. package/src/zexus/strategy_recovery.py +6 -3
  114. package/src/zexus/type_checker.py +423 -0
  115. package/src/zexus/virtual_filesystem.py +189 -2
  116. package/src/zexus/vm/__init__.py +113 -3
  117. package/src/zexus/vm/__pycache__/async_optimizer.cpython-312.pyc +0 -0
  118. package/src/zexus/vm/__pycache__/bytecode.cpython-312.pyc +0 -0
  119. package/src/zexus/vm/__pycache__/bytecode_converter.cpython-312.pyc +0 -0
  120. package/src/zexus/vm/__pycache__/cache.cpython-312.pyc +0 -0
  121. package/src/zexus/vm/__pycache__/compiler.cpython-312.pyc +0 -0
  122. package/src/zexus/vm/__pycache__/gas_metering.cpython-312.pyc +0 -0
  123. package/src/zexus/vm/__pycache__/jit.cpython-312.pyc +0 -0
  124. package/src/zexus/vm/__pycache__/parallel_vm.cpython-312.pyc +0 -0
  125. package/src/zexus/vm/__pycache__/vm.cpython-312.pyc +0 -0
  126. package/src/zexus/vm/async_optimizer.py +14 -1
  127. package/src/zexus/vm/binary_bytecode.py +659 -0
  128. package/src/zexus/vm/bytecode.py +28 -1
  129. package/src/zexus/vm/bytecode_converter.py +26 -12
  130. package/src/zexus/vm/cabi.c +1985 -0
  131. package/src/zexus/vm/cabi.cpython-312-x86_64-linux-gnu.so +0 -0
  132. package/src/zexus/vm/cabi.h +127 -0
  133. package/src/zexus/vm/cache.py +557 -17
  134. package/src/zexus/vm/compiler.py +703 -5
  135. package/src/zexus/vm/fastops.c +15743 -0
  136. package/src/zexus/vm/fastops.cpython-312-x86_64-linux-gnu.so +0 -0
  137. package/src/zexus/vm/fastops.pyx +288 -0
  138. package/src/zexus/vm/gas_metering.py +50 -9
  139. package/src/zexus/vm/jit.py +83 -2
  140. package/src/zexus/vm/native_jit_backend.py +1816 -0
  141. package/src/zexus/vm/native_runtime.cpp +1388 -0
  142. package/src/zexus/vm/native_runtime.cpython-312-x86_64-linux-gnu.so +0 -0
  143. package/src/zexus/vm/optimizer.py +161 -11
  144. package/src/zexus/vm/parallel_vm.py +118 -42
  145. package/src/zexus/vm/peephole_optimizer.py +82 -4
  146. package/src/zexus/vm/profiler.py +38 -18
  147. package/src/zexus/vm/register_allocator.py +16 -5
  148. package/src/zexus/vm/register_vm.py +8 -5
  149. package/src/zexus/vm/vm.py +3411 -573
  150. package/src/zexus/vm/wasm_compiler.py +658 -0
  151. package/src/zexus/zexus_ast.py +63 -11
  152. package/src/zexus/zexus_token.py +13 -5
  153. package/src/zexus/zpm/installer.py +55 -15
  154. package/src/zexus/zpm/package_manager.py +1 -1
  155. package/src/zexus/zpm/registry.py +257 -28
  156. package/src/zexus.egg-info/PKG-INFO +7 -4
  157. package/src/zexus.egg-info/SOURCES.txt +116 -9
  158. package/src/zexus.egg-info/entry_points.txt +1 -0
  159. package/src/zexus.egg-info/requires.txt +4 -0
@@ -0,0 +1,423 @@
1
+ """
2
+ Zexus Blockchain — Pluggable Storage Backends
3
+
4
+ Provides an abstract StorageBackend interface and concrete implementations
5
+ for SQLite, LevelDB, and RocksDB. This replaces the previous hard-coded
6
+ SQLite persistence in ``chain.py`` and allows operators to choose the
7
+ backend that best fits their deployment — SQLite for development and
8
+ lightweight nodes, LevelDB/RocksDB for high-throughput production chains.
9
+
10
+ Usage::
11
+
12
+ # Development / quick start — zero config (default)
13
+ backend = get_storage_backend("sqlite", db_path="/data/chain.db")
14
+
15
+ # Production — LevelDB
16
+ backend = get_storage_backend("leveldb", db_path="/data/chaindb")
17
+
18
+ # Production — RocksDB (best write-throughput)
19
+ backend = get_storage_backend("rocksdb", db_path="/data/chaindb")
20
+
21
+ Pass the backend to ``Chain(storage=backend)``.
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ import json
27
+ import logging
28
+ import os
29
+ import sqlite3
30
+ from abc import ABC, abstractmethod
31
+ from typing import Any, Dict, Iterator, List, Optional, Tuple
32
+
33
+ logger = logging.getLogger("zexus.blockchain.storage")
34
+
35
+
36
+ # ── Abstract interface ─────────────────────────────────────────────────
37
+
38
+ class StorageBackend(ABC):
39
+ """Minimal key-value style interface for blockchain persistence.
40
+
41
+ The blockchain uses three *namespaces* (logical tables):
42
+
43
+ * ``blocks`` — height ↦ serialised block JSON
44
+ * ``state`` — address ↦ account JSON
45
+ * ``contract_state`` — address ↦ contract state JSON
46
+
47
+ Implementations MUST support these three namespaces and provide
48
+ atomic writes (``commit``).
49
+ """
50
+
51
+ NAMESPACES = ("blocks", "state", "contract_state")
52
+
53
+ # -- core operations -------------------------------------------------
54
+
55
+ @abstractmethod
56
+ def get(self, namespace: str, key: str) -> Optional[str]:
57
+ """Retrieve a value by key from *namespace*, or ``None``."""
58
+
59
+ @abstractmethod
60
+ def put(self, namespace: str, key: str, value: str) -> None:
61
+ """Write *value* under *key* in *namespace*.
62
+
63
+ Implementations may buffer the write until ``commit()`` is called.
64
+ """
65
+
66
+ @abstractmethod
67
+ def delete(self, namespace: str, key: str) -> None:
68
+ """Remove *key* from *namespace*."""
69
+
70
+ @abstractmethod
71
+ def has(self, namespace: str, key: str) -> bool:
72
+ """Return ``True`` if *key* exists in *namespace*."""
73
+
74
+ @abstractmethod
75
+ def iterate(self, namespace: str) -> Iterator[Tuple[str, str]]:
76
+ """Yield all ``(key, value)`` pairs in *namespace* in
77
+ insertion / key order."""
78
+
79
+ @abstractmethod
80
+ def iterate_sorted(self, namespace: str) -> Iterator[Tuple[str, str]]:
81
+ """Yield all ``(key, value)`` pairs sorted numerically if
82
+ applicable (e.g. block heights)."""
83
+
84
+ @abstractmethod
85
+ def commit(self) -> None:
86
+ """Flush any buffered writes to durable storage."""
87
+
88
+ @abstractmethod
89
+ def close(self) -> None:
90
+ """Release resources held by the backend."""
91
+
92
+ # -- convenience helpers (overridable) --------------------------------
93
+
94
+ def get_json(self, namespace: str, key: str) -> Optional[Any]:
95
+ raw = self.get(namespace, key)
96
+ if raw is None:
97
+ return None
98
+ return json.loads(raw)
99
+
100
+ def put_json(self, namespace: str, key: str, obj: Any) -> None:
101
+ self.put(namespace, key, json.dumps(obj, default=str))
102
+
103
+ @property
104
+ def name(self) -> str:
105
+ return self.__class__.__name__
106
+
107
+
108
+ # ── SQLite backend (default) ───────────────────────────────────────────
109
+
110
+ class SQLiteBackend(StorageBackend):
111
+ """SQLite-based storage — the battle-tested default.
112
+
113
+ Best for: development, testnets, single-node setups, light nodes.
114
+ """
115
+
116
+ def __init__(self, db_path: str):
117
+ self._db_path = db_path
118
+ os.makedirs(os.path.dirname(db_path) or ".", exist_ok=True)
119
+ self._conn = sqlite3.connect(db_path, check_same_thread=False)
120
+ self._conn.execute("PRAGMA journal_mode=WAL") # better concurrent reads
121
+ self._conn.execute("PRAGMA synchronous=NORMAL")
122
+ self._init_tables()
123
+ logger.info("SQLite storage backend opened: %s", db_path)
124
+
125
+ def _init_tables(self):
126
+ for ns in self.NAMESPACES:
127
+ self._conn.execute(f"""
128
+ CREATE TABLE IF NOT EXISTS [{ns}] (
129
+ key TEXT PRIMARY KEY,
130
+ value TEXT NOT NULL
131
+ )
132
+ """)
133
+ self._conn.commit()
134
+
135
+ # -- interface -------------------------------------------------------
136
+
137
+ def get(self, namespace: str, key: str) -> Optional[str]:
138
+ row = self._conn.execute(
139
+ f"SELECT value FROM [{namespace}] WHERE key = ?", (key,)
140
+ ).fetchone()
141
+ return row[0] if row else None
142
+
143
+ def put(self, namespace: str, key: str, value: str) -> None:
144
+ self._conn.execute(
145
+ f"INSERT OR REPLACE INTO [{namespace}] (key, value) VALUES (?, ?)",
146
+ (key, value),
147
+ )
148
+
149
+ def delete(self, namespace: str, key: str) -> None:
150
+ self._conn.execute(
151
+ f"DELETE FROM [{namespace}] WHERE key = ?", (key,)
152
+ )
153
+
154
+ def has(self, namespace: str, key: str) -> bool:
155
+ row = self._conn.execute(
156
+ f"SELECT 1 FROM [{namespace}] WHERE key = ? LIMIT 1", (key,)
157
+ ).fetchone()
158
+ return row is not None
159
+
160
+ def iterate(self, namespace: str) -> Iterator[Tuple[str, str]]:
161
+ cursor = self._conn.execute(f"SELECT key, value FROM [{namespace}]")
162
+ yield from cursor
163
+
164
+ def iterate_sorted(self, namespace: str) -> Iterator[Tuple[str, str]]:
165
+ # For blocks, key is height (numeric string) — CAST allows proper sort
166
+ cursor = self._conn.execute(
167
+ f"SELECT key, value FROM [{namespace}] ORDER BY CAST(key AS INTEGER) ASC"
168
+ )
169
+ yield from cursor
170
+
171
+ def commit(self) -> None:
172
+ self._conn.commit()
173
+
174
+ def close(self) -> None:
175
+ if self._conn:
176
+ self._conn.close()
177
+ self._conn = None # type: ignore[assignment]
178
+ logger.info("SQLite storage backend closed")
179
+
180
+
181
+ # ── LevelDB backend ───────────────────────────────────────────────────
182
+
183
+ class LevelDBBackend(StorageBackend):
184
+ """LevelDB-based storage — optimised for read-heavy workloads.
185
+
186
+ Best for: production validator nodes, archive nodes (read-heavy).
187
+
188
+ Requires the ``plyvel`` package::
189
+
190
+ pip install plyvel
191
+ """
192
+
193
+ def __init__(self, db_path: str):
194
+ try:
195
+ import plyvel
196
+ except ImportError:
197
+ raise ImportError(
198
+ "LevelDB backend requires the 'plyvel' package.\n\n"
199
+ " Install with: pip install plyvel\n\n"
200
+ " plyvel requires the LevelDB C library. "
201
+ "On Ubuntu/Debian:\n"
202
+ " sudo apt-get install libleveldb-dev\n"
203
+ " On macOS:\n"
204
+ " brew install leveldb\n\n"
205
+ " Alternatively, use SQLiteBackend (no native deps) "
206
+ "or MemoryBackend for development."
207
+ )
208
+ self._db_path = db_path
209
+ os.makedirs(db_path, exist_ok=True)
210
+ self._dbs: Dict[str, Any] = {}
211
+ for ns in self.NAMESPACES:
212
+ self._dbs[ns] = plyvel.DB(
213
+ os.path.join(db_path, ns), create_if_missing=True
214
+ )
215
+ logger.info("LevelDB storage backend opened: %s", db_path)
216
+
217
+ def _encode(self, s: str) -> bytes:
218
+ return s.encode("utf-8")
219
+
220
+ def _decode(self, b: bytes) -> str:
221
+ return b.decode("utf-8")
222
+
223
+ def get(self, namespace: str, key: str) -> Optional[str]:
224
+ val = self._dbs[namespace].get(self._encode(key))
225
+ return self._decode(val) if val is not None else None
226
+
227
+ def put(self, namespace: str, key: str, value: str) -> None:
228
+ self._dbs[namespace].put(self._encode(key), self._encode(value))
229
+
230
+ def delete(self, namespace: str, key: str) -> None:
231
+ self._dbs[namespace].delete(self._encode(key))
232
+
233
+ def has(self, namespace: str, key: str) -> bool:
234
+ return self._dbs[namespace].get(self._encode(key)) is not None
235
+
236
+ def iterate(self, namespace: str) -> Iterator[Tuple[str, str]]:
237
+ for k, v in self._dbs[namespace]:
238
+ yield self._decode(k), self._decode(v)
239
+
240
+ def iterate_sorted(self, namespace: str) -> Iterator[Tuple[str, str]]:
241
+ # LevelDB iterates in lexicographic order by default.
242
+ # For numeric keys (block heights) we zero-pad on write, or
243
+ # fall back to in-memory sort for safety.
244
+ pairs = list(self.iterate(namespace))
245
+ try:
246
+ pairs.sort(key=lambda kv: int(kv[0]))
247
+ except ValueError:
248
+ pairs.sort()
249
+ yield from pairs
250
+
251
+ def commit(self) -> None:
252
+ pass # LevelDB writes are durable by default
253
+
254
+ def close(self) -> None:
255
+ for db in self._dbs.values():
256
+ db.close()
257
+ self._dbs.clear()
258
+ logger.info("LevelDB storage backend closed")
259
+
260
+
261
+ # ── RocksDB backend ───────────────────────────────────────────────────
262
+
263
+ class RocksDBBackend(StorageBackend):
264
+ """RocksDB-based storage — high write-throughput for busy chains.
265
+
266
+ Best for: production mining/validator nodes with high TPS.
267
+
268
+ Requires the ``python-rocksdb`` (or ``rocksdb``) package::
269
+
270
+ pip install python-rocksdb
271
+ """
272
+
273
+ def __init__(self, db_path: str):
274
+ try:
275
+ import rocksdb # type: ignore[import-untyped]
276
+ except ImportError:
277
+ raise ImportError(
278
+ "RocksDB backend requires the 'python-rocksdb' package.\n\n"
279
+ " Install with: pip install python-rocksdb\n\n"
280
+ " python-rocksdb requires the RocksDB C++ library. "
281
+ "On Ubuntu/Debian:\n"
282
+ " sudo apt-get install librocksdb-dev\n"
283
+ " On macOS:\n"
284
+ " brew install rocksdb\n\n"
285
+ " Alternatively, use SQLiteBackend (no native deps) "
286
+ "or MemoryBackend for development."
287
+ )
288
+ self._db_path = db_path
289
+ os.makedirs(db_path, exist_ok=True)
290
+ self._rocksdb = rocksdb
291
+ opts = rocksdb.Options()
292
+ opts.create_if_missing = True
293
+ opts.max_open_files = 512
294
+ opts.write_buffer_size = 64 * 1024 * 1024 # 64 MB
295
+ opts.max_write_buffer_number = 3
296
+ opts.target_file_size_base = 64 * 1024 * 1024
297
+
298
+ self._dbs: Dict[str, Any] = {}
299
+ for ns in self.NAMESPACES:
300
+ ns_path = os.path.join(db_path, ns)
301
+ os.makedirs(ns_path, exist_ok=True)
302
+ self._dbs[ns] = rocksdb.DB(ns_path, opts)
303
+ logger.info("RocksDB storage backend opened: %s", db_path)
304
+
305
+ def _encode(self, s: str) -> bytes:
306
+ return s.encode("utf-8")
307
+
308
+ def _decode(self, b: bytes) -> str:
309
+ return b.decode("utf-8")
310
+
311
+ def get(self, namespace: str, key: str) -> Optional[str]:
312
+ val = self._dbs[namespace].get(self._encode(key))
313
+ return self._decode(val) if val is not None else None
314
+
315
+ def put(self, namespace: str, key: str, value: str) -> None:
316
+ self._dbs[namespace].put(self._encode(key), self._encode(value))
317
+
318
+ def delete(self, namespace: str, key: str) -> None:
319
+ self._dbs[namespace].delete(self._encode(key))
320
+
321
+ def has(self, namespace: str, key: str) -> bool:
322
+ return self._dbs[namespace].get(self._encode(key)) is not None
323
+
324
+ def iterate(self, namespace: str) -> Iterator[Tuple[str, str]]:
325
+ it = self._dbs[namespace].iteritems()
326
+ it.seek_to_first()
327
+ for k, v in it:
328
+ yield self._decode(k), self._decode(v)
329
+
330
+ def iterate_sorted(self, namespace: str) -> Iterator[Tuple[str, str]]:
331
+ pairs = list(self.iterate(namespace))
332
+ try:
333
+ pairs.sort(key=lambda kv: int(kv[0]))
334
+ except ValueError:
335
+ pairs.sort()
336
+ yield from pairs
337
+
338
+ def commit(self) -> None:
339
+ pass # RocksDB writes are durable by default
340
+
341
+ def close(self) -> None:
342
+ # python-rocksdb DBs are closed via garbage collection
343
+ self._dbs.clear()
344
+ logger.info("RocksDB storage backend closed")
345
+
346
+
347
+ # ── In-memory backend (testing) ────────────────────────────────────────
348
+
349
+ class MemoryBackend(StorageBackend):
350
+ """Ephemeral in-memory backend — useful for tests and benchmarks."""
351
+
352
+ def __init__(self):
353
+ self._data: Dict[str, Dict[str, str]] = {ns: {} for ns in self.NAMESPACES}
354
+
355
+ def get(self, namespace: str, key: str) -> Optional[str]:
356
+ return self._data[namespace].get(key)
357
+
358
+ def put(self, namespace: str, key: str, value: str) -> None:
359
+ self._data[namespace][key] = value
360
+
361
+ def delete(self, namespace: str, key: str) -> None:
362
+ self._data[namespace].pop(key, None)
363
+
364
+ def has(self, namespace: str, key: str) -> bool:
365
+ return key in self._data[namespace]
366
+
367
+ def iterate(self, namespace: str) -> Iterator[Tuple[str, str]]:
368
+ yield from self._data[namespace].items()
369
+
370
+ def iterate_sorted(self, namespace: str) -> Iterator[Tuple[str, str]]:
371
+ pairs = list(self._data[namespace].items())
372
+ try:
373
+ pairs.sort(key=lambda kv: int(kv[0]))
374
+ except ValueError:
375
+ pairs.sort()
376
+ yield from pairs
377
+
378
+ def commit(self) -> None:
379
+ pass
380
+
381
+ def close(self) -> None:
382
+ self._data.clear()
383
+
384
+
385
+ # ── Backend factory ────────────────────────────────────────────────────
386
+
387
+ _BACKENDS = {
388
+ "sqlite": SQLiteBackend,
389
+ "leveldb": LevelDBBackend,
390
+ "rocksdb": RocksDBBackend,
391
+ "memory": MemoryBackend,
392
+ }
393
+
394
+
395
+ def get_storage_backend(name: str, **kwargs) -> StorageBackend:
396
+ """Instantiate a storage backend by name.
397
+
398
+ Parameters
399
+ ----------
400
+ name : str
401
+ One of ``"sqlite"``, ``"leveldb"``, ``"rocksdb"``, ``"memory"``.
402
+ **kwargs
403
+ Forwarded to the backend constructor (e.g. ``db_path``).
404
+
405
+ Returns
406
+ -------
407
+ StorageBackend
408
+ """
409
+ name = name.lower().strip()
410
+ cls = _BACKENDS.get(name)
411
+ if cls is None:
412
+ raise ValueError(
413
+ f"Unknown storage backend '{name}'. "
414
+ f"Available: {', '.join(sorted(_BACKENDS))}"
415
+ )
416
+ return cls(**kwargs)
417
+
418
+
419
+ def register_backend(name: str, cls: type) -> None:
420
+ """Register a custom storage backend class."""
421
+ if not issubclass(cls, StorageBackend):
422
+ raise TypeError(f"{cls} is not a StorageBackend subclass")
423
+ _BACKENDS[name.lower()] = cls