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

  1. Coerce to str first: await ws.send_str(str(value)) or json.dumps(value) for JSON payloads.
  2. For raw bytes, use ws.send_bytes(data) (sends a BINARY frame).
  3. 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

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


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)