aio-libs/aiohttp · error · TypeError
text argument must be str (%r)
Error message
text argument must be str (%r)
What it means
When text= is supplied it must be a str so it can be encoded to bytes via text.encode(charset). The check at line 575-576 catches non-str values (the elif branch only runs when no Content-Type header is present, which is the common fast path).
Source
Thrown at aiohttp/web_response.py:576
real_headers: CIMultiDict[str] = CIMultiDict()
else:
real_headers = CIMultiDict(headers)
if content_type is not None and "charset" in content_type:
raise ValueError("charset must not be in content_type argument")
if text is not None:
if hdrs.CONTENT_TYPE in real_headers:
if content_type or charset:
raise ValueError(
"passing both Content-Type header and "
"content_type or charset params "
"is forbidden"
)
else:
# fast path for filling headers
if not isinstance(text, str):
raise TypeError("text argument must be str (%r)" % type(text))
if content_type is None:
content_type = "text/plain"
if charset is None:
charset = "utf-8"
real_headers[hdrs.CONTENT_TYPE] = content_type + "; charset=" + charset
body = text.encode(charset)
text = None
elif hdrs.CONTENT_TYPE in real_headers:
if content_type is not None or charset is not None:
raise ValueError(
"passing both Content-Type header and "
"content_type or charset params "
"is forbidden"
)
elif content_type is not None:
if charset is not None:
content_type += "; charset=" + charset
real_headers[hdrs.CONTENT_TYPE] = content_typeView on GitHub (pinned to c0ef574e29)
Solutions
- If you have bytes, use body=, not text=.
- For bytes-returning JSON encoders (orjson), use json_bytes_response() instead of json_response().
- Decode bytes to str before passing to text=.
Example fix
# before resp = Response(text=orjson.dumps(data)) # bytes -> TypeError # after resp = Response(body=orjson.dumps(data), content_type='application/json')
Defensive patterns
Strategy: type-guard
Validate before calling
def safe_text_response(value, **kw):
if isinstance(value, bytes):
return Response(body=value, **kw)
return Response(text=value, **kw) Type guard
def is_str_text(value) -> bool:
return isinstance(value, str) Prevention
- Use body= for bytes and text= for str.
- For orjson/msgpack bytes, use json_bytes_response or Response(body=...).
- Add a type check in serializers that may return either str or bytes.
When it happens
Trigger: Calling Response(text=b'bytes'), Response(text=123), Response(text=some_dict), or json_response(text=json_bytes) where bytes leak in. The fast-path isinstance(text, str) guard rejects them.
Common situations: Passing already-encoded bytes to text=; an encoder returning bytes (e.g. orjson.dumps) routed into text= instead of body=; a number or object slipped in.
Related errors
- Unsupported type for last_modified: {type(value).__name__}
- Unsupported etag type: {type(value)}. etag must be str, ETag
- Unsupported body type %r
- 1007
- Got more than {limit} bytes when reading: {line!r}.
AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04).
Data as JSON: /data/errors/1955cdb94cd2a459.json.
Report an issue: GitHub.