numpy-vector-store 0.3.2__tar.gz → 0.4.0__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 (20) hide show
  1. {numpy_vector_store-0.3.2 → numpy_vector_store-0.4.0}/.github/workflows/checks.yml +5 -6
  2. numpy_vector_store-0.4.0/CHANGELOG.md +160 -0
  3. numpy_vector_store-0.4.0/MIGRATION.md +169 -0
  4. {numpy_vector_store-0.3.2 → numpy_vector_store-0.4.0}/PKG-INFO +88 -36
  5. {numpy_vector_store-0.3.2 → numpy_vector_store-0.4.0}/README.md +85 -32
  6. {numpy_vector_store-0.3.2 → numpy_vector_store-0.4.0}/ROADMAP.md +101 -16
  7. {numpy_vector_store-0.3.2 → numpy_vector_store-0.4.0}/pyproject.toml +4 -5
  8. {numpy_vector_store-0.3.2 → numpy_vector_store-0.4.0}/src/numpy_vector_store/__init__.py +1 -1
  9. {numpy_vector_store-0.3.2 → numpy_vector_store-0.4.0}/src/numpy_vector_store/vector_store.py +251 -34
  10. {numpy_vector_store-0.3.2 → numpy_vector_store-0.4.0}/tests/test_vector_store.py +538 -10
  11. {numpy_vector_store-0.3.2 → numpy_vector_store-0.4.0}/uv.lock +4 -110
  12. numpy_vector_store-0.3.2/CHANGELOG.md +0 -73
  13. {numpy_vector_store-0.3.2 → numpy_vector_store-0.4.0}/.github/FUNDING.yml +0 -0
  14. {numpy_vector_store-0.3.2 → numpy_vector_store-0.4.0}/.github/workflows/publish-pypi.yml +0 -0
  15. {numpy_vector_store-0.3.2 → numpy_vector_store-0.4.0}/.github/workflows/publish-testpypi.yml +0 -0
  16. {numpy_vector_store-0.3.2 → numpy_vector_store-0.4.0}/.gitignore +0 -0
  17. {numpy_vector_store-0.3.2 → numpy_vector_store-0.4.0}/LICENSE +0 -0
  18. {numpy_vector_store-0.3.2 → numpy_vector_store-0.4.0}/justfile +0 -0
  19. {numpy_vector_store-0.3.2 → numpy_vector_store-0.4.0}/src/numpy_vector_store/py.typed +0 -0
  20. {numpy_vector_store-0.3.2 → numpy_vector_store-0.4.0}/tests/__init__.py +0 -0
@@ -55,7 +55,6 @@ jobs:
55
55
  fail-fast: false
56
56
  matrix:
57
57
  python-version:
58
- - "3.10"
59
58
  - "3.11"
60
59
  - "3.12"
61
60
  - "3.13"
@@ -84,7 +83,7 @@ jobs:
84
83
  run: uv run --locked pytest
85
84
 
86
85
  minimum-numpy:
87
- name: Test minimum NumPy on Python 3.10
86
+ name: Test minimum NumPy on Python 3.11
88
87
  runs-on: ubuntu-latest
89
88
 
90
89
  steps:
@@ -94,13 +93,13 @@ jobs:
94
93
  - name: Set up Python
95
94
  uses: actions/setup-python@v6
96
95
  with:
97
- python-version: "3.10"
96
+ python-version: "3.11"
98
97
 
99
98
  - name: Set up uv
100
99
  uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
101
100
  with:
102
101
  version: "0.11.32"
103
- python-version: "3.10"
102
+ python-version: "3.11"
104
103
  enable-cache: true
105
104
 
106
105
  - name: Run tests with the minimum NumPy version
@@ -108,8 +107,8 @@ jobs:
108
107
  uv run
109
108
  --isolated
110
109
  --no-project
111
- --python 3.10
110
+ --python 3.11
112
111
  --with-editable .
113
- --with numpy==1.21.3
112
+ --with numpy==1.23.2
114
113
  --with pytest==8.4.2
