proto-db 2.0.0rc3__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 (70) hide show
  1. proto_db-2.0.0rc3/CHANGELOG.md +224 -0
  2. proto_db-2.0.0rc3/LICENSE +21 -0
  3. proto_db-2.0.0rc3/MANIFEST.in +16 -0
  4. proto_db-2.0.0rc3/PKG-INFO +315 -0
  5. proto_db-2.0.0rc3/PYPI_UPLOAD.md +128 -0
  6. proto_db-2.0.0rc3/README.md +251 -0
  7. proto_db-2.0.0rc3/proto_db/__init__.py +141 -0
  8. proto_db-2.0.0rc3/proto_db/cluster/__init__.py +1 -0
  9. proto_db-2.0.0rc3/proto_db/cluster/coordinator.py +4279 -0
  10. proto_db-2.0.0rc3/proto_db/cluster/lease.py +429 -0
  11. proto_db-2.0.0rc3/proto_db/cluster/persister.py +493 -0
  12. proto_db-2.0.0rc3/proto_db/cluster/protocol.py +408 -0
  13. proto_db-2.0.0rc3/proto_db/cluster/rootlog.py +723 -0
  14. proto_db-2.0.0rc3/proto_db/cluster/transport.py +565 -0
  15. proto_db-2.0.0rc3/proto_db/collections/__init__.py +1 -0
  16. proto_db-2.0.0rc3/proto_db/collections/avl.py +878 -0
  17. proto_db-2.0.0rc3/proto_db/collections/base.py +631 -0
  18. proto_db-2.0.0rc3/proto_db/collections/dictionaries.py +429 -0
  19. proto_db-2.0.0rc3/proto_db/collections/hash_dictionaries.py +187 -0
  20. proto_db-2.0.0rc3/proto_db/collections/lists.py +160 -0
  21. proto_db-2.0.0rc3/proto_db/collections/sets.py +411 -0
  22. proto_db-2.0.0rc3/proto_db/core/__init__.py +5 -0
  23. proto_db-2.0.0rc3/proto_db/core/atom.py +1233 -0
  24. proto_db-2.0.0rc3/proto_db/core/codec.py +735 -0
  25. proto_db-2.0.0rc3/proto_db/core/dbobject.py +125 -0
  26. proto_db-2.0.0rc3/proto_db/errors.py +164 -0
  27. proto_db-2.0.0rc3/proto_db/indexing/__init__.py +32 -0
  28. proto_db-2.0.0rc3/proto_db/indexing/base.py +338 -0
  29. proto_db-2.0.0rc3/proto_db/indexing/equality.py +35 -0
  30. proto_db-2.0.0rc3/proto_db/indexing/keyed.py +737 -0
  31. proto_db-2.0.0rc3/proto_db/indexing/keyspec.py +265 -0
  32. proto_db-2.0.0rc3/proto_db/indexing/range.py +58 -0
  33. proto_db-2.0.0rc3/proto_db/indexing/vector.py +429 -0
  34. proto_db-2.0.0rc3/proto_db/indexing/vector_backends.py +832 -0
  35. proto_db-2.0.0rc3/proto_db/interop/__init__.py +4 -0
  36. proto_db-2.0.0rc3/proto_db/interop/arrow.py +308 -0
  37. proto_db-2.0.0rc3/proto_db/query/__init__.py +15 -0
  38. proto_db-2.0.0rc3/proto_db/query/cache.py +533 -0
  39. proto_db-2.0.0rc3/proto_db/query/expressions.py +357 -0
  40. proto_db-2.0.0rc3/proto_db/query/linq.py +1179 -0
  41. proto_db-2.0.0rc3/proto_db/query/pager.py +294 -0
  42. proto_db-2.0.0rc3/proto_db/query/planner.py +343 -0
  43. proto_db-2.0.0rc3/proto_db/query/plans.py +1099 -0
  44. proto_db-2.0.0rc3/proto_db/query/terms.py +333 -0
  45. proto_db-2.0.0rc3/proto_db/runtime/__init__.py +5 -0
  46. proto_db-2.0.0rc3/proto_db/runtime/fsm.py +446 -0
  47. proto_db-2.0.0rc3/proto_db/runtime/parallel.py +458 -0
  48. proto_db-2.0.0rc3/proto_db/storage/__init__.py +1 -0
  49. proto_db-2.0.0rc3/proto_db/storage/base.py +125 -0
  50. proto_db-2.0.0rc3/proto_db/storage/cache.py +148 -0
  51. proto_db-2.0.0rc3/proto_db/storage/cloud.py +894 -0
  52. proto_db-2.0.0rc3/proto_db/storage/crc.py +114 -0
  53. proto_db-2.0.0rc3/proto_db/storage/file.py +842 -0
  54. proto_db-2.0.0rc3/proto_db/storage/format.py +228 -0
  55. proto_db-2.0.0rc3/proto_db/storage/logfile.py +338 -0
  56. proto_db-2.0.0rc3/proto_db/storage/memory.py +135 -0
  57. proto_db-2.0.0rc3/proto_db/storage/ownerlock.py +221 -0
  58. proto_db-2.0.0rc3/proto_db/storage/tiered.py +2353 -0
  59. proto_db-2.0.0rc3/proto_db/tools/__init__.py +1 -0
  60. proto_db-2.0.0rc3/proto_db/tools/notation.py +182 -0
  61. proto_db-2.0.0rc3/proto_db/tools/verify.py +329 -0
  62. proto_db-2.0.0rc3/proto_db/transactions.py +2302 -0
  63. proto_db-2.0.0rc3/proto_db/values.py +975 -0
  64. proto_db-2.0.0rc3/proto_db.egg-info/PKG-INFO +315 -0
  65. proto_db-2.0.0rc3/proto_db.egg-info/SOURCES.txt +68 -0
  66. proto_db-2.0.0rc3/proto_db.egg-info/dependency_links.txt +1 -0
  67. proto_db-2.0.0rc3/proto_db.egg-info/requires.txt +26 -0
  68. proto_db-2.0.0rc3/proto_db.egg-info/top_level.txt +1 -0
  69. proto_db-2.0.0rc3/pyproject.toml +58 -0
  70. proto_db-2.0.0rc3/setup.cfg +4 -0
