aio-libs/aiohttp · error · TypeError
expected str, got {value!r}
Error message
expected str, got {value!r} What it means
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.
Source
Thrown at aiohttp/formdata.py:112
to_add.extend(rec.items())
elif isinstance(rec, (list, tuple)) and len(rec) == 2:
k, fp = rec
self.add_field(k, fp)
else:
raise TypeError(
"Only io.IOBase, multidict and (name, file) "
"pairs allowed, use .add_field() for passing "
f"more complex parameters, got {rec!r}"
)
def _gen_form_urlencoded(self) -> payload.BytesPayload:
# form data (x-www-form-urlencoded)
data = []
for type_options, _, value in self._fields:
if not isinstance(value, str):
raise TypeError(f"expected str, got {value!r}")
data.append((type_options["name"], value))
charset = self._charset if self._charset is not None else "utf-8"
if charset == "utf-8":
content_type = "application/x-www-form-urlencoded"
else:
content_type = "application/x-www-form-urlencoded; charset=%s" % charset
return payload.BytesPayload(
urlencode(data, doseq=True, encoding=charset).encode(),
content_type=content_type,
)
def _gen_form_data(self) -> multipart.MultipartWriter:
"""Encode a list of fields using the multipart/form-data MIME format"""
for dispparams, headers, value in self._fields:
try:View on GitHub (pinned to c0ef574e29)
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.
Example fix
// before
form = FormData({'count': 5, 'name': None})
// after
form = FormData({'count': str(5), 'name': ''}) Defensive patterns
Strategy: validation
Validate before calling
def stringify_form(data: dict) -> dict:
return {k: ('' if v is None else str(v)) for k, v in data.items()} Type guard
def all_str_values(data) -> bool:
return all(isinstance(v, str) for v in data.values()) Prevention
- 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.
When it happens
Trigger: 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.
Common situations: Forgetting to stringify ints/floats/bools from form data; passing None for empty fields instead of ''; expecting requests-like coercion of non-string values.
Related errors
- filename must be an instance of str. Got: %s
- content_type must be an instance of str. Got: %s
- Only io.IOBase, multidict and (name, file) pairs allowed, us
- Can not serialize value type: %r headers: %r value: %r
- ssl should be SSLContext, Fingerprint, or bool, got {ssl!r}
AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04).
Data as JSON: /data/errors/f78b10ef8a0e6cf7.json.
Report an issue: GitHub.