Skip to content

Commit dd86ac7

Browse files
nuglifeleojiLeo Ji
andauthored
fix: convert zarr Array to numpy before __setitem__ async dispatch (#3857)
* fix: convert zarr Array to numpy before __setitem__ async dispatch When the value passed to Array.__setitem__ is itself a zarr Array, the codec pipeline later calls value[...] from inside an already-running async event loop, which raises SyncError. Convert the value to a NumPy array eagerly before entering the async context to avoid this. Closes #3611 Made-with: Cursor * fix: restore skip marker on test_setitem_repeated_index Inserting test_setitem_zarr_array_as_value between the existing @pytest.mark.skip decorator and test_setitem_repeated_index accidentally moved the skip onto the new test, leaving test_setitem_repeated_index unskipped and failing. Restore the skip to its original target. Made-with: Cursor --------- Co-authored-by: Leo Ji <nuglifeleoji@gmail.com>
1 parent 378ef10 commit dd86ac7

File tree

3 files changed

+25
-0
lines changed

3 files changed

+25
-0
lines changed

changes/3611.bugfix.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix `SyncError` raised when assigning a `zarr.Array` as the value in a `__setitem__` call (e.g. `dst[:] = src` where `src` is a zarr array). The source array is now converted to a NumPy array before entering the async codec pipeline.

src/zarr/core/array.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2930,6 +2930,12 @@ def __setitem__(self, selection: Selection, value: npt.ArrayLike) -> None:
29302930
[blocks][zarr.Array.blocks], [__getitem__][zarr.Array.__getitem__]
29312931
29322932
"""
2933+
# Converting a zarr Array to numpy here avoids a SyncError that occurs when
2934+
# value.__getitem__ is called inside the async codec pipeline (which already
2935+
# runs within a running event loop). np.asarray triggers Array.__array__,
2936+
# which reads the data synchronously before we enter the async context.
2937+
if isinstance(value, Array):
2938+
value = np.asarray(value)
29332939
fields, pure_selection = pop_fields(selection)
29342940
if is_pure_fancy_indexing(pure_selection, self.ndim):
29352941
self.vindex[cast("CoordinateSelection | MaskSelection", selection)] = value

tests/test_indexing.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,24 @@ def test_orthogonal_indexing_fallback_on_getitem_2d(
443443
np.testing.assert_array_equal(z[index], expected_result)
444444

445445

446+
def test_setitem_zarr_array_as_value() -> None:
447+
# Regression test for https://github.com/zarr-developers/zarr-python/issues/3611
448+
# Assigning a zarr Array as the value used to raise
449+
# SyncError("Calling sync() from within a running loop") because the codec
450+
# pipeline tried to index the zarr array inside an already-running async loop.
451+
src = zarr.array(np.arange(10), chunks=(5,))
452+
dst = zarr.zeros(10, chunks=(5,), dtype=src.dtype)
453+
454+
# Full assignment
455+
dst[:] = src
456+
assert_array_equal(dst[:], np.arange(10))
457+
458+
# Slice assignment
459+
dst2 = zarr.zeros(10, chunks=(5,), dtype=src.dtype)
460+
dst2[2:7] = src[2:7]
461+
assert_array_equal(dst2[2:7], np.arange(2, 7))
462+
463+
446464
@pytest.mark.skip(reason="fails on ubuntu, windows; numpy=2.2; in CI")
447465
def test_setitem_repeated_index():
448466
array = zarr.array(data=np.zeros((4,)), chunks=(1,))

0 commit comments

Comments
 (0)