Skip to content

Commit d3fc21e

Browse files
committed
Merge branch 'main' of github.com:K-Meech/zarr-python into km/v2-v3-conversion
2 parents 42aa7db + 4eda04e commit d3fc21e

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,
@@ -92,7 +95,6 @@
9295
Selection,
9396
VIndex,
9497
_iter_grid,
95-
ceildiv,
9698
check_fields,
9799
check_no_multi_fields,
98100
is_pure_fancy_indexing,
@@ -1425,6 +1427,56 @@ async def getitem(
14251427
)
14261428
return await self._get_selection(indexer, prototype=prototype)
14271429

1430+
async def get_orthogonal_selection(
1431+
self,
1432+
selection: OrthogonalSelection,
1433+
*,
1434+
out: NDBuffer | None = None,
1435+
fields: Fields | None = None,
1436+
prototype: BufferPrototype | None = None,
1437+
) -> NDArrayLikeOrScalar:
1438+
if prototype is None:
1439+
prototype = default_buffer_prototype()
1440+
indexer = OrthogonalIndexer(selection, self.shape, self.metadata.chunk_grid)
1441+
return await self._get_selection(
1442+
indexer=indexer, out=out, fields=fields, prototype=prototype
1443+
)
1444+
1445+
async def get_mask_selection(
1446+
self,
1447+
mask: MaskSelection,
1448+
*,
1449+
out: NDBuffer | None = None,
1450+
fields: Fields | None = None,
1451+
prototype: BufferPrototype | None = None,
1452+
) -> NDArrayLikeOrScalar:
1453+
if prototype is None:
1454+
prototype = default_buffer_prototype()
1455+
indexer = MaskIndexer(mask, self.shape, self.metadata.chunk_grid)
1456+
return await self._get_selection(
1457+
indexer=indexer, out=out, fields=fields, prototype=prototype
1458+
)
1459+
1460+
async def get_coordinate_selection(
1461+
self,
1462+
selection: CoordinateSelection,
1463+
*,
1464+
out: NDBuffer | None = None,
1465+
fields: Fields | None = None,
1466+
prototype: BufferPrototype | None = None,
1467+
) -> NDArrayLikeOrScalar:
1468+
if prototype is None:
1469+
prototype = default_buffer_prototype()
1470+
indexer = CoordinateIndexer(selection, self.shape, self.metadata.chunk_grid)
1471+
out_array = await self._get_selection(
1472+
indexer=indexer, out=out, fields=fields, prototype=prototype
1473+
)
1474+
1475+
if hasattr(out_array, "shape"):
1476+
# restore shape
1477+
out_array = np.array(out_array).reshape(indexer.sel_shape)
1478+
return out_array
1479+
14281480
async def _save_metadata(self, metadata: ArrayMetadata, ensure_parents: bool = False) -> None:
14291481
"""
14301482
Asynchronously save the array metadata.
@@ -1556,6 +1608,19 @@ async def setitem(
15561608
)
15571609
return await self._set_selection(indexer, value, prototype=prototype)
15581610

1611+
@property
1612+
def oindex(self) -> AsyncOIndex[T_ArrayMetadata]:
1613+
"""Shortcut for orthogonal (outer) indexing, see :func:`get_orthogonal_selection` and
1614+
:func:`set_orthogonal_selection` for documentation and examples."""
1615+
return AsyncOIndex(self)
1616+
1617+
@property
1618+
def vindex(self) -> AsyncVIndex[T_ArrayMetadata]:
1619+
"""Shortcut for vectorized (inner) indexing, see :func:`get_coordinate_selection`,
1620+
:func:`set_coordinate_selection`, :func:`get_mask_selection` and
1621+
:func:`set_mask_selection` for documentation and examples."""
1622+
return AsyncVIndex(self)
1623+
15591624
async def resize(self, new_shape: ShapeLike, delete_outside_chunks: bool = True) -> None:
15601625
"""
15611626
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

@@ -960,6 +957,25 @@ def __setitem__(self, selection: OrthogonalSelection, value: npt.ArrayLike) -> N
960957
)
961958

962959

960+
@dataclass(frozen=True)
961+
class AsyncOIndex(Generic[T_ArrayMetadata]):
962+
array: AsyncArray[T_ArrayMetadata]
963+
964+
async def getitem(self, selection: OrthogonalSelection | Array) -> NDArrayLikeOrScalar:
965+
from zarr.core.array import Array
966+
967+
# if input is a Zarr array, we materialize it now.
968+
if isinstance(selection, Array):
969+
selection = _zarr_array_to_int_or_bool_array(selection)
970+
971+
fields, new_selection = pop_fields(selection)
972+
new_selection = ensure_tuple(new_selection)
973+
new_selection = replace_lists(new_selection)
974+
return await self.array.get_orthogonal_selection(
975+
cast(OrthogonalSelection, new_selection), fields=fields
976+
)
977+
978+
963979
@dataclass(frozen=True)
964980
class BlockIndexer(Indexer):
965981
dim_indexers: list[SliceDimIndexer]
@@ -1268,6 +1284,32 @@ def __setitem__(
12681284
raise VindexInvalidSelectionError(new_selection)
12691285

12701286

1287+
@dataclass(frozen=True)
1288+
class AsyncVIndex(Generic[T_ArrayMetadata]):
1289+
array: AsyncArray[T_ArrayMetadata]
1290+
1291+
# TODO: develop Array generic and move zarr.Array[np.intp] | zarr.Array[np.bool_] to ArrayOfIntOrBool
1292+
async def getitem(
1293+
self, selection: CoordinateSelection | MaskSelection | Array
1294+
) -> NDArrayLikeOrScalar:
1295+
# TODO deduplicate these internals with the sync version of getitem
1296+
# TODO requires solving this circular sync issue: https://github.com/zarr-developers/zarr-python/pull/3083#discussion_r2230737448
1297+
from zarr.core.array import Array
1298+
1299+
# if input is a Zarr array, we materialize it now.
1300+
if isinstance(selection, Array):
1301+
selection = _zarr_array_to_int_or_bool_array(selection)
1302+
fields, new_selection = pop_fields(selection)
1303+
new_selection = ensure_tuple(new_selection)
1304+
new_selection = replace_lists(new_selection)
1305+
if is_coordinate_selection(new_selection, self.array.shape):
1306+
return await self.array.get_coordinate_selection(new_selection, fields=fields)
1307+
elif is_mask_selection(new_selection, self.array.shape):
1308+
return await self.array.get_mask_selection(new_selection, fields=fields)
1309+
else:
1310+
raise VindexInvalidSelectionError(new_selection)
1311+
1312+
12711313
def check_fields(fields: Fields | None, dtype: np.dtype[Any]) -> np.dtype[Any]:
12721314
# early out
12731315
if fields is None:

0 commit comments

Comments
 (0)