Skip to content

ak.from_buffers raises NotImplementedError when a container's __iter__ raises (regression introduced in #4106) #4205

Description

@TaiSakuma

Calling ak.from_buffers with a Mapping container whose __iter__ raises now fails with NotImplementedError, even though from_buffers only ever accesses the container by key. This works in 2.10.0 and fails on current main.

Reproducer

Self-contained, no coffea or network:

from collections.abc import Mapping

import numpy as np

import awkward as ak


class NoIterMapping(Mapping):
    """A Mapping that supports __getitem__ but not iteration."""

    def __init__(self, data):
        self._data = data

    def __getitem__(self, key):
        return self._data[key]

    def __len__(self):
        return len(self._data)

    def __iter__(self):
        raise NotImplementedError


form = ak.forms.NumpyForm("int64", form_key="node0")
container = NoIterMapping({"node0-data": np.arange(5, dtype=np.int64)})

ak.from_buffers(form, 5, container)

Expected: <Array [0, 1, 2, 3, 4] type='5 * int64'>.

Actual: raises NotImplementedError (empty message) before from_buffers does any real work.

Cause

OperationErrorContext.any_backend_is_delayed (src/awkward/_errors.py) walks an operation's arguments to detect a delayed (dask) backend, and recurses into any collections.abc.Collection by iterating it:

for obj in iterable:
    backend = backend_of_obj(obj, default=None)
    if backend is None:
        if isinstance(obj, Collection) and depth != depth_limit:
            if self.any_backend_is_delayed(obj, depth=depth + 1, depth_limit=depth_limit):
                return True
        # otherwise fall through and continue to the next argument
    ...

This runs on every operation, purely to decide whether to eagerly format error strings. A Mapping is a Collection, so the recursion calls its __iter__; if that raises, the exception propagates out of a normal, successful call.

Regression

Introduced in #4106. Previously the loop returned early on the first non-backend argument and never reached a later container argument; it now inspects every argument and iterates the container. Restoring the previous method body locally makes the same reproducer return [0, 1, 2, 3, 4], which isolates the change to this commit.

2.10.0 predates #4106 and is not affected. First observed in the nightly integration tests against coffea: 2026-07-13 and 2026-07-14.

Where it surfaces in practice

This is the standard data-loading path in coffea: NanoEventsFactory.events() calls ak.from_buffers with a UprootSourceMapping, a Mapping whose __iter__ raises NotImplementedError. Every events() call therefore raises. It reproduces on all platforms and Python versions in CI; affected coffea tests include test_buffer_cache.py, test_local_executors.py::test_nanoevents_analysis, and test_jetmet_tools.py.

coffea/nanoevents/factory.py:798: in events
    events = awkward.from_buffers(...)
awkward/_dispatch.py:41: in dispatch
    with OperationErrorContext(name, args, kwargs):
awkward/_errors.py:207: in __init__
    or self.any_backend_is_delayed(kwargs.values())
awkward/_errors.py:190: in any_backend_is_delayed
    if self.any_backend_is_delayed(obj, ...)
awkward/_errors.py:184: in any_backend_is_delayed
    for obj in iterable:
coffea/nanoevents/mapping/uproot.py:284: NotImplementedError

Suggested fix

Guard the backend-detection walk so it can never crash on a user-supplied argument: wrap the iteration in try/except (treat an object that cannot be iterated as not delayed), or restrict the recursion to concrete containers (list/tuple/dict/set) rather than any Collection. The #4106 change is itself correct for mixed-backend argument lists, so this is an unintended interaction; a guard is preferable to reverting.

The reproducer above doubles as a regression test (name it per the repo convention, test_<issue#>_from_buffers_non_iterable_container.py).

Versions

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status
    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions