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

  1. Guard the files= argument: only pass it when the dict/list is non-empty (if files: requests.post(..., files=files) else: requests.post(...)).
  2. If your intent is form data with no files, use the data= argument instead of files=.
  3. 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

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


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)