aio-libs/aiohttp · error · TypeError
data argument must be str (%r)
Error message
data argument must be str (%r)
What it means
WebSocketResponse.send_str() raises TypeError if the data argument is not a str — it must be encoded to UTF-8 before being sent as a TEXT frame. This check runs after the prepare() guard (error 278), so it only fires when the writer exists. Use send_bytes() for bytes/bytearray/memoryview (BINARY frames), or send_str(str(data)) to coerce.
Solutions
- Coerce to str first: await ws.send_str(str(value)) or json.dumps(value) for JSON payloads.
- For raw bytes, use ws.send_bytes(data) (sends a BINARY frame).
- For JSON, use ws.send_json(obj) which handles serialisation.
Example fix
// before
await ws.send_str({'event': 'open'}) # TypeError: dict is not str
await ws.send_str(b'raw') # TypeError: bytes is not str
// after
await ws.send_json({'event': 'open'})
await ws.send_bytes(b'raw') Defensive patterns
Strategy: type-guard
Validate before calling
async def send_text(ws, value):
if not isinstance(value, str):
value = str(value)
await ws.send_str(value) Type guard
def is_str_payload(value) -> bool:
return isinstance(value, str) Try / catch
try:
await ws.send_str(payload)
except TypeError as e:
if 'must be str' in str(e):
if isinstance(payload, (bytes, bytearray, memoryview)):
await ws.send_bytes(payload)
else:
await ws.send_json(payload)
else:
raise Prevention
- Use send_json(obj) for structured data, send_bytes(b'...') for binary, send_str('...') for text.
- Coerce with str(value) or json.dumps(value) before send_str.
- Run mypy on handler code — it catches non-str arguments to send_str.
When it happens
Trigger: Calling await ws.send_str(42) (int), ws.send_str(b'hi') (bytes — should be send_bytes), ws.send_str({'k': 1}) (dict — should be send_json), ws.send_str(None).
Common situations: Passing JSON-serialised data without calling json.dumps first (sending a dict directly); passing bytes that should be a binary frame; passing numbers/bools from loosely-typed code; forgetting to coerce config values.
Related errors
- 1007
- Compress wbits must between 9 and 15, zlib does not support…
- content_type must be an instance of str. Got
- data argument must be byte-ish (%r)
- data argument must be str (%r)
AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11).
Data as JSON: /api/errors/aa93cabc12756f71.
Report an issue: GitHub.
Appendix: source
Thrown at aiohttp/web_ws.py:466
async def pong(self, message: bytes = b"") -> None:
# unsolicited pong
if self._writer is None:
raise RuntimeError("Call .prepare() first")
await self._writer.send_frame(message, WSMsgType.PONG)
async def send_frame(
self, message: bytes, opcode: WSMsgType, compress: int | None = None
) -> None:
"""Send a frame over the websocket."""
if self._writer is None:
raise RuntimeError("Call .prepare() first")
await self._writer.send_frame(message, opcode, compress)
async def send_str(self, data: str, compress: int | None = None) -> None:
if self._writer is None:
raise RuntimeError("Call .prepare() first")
if not isinstance(data, str):
raise TypeError("data argument must be str (%r)" % type(data))
await self._writer.send_frame(
data.encode("utf-8"), WSMsgType.TEXT, compress=compress
)
async def send_bytes(self, data: bytes, compress: int | None = None) -> None:
if self._writer is None:
raise RuntimeError("Call .prepare() first")
if not isinstance(data, (bytes, bytearray, memoryview)):
raise TypeError("data argument must be byte-ish (%r)" % type(data))
await self._writer.send_frame(data, WSMsgType.BINARY, compress=compress)
async def send_json(
self,
data: Any,
compress: int | None = None,
*,
dumps: JSONEncoder = json.dumps,
) -> None:View on GitHub (pinned to d041d4d0fd)