{"id":"70e2ba2123cde389","repo":"encode/httpx","slug":"multipart-file-uploads-must-be-opened-in-binary-mo","errorCode":null,"errorMessage":"Multipart file uploads must be opened in binary mode, not text mode.","messagePattern":"Multipart file uploads must be opened in binary mode, not text mode\\.","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"httpx/_multipart.py","lineNumber":163,"sourceCode":"            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)\n\n        # If we can't determine the filesize without reading it into memory,\n        # then return `None` here, to indicate an unknown file length.\n        if file_length is None:","sourceCodeStart":145,"sourceCodeEnd":181,"githubUrl":"https://github.com/encode/httpx/blob/b5addb64f0161ff6bfe94c124ef76f6a1fba5254/httpx/_multipart.py#L145-L181","documentation":"Raised as `TypeError` by `FileField.__init__` when the file object is an instance of `io.TextIOBase` — i.e. a file opened in text mode (`open(path, 'r')`). Multipart uploads require binary file objects because httpx reads raw bytes; a text-mode file would require decoding/encoding assumptions httpx will not make.","triggerScenarios":"Calling `client.post(url, files={'f': open('data.csv', 'r')})` — note the `'r'` mode. The same file opened with `'rb'` works fine.","commonSituations":"Reusing a file handle that was opened for reading as text earlier in the program; platform-default mode on Windows where text mode performs CR/LF translation; copy-pasted examples that omit the `b`.","solutions":["Open in binary mode: `open(path, 'rb')`.","Use `pathlib.Path(path).read_bytes()` to get bytes directly, then pass a BytesIO or the bytes.","Prefer `with open(path, 'rb') as f:` to scope the handle correctly.","On Windows, always pass `'rb'`/`'wb'` for network payloads to avoid CRLF translation."],"exampleFix":"// before\nclient.post(url, files={'f': open('data.csv', 'r')})  # TypeError\n\n// after\nwith open('data.csv', 'rb') as f:\n    client.post(url, files={'f': ('data.csv', f)})","handlingStrategy":"validation","validationCode":"import io\n\ndef assert_binary(fileobj) -> None:\n    if isinstance(fileobj, io.TextIOBase):\n        raise TypeError(f'{fileobj!r} opened in text mode; reopen with mode=\"rb\"')","typeGuard":"import io\n\ndef is_binary_mode(fileobj) -> bool:\n    return not isinstance(fileobj, io.TextIOBase)","tryCatchPattern":"try:\n    client.post(url, files={'f': open(path, 'r')})\nexcept TypeError:\n    with open(path, 'rb') as f:\n        client.post(url, files={'f': (path, f)})","preventionTips":["Always open files for upload with mode='rb'.","On Windows, binary mode also avoids unwanted CRLF translation.","Prefer pathlib.Path.read_bytes() for small uploads."],"tags":["multipart","file-upload","type-error","binary-mode","validation"],"analyzedSha":"b5addb64f0161ff6bfe94c124ef76f6a1fba5254","analyzedAt":"2026-08-04T19:32:56.768Z","schemaVersion":2}