aio-libs/aiohttp · error · TypeError
data argument must be byte-ish (%r)
Error message
data argument must be byte-ish (%r)
What it means
Raised by ClientWebSocketResponse.send_bytes() when the data argument is not one of bytes, bytearray, or memoryview. The method exists specifically to transmit BINARY frames, so aiohttp refuses to guess how to encode non-byte payloads and fails fast rather than silently sending malformed data. The %r placeholder interpolates the actual type, making the call-site mistake obvious.
Solutions
- Pass bytes: encode text first with data.encode('utf-8') or use a bytes literal b'...'.
- If the payload is text or JSON, use send_str / send_json instead of send_bytes.
- Wrap non-byte buffers with bytes(obj) or memoryview(obj) before calling send_bytes.
- Add a type check at the producer boundary so wrong types are caught before reaching the websocket.
Example fix
# before
await ws.send_bytes(json.dumps(payload))
# after
await ws.send_bytes(json.dumps(payload).encode('utf-8')) Defensive patterns
Strategy: type-guard
Validate before calling
def to_binary_frame(data) -> bytes:
if isinstance(data, (bytes, bytearray, memoryview)):
return bytes(data)
if isinstance(data, str):
return data.encode('utf-8')
raise TypeError(f'cannot send {type(data)} as a binary frame')
await ws.send_bytes(to_binary_frame(payload)) Type guard
from collections.abc import Buffer
from typing import Any
def is_byte_ish(data: Any) -> bool:
return isinstance(data, (bytes, bytearray, memoryview)) Try / catch
try:
await ws.send_bytes(payload)
except TypeError as exc:
if 'byte-ish' in str(exc):
payload = payload.encode('utf-8') if isinstance(payload, str) else bytes(payload)
await ws.send_bytes(payload)
else:
raise Prevention
- Pick send_str for text and send_bytes for binary - do not mix them.
- Encode at the producer boundary so the websocket layer always sees bytes.
- Unit-test the producer with both str and bytes inputs to catch type drift.
When it happens
Trigger: Calling await ws.send_bytes('text') with a str; passing an int, float, dict, list, or None; passing a json.dumps(...) result (which is str) instead of json.dumps(...).encode(); calling send_bytes on data read from a text-mode file without encoding.
Common situations: Mixing up send_str and send_bytes; using send_bytes when the producer is a JSON/text serializer that returns str; reading from an open() opened without 'b'; passing a numpy array or other buffer-like object that is not one of the three accepted types.
Related errors
- data argument must be str (%r)
- 1002
- Concurrent call to receive() is not allowed
- data argument must be str (%r)
- Received message : is not WSMsgType.TEXT
AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11).
Data as JSON: /api/errors/ab4236ef6f4a84fb.
Report an issue: GitHub.
Appendix: source
Thrown at aiohttp/client_ws.py:294
async def pong(self, message: bytes = b"") -> None:
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."""
await self._writer.send_frame(message, opcode, compress)
async def send_str(self, data: str, compress: int | None = None) -> None:
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 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 = DEFAULT_JSON_ENCODER,
) -> None:
await self.send_str(dumps(data), compress=compress)
async def send_json_bytes(
self,
data: Any,
compress: int | None = None,
*,
dumps: JSONBytesEncoder,
) -> None:View on GitHub (pinned to d041d4d0fd)