aio-libs/aiohttp · error · TypeError

Only async functions are allowed as web-handlers, got {handl

Error message

Only async functions are allowed as web-handlers, got {handler!r}

What it means

Raised as TypeError by Route.__init__ when the handler is neither an async function (coroutine function) nor a class subclassing aiohttp.web.AbstractView. aiohttp dispatches handlers via await, so a synchronous function or a plain callable would return a non-awaitable and break the request pipeline; the check rejects it at registration time.

Source

Thrown at aiohttp/web_urldispatcher.py:175

        if expect_handler is None:
            expect_handler = _default_expect_handler

        assert inspect.iscoroutinefunction(expect_handler) or (
            sys.version_info < (3, 14) and asyncio.iscoroutinefunction(expect_handler)  # type: ignore[deprecated]
        ), f"Coroutine is expected, got {expect_handler!r}"

        method = method.upper()
        if not HTTP_METHOD_RE.match(method):
            raise ValueError(f"{method} is not allowed HTTP method")

        if inspect.iscoroutinefunction(handler) or (
            sys.version_info < (3, 14) and asyncio.iscoroutinefunction(handler)  # type: ignore[deprecated]
        ):
            pass
        elif isinstance(handler, type) and issubclass(handler, AbstractView):
            pass
        else:
            raise TypeError(
                f"Only async functions are allowed as web-handlers, got {handler!r}"
            )

        self._method = method
        self._handler = handler
        self._expect_handler = expect_handler
        self._resource = resource

    @property
    def method(self) -> str:
        return self._method

    @property
    def handler(self) -> Handler:
        return self._handler

    @property
    @abc.abstractmethod

View on GitHub (pinned to c0ef574e29)

Solutions

  1. Make the handler async: change 'def handler(request):' to 'async def handler(request):'.
  2. For class-based views, subclass aiohttp.web.AbstractView and pass the class (not an instance): app.router.add_route('GET', '/', MyView).
  3. If wrapping with functools.partial, ensure the underlying function is async.
  4. Run a grep for route-registered handlers missing 'async def' after refactors.

Example fix

# before
def handle(request):
    return web.Response(text="hi")
app.router.add_get('/', handle)

# after
async def handle(request):
    return web.Response(text="hi")
app.router.add_get('/', handle)
Defensive patterns

Strategy: type-guard

Validate before calling

import inspect

def ensure_async_handler(handler):
    if not (inspect.iscoroutinefunction(handler) or
            (isinstance(handler, type) and issubclass(handler, AbstractView))):
        raise TypeError(f"handler must be async def or AbstractView subclass: {handler!r}")
    return handler

Type guard

import inspect
from aiohttp.web import AbstractView

def is_web_handler(h) -> bool:
    return inspect.iscoroutinefunction(h) or (
        isinstance(h, type) and issubclass(h, AbstractView)
    )

Try / catch

try:
    app.router.add_get(path, handler)
except TypeError as e:
    raise TypeError(f"{handler!r} is not an async web handler: {e}") from e

Prevention

When it happens

Trigger: Calling app.router.add_get('/x', sync_handler) where def sync_handler(request): return ... (no async). Passing a lambda, a functools.partial of a sync function, or a class instance instead of a class. Using @routes.get on a def (non-async) function.

Common situations: Copying Flask/FastAPI-style sync handlers into aiohttp; forgetting 'async def'; wrapping a coroutine in partial incorrectly; using class-based views but passing an instance instead of the class; refactoring that dropped the async keyword.

Related errors


AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04). Data as JSON: /data/errors/1ae8def7e9807e3c.json. Report an issue: GitHub.