{"id":"380a4c0c7ed31e8f","repo":"psf/requests","slug":"data-must-not-be-a-string","errorCode":null,"errorMessage":"Data must not be a string.","messagePattern":"Data must not be a string\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/requests/models.py","lineNumber":197,"sourceCode":"        else:\n            return data  # type: ignore[return-value]  # unreachable for valid _t.DataType\n\n    @staticmethod\n    def _encode_files(\n        files: _t.FilesType, data: _t.RawDataType | None\n    ) -> tuple[bytes, str]:\n        \"\"\"Build the body for a multipart/form-data request.\n\n        Will successfully encode files when passed as a dict or a list of\n        tuples. Order is retained if data is a list of tuples but arbitrary\n        if parameters are supplied as a dict.\n        The tuples may be 2-tuples (filename, fileobj), 3-tuples (filename, fileobj, contentype)\n        or 4-tuples (filename, fileobj, contentype, custom_headers).\n        \"\"\"\n        if not files:\n            raise ValueError(\"Files must be provided.\")\n        elif isinstance(data, basestring):\n            raise ValueError(\"Data must not be a string.\")\n\n        new_fields: list[RequestField | tuple[str, bytes]] = []\n        fields = to_key_val_list(data or {})\n        files = to_key_val_list(files or {})\n\n        for field, val in fields:\n            if isinstance(val, basestring) or not hasattr(val, \"__iter__\"):\n                val = [val]\n            for v in val:\n                if v is not None:\n                    # Don't call str() on bytestrings: in Py3 it all goes wrong.\n                    if not isinstance(v, bytes):\n                        v = str(v)\n\n                    new_fields.append(\n                        (\n                            field.decode(\"utf-8\")\n                            if isinstance(field, bytes)","sourceCodeStart":179,"sourceCodeEnd":215,"githubUrl":"https://github.com/psf/requests/blob/414f0513c33883adf6f2b46901d4f0b38a455851/src/requests/models.py#L179-L215","documentation":"Raised by `_encode_files` when both `files` and `data` are supplied but `data` is a string (or bytes). In a multipart request, `data` must be a dict or list of key/value tuples so each entry can become a form field; a raw string body cannot be merged into multipart form fields, so requests rejects it rather than guessing.","triggerScenarios":"`requests.post(url, files={'f': fh}, data='raw string body')` or `data=b'...'` — combining a pre-serialized body (e.g. a JSON string) with `files=`. Any call path reaching `PreparedRequest.prepare_body` with non-empty `files` and string `data`.","commonSituations":"Developers do `data=json.dumps(payload)` (a common pattern for JSON APIs) and then add `files=` for an upload — the serialized string collides with multipart encoding. Also migrating code from a plain POST to a file upload without converting the body to a field dict.","solutions":["Pass `data` as a dict of form fields: `requests.post(url, files=files, data={'meta': json.dumps(payload)})`.","If the API expects the JSON as one part, add it to `files` as an extra part: `files={'file': fh, 'payload': (None, json.dumps(payload), 'application/json')}`.","If you truly need a raw string body plus a file, the request isn't multipart — build the body manually or use a MultipartEncoder (requests-toolbelt)."],"exampleFix":"# before\nrequests.post(url, files={'f': open('a.bin','rb')}, data=json.dumps(meta))\n\n# after\nrequests.post(url, files={'f': open('a.bin','rb')}, data={'meta': json.dumps(meta)})","handlingStrategy":"type-guard","validationCode":"if isinstance(data, str):\n    raise TypeError(\"when files= is used, data must be a dict/list of fields, not a string body\")","typeGuard":"def is_multipart_safe_data(data):\n    return data is None or isinstance(data, (dict, list, tuple))","tryCatchPattern":"try:\n    resp = requests.post(url, files=files, data=data)\nexcept ValueError:\n    # data was a raw string alongside files; convert to field dict or drop files=\n    raise","preventionTips":["Never combine files= with a raw string body — multipart encoding needs key/value fields","If you need a raw body plus a file, build the multipart body yourself (e.g. requests_toolbelt MultipartEncoder)","Keep form data as a dict when uploading files"],"tags":["python","requests","multipart","form-data","validation"],"analyzedSha":"414f0513c33883adf6f2b46901d4f0b38a455851","analyzedAt":"2026-07-31T19:24:57.696Z","schemaVersion":2}