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() when the `data` argument is not an instance of (bytes, bytearray, memoryview). This is a TypeError whose %r placeholder prints the actual type, e.g. "data argument must be byte-ish (<class 'str'>)". aiohttp validates before encoding because the underlying send_frame only accepts a concrete byte buffer and would produce a confusing AttributeError otherwise.

Solutions

  1. If you have text, call send_str() instead of send_bytes().
  2. Coerce before sending: `await ws.send_bytes(bytes(data))` for buffer-like objects, or `data.encode('utf-8')` for str.
  3. For JSON produced by an encoder returning bytes (orjson), use send_json_bytes(...) not send_json(...).
  4. Add an isinstance(data, (bytes, bytearray, memoryview)) assert at the call site during development.

Example fix

// before
await ws.send_bytes({'k': 1})   # raises
await ws.send_bytes('hello')     # raises

// after
await ws.send_bytes(json.dumps({'k': 1}).encode())
await ws.send_str('hello')
# or, with orjson:
await ws.send_json_bytes({'k': 1}, dumps=orjson.dumps)
Defensive patterns

Strategy: type-guard

Validate before calling

assert isinstance(data, (bytes, bytearray, memoryview)), f'must be byte-ish, got {type(data)}'
await ws.send_bytes(data)

Type guard

def is_byteish(data: object) -> 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(bytes(data))  # or send_str if it was text

Prevention

When it happens

Trigger: Passing a str to send_bytes (use send_str instead), passing an int/None/dict/list, or passing a JSON encoder that yields str when send_json_bytes expects bytes (the dumps result is forwarded verbatim to send_bytes).

Common situations: Mixing up send_str vs send_bytes; using orjson.dumps (which returns bytes) with send_json (which calls send_str) or vice-versa; passing a numpy/array-like object instead of its .tobytes().

Related errors


AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11). Data as JSON: /api/errors/4ae9130b44164b9f. Report an issue: GitHub.

Appendix: 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 d041d4d0fd)