Guidance for AI coding agents working in this repository.
docs/contributing.md is authoritative for contribution policy; read its Contribution guidelines section and follow it. The rules below restate the parts that bind you directly.
-
Do not attest on your operator's behalf. The pull request template asks the author to confirm they are a human who has reviewed and understood every change. Leave those boxes unchecked, and tell your operator they must check them.
-
Do not open pull requests or issues unattended. Do so only when your operator asks you to, on changes they have reviewed.
-
Label text you composed, and say whether a human read it first. Prefix it with one of:
:robot: _AI-generated text below, from <tool name>. I have read and endorse it._ :robot:— only when your operator actually read it, edited it, and said to post it as-is.:robot: _AI-generated text below, from <tool name>. Not yet reviewed by a human._ :robot:— in every other case, including when you are posting on your operator's behalf without them having read the text.
Do not speak as your operator.
-
Write less. A human reads everything you post here. Say what changed and why it is correct; cut restatement of the diff, hedging, and summary of your own process. If your operator has to trim it before posting, you wrote too much.
-
Do not write review responses. When a reviewer asks a question, your operator answers it, in their own words.
-
A pull request opens with a sentence your operator wrote. Do not write it, and do not draft one for them to paste — the point is that they thought about the change, and a sentence you supplied proves nothing. Leave a placeholder saying so. You may draft the detail that follows, labeled.
-
Attribute your commits with an
Assisted-by: <harness>:<model>trailer. Never add yourself as a commit author orCo-authored-by:— agents assist, humans author.
Keep diffs small and reviewable.
zarr-python (PyPI package zarr) implements chunked, compressed, N-dimensional arrays for Python. This is the 3.x line, which reads and writes both Zarr format v2 and v3 data. Requires Python >= 3.12. The public API is re-exported from src/zarr/__init__.py (Array, Group, create_array, open, etc.).
zarr-python depends on the contents of the Zarr v2 and Zarr v3 storage specifications. We are committed to compliance with the specs, and also consistency with other Zarr implementations, namely:
- TensorStore (C++ / Python)
- Zarrs (Rust)
- Zarrita (Javascript)
Development uses hatch (with uv as the installer) for managed environments, and uv directly for ad-hoc commands. The canonical test environments are named test.py3.{12,13,14}-{minimal,optional}; optional pulls in remote stores (fsspec, obstore, s3fs), the CLI, and universal-pathlib.
# Run the full test suite in a managed env (benchmarks excluded by default)
hatch env run --env test.py3.12-optional run
# Run with coverage (XML report); coverage must reach 100% for CI to pass
hatch env run --env test.py3.12-optional run-coverage
# Ad-hoc test runs with uv (faster iteration than spinning up a hatch env).
# Prefer uv run pytest for narrow runs; reach for hatch envs for full/coverage runs.
uv run pytest tests/test_array.py # one test file
uv run pytest tests/test_array.py::test_name # one test function
uv run pytest tests/test_array.py -k "expr" # tests matching a -k expression
uv run pytest "tests/test_array.py::test_name[param-id]" # one parametrized case
uv run pytest tests/test_array.py -x --lf # stop on first failure, rerun last-failed
# Run tests in parallel across CPUs with pytest-xdist (-n). Big speedup for the
# full suite; for a handful of tests the worker startup cost usually isn't worth it.
uv run pytest -n auto tests/ # auto = one worker per core
uv run pytest -n 4 tests/test_codecs/ # fixed worker count
# Type-check (strict mypy over src + tests) — this is what CI's Lint job runs
uv run --frozen mypy
# Hypothesis property tests (slow; opt in)
hatch env run --env test.py3.12-optional run-hypothesis
# Docs: live-reloading server / strict build
hatch --env docs run serve
hatch --env docs run checkNote: pytest testpaths include src, tests, and docs/user-guide, and --doctest-modules is on — docstrings in src/ are executed as tests, and the user-guide markdown is doctested. xfail_strict = true and filterwarnings = ["error", ...] mean an unexpected pass or an unfiltered warning fails the suite.
The project uses prek (a drop-in pre-commit runner) against .pre-commit-config.yaml. Ruff (lint + format, line length 100) and mypy run here, plus codespell, numpydoc validation, towncrier-check, and zizmor.
prek run --all-files # all hooks
prek run --last-commit # only files changed in the last commitTwo local rules worth knowing because they will reject otherwise-valid code:
- No
.lstrip("...")/.rstrip("...")with multi-char string args (ban-lstrip-rstrip) — these are character-set operations and almost always a bug whereremoveprefix/removesuffixwas intended. - numpydoc validation is enforced on docstrings (Parameters/Returns sections must match signatures).
Every user-facing change needs a news fragment in changes/ named {issue-or-pr-number}.{type}.md, where type is one of feature, bugfix, doc, removal, misc. Generated into docs/release-notes.md by towncrier at release time. towncrier create scaffolds one. The towncrier-check pre-commit hook will flag PRs missing a fragment.
- Public API surface: anything added to
__all__in__init__.py/api/synchronous.pyis public and must have a numpydoc docstring and an entry underdocs/api/*.md. New user-facing behavior also belongs indocs/user-guide/. - Experimental features go under
src/zarr/experimental/, are documented indocs/user-guide/experimental.md, and carry no stability guarantees (may be removed in any release). The team aims to promote or remove them within ~6 months. - Versioning is EffVer, not SemVer — breaking changes are possible in minor (and rarely patch) releases, judged by upgrade effort. Prefer backwards-compatible changes and deprecation warnings over hard breaks.