@@ -0,0 +1,224 @@
1
+ # Changelog
2
+
3
+ All notable changes to `proto_db` are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [2.0.0rc3] - 2026-11-13
9
+
10
+ The first release candidate published on PyPI.
11
+ - Python 3.12 is the minimum (D61): on 3.11, comparing deeply nested tuples counts against the recursion
12
+ limit, so a value near `MAX_VALUE_DEPTH` could raise a bare `RecursionError`. `pip` refuses to
13
+ install it on 3.11 (`requires-python >=3.12`).
14
+ - Test suite: process-wide object counts replaced by per-test counts, a stress mode for the cluster tests
15
+ (`PROTO_STRESS`, safety asserts only), and fixes to tests that failed in a clean export or under load.
16
+ - CI: a test matrix over 3.12, 3.13, 3.14 and 3.14t, and a `sphinx-build -W` documentation job.
17
+ - Benchmarks: a 10^7-object inventory and the periodic cluster stress task, with result files under
18
+ `benchmarks/results/`.
19
+
20
+ ## [2.0.0] - unreleased
21
+
22
+ `proto_db` 2.0 is a complete rebuild of the 0.1 code base under the design principles of
23
+ `docs/design-principles.md`: everything stored is immutable, a transaction is an explicit object, and
24
+ every safety argument holds under arbitrary delay and loss. There are no 0.1 data or API to preserve.
25
+ The design is `docs/superpowers/specs/2026-11-01-protodb-v2-design.md`; every decision is in
26
+ `docs/decisions.md` (D1-D61).
27
+
28
+ ### Added
29
+ - **One value model** with a total order across types (`1 == 1.0 == Decimal(1)`, `True != 1`), used by
30
+ collections, indexes and queries identically (D2, D3); frozen `list` and `dict` values (D25).
31
+ - **Atoms** with stable identity (`oid`), lazy thread-safe loading and msgpack serialisation (D10, D18, D21).
32
+ - **Collections**, all persistent AVL trees and all indexable: `List`, `Dictionary` (key-ordered),
33
+ `HashDictionary`, `RepeatedKeysDictionary`, `Set`, `CountedSet`, and `DBObject` records (D8, D20-D27).
34
+ - **Index framework**: `Index` base class with a documented hook contract, `KeySpec` (paths, the element,
35
+ registered extractors), `EqualityIndex`, `RangeIndex` and `VectorIndex` (exact, IVF-Flat, optional HNSW),
36
+ every mutator of every collection keeping its indexes current, and a reusable index contract
37
+ (`tests/contracts/index_contract.py`) for new kinds (D22, D29, D30, D39).
38
+ - **Query layer**: expression terms, physical plans, one cost-based planner that never drops a predicate, a
39
+ LINQ front end (`from_collection`, `F`, `Agg`), explicit `PythonFilter` nodes, `explain()`, a query result
40
+ cache keyed by pointers, and `Pager`s with portable tokens over snapshots (D28, D36-D38, D56).
41
+ - **Transactions**: snapshot isolation with read-set validation (absent roots included), `Database.run` with
42
+ bounded exponential back-off and jitter, per-key and per-element merge of unread collection roots,
43
+ `MutableObject` cells, group commit, `Database.history()` and read-only `Snapshot`s of any past version,
44
+ `ProtoOutcomeUnknownError` for commits whose outcome cannot be proved (D9, D11, D32-D35, D52).
45
+ - **Storage v2**: `MemoryStorage`; `FileStorage` with append-only CRC-checked segments, atomic root
46
+ publication and `fsync` per commit by default (`durability="flush"` and `"none"` are explicit opt-outs);
47
+ `CloudStorage` over S3 or Google Cloud Storage with data strictly before root and a conditional root write;
48
+ `TieredStorage` (D4, D6, D19, D43, D46, D51).
49
+ - **Cluster**: several writer processes through a peer coordinator (majority lease, write permissions, commit
50
+ by majority acknowledgement, bounded replication, learners, fencing), with the object store written after
51
+ the commit point (D44-D46, D50, D54, D55). `ObjectSpace.sync()` is a durability barrier; a barrier commit is
52
+ the linearizable read path (it adds a permanent database version per read).
53
+ - **Decoded-atom cache** shared by the transactions and threads of a space, by pointer, bounded on loaded
54
+ atoms, with counters (`ObjectSpace.atom_cache_stats()`) that expose a thrashing working set (D53).
55
+ - **Runtime services**: `parallel_scan` and `parallel_filter` (real speedup on free-threaded CPython),
56
+ `FSM`; **interop**: `to_arrow`, `to_parquet`, `scan_parquet` (D40-D42).
57
+ - **Free-threading**: tested on CPython 3.14t with the GIL disabled and with the GIL.
58
+ - **Performance suite** (`python -m benchmarks`): self-verifying benchmarks, with results by workload
59
+ profile in `docs/source/performance.rst` (D49).
60
+ - **Documentation**: a rewritten Sphinx site (concepts, design pages, ten executable tutorials, API
61
+ reference) and a test that runs every tutorial code block (`tests/docs/test_tutorials.py`).
62
+ - A single exception hierarchy rooted at `ProtoDBError` (D12), and a static check that forbids swallowed
63
+ exceptions and test-aware code in `proto_db/` (D16).
64
+
65
+ ### Changed
66
+ - The public API is the set of names in `proto_db.__all__`, resolved lazily: `import proto_db` imports no
67
+ submodule or optional dependency and starts no thread (D47).
68
+ - Dictionaries are key-ordered AVL trees (O(log n)), not a binary search over a positional list (D8).
69
+ - A conflict raises `ProtoConflictError` and the caller retries; merging applies only to roots the transaction
70
+ did not read and only by replaying recorded operations, never by inferring intent from value types (D9, D33).
71
+ - Strings are stored inline; the global `Literal` catalog is gone (D7).
72
+ - Python floor 3.12 (D61; was 3.11 under D14); CI targets 3.12-3.14 and 3.14t. `six` and `wrapt` are no longer
73
+ dependencies (D14).
74
+ - The package is split into subpackages: `core`, `storage`, `collections`, `indexing`, `query`, `cluster`,
75
+ `runtime`, `interop` (D15; the spec's `indexes` became `indexing`).
76
+
77
+ - Storage format 4 (D58): exact tokens are canonical encodings, datetimes in an IANA zone are stored as their
78
+ UTC instant, map keys follow an explicit conflict rule, and the class of a built-in subclass is not persisted
79
+ (a `str` or `int` `Enum` is stored and read back as its value). Formats 2 and 3 are still read. Migration
80
+ cost: a key-group or vector index written by format 3 is rebuilt from its collection at the collection's
81
+ first change, inside that write transaction (an ivf_flat index retrains, an hnsw graph is rebuilt). A
82
+ format-3 map keyed by a zone datetime or by repeated NaNs is read as written but refused when written again
83
+ until the application converts those keys.
84
+ - Polyglot freeze (D59, Task 10.P): a record of a type this process has no class for (another language's
85
+ type) loads as an `OpaqueAtom` that keeps its fields readable and is never rewritten, instead of raising
86
+ `ProtoCorruptionError`; root names starting with `__` are reserved for the format (`ProtoValidationError`);
87
+ the attribute-name errors explain the accessor rule. `python -m proto_db.tools.verify <dir>` decodes and
88
+ validates a whole file-storage space and dumps it in the conformance notation. The format specification
89
+ gains "Format stability" (the freeze point, the additive `@sym` and registry), "Atoms of unknown types",
90
+ the accessor rule and "A minimal external writer"; `tests/conformance/vectors.sha256` pins the vectors.
91
+ After the pre-tag review: a reference to an opaque atom may name its type differently from the record's
92
+ `t` (an alias only the owner knows) and keeps the name it was referenced by; writers write the lowest
93
+ sufficient format version (`WRITE_FORMAT_VERSION`, separate from the highest read, `FORMAT_VERSION`); the
94
+ verifier also reports non-canonical encodings and exits 2 only for an unreadable root; a field named like a
95
+ metaclass attribute (`mro`) is readable; a `date` or `timedelta` subclass is stored from its base fields,
96
+ never its `__reduce__`.
97
+ - Format-3 data misfiled by built-in subclasses: format 3 wrote a `(str, Enum)` member (on every Python
98
+ version) or a subclass overriding `__str__`, `__float__` or `__bytes__` through that conversion
99
+ (`"Color.RED"`) but filed it by its base value (`"red"`). Collections holding such values cannot find them,
100
+ and a key-ordered Dictionary also loses other keys. `collection.validate()` now detects it
101
+ (`ProtoMisfiledError`, a `ProtoCorruptionError`) and `tx.set_root_object(name, collection.rebuilt())`
102
+ repairs it (entries refiled, indexes rebuilt; one key with two different values is refused, never dropped).
103
+ - IVF-Flat training no longer depends on the members' oids (R128, 012db7f): a bulk build of the same vectors
104
+ with the same `seed` gives the same centroids and the same answers, in any order (an index grown by inserts
105
+ still trains on the members present at each retrain threshold, which depends on the insertion order). An `ivf_flat` index built before 012db7f
106
+ keeps its persisted centroids until it retrains; at its next retrain (or rebuild) it gets different
107
+ centroids than that same data gave before, and stable ones from then on. No format change.
108
+
109
+ ### Deprecated
110
+ - The 0.1 exception names `ProtoBaseException`, `ProtoValidationException`, `ProtoUserException`,
111
+ `ProtoCorruptionException`, `ProtoNotSupportedException`, `ProtoNotAuthorizedException`,
112
+ `ProtoUnexpectedException` and `ProtoLockingException`: aliases of the `Proto*Error` classes, removed in 2.1
113
+ (D12).
114
+
115
+ ### Removed
116
+ - The 0.1 implementation (`common`, `db_access`, the 0.1 storages, collections, queries, LINQ, vectors,
117
+ `parallel`, `fsm`, `arrow_bridge`, `hybrid_executor`, `exceptions`) and its test suite `proto_db/tests/`
118
+ (D47, D48; dropped tests: `docs/legacy/dropped-tests.md`). UDP voting is gone from the cloud storages (D5).
119
+
120
+ ### Fixed
121
+ - Saving a chain of unsaved versions built in one transaction (the `previous` pattern) no longer raises
122
+ `RecursionError` past about 150 links; 10,000 versions save in one commit, and the verifier describes them
123
+ (R128).
124
+ - Nested values: `proto_db` no longer writes values it cannot read back (B12, D60). The codec, `canonical()`,
125
+ the map-key, order-key, hash and equality helpers and the verifier walked values by recursion, so a value
126
+ past about 330 nested lists could be written but raised a bare `RecursionError` when read in a fresh
127
+ process (`verify` exited 1), and the limit moved with the Python stack in use. They now go on with an
128
+ explicit stack past 12 levels, and the format has a normative limit, `MAX_VALUE_DEPTH` = 1022 MessagePack
129
+ containers per value (format spec, "Value nesting depth"): a writer refuses a deeper value with
130
+ `ProtoValidationError` naming the field, a reader refuses deeper data with `ProtoCorruptionError`, at the
131
+ same count. No format version change (no conforming writer could produce deeper data); the encoded bytes
132
+ are unchanged. `proto_db` now requires msgpack 1.2.0 or later, the first release whose packer nests the 1024
133
+ containers a record at the limit needs, and refuses to import with an older one. The limit binds collection
134
+ and index nodes too: a Set, CountedSet, HashDictionary or RepeatedKeysDictionary, and an index (whose member
135
+ keys nest an unorderable element about four times deeper: 254 nested lists, 169 nested maps), refuse an
136
+ element they cannot store when it is added, naming the collection or the index and the effective limit,
137
+ never later at commit. The query result cache, `explain()` and `to_arrow` work at any depth too.
138
+ **Readers must accept records 1024 containers deep**: an implementation that stops at 509 levels is not
139
+ conforming from rc2.
140
+
141
+ Defects of the 0.1 code base found by the 2026-11-01 review, none of which survives the rebuild: integer
142
+ values merged as `+1` and special cases for keys named `counter` and `k`; commits that were not durable;
143
+ indexes lost on the first write; queries that silently returned empty or partial results; about 280 broad
144
+ `except` handlers hiding errors; two writers appending to one segment.
145
+
146
+ ### Known limitations
147
+ - No `proto_db.tools.inspect` browser (the spec's section 6 named one); the formats are specified in
148
+ `docs/source/format-spec.rst`, `python -m proto_db.tools.verify` checks and dumps a space, and the operations
149
+ tutorial shows both.
150
+ - No online-backup call; see the operations tutorial for what a copy of an open space contains.
151
+ - Cluster fairness for eight or more contending writer processes, and for hot-key read-modify-write
152
+ transactions, is not guaranteed (open defect D-LIVE-2).
153
+ - A read-only linearizable read (`read_barrier`) is proposed in the D46 amendment but not implemented; the
154
+ barrier commit is the supported path.
155
+
156
+ ## Before 2.0
157
+
158
+ Entries from the 0.1 line, kept as written. They describe code that 2.0 removed.
159
+
160
+ ### Added (0.1 line, unreleased)
161
+ - Optional parallel scanning module (proto_db.parallel) with adaptive chunking and a simple work-stealing scheduler.
162
+ - Metrics hooks for per-worker and global stats (chunk latencies, steals, queue depths).
163
+ - parallel_scan helper for easy opt-in usage without altering existing query code.
164
+ - Documentation: user guide (Parallel Scans), API reference, and ADR.
165
+ - Unit tests for adaptive chunking, work-stealing behavior, cancellation, and fallback correctness.
166
+
167
+ ### Changed
168
+ - Attribute access style: migrated docs/tests/examples to prefer dot notation; clarified DBObject semantics and dynamic getattr in README.
169
+
170
+ ## [0.1.0] - 2023
171
+
172
+ ### Added
173
+
174
+ #### Google Cloud Storage Support
175
+
176
+ - Added `GoogleCloudClient` as an implementation of `CloudStorageClient` for Google Cloud Storage
177
+ - Ensured that `CloudBlockProvider`, `CloudFileStorage`, and `CloudClusterFileStorage` work with both `S3Client` and
178
+ `GoogleCloudClient`
179
+ - Added comprehensive test suites for Google Cloud Storage integration
180
+ - Added usage examples for Google Cloud Storage in documentation
181
+
182
+ #### Multiple Serialization Formats Support
183
+
184
+ - Added a format indicator after the size header to specify the serialization format
185
+ - Added support for JSON UTF-8 (the original format) and MessagePack (a new efficient binary format)
186
+ - Maintained backward compatibility with existing data
187
+ - Added new methods to support the new formats:
188
+ - `push_atom(atom: dict, format_type: int = FORMAT_JSON_UTF8) -> Future[AtomPointer]`
189
+ - `push_atom_msgpack(atom: dict) -> Future[AtomPointer]`
190
+ - `push_bytes(data: bytes, format_type: int = FORMAT_RAW_BINARY) -> Future[tuple[uuid.UUID, int]]`
191
+ - `push_bytes_msgpack(data: dict) -> Future[tuple[uuid.UUID, int]]`
192
+
193
+ #### PyPI Package Support
194
+
195
+ - Created `pyproject.toml` with package metadata, dependencies, and build system information
196
+ - Added MIT License file
197
+ - Created `MANIFEST.in` to include non-Python files in the package distribution
198
+ - Added `test_install.py` script to test the local installation of the package
199
+ - Created `PYPI_UPLOAD.md` with documentation on how to build and upload the package to PyPI
200
+ - Updated README.md with installation instructions and license information
201
+
202
+ #### Advanced Storage Options Documentation
203
+
204
+ - Added comprehensive documentation for `ClusterFileStorage` and `CloudFileStorage`
205
+ - Updated `__init__.py` to expose the advanced storage options
206
+ - Added detailed use cases for the advanced storage options
207
+
208
+ ### Fixed
209
+
210
+ #### CloudFileStorage Test Hanging Issue
211
+
212
+ - Added an `uploader_running` flag to control the background uploader thread
213
+ - Modified the `_background_uploader` method to check both the `state` and the `uploader_running` flag
214
+ - Updated the `close` method to properly stop the background uploader thread and wait for it to finish
215
+ - Added test verification to ensure the uploader thread is properly stopped
216
+
217
+ ### Changed
218
+
219
+ - Completed the implementation of `S3Client` by adding missing abstract methods:
220
+ - `_init_client`: Initializes the S3 client using boto3 or falls back to a mock implementation
221
+ - `get_object`: Gets an object from S3
222
+ - `put_object`: Puts an object to S3
223
+ - `list_objects`: Lists objects in S3
224
+ - `delete_object`: Deletes an object from S3
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 ProtoBase Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,16 @@
1
+ include LICENSE
2
+ include README.md
3
+ include CHANGELOG.md
4
+ include PYPI_UPLOAD.md
5
+
6
+ # Exclude non-runtime large artifacts
7
+ prune examples
8
+ prune docs/build
9
+ prune cloud_page_cache
10
+ prune benchmark_data_small
11
+
12
+ # Exclude benchmark outputs and misc
13
+ global-exclude *.pyc __pycache__ *.pyo *.DS_Store
14
+ global-exclude *.json
15
+
16
+ recursive-include proto_db *.py
@@ -0,0 +1,315 @@
1
+ Metadata-Version: 2.4
2
+ Name: proto_db
3
+ Version: 2.0.0rc3
4
+ Summary: proto_db is an embedded, transactional, object-oriented database for Python.
5
+ Author-email: Gustavo Marino <gamarino@numaes.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2023 ProtoBase Team
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+ Project-URL: Homepage, https://github.com/gamarino/ProtoDB
28
+ Project-URL: Bug Tracker, https://github.com/gamarino/ProtoDB/issues
29
+ Keywords: database,embedded,transactional,object-oriented,ACID,ORM-free,NoSQL
30
+ Classifier: Development Status :: 3 - Alpha
31
+ Classifier: Intended Audience :: Developers
32
+ Classifier: License :: OSI Approved :: MIT License
33
+ Classifier: Operating System :: OS Independent
34
+ Classifier: Programming Language :: Python :: 3.12
35
+ Classifier: Programming Language :: Python :: 3.13
36
+ Classifier: Programming Language :: Python :: 3.14
37
+ Classifier: Programming Language :: Python :: Free Threading
38
+ Classifier: Topic :: Database
39
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
40
+ Requires-Python: >=3.12
41
+ Description-Content-Type: text/markdown
42
+ License-File: LICENSE
43
+ Requires-Dist: msgpack>=1.2.0
44
+ Provides-Extra: parquet
45
+ Requires-Dist: pyarrow; extra == "parquet"
46
+ Provides-Extra: vectors
47
+ Requires-Dist: numpy; extra == "vectors"
48
+ Provides-Extra: hnsw
49
+ Requires-Dist: numpy; extra == "hnsw"
50
+ Requires-Dist: hnswlib; extra == "hnsw"
51
+ Provides-Extra: s3
52
+ Requires-Dist: boto3; extra == "s3"
53
+ Provides-Extra: gcs
54
+ Requires-Dist: google-cloud-storage; extra == "gcs"
55
+ Provides-Extra: dev
56
+ Requires-Dist: pytest; extra == "dev"
57
+ Requires-Dist: pytest-timeout; extra == "dev"
58
+ Requires-Dist: pytest-cov; extra == "dev"
59
+ Requires-Dist: hypothesis; extra == "dev"
60
+ Requires-Dist: sphinx; extra == "dev"
61
+ Requires-Dist: build; extra == "dev"
62
+ Requires-Dist: twine; extra == "dev"
63
+ Dynamic: license-file
64
+
65
+ # proto_db
66
+
67
+ [![PyPI version](https://img.shields.io/pypi/v/proto_db.svg)](https://pypi.org/project/proto_db/)
68
+ [![Python Version](https://img.shields.io/pypi/pyversions/proto_db.svg)](https://pypi.org/project/proto_db/)
69
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
70
+ [![Project Status](https://img.shields.io/badge/status-2.0.0.dev0-orange.svg)](#status)
71
+
72
+ `proto_db` is an embedded, transactional, object-oriented database for Python. It stores immutable,
73
+ persistent collections with secondary indexes, runs queries through an index-aware planner, and
74
+ commits serializable transactions per root, all in-process, without a separate server.
75
+
76
+ ### Status
77
+
78
+ `2.0.0.dev0`: the 2.0 rebuild, unreleased (design: `docs/superpowers/specs/2026-11-01-protodb-v2-design.md`,
79
+ decisions: `docs/decisions.md`). The 0.1 implementation has been removed; the API below is the 2.0 API.
80
+ Everything this page lists is implemented and covered by the test suite, which runs on CPython 3.14t
81
+ (free-threaded) and with the GIL. The cluster is new: it is documented, tested and measured in this
82
+ repository and has no production history. Open defects are listed where they apply (see the cluster item
83
+ under Features). The performance suite has run (results by workload in `docs/source/performance.rst`);
84
+ the API may still change before 2.0.0 is released.
85
+
86
+ ### A note on names
87
+
88
+ The product is named after its import. The repository has its own name, and the sibling
89
+ Smalltalk project keeps its name too:
90
+
91
+ | | Name |
92
+ |---|---|
93
+ | The product (the library, the database) and its documentation | **`proto_db`** — `pip install proto_db`, `import proto_db` |
94
+ | The repository | **protoBase** — `github.com/gamarino/ProtoDB` |
95
+ | The Smalltalk sibling project | **protoBaseST** |
96
+
97
+ Earlier documentation called the product *ProtoDB* or *ProtoBase*; both are the same
98
+ product, now named `proto_db` everywhere so that the name matches the import. The old
99
+ spelling survives in `ProtoBaseException`, a deprecated alias of `ProtoDBError` (the base
100
+ of the exception hierarchy) that is removed in 2.1.
101
+
102
+ ## Design principles
103
+
104
+ `proto_db` is built from a small set of principles. Every design decision cites them, and
105
+ a change that contradicts one is treated as conceptual debt, resolved before it is built.
106
+ If you read only one thing before the code, read
107
+ [docs/design-principles.md](docs/design-principles.md).
108
+
109
+ 1. **Everything is immutable.** A pointer names one content forever. History is never lost,
110
+ caches keyed by pointer never go stale, and snapshots are stable.
111
+ 2. **A transaction is an explicit object.** It is not bound to a thread, and its commit
112
+ concerns only its own reads and writes.
113
+ 3. **Each node owns its work.** Commits are built where they happen, local contention is
114
+ resolved first, and replication is bounded.
115
+ 4. **Contention is resolved among the processes, never by the storage.** The storage only
116
+ fences, and the object store is written after the commit.
117
+ 5. **Safety never depends on time.** Timers drive liveness only, and delays can cause only
118
+ conservative failures.
119
+ 6. **Known techniques, honestly measured.** Established mechanisms are adopted and cited, and
120
+ every performance claim carries its measurement.
121
+ 7. **Correct before fast.**
122
+
123
+ ## Features
124
+
125
+ - **Transactions**: snapshot isolation, read sets checked at commit (a conflict raises
126
+ `ProtoConflictError`; `Database.run` retries), per-key merges of collections written without being
127
+ read, named mutable cells (`MutableObject`), a version history per database.
128
+ - **Values**: one value model with a total order across types (`1 == 1.0 == Decimal(1)`, `True != 1`),
129
+ used identically by collections, indexes and queries.
130
+ - **Collections**: `List`, `Dictionary` (key-ordered), `HashDictionary`, `RepeatedKeysDictionary`,
131
+ `Set`, `CountedSet` and `DBObject` records, all immutable and persistent (every change returns a new
132
+ version; unchanged parts are shared).
133
+ - **Indexes on every collection**: `EqualityIndex`, `RangeIndex`, `VectorIndex` (exact, IVF-Flat, or
134
+ HNSW when `hnswlib` is installed), and an `Index` base class for custom indexes. Indexes are
135
+ persisted with the collection and maintained by every mutator.
136
+ - **Queries**: a LINQ front end (`from_collection`, `F`) and expression terms over physical plans; the
137
+ planner picks indexes by cost, never drops a predicate, and `explain()` shows the plan that runs.
138
+ Python lambdas are allowed and appear as an explicit `PythonFilter` node.
139
+ - **Storage**: `MemoryStorage`; `FileStorage` (append-only segments, CRC-checked records, `fsync` per
140
+ commit by default); `CloudStorage` over S3 or Google Cloud Storage; `TieredStorage` and a cluster
141
+ coordinator for several writer processes. The cluster is new, with no production history, and its
142
+ guarantees assume that the storage devices honour `fsync`:
143
+ - *Power loss with the disks intact* (D50, Task 8.3f): a node accepts a root with ONE synced
144
+ acceptor-log entry that also holds the root's records, in every durability mode, so an acknowledged
145
+ commit survives power loss of any set of nodes.
146
+ - *Lease and ballots* (D46, Task 8.3e): every node syncs its acceptor log before replying, in every
147
+ durability mode, so across process crashes and power losses no term is granted twice, provided at
148
+ most one acceptor log is lost at a time (R48).
149
+ - *Loss of disks* (D54, Task 8.3g): replication is bounded. Three voters (configurable) order roots, the
150
+ other members are learners, and a commit's records are synced on the committer plus `f` data replicas
151
+ (one, with three voters), not on every member; the others keep them only in a bounded cache and fetch on
152
+ demand what they miss. Losing `f + 1` voters stops writes while learners keep serving reads; losing every
153
+ durable copy of a commit's records (`f + 1` disks) before the persister uploads them to the object store
154
+ loses that data, as no re-replication runs.
155
+ - *Fairness*: two to four contending writer processes take the lease in turn; at eight, and for hot-key
156
+ read-modify-write transactions, fairness is not yet guaranteed (open defect D-LIVE-2).
157
+ - *Reads*: a local read is a committed snapshot that may lag; a linearizable read is a barrier commit,
158
+ which adds a permanent version per read (see the cluster tutorial).
159
+ - **Free-threading**: tested on CPython 3.14t (GIL disabled) and with the GIL; `parallel_scan` and
160
+ `parallel_filter` scale on free-threaded builds.
161
+ - **Interop**: export to Apache Arrow and Parquet (`to_arrow`, `to_parquet`).
162
+
163
+ ## Installation
164
+
165
+ Requirements: Python 3.12+.
166
+
167
+ ```bash
168
+ pip install --pre proto_db
169
+ ```
170
+
171
+ Until 2.0.0 is final, the published versions are release candidates, so `pip` needs `--pre` (or an
172
+ explicit version such as `proto_db==2.0.0rc3`). Start with a single-node space: under heavy load the
173
+ cluster reports outcomes as unknown, or times out, more often than it should (see the CHANGELOG's known
174
+ limitations).
175
+
176
+ Optional extras:
177
+
178
+ | Extra | Installs | Needed for |
179
+ |---|---|---|
180
+ | `vectors` | numpy | `VectorIndex` |
181
+ | `hnsw` | numpy, hnswlib | the HNSW vector backend (note: hnswlib re-enables the GIL on 3.14t) |
182
+ | `parquet` | pyarrow | `to_arrow`, `to_parquet`, `scan_parquet` |
183
+ | `s3` / `gcs` | boto3 / google-cloud-storage | `CloudStorage` with `S3ObjectStore` / `GCSObjectStore` |
184
+ | `dev` | pytest, hypothesis, sphinx, build, twine, ... | development |
185
+
186
+ `import proto_db` imports none of them and starts no thread; each optional package is imported when
187
+ the feature that needs it is first used.
188
+
189
+ ## Quick start
190
+
191
+ ```python
192
+ from proto_db import MemoryStorage, ObjectSpace
193
+
194
+ space = ObjectSpace(MemoryStorage())
195
+ db = space.new_database("example")
196
+
197
+ # A transaction commits when the block ends without an exception.
198
+ with db.new_transaction() as tr:
199
+ numbers = tr.new_list().append_last(1).append_last(2).append_last(3)
200
+ tr.set_root_object("numbers", numbers)
201
+ tr.set_root_object("ada", tr.new_object(name="Ada Lovelace", born=1815))
202
+
203
+ tr = db.new_transaction()
204
+ print(list(tr.get_root_object("numbers"))) # [1, 2, 3]
205
+ print(tr.get_root_object("ada").name) # Ada Lovelace
206
+ tr.abort()
207
+ space.close()
208
+ ```
209
+
210
+ Use `FileStorage("/path/to/space")` instead of `MemoryStorage()` to persist the space;
211
+ `durability="fsync"` (the default), `"flush"` or `"none"` selects what a returned commit survives.
212
+
213
+ ## Indexes and queries
214
+
215
+ ```python
216
+ from proto_db import EqualityIndex, F, MemoryStorage, ObjectSpace, RangeIndex, from_collection
217
+
218
+ space = ObjectSpace(MemoryStorage())
219
+ db = space.new_database("people")
220
+ with db.new_transaction() as tr:
221
+ people = tr.new_dictionary() \
222
+ .add_index(EqualityIndex("by_city", "city")) \
223
+ .add_index(RangeIndex("by_age", "age"))
224
+ for key, city, age in [("ann", "Paris", 25), ("bob", "Rome", 40), ("cid", "Paris", 33)]:
225
+ people = people.set_at(key, tr.new_object(name=key, city=city, age=age))
226
+ tr.set_root_object("people", people)
227
+
228
+ tr = db.new_transaction()
229
+ people = tr.get_root_object("people")
230
+ query = from_collection(people).where((F.city == "Paris") & (F.age > 30))
231
+ print(query.explain()["plan"]) # IndexLookup or IndexRange, chosen by cost
232
+ print([p.name for p in query.to_list()]) # ['cid']
233
+ tr.abort()
234
+ space.close()
235
+ ```
236
+
237
+ The LINQ front end works over plain iterables too:
238
+
239
+ ```python
240
+ from proto_db import Agg, F, from_collection
241
+
242
+ orders = [{"customer": 1, "total": 10.0}, {"customer": 2, "total": 7.5}, {"customer": 1, "total": 2.5}]
243
+ totals = (from_collection(orders)
244
+ .group_by(F.customer, {"customer": Agg.key(), "spent": Agg.sum(F.total)})
245
+ .order_by(F.spent, descending=True))
246
+ print(totals.to_list()) # [{'customer': 1, 'spent': 12.5}, {'customer': 2, 'spent': 7.5}]
247
+ ```
248
+
249
+ ## Concurrency
250
+
251
+ ```python
252
+ import threading
253
+ from proto_db import MemoryStorage, ObjectSpace
254
+
255
+ space = ObjectSpace(MemoryStorage())
256
+ db = space.new_database("counters")
257
+ with db.new_transaction() as tr:
258
+ tr.set_root_object("hits", 0)
259
+
260
+ def increment(tr):
261
+ tr.set_root_object("hits", tr.get_root_object("hits") + 1)
262
+
263
+ # A read-modify-write that loses the race raises ProtoConflictError; run() retries it.
264
+ threads = [threading.Thread(target=lambda: [db.run(increment, retries=100) for _ in range(50)])
265
+ for _ in range(4)]
266
+ for t in threads:
267
+ t.start()
268
+ for t in threads:
269
+ t.join()
270
+
271
+ tr = db.new_transaction()
272
+ print(tr.get_root_object("hits")) # 200
273
+ tr.abort()
274
+ space.close()
275
+ ```
276
+
277
+ ## Examples
278
+
279
+ Runnable, self-checking scripts live in `examples/` (see `examples/README.md`): getting started,
280
+ file storage, indexed queries with `explain()`, LINQ queries, transactions under concurrency, and
281
+ vector search. Each one runs in the test suite, as do the Python snippets of this README and every code
282
+ block of the tutorials.
283
+
284
+ ## Documentation
285
+
286
+ The Sphinx site is under `docs/source` (build it with `python -m sphinx -W -b html docs/source docs/build/html`):
287
+
288
+ - **Tutorials**, each executed by the test suite: getting started, modelling data, indexing and querying,
289
+ concurrency and retries, vector search, writing a custom index, operations (durability, inspection,
290
+ backup), an ERP example ("as of" queries, an audit with no audit table, stable paginated reports), a CAD
291
+ example (reusing decoded objects across transactions) and a cluster tutorial (local against linearizable
292
+ reads).
293
+ - **Design**: concepts, architecture, the concurrency and isolation model, the index framework, the query
294
+ planner, durability and recovery, objects across transactions, the storage format.
295
+ - **Reference**: the API (generated from the docstrings of the `proto_db` package, whose module docstring
296
+ lists the public names) and the performance results by workload profile.
297
+ - Design specification: `docs/superpowers/specs/2026-11-01-protodb-v2-design.md`; decision log:
298
+ `docs/decisions.md`; principles: `docs/design-principles.md`.
299
+
300
+ The 0.1 pages are kept for reference in `docs/legacy/`.
301
+
302
+ ## Testing
303
+
304
+ ```bash
305
+ pip install -e ".[dev]"
306
+ python -m pytest # runs tests/ (pytest's testpaths)
307
+ ```
308
+
309
+ The suite has unit, contract, property-based (Hypothesis), integration, crash (fault injection
310
+ and SIGKILL), concurrency and static-check layers. The primary target is the free-threaded build:
311
+ `python3.14t -m pytest`, and once more with `-X gil=1`.
312
+
313
+ ## License
314
+
315
+ MIT License. See LICENSE for details.