aio-libs/aiohttp · error · TypeError

Only async functions are allowed as web-handlers, got

Error message

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

What it means

A web handler registered on a route must be either an async function (coroutine function) or a class that subclasses AbstractView (class-based views). The check uses inspect.iscoroutinefunction and, as a fallback for Python < 3.14, asyncio.iscoroutinefunction. aiohttp will await the handler during request processing, so a plain synchronous function or a non-AbstractView class would fail with a TypeError at request time — the router catches this at registration instead.

Solutions

  1. Declare the handler as `async def handler(request): ...`.
  2. For class-based views, inherit from aiohttp.web.View and define async methods named after HTTP verbs (get, post, etc.).
  3. If using a decorator, ensure it preserves the coroutine function (use functools.wraps and re-async the wrapper).
  4. Replace any partial or lambda with a proper async def.

Example fix

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

Strategy: type-guard

Validate before calling

import inspect
from aiohttp.web import View
from aiohttp.abc import AbstractView

def assert_handler(h):
    if inspect.iscoroutinefunction(h):
        return h
    if isinstance(h, type) and issubclass(h, AbstractView):
        return h
    raise TypeError(f'Handler must be async function or AbstractView subclass: {h!r}')

Type guard

import inspect
from aiohttp.abc import AbstractView

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

Prevention

When it happens

Trigger: Calling add_route/add_get/add_post/etc. with a regular def (non-async) function; passing a class that does not inherit from AbstractView; passing a callable object instance rather than a function or view class; passing a functools.partial or wrapped object that is not itself a coroutine function.

Common situations: Forgetting `async def` when writing a handler; migrating a Flask-style sync handler to aiohttp without adding async; using a class-based view without subclassing web.View (which is itself AbstractView); wrapping handlers with a synchronous decorator that hides the coroutine nature.

Related errors


AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11). Data as JSON: /api/errors/1ae8def7e9807e3c. Report an issue: GitHub.

Appendix: 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 d041d4d0fd)