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.
- proto_db-2.0.0rc3/CHANGELOG.md +224 -0
- proto_db-2.0.0rc3/LICENSE +21 -0
- proto_db-2.0.0rc3/MANIFEST.in +16 -0
- proto_db-2.0.0rc3/PKG-INFO +315 -0
- proto_db-2.0.0rc3/PYPI_UPLOAD.md +128 -0
- proto_db-2.0.0rc3/README.md +251 -0
- proto_db-2.0.0rc3/proto_db/__init__.py +141 -0
- proto_db-2.0.0rc3/proto_db/cluster/__init__.py +1 -0
- proto_db-2.0.0rc3/proto_db/cluster/coordinator.py +4279 -0
- proto_db-2.0.0rc3/proto_db/cluster/lease.py +429 -0
- proto_db-2.0.0rc3/proto_db/cluster/persister.py +493 -0
- proto_db-2.0.0rc3/proto_db/cluster/protocol.py +408 -0
- proto_db-2.0.0rc3/proto_db/cluster/rootlog.py +723 -0
- proto_db-2.0.0rc3/proto_db/cluster/transport.py +565 -0
- proto_db-2.0.0rc3/proto_db/collections/__init__.py +1 -0
- proto_db-2.0.0rc3/proto_db/collections/avl.py +878 -0
- proto_db-2.0.0rc3/proto_db/collections/base.py +631 -0
- proto_db-2.0.0rc3/proto_db/collections/dictionaries.py +429 -0
- proto_db-2.0.0rc3/proto_db/collections/hash_dictionaries.py +187 -0
- proto_db-2.0.0rc3/proto_db/collections/lists.py +160 -0
- proto_db-2.0.0rc3/proto_db/collections/sets.py +411 -0
- proto_db-2.0.0rc3/proto_db/core/__init__.py +5 -0
- proto_db-2.0.0rc3/proto_db/core/atom.py +1233 -0
- proto_db-2.0.0rc3/proto_db/core/codec.py +735 -0
- proto_db-2.0.0rc3/proto_db/core/dbobject.py +125 -0
- proto_db-2.0.0rc3/proto_db/errors.py +164 -0
- proto_db-2.0.0rc3/proto_db/indexing/__init__.py +32 -0
- proto_db-2.0.0rc3/proto_db/indexing/base.py +338 -0
- proto_db-2.0.0rc3/proto_db/indexing/equality.py +35 -0
- proto_db-2.0.0rc3/proto_db/indexing/keyed.py +737 -0
- proto_db-2.0.0rc3/proto_db/indexing/keyspec.py +265 -0
- proto_db-2.0.0rc3/proto_db/indexing/range.py +58 -0
- proto_db-2.0.0rc3/proto_db/indexing/vector.py +429 -0
- proto_db-2.0.0rc3/proto_db/indexing/vector_backends.py +832 -0
- proto_db-2.0.0rc3/proto_db/interop/__init__.py +4 -0
- proto_db-2.0.0rc3/proto_db/interop/arrow.py +308 -0
- proto_db-2.0.0rc3/proto_db/query/__init__.py +15 -0
- proto_db-2.0.0rc3/proto_db/query/cache.py +533 -0
- proto_db-2.0.0rc3/proto_db/query/expressions.py +357 -0
- proto_db-2.0.0rc3/proto_db/query/linq.py +1179 -0
- proto_db-2.0.0rc3/proto_db/query/pager.py +294 -0
- proto_db-2.0.0rc3/proto_db/query/planner.py +343 -0
- proto_db-2.0.0rc3/proto_db/query/plans.py +1099 -0
- proto_db-2.0.0rc3/proto_db/query/terms.py +333 -0
- proto_db-2.0.0rc3/proto_db/runtime/__init__.py +5 -0
- proto_db-2.0.0rc3/proto_db/runtime/fsm.py +446 -0
- proto_db-2.0.0rc3/proto_db/runtime/parallel.py +458 -0
- proto_db-2.0.0rc3/proto_db/storage/__init__.py +1 -0
- proto_db-2.0.0rc3/proto_db/storage/base.py +125 -0
- proto_db-2.0.0rc3/proto_db/storage/cache.py +148 -0
- proto_db-2.0.0rc3/proto_db/storage/cloud.py +894 -0
- proto_db-2.0.0rc3/proto_db/storage/crc.py +114 -0
- proto_db-2.0.0rc3/proto_db/storage/file.py +842 -0
- proto_db-2.0.0rc3/proto_db/storage/format.py +228 -0
- proto_db-2.0.0rc3/proto_db/storage/logfile.py +338 -0
- proto_db-2.0.0rc3/proto_db/storage/memory.py +135 -0
- proto_db-2.0.0rc3/proto_db/storage/ownerlock.py +221 -0
- proto_db-2.0.0rc3/proto_db/storage/tiered.py +2353 -0
- proto_db-2.0.0rc3/proto_db/tools/__init__.py +1 -0
- proto_db-2.0.0rc3/proto_db/tools/notation.py +182 -0
- proto_db-2.0.0rc3/proto_db/tools/verify.py +329 -0
- proto_db-2.0.0rc3/proto_db/transactions.py +2302 -0
- proto_db-2.0.0rc3/proto_db/values.py +975 -0
- proto_db-2.0.0rc3/proto_db.egg-info/PKG-INFO +315 -0
- proto_db-2.0.0rc3/proto_db.egg-info/SOURCES.txt +68 -0
- proto_db-2.0.0rc3/proto_db.egg-info/dependency_links.txt +1 -0
- proto_db-2.0.0rc3/proto_db.egg-info/requires.txt +26 -0
- proto_db-2.0.0rc3/proto_db.egg-info/top_level.txt +1 -0
- proto_db-2.0.0rc3/pyproject.toml +58 -0
- 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
|
+
[](https://pypi.org/project/proto_db/)
|
|
68
|
+
[](https://pypi.org/project/proto_db/)
|
|
69
|
+
[](LICENSE)
|
|
70
|
+
[](#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.
|