python/cpython · error · TypeError

expects an iterable of awaitables, not {type(fs).__name__}

Error message

expects an iterable of awaitables, not {type(fs).__name__}

What it means

Raised by asyncio.as_completed() when its first argument is a single awaitable instead of an iterable of awaitables. The guard `inspect.isawaitable(fs)` exists to catch the common mistake of passing one coroutine where a list is expected, which would otherwise iterate the object in confusing ways or silently do nothing.

Source

Thrown at Lib/asyncio/tasks.py:671

    awaitable.  This pattern is compatible with Python versions older than
    3.13:

        ipv4_connect = create_task(open_connection("127.0.0.1", 80))
        ipv6_connect = create_task(open_connection("::1", 80))
        tasks = [ipv4_connect, ipv6_connect]

        for next_connect in as_completed(tasks):
            # next_connect is not one of the original task objects. It must
            # be awaited to obtain the result value or raise the exception
            # of the awaitable that finishes next.
            reader, writer = await next_connect

    A TimeoutError is raised if the timeout occurs before all awaitables
    are done.  This is raised by the async for loop during asynchronous
    iteration or by the coroutines yielded during plain iteration.
    """
    if inspect.isawaitable(fs):
        raise TypeError(
            f"expects an iterable of awaitables, not {type(fs).__name__}"
        )

    return _AsCompletedIterator(fs, timeout)


@types.coroutine
def __sleep0():
    """Skip one event loop run cycle.

    This is a private helper for 'asyncio.sleep()', used
    when the 'delay' is set to 0.  It uses a bare 'yield'
    expression (which Task.__step knows how to handle)
    instead of creating a Future object.
    """
    yield

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Wrap the single awaitable in a list: `for coro in asyncio.as_completed([fetch(url)])`
  2. If there is only one awaitable, just `await fetch(url)` directly — no iterator needed
  3. When building from a loop, pass a list comprehension: `asyncio.as_completed([fetch(u) for u in urls])`

Example fix

// before
for nxt in asyncio.as_completed(fetch(url)):
    ...

// after
for nxt in asyncio.as_completed([fetch(url)]):
    result = await nxt
Defensive patterns

Strategy: validation

Validate before calling

import inspect, asyncio

arg = fetch(url)  # maybe a single awaitable
if inspect.isawaitable(arg) and not hasattr(arg, '__iter__'):
    arg = [arg]          # normalize single awaitable to a list
for nxt in asyncio.as_completed(arg):
    result = await nxt

Type guard

import inspect
from typing import Any, Iterable, Awaitable

def is_awaitable_iterable(obj: Any) -> bool:
    """True if obj is safe to pass to asyncio.as_completed."""
    return not inspect.isawaitable(obj) and isinstance(obj, Iterable) and all(
        inspect.isawaitable(x) for x in obj
    )

Try / catch

try:
    it = asyncio.as_completed(fs)
except TypeError:
    it = asyncio.as_completed([fs])  # single awaitable was passed

Prevention

When it happens

Trigger: Calling `asyncio.as_completed(fetch(url))` instead of `asyncio.as_completed([fetch(url)])`; also passing a single Task or Future where the API expects a list/iterable of them.

Common situations: Refactoring code that previously awaited a single coroutine into an as_completed loop; copy-pasting a gather() call pattern into as_completed() without wrapping the single item in a list.

Related errors


AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14). Data as JSON: /api/errors/4210431caa33c439. Report an issue: GitHub.