psf/requests · error · NotImplementedError

Streamed bodies and files are mutually exclusive.

Error message

Streamed bodies and files are mutually exclusive.

What it means

Raised by PreparedRequest.prepare_body when both a streamed (iterable, non-str/bytes/list/tuple/Mapping) body and a files= argument are supplied. The multipart encoder needs to size and frame the body, which is incompatible with an open-ended stream. requests refuses the combination rather than guessing how to interleave them.

Solutions

  1. If you need streaming, drop files= and send the stream as the raw body (data=stream) with an explicit Content-Type.
  2. If you need multipart, materialize the data into a dict/list (not a generator) and keep files=.
  3. Choose one upload strategy per request — never both.

Example fix

// before
def gen(): yield b'chunk'
requests.post(url, data=gen(), files={'f': open('x','rb')})

// after
# option A: streaming, no multipart
requests.post(url, data=gen(), headers={'Content-Type': 'application/octet-stream'})
# option B: multipart, materialize fields
requests.post(url, data={'k': 'v'}, files={'f': open('x','rb')})
Defensive patterns

Strategy: validation

Validate before calling

from collections.abc import Iterable, Mapping

def is_streaming_body(data) -> bool:
    if data is None or isinstance(data, (str, bytes, list, tuple, Mapping)):
        return False
    return isinstance(data, Iterable) or hasattr(data, '__iter__')

def assert_not_streaming_with_files(data, files):
    if files and is_streaming_body(data):
        raise ValueError('cannot combine streaming data= with files=')

Type guard

from collections.abc import Iterable, Mapping

def is_materialized(data) -> bool:
    return data is None or isinstance(data, (str, bytes, list, tuple, Mapping))

Try / catch

# NotImplementedError signals a misuse; fix the caller rather than catch.
# Defensive form:
from collections.abc import Iterable, Mapping

if files and not (data is None or isinstance(data, (str, bytes, list, tuple, Mapping))):
    # materialize or drop files
    data = list(data) if isinstance(data, Iterable) else data
requests.post(url, data=data, files=files)

Prevention

When it happens

Trigger: Calling requests.post(url, data=generator_obj, files={'f': f}) where data is a generator/iterator; passing a streaming source (e.g. a file-like object yielding chunks) together with files for multipart upload; mixing chunked upload semantics with multipart.

Common situations: Upload code that evolved from form-fields+files to streaming content but kept the files= argument; pipelines that wrap data= in a generator for memory efficiency and forgot to remove files=.

Related errors


AI-assisted analysis of psf/requests@8068356288 (2026-08-11). Data as JSON: /api/errors/4fc31d6ad99080ec. Report an issue: GitHub.

Appendix: source

Thrown at src/requests/models.py:623

                length = super_len(data)
            except (TypeError, AttributeError, UnsupportedOperation):
                length = None

            body = data

            if getattr(body, "tell", None) is not None:
                # Record the current file position before reading.
                # This will allow us to rewind a file in the event
                # of a redirect.
                try:
                    self._body_position = body.tell()  # type: ignore[union-attr]  # guarded by getattr check
                except OSError:
                    # This differentiates from None, allowing us to catch
                    # a failed `tell()` later when trying to rewind the body
                    self._body_position = object()

            if files:
                raise NotImplementedError(
                    "Streamed bodies and files are mutually exclusive."
                )

            if length:
                self.headers["Content-Length"] = builtin_str(length)
            else:
                self.headers["Transfer-Encoding"] = "chunked"
        else:
            # After is_stream filtering, remaining data is raw (not streamed)
            raw_data = cast("_t.RawDataType | None", data)

            # Multi-part file uploads.
            if files:
                (body, content_type) = self._encode_files(files, raw_data)
            else:
                if raw_data:
                    body = self._encode_params(raw_data)
                    if isinstance(data, basestring) or _t.has_read(data):

View on GitHub (pinned to 8068356288)