Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Read data from file-like objects in requests (#719) #736

Merged
merged 4 commits into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()
Loading