aio-libs/aiohttp · error · TypeError

data argument must be str (%r)

Error message

data argument must be str (%r)

What it means

Raised by ClientWebSocketResponse.send_str() when the 'data' argument is not a str. send_str encodes the string to UTF-8 and sends a TEXT frame; passing bytes, int, dict, or None is a type error caught by an isinstance check before any frame is written. Use send_bytes() for binary data or send_json() for JSON-serializable objects.

Solutions

  1. Ensure the argument is a str: await ws.send_str(value.decode() if isinstance(value, bytes) else value).
  2. For binary data use await ws.send_bytes(data).
  3. For JSON use await ws.send_json(obj) — and if your encoder returns bytes, use send_json_bytes().
  4. Add a type check or cast at the call site when data comes from an untyped source.

Example fix

# before
await ws.send_str(b'hello')        # bytes -> TypeError
await ws.send_str(orjson.dumps(x))  # bytes -> TypeError

# after
await ws.send_str('hello')                 # str
await ws.send_bytes(b'hello')              # bytes via send_bytes
await ws.send_json_bytes(x, dumps=orjson.dumps)  # bytes encoder -> binary frame
Defensive patterns

Strategy: type-guard

Validate before calling

async def safe_send_str(ws, data):
    if isinstance(data, str):
        await ws.send_str(data)
    elif isinstance(data, (bytes, bytearray, memoryview)):
        await ws.send_bytes(bytes(data))
    else:
        await ws.send_json(data)  # or raise

Type guard

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

Try / catch

try:
    await ws.send_str(value)
except TypeError as e:
    if 'must be str' in str(e):
        if isinstance(value, (bytes, bytearray)):
            await ws.send_bytes(value)
        else:
            await ws.send_json(value)
    else:
        raise

Prevention

When it happens

Trigger: Calling await ws.send_str(b'hello') (bytes), ws.send_str(42), ws.send_str({'k':'v'}), or any non-str value. The isinstance(data, str) check fails and TypeError is raised immediately, before the frame is sent.

Common situations: Passing bytes that should be str (or vice versa); JSON-encoding with a bytes-returning encoder (e.g. orjson.dumps) into send_str instead of send_str via send_json_bytes; reading a value from a source typed as Any and not converting; confusing send_str with send_bytes.

Related errors


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

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