numpy-vector-store 0.5.0__tar.gz → 0.7.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 (29) hide show
  1. {numpy_vector_store-0.5.0 → numpy_vector_store-0.7.0}/.github/workflows/checks.yml +33 -7
  2. {numpy_vector_store-0.5.0 → numpy_vector_store-0.7.0}/.github/workflows/publish-pypi.yml +8 -3
  3. {numpy_vector_store-0.5.0 → numpy_vector_store-0.7.0}/.github/workflows/publish-testpypi.yml +6 -3
  4. {numpy_vector_store-0.5.0 → numpy_vector_store-0.7.0}/CHANGELOG.md +251 -0
  5. numpy_vector_store-0.7.0/COMPATIBILITY.md +145 -0
  6. {numpy_vector_store-0.5.0 → numpy_vector_store-0.7.0}/MIGRATION.md +25 -4
  7. {numpy_vector_store-0.5.0 → numpy_vector_store-0.7.0}/PKG-INFO +153 -31
  8. {numpy_vector_store-0.5.0 → numpy_vector_store-0.7.0}/README.md +149 -27
  9. {numpy_vector_store-0.5.0 → numpy_vector_store-0.7.0}/ROADMAP.md +140 -17
  10. numpy_vector_store-0.7.0/benchmarks/README.md +91 -0
  11. numpy_vector_store-0.7.0/benchmarks/__init__.py +1 -0
  12. numpy_vector_store-0.7.0/benchmarks/benchmark.py +335 -0
  13. {numpy_vector_store-0.5.0 → numpy_vector_store-0.7.0}/pyproject.toml +4 -3
  14. {numpy_vector_store-0.5.0 → numpy_vector_store-0.7.0}/src/numpy_vector_store/__init__.py +2 -2
  15. {numpy_vector_store-0.5.0 → numpy_vector_store-0.7.0}/src/numpy_vector_store/vector_store.py +131 -49
  16. numpy_vector_store-0.7.0/tests/fixtures/README.md +36 -0
  17. numpy_vector_store-0.7.0/tests/fixtures/vector-store-0.4.0-format-v1.npz +0 -0
  18. numpy_vector_store-0.7.0/tests/test_benchmark.py +119 -0
  19. numpy_vector_store-0.7.0/tests/test_package.py +34 -0
  20. numpy_vector_store-0.7.0/tests/test_release_version.py +111 -0
  21. {numpy_vector_store-0.5.0 → numpy_vector_store-0.7.0}/tests/test_vector_store.py +520 -28
  22. numpy_vector_store-0.7.0/tools/verify_release_version.py +169 -0
  23. {numpy_vector_store-0.5.0 → numpy_vector_store-0.7.0}/.github/FUNDING.yml +0 -0
  24. {numpy_vector_store-0.5.0 → numpy_vector_store-0.7.0}/.gitignore +0 -0
  25. {numpy_vector_store-0.5.0 → numpy_vector_store-0.7.0}/LICENSE +0 -0
  26. {numpy_vector_store-0.5.0 → numpy_vector_store-0.7.0}/justfile +0 -0
  27. {numpy_vector_store-0.5.0 → numpy_vector_store-0.7.0}/src/numpy_vector_store/py.typed +0 -0
  28. {numpy_vector_store-0.5.0 → numpy_vector_store-0.7.0}/tests/__init__.py +0 -0
  29. {numpy_vector_store-0.5.0 → numpy_vector_store-0.7.0}/uv.lock +0 -0
@@ -22,10 +22,10 @@ jobs:
22
22
 
23
23
  steps:
24
24
  - name: Check out repository
25
- uses: actions/checkout@v6
25
+ uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
26
26
 
27
27
  - name: Set up Python
28
- uses: actions/setup-python@v6
28
+ uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
29
29
  with:
30
30
  python-version: "3.13"
31
31
 
@@ -46,7 +46,7 @@ jobs:
46
46
  run: uv run --locked ruff format --check .