115
114
  python -m pytest
@@ -0,0 +1,160 @@
1
+ # Changelog
2
+
3
+ This changelog records user-visible changes to NumPy Vector Store. Earlier
4
+ release notes remain available on the
5
+ [GitHub releases page](https://github.com/tvanreenen/numpy-vector-store/releases).
6
+
7
+ ## 0.4.0 - 2026-08-09
8
+
9
+ This release makes persistence explicit, self-describing, and safer to update.
10
+ The earlier archive format stored vectors and metadata but omitted the settings
11
+ needed to interpret those vectors correctly. Version 0.4 records that
12
+ configuration in every new archive and introduces a lifecycle that clearly
13
+ separates creating, opening, saving, and reloading a store.
14
+
15
+ ### Explicit persistence lifecycle
16
+
17
+ - Add `VectorStore.open(path)` to construct a store from a versioned archive.
18
+ The archive supplies its own dimensions and normalization mode, so callers no
19
+ longer need to repeat configuration that may be wrong.
20
+ - Let `save(path)` perform the first save or a Save As operation and bind that
21
+ destination. Later `save()` calls update the bound archive.
22
+ - Add `reload()` as a deliberate refresh from disk. It always attempts to read
23
+ the bound archive and leaves current in-memory state unchanged if reading or
24
+ validation fails.
25
+ - Keep creating a new in-memory store separate from opening one on disk. This
26
+ makes file access and persistence boundaries visible in application code.
27
+
28
+ ### Versioned, self-describing archives
29
+
30
+ - Write archive format version 1 with `format_version`, `dimensions`,
31
+ `normalize`, `vectors`, and `metadata` fields.
32
+ - Validate the complete archive before changing live store state, including
33
+ field names, scalar configuration, array dtypes and shapes, row counts,
34
+ finite vector values, and normalized-store zero-vector rules.
35
+ - Reject unsupported format versions and malformed archives clearly rather
36
+ than inferring missing configuration or partially applying valid fields.
37
+ - Continue preserving each metadata item as one opaque row payload.
38
+
39
+ ### Safer archive replacement
40
+
41
+ - Write each save to a uniquely named temporary archive in the destination
42
+ directory, close it, and then replace the destination with `os.replace`.
43
+ - Preserve the previous complete archive when writing or replacement fails and
44
+ clean up temporary files after failures.
45
+ - Bind a new Save As destination only after its archive has been written
46
+ successfully.
47
+
48
+ This provides an atomic visibility boundary: a reader opening the destination
49
+ sees the previous complete archive or the new complete archive instead of a
50
+ partially written file. It does not provide file locking, multi-writer
51
+ coordination, or a universal power-loss durability guarantee.
52
+
53
+ ### Short migration window
54
+
55
+ - Keep constructor `file_path=`, instance `load()`, and direct context-manager
56
+ persistence for version 0.4 with `FutureWarning`. They will be removed in
57
+ 0.5.
58
+ - Make the deprecated context manager save only after a successful block. If
59
+ the block raises, it does not save or suppress the exception.
60
+ - Keep a configuration-aware reader for older archives containing only
61
+ `vectors` and `metadata`. Loading one warns, and its next save rewrites it as
62
+ format version 1.
63
+ - Intentionally make `open()` reject an unversioned archive because that file
64
+ cannot report its original dimensions or normalization semantics.
65
+ - Add a dedicated [persistence migration guide](MIGRATION.md) with side-by-side
66
+ API replacements and one-time legacy archive conversion instructions.
67
+
68
+ The legacy API and unversioned archive reader are removed in 0.5. Applications
69
+ should migrate an old archive once with 0.4 or recreate it from source data;
70
+ indefinite compatibility with the incomplete two-array format is not planned.
71
+
72
+ ### Runtime compatibility
73
+
74
+ - Support Python 3.11 through 3.14. Python 3.10 remains supported by the 0.3
75
+ release series but is not supported by 0.4.
76
+ - Raise the minimum NumPy version from 1.21.3 to 1.23.2, the earliest release
77
+ that supports Python 3.11.
78
+ - Exercise every supported Python version in CI and test NumPy 1.23.2 in a
79
+ dedicated minimum-dependency job.
80
+
81
+ ### Upgrade notes and boundaries
82
+
83
+ - Search, insertion, retrieval, clearing, normalization, and metadata behavior
84
+ are unchanged from 0.3.2.
85
+ - Code using `VectorStore.open()`, `save(path)`, `save()`, and `reload()` is on
86
+ the persistence API intended for 0.5.
87
+ - Code using a transitional entry point continues to work in 0.4 but emits a
88
+ warning so the required 0.5 migration is visible during testing.
89
+ - Metadata still uses NumPy's pickle-backed object-array loading. Only open
90
+ archives produced by your application or another trusted source.
91
+ - Mutable public state, repeated-add performance, deterministic tie ordering,
92
+ and a formal thread-safety contract remain planned for later releases.
93
+
94
+ ## 0.3.2 - 2026-07-27
95
+
96
+ This reliability and performance patch makes existing vector storage, search,
97
+ metadata, and persistence behavior safer and more predictable. It does not
98
+ intentionally break valid existing usage or change the `.npz` archive format.
99
+
100
+ ### Numerical reliability
101
+
102
+ - Reject vectors, queries, and search thresholds that contain non-finite values
103
+ or cannot remain finite when represented as `float32`. Invalid input now
104
+ fails before it can corrupt stored state or ranking.
105
+ - Calculate norms and raw metric intermediates with `float64` where `float32`
106
+ could overflow or underflow. Large and very small finite vectors can now be
107
+ normalized and compared reliably.
108
+ - Allow zero vectors in stores created with `normalize=False`, where they are
109
+ valid for dot-product and Euclidean search.
110
+ - Raise a clear error if a raw cosine search includes a zero vector, because
111
+ cosine similarity is undefined for that row.
112
+ - Avoid duplicate full-size `float64` buffers when calculating raw Euclidean
113
+ distance.
114
+
115
+ ### Persistence
116
+
117
+ - Resolve a path without an `.npz` suffix to the same archive for both saving
118
+ and loading. For example, `file_path="vectors"` consistently uses
119
+ `vectors.npz`.
120
+ - Allow `load()` to be retried when the persistence file did not exist during
121
+ an earlier attempt.
122
+ - Reset load state in `clear()` so a subsequent explicit `load()` can restore
123
+ the saved rows.
124
+ - Keep repeated `load()` calls idempotent after a successful load.
125
+
126
+ ### Metadata
127
+
128
+ - Preserve each item in the outer metadata sequence as one opaque row payload.
129
+ Tuples and lists are no longer mistaken for extra NumPy array dimensions.
130
+ - Support dictionary, dataclass, tuple, list, string, integer, and other scalar
131
+ payloads consistently through insertion, search results, saving, and loading.
132
+ - Continue rejecting explicitly multidimensional NumPy metadata arrays rather
133
+ than silently flattening ambiguous input.
134
+
135
+ ### Search memory use
136
+
137
+ - Search the stored vector matrix directly when `within_rows` is omitted,
138
+ avoiding an unnecessary full-matrix copy on every unfiltered query.
139
+ - Preserve original store indexes and metadata when `within_rows` selects a
140
+ filtered subset.
141
+ - Document that filtered searches allocate a temporary matrix proportional to
142
+ the selected row count and vector dimensions.
143
+
144
+ ### Compatibility and validation
145
+
146
+ - Test Python 3.10 through 3.14 in GitHub Actions.
147
+ - Test the minimum supported NumPy version in a dedicated Python 3.10 job.
148
+ - Raise the minimum NumPy requirement from 1.20 to 1.21.3 so it is compatible
149
+ with the oldest supported Python version.
150
+ - Require linting, formatting, type checking, and the full Python test matrix
151
+ before publishing to PyPI.
152
+
153
+ ### Upgrade notes
154
+
155
+ - No public method signatures or persisted field names changed.
156
+ - Existing trusted `.npz` archives with `vectors` and `metadata` remain
157
+ readable.
158
+ - Environments using NumPy 1.20 must upgrade to NumPy 1.21.3 or newer.
159
+ - Inputs that previously produced `nan`, `inf`, or unreliable rankings now
160
+ raise `ValueError` instead.
@@ -0,0 +1,169 @@
1
+ # Persistence migration guide
2
+
3
+ Version 0.4 introduces a persistence lifecycle that separates creating a new
4
+ store, opening an existing archive, saving, and deliberately refreshing from
5
+ disk. It also provides a one-release bridge for applications and archives using
6
+ the earlier API.
7
+
8
+ The transition is intentionally short. Version 0.4 emits `FutureWarning` for
9
+ constructor `file_path=`, instance `load()`, direct context-manager use, and
10
+ unversioned archives. Version 0.5 removes those entry points and the
11
+ unversioned archive reader.
12
+
13
+ ## Creating and saving a new store
14
+
15
+ Previously, the destination was supplied while constructing the store:
16
+
17
+ ```python
18
+ store = VectorStore(dimensions=1536, file_path="vectors.npz")
19
+ store.add(vectors, metadata)
20
+ store.save()
21
+ ```
22
+
23
+ Create the in-memory store first, then bind its destination with the first
24
+ save:
25
+
26
+ ```python
27
+ store = VectorStore(dimensions=1536)
28
+ store.add(vectors, metadata)
29
+ store.save("vectors.npz")
30
+ ```
31
+
32
+ Later `save()` calls update the bound archive. Supplying another path performs
33
+ a Save As operation and binds the new destination after the write succeeds.
34
+ Calling `save()` before a store is bound raises `ValueError` rather than
35
+ silently leaving the data unsaved.
36
+
37
+ ## Opening an existing archive
38
+
39
+ The old API required callers to repeat configuration that should belong to the
40
+ archive:
41
+
42
+ ```python
43
+ store = VectorStore(
44
+ dimensions=1536,
45
+ file_path="vectors.npz",
46
+ normalize=True,
47
+ )
48
+ store.load()
49
+ ```
50
+
51
+ Open a version 1 archive directly:
52
+
53
+ ```python
54
+ store = VectorStore.open("vectors.npz")
55
+ ```
56
+
57
+ `open()` restores `dimensions` and `normalize` from the archive, validates its
58
+ contents, loads its rows, and binds its path. Applications no longer need to
59
+ keep archive configuration separately or risk loading the same vectors with
60
+ different semantics.
61
+
62
+ The generic parameter still describes application metadata. It can be kept
63
+ when useful:
64
+
65
+ ```python
66
+ from dataclasses import dataclass
67
+
68
+
69
+ @dataclass(frozen=True)
70
+ class ChunkMetadata:
71
+ source: str
72
+ chunk_index: int
73
+
74
+
75
+ store = VectorStore[ChunkMetadata].open("vectors.npz")
76
+ ```
77
+
78
+ `ChunkMetadata` is an example application type, not a class provided by this
79
+ library.
80
+
81
+ ## Refreshing from disk
82
+
83
+ Use `reload()` when another process may have changed the bound archive and the
84
+ current in-memory changes should be discarded:
85
+
86
+ ```python
87
+ store = VectorStore.open("vectors.npz")
88
+
89
+ # Later, after the file may have changed:
90
+ store.reload()
91
+ ```
92
+
93
+ Unlike transitional `load()`, `reload()` always attempts to read. It raises if
94
+ the store is unbound, the file is missing, or the archive is invalid. A failed
95
+ reload leaves the current in-memory vectors and metadata unchanged.
96
+
97
+ ## Replacing context-manager persistence
98
+
99
+ The earlier context manager saved automatically on exit:
100
+
101
+ ```python
102
+ with VectorStore(dimensions=1536, file_path="vectors.npz") as store:
103
+ store.add(vectors, metadata)
104
+ ```
105
+
106
+ Use an explicit save after the work succeeds:
107
+
108
+ ```python
109
+ store = VectorStore(dimensions=1536)
110
+ store.add(vectors, metadata)
111
+ store.save("vectors.npz")
112
+ ```
113
+
114
+ Normal Python control flow already prevents the final line from running if
115
+ `add()` raises. The persistence boundary is visible, and readers do not need to
116
+ remember an implicit exit side effect.
117
+
118
+ During 0.4, the deprecated context manager saves only after normal completion.
119
+ It does not save while an exception is propagating and does not suppress the
120
+ exception. There is no planned replacement autosave context manager.
121
+
122
+ ## Migrating an archive created before 0.4
123
+
124
+ Older archives contain only `vectors` and `metadata`. They do not record their
125
+ dimensions or whether vectors use normalized or raw semantics, so `open()`
126
+ cannot construct a correct store from them.
127
+
128
+ Use the 0.4 compatibility API once with the archive's original configuration:
129
+
130
+ ```python
131
+ legacy = VectorStore(
132
+ dimensions=1536,
133
+ file_path="legacy-vectors.npz",
134
+ normalize=True,
135
+ )
136
+ legacy.load()
137
+ legacy.save()
138
+ ```
139
+
140
+ This code emits transition warnings by design. The final `save()` rewrites the
141
+ archive as format version 1 with `format_version`, `dimensions`, `normalize`,
142
+ `vectors`, and `metadata`. It can then use the preferred API:
143
+
144
+ ```python
145
+ store = VectorStore.open("legacy-vectors.npz")
146
+ ```
147
+
148
+ Applications that can recreate archives from source vectors and metadata may
149
+ choose to do that instead. The unversioned reader is removed in 0.5 rather than
150
+ maintained as a long-term compatibility format.
151
+
152
+ ## Removal schedule
153
+
154
+ | Transitional behavior | 0.4 | 0.5 |
155
+ |---|---|---|
156
+ | Constructor `file_path=` | Works with `FutureWarning` | Removed |
157
+ | Instance `load()` | Works with `FutureWarning` | Removed |
158
+ | Direct context-manager persistence | Saves only on successful exit and warns | Removed |
159
+ | Unversioned two-array archives | Load with known configuration and warn | Reader removed |
160
+ | `open()`, `save(path)`, `save()`, and `reload()` | Preferred | Supported |
161
+
162
+ ## Persistence boundaries that do not change
163
+
164
+ Metadata is stored in a pickle-backed NumPy object array. Archives remain
165
+ trusted input and must not be opened from untrusted or unverifiable sources.
166
+
167
+ Saves use same-directory temporary files and atomic replacement, but the
168
+ library does not add file locking, coordinate concurrent writers, or promise
169
+ power-loss durability across every operating system and filesystem.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: numpy-vector-store
3
- Version: 0.3.2
3
+ Version: 0.4.0
4
4
  Summary: A fast, lightweight, and zero-setup in-memory vector store powered by NumPy
5
5
  Project-URL: Homepage, https://github.com/tvanreenen/numpy-vector-store
6
6
  Project-URL: Repository, https://github.com/tvanreenen/numpy-vector-store
@@ -13,13 +13,12 @@ Classifier: Development Status :: 3 - Alpha
13
13
  Classifier: Intended Audience :: Developers
14
14
  Classifier: License :: OSI Approved :: MIT License
15
15
  Classifier: Programming Language :: Python :: 3
16
- Classifier: Programming Language :: Python :: 3.10
17
16
  Classifier: Programming Language :: Python :: 3.11
18
17
  Classifier: Programming Language :: Python :: 3.12
19
18
  Classifier: Programming Language :: Python :: 3.13
20
19
  Classifier: Programming Language :: Python :: 3.14
21
- Requires-Python: >=3.10
22
- Requires-Dist: numpy>=1.21.3
20
+ Requires-Python: >=3.11
21
+ Requires-Dist: numpy>=1.23.2
23
22
  Description-Content-Type: text/markdown
24
23
 
25
24
  # NumPy Vector Store
@@ -30,7 +29,7 @@ A fast, lightweight, zero-setup in-memory vector store powered by NumPy.
30
29
  - **Fast exact vector search** using vectorized NumPy operations
31
30
  - **Simple typed API** returning `VectorHit(index, value, metadata)`
32
31
  - **Composable filtering** by passing prefiltered row indexes with `within_rows`
33
- - **Portable persistence** as trusted local `.npz` files with `vectors` + `metadata`
32
+ - **Portable persistence** as versioned, self-describing trusted local `.npz` files
34
33
  - **No framework opinions**: bring your own embeddings, chunking, async, and metadata model
35
34
 
36
35
  ## Why?
@@ -210,54 +209,102 @@ for hit in hits:
210
209
 
211
210
  ## Persistence
212
211
 
213
- Pass a `file_path` and call `save()` / `load()` explicitly:
212
+ Create a new store normally, then supply its destination on the first save:
214
213
 
215
214
  ```python
216
- store = VectorStore[dict[str, str]](dimensions=1536, file_path="vectors.npz")
215
+ store = VectorStore[dict[str, str]](dimensions=1536)
217
216
  store.add(embeddings, metadata)
218
- store.save()
217
+ store.save("vectors.npz")
218
+ ```
219
+
220
+ `save(path)` writes the archive and binds that path to the store. Later
221
+ `save()` calls update the bound archive. Passing another path performs a Save
222
+ As operation; the new path becomes the binding only after the write succeeds.
223
+
224
+ Open an existing store directly from its self-describing archive:
219
225
 
220
- loaded = VectorStore[dict[str, str]](dimensions=1536, file_path="vectors.npz")
221
- loaded.load()
226
+ ```python
227
+ store = VectorStore[dict[str, str]].open("vectors.npz")
222
228
  ```
223
229
 
230
+ `open()` restores `dimensions` and `normalize` from the archive and binds its
231
+ path. Use `reload()` when the file may have changed externally and you
232
+ explicitly want to discard current in-memory changes:
233
+
234
+ ```python
235
+ store.reload()
236
+ ```
237
+
238
+ Unlike the transitional `load()` method, `reload()` always rereads the bound
239
+ archive and raises if the store is unbound, the file is missing, or the archive
240
+ is invalid. A failed reload leaves the current in-memory vectors and metadata
241
+ unchanged.
242
+
224
243
  The `.npz` suffix may be omitted. An extensionless path such as `"vectors"` is
225
- resolved to `"vectors.npz"` for both saving and loading.
244
+ resolved to `"vectors.npz"` for saving, opening, and reloading.
226
245
 
227
- If you save with `normalize=False`, load with `normalize=False` too:
246
+ Raw-vector configuration is also restored from the archive:
228
247
 
229
248
  ```python
230
249
  store = VectorStore[dict[str, str]](
231
250
  dimensions=1536,
232
- file_path="raw-vectors.npz",
233
251
  normalize=False,
234
252
  )
235
253
  store.add(raw_vectors, metadata)
236
- store.save()
254
+ store.save("raw-vectors.npz")
237
255
 
238
- loaded = VectorStore[dict[str, str]](
239
- dimensions=1536,
240
- file_path="raw-vectors.npz",
241
- normalize=False,
242
- )
243
- loaded.load()
256
+ loaded = VectorStore[dict[str, str]].open("raw-vectors.npz")
257
+ assert loaded.normalize is False
244
258
  ```
245
259
 
246
- Context manager usage auto-saves on exit:
260
+ Archives written by 0.4 use format version 1 and contain `format_version`,
261
+ `dimensions`, `normalize`, `vectors`, and `metadata`. The stored configuration
262
+ prevents an archive from being loaded with different dimensions or normalization
263
+ semantics.
264
+
265
+ Opening and reloading validate the complete schema, array dtypes and shapes, row
266
+ counts, finite vector values, and zero-norm behavior before changing in-memory
267
+ state. Opaque metadata values remain individual row payloads across persistence
268
+ round trips.
269
+
270
+ Each save writes a uniquely named temporary archive in the destination
271
+ directory, closes it, and then replaces the destination with `os.replace`.
272
+ Readers opening the destination path therefore see either the previous complete
273
+ archive or the new complete archive rather than a partially written file. If
274
+ writing or replacement fails, the previous destination remains in place and the
275
+ temporary file is removed.
276
+
277
+ Atomic replacement is not file locking or multi-writer coordination. Concurrent
278
+ writers can still replace one another, and the library does not promise that a
279
+ successful save has reached durable hardware storage across every operating
280
+ system or power failure.
281
+
282
+ Older archives containing only `vectors` and `metadata` remain readable in 0.4
283
+ through the configuration-aware legacy API:
247
284
 
248
285
  ```python
249
- with VectorStore[dict[str, str]](dimensions=1536, file_path="vectors.npz") as store:
250
- store.add(embeddings, metadata)
286
+ legacy = VectorStore[dict[str, str]](
287
+ dimensions=1536,
288
+ file_path="legacy-vectors.npz",
289
+ normalize=True,
290
+ )
291
+ legacy.load()
292
+ legacy.save()
251
293
  ```
252
294
 
253
- Persistence uses a minimal NumPy `.npz` contract with `vectors` and `metadata`
254
- arrays. The `.npz` file does not encode the `normalize` setting; choose the same
255
- setting when loading that you used when saving. Loading validates shape,
256
- dimensions, row counts, and zero-norm vectors. A `load()` call made before the
257
- file exists can be retried after the file is created. Repeated calls after a
258
- successful load do nothing unless `clear()` resets the in-memory store. Opaque
259
- metadata values remain individual row payloads across save/load round trips.
260
- Persistence uses `allow_pickle=True` for flexible Python metadata payloads, so
295
+ Loading a legacy archive emits a `FutureWarning`, and saving rewrites it as
296
+ format version 1. `open()` intentionally rejects unversioned archives because
297
+ they do not contain enough configuration to construct a store safely. The
298
+ legacy reader will be removed in 0.5; migrate an archive once with 0.4 or
299
+ recreate it from source data.
300
+
301
+ Constructor `file_path=`, instance `load()`, and direct context-manager
302
+ persistence remain available with `FutureWarning` during the 0.4 transition.
303
+ The explicit lifecycle shown above is the preferred API and will be the only
304
+ persistence API in 0.5. See the [persistence migration guide](MIGRATION.md) for
305
+ side-by-side replacements and the one-time legacy archive conversion.
306
+
307
+ Metadata persistence uses `allow_pickle=True` for flexible Python payloads, so
261
308
  only load files generated by your own application or another trusted local
262
309
  process. Loading untrusted `.npz` files is not a supported security model.
263
310
 
@@ -268,14 +315,19 @@ the API stabilizes. Changes are documented in the [changelog](CHANGELOG.md) and
268
315
  GitHub release notes. Deprecated APIs will keep warning for at least one point
269
316
  release before removal.
270
317
 
271
- Supported Python versions are listed in the package classifiers and exercised
272
- in CI. The project generally retains stable CPython versions until their
273
- upstream end-of-life, adds new versions after its dependencies and CI support
274
- them, and drops versions only in minor releases.
318
+ Version 0.4 supports Python 3.11 through 3.14 and NumPy 1.23.2 or newer. These
319
+ versions are listed in the package metadata and exercised in CI, including a
320
+ dedicated check against the minimum NumPy version. Python 3.10 remains supported
321
+ by the 0.3 release series but is not supported by 0.4.
322
+
323
+ The project generally retains stable CPython versions until their upstream
324
+ end-of-life, adds new versions after its dependencies and CI support them, and
325
+ drops versions only in minor releases.
275
326
 
276
327
  See the [changelog](CHANGELOG.md) for release history and the
277
328
  [project roadmap](ROADMAP.md) for the planned path to stable API and persistence
278
- contracts.
329
+ contracts. Persistence users upgrading from the 0.3 API should also read the
330
+ [migration guide](MIGRATION.md).
279
331
 
280
332
  ## Contributing
281
333