Skip to content

Commit d754108

Browse files
d-v-bclaude
andcommitted
ci: make the in-tree zarr-metadata resolvable by every environment
The zarr-metadata model layer that zarr now depends on is unreleased, and the previous wiring only covered uv project flows, breaking everything else: - The floor 'zarr-metadata>=0.4.0' is unresolvable from PyPI (only <0.4.0 exists), failing every hatch env (test matrix, doctests, benchmarks, hypothesis, min_deps, upstream) and the readthedocs build. The floor now names the newest release (>=0.3) with a TODO to bump at release time, and each non-uv flow installs the in-tree package explicitly: hatch envs get a '{root:uri}/packages/zarr-metadata' direct reference (repeated per env, since hatch replaces extra-dependencies on inheritance), readthedocs gets an extra pip install step. - uv workspace membership made 'uv sync' inside packages/zarr-metadata resolve the whole workspace, dragging zarr's requires-python (>=3.12) into the subpackage's own CI, which tests down to 3.11. The workspace is now a plain editable path source, restoring the subpackage's independence. - min_deps pins typing_extensions==4.16.* (was 4.14.*): 4.16 is the effective minimum because zarr-metadata requires it for sentinel pickling. Also fixes the Lint (mypy) findings in the new tests — a bare generic Array annotation, stale type-ignores, and two sentinel comparisons mypy cannot narrow (python/mypy#21647) — and renames the changelog entries to the PR number (4129) required by the changelog check. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent fde5dfb commit d754108

7 files changed

Lines changed: 55 additions & 28 deletions

File tree

.readthedocs.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ build:
88
install:
99
- pip install --upgrade pip
1010
- pip install .[remote] --group docs
11+
# The in-tree zarr-metadata: zarr needs the unreleased model layer
12+
# (ships in zarr-metadata 0.4.0); the project install above resolves
13+
# the released 0.3.x from PyPI, which lacks it. Remove once the
14+
# zarr-metadata floor in pyproject.toml is >=0.4.0.
15+
- pip install ./packages/zarr-metadata
1116
pre_build:
1217
- |
1318
if [ "$READTHEDOCS_VERSION_TYPE" != "tag" ];
File renamed without changes.
File renamed without changes.

pyproject.toml

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,14 @@ dependencies = [
3737
'google-crc32c>=1.5',
3838
'typing_extensions>=4.14',
3939
'donfig>=0.8',
40-
'zarr-metadata>=0.4.0',
40+
# zarr needs the zarr-metadata model layer, which first ships in
41+
# zarr-metadata 0.4.0 (not yet released). Until then the floor names the
42+
# newest release on PyPI so plain-pip resolvers can proceed, and every
43+
# dev/CI environment installs the in-tree package instead: uv flows via
44+
# [tool.uv.sources], hatch envs via the `{root:uri}` direct reference in
45+
# their extra-dependencies, readthedocs via an extra install step.
46+
# TODO(release): bump to 'zarr-metadata>=0.4.0' once it is released.
47+
'zarr-metadata>=0.3',
4148
]
4249

