aio-libs/aiohttp · error · RuntimeError
Cannot call write() before prepare()
Error message
Cannot call write() before prepare()
What it means
Raised by Response.write() when _payload_writer is None, i.e. prepare() was never called for this response. The writer is the channel that actually pushes bytes onto the socket; without it write() has nowhere to send data. The error prevents silently dropping the body.
Solutions
- Always call `await resp.prepare(request)` before any `resp.write()`.
- Use the higher-level `Response(body=...)` for fixed-size bodies — it handles prepare/write_eof automatically.
- In error/cleanup paths, track whether prepare() succeeded before attempting writes.
- If prepare() may have been skipped on an exception, return a fresh Response instead of writing to the unprepared one.
Example fix
// before
async def handler(request):
resp = StreamResponse()
await resp.write(b'hi') # RuntimeError: no prepare()
return resp
// after
async def handler(request):
resp = StreamResponse()
await resp.prepare(request)
await resp.write(b'hi')
await resp.write_eof()
return resp Defensive patterns
Strategy: validation
Validate before calling
async def write_safe(resp, data):
if getattr(resp, '_payload_writer', None) is None:
await resp.prepare(resp._req)
await resp.write(data) Type guard
def is_prepared(resp) -> bool:
return getattr(resp, '_payload_writer', None) is not None Prevention
- Always await resp.prepare(request) before any write().
- Prefer Response(body=...) for fixed payloads — it handles prepare/write_eof.
- In error paths, return a fresh Response rather than writing to an unprepared one.
- Centralize streaming logic in a helper that prepares unconditionally.
When it happens
Trigger: Calling `await resp.write(data)` on a StreamResponse without first `await resp.prepare(request)`. Also triggered when prepare() raises or is interrupted and code continues to write() in a finally/except block.
Common situations: New aiohttp users forgetting the prepare() step; refactoring that moves prepare() into a conditional branch that is skipped on an error path; middleware that writes to a response it did not prepare.
Related errors
- Cannot call write() after write_eof()
- Using chunked encoding is forbidden for HTTP/
- body and text are not allowed together
- Call .prepare() first
- Call runner.setup() before making a site
AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11).
Data as JSON: /api/errors/4a37cdc80d251447.
Report an issue: GitHub.
Appendix: source
Thrown at aiohttp/web_response.py:459
version = request.version
status_line = f"HTTP/{version[0]}.{version[1]} {self._status} {self._reason}"
await writer.write_headers(status_line, self._headers)
# Send headers immediately if not opted into buffering
if self._send_headers_immediately:
writer.send_headers()
async def write(
self, data: Union[bytes, bytearray, "memoryview[int]", "memoryview[bytes]"]
) -> None:
assert isinstance(
data, (bytes, bytearray, memoryview)
), "data argument must be byte-ish (%r)" % type(data)
if self._eof_sent:
raise RuntimeError("Cannot call write() after write_eof()")
if self._payload_writer is None:
raise RuntimeError("Cannot call write() before prepare()")
await self._payload_writer.write(data)
async def drain(self) -> None:
assert not self._eof_sent, "EOF has already been sent"
assert self._payload_writer is not None, "Response has not been started"
warnings.warn(
"drain method is deprecated, use await resp.write()",
DeprecationWarning,
stacklevel=2,
)
await self._payload_writer.drain()
async def write_eof(self, data: bytes = b"") -> None:
assert isinstance(
data, (bytes, bytearray, memoryview)
), "data argument must be byte-ish (%r)" % type(data)
View on GitHub (pinned to d041d4d0fd)