Repository: google/google-api-python-client
Pull request #312 (2016-11-28 17:34:50)

Fix error in resumable upload handler
The client library depends on the range header being in the response when the HTTP response code is 308, but the range header is not present in the response when the server hasn't received any bytes previously. This PR defaults to 0 in that case.


jonparrott: @matthewayne it would be great if you could also update the test cases to cover this situation.

Patch:
diff --git a/README.md b/README.md
index 19590c77d..03bdf8c9f 100644
--- a/README.md
+++ b/README.md
@@ -18,9 +18,7 @@ See the [Developers Guide](https://developers.google.com/api-client-library/pyth
 # Python Version
 Python 2.6 or 2.7 are fully supported.
 
-Python 3.3+ is also now supported! However, this library has not yet been used
-as thoroughly with Python 3, so we'd recommend testing before deploying with
-Python 3 in production.
+Python 3.3 and 3.4 are supported and tested. This library may work on later versions of 3, but we do not currently run tests against those versions.
 
 # Third Party Libraries and Dependencies
 The following libraries will be installed when you install the client library:
diff --git a/googleapiclient/http.py b/googleapiclient/http.py
index 14580f0c2..0ef10b955 100644
--- a/googleapiclient/http.py
+++ b/googleapiclient/http.py
@@ -996,7 +996,11 @@ class HttpRequest(object):
     elif resp.status == 308:
       self._in_error_state = False
       # A "308 Resume Incomplete" indicates we are not done.
-      self.resumable_progress = int(resp['range'].split('-')[1]) + 1
+      try:
+        self.resumable_progress = int(resp['range'].split('-')[1]) + 1
+      except KeyError:
+        # If resp doesn't contain range header, resumable progress is 0
+        self.resumable_progress = 0
       if 'location' in resp:
         self.resumable_uri = resp['location']
     else:


Test patch:
diff --git a/tests/test_discovery.py b/tests/test_discovery.py
index 210717d26..7b34606e2 100644
--- a/tests/test_discovery.py
+++ b/tests/test_discovery.py
@@ -868,10 +868,12 @@ class Discovery(unittest.TestCase):
       ({'status': '200',
         'location': 'http://upload.example.com'}, ''),
       ({'status': '308',
-        'location': 'http://upload.example.com/2',
-        'range': '0-12'}, ''),
+        'location': 'http://upload.example.com/2'}, ''),
       ({'status': '308',
         'location': 'http://upload.example.com/3',
+        'range': '0-12'}, ''),
+      ({'status': '308',
+        'location': 'http://upload.example.com/4',
         'range': '0-%d' % (media_upload.size() - 2)}, ''),
       ({'status': '200'}, '{"foo": "bar"}'),
       ])
@@ -879,17 +881,23 @@ class Discovery(unittest.TestCase):
     status, body = request.next_chunk(http=http)
     self.assertEquals(None, body)
     self.assertTrue(isinstance(status, MediaUploadProgress))
-    self.assertEquals(13, status.resumable_progress)
+    self.assertEquals(0, status.resumable_progress)
 
     # Two requests should have been made and the resumable_uri should have been
     # updated for each one.
     self.assertEquals(request.resumable_uri, 'http://upload.example.com/2')
-
+    self.assertEquals(media_upload, request.resumable)
+    self.assertEquals(0, request.resumable_progress)
+    
+    # This next chuck call should upload the first chunk
+    status, body = request.next_chunk(http=http)
+    self.assertEquals(request.resumable_uri, 'http://upload.example.com/3')
     self.assertEquals(media_upload, request.resumable)
     self.assertEquals(13, request.resumable_progress)
 
+    # This call will upload the next chunk
     status, body = request.next_chunk(http=http)
-    self.assertEquals(request.resumable_uri, 'http://upload.example.com/3')
+    self.assertEquals(request.resumable_uri, 'http://upload.example.com/4')
     self.assertEquals(media_upload.size()-1, request.resumable_progress)
     self.assertEquals('{"data": {}}', request.body)