4350
dynamic = [
@@ -148,15 +155,15 @@ dev = [
148155
"mypy==2.1.0",
149156
]
150157

151-
# The repo is a uv workspace so that `zarr` can depend on the in-tree
152-
# `packages/zarr-metadata` during development and CI. Released wheels depend on
153-
# the published zarr-metadata distribution; the workspace source below only
154-
# affects resolution from a checkout (uv.lock, `uv run`, hatch envs using uv).
155-
[tool.uv.workspace]
156-
members = ["packages/zarr-metadata"]
157-
158+
# uv flows from a checkout (uv.lock, `uv sync`, `uv run`) resolve
159+
# zarr-metadata to the in-tree package. This is a path source, deliberately
160+
# NOT a uv workspace: workspace membership would make `uv sync` inside
161+
# packages/zarr-metadata resolve the whole workspace, dragging zarr's
162+
# requires-python (>=3.12) and dependency set into the subpackage's own CI
163+
# (which tests down to Python 3.11). Released wheels depend on the published
164+
# zarr-metadata distribution; this source only affects checkouts.
158165
[tool.uv.sources]
159-
zarr-metadata = { workspace = true }
166+
zarr-metadata = { path = "packages/zarr-metadata", editable = true }
160167

161168
[tool.coverage.report]
162169
exclude_also = [
@@ -180,11 +187,22 @@ version.raw-options = { git_describe_command = "git describe --dirty --tags --lo
180187
[tool.hatch.build]
181188
hooks.vcs.version-file = "src/zarr/_version.py"
182189

190+
# Hatch envs resolve project dependencies from PyPI and do not read
191+
# [tool.uv.sources], so each env that defines its own environment installs the
192+
# in-tree zarr-metadata via a direct reference. Hatch replaces (does not
193+
# merge) extra-dependencies on inheritance, so every env repeats the line.
194+
# Remove these once zarr-metadata 0.4.0 is on PyPI and the floor is bumped.
183195
[tool.hatch.envs.dev]
184196
dependency-groups = ["dev"]
197+
extra-dependencies = [
198+
"zarr-metadata @ {root:uri}/packages/zarr-metadata",
199+
]
185200

186201
[tool.hatch.envs.test]
187202
dependency-groups = ["test"]
203+
extra-dependencies = [
204+
"zarr-metadata @ {root:uri}/packages/zarr-metadata",
205+
]
188206

189207
[tool.hatch.envs.test.env-vars]
190208

@@ -225,6 +243,7 @@ list-env = "pip list"
225243
[tool.hatch.envs.gputest]
226244
template = "test"
227245
extra-dependencies = [
246+
"zarr-metadata @ {root:uri}/packages/zarr-metadata",
228247
"universal_pathlib",
229248
# Needed so tests/test_docs.py is collectable under `pytest -m gpu`; otherwise its
230249
# module-level importorskip("pytest_examples") skips the whole module and the gpu
@@ -247,6 +266,7 @@ run = "pytest -m gpu --ignore tests/benchmarks"
247266
template = 'test'
248267
python = "3.14"
249268
extra-dependencies = [
269+
'zarr-metadata @ {root:uri}/packages/zarr-metadata',
250270
'packaging @ git+https://github.com/pypa/packaging',
251271
'numpy', # from scientific-python-nightly-wheels
252272
'numcodecs @ git+https://github.com/zarr-developers/numcodecs',
@@ -272,13 +292,16 @@ python = "3.12"
272292
features = ["remote"]
273293
dependency-groups = ["remote-tests"]
274294
extra-dependencies = [
295+
'zarr-metadata @ {root:uri}/packages/zarr-metadata',
275296
'packaging==22.*',
276297
'numpy==2.0.*',
277298
'numcodecs==0.14.*', # 0.14 needed for zarr3 codecs
278299
'fsspec==2023.10.0',
279300
's3fs==2023.10.0',
280301
'universal_pathlib==0.2.0',
281-
'typing_extensions==4.14.*',
302+
# 4.16 is the effective minimum: zarr-metadata requires >=4.16 for
303+
# reference-picklable PEP 661 sentinels.
304+
'typing_extensions==4.16.*',
282305
'donfig==0.8.*',
283306
'obstore==0.5.*',
284307
]
@@ -289,6 +312,9 @@ installer = "uv"
289312
[tool.hatch.envs.docs]
290313
features = ['remote']
291314
dependency-groups = ['docs']
315+
extra-dependencies = [
316+
"zarr-metadata @ {root:uri}/packages/zarr-metadata",
317+
]
292318

293319
[tool.hatch.envs.docs.env-vars]
294320
DISABLE_MKDOCS_2_WARNING = "true"
@@ -305,6 +331,7 @@ description = "Test environment for validating executable code blocks in documen
305331
features = ['remote']
306332
dependency-groups = ['remote-tests']
307333
extra-dependencies = [
334+
"zarr-metadata @ {root:uri}/packages/zarr-metadata",
308335
"pytest-examples",
309336
]
310337

tests/test_future_metadata.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@
2929
if TYPE_CHECKING:
3030
from collections.abc import Callable
3131

32-
from zarr import Array, Group
32+
from zarr import Group
3333
from zarr.core.common import ZarrFormat
34+
from zarr.types import AnyArray
3435

3536

3637
@pytest.mark.parametrize("zarr_format", [2, 3])
@@ -186,7 +187,7 @@ def test_attrs_interface_does_not_warn() -> None:
186187

187188

188189
@pytest.mark.parametrize("node_factory", NODE_CASES.values(), ids=NODE_CASES.keys())
189-
def test_node_picklable_with_populated_cache(node_factory: Callable[[], Array | Group]) -> None:
190+
def test_node_picklable_with_populated_cache(node_factory: Callable[[], AnyArray | Group]) -> None:
190191
"""A node whose _future_metadata cache is populated pickles, and the
191192
restored node re-derives an equal model. The cache itself is derived
192193
state and is excluded from pickled state."""

tests/test_metadata/test_model.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
ZarrV2ArrayMetadata,
1515
ZarrV2GroupMetadata,
1616
ZarrV3ArrayMetadata,
17+
ZarrV3ConsolidatedMetadata,
1718
ZarrV3GroupMetadata,
1819
ZarrV3NamedConfig,
1920
)
@@ -34,7 +35,7 @@
3435

3536

3637
@pytest.mark.filterwarnings("ignore::zarr.core.dtype.common.UnstableSpecificationWarning")
37-
@given(metadata=array_metadata()) # type: ignore[misc]
38+
@given(metadata=array_metadata())
3839
@settings(max_examples=100)
3940
def test_array_metadata_round_trip(metadata: ArrayV2Metadata | ArrayV3Metadata) -> None:
4041
"""Runtime -> model -> runtime is the identity (runtime equality compares
@@ -48,7 +49,7 @@ def test_array_metadata_round_trip(metadata: ArrayV2Metadata | ArrayV3Metadata)
4849

4950

5051
@pytest.mark.filterwarnings("ignore::zarr.core.dtype.common.UnstableSpecificationWarning")
51-
@given(metadata=array_metadata()) # type: ignore[misc]
52+
@given(metadata=array_metadata())
5253
@settings(max_examples=100)
5354
def test_array_metadata_model_stable(metadata: ArrayV2Metadata | ArrayV3Metadata) -> None:
5455
"""The model's own document form re-parses to an equal model: conversion
@@ -133,7 +134,10 @@ def test_group_metadata_v3_round_trip(attributes: dict[str, object]) -> None:
133134
metadata = GroupMetadata(attributes=attributes, zarr_format=3)
134135
model = group_metadata_to_model(metadata)
135136
assert isinstance(model, ZarrV3GroupMetadata)
136-
assert model.consolidated_metadata is UNSET
137+
# Absence is asserted on the document form rather than via `is UNSET`:
138+
# mypy lacks PEP 661 sentinel narrowing (python/mypy#21647) and marks
139+
# statements after a sentinel check unreachable.
140+
assert "consolidated_metadata" not in model.to_json()
137141
assert group_metadata_from_model(model) == metadata
138142

139143

@@ -175,7 +179,9 @@ def test_group_metadata_v3_consolidated_round_trip() -> None:
175179
)
176180
model = group_metadata_to_model(metadata)
177181
assert isinstance(model, ZarrV3GroupMetadata)
178-
assert model.consolidated_metadata is not UNSET
182+
# isinstance rather than `is not UNSET`: mypy lacks PEP 661 sentinel
183+
# narrowing (python/mypy#21647).
184+
assert isinstance(model.consolidated_metadata, ZarrV3ConsolidatedMetadata)
179185
# The model holds consolidated entries flat, keyed by full path.
180186
assert set(model.consolidated_metadata.metadata) == {
181187
"child-group",

uv.lock

Lines changed: 0 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)