diff --git a/responses/__init__.py b/responses/__init__.py index 097e61ee..fb375166 100644 --- a/responses/__init__.py +++ b/responses/__init__.py @@ -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", @@ -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 diff --git a/responses/tests/test_responses.py b/responses/tests/test_responses.py index 9088ff3f..012eb90f 100644 --- a/responses/tests/test_responses.py +++ b/responses/tests/test_responses.py @@ -1,6 +1,7 @@ import inspect import os import re +import tempfile import warnings from io import BufferedReader from io import BytesIO @@ -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()