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.abstractmethodView on GitHub (pinned to c0ef574e29)
Solutions
- Make the handler async: change 'def handler(request):' to 'async def handler(request):'.
- For class-based views, subclass aiohttp.web.AbstractView and pass the class (not an instance): app.router.add_route('GET', '/', MyView).
- If wrapping with functools.partial, ensure the underlying function is async.
- 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
- Default to 'async def' for every route handler.
- Add a CI grep that flags 'def ' decorators registered as routes without async.
- For class-based views, subclass aiohttp.web.AbstractView.
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
- The first argument should be web.Application instance, got {
- access_log_class must be subclass of aiohttp.abc.AbstractAcc
- {method} is not allowed HTTP method
- Cannot change apps stack after .freeze() call
- Expected one of the following apps {self._apps!r}, got {app!
AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04).
Data as JSON: /data/errors/1ae8def7e9807e3c.json.
Report an issue: GitHub.