aio-libs/aiohttp · error · TypeError

data argument must be str (%r)

Error message

data argument must be str (%r)

What it means

Raised as TypeError in ClientWebSocketResponse.send_str() when the data argument is not a str. WebSocket text frames must carry text; sending bytes via send_str would silently mis-encode the frame, so the type is enforced strictly.

Source

Thrown at aiohttp/client_ws.py:287

    def exception(self) -> BaseException | None:
        return self._exception

    async def ping(self, message: bytes = b"") -> None:
        await self._writer.send_frame(message, WSMsgType.PING)

    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)

View on GitHub (pinned to c0ef574e29)

Solutions

  1. Pass a str to send_str: await ws.send_str('hello').
  2. For bytes use send_bytes: await ws.send_bytes(b'hello').
  3. For JSON use send_json: await ws.send_json({'k': 1}).
  4. Decode bytes to str first: data.decode('utf-8') if you have UTF-8 text bytes.

Example fix

# before
await ws.send_str(b'hello')      # bytes
await ws.send_str({'k': 1})     # dict
# after
await ws.send_str('hello')
await ws.send_bytes(b'hello')
await ws.send_json({'k': 1})
Defensive patterns

Strategy: type-guard

Validate before calling

if not isinstance(data, str):
    raise TypeError(f'send_str requires str, got {type(data).__name__}')
await ws.send_str(data)

Type guard

def is_ws_text(data) -> bool:
    return isinstance(data, str)

Prevention

When it happens

Trigger: Fires at line 286-287 when isinstance(data, str) is False. Triggered by passing bytes (b'hello'), a dict (use send_json), an int, or None to send_str.

Common situations: Passing already-encoded UTF-8 bytes to send_str instead of send_bytes; passing JSON-serialized bytes; passing a non-string from dynamic code.

Related errors


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