{"record":{"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":"exception","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/8068356288978c4f54661ae6f95afe0e0831885e/src/requests/models.py#L179-L215","documentation":"Raised by Request._encode_files when the data argument accompanying a multipart upload is a plain string (bytes or str). For multipart encoding, data must be a mapping or list of tuples so it can be merged into the form fields; passing a raw string body is ambiguous and would corrupt the multipart structure. This is a sibling guard to the files-must-be-provided check.","triggerScenarios":"Calling requests.post(url, files=files, data='a=b&c=d') with a URL-encoded string instead of a dict; passing data=request.body (a string) alongside files=; mixing the JSON/string body API with the files API.","commonSituations":"Migrating a call from data=string to a files= upload and forgetting to convert data to a dict; building query strings manually then adding files; library wrappers that accept 'body' generically and forward it to both data= and files=.","solutions":["Convert the string body to a dict or list of (key, value) tuples before passing as data= with files= (e.g. urllib.parse.parse_qs to a dict).","If the string is genuinely the request body, drop the files= argument — you cannot mix a raw body with multipart files.","Use a dict for form fields: data={'field': 'value'} alongside files={'file': f}."],"exampleFix":"// before\nrequests.post(url, files=files, data='field=value')\n\n// after\nrequests.post(url, files=files, data={'field': 'value'})","handlingStrategy":"validation","validationCode":"def post_multipart(url, files, data=None):\n    if isinstance(data, (str, bytes)):\n        raise TypeError('data must be a dict or list of tuples, not a string, when files is set')\n    return requests.post(url, files=files, data=data)","typeGuard":"from typing import Any\n\ndef is_form_data(data: Any) -> bool:\n    return data is None or isinstance(data, (dict, list, tuple))","tryCatchPattern":"# This is a caller bug; convert data instead of catching.\nif isinstance(data, str):\n    from urllib.parse import parse_qs\n    data = {k: v[0] for k, v in parse_qs(data).items()}\nrequests.post(url, files=files, data=data)","preventionTips":["Pass form fields as a dict, never as a URL-encoded string, when using files=.","Do not mix raw-body (data=str) semantics with multipart (files=) semantics.","Unit-test the upload path with both empty and non-empty field dicts."],"tags":["upload","multipart","files","data","valueerror"],"backgroundTag":null,"analyzedSha":"8068356288978c4f54661ae6f95afe0e0831885e","analyzedAt":"2026-08-11T20:11:09.238Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}