This is an automated email from the ASF dual-hosted git repository.

paleolimbot pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-nanoarrow.git


The following commit(s) were added to refs/heads/main by this push:
     new 65d20676 fix(python): Support iterating map arrays as Python objects 
(#998)
65d20676 is described below

commit 65d2067606289e5dc3d1e25f7dbf27c224cf04ad
Author: Edgar Ramírez Mondragón 
<[email protected]>
AuthorDate: Thu Oct 29 10:43:34 2026 -0600

    fix(python): Support iterating map arrays as Python objects (#998)
    
    Adds support for iterating map arrays:
    
    ```python
    # mre_map.py
    
    # /// script
    # requires-python = ">=3.10"
    # dependencies = [
    #     "nanoarrow",
    #     "pyarrow",
    # ]
    # ///
    """Iterating a map array with nanoarrow fails."""
    
    import nanoarrow as na
    import pyarrow as pa
    
    array = pa.array(
        [[("a", 1), ("b", None)], [], None, [("c", 3)]],
        type=pa.map_(pa.string(), pa.int64()),
    )
    
    print(f"nanoarrow {na.__version__}, pyarrow {pa.__version__}")
    print(na.Array(array).to_pylist())
    ```
    
    Run it with `uv run --no-project mre_map.py`. The output is:
    
    ```
    nanoarrow 0.9.0, pyarrow 26.0.0
    ...
    KeyError: "Can't resolve iterator for type 'map'"
    ```
    
    ---------
    
    Signed-off-by: Edgar Ramírez Mondragón <[email protected]>
---
 python/src/nanoarrow/iterator.py | 23 +++++++++++++++++++++++
 python/tests/test_iterator.py    | 17 +++++++++++++++++
 2 files changed, 40 insertions(+)

diff --git a/python/src/nanoarrow/iterator.py b/python/src/nanoarrow/iterator.py
index 6b66e9eb..0d577b77 100644
--- a/python/src/nanoarrow/iterator.py
+++ b/python/src/nanoarrow/iterator.py
@@ -281,6 +281,28 @@ class PyIterator(ArrayViewBaseIterator):
             for start, end in zip(starts, ends):
                 yield list(islice(child_iter, end - start))
 
+    def _map_iter(self, offset: int, length: int):
+        if length == 0:
+            return
+
+        view = self._array_view
+        offset += view.offset
+
+        offsets = memoryview(view.buffer(1))[offset : (offset + length + 1)]
+        starts = offsets[:-1]
+        ends = offsets[1:]
+        entries = self._children[0]
+        entries_iter = entries._struct_tuple_iter(starts[0], ends[-1] - 
starts[0])
+
+        if self._contains_nulls():
+            validity = view.buffer(0).elements(offset, length)
+            for is_valid, start, end in zip(validity, starts, ends):
+                item = list(islice(entries_iter, end - start))
+                yield item if is_valid else None
+        else:
+            for start, end in zip(starts, ends):
+                yield list(islice(entries_iter, end - start))
+
     def _fixed_size_list_iter(self, offset, length):
         view = self._array_view
         offset += view.offset
@@ -625,6 +647,7 @@ _ITEMS_ITER_LOOKUP = {
     _types.STRUCT: "_struct_iter",
     _types.LIST: "_list_iter",
     _types.LARGE_LIST: "_list_iter",
+    _types.MAP: "_map_iter",
     _types.FIXED_SIZE_LIST: "_fixed_size_list_iter",
     _types.SPARSE_UNION: "_sparse_union_iter",
     _types.DENSE_UNION: "_dense_union_iter",
diff --git a/python/tests/test_iterator.py b/python/tests/test_iterator.py
index 8e62027b..b07a585d 100644
--- a/python/tests/test_iterator.py
+++ b/python/tests/test_iterator.py
@@ -278,6 +278,23 @@ def test_iterator_nullable_list():
     assert (list(iter_py(nested_sliced))) == [[4, 5, 6], [7, 8, None], [0], 
None]
 
 
+def test_iterator_map():
+    pa = pytest.importorskip("pyarrow")
+
+    array = pa.array([], pa.map_(pa.string(), pa.int64()))
+    assert list(iter_py(array)) == []
+
+    items = [[("a", 1), ("b", None)], [], None, [("c", 3)]]
+    array = pa.array(items, pa.map_(pa.string(), pa.int64()))
+    assert list(iter_py(array)) == items
+    assert list(iter_py(array[1:])) == items[1:]
+
+    # No nulls takes the non-nullable path
+    items = [[("a", 1)], [], [("b", 2), ("c", 3)]]
+    array = pa.array(items, pa.map_(pa.string(), pa.int64()))
+    assert list(iter_py(array)) == items
+
+
 def test_iterator_fixed_size_list():
     pa = pytest.importorskip("pyarrow")
     items = [[1, 2, 3], [4, 5, 6], [7, 8, None]]

Reply via email to