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
Calling
ak.from_bufferswith aMappingcontainer whose__iter__raises now fails withNotImplementedError, even thoughfrom_buffersonly ever accesses the container by key. This works in 2.10.0 and fails on currentmain.Reproducer
Self-contained, no coffea or network:
Expected:
<Array [0, 1, 2, 3, 4] type='5 * int64'>.Actual: raises
NotImplementedError(empty message) beforefrom_buffersdoes 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 anycollections.abc.Collectionby iterating it:This runs on every operation, purely to decide whether to eagerly format error strings. A
Mappingis aCollection, 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()callsak.from_bufferswith aUprootSourceMapping, aMappingwhose__iter__raisesNotImplementedError. Everyevents()call therefore raises. It reproduces on all platforms and Python versions in CI; affected coffea tests includetest_buffer_cache.py,test_local_executors.py::test_nanoevents_analysis, andtest_jetmet_tools.py.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 anyCollection. 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
mainatc90cb153(2.10.0 is unaffected — it predates fix: categorical hashing, delayed-backend detection, and pretty-printing #4106)🤖 Generated with Claude Code