Skip to content

Commit 4228a9d

Browse files
authored
Merge branch 'main' into refactor/upstream-apis
2 parents 35537c5 + 9681cf9 commit 4228a9d

47 files changed

Lines changed: 6218 additions & 480 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/docs.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: Docs
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
workflow_dispatch:
9+
10+
permissions:
11+
contents: read
12+
13+
concurrency:
14+
group: ${{ github.workflow }}-${{ github.ref }}
15+
cancel-in-progress: true
16+
17+
jobs:
18+
docs:
19+
name: Check docs
20+
runs-on: ubuntu-latest
21+
steps:
22+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
23+
with:
24+
persist-credentials: false
25+
- uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
26+
- run: uv sync --group docs
27+
- run: uv run mkdocs build
28+
env:
29+
DISABLE_MKDOCS_2_WARNING: "true"
30+
NO_MKDOCS_2_WARNING: "true"
31+
- run: uv run python ci/check_unlinked_types.py
32+
continue-on-error: true

.github/workflows/releases.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
name: Wheels
22

33
on:
4+
release:
5+
types:
6+
- published
47
push:
58
branches: [main]
69
pull_request:
@@ -64,7 +67,7 @@ jobs:
6467
name: Upload to PyPI
6568
needs: [build_artifacts, test_dist_pypi]
6669
runs-on: ubuntu-latest
67-
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/v')
70+
if: github.event_name == 'release'
6871
environment:
6972
name: releases
7073
url: https://pypi.org/p/zarr

.github/workflows/test.yml

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,14 +155,36 @@ jobs:
155155
run: |
156156
hatch run doctest:test
157157
158+
benchmarks:
159+
name: Benchmark smoke test
160+
runs-on: ubuntu-latest
161+
steps:
162+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
163+
with:
164+
fetch-depth: 0
165+
persist-credentials: false
166+
- name: Set up Python
167+
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
168+
with:
169+
python-version: '3.13'
170+
cache: 'pip'
171+
- name: Install Hatch
172+
uses: pypa/hatch@257e27e51a6a5616ed08a39a408a21c35c9931bc
173+
with:
174+
version: '1.16.5'
175+
- name: Run Benchmarks
176+
run: |
177+
hatch env run --env "test.py3.13-minimal" run-benchmark
178+
158179
test-complete:
159180
name: Test complete
160181

161182
needs:
162183
[
163184
test,
164185
test-upstream-and-min-deps,
165-
doctests
186+
doctests,
187+
benchmarks
166188
]
167189
if: always()
168190
runs-on: ubuntu-latest

TEAM.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
- @dstansby (David Stansby)
1111
- @dcherian (Deepak Cherian)
1212
- @TomAugspurger (Tom Augspurger)
13+
- @maxrjones (Max Jones)
1314

1415
## Emeritus core-developers
1516
- @alimanfoo (Alistair Miles)

changes/3802.feature.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
Add support for rectilinear (variable-sized) chunk grids. This feature is experimental and
2+
must be explicitly enabled via ``zarr.config.set({'array.rectilinear_chunks': True})``.
3+
4+
Rectilinear chunks can be used through:
5+
6+
- **Creating arrays**: Pass nested sequences (e.g., ``[[10, 20, 30], [50, 50]]``) to ``chunks``
7+
in ``zarr.create_array``, ``zarr.from_array``, ``zarr.zeros``, ``zarr.ones``, ``zarr.full``,
8+
``zarr.open``, and related functions, or to ``chunk_shape`` in ``zarr.create``.
9+
- **Opening existing arrays**: Arrays stored with the ``rectilinear`` chunk grid are read
10+
transparently via ``zarr.open`` and ``zarr.open_array``.
11+
- **Rectilinear sharding**: Shard boundaries can be rectilinear while inner chunks remain regular.
12+
13+
**Breaking change**: The ``validate`` method on ``BaseCodec`` and ``CodecPipeline`` now receives
14+
a ``ChunkGridMetadata`` instance instead of a ``ChunkGrid`` instance for the ``chunk_grid``
15+
parameter. Third-party codecs that override ``validate`` and inspect the chunk grid will need to
16+
update their type annotations. No known downstream packages were using this parameter.

