Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions .github/workflows/zarr-storage-extraction.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
name: zarr-storage extraction

on:
pull_request:
paths:
- 'packages/zarr-storage/**'
- 'src/zarr/abc/store.py'
- 'src/zarr/core/_coalesce.py'
- 'src/zarr/storage/**'
- 'src/zarr/testing/**'
- 'src/zarr/experimental/cache_store.py'
- 'tests/test_store/**'
- 'tests/test_experimental/test_cache_store.py'
- '.github/workflows/zarr-storage-extraction.yml'
push:
branches: [main]
paths:
- 'packages/zarr-storage/**'
- 'src/zarr/abc/store.py'
- 'src/zarr/core/_coalesce.py'
- 'src/zarr/storage/**'
- 'src/zarr/testing/**'
- 'src/zarr/experimental/cache_store.py'
- 'tests/test_store/**'
- 'tests/test_experimental/test_cache_store.py'
- '.github/workflows/zarr-storage-extraction.yml'
workflow_dispatch:

permissions:
contents: read

jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- python-version: '3.12'
dependencies: minimal
- python-version: '3.14'
dependencies: minimal
- python-version: '3.12'
dependencies: optional
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
- name: Install Python and Hatch
run: |
uv python install ${{ matrix.python-version }}
uv tool install hatch==1.18.0
- name: Test extracted interfaces against this checkout
env:
PYTHONPATH: packages/zarr-storage/src
run: >-
hatch run test.py${{ matrix.python-version }}-${{ matrix.dependencies }}:pytest
packages/zarr-storage/tests --import-mode=importlib
- name: Build storage distributions
working-directory: packages/zarr-storage
run: hatch build
- name: Test built wheels
env:
PYTHONPATH: packages/zarr-storage/dist/zarr_storage-0.1.0-py3-none-any.whl
run: >-
hatch run test.py${{ matrix.python-version }}-${{ matrix.dependencies }}:pytest
packages/zarr-storage/tests --import-mode=importlib
- name: Run stateful store tests
if: matrix.dependencies == 'optional'
env:
PYTHONPATH: packages/zarr-storage/dist/zarr_storage-0.1.0-py3-none-any.whl
run: >-
hatch run test.py${{ matrix.python-version }}-${{ matrix.dependencies }}:pytest
packages/zarr-storage/tests/test_store/test_stateful.py --run-slow-hypothesis
9 changes: 9 additions & 0 deletions packages/zarr-storage/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# zarr-storage changelog

## Unreleased

- Extract the existing storage interfaces, concrete implementations, and accessory
stores into `zarr_storage.legacy`, preserving Zarr's current runtime imports.
- Include the store and experimental cache-store suites and distribute reusable
conformance tests and stateful testing utilities in `zarr_storage.testing`.
- Make `LatencyStore` usable without importing pytest.
21 changes: 21 additions & 0 deletions packages/zarr-storage/LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2015-2025 Zarr Developers <https://github.com/zarr-developers>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
80 changes: 80 additions & 0 deletions packages/zarr-storage/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# zarr-storage

An extraction of Zarr-Python's existing storage layer into the `legacy`
namespace, with concrete stores, wrappers, and reusable conformance tests.

```python
from zarr.core.buffer import default_buffer_prototype
from zarr_storage.legacy import LatencyStore, MemoryStore, WrapperStore

store = LatencyStore(MemoryStore(), get_latency=0.01)
```

## Included APIs

- The `Store` ABC, byte requests, byte getter/setter protocols, and sync capabilities.
- `MemoryStore`, `ManagedMemoryStore`, `GpuMemoryStore`, `LocalStore`, `ZipStore`,
`FsspecStore`, and `ObjectStore`.
- `WrapperStore`, `LoggingStore`, and `LatencyStore`.
- `StorePath`, store construction/path utilities, and byte-range coalescing.
- Experimental `CacheStore` at `zarr_storage.legacy.experimental.cache_store`.
- `zarr_storage.testing.StoreTests`, state machines, strategies, buffer fixtures,
and assertion helpers for third-party implementations.

`LatencyStore` is available without pytest. The conformance utilities are optional:
install `zarr-storage[testing]` to use them. Install `zarr-storage[remote]` for
fsspec and obstore backends. GPU execution additionally requires a suitable CuPy
installation and hardware, as it does in Zarr-Python.

## Status and compatibility

This is an experimental extraction draft. The source comes from Zarr-Python's
`src/zarr/abc/store.py`, `src/zarr/storage`, and storage-related testing utilities
at commit `9c29a0da9`. Imports are redirected to the extracted implementations;
legacy signatures, inherited behavior, and async execution are preserved.
Future APIs can coexist under a different namespace. No new storage contract or
deprecation is introduced here.

**There is still a runtime dependency on `zarr`.** Shared buffers, configuration,
concurrency, metadata-aware IO helpers, and sync utilities remain there. This
package must not become a dependency of `zarr` until that dependency is removed.
The tested compatibility baseline is this source checkout, not every published
release in the declared dependency range.

The extracted classes have distinct identities. Importing this package does not
change Zarr's imports or make its array entry points accept these stores. At
runtime adoption, the old Zarr import paths should re-export one canonical
implementation. Until then, use these stores through their storage APIs.

## Tests

The package includes the full `tests/test_store` suite and the experimental
cache-store suite, redirected to the extracted implementations. The original
Zarr tests remain in place. Tests cover sync/async IO, ranges, listing, lifecycle,
read-only behavior, pickling, wrappers, caching, and array/group integration.

The integration tests have an explicit, test-only fixture that rebinds Zarr's
storage references to the extracted implementations, simulating future
re-exports. They use real stores and real arrays; no IO methods are mocked by
that fixture. Bindings are restored after each test. Import-isolation tests
separately verify that normal package imports leave Zarr unchanged.

