diff --git a/requests/exceptions.py b/requests/exceptions.py index f45d9c76..2f093baf 100644 --- a/requests/exceptions.py +++ b/requests/exceptions.py @@ -104,12 +104,15 @@ class StreamConsumedError(RequestException, TypeError): class RetryError(RequestException): """Custom retries logic failed""" + class UnrewindableBodyError(RequestException): """Requests encountered an error when trying to rewind a body""" + class ConflictingHeaderError(RequestException): """Mutually exclusive request headers set""" + class InvalidBodyError(RequestException, ValueError): """An invalid request body was specified""" diff --git a/requests/models.py b/requests/models.py index c1b8ba0c..2b3e1fe3 100644 --- a/requests/models.py +++ b/requests/models.py @@ -39,7 +39,7 @@ from .utils import ( guess_filename, get_auth_from_url, requote_uri, stream_decode_response_unicode, to_key_val_list, parse_header_links, iter_slices, guess_json_utf, super_len, check_header_validity, - determine_if_stream) + is_stream) from .compat import ( cookielib, urlunparse, urlsplit, urlencode, str, bytes, StringIO, is_py2, chardet, builtin_str, basestring) @@ -468,9 +468,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): if not isinstance(body, bytes): body = body.encode('utf-8') - is_stream = determine_if_stream(data) - - if is_stream: + if is_stream(data): body = data if getattr(body, 'tell', None) is not None: @@ -509,30 +507,26 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): def prepare_content_length(self, body): """Prepares Content-Length header. - If the length of the body of the request can be computed, Content-Length is set using - super_len. If user has manually set either a Transfer-Encoding or Content-Length header - when it should not be set (they should be mutually exclusive) an ConflictingHeaderError + If the length of the body of the request can be computed, Content-Length + is set using ``super_len``. If user has manually set either a + Transfer-Encoding or Content-Length header when it should not be set + (they should be mutually exclusive) a ConflictingHeaderError error will be raised. """ if body is not None: - is_stream = determine_if_stream(body) - - try: - length = super_len(body) - except (TypeError, AttributeError, UnsupportedOperation): - length = None + length = super_len(body) if length: self.headers['Content-Length'] = builtin_str(length) - elif is_stream and not length: + elif is_stream(body): self.headers['Transfer-Encoding'] = 'chunked' else: - raise InvalidBodyError("Non-null body must have length or be streamable") - elif (self.method not in ('GET', 'HEAD')) and (self.headers.get('Content-Length') is None): + raise InvalidBodyError('Non-null body must have length or be streamable.') + elif self.method not in ('GET', 'HEAD') and self.headers.get('Content-Length') is None: self.headers['Content-Length'] = '0' if 'Transfer-Encoding' in self.headers and 'Content-Length' in self.headers: - raise ConflictingHeaderError('Transfer-Encoding and Content-Length headers both set') + raise ConflictingHeaderError('Transfer-Encoding and Content-Length headers both set.') def prepare_auth(self, auth, url=''): """Prepares the given HTTP auth data.""" diff --git a/requests/utils.py b/requests/utils.py index e541a626..fff14db7 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -881,9 +881,8 @@ def rewind_body(prepared_request): raise UnrewindableBodyError("Unable to rewind request body for redirect.") -def determine_if_stream(data): - """Given data, determines if it should be sent as a stream. - """ +def is_stream(data): + """Given data, determines if it should be sent as a stream.""" is_iterable = getattr(data, '__iter__', False) - is_io_type = not isinstance(data, (basestring, list, tuple, dict)) + is_io_type = not isinstance(data, (basestring, list, tuple, collections.Mapping)) return is_iterable and is_io_type diff --git a/tests/test_requests.py b/tests/test_requests.py index a9de9c17..3beb861d 100755 --- a/tests/test_requests.py +++ b/tests/test_requests.py @@ -1925,16 +1925,18 @@ class TestRequests: assert 'Content-Length' not in prepared_request.headers def test_chunked_upload_with_manually_set_content_length_header_raises_error(self, httpbin): - """Ensure that if a user manually sets a content length header when the data - is chunked that an ConflictingHeaderError is raised""" - data = (i for i in [b'a', b'b', b'c']) + """Ensure that if a user manually sets a content length header, when + the data is chunked, that a ConflictingHeaderError is raised. + """ + data = (i for i in [b'a', b'b', b'c']) url = httpbin('post') with pytest.raises(ConflictingHeaderError): r = requests.post(url, data=data, headers={'Content-Length': 'foo'}) def test_content_length_with_manually_set_transfer_encoding_raises_error(self, httpbin): - """Ensure that if a user manually sets a Transfer-Encoding header when data is not chunked - that an ConflictingHeaderError is raised""" + """Ensure that if a user manually sets a Transfer-Encoding header when + data is not chunked that an ConflictingHeaderError is raised. + """ data = 'test data' url = httpbin('post') with pytest.raises(ConflictingHeaderError): @@ -1945,7 +1947,7 @@ class TestRequests: try: requests.post(url, data=None) except ConflictingHeaderError: - pytest.fail('ConflictingHeaderError raised') + pytest.fail('ConflictingHeaderError raised.') def test_custom_redirect_mixin(self, httpbin): """Tests a custom mixin to overwrite ``get_redirect_target``.