47
47
 
48
48
  - name: Run mypy
49
- run: uv run --locked mypy src/
49
+ run: uv run --locked mypy src/ benchmarks/ tools/
50
50
 
51
51
  tests:
52
52
  name: Test Python ${{ matrix.python-version }}
@@ -62,10 +62,10 @@ jobs:
62
62
 
63
63
  steps:
64
64
  - name: Check out repository
65
- uses: actions/checkout@v6
65
+ uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
66
66
 
67
67
  - name: Set up Python
68
- uses: actions/setup-python@v6
68
+ uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
69
69
  with:
70
70
  python-version: ${{ matrix.python-version }}
71
71
 
@@ -82,16 +82,42 @@ jobs:
82
82
  - name: Run tests
83
83
  run: uv run --locked pytest
84
84
 
85
+ windows:
86
+ name: Test Windows on Python 3.13
87
+ runs-on: windows-latest
88
+
89
+ steps:
90
+ - name: Check out repository
91
+ uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
92
+
93
+ - name: Set up Python
94
+ uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
95
+ with:
96
+ python-version: "3.13"
97
+
98
+ - name: Set up uv
99
+ uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
100
+ with:
101
+ version: "0.11.32"
102
+ python-version: "3.13"
103
+ enable-cache: true
104
+
105
+ - name: Install project dependencies
106
+ run: uv sync --locked --group dev
107
+
108
+ - name: Run tests
109
+ run: uv run --locked pytest
110
+
85
111
  minimum-numpy:
86
112
  name: Test minimum NumPy on Python 3.11
87
113
  runs-on: ubuntu-latest
88
114
 
89
115
  steps:
90
116
  - name: Check out repository
91
- uses: actions/checkout@v6
117
+ uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
92
118
 
93
119
  - name: Set up Python
94
- uses: actions/setup-python@v6
120
+ uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
95
121
  with:
96
122
  python-version: "3.11"
97
123
 
@@ -20,10 +20,10 @@ jobs:
20
20
 
21
21
  steps:
22
22
  - name: Check out repository
23
- uses: actions/checkout@v6
23
+ uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
24
24
 
25
25
  - name: Set up Python
26
- uses: actions/setup-python@v6
26
+ uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
27
27
  with:
28
28
  python-version: "3.12"
29
29
 
@@ -33,8 +33,13 @@ jobs:
33
33
  - name: Build distributions
34
34
  run: python -m build
35
35
 
36
+ - name: Verify release versions
37
+ env:
38
+ RELEASE_TAG: ${{ github.event.release.tag_name }}
39
+ run: python tools/verify_release_version.py --tag "$RELEASE_TAG"
40
+
36
41
  - name: Check distribution metadata
