encode/httpx · error · TypeError
Cannot use an async handler in a sync Client
Error message
Cannot use an async handler in a sync Client
What it means
Raised by MockTransport.handle_request (the synchronous path) when the handler callable returns a coroutine instead of a Response. MockTransport accepts either a sync or async handler, but the sync handle_request cannot await a coroutine, so it detects the non-Response return and raises TypeError. It indicates a mismatch between the Client type (sync) and the handler (async).
Source
Thrown at httpx/_transports/mock.py:26
SyncHandler = typing.Callable[[Request], Response]
AsyncHandler = typing.Callable[[Request], typing.Coroutine[None, None, Response]]
__all__ = ["MockTransport"]
class MockTransport(AsyncBaseTransport, BaseTransport):
def __init__(self, handler: SyncHandler | AsyncHandler) -> None:
self.handler = handler
def handle_request(
self,
request: Request,
) -> Response:
request.read()
response = self.handler(request)
if not isinstance(response, Response): # pragma: no cover
raise TypeError("Cannot use an async handler in a sync Client")
return response
async def handle_async_request(
self,
request: Request,
) -> Response:
await request.aread()
response = self.handler(request)
# Allow handler to *optionally* be an `async` function.
# If it is, then the `response` variable need to be awaited to actually
# return the result.
if not isinstance(response, Response):
response = await response
return response
View on GitHub (pinned to b5addb64f0)
Solutions
- Use httpx.AsyncClient with the MockTransport when your handler is 'async def'.
- Or rewrite the handler as a plain 'def' returning httpx.Response when using a sync Client.
- Keep one MockTransport per Client flavour; do not share between sync and async tests.
- Add a unit-level check: if asyncio.iscoroutinefunction(handler) and sync client -> fail fast in test setup.
Example fix
// before
def handler(request): # actually defined as 'async def' elsewhere
return httpx.Response(200)
transport = httpx.MockTransport(handler)
client = httpx.Client(transport=transport) # TypeError at request time
// after
async def handler(request):
return httpx.Response(200)
transport = httpx.MockTransport(handler)
client = httpx.AsyncClient(transport=transport) Defensive patterns
Strategy: type-guard
Validate before calling
import asyncio, typing
import httpx
def make_mock_transport(handler):
is_async = asyncio.iscoroutinefunction(handler)
transport = httpx.MockTransport(handler)
return transport, is_async
transport, is_async = make_mock_transport(my_handler)
client = (
httpx.AsyncClient(transport=transport) if is_async
else httpx.Client(transport=transport)
) Type guard
import asyncio, typing
def is_async_handler(handler: typing.Callable) -> bool:
return asyncio.iscoroutinefunction(handler) Try / catch
try:
response = client.get("https://test.local/")
except TypeError as e:
if "async handler" in str(e):
raise TypeError("Use httpx.AsyncClient for async MockTransport handlers") from e
raise Prevention
- Decide upfront whether a test is sync or async; align Client and handler.
- Do not share one MockTransport across sync and async clients.
- Add a pytest fixture that pairs the right Client class with the handler flavour.
When it happens
Trigger: Passing an 'async def handler(request)' to httpx.MockTransport and then using it from a sync httpx.Client (client.get(...)). The sync code path calls handle_request, which calls the handler synchronously and gets back a coroutine object.
Common situations: Writing tests that share one MockTransport between sync and async code; refactoring a sync test to async but forgetting to switch Client to AsyncClient; copy-pasting an async handler example into a sync test suite.
Related errors
- Cannot send a request, as the client has been closed.
- Exceeded maximum allowed redirects.
- Attempted to send an async request with a sync Client instan
- Cannot open a client instance more than once.
- Cannot reopen a client instance, once it has been closed.
AI-assisted analysis of encode/httpx@b5addb64f0 (2026-08-04).
Data as JSON: /data/errors/a0ca0899d18106b6.json.
Report an issue: GitHub.