diff --git a/botocore/awsrequest.py b/botocore/awsrequest.py index 5a57b41882..d3e654ad46 100644 --- a/botocore/awsrequest.py +++ b/botocore/awsrequest.py @@ -82,10 +82,13 @@ def __init__(self, original_request): def reset_stream_on_redirect(self, response, **kwargs): if response.status_code in REDIRECT_STATI and \ - isinstance(self.body, file_type): + self._looks_like_file(self.body): logger.debug("Redirect received, rewinding stream: %s", self.body) self.reset_stream() + def _looks_like_file(self, body): + return hasattr(body, 'read') and hasattr(body, 'seek') + def reset_stream(self): # Trying to reset a stream when there is a no stream will # just immediately return. It's not an error, it will produce diff --git a/tests/unit/test_awsrequest.py b/tests/unit/test_awsrequest.py index 1a6f28131a..f133250c68 100644 --- a/tests/unit/test_awsrequest.py +++ b/tests/unit/test_awsrequest.py @@ -79,7 +79,7 @@ def test_cannot_reset_stream_raises_error(self): # This means that we read the body: body.read() # And create a response object that indicates - # a redirect. + # a redirect fake_response = Mock() fake_response.status_code = 307 @@ -87,6 +87,34 @@ def test_cannot_reset_stream_raises_error(self): with self.assertRaises(UnseekableStreamError): self.prepared_request.reset_stream_on_redirect(fake_response) + def test_duck_type_for_file_check(self): + # As part of determining whether or not we can rewind a stream + # we first need to determine if the thing is a file like object. + # We should not be using an isinstance check. Instead, we should + # be using duck type checks. + class LooksLikeFile(object): + def __init__(self): + self.seek_called = False + + def read(self, amount=None): + pass + + def seek(self, where): + self.seek_called = True + + looks_like_file = LooksLikeFile() + self.prepared_request.body = looks_like_file + + fake_response = Mock() + fake_response.status_code = 307 + + # Then requests calls our reset_stream hook. + self.prepared_request.reset_stream_on_redirect(fake_response) + + # The stream should now be reset. + self.assertTrue(looks_like_file.seek_called) + + if __name__ == "__main__": unittest.main()