37
42
  run: python -m twine check dist/*
38
43
 
39
44
  - name: Publish package distributions to PyPI
40
- uses: pypa/gh-action-pypi-publish@release/v1
45
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
@@ -18,10 +18,10 @@ jobs:
18
18
 
19
19
  steps:
20
20
  - name: Check out repository
21
- uses: actions/checkout@v6
21
+ uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
22
22
 
23
23
  - name: Set up Python
24
- uses: actions/setup-python@v6
24
+ uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
25
25
  with:
26
26
  python-version: "3.12"
27
27
 
@@ -31,10 +31,13 @@ jobs:
31
31
  - name: Build distributions
32
32
  run: python -m build
33
33
 
34
+ - name: Verify distribution versions
35
+ run: python tools/verify_release_version.py
36
+
34
37
  - name: Check distribution metadata
35
38
  run: python -m twine check dist/*
36
39
 
37
40
  - name: Publish package distributions to TestPyPI
38
- uses: pypa/gh-action-pypi-publish@release/v1
41
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
39
42
  with:
40
43
  repository-url: https://test.pypi.org/legacy/
@@ -4,6 +4,257 @@ This changelog records user-visible changes to NumPy Vector Store. Earlier
4
4
  release notes remain available on the
5
5
  [GitHub releases page](https://github.com/tvanreenen/numpy-vector-store/releases).
6
6
 
7
+ ## 0.7.0 - 2026-08-24
8
+
9
+ This release closes the remaining public contract gaps found during the 1.0
10
+ readiness audit. It rejects two forms of lossy NumPy coercion, makes a bound
11
+ archive path stable for the lifetime of its store, and publishes the API and
12
+ compatibility policy intended for 1.x. It also adds release checks that enforce
13
+ the package version and distribution metadata before publication.
14
+
15
+ Valid, documented 0.6 usage continues to work. Version 0.7 does not add a new
16
+ feature family, remove a documented API, change search ranking, add a runtime
17
+ dependency, or change archive format version 1.
18
+
19
+ ### Public API and 1.x compatibility policy
20
+
21
+ The supported package-level imports are now explicit:
22
+
23
+ ```python
24
+ from numpy_vector_store import VectorHit, VectorStore, __version__
25
+ ```
26
+
27
+ - Add a [public API and compatibility policy](COMPATIBILITY.md) that names the
28
+ supported `VectorStore` methods and properties, `VectorHit` fields, return
29
+ types, ownership behavior, exception classes, and persistence guarantees.
30
+ - Distinguish the supported top-level interface from private helpers, submodule
31
+ layout, subclass hooks, exact error wording, allocation details, and internal
32
+ algorithms.
33
+ - Define the 1.x semantic-versioning, deprecation, removal, Python and NumPy
34
+ support, and archive-compatibility policies before those promises become
35
+ long-term commitments in 1.0.
36
+ - Keep `VectorHit` as an immutable result record whose equality and hashability
37
+ follow its three values. Opaque metadata retains its own comparison and
38
+ hashing behavior; a hit containing a dictionary remains unhashable.
39
+
40
+ The API itself remains centered on `VectorStore`, `VectorHit`, three explicit
41
+ metric methods, typed opaque metadata, and the create/open/save/reload
42
+ persistence lifecycle.
43
+
44
+ ### Lossless numeric input handling
45
+
46
+ - Reject Python row-selector sequences containing a Python or NumPy Boolean,
47
+ including mixed values such as `[0, True]`, before NumPy can coerce the
48
+ Boolean to row zero or one.
49
+ - Preserve the vectorized validation path for native NumPy integer selectors,
50
+ along with the existing shape, uniqueness, and bounds rules.
51
+ - Reject complex vector batches passed to `add()` and complex queries passed to
52
+ every metric method before conversion to `float32` can discard their
53
+ imaginary components. This includes complex dtypes whose current values have
54
+ a zero imaginary part.
55
+ - Apply complex-query validation before empty-store and empty-filter shortcuts,
56
+ so the accepted query contract does not depend on current store state.
57
+
58
+ These inputs were accepted only through implicit NumPy conversion. Calls that
59
+ deliberately used Boolean row indexes or complex vectors now raise `TypeError`;
60
+ real-valued integer and floating-point inputs keep their existing behavior.
61
+
62
+ ### Stable archive bindings
63
+
64
+ - Anchor a relative path to the current working directory when `save(path)` or
65
+ `VectorStore.open(path)` first binds it.
66
+ - Return that anchored absolute path from `file_path` and keep later `save()` and
67
+ `reload()` calls pointed at the same archive if the process changes working
68
+ directory.
69
+ - Preserve extensionless `.npz` resolution and lexical path behavior. Anchoring
70
+ does not resolve `..` components or dereference symlinks, so ordinary
71
+ filesystem replacement semantics remain intact.
72
+ - Keep Save As transactional: a new binding becomes visible only after the new
73
+ archive has been written successfully.
74
+
75
+ Format version 1 is unchanged, and no archive conversion is required. Code that
76
+ compares `file_path` with a relative path should compare against its anchored
77
+ absolute form. Code that intentionally changed directories to redirect a later
78
+ pathless save should pass the new destination explicitly to `save(path)`.
79
+
80
+ ### Distribution and release safeguards
81
+
82
+ - Publish the MIT license as an SPDX expression and include `LICENSE` in both
83
+ the wheel and source distribution.
84
+ - Declare the existing `py.typed` marker through the `Typing :: Typed`
85
+ classifier and identify 0.7 as the project's beta adoption cycle before 1.0.
86
+ - Verify that the source `__version__`, GitHub release tag, wheel metadata, and
87
+ source-distribution metadata agree before publishing to PyPI. The verifier
88
+ also requires exactly one wheel and one source distribution so stale build
89
+ output cannot be uploaded accidentally.
90
+ - Pin remote GitHub Actions to immutable commits while retaining readable
91
+ release comments beside each pin.
92
+ - Run the complete test suite on Windows with Python 3.13 in addition to the
93
+ supported Python 3.11 through 3.14 matrix on Linux and the Python 3.11 minimum
94
+ NumPy check.
95
+ - Add regression coverage for unexpected archive fields, nonpositive persisted
96
+ dimensions, malformed persisted array shapes, and platform-specific
97
+ filesystem exception and permission behavior.
98
+
99
+ The release verifier and benchmark tools remain outside the runtime wheel and
100
+ add no package dependency.
101
+
102
+ ### Runtime compatibility and upgrade notes
103
+
104
+ - Continue supporting Python 3.11 through 3.14 and NumPy 1.23.2 or newer.
105
+ - Continue reading and writing self-describing format-version-1 archives,
106
+ including archives created by the published 0.4 package.
107
+ - Continue treating persistence as a trusted-file feature because opaque
108
+ metadata uses pickle-backed NumPy object arrays.
109
+ - Require no data migration when upgrading from 0.6.
110
+
111
+ Most applications can upgrade without code changes. Review only code that used
112
+ Boolean row selectors, supplied complex vectors or queries, compared a bound
113
+ `file_path` with a relative path, or relied on a later working-directory change
114
+ to redirect a bound store.
115
+
116
+ ## 0.6.0 - 2026-08-22
117
+
118
+ This release makes the existing API's input and failure contracts predictable
119
+ before 1.0. It closes cases where the same invalid call could behave
120
+ differently on an empty and populated store, where Python booleans could act as
121
+ row indexes or counts, and where loosely coerced configuration could change
122
+ meaning after persistence. It also turns the project's performance claims and
123
+ format-version-1 compatibility promise into reproducible regression evidence.
124
+
125
+ Valid, documented 0.5 usage continues to work unchanged. Version 0.6 does not
126
+ add a new feature family, archive format, runtime dependency, or public
127
+ exception hierarchy.
128
+
129
+ ### API at a glance
130
+
131
+ The complete workflow remains centered on `VectorStore` and `VectorHit`:
132
+
133
+ ```python
134
+ store = VectorStore(dimensions=1536, normalize=True)
135
+ store.add(vectors, metadata)
136
+
137
+ hits = store.cosine_search(query, top_k=10)
138
+ hits = store.dot_search(query, top_k=10, min_value=0.5)
139
+ hits = store.euclidean_search(query, top_k=10, within_rows=row_indexes)
140
+
141
+ row = store.get(0)
142
+ store.clear()
143
+
144
+ store.save("vectors.npz")
145
+ store.save()
146
+
147
+ loaded = VectorStore.open("vectors.npz")
148
+ loaded.reload()
149
+ ```
150
+
151
+ The ownership, amortized ingestion, deterministic tie ordering, explicit
152
+ persistence lifecycle, and trusted-local-file boundary established in 0.5 are
153
+ unchanged.
154
+
155
+ ### Predictable scalar inputs
156
+
157
+ - Accept Python integers, NumPy integer scalars, and other values implementing
158
+ the standard integer-index protocol for `dimensions`, `top_k`, and
159
+ `get(index)`, then convert them to a Python `int`.
160
+ - Reject Python and NumPy booleans where integer semantics are required.
161
+ `top_k=True` no longer means one, and `get(True)` no longer invokes NumPy
162
+ boolean indexing.
163
+ - Continue requiring positive `dimensions` and `top_k` values. A valid integer
164
+ outside the stored row range still makes `get()` return `None`.
165
+ - Accept Python and NumPy booleans for `normalize`, then retain a canonical
166
+ Python `bool` in memory and persistence.
167
+ - Accept finite Python and NumPy integer or floating-point scalars for
168
+ `min_value` and `max_value`. Reject booleans, strings, complex values,
169
+ arrays, and non-finite thresholds before search.
170
+
171
+ An inappropriate scalar type now raises `TypeError`, while a supported type
172
+ with an invalid value raises `ValueError`. Row bounds continue using
173
+ `IndexError` where row selectors require an existing row. Exact error wording
174
+ remains explanatory rather than a compatibility guarantee.
175
+
176
+ ### Search checks independent of store state
177
+
178
+ - Validate `within_rows` before returning an empty result. It must be a
179
+ one-dimensional sequence of unique integer row indexes, without booleans,
180
+ and every index must be within the current store.
181
+ - Reject duplicate filtered indexes instead of returning the same stored row
182
+ more than once.
183
+ - Apply the same zero-query rules before the empty-store shortcut. Cosine
184
+ search always requires a non-zero query. Dot-product and Euclidean searches
185
+ require one when the store normalizes vectors, while raw stores continue to
186
+ accept a zero query for those two metrics.
187
+ - Keep selector validation vectorized for native NumPy integer arrays and avoid
188
+ changing process-global warning filters while validating Python sequences.
189
+
190
+ These changes affect preconditions, not ranking. Valid 0.5 searches retain the
191
+ same metric calculations, thresholds, deterministic tie handling, filtered-row
192
+ semantics, and `VectorHit` results.
193
+
194
+ ### Persistence inputs and compatibility boundaries
195
+
196
+ - Accept strings and string-returning path-like objects for `open()`,
197
+ `save(path)`, and bound archive paths. Reject an explicitly empty path with
198
+ `ValueError` and an inappropriate path type, including bytes paths, with
199
+ `TypeError`.
200
+ - Keep `save()` with no argument distinct from `save("")`: omission means
201
+ "save to the current binding," while an empty string is an invalid path.
202
+ - Preserve extension resolution. A path without `.npz` still resolves to the
203
+ same archive for saving, opening, and reloading.
204
+ - Preserve owning exceptions at the persistence boundary. Filesystem failures
205
+ use the relevant `OSError` subclass, malformed archive schemas use
206
+ `ValueError`, and NumPy, pickle, or application metadata-loading failures are
207
+ not hidden inside a package-specific wrapper.
208
+
209
+ Archive format version 1 is unchanged. The compatibility suite now includes an
210
+ archive produced by the published 0.4.0 package on Python 3.11 with NumPy
211
+ 1.23.2 and verifies that the current reader recovers its configuration, vectors,
212
+ and metadata. This is a forward-reading promise for the self-describing format,
213
+ not support for older unversioned two-array archives or a promise that an old
214
+ package can read an arbitrary future format. Persistence remains a trusted-file
215
+ feature because opaque metadata uses NumPy's pickle-backed object arrays.
216
+
217
+ ### Reproducible performance evidence
218
+
219
+ - Add repository benchmark commands for public exact search and ingestion.
220
+ Each command records the workload, seeded input digests, complete timing
221
+ samples, median, Python and NumPy versions, Git state, timer, thread-related
222
+ environment variables, and NumPy build configuration as JSON.
223
+ - Measure prepared inputs and documented public operations rather than random
224
+ input generation. Search records complete query batches; ingestion records a
225
+ fresh store and every `add()` call.
226
+ - Add structural regression checks for geometric capacity reuse, partial
227
+ top-k selection, and unfiltered access to the stored vector matrix without a
228
+ preliminary full-matrix copy.
229
+ - Keep wall-clock limits out of shared CI, where runner load would make failures
230
+ noisy and machine-specific.
231
+
232
+ The README now reports one aligned grid at 1,000, 10,000, and 100,000 rows for
233
+ 384, 1,536, and 3,072 dimensions, measured on a 24 GB Apple M4 Mac mini. It
234
+ also makes the intended scale explicit: this project is for small-to-medium,
235
+ in-process exact search. The 100,000-row measurements are an upper reference,
236
+ not a target for indefinite scaling; routinely million-row workloads generally
237
+ need an indexed or service-backed system.
238
+
239
+ ### Runtime compatibility and upgrade notes
240
+
241
+ - Continue supporting Python 3.11 through 3.14 and NumPy 1.23.2 or newer.
242
+ - Continue exercising every supported Python version in CI, including a
243
+ dedicated Python 3.11 job with the minimum NumPy version.
244
+ - Keep the runtime wheel limited to the package. Benchmark tooling and its
245
+ guide are included in the source distribution without adding runtime
246
+ dependencies.
247
+ - Require no archive conversion when upgrading from 0.5; format version 1 is
248
+ still the only supported archive format.
249
+
250
+ Applications using documented 0.5 input types should not need code changes.
251
+ Review call sites that pass booleans where integers are expected, fractional
252
+ result counts, string or boolean thresholds, duplicate or malformed
253
+ `within_rows` values, zero queries that were only attempted against empty
254
+ stores, or explicitly empty persistence paths. Those accidentally accepted or
255
+ state-dependent cases now fail at the public boundary with consistent standard
256
+ exceptions.
257
+
7
258
  ## 0.5.0 - 2026-08-21
8
259
 
9
260
  This release gives `VectorStore` clear ownership of its configuration and row
@@ -0,0 +1,145 @@
1
+ # Public API and compatibility policy
2
+
3
+ NumPy Vector Store 0.7 publishes the contract intended for 1.0 so applications
4
+ can evaluate it before the project makes a long-term stability commitment. The
5
+ package remains pre-1.0 until the 0.7 API has completed a normal release cycle.
6
+
7
+ This policy separates the small supported interface from implementation details
8
+ that may continue to improve during 1.x.
9
+
10
+ ## Supported public API
11
+
12
+ Import supported names from the top-level package:
13
+
14
+ ```python
15
+ from numpy_vector_store import VectorHit, VectorStore, __version__
16
+ ```
17
+
18
+ These are the package-level public names declared by `numpy_vector_store.__all__`:
19
+
20
+ - `VectorStore`, including its constructor, `len(store)`, and the documented
21
+ methods and properties below.
22
+ - `VectorHit`, including its read-only `index`, `value`, and `metadata` fields.
23
+ - `__version__`, the installed package version as a string.
24
+
25
+ The supported `VectorStore` surface is:
26
+
27
+ | Area | Public interface |
28
+ |---|---|
29
+ | Create | `VectorStore(dimensions, *, normalize=True)` |
30
+ | Inspect configuration | `dimensions`, `normalize`, `file_path` |
31
+ | Inspect rows | `vectors`, `metadata`, `len(store)` |
32
+ | Change rows | `add(vectors, metadata)`, `clear()` |
33
+ | Retrieve one row | `get(index)` |
34
+ | Search | `cosine_search(...)`, `dot_search(...)`, `euclidean_search(...)` |
35
+ | Persist | `VectorStore.open(path)`, `save(path)`, `save()`, `reload()` |
36
+
37
+ The public contract includes documented signatures, accepted input categories,
38
+ return types, public type annotations, state and ownership behavior, search
39
+ ordering, and exception classes. The `VectorStore` generic parameter describes
40
+ its metadata payload type and flows through row retrieval and search results.
41
+ The contract also includes the documented `float32` vector representation and
42
+ format-version-1 archive schema. Exact exception messages are explanatory text,
43
+ not identifiers for application logic.
44
+
45
+ Importing from `numpy_vector_store.vector_store` may work, but the submodule
46
+ layout is an implementation detail. Applications should use top-level imports.
47
+ Names beginning with a single underscore, private object state, spare-capacity
48
+ layout, and internal calculation steps are not public API.
49
+
50
+ `VectorStore` is not designed as a subclass extension framework. Subclassing is
51
+ not prohibited, but overriding methods or depending on private helpers and state
52
+ is outside the compatibility promise. Prefer composition when application code
53
+ needs behavior around a store.
54
+
55
+ ## `VectorHit` and opaque metadata
56
+
57
+ `VectorHit` is an immutable result record. Its fields cannot be reassigned, but
58
+ the `metadata` value remains the same opaque application object held by the
59
+ store. A mutable metadata payload can still be changed through that object.
60
+
61
+ Equality and hashability follow the values stored in the three fields. There is
62
+ no package-specific metadata comparison or hashing policy. In particular:
63
+
64
+ - A hit containing a dictionary or another unhashable payload is unhashable.
65
+ - A metadata type with non-Boolean equality, such as a NumPy array, can make
66
+ whole-hit equality return a non-Boolean value or raise when converted to a
67
+ truth value.
68
+ - A metadata type with application-defined equality keeps those semantics.
69
+
70
+ Use `hit.index` or an immutable application identifier inside `hit.metadata`
71
+ when code needs a stable key. Do not assume every possible metadata payload
72
+ makes the complete `VectorHit` suitable for a set, dictionary key, or Boolean
73
+ equality assertion.
74
+
75
+ ## Semantic versioning after 1.0
76
+
77
+ Starting with 1.0, release numbers follow
78
+ [Semantic Versioning 2.0.0](https://semver.org/) and communicate changes to the
79
+ documented public API:
80
+
81
+ - Patch releases contain backward-compatible defect fixes, documentation
82
+ corrections, and internal improvements. A patch may correct behavior that
83
+ contradicts the documented contract, and the release notes will identify the
84
+ affected case.
85
+ - Minor releases add backward-compatible functionality and may deprecate public
86
+ API. They do not remove or incompatibly change documented public API.
87
+ - Major releases may remove deprecated API or make other incompatible public
88
+ contract changes.
89
+
90
+ A public API deprecation will appear in the documentation and changelog and
91
+ will emit a warning where a runtime warning is practical. Removal waits for a
92
+ later major release and will have at least one minor release of prior
93
+ deprecation.
94
+
95
+ Exact error wording, object representations, private names, submodule layout,
96
+ internal algorithms, allocation strategy, benchmark timings, and undocumented
97
+ side effects may change in any release. Changes to these details must still
98
+ preserve the documented results and ownership rules.
99
+
100
+ ## Archive compatibility
101
+
102
+ Archive format versions are independent of package versions. Format version 1
103
+ is the stable persistence contract for 1.x:
104
+
105
+ - Every 1.x release will continue reading and writing valid format-version-1
106
+ archives.
107
+ - A mandatory replacement for format version 1 requires a major package
108
+ release and a documented migration path.
109
+ - A new archive format may appear during 1.x only as an explicit opt-in while
110
+ format-version-1 read and write support remains available.
111
+ - An older reader is not expected to understand an arbitrary newer archive
112
+ format. Compatibility is a newer-reader promise, not a future-format promise
113
+ for already published code.
114
+
115
+ Unversioned archives containing only `vectors` and `metadata` remain outside
116
+ this contract because they do not record dimensions or normalization semantics.
117
+
118
+ Format compatibility does not make every Python metadata object portable.
119
+ Opaque metadata uses pickle-backed NumPy object arrays and may depend on the
120
+ same application classes, import paths, and compatible dependencies being
121
+ available when it is opened. Archives must come from a trusted source. The
122
+ library does not treat untrusted pickle data as safe input.
123
+
124
+ ## Python and NumPy support
125
+
126
+ The Python versions listed in package metadata are tested in CI. The declared
127
+ minimum NumPy version is tested with the oldest supported Python version.
128
+
129
+ Support for a Python or NumPy version may be removed in a minor release after
130
+ its relevant upstream support window or ecosystem compatibility makes continued
131
+ testing impractical. Such a change will be made in package metadata and called
132
+ out in the changelog and release notes. Dropping a runtime version does not
133
+ remove callable API from applications running on a supported interpreter.
134
+
135
+ ## Performance expectations
136
+
137
+ Search results, metric definitions, threshold behavior, deterministic tie
138
+ ordering, and filtered-row semantics are public behavior. The implementation
139
+ may change how it produces those results.
140
+
141
+ Published benchmark measurements are reference evidence, not latency or memory
142
+ guarantees. Hardware, NumPy, BLAS, process load, vector shape, and metadata all
143
+ affect observed performance. Structural regressions such as repeated full-store
144
+ copies during ordinary ingestion or unnecessary unfiltered search copies remain
145
+ covered by tests rather than fixed wall-clock promises.
@@ -71,10 +71,11 @@ payload objects by reference.
71
71
  Row retrieval deliberately treats vectors and metadata differently:
72
72
 
73
73
  ```python
74
- vector, payload = store.get(0)
75
-
76
- vector[0] = 10.0 # Independent copy; the store is unchanged.
77
- payload["reviewed"] = True # Shared application metadata object.
74
+ row = store.get(0)
75
+ if row is not None:
76
+ vector, payload = row
77
+ vector[0] = 10.0 # Independent copy; the store is unchanged.
78
+ payload["reviewed"] = True # Shared application metadata object.
78
79
  ```
79
80
 
80
81
  Vectors have one uniform NumPy representation, so copying a single row gives
@@ -177,6 +178,15 @@ contents, loads its rows, and binds its path. Applications no longer need to
177
178
  keep archive configuration separately or risk loading the same vectors with
178
179
  different semantics.
179
180
 
181
+ Paths may be strings or string-valued `os.PathLike` objects such as
182
+ `pathlib.Path`. Empty paths raise `ValueError`, while bytes and other non-path
183
+ values raise `TypeError`. A `None` path is meaningful only for `save()`, where
184
+ it reuses an existing binding. A relative path is anchored to the current
185
+ working directory when `save(path)` or `open(path)` binds it. The public
186
+ `file_path` is therefore absolute, and later working-directory changes do not
187
+ redirect pathless saves or reloads. Symlinks and `..` components are not
188
+ canonicalized.
189
+
180
190
  The generic parameter still describes application metadata. It can be kept
181
191
  when useful:
182
192
 
@@ -284,6 +294,17 @@ configuration safely.
284
294
  Metadata is stored in a pickle-backed NumPy object array. Archives remain
285
295
  trusted input and must not be opened from untrusted or unverifiable sources.
286
296
 
297
+ Current releases retain forward-reading support for self-describing
298
+ format-version-1 archives written by 0.4. The test suite keeps a binary fixture
299
+ generated by the published 0.4.0 package on Python 3.11 and NumPy 1.23.2. This
300
+ does not restore the unversioned reader or promise that an older release can
301
+ read an arbitrary future format.
302
+
303
+ Read and write failures retain the exception type owned by their source:
304
+ filesystem `OSError` subclasses, schema `ValueError`, and NumPy, pickle, or
305
+ application metadata-loading errors are not collapsed into a package-specific
306
+ exception.
307
+
287
308
  Saves use same-directory temporary files and atomic replacement, but the
288
309
  library does not add file locking, coordinate concurrent writers, or promise
289
310
  power-loss durability across every operating system and filesystem.