aio-libs/aiohttp · error · TypeError
filename must be an instance of str. Got: %s
Error message
filename must be an instance of str. Got: %s
What it means
Raised by FormData.add_field when the filename argument is provided but is not a str. The type annotation declares filename as str | None, and aiohttp uses the filename verbatim to build the Content-Disposition header, so a non-string (e.g. pathlib.Path, int) is rejected with a TypeError before any serialization happens. The check runs after _safe_header(name) so the field name is already validated.
Source
Thrown at aiohttp/formdata.py:63
@property
def is_multipart(self) -> bool:
return self._is_multipart
def add_field(
self,
name: str,
value: Any,
*,
content_type: str | None = None,
filename: str | None = None,
) -> None:
if isinstance(value, (io.IOBase, bytes, bytearray, memoryview)):
self._is_multipart = True
_safe_header(name)
type_options: MultiDict[str] = MultiDict({"name": name})
if filename is not None and not isinstance(filename, str):
raise TypeError("filename must be an instance of str. Got: %s" % filename)
if filename is None and isinstance(value, io.IOBase):
filename = guess_filename(value, name)
if filename is not None:
_safe_header(filename)
type_options["filename"] = filename
self._is_multipart = True
headers = {}
if content_type is not None:
if not isinstance(content_type, str):
raise TypeError(
"content_type must be an instance of str. Got: %s" % content_type
)
_safe_header(content_type)
headers[hdrs.CONTENT_TYPE] = content_type
self._is_multipart = True
self._fields.append((type_options, headers, value))View on GitHub (pinned to c0ef574e29)
Solutions
- Coerce the filename to str before passing: filename=str(path).
- Use Path.name / os.path.basename to get a plain string leaf name.
- If using a pathlib.Path, pass str(path.name).
Example fix
// before
form.add_field('upload', fp, filename=Path('/data/report.pdf'))
// after
form.add_field('upload', fp, filename=str(Path('/data/report.pdf').name)) Defensive patterns
Strategy: type-guard
Validate before calling
def coerce_filename(fn):
if fn is None:
return None
if isinstance(fn, str):
return fn
if isinstance(fn, os.PathLike):
return os.path.basename(os.fspath(fn))
return str(fn) Type guard
def is_str_filename(fn) -> bool:
return fn is None or isinstance(fn, str) Prevention
- Always wrap Path objects with str(path.name) before passing as filename.
- Keep filename parameters as plain str or None at API boundaries.
- Run mypy/pyright with strict optional checks on FormData call sites.
When it happens
Trigger: Calling form.add_field('file', fp, filename=Path('x.txt')) or FormData(fields=..., filename=123). Also triggered when an (name, file) pair is built programmatically and a Path object is passed where a filename string is expected.
Common situations: Developers coming from requests where Path objects are accepted; passing os.path.join results wrapped in Path; integer filename IDs from DB rows passed as filename.
Related errors
- content_type must be an instance of str. Got: %s
- Only io.IOBase, multidict and (name, file) pairs allowed, us
- data argument must be str (%r)
- data argument must be byte-ish (%r)
- expected str, got {value!r}
AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04).
Data as JSON: /data/errors/1c2dd7d5de19f6ed.json.
Report an issue: GitHub.