Skip to content

Clarify persistence paths and preserve archive compatibility - #23

Merged
tvanreenen merged 4 commits into
mainfrom
persistence-contracts-0.6.0
Aug 21, 2026
Merged

Clarify persistence paths and preserve archive compatibility#23
tvanreenen merged 4 commits into
mainfrom
persistence-contracts-0.6.0

Conversation

@tvanreenen

Copy link
Copy Markdown
Owner

Why this change

Persistence already had a stable archive shape, but some important boundaries were left implicit. Empty paths could be mistaken for an omitted save() destination, path-like objects were accepted through implementation details rather than an explicit contract, and users had to infer which exception type would surface when opening a bad file. The project also said that format-version-1 archives remained readable without testing an archive produced by an older published release.

This PR makes those behaviors deliberate and testable before 0.6.0. It does not introduce a new archive format or a package-specific exception hierarchy.

What changes

  • open() and save() now explicitly accept strings and string-valued os.PathLike objects, including pathlib.Path and custom path-like types.
  • An explicit empty path raises ValueError instead of being treated like an omitted path.
  • None, bytes, booleans, numbers, and other non-path values passed to open() raise TypeError. Explicit non-path values passed to save() do the same.
  • save(None) keeps its existing meaning: reuse the store's current file binding, or raise ValueError when no binding exists.
  • Extensionless paths continue resolving to .npz for save and open operations.

Archive compatibility and failures

The compatibility suite now opens a checked-in 1,199-byte archive created by the published numpy-vector-store==0.4.0 wheel on Python 3.11 with NumPy 1.23.2. The fixture contains built-in metadata, has a recorded SHA-256 digest, and verifies the complete restored configuration, vectors, metadata, and file binding. Using a real published artifact prevents current writer code from accidentally validating itself.

The compatibility promise remains intentionally one-way and narrow: current releases can read the recorded self-describing format-version-1 archive. This does not restore support for unversioned archives, guarantee that 0.4 can read future formats, or promise portability for pickled application objects whose classes or dependencies are unavailable.

Persistence errors also keep the exception type owned by the failing layer. Filesystem failures remain OSError subclasses, invalid archive schemas remain ValueError, and NumPy, pickle, or application metadata-loading exceptions are allowed to surface without being collapsed into a package-specific wrapper. Exact error text is not a compatibility guarantee.

User impact

Valid persistence calls remain unchanged. Users gain predictable feedback for bad path inputs, can rely on normal string-valued path-like objects, and can catch the native exception that best describes a filesystem, schema, or deserialization failure. Applications that intentionally passed an empty string as if it meant save() without a destination should use save() or save(None) instead.

Because metadata remains pickle-backed, archives are still trusted input and should not be opened from untrusted or unverifiable sources.

Documentation and ancillary changes

  • README persistence guidance now explains accepted path types, omitted versus empty paths, exception ownership, and the exact compatibility boundary.
  • The migration guide records the same rules for users moving through the pre-1.0 API cleanup.
  • The fixture directory includes provenance, generation code, checksum, and explicit non-promises so the binary artifact can be audited and reproduced.

Verification

  • uv run ruff check .
  • uv run ruff format --check .
  • uv run mypy src
  • uv run pytest -q — 294 tests passed on the locked Python 3.13 environment
  • uv run --isolated --python 3.11 --with numpy==1.23.2 --with pytest==8.4.2 --with-editable . pytest -W error -q — 294 tests passed on the minimum supported Python and NumPy boundary
  • The fixture SHA-256 is 570f837ac7652a860fb2ee41abe36a0e028318903ea55e0eeee3daa269119894, matching its recorded provenance.

Commit organization

  1. Define persistence path input contracts.
  2. Preserve archive failure boundaries with focused regression tests.
  3. Add and validate the published 0.4 format-version-1 fixture.
  4. Document the persistence and compatibility boundaries.

@tvanreenen tvanreenen self-assigned this Aug 21, 2026
@tvanreenen
tvanreenen marked this pull request as ready for review August 21, 2026 18:38
@tvanreenen
tvanreenen merged commit 37b4f6c into main Aug 21, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant