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
- Ensure the argument is a str: await ws.send_str(value.decode() if isinstance(value, bytes) else value).
- For binary data use await ws.send_bytes(data).
- For JSON use await ws.send_json(obj) — and if your encoder returns bytes, use send_json_bytes().
- 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
- Match the API to the data type: send_str for str, send_bytes for binary, send_json for objects.
- For bytes-returning JSON encoders (orjson), use send_json_bytes.
- Add isinstance checks at boundaries when data arrives untyped.
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
- data argument must be byte-ish (%r)
- 1002
- Cannot compute fallback encoding of a not yet read body
- Concurrent call to receive() is not allowed
- Connection closed
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)