diff --git a/requests/utils.py b/requests/utils.py index 149b7a0d..4b2126e6 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -45,7 +45,7 @@ def dict_to_sequence(d): def super_len(o): - total_length = 0 + total_length = None current_position = 0 if hasattr(o, '__len__'): @@ -54,10 +54,6 @@ def super_len(o): elif hasattr(o, 'len'): total_length = o.len - elif hasattr(o, 'getvalue'): - # e.g. BytesIO, cStringIO.StringIO - total_length = len(o.getvalue()) - elif hasattr(o, 'fileno'): try: fileno = o.fileno() @@ -87,7 +83,22 @@ def super_len(o): # is actually a special file descriptor like stdin. In this # instance, we don't know what the length is, so set it to zero and # let requests chunk it instead. - current_position = total_length + if total_length is not None: + current_position = total_length + + if hasattr(o, 'seek') and total_length is None: + # StringIO and BytesIO have seek but no useable fileno + + # seek to end of file + o.seek(0, 2) + total_length = o.tell() + + # seek back to current position to support + # partially read file-like objects + o.seek(current_position or 0) + + if total_length is None: + total_length = 0 return max(0, total_length - current_position) diff --git a/tests/test_utils.py b/tests/test_utils.py index 03cff7a6..7e0b4f28 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -66,6 +66,34 @@ class TestSuperLen: assert super_len(fd) == 4 assert len(recwarn) == warnings_num + def test_super_len_with__len__(self): + foo = [1,2,3,4] + len_foo = super_len(foo) + assert len_foo == 4 + + def test_super_len_with_no__len__(self): + class LenFile(object): + def __init__(self): + self.len = 5 + + assert super_len(LenFile()) == 5 + + def test_super_len_with_tell(self): + foo = StringIO.StringIO('12345') + assert super_len(foo) == 5 + foo.read(2) + assert super_len(foo) == 3 + + def test_super_len_with_fileno(self): + with open(__file__, 'rb') as f: + length = super_len(f) + file_data = f.read() + assert length == len(file_data) + + def test_super_len_with_no_matches(self): + """Ensure that objects without any length methods default to 0""" + assert super_len(object()) == 0 + class TestToKeyValList: