{"id":"f78b10ef8a0e6cf7","repo":"aio-libs/aiohttp","slug":"expected-str-got-value-r","errorCode":null,"errorMessage":"expected str, got {value!r}","messagePattern":"expected str, got (.+?)","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"aiohttp/formdata.py","lineNumber":112,"sourceCode":"                to_add.extend(rec.items())\n\n            elif isinstance(rec, (list, tuple)) and len(rec) == 2:\n                k, fp = rec\n                self.add_field(k, fp)\n\n            else:\n                raise TypeError(\n                    \"Only io.IOBase, multidict and (name, file) \"\n                    \"pairs allowed, use .add_field() for passing \"\n                    f\"more complex parameters, got {rec!r}\"\n                )\n\n    def _gen_form_urlencoded(self) -> payload.BytesPayload:\n        # form data (x-www-form-urlencoded)\n        data = []\n        for type_options, _, value in self._fields:\n            if not isinstance(value, str):\n                raise TypeError(f\"expected str, got {value!r}\")\n            data.append((type_options[\"name\"], value))\n\n        charset = self._charset if self._charset is not None else \"utf-8\"\n\n        if charset == \"utf-8\":\n            content_type = \"application/x-www-form-urlencoded\"\n        else:\n            content_type = \"application/x-www-form-urlencoded; charset=%s\" % charset\n\n        return payload.BytesPayload(\n            urlencode(data, doseq=True, encoding=charset).encode(),\n            content_type=content_type,\n        )\n\n    def _gen_form_data(self) -> multipart.MultipartWriter:\n        \"\"\"Encode a list of fields using the multipart/form-data MIME format\"\"\"\n        for dispparams, headers, value in self._fields:\n            try:","sourceCodeStart":94,"sourceCodeEnd":130,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/c0ef574e29109210e96e652771ae4e7b88615fa4/aiohttp/formdata.py#L94-L130","documentation":"Raised by FormData._gen_form_urlencoded when, in urlencoded (non-multipart) mode, a field value is not a str. urlencoded bodies can only carry string key/value pairs, so int/None/bytes values are rejected at serialization time with a TypeError. This path runs only when is_multipart is False.","triggerScenarios":"FormData({'count': 5}) or FormData([('name', None)]) where no field forced multipart mode (no file/bytes/content_type). Calling form() then triggers _gen_form_urlencoded which iterates fields and hits the non-str value.","commonSituations":"Forgetting to stringify ints/floats/bools from form data; passing None for empty fields instead of ''; expecting requests-like coercion of non-string values.","solutions":["Stringify every value: FormData({k: str(v) for k, v in data.items()}).","Convert None to '' explicitly.","Force multipart if you need binary: pass default_to_multipart=True or include a bytes value."],"exampleFix":"// before\nform = FormData({'count': 5, 'name': None})\n// after\nform = FormData({'count': str(5), 'name': ''})","handlingStrategy":"validation","validationCode":"def stringify_form(data: dict) -> dict:\n    return {k: ('' if v is None else str(v)) for k, v in data.items()}","typeGuard":"def all_str_values(data) -> bool:\n    return all(isinstance(v, str) for v in data.values())","tryCatchPattern":null,"preventionTips":["Stringify all non-str values before building a urlencoded form.","Convert None to '' explicitly to match form semantics.","Use default_to_multipart=True if binary values are required."],"tags":["formdata","type-error","urlencoded","serialization"],"analyzedSha":"c0ef574e29109210e96e652771ae4e7b88615fa4","analyzedAt":"2026-08-04T19:51:05.467Z","schemaVersion":2}