Skip to content

Commit

Permalink
Read data from file-like objects in requests (#719) (#736)
Browse files Browse the repository at this point in the history
* Read data from file-like objects in requests (#719)
* fix line length
* fix missing tempfile import
* Fix mypy errors

---------

Co-authored-by: Mark Story <[email protected]>
  • Loading branch information
mrob95 and markstory authored Sep 9, 2024
1 parent b68e513 commit fc457c7
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 0 deletions.
17 changes: 17 additions & 0 deletions responses/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1054,6 +1054,22 @@ def _parse_request_params(
params[key] = values
return params

def _read_filelike_body(
self, body: Union[str, bytes, BufferedReader, None]
) -> Union[str, bytes, None]:
# Requests/urllib support multiple types of body, including file-like objects.
# Read from the file if it's a file-like object to avoid storing a closed file
# in the call list and allow the user to compare against the data that was in the
# request.
# See GH #719
if isinstance(body, str) or isinstance(body, bytes) or body is None:
return body
# Based on
# https://github.com/urllib3/urllib3/blob/abbfbcb1dd274fc54b4f0a7785fd04d59b634195/src/urllib3/util/request.py#L220
if hasattr(body, "read") or isinstance(body, BufferedReader):
return body.read()
return body

def _on_request(
self,
adapter: "HTTPAdapter",
Expand All @@ -1067,6 +1083,7 @@ def _on_request(
request.params = self._parse_request_params(request.path_url) # type: ignore[attr-defined]
request.req_kwargs = kwargs # type: ignore[attr-defined]
request_url = str(request.url)
request.body = self._read_filelike_body(request.body)

match, match_failed_reasons = self._find_match(request)
resp_callback = self.response_callback
Expand Down
21 changes: 21 additions & 0 deletions responses/tests/test_responses.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import inspect
import os
import re
import tempfile
import warnings
from io import BufferedReader
from io import BytesIO
Expand Down Expand Up @@ -2708,3 +2709,23 @@ def run():

run()
assert_reset()


def test_file_like_body_in_request():
"""Validate that when file-like objects are used in requests the data can be accessed
in the call list. This ensures that we are not storing file handles that may be closed
by the time the user wants to assert on the data in the request. GH #719.
"""

@responses.activate
def run():
responses.add(responses.POST, "https://example.com")
with tempfile.TemporaryFile() as f:
f.write(b"test")
f.seek(0)
requests.post("https://example.com", data=f)
assert len(responses.calls) == 1
assert responses.calls[0].request.body == b"test"

run()
assert_reset()

0 comments on commit fc457c7

Please sign in to comment.