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 WebSocketResponse.send_bytes() as a TypeError when the `data` argument is not one of bytes, bytearray, or memoryview. send_bytes encodes and transmits a BINARY frame, which must be raw bytes; passing a str (or int/None/dict) has no valid binary representation. The offending type is included in the message via %r.

Source

Thrown at aiohttp/web_ws.py:475

        """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:
        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 c0ef574e29)

Solutions

  1. Pass bytes: encode strings first, e.g. `await ws.send_bytes(value.encode('utf-8'))`.
  2. Use send_str() when you actually have text, or send_json() for JSON payloads.
  3. If using an encoder that returns bytes (e.g. orjson.dumps), use send_json_bytes() which routes to send_bytes correctly.
  4. Add a type guard so callers with mixed types route to the correct method.

Example fix

# before
await ws.send_bytes('hello')

# after
await ws.send_bytes(b'hello')
# or, for text:
await ws.send_str('hello')
Defensive patterns

Strategy: type-guard

Validate before calling

from collections.abc import Buffer

def to_bytes(data) -> bytes:
    if isinstance(data, (bytes, bytearray, memoryview)):
        return bytes(data)
    raise TypeError(f"expected byte-ish, got {type(data)!r}")

await ws.send_bytes(to_bytes(payload))

Type guard

def is_byteish(data) -> bool:
    return isinstance(data, (bytes, bytearray, memoryview))

Try / catch

try:
    await ws.send_bytes(data)
except TypeError as e:
    if "byte-ish" in str(e):
        await ws.send_bytes(str(data).encode())
    else:
        raise

Prevention

When it happens

Trigger: Calling `await ws.send_bytes('text')` (passing a str), `ws.send_bytes(123)`, `ws.send_bytes(None)`, or any non-bytes value. The isinstance check at aiohttp/web_ws.py:474-475 fires after the prepare check.

Common situations: Mixing up send_str (for text) and send_bytes (for binary); passing a JSON string instead of using send_json; reading a file in text mode and forwarding the str; passing a serialised object that returns str from its encoder.

Related errors


AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04). Data as JSON: /data/errors/4ae9130b44164b9f.json. Report an issue: GitHub.