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
- Declare the handler as `async def handler(request): ...`.
- For class-based views, inherit from aiohttp.web.View and define async methods named after HTTP verbs (get, post, etc.).
- If using a decorator, ensure it preserves the coroutine function (use functools.wraps and re-async the wrapper).
- 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
- Always write handlers with `async def`.
- For class-based views, inherit from aiohttp.web.View.
- When using decorators, wrap with async def and functools.wraps to preserve the coroutine nature.
- Lint with a rule that flags sync functions passed to add_route.
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
- Domain must be str
- access_log_class must be subclass of…
- Added route will never be executed, method
- Bad pattern
- Cannot change apps stack after .freeze() call
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.abstractmethodView on GitHub (pinned to d041d4d0fd)