From the repository root:

```sh
# Core stores; optional-backend and GPU tests skip when dependencies are absent.
PYTHONPATH=packages/zarr-storage/src hatch run test.py3.12-minimal:pytest packages/zarr-storage/tests

# Includes fsspec/obstore tests and a local moto S3 server.
PYTHONPATH=packages/zarr-storage/src hatch run test.py3.12-optional:pytest packages/zarr-storage/tests

# Stateful tests retain the upstream opt-in flag.
PYTHONPATH=packages/zarr-storage/src hatch run test.py3.12-minimal:pytest packages/zarr-storage/tests/test_store/test_stateful.py --run-slow-hypothesis
```

Unsupported backend operations retain their upstream skips/xfails. The standalone
package suite uses a 50-example Hypothesis profile with no deadline; select a
registered profile via `HYPOTHESIS_PROFILE` to override it.

Build from this directory with `hatch build`. CI runs source and wheel tests on
Python 3.12/3.14, with an additional optional-backend job on Python 3.12.
17 changes: 17 additions & 0 deletions packages/zarr-storage/docs/api/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Legacy storage API

::: zarr_storage.legacy

## Experimental cache

::: zarr_storage.legacy.experimental.cache_store.CacheStore

## Conformance utilities

Install `zarr-storage[testing]` to use these utilities.

::: zarr_storage.testing.StoreTests

::: zarr_storage.testing.stateful.ZarrStoreStateMachine

::: zarr_storage.testing.stateful.ZarrHierarchyStateMachine
75 changes: 75 additions & 0 deletions packages/zarr-storage/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
[build-system]
requires = ["hatchling>=1.29.0"]
build-backend = "hatchling.build"

[project]
name = "zarr-storage"
version = "0.1.0"
description = "Legacy Zarr stores, interfaces, and reusable conformance tests."
readme = "README.md"
requires-python = ">=3.12"
license = "MIT"
license-files = ["LICENSE.txt"]
authors = [{ name = "Davis Bennett", email = "davis.v.bennett@gmail.com" }]
# Temporary: legacy interfaces still consume Zarr's shared foundations.
# Remove this dependency before making zarr depend on this distribution.
dependencies = ["zarr>=3.3,<3.4", "numpy>=2", "typing_extensions>=4.14"]

[project.optional-dependencies]
remote = ["fsspec>=2023.10.0", "obstore>=0.5.1"]
testing = ["pytest>=9.1", "pytest-asyncio>=1.0", "hypothesis>=6.160.0"]

[project.urls]
Source = "https://github.com/zarr-developers/zarr-python/tree/main/packages/zarr-storage"
Issues = "https://github.com/zarr-developers/zarr-python/issues"

[tool.hatch.build.targets.wheel]
packages = ["src/zarr_storage"]

[tool.hatch.build.targets.sdist]
include = ["/src", "/tests", "/docs", "/CHANGELOG.md"]

[tool.pytest.ini_options]
addopts = ["--import-mode=importlib"]
strict = true
faulthandler_timeout = 600
faulthandler_exit_on_timeout = true
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "function"
filterwarnings = [
"error",
"ignore:Unclosed client session <aiohttp.client.ClientSession.*:ResourceWarning",
"ignore:Numcodecs codecs are not in the Zarr version 3 specification.*:UserWarning",
# s3fs finalizers can fail during session cleanup when aiobotocore sessions are garbage
# collected without being entered. This is a known issue in s3fs/aiobotocore, and pytest
# per-test filterwarnings markers can't catch it (https://github.com/pytest-dev/pytest/issues/14096).
"ignore:Exception ignored ((on calling weakref callback)|(in[\\s\\S]*Session was never entered)):pytest.PytestUnraisableExceptionWarning",
# pytest-asyncio implicitly creates an event loop in _get_event_loop_no_warn during
# fixture setup/teardown and never closes it (allocation site verified with
# PYTHONTRACEMALLOC: pytest_asyncio/plugin.py). When the garbage collector reclaims
# that loop (and its self-pipe socketpair: AF_UNIX family=1 on POSIX, emulated with
# AF_INET family=2 on Windows) mid-test, the unraisable hook fails whichever unrelated
# test happens to be running — the long-standing "random cross-file failure" in the
# pipeline suites. The message contains only the __del__ repr, so these patterns cannot
# scope to pytest-asyncio specifically: a loop/socketpair leak in zarr's own sync
# machinery would also be silenced. Accepted tradeoff — revisit if zarr.core.sync grows
# loop-lifecycle changes.
"ignore:Exception ignored in[\\s\\S]*<function BaseEventLoop.__del__:pytest.PytestUnraisableExceptionWarning",
"ignore:Exception ignored in[\\s\\S]*<socket.socket fd=\\d+, family=(1|2), type=1:pytest.PytestUnraisableExceptionWarning",
]
markers = [
"gpu: requires CuPy and a GPU",
"s3: requires a mock S3 backend via moto",
"slow_hypothesis: opt-in stateful tests",
]

[tool.ruff]
extend = "../../pyproject.toml"

[tool.ruff.lint.extend-per-file-ignores]
"tests/test_store/test_object.py" = ["E402"]
"tests/test_store/test_local.py" = ["SIM117"]

# Match the parent project's validation of the verbatim legacy docstrings.
[tool.numpydoc_validation]
checks = ["GL10", "SS04", "PR02", "PR03", "PR05", "PR06"]
5 changes: 5 additions & 0 deletions packages/zarr-storage/src/zarr_storage/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""Extracted Zarr storage interfaces; see :mod:`zarr_storage.legacy`."""

from zarr_storage import legacy

__all__ = ["legacy"]
Loading
Loading