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_type

View on GitHub (pinned to c0ef574e29)

Solutions

  1. If you have bytes, use body=, not text=.
  2. For bytes-returning JSON encoders (orjson), use json_bytes_response() instead of json_response().
  3. 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

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


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