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
- If you need streaming, drop files= and send the stream as the raw body (data=stream) with an explicit Content-Type.
- If you need multipart, materialize the data into a dict/list (not a generator) and keep files=.
- 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
- Never combine a generator/iterator data= with files=.
- Choose one upload strategy per request (streaming raw body OR multipart).
- Materialize generators into lists before multipart uploads.
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
- Auth hooks must be callable.
- Cookie headers should be added with…
- Data must not be a string.
- {e}
- Files must be provided.
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)