{"id":"7ab7ad3b1afbef01","repo":"encode/httpx","slug":"multipart-file-uploads-require-io-bytesio-not","errorCode":null,"errorMessage":"Multipart file uploads require 'io.BytesIO', not 'io.StringIO'.","messagePattern":"Multipart file uploads require 'io\\.BytesIO', not 'io\\.StringIO'\\.","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"httpx/_multipart.py","lineNumber":159,"sourceCode":"            else:\n                # all 4 parameters included\n                filename, fileobj, content_type, headers = value  # type: ignore\n        else:\n            filename = Path(str(getattr(value, \"name\", \"upload\"))).name\n            fileobj = value\n\n        if content_type is None:\n            content_type = _guess_content_type(filename)\n\n        has_content_type_header = any(\"content-type\" in key.lower() for key in headers)\n        if content_type is not None and not has_content_type_header:\n            # note that unlike requests, we ignore the content_type provided in the 3rd\n            # tuple element if it is also included in the headers requests does\n            # the opposite (it overwrites the headerwith the 3rd tuple element)\n            headers[\"Content-Type\"] = content_type\n\n        if isinstance(fileobj, io.StringIO):\n            raise TypeError(\n                \"Multipart file uploads require 'io.BytesIO', not 'io.StringIO'.\"\n            )\n        if isinstance(fileobj, io.TextIOBase):\n            raise TypeError(\n                \"Multipart file uploads must be opened in binary mode, not text mode.\"\n            )\n\n        self.filename = filename\n        self.file = fileobj\n        self.headers = headers\n\n    def get_length(self) -> int | None:\n        headers = self.render_headers()\n\n        if isinstance(self.file, (str, bytes)):\n            return len(headers) + len(to_bytes(self.file))\n\n        file_length = peek_filelike_length(self.file)","sourceCodeStart":141,"sourceCodeEnd":177,"githubUrl":"https://github.com/encode/httpx/blob/b5addb64f0161ff6bfe94c124ef76f6a1fba5254/httpx/_multipart.py#L141-L177","documentation":"Raised as `TypeError` by `FileField.__init__` when the uploaded file object is an `io.StringIO`. Multipart bodies are 8-bit clean byte streams, so file uploads must be binary (`io.BytesIO` or any `io.BufferedIOBase`). Passing a text-mode in-memory stream would force an implicit, lossy encode, so httpx rejects it.","triggerScenarios":"Calling `client.post(url, files={'f': ('name.txt', io.StringIO('hello'))})` or handing a `StringIO` directly as the file value.","commonSituations":"Generating CSV/text content in memory with `StringIO` and uploading without converting; porting code that treated file content as text; building file payloads from string templates.","solutions":["Use `io.BytesIO`: `io.BytesIO(text.encode('utf-8'))`.","Write the text to a real file opened in binary mode (`open(path, 'rb')`).","If the content is already a `str`, pass `('name.txt', text)` directly (httpx will encode it).","Pick an explicit encoding (utf-8, latin-1) when converting to avoid surprises."],"exampleFix":"// before\nclient.post(url, files={'f': ('x.txt', io.StringIO('hello'))})  # TypeError\n\n// after\nclient.post(url, files={'f': ('x.txt', io.BytesIO('hello'.encode('utf-8')))})","handlingStrategy":"validation","validationCode":"import io\n\ndef to_binary_file(obj):\n    if isinstance(obj, io.StringIO):\n        return io.BytesIO(obj.getvalue().encode('utf-8'))\n    return obj","typeGuard":"import io\n\ndef is_binary_filelike(obj) -> bool:\n    return not isinstance(obj, (io.StringIO, io.TextIOBase))","tryCatchPattern":"try:\n    client.post(url, files={'f': ('x.txt', StringIO('hi'))})\nexcept TypeError:\n    client.post(url, files={'f': ('x.txt', io.BytesIO(b'hi'))})","preventionTips":["Use io.BytesIO for in-memory uploads; encode text explicitly.","Pass ('name', str_payload) directly if the content is already a str.","Centralize file-payload construction so the binary requirement is enforced once."],"tags":["multipart","file-upload","type-error","encoding","validation"],"analyzedSha":"b5addb64f0161ff6bfe94c124ef76f6a1fba5254","analyzedAt":"2026-08-04T19:32:56.768Z","schemaVersion":2}