psf/requests · error · ValueError
Files must be provided.
Error message
Files must be provided.
What it means
Raised by Request._encode_files (the multipart/form-data body builder) when the files argument is empty or None. Multipart encoding is only meaningful when at least one file is supplied; an empty files dict usually indicates the caller reached the multipart path by mistake. The guard fails fast rather than emitting a malformed body.
Solutions
- Guard the files= argument: only pass it when the dict/list is non-empty (if files: requests.post(..., files=files) else: requests.post(...)).
- If your intent is form data with no files, use the data= argument instead of files=.
- Initialize files with a sentinel and skip the request entirely if no attachments were collected.
Example fix
// before
files = {k: v for k, v in uploads if v}
requests.post(url, files=files)
// after
files = {k: v for k, v in uploads if v}
if files:
requests.post(url, files=files)
else:
requests.post(url) Defensive patterns
Strategy: validation
Validate before calling
def post_with_optional_files(url, files=None, **kw):
if files:
return requests.post(url, files=files, **kw)
return requests.post(url, **kw) Type guard
from typing import Mapping, Any
def has_files(files) -> bool:
return bool(files) and (len(files) > 0) Try / catch
# ValueError('Files must be provided.') indicates a logic bug; prefer to fix the caller.
# Defensive form:
if not files:
raise ValueError('no files to upload')
requests.post(url, files=files) Prevention
- Only pass files= when the collection is non-empty.
- Use data= for form fields when no attachments are present.
- Gate upload code behind an explicit 'if attachments' branch.
When it happens
Trigger: Calling requests.post(url, files={}) or files=None; building the files dict conditionally and the condition evaluated to empty; passing a dict comprehension that filters down to zero entries.
Common situations: Optional file-upload UIs where the user attached nothing but the request code still takes the files= branch; dynamically-built upload payloads where all attachments were filtered out by validation.
Related errors
- Data must not be a string.
- Streamed bodies and files are mutually exclusive.
- Unsupported event specified, with event name
- You can only merge into CookieJar
- create_cookie() got unexpected keyword arguments
AI-assisted analysis of psf/requests@8068356288 (2026-08-11).
Data as JSON: /api/errors/1dbcf8ed1b12d2cf.
Report an issue: GitHub.
Appendix: source
Thrown at src/requests/models.py:195
)
return urlencode(result, doseq=True)
else:
return data # type: ignore[return-value] # unreachable for valid _t.DataType
@staticmethod
def _encode_files(
files: _t.FilesType, data: _t.RawDataType | None
) -> tuple[bytes, str]:
"""Build the body for a multipart/form-data request.
Will successfully encode files when passed as a dict or a list of
tuples. Order is retained if data is a list of tuples but arbitrary
if parameters are supplied as a dict.
The tuples may be 2-tuples (filename, fileobj), 3-tuples (filename, fileobj, contentype)
or 4-tuples (filename, fileobj, contentype, custom_headers).
"""
if not files:
raise ValueError("Files must be provided.")
elif isinstance(data, basestring):
raise ValueError("Data must not be a string.")
new_fields: list[RequestField | tuple[str, bytes]] = []
fields = to_key_val_list(data or {})
files = to_key_val_list(files or {})
for field, val in fields:
if isinstance(val, basestring) or not hasattr(val, "__iter__"):
val = [val]
for v in val:
if v is not None:
# Don't call str() on bytestrings: in Py3 it all goes wrong.
if not isinstance(v, bytes):
v = str(v)
new_fields.append(
(View on GitHub (pinned to 8068356288)