psf/requests · error · NotImplementedError

Streamed bodies and files are mutually exclusive.

Error message

Streamed bodies and files are mutually exclusive.

What it means

A `NotImplementedError` raised in `PreparedRequest.prepare_body` when `data` is a stream (a generator, iterator, or file-like object that isn't a str/bytes/list/tuple/dict) and `files` is also supplied. A streamed body is sent as-is (chunked or with Content-Length), while `files` requires building a multipart body in memory — the two body sources cannot be combined, and requests has no implementation to interleave them.

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 414f0513c3)

Solutions

  1. Pick one body style: for multipart uploads put the stream into `files` (`files={'file': open('big.bin','rb')}`) and drop `data=` or make it a field dict.
  2. For raw streaming uploads, use `data=fileobj` alone and set the Content-Type header yourself; do not pass `files`.
  3. For streaming multipart of large files, use `requests_toolbelt.MultipartEncoder` and pass it as `data=` with its content type header.

Example fix

# before
requests.post(url, data=open('big.bin', 'rb'), files={'meta': ('m.json', meta)})

# after
requests.post(url, files={'file': open('big.bin', 'rb'),
                          'meta': ('m.json', meta, 'application/json')})
Defensive patterns

Strategy: validation

Validate before calling

is_stream = data is not None and not isinstance(data, (str, bytes, dict, list, tuple))
if is_stream and files:
    raise ValueError("pick one: a streamed body (file-like/generator data=) OR multipart files=")

Type guard

def is_streamed_body(data):
    return data is not None and not isinstance(data, (str, bytes, dict, list, tuple)) and (hasattr(data, "read") or hasattr(data, "__iter__"))

Try / catch

try:
    resp = requests.post(url, data=body_stream, files=files)
except NotImplementedError:
    # can't mix a generator/file-like data= with files=; use multipart for everything
    resp = requests.post(url, files={**files, "body": body_stream})

Prevention

When it happens

Trigger: `requests.post(url, data=open('big.bin','rb'), files={'f': fh})`, or `data=my_generator()` together with `files=`. Any call where `prepare_body` classifies `data` as iterable-but-not-basic (src/requests/models.py:602-603) while `files` is truthy.

Common situations: Upgrading an upload endpoint from streaming raw bytes to multipart and leaving the old file-object `data=` in place; passing a file handle to `data=` when the intent was `files=`; wrapper functions that forward both parameters unconditionally.

Related errors


AI-assisted analysis of psf/requests@414f0513c3 (2026-07-31). Data as JSON: /data/errors/4fc31d6ad99080ec.json. Report an issue: GitHub ↗.