ci/check_unlinked_types.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
"""Check for unlinked type annotations in built documentation.
2+
3+
mkdocstrings renders resolved types as <a href="..."> links and unresolved
4+
types as <span title="fully.qualified.Name">Name</span> without an anchor.
5+
This script finds all such unlinked types in the built HTML and reports them.
6+
7+
Usage:
8+
python ci/check_unlinked_types.py [site_dir]
9+
10+
Raises ValueError if unlinked types are found.
11+
"""
12+
13+
from __future__ import annotations
14+
15+
import re
16+
import sys
17+
from pathlib import Path
18+
19+
# Matches the griffe/mkdocstrings pattern for unlinked cross-references:
20+
# <span class="n"><span title="fully.qualified.Name">Name</span></span>
21+
UNLINKED_PATTERN = re.compile(
22+
r'<span class="n"><span title="(?P<qualname>[^"]+)">(?P<name>[^<]+)</span></span>'
23+
)
24+
25+
# Patterns to exclude from the report
26+
EXCLUDE_PATTERNS = [
27+
# TypeVars and type parameters (single brackets like Foo[T])
28+
re.compile(r"\[.+\]$"),
29+
# Dataclass field / namedtuple field references (contain parens)
30+
re.compile(r"\("),
31+
# Private names
32+
re.compile(r"\._"),
33+
# Dunder attributes
34+
re.compile(r"\.__\w+__$"),
35+
# Testing utilities
36+
re.compile(r"^zarr\.testing\."),
37+
# Third-party types (hypothesis, pytest, etc.)
38+
re.compile(r"^(hypothesis|pytest|typing_extensions|builtins|dataclasses)\."),
39+
]
40+
41+
42+
def should_exclude(qualname: str) -> bool:
43+
return any(p.search(qualname) for p in EXCLUDE_PATTERNS)
44+
45+
46+
def find_unlinked_types(site_dir: Path) -> dict[str, set[str]]:
47+
"""Find all unlinked types in built HTML files.
48+
49+
Returns a dict mapping qualified type names to the set of pages where they appear.
50+
"""
51+
api_dir = site_dir / "api"
52+
if not api_dir.exists():
53+
raise FileNotFoundError(f"{api_dir} does not exist. Run 'mkdocs build' first.")
54+
55+
unlinked: dict[str, set[str]] = {}
56+
for html_file in api_dir.rglob("*.html"):
57+
content = html_file.read_text(errors="replace")
58+
rel_path = str(html_file.relative_to(site_dir))
59+
for match in UNLINKED_PATTERN.finditer(content):
60+
qualname = match.group("qualname")
61+
if not should_exclude(qualname):
62+
unlinked.setdefault(qualname, set()).add(rel_path)
63+
64+
return unlinked
65+
66+
67+
def main() -> None:
68+
site_dir = Path(sys.argv[1]) if len(sys.argv) > 1 else Path("site")
69+
unlinked = find_unlinked_types(site_dir)
70+
71+
if not unlinked:
72+
print("No unlinked types found.")
73+
return
74+
75+
lines = [f"Found {len(unlinked)} unlinked types:\n"]
76+
for qualname in sorted(unlinked):
77+
pages = sorted(unlinked[qualname])
78+
lines.append(f" {qualname}")
79+
lines.extend(f" - {page}" for page in pages)
80+
81+
all_pages = {p for ps in unlinked.values() for p in ps}
82+
lines.append(f"\nTotal: {len(unlinked)} unlinked types across {len(all_pages)} pages")
83+
report = "\n".join(lines)
84+
raise ValueError(report)
85+
86+
87+
if __name__ == "__main__":
88+
main()

0 commit comments

Comments
 (0)