Skip to content

Commit 7e73301

Browse files
committed
Merge branch 'main' of https://github.com/zarr-developers/zarr-python into fix/_iter_chunk_keys
2 parents 0b99164 + 4eda04e commit 7e73301

File tree

13 files changed

+284
-26
lines changed

13 files changed

+284
-26
lines changed

.github/ISSUE_TEMPLATE/release-checklist.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ assignees: ''
77

88
---
99

10-
**Release**: [v0.x.x](https://github.com/zarr-developers/zarr-python/milestones/?)
10+
**Release**: [v3.x.x](https://github.com/zarr-developers/zarr-python/milestones/?)
1111
**Scheduled Date**: 20YY/MM/DD
1212

1313
**Priority PRs/issues to complete prior to release**
@@ -16,8 +16,9 @@ assignees: ''
1616

1717
**Before release**:
1818

19+
- [ ] Make sure the release branch (e.g., `3.1.x`) is up to date with any backports.
1920
- [ ] Make sure that all pull requests which will be included in the release have been properly documented as changelog files in the [`changes/` directory](https://github.com/zarr-developers/zarr-python/tree/main/changes).
20-
- [ ] Run ``towncrier build --version x.y.z`` to create the changelog, and commit the result to the main branch.
21+
- [ ] Run ``towncrier build --version x.y.z`` to create the changelog, and commit the result to the release branch.
2122
- [ ] Check [SPEC 0](https://scientific-python.org/specs/spec-0000/#support-window) to see if the minimum supported version of Python or NumPy needs bumping.
2223
- [ ] Check to ensure that:
2324
- [ ] Deprecated workarounds/codes/tests are removed. Run `grep "# TODO" **/*.py` to find all potential TODOs.
@@ -41,6 +42,7 @@ assignees: ''
4142
- [ ] Go to https://github.com/zarr-developers/zarr-python/releases.
4243
- [ ] Click "Draft a new release".
4344
- [ ] Choose a version number prefixed with a `v` (e.g. `v0.0.0`). For pre-releases, include the appropriate suffix (e.g. `v0.0.0a1` or `v0.0.0rc2`).
45+
- [ ] Set the target branch to the release branch (e.g., `3.1.x`)
4446
- [ ] Set the description of the release to: `See release notes https://zarr.readthedocs.io/en/stable/release-notes.html#release-0-0-0`, replacing the correct version numbers. For pre-release versions, the URL should omit the pre-release suffix, e.g. "a1" or "rc1".
4547
- [ ] Click on "Generate release notes" to auto-fill the description.
4648
- [ ] Make a release by clicking the 'Publish Release' button, this will automatically create a tag too.

.github/workflows/gpu_test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ name: GPU Test
55

66
on:
77
push:
8-
branches: [ main ]
8+
branches: [ main, 3.1.x ]
99
pull_request:
10-
branches: [ main ]
10+
branches: [ main, 3.1.x ]
1111
workflow_dispatch:
1212

1313
env:

.github/workflows/hypothesis.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
name: Slow Hypothesis CI
22
on:
33
push:
4-
branches: [main, 3.0.x]
4+
branches: [main, 3.1.x]
55
pull_request:
6-
branches: [main, 3.0.x]
6+
branches: [main, 3.1.x]
77
types: [opened, reopened, synchronize, labeled]
88
schedule:
99
- cron: "0 0 * * *" # Daily “At 00:00” UTC

.github/workflows/test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ name: Test
55

66
on:
77
push:
8-
branches: [ main, 3.0.x ]
8+
branches: [ main, 3.1.x ]
99
pull_request:
10-
branches: [ main, 3.0.x ]
10+
branches: [ main, 3.1.x ]
1111
workflow_dispatch:
1212

1313
concurrency:

changes/3083.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added support for async vectorized and orthogonal indexing.

docs/developers/contributing.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,9 +261,12 @@ Pull requests submitted by an external contributor should be reviewed and approv
261261
one core developer before being merged. Ideally, pull requests submitted by a core developer
262262
should be reviewed and approved by at least one other core developer before being merged.
263263

264-
Pull requests should not be merged until all CI checks have passed (GitHub Actions
264+
Pull requests should not be merged until all CI checks have passed (GitHub Actions,
265265
Codecov) against code that has had the latest main merged in.
266266

267+
Before merging the milestone must be set either to decide whether a PR will be in the next
268+
patch, minor, or major release. The next section explains which types of changes go in each release.
269+
267270
Compatibility and versioning policies
268271
-------------------------------------
269272

src/zarr/core/array.py

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
ZarrFormat,
6262
_default_zarr_format,
6363
_warn_order_kwarg,
64+
ceildiv,
6465
concurrent_map,
6566
parse_shapelike,
6667
product,
@@ -76,6 +77,8 @@
7677
)
7778
from zarr.core.dtype.common import HasEndianness, HasItemSize, HasObjectCodec
7879
from zarr.core.indexing import (
80+
AsyncOIndex,
81+
AsyncVIndex,
7982
BasicIndexer,
8083
BasicSelection,
8184
BlockIndex,
@@ -91,7 +94,6 @@
9194
OrthogonalSelection,
9295
Selection,
9396
VIndex,
94-
ceildiv,
9597
check_fields,
9698
check_no_multi_fields,
9799
is_pure_fancy_indexing,
@@ -1564,6 +1566,56 @@ async def getitem(
15641566
)
15651567
return await self._get_selection(indexer, prototype=prototype)
15661568

1569+
async def get_orthogonal_selection(
1570+
self,
1571+
selection: OrthogonalSelection,
1572+
*,
1573+
out: NDBuffer | None = None,
1574+
fields: Fields | None = None,
1575+
prototype: BufferPrototype | None = None,
1576+
) -> NDArrayLikeOrScalar:
1577+
if prototype is None:
1578+
prototype = default_buffer_prototype()
1579+
indexer = OrthogonalIndexer(selection, self.shape, self.metadata.chunk_grid)
1580+
return await self._get_selection(
1581+
indexer=indexer, out=out, fields=fields, prototype=prototype
1582+
)
1583+
1584+
async def get_mask_selection(
1585+
self,
1586+
mask: MaskSelection,
1587+
*,
1588+
out: NDBuffer | None = None,
1589+
fields: Fields | None = None,
1590+
prototype: BufferPrototype | None = None,
1591+
) -> NDArrayLikeOrScalar:
1592+
if prototype is None:
1593+
prototype = default_buffer_prototype()
1594+
indexer = MaskIndexer(mask, self.shape, self.metadata.chunk_grid)
1595+
return await self._get_selection(
1596+
indexer=indexer, out=out, fields=fields, prototype=prototype
1597+
)
1598+
1599+
async def get_coordinate_selection(
1600+
self,
1601+
selection: CoordinateSelection,
1602+
*,
1603+
out: NDBuffer | None = None,
1604+
fields: Fields | None = None,
1605+
prototype: BufferPrototype | None = None,
1606+
) -> NDArrayLikeOrScalar:
1607+
if prototype is None:
1608+
prototype = default_buffer_prototype()
1609+
indexer = CoordinateIndexer(selection, self.shape, self.metadata.chunk_grid)
1610+
out_array = await self._get_selection(
1611+
indexer=indexer, out=out, fields=fields, prototype=prototype
1612+
)
1613+
1614+
if hasattr(out_array, "shape"):
1615+
# restore shape
1616+
out_array = np.array(out_array).reshape(indexer.sel_shape)
1617+
return out_array
1618+
15671619
async def _save_metadata(self, metadata: ArrayMetadata, ensure_parents: bool = False) -> None:
15681620
"""
15691621
Asynchronously save the array metadata.
@@ -1695,6 +1747,19 @@ async def setitem(
16951747
)
16961748
return await self._set_selection(indexer, value, prototype=prototype)
16971749

1750+
@property
1751+
def oindex(self) -> AsyncOIndex[T_ArrayMetadata]:
1752+
"""Shortcut for orthogonal (outer) indexing, see :func:`get_orthogonal_selection` and
1753+
:func:`set_orthogonal_selection` for documentation and examples."""
1754+
return AsyncOIndex(self)
1755+
1756+
@property
1757+
def vindex(self) -> AsyncVIndex[T_ArrayMetadata]:
1758+
"""Shortcut for vectorized (inner) indexing, see :func:`get_coordinate_selection`,
1759+
:func:`set_coordinate_selection`, :func:`get_mask_selection` and
1760+
:func:`set_mask_selection` for documentation and examples."""
1761+
return AsyncVIndex(self)
1762+
16981763
async def resize(self, new_shape: ShapeLike, delete_outside_chunks: bool = True) -> None:
16991764
"""
17001765
Asynchronously resize the array to a new shape.

src/zarr/core/chunk_grids.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@
1818
ChunkCoords,
1919
ChunkCoordsLike,
2020
ShapeLike,
21+
ceildiv,
2122
parse_named_configuration,
2223
parse_shapelike,
2324
)
24-
from zarr.core.indexing import ceildiv
2525

2626
if TYPE_CHECKING:
2727
from collections.abc import Iterator

src/zarr/core/common.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import asyncio
44
import functools
5+
import math
56
import operator
67
import warnings
78
from collections.abc import Iterable, Mapping, Sequence
@@ -69,6 +70,12 @@ def product(tup: ChunkCoords) -> int:
6970
return functools.reduce(operator.mul, tup, 1)
7071

7172

73+
def ceildiv(a: float, b: float) -> int:
74+
if a == 0:
75+
return 0
76+
return math.ceil(a / b)
77+
78+
7279
T = TypeVar("T", bound=tuple[Any, ...])
7380
V = TypeVar("V")
7481

src/zarr/core/indexing.py

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from typing import (
1313
TYPE_CHECKING,
1414
Any,
15+
Generic,
1516
Literal,
1617
NamedTuple,
1718
Protocol,
@@ -25,14 +26,16 @@
2526
import numpy as np
2627
import numpy.typing as npt
2728

28-
from zarr.core.common import product
29+
from zarr.core.common import ceildiv, product
30+
from zarr.core.metadata import T_ArrayMetadata
2931

3032
if TYPE_CHECKING:
31-
from zarr.core.array import Array
33+
from zarr.core.array import Array, AsyncArray
3234
from zarr.core.buffer import NDArrayLikeOrScalar
3335
from zarr.core.chunk_grids import ChunkGrid
3436
from zarr.core.common import ChunkCoords
3537

38+
3639
IntSequence = list[int] | npt.NDArray[np.intp]
3740
ArrayOfIntOrBool = npt.NDArray[np.intp] | npt.NDArray[np.bool_]
3841
BasicSelector = int | slice | EllipsisType
@@ -93,12 +96,6 @@ class Indexer(Protocol):
9396
def __iter__(self) -> Iterator[ChunkProjection]: ...
9497

9598

96-
def ceildiv(a: float, b: float) -> int:
97-
if a == 0:
98-
return 0
99-
return math.ceil(a / b)
100-
101-
10299
_ArrayIndexingOrder: TypeAlias = Literal["lexicographic"]
103100

104101

@@ -1020,6 +1017,25 @@ def __setitem__(self, selection: OrthogonalSelection, value: npt.ArrayLike) -> N
10201017
)
10211018

10221019

1020+
@dataclass(frozen=True)
1021+
class AsyncOIndex(Generic[T_ArrayMetadata]):
1022+
array: AsyncArray[T_ArrayMetadata]
1023+
1024+
async def getitem(self, selection: OrthogonalSelection | Array) -> NDArrayLikeOrScalar:
1025+
from zarr.core.array import Array
1026+
1027+
# if input is a Zarr array, we materialize it now.
1028+
if isinstance(selection, Array):
1029+
selection = _zarr_array_to_int_or_bool_array(selection)
1030+
1031+
fields, new_selection = pop_fields(selection)
1032+
new_selection = ensure_tuple(new_selection)
1033+
new_selection = replace_lists(new_selection)
1034+
return await self.array.get_orthogonal_selection(
1035+
cast(OrthogonalSelection, new_selection), fields=fields
1036+
)
1037+
1038+
10231039
@dataclass(frozen=True)
10241040
class BlockIndexer(Indexer):
10251041
dim_indexers: list[SliceDimIndexer]
@@ -1328,6 +1344,32 @@ def __setitem__(
13281344
raise VindexInvalidSelectionError(new_selection)
13291345

13301346

1347+
@dataclass(frozen=True)
1348+
class AsyncVIndex(Generic[T_ArrayMetadata]):
1349+
array: AsyncArray[T_ArrayMetadata]
1350+
1351+
# TODO: develop Array generic and move zarr.Array[np.intp] | zarr.Array[np.bool_] to ArrayOfIntOrBool
1352+
async def getitem(
1353+
self, selection: CoordinateSelection | MaskSelection | Array
1354+
) -> NDArrayLikeOrScalar:
1355+
# TODO deduplicate these internals with the sync version of getitem
1356+
# TODO requires solving this circular sync issue: https://github.com/zarr-developers/zarr-python/pull/3083#discussion_r2230737448
1357+
from zarr.core.array import Array
1358+
1359+
# if input is a Zarr array, we materialize it now.
1360+
if isinstance(selection, Array):
1361+
selection = _zarr_array_to_int_or_bool_array(selection)
1362+
fields, new_selection = pop_fields(selection)
1363+
new_selection = ensure_tuple(new_selection)
1364+
new_selection = replace_lists(new_selection)
1365+
if is_coordinate_selection(new_selection, self.array.shape):
1366+
return await self.array.get_coordinate_selection(new_selection, fields=fields)
1367+
elif is_mask_selection(new_selection, self.array.shape):
1368+
return await self.array.get_mask_selection(new_selection, fields=fields)
1369+
else:
1370+
raise VindexInvalidSelectionError(new_selection)
1371+
1372+
13311373
def check_fields(fields: Fields | None, dtype: np.dtype[Any]) -> np.dtype[Any]:
13321374
# early out
13331375
if fields is None:

0 commit comments

Comments
 (0)