numpy-vector-store 0.3.1__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 (22) hide show
  1. numpy_vector_store-0.4.0/.github/workflows/checks.yml +114 -0
  2. {numpy_vector_store-0.3.1 → numpy_vector_store-0.4.0}/.github/workflows/publish-pypi.yml +5 -0
  3. {numpy_vector_store-0.3.1 → numpy_vector_store-0.4.0}/.github/workflows/publish-testpypi.yml +5 -0
  4. numpy_vector_store-0.4.0/CHANGELOG.md +160 -0
  5. numpy_vector_store-0.4.0/MIGRATION.md +169 -0
  6. {numpy_vector_store-0.3.1 → numpy_vector_store-0.4.0}/PKG-INFO +127 -36
  7. numpy_vector_store-0.4.0/README.md +326 -0
  8. numpy_vector_store-0.4.0/ROADMAP.md +225 -0
  9. {numpy_vector_store-0.3.1 → numpy_vector_store-0.4.0}/pyproject.toml +7 -5
  10. {numpy_vector_store-0.3.1 → numpy_vector_store-0.4.0}/src/numpy_vector_store/__init__.py +1 -1
  11. numpy_vector_store-0.4.0/src/numpy_vector_store/vector_store.py +723 -0
  12. numpy_vector_store-0.4.0/tests/test_vector_store.py +1559 -0
  13. {numpy_vector_store-0.3.1 → numpy_vector_store-0.4.0}/uv.lock +4 -110
  14. numpy_vector_store-0.3.1/README.md +0 -234
  15. numpy_vector_store-0.3.1/src/numpy_vector_store/vector_store.py +0 -419
  16. numpy_vector_store-0.3.1/tests/test_vector_store.py +0 -693
  17. {numpy_vector_store-0.3.1 → numpy_vector_store-0.4.0}/.github/FUNDING.yml +0 -0
  18. {numpy_vector_store-0.3.1 → numpy_vector_store-0.4.0}/.gitignore +0 -0
  19. {numpy_vector_store-0.3.1 → numpy_vector_store-0.4.0}/LICENSE +0 -0
  20. {numpy_vector_store-0.3.1 → numpy_vector_store-0.4.0}/justfile +0 -0
  21. {numpy_vector_store-0.3.1 → numpy_vector_store-0.4.0}/src/numpy_vector_store/py.typed +0 -0
  22. {numpy_vector_store-0.3.1 → numpy_vector_store-0.4.0}/tests/__init__.py +0 -0
@@ -0,0 +1,114 @@
1
+ name: Checks
2
+
3
+ on:
4
+ workflow_call:
5
+ pull_request:
6
+ push:
7
+ branches:
8
+ - main
9
+ workflow_dispatch:
10
+
11
+ concurrency:
12
+ group: checks-${{ github.workflow }}-${{ github.ref }}
13
+ cancel-in-progress: true
14
+
15
+ permissions:
16
+ contents: read
17
+
18
+ jobs:
19
+ quality:
20
+ name: Lint, format, and type-check
21
+ runs-on: ubuntu-latest
22
+
23
+ steps:
24
+ - name: Check out repository
25
+ uses: actions/checkout@v6
26
+
27
+ - name: Set up Python
28
+ uses: actions/setup-python@v6
29
+ with:
30
+ python-version: "3.13"
31
+
32
+ - name: Set up uv
33
+ uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
34
+ with:
35
+ version: "0.11.32"
36
+ python-version: "3.13"
37
+ enable-cache: true
38
+
39
+ - name: Install project dependencies
40
+ run: uv sync --locked --group dev
41
+
42
+ - name: Run Ruff lint checks
43
+ run: uv run --locked ruff check .
44
+
45
+ - name: Check Ruff formatting
46
+ run: uv run --locked ruff format --check .
47
+
48
+ - name: Run mypy
49
+ run: uv run --locked mypy src/
50
+
51
+ tests:
52
+ name: Test Python ${{ matrix.python-version }}
53
+ runs-on: ubuntu-latest
54
+ strategy:
55
+ fail-fast: false
56
+ matrix:
57
+ python-version:
58
+ - "3.11"
59
+ - "3.12"
60
+ - "3.13"
61
+ - "3.14"
62
+
63
+ steps:
64
+ - name: Check out repository
65
+ uses: actions/checkout@v6
66
+
67
+ - name: Set up Python
68
+ uses: actions/setup-python@v6
69
+ with:
70
+ python-version: ${{ matrix.python-version }}
71
+
72
+ - name: Set up uv
73
+ uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
74
+ with:
75
+ version: "0.11.32"
76
+ python-version: ${{ matrix.python-version }}
77
+ enable-cache: true
78
+
79
+ - name: Install project dependencies
80
+ run: uv sync --locked --group dev
81
+
82
+ - name: Run tests
83
+ run: uv run --locked pytest
84
+
85
+ minimum-numpy:
86
+ name: Test minimum NumPy on Python 3.11
87
+ runs-on: ubuntu-latest
88
+
89
+ steps:
90
+ - name: Check out repository
91
+ uses: actions/checkout@v6
92
+
93
+ - name: Set up Python
94
+ uses: actions/setup-python@v6
95
+ with:
96
+ python-version: "3.11"
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.11"
103
+ enable-cache: true
104
+
105
+ - name: Run tests with the minimum NumPy version
106
+ run: >-
107
+ uv run
108
+ --isolated
109
+ --no-project
110
+ --python 3.11
111
+ --with-editable .
112
+ --with numpy==1.23.2
113
+ --with pytest==8.4.2
114
+ python -m pytest
@@ -6,8 +6,13 @@ on:
6
6
  - published
7
7
 
8
8
  jobs:
9
+ validate:
10
+ name: Validate release
11
+ uses: ./.github/workflows/checks.yml
12
+
9
13
  publish-pypi:
10
14
  name: Publish to PyPI
15
+ needs: validate
11
16
  runs-on: ubuntu-latest
12
17
  permissions:
13
18
  contents: read
@@ -4,8 +4,13 @@ on:
4
4
  workflow_dispatch:
5
5
 
6
6
  jobs:
7
+ validate:
8
+ name: Validate release candidate
9
+ uses: ./.github/workflows/checks.yml
10
+
7
11
  publish-testpypi:
8
12
  name: Publish to TestPyPI
13
+ needs: validate
9
14
  runs-on: ubuntu-latest
10
15
  permissions:
11
16
  contents: read
@@ -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.