{"id":"1955cdb94cd2a459","repo":"aio-libs/aiohttp","slug":"text-argument-must-be-str-r","errorCode":null,"errorMessage":"text argument must be str (%r)","messagePattern":"text argument must be str \\(%r\\)","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"aiohttp/web_response.py","lineNumber":576,"sourceCode":"            real_headers: CIMultiDict[str] = CIMultiDict()\n        else:\n            real_headers = CIMultiDict(headers)\n\n        if content_type is not None and \"charset\" in content_type:\n            raise ValueError(\"charset must not be in content_type argument\")\n\n        if text is not None:\n            if hdrs.CONTENT_TYPE in real_headers:\n                if content_type or charset:\n                    raise ValueError(\n                        \"passing both Content-Type header and \"\n                        \"content_type or charset params \"\n                        \"is forbidden\"\n                    )\n            else:\n                # fast path for filling headers\n                if not isinstance(text, str):\n                    raise TypeError(\"text argument must be str (%r)\" % type(text))\n                if content_type is None:\n                    content_type = \"text/plain\"\n                if charset is None:\n                    charset = \"utf-8\"\n                real_headers[hdrs.CONTENT_TYPE] = content_type + \"; charset=\" + charset\n                body = text.encode(charset)\n                text = None\n        elif hdrs.CONTENT_TYPE in real_headers:\n            if content_type is not None or charset is not None:\n                raise ValueError(\n                    \"passing both Content-Type header and \"\n                    \"content_type or charset params \"\n                    \"is forbidden\"\n                )\n        elif content_type is not None:\n            if charset is not None:\n                content_type += \"; charset=\" + charset\n            real_headers[hdrs.CONTENT_TYPE] = content_type","sourceCodeStart":558,"sourceCodeEnd":594,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/c0ef574e29109210e96e652771ae4e7b88615fa4/aiohttp/web_response.py#L558-L594","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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=."],"exampleFix":"# before\nresp = Response(text=orjson.dumps(data))  # bytes -> TypeError\n\n# after\nresp = Response(body=orjson.dumps(data), content_type='application/json')","handlingStrategy":"type-guard","validationCode":"def safe_text_response(value, **kw):\n    if isinstance(value, bytes):\n        return Response(body=value, **kw)\n    return Response(text=value, **kw)","typeGuard":"def is_str_text(value) -> bool:\n    return isinstance(value, str)","tryCatchPattern":null,"preventionTips":["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."],"tags":["http","response","type-check","text","encoding"],"analyzedSha":"c0ef574e29109210e96e652771ae4e7b88615fa4","analyzedAt":"2026-08-04T19:51:05.467Z","schemaVersion":2}