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

  1. Pass bytes: encode text first with data.encode('utf-8') or use a bytes literal b'...'.
  2. If the payload is text or JSON, use send_str / send_json instead of send_bytes.
  3. Wrap non-byte buffers with bytes(obj) or memoryview(obj) before calling send_bytes.
  4. 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

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


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)