Hello community,

here is the log from the commit of package python-google-resumable-media for 
openSUSE:Factory checked in at 2019-09-18 13:09:18
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-google-resumable-media (Old)
 and      /work/SRC/openSUSE:Factory/.python-google-resumable-media.new.7948 
(New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-google-resumable-media"

Wed Sep 18 13:09:18 2019 rev:4 rq:730356 version:0.4.0

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/python-google-resumable-media/python-google-resumable-media.changes
      2019-03-19 09:58:33.376099014 +0100
+++ 
/work/SRC/openSUSE:Factory/.python-google-resumable-media.new.7948/python-google-resumable-media.changes
    2019-09-18 13:09:23.628715723 +0200
@@ -1,0 +2,10 @@
+Thu Sep 12 10:14:02 UTC 2019 - Tomáš Chvátal <[email protected]>
+
+- Update to 0.4.0:
+  * Require 200 response for initial resumable upload request. (#95)
+  * Use response as variable for object returned from http_request. (#98)
+  * Further DRY request dependency pins. (#96)
+  * Finish download on seeing 416 response with zero byte range. (#86)
+  * Always use raw response data. (#87)
+
+-------------------------------------------------------------------

Old:
----
  google-resumable-media-0.3.2.tar.gz

New:
----
  google-resumable-media-0.4.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-google-resumable-media.spec ++++++
--- /var/tmp/diff_new_pack.SE7Ngz/_old  2019-09-18 13:09:24.128715618 +0200
+++ /var/tmp/diff_new_pack.SE7Ngz/_new  2019-09-18 13:09:24.132715617 +0200
@@ -18,7 +18,7 @@
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 Name:           python-google-resumable-media
-Version:        0.3.2
+Version:        0.4.0
 Release:        0
 Summary:        Utilities for Google Media Downloads and Resumable Uploads
 License:        Apache-2.0
@@ -34,7 +34,7 @@
 BuildRequires:  fdupes
 BuildRequires:  python-rpm-macros
 Requires:       python-six
-Suggests:       python-requests >= 2.18.0
+Recommends:     python-requests >= 2.18.0
 BuildArch:      noarch
 %python_subpackages
 

++++++ google-resumable-media-0.3.2.tar.gz -> 
google-resumable-media-0.4.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/google-resumable-media-0.3.2/PKG-INFO 
new/google-resumable-media-0.4.0/PKG-INFO
--- old/google-resumable-media-0.3.2/PKG-INFO   2018-12-20 23:28:49.000000000 
+0100
+++ new/google-resumable-media-0.4.0/PKG-INFO   2019-09-06 18:49:51.000000000 
+0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: google-resumable-media
-Version: 0.3.2
+Version: 0.4.0
 Summary: Utilities for Google Media Downloads and Resumable Uploads
 Home-page: https://github.com/GoogleCloudPlatform/google-resumable-media-python
 Author: Google Cloud Platform
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/google-resumable-media-0.3.2/google/resumable_media/_download.py 
new/google-resumable-media-0.4.0/google/resumable_media/_download.py
--- old/google-resumable-media-0.3.2/google/resumable_media/_download.py        
2018-12-20 20:33:22.000000000 +0100
+++ new/google-resumable-media-0.4.0/google/resumable_media/_download.py        
2019-09-06 18:48:30.000000000 +0200
@@ -28,6 +28,7 @@
     flags=re.IGNORECASE)
 _ACCEPTABLE_STATUS_CODES = (http_client.OK, http_client.PARTIAL_CONTENT)
 _GET = u'GET'
+_ZERO_CONTENT_RANGE_HEADER = u'bytes */0'
 
 
 class DownloadBase(object):
@@ -340,21 +341,38 @@
         .. _sans-I/O: https://sans-io.readthedocs.io/
         """
         # Verify the response before updating the current instance.
+        if _check_for_zero_content_range(response, self._get_status_code,
+                                         self._get_headers):
+            self._finished = True
+            return
+
         _helpers.require_status_code(
             response, _ACCEPTABLE_STATUS_CODES,
             self._get_status_code, callback=self._make_invalid)
-        content_length = _helpers.header_required(
-            response, u'content-length', self._get_headers,
-            callback=self._make_invalid)
-        num_bytes = int(content_length)
-        _, end_byte, total_bytes = get_range_info(
-            response, self._get_headers, callback=self._make_invalid)
+        headers = self._get_headers(response)
         response_body = self._get_body(response)
-        if len(response_body) != num_bytes:
-            self._make_invalid()
-            raise common.InvalidResponse(
-                response, u'Response is different size than content-length',
-                u'Expected', num_bytes, u'Received', len(response_body))
+
+        start_byte, end_byte, total_bytes = get_range_info(
+            response, self._get_headers, callback=self._make_invalid)
+
+        transfer_encoding = headers.get(u'transfer-encoding')
+
+        if transfer_encoding is None:
+            content_length = _helpers.header_required(
+                response, u'content-length', self._get_headers,
+                callback=self._make_invalid)
+            num_bytes = int(content_length)
+            if len(response_body) != num_bytes:
+                self._make_invalid()
+                raise common.InvalidResponse(
+                    response,
+                    u'Response is different size than content-length',
+                    u'Expected', num_bytes,
+                    u'Received', len(response_body),
+                )
+        else:
+            # 'content-length' header not allowed with chunked encoding.
+            num_bytes = end_byte - start_byte + 1
 
         # First update ``bytes_downloaded``.
         self._bytes_downloaded += num_bytes
@@ -466,3 +484,28 @@
         int(match.group(u'end_byte')),
         int(match.group(u'total_bytes'))
     )
+
+
+def _check_for_zero_content_range(response, get_status_code, get_headers):
+    """ Validate if response status code is 416 and content range is zero.
+
+    This is the special case for handling zero bytes files.
+
+    Args:
+        response (object): An HTTP response object.
+        get_status_code (Callable[Any, int]): Helper to get a status code
+            from a response.
+        get_headers (Callable[Any, Mapping[str, str]]): Helper to get headers
+            from an HTTP response.
+
+    Returns:
+        bool: True if content range total bytes is zero, false otherwise.
+    """
+    if get_status_code(response) == http_client. \
+            REQUESTED_RANGE_NOT_SATISFIABLE:
+        content_range = _helpers.header_required(
+                response, _helpers.CONTENT_RANGE_HEADER,
+                get_headers, callback=_helpers.do_nothing)
+        if content_range == _ZERO_CONTENT_RANGE_HEADER:
+            return True
+    return False
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/google-resumable-media-0.3.2/google/resumable_media/_upload.py 
new/google-resumable-media-0.4.0/google/resumable_media/_upload.py
--- old/google-resumable-media-0.3.2/google/resumable_media/_upload.py  
2018-12-20 08:07:56.000000000 +0100
+++ new/google-resumable-media-0.4.0/google/resumable_media/_upload.py  
2019-09-06 18:48:30.000000000 +0200
@@ -319,7 +319,8 @@
     def __init__(self, upload_url, chunk_size, headers=None):
         super(ResumableUpload, self).__init__(upload_url, headers=headers)
         if chunk_size % resumable_media.UPLOAD_CHUNK_SIZE != 0:
-            raise ValueError(u'256 KB must divide chunk size')
+            raise ValueError(u'{} KB must divide chunk size'.format(
+                resumable_media.UPLOAD_CHUNK_SIZE / 1024))
         self._chunk_size = chunk_size
         self._stream = None
         self._content_type = None
@@ -445,6 +446,12 @@
 
         .. _sans-I/O: https://sans-io.readthedocs.io/
         """
+        _helpers.require_status_code(
+            response,
+            (http_client.OK,),
+            self._get_status_code,
+            callback=self._make_invalid,
+        )
         self._resumable_url = _helpers.header_required(
             response, u'location', self._get_headers)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/google-resumable-media-0.3.2/google/resumable_media/requests/__init__.py 
new/google-resumable-media-0.4.0/google/resumable_media/requests/__init__.py
--- 
old/google-resumable-media-0.3.2/google/resumable_media/requests/__init__.py    
    2018-12-20 08:07:56.000000000 +0100
+++ 
new/google-resumable-media-0.4.0/google/resumable_media/requests/__init__.py    
    2019-09-06 18:48:30.000000000 +0200
@@ -658,20 +658,6 @@
    >>> json_response[u'name'] == blob_name
    True
 """
-
-import pkg_resources
-
-try:
-    pkg_resources.require('requests >= 2.18.0')
-except pkg_resources.ResolutionError as caught_exc:  # pragma: NO COVER
-    import six
-    new_exc = ImportError(
-        '``requests >= 2.18.0`` is required by the '
-        '``google.resumable_media.requests`` subpackage.\n'
-        'It can be installed via\n'
-        '    pip install google-resumable-media[requests].')
-    six.raise_from(new_exc, caught_exc)
-
 from google.resumable_media.requests.download import ChunkedDownload
 from google.resumable_media.requests.download import Download
 from google.resumable_media.requests.upload import MultipartUpload
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/google-resumable-media-0.3.2/google/resumable_media/requests/_helpers.py 
new/google-resumable-media-0.4.0/google/resumable_media/requests/_helpers.py
--- 
old/google-resumable-media-0.3.2/google/resumable_media/requests/_helpers.py    
    2018-12-12 22:17:35.000000000 +0100
+++ 
new/google-resumable-media-0.4.0/google/resumable_media/requests/_helpers.py    
    2019-09-06 18:48:30.000000000 +0200
@@ -25,6 +25,13 @@
 
 
 _DEFAULT_RETRY_STRATEGY = common.RetryStrategy()
+_SINGLE_GET_CHUNK_SIZE = 8192
+# The number of seconds to wait to establish a connection
+# (connect() call on socket). Avoid setting this to a multiple of 3 to not
+# Align with TCP Retransmission timing. (typically 2.5-3s)
+_DEFAULT_CONNECT_TIMEOUT = 61
+# The number of seconds to wait between bytes sent from the server.
+_DEFAULT_READ_TIMEOUT = 60
 
 
 class RequestsMixin(object):
@@ -69,7 +76,12 @@
         Returns:
             bytes: The body of the ``response``.
         """
-        return response.content
+        if response._content is False:
+            response._content = b''.join(
+                response.raw.stream(
+                    _SINGLE_GET_CHUNK_SIZE, decode_content=False))
+            response._content_consumed = True
+        return response._content
 
 
 def http_request(transport, method, url, data=None, headers=None,
@@ -94,6 +106,10 @@
     Returns:
         ~requests.Response: The return value of ``transport.request()``.
     """
+    if "timeout" not in transport_kwargs:
+        transport_kwargs["timeout"] = (
+            _DEFAULT_CONNECT_TIMEOUT, _DEFAULT_READ_TIMEOUT)
+
     func = functools.partial(
         transport.request, method, url, data=data, headers=headers,
         **transport_kwargs)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/google-resumable-media-0.3.2/google/resumable_media/requests/download.py 
new/google-resumable-media-0.4.0/google/resumable_media/requests/download.py
--- 
old/google-resumable-media-0.3.2/google/resumable_media/requests/download.py    
    2018-12-20 22:14:25.000000000 +0100
+++ 
new/google-resumable-media-0.4.0/google/resumable_media/requests/download.py    
    2019-09-06 18:48:30.000000000 +0200
@@ -18,15 +18,12 @@
 import hashlib
 import logging
 
-import urllib3.response
-
 from google.resumable_media import _download
 from google.resumable_media import common
 from google.resumable_media.requests import _helpers
 
 
 _LOGGER = logging.getLogger(__name__)
-_SINGLE_GET_CHUNK_SIZE = 8192
 _HASH_HEADER = u'x-goog-hash'
 _MISSING_MD5 = u"""\
 No MD5 checksum was returned from the service while downloading {}
@@ -117,12 +114,12 @@
         with response:
             # NOTE: This might "donate" ``md5_hash`` to the decoder and replace
             #       it with a ``_DoNothingHash``.
-            local_hash = _add_decoder(response.raw, md5_hash)
-            body_iter = response.iter_content(
-                chunk_size=_SINGLE_GET_CHUNK_SIZE, decode_unicode=False)
+            body_iter = response.raw.stream(
+                _helpers._SINGLE_GET_CHUNK_SIZE, decode_content=False)
             for chunk in body_iter:
                 self._stream.write(chunk)
-                local_hash.update(chunk)
+                md5_hash.update(chunk)
+            response._content_consumed = True
 
         if expected_md5_hash is None:
             return
@@ -157,23 +154,22 @@
         """
         method, url, payload, headers = self._prepare_request()
         # NOTE: We assume "payload is None" but pass it along anyway.
-        request_kwargs = {
-            u'data': payload,
-            u'headers': headers,
-            u'retry_strategy': self._retry_strategy,
-        }
-        if self._stream is not None:
-            request_kwargs[u'stream'] = True
-
-        result = _helpers.http_request(
-            transport, method, url, **request_kwargs)
+        response = _helpers.http_request(
+            transport,
+            method,
+            url,
+            data=payload,
+            headers=headers,
+            retry_strategy=self._retry_strategy,
+            stream=True,
+        )
 
-        self._process_response(result)
+        self._process_response(response)
 
         if self._stream is not None:
-            self._write_to_stream(result)
+            self._write_to_stream(response)
 
-        return result
+        return response
 
 
 class ChunkedDownload(_helpers.RequestsMixin, _download.ChunkedDownload):
@@ -219,11 +215,11 @@
         """
         method, url, payload, headers = self._prepare_request()
         # NOTE: We assume "payload is None" but pass it along anyway.
-        result = _helpers.http_request(
+        response = _helpers.http_request(
             transport, method, url, data=payload, headers=headers,
-            retry_strategy=self._retry_strategy)
-        self._process_response(result)
-        return result
+            retry_strategy=self._retry_strategy, stream=True)
+        self._process_response(response)
+        return response
 
 
 def _parse_md5_header(header_value, response):
@@ -291,58 +287,3 @@
         Args:
             unused_chunk (bytes): A chunk of data.
         """
-
-
-def _add_decoder(response_raw, md5_hash):
-    """Patch the ``_decoder`` on a ``urllib3`` response.
-
-    This is so that we can intercept the compressed bytes before they are
-    decoded.
-
-    Only patches if the content encoding is ``gzip``.
-
-    Args:
-        response_raw (urllib3.response.HTTPResponse): The raw response for
-            an HTTP request.
-        md5_hash (Union[_DoNothingHash, hashlib.md5]): A hash function which
-            will get updated when it encounters compressed bytes.
-
-    Returns:
-        Union[_DoNothingHash, hashlib.md5]: Either the original ``md5_hash``
-        if ``_decoder`` is not patched. Otherwise, returns a ``_DoNothingHash``
-        since the caller will no longer need to hash to decoded bytes.
-    """
-    encoding = response_raw.headers.get(u'content-encoding', u'').lower()
-    if encoding != u'gzip':
-        return md5_hash
-
-    response_raw._decoder = _GzipDecoder(md5_hash)
-    return _DoNothingHash()
-
-
-class _GzipDecoder(urllib3.response.GzipDecoder):
-    """Custom subclass of ``urllib3`` decoder for ``gzip``-ed bytes.
-
-    Allows an MD5 hash function to see the compressed bytes before they are
-    decoded. This way the hash of the compressed value can be computed.
-
-    Args:
-        md5_hash (Union[_DoNothingHash, hashlib.md5]): A hash function which
-            will get updated when it encounters compressed bytes.
-    """
-
-    def __init__(self, md5_hash):
-        super(_GzipDecoder, self).__init__()
-        self._md5_hash = md5_hash
-
-    def decompress(self, data):
-        """Decompress the bytes.
-
-        Args:
-            data (bytes): The compressed bytes to be decompressed.
-
-        Returns:
-            bytes: The decompressed bytes from ``data``.
-        """
-        self._md5_hash.update(data)
-        return super(_GzipDecoder, self).decompress(data)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/google-resumable-media-0.3.2/google/resumable_media/requests/upload.py 
new/google-resumable-media-0.4.0/google/resumable_media/requests/upload.py
--- old/google-resumable-media-0.3.2/google/resumable_media/requests/upload.py  
2018-12-12 22:17:35.000000000 +0100
+++ new/google-resumable-media-0.4.0/google/resumable_media/requests/upload.py  
2019-09-06 18:48:30.000000000 +0200
@@ -53,11 +53,11 @@
         """
         method, url, payload, headers = self._prepare_request(
             data, content_type)
-        result = _helpers.http_request(
+        response = _helpers.http_request(
             transport, method, url, data=payload, headers=headers,
             retry_strategy=self._retry_strategy)
-        self._process_response(result)
-        return result
+        self._process_response(response)
+        return response
 
 
 class MultipartUpload(_helpers.RequestsMixin, _upload.MultipartUpload):
@@ -92,11 +92,11 @@
         """
         method, url, payload, headers = self._prepare_request(
             data, metadata, content_type)
-        result = _helpers.http_request(
+        response = _helpers.http_request(
             transport, method, url, data=payload, headers=headers,
             retry_strategy=self._retry_strategy)
-        self._process_response(result)
-        return result
+        self._process_response(response)
+        return response
 
 
 class ResumableUpload(_helpers.RequestsMixin, _upload.ResumableUpload):
@@ -321,11 +321,11 @@
         method, url, payload, headers = self._prepare_initiate_request(
             stream, metadata, content_type,
             total_bytes=total_bytes, stream_final=stream_final)
-        result = _helpers.http_request(
+        response = _helpers.http_request(
             transport, method, url, data=payload, headers=headers,
             retry_strategy=self._retry_strategy)
-        self._process_initiate_response(result)
-        return result
+        self._process_initiate_response(response)
+        return response
 
     def transmit_next_chunk(self, transport):
         """Transmit the next chunk of the resource to be uploaded.
@@ -390,11 +390,11 @@
                 code is not 200 or 308.
         """
         method, url, payload, headers = self._prepare_request()
-        result = _helpers.http_request(
+        response = _helpers.http_request(
             transport, method, url, data=payload, headers=headers,
             retry_strategy=self._retry_strategy)
-        self._process_response(result, len(payload))
-        return result
+        self._process_response(response, len(payload))
+        return response
 
     def recover(self, transport):
         """Recover from a failure.
@@ -415,8 +415,8 @@
         """
         method, url, payload, headers = self._prepare_recover_request()
         # NOTE: We assume "payload is None" but pass it along anyway.
-        result = _helpers.http_request(
+        response = _helpers.http_request(
             transport, method, url, data=payload, headers=headers,
             retry_strategy=self._retry_strategy)
-        self._process_recover_response(result)
-        return result
+        self._process_recover_response(response)
+        return response
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/google-resumable-media-0.3.2/google_resumable_media.egg-info/PKG-INFO 
new/google-resumable-media-0.4.0/google_resumable_media.egg-info/PKG-INFO
--- old/google-resumable-media-0.3.2/google_resumable_media.egg-info/PKG-INFO   
2018-12-20 23:28:45.000000000 +0100
+++ new/google-resumable-media-0.4.0/google_resumable_media.egg-info/PKG-INFO   
2019-09-06 18:49:51.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: google-resumable-media
-Version: 0.3.2
+Version: 0.4.0
 Summary: Utilities for Google Media Downloads and Resumable Uploads
 Home-page: https://github.com/GoogleCloudPlatform/google-resumable-media-python
 Author: Google Cloud Platform
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/google-resumable-media-0.3.2/google_resumable_media.egg-info/SOURCES.txt 
new/google-resumable-media-0.4.0/google_resumable_media.egg-info/SOURCES.txt
--- 
old/google-resumable-media-0.3.2/google_resumable_media.egg-info/SOURCES.txt    
    2018-12-20 23:28:47.000000000 +0100
+++ 
new/google-resumable-media-0.4.0/google_resumable_media.egg-info/SOURCES.txt    
    2019-09-06 18:49:51.000000000 +0200
@@ -20,9 +20,7 @@
 google_resumable_media.egg-info/not-zip-safe
 google_resumable_media.egg-info/requires.txt
 google_resumable_media.egg-info/top_level.txt
-tests/.DS_Store
 tests/__init__.py
-tests/data/.DS_Store
 tests/data/favicon.ico
 tests/data/file.txt
 tests/data/gzipped.txt
@@ -33,6 +31,7 @@
 tests/system/credentials.json.enc
 tests/system/utils.py
 tests/system/requests/__init__.py
+tests/system/requests/conftest.py
 tests/system/requests/test_download.py
 tests/system/requests/test_upload.py
 tests/unit/__init__.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/google-resumable-media-0.3.2/setup.py 
new/google-resumable-media-0.4.0/setup.py
--- old/google-resumable-media-0.3.2/setup.py   2018-12-20 23:22:12.000000000 
+0100
+++ new/google-resumable-media-0.4.0/setup.py   2019-09-06 18:48:30.000000000 
+0200
@@ -34,7 +34,7 @@
 
 setuptools.setup(
     name='google-resumable-media',
-    version='0.3.2',
+    version = '0.4.0',
     description='Utilities for Google Media Downloads and Resumable Uploads',
     author='Google Cloud Platform',
     author_email='[email protected]',
Binary files old/google-resumable-media-0.3.2/tests/.DS_Store and 
new/google-resumable-media-0.4.0/tests/.DS_Store differ
Binary files old/google-resumable-media-0.3.2/tests/data/.DS_Store and 
new/google-resumable-media-0.4.0/tests/data/.DS_Store differ
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/google-resumable-media-0.3.2/tests/system/requests/conftest.py 
new/google-resumable-media-0.4.0/tests/system/requests/conftest.py
--- old/google-resumable-media-0.3.2/tests/system/requests/conftest.py  
1970-01-01 01:00:00.000000000 +0100
+++ new/google-resumable-media-0.4.0/tests/system/requests/conftest.py  
2019-09-06 18:48:30.000000000 +0200
@@ -0,0 +1,61 @@
+# Copyright 2019 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""py.test fixtures to be shared across multiple system test modules."""
+
+import google.auth
+import google.auth.transport.requests as tr_requests
+import pytest
+
+from tests.system import utils
+
+
+def ensure_bucket(transport):
+    get_response = transport.get(utils.BUCKET_URL)
+    if get_response.status_code == 404:
+        credentials = transport.credentials
+        query_params = {
+            'project': credentials.project_id,
+        }
+        payload = {
+            'name': utils.BUCKET_NAME,
+        }
+        post_response = transport.post(
+            utils.BUCKET_POST_URL, params=query_params, json=payload)
+
+        if not post_response.ok:
+            raise ValueError("{}: {}".format(
+                post_response.status_code, post_response.reason))
+
+
+def cleanup_bucket(transport):
+    del_response = transport.delete(utils.BUCKET_URL)
+
+    if not del_response.ok:
+        raise ValueError("{}: {}".format(
+            del_response.status_code, del_response.reason))
+
+
[email protected](scope=u'session')
+def authorized_transport():
+    credentials, _ = google.auth.default(scopes=(utils.GCS_RW_SCOPE,))
+    yield tr_requests.AuthorizedSession(credentials)
+
+
[email protected](scope=u'session')
+def bucket(authorized_transport):
+    ensure_bucket(authorized_transport)
+
+    yield utils.BUCKET_URL
+
+    cleanup_bucket(authorized_transport)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/google-resumable-media-0.3.2/tests/system/requests/test_download.py 
new/google-resumable-media-0.4.0/tests/system/requests/test_download.py
--- old/google-resumable-media-0.3.2/tests/system/requests/test_download.py     
2018-12-20 23:22:12.000000000 +0100
+++ new/google-resumable-media-0.4.0/tests/system/requests/test_download.py     
2019-09-06 18:48:30.000000000 +0200
@@ -25,8 +25,9 @@
 from six.moves import http_client
 
 from google import resumable_media
-import google.resumable_media.requests as resumable_requests
-import google.resumable_media.requests.download as download_mod
+from google.resumable_media import requests as resumable_requests
+from google.resumable_media.requests import download as download_mod
+from google.resumable_media.requests import _helpers
 from tests.system import utils
 
 
@@ -58,11 +59,10 @@
     }, {
         u'path': os.path.realpath(os.path.join(DATA_DIR, u'file.txt')),
         u'content_type': PLAIN_TEXT,
-        u'checksum': u'KHRs/+ZSrc/FuuR4qz/PZQ==',
+        u'checksum': u'XHSHAr/SpIeZtZbjgQ4nGw==',
         u'slices': (),
     }, {
         u'path': os.path.realpath(os.path.join(DATA_DIR, u'gzipped.txt.gz')),
-        u'uncompressed': os.path.realpath(os.path.join(DATA_DIR, 
u'gzipped.txt')),
         u'content_type': PLAIN_TEXT,
         u'checksum': u'KHRs/+ZSrc/FuuR4qz/PZQ==',
         u'slices': (),
@@ -77,7 +77,7 @@
     u'The content for this response was already consumed')
 NOT_FOUND_ERR = (
     b'No such object: ' +
-    bytes(os.environ['GOOGLE_RESUMABLE_MEDIA_BUCKET'], 'utf-8') +
+    utils.BUCKET_NAME.encode('utf-8') +
     b'/does-not-exist.txt'
 )
 
@@ -111,12 +111,6 @@
         return response
 
 
[email protected](scope=u'module')
-def authorized_transport():
-    credentials, _ = google.auth.default(scopes=(utils.GCS_RW_SCOPE,))
-    yield tr_requests.AuthorizedSession(credentials)
-
-
 # Transport that returns corrupt data, so we can exercise checksum handling.
 @pytest.fixture(scope=u'module')
 def corrupting_transport():
@@ -136,18 +130,18 @@
 
 
 def _get_contents(info):
-    full_path = info.get(u'uncompressed', info[u'path'])
+    full_path = info[u'path']
     with open(full_path, u'rb') as file_obj:
         return file_obj.read()
 
 
 def _get_blob_name(info):
-    full_path = info.get(u'uncompressed', info[u'path'])
+    full_path = info[u'path']
     return os.path.basename(full_path)
 
 
 @pytest.fixture(scope=u'module')
-def add_files(authorized_transport):
+def add_files(authorized_transport, bucket):
     blob_names = []
     for info in ALL_FILES:
         to_upload = _get_contents_for_upload(info)
@@ -189,6 +183,11 @@
         assert exc_info.match(u'Download has finished.')
 
 
+def read_raw_content(response):
+    return b''.join(response.raw.stream(
+        _helpers._SINGLE_GET_CHUNK_SIZE, decode_content=False))
+
+
 def test_download_full(add_files, authorized_transport):
     for info in ALL_FILES:
         actual_contents = _get_contents(info)
@@ -200,7 +199,7 @@
         # Consume the resource.
         response = download.consume(authorized_transport)
         assert response.status_code == http_client.OK
-        assert response.content == actual_contents
+        assert read_raw_content(response) == actual_contents
         check_tombstoned(download, authorized_transport)
 
 
@@ -245,7 +244,7 @@
 
 
 @pytest.fixture(scope=u'module')
-def secret_file(authorized_transport):
+def secret_file(authorized_transport, bucket):
     blob_name = u'super-seekrit.txt'
     data = b'Please do not tell anyone my encrypted seekrit.'
 
@@ -290,7 +289,7 @@
     check_tombstoned(download_wo, authorized_transport)
 
 
-def test_non_existent_file(authorized_transport):
+def test_non_existent_file(authorized_transport, bucket):
     blob_name = u'does-not-exist.txt'
     media_url = utils.DOWNLOAD_URL_TEMPLATE.format(blob_name=blob_name)
     download = resumable_requests.Download(media_url)
@@ -303,7 +302,7 @@
 
 
 @pytest.fixture(scope=u'module')
-def simple_file(authorized_transport):
+def simple_file(authorized_transport, bucket):
     blob_name = u'basic-file.txt'
     upload_url = utils.SIMPLE_UPLOAD_TEMPLATE.format(blob_name=blob_name)
     upload = resumable_requests.SimpleUpload(upload_url)
@@ -398,7 +397,7 @@
     return num_responses, response
 
 
-def test_chunked_download(add_files, authorized_transport):
+def test_chunked_download_full(add_files, authorized_transport):
     for info in ALL_FILES:
         actual_contents = _get_contents(info)
         blob_name = _get_blob_name(info)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/google-resumable-media-0.3.2/tests/system/requests/test_upload.py 
new/google-resumable-media-0.4.0/tests/system/requests/test_upload.py
--- old/google-resumable-media-0.3.2/tests/system/requests/test_upload.py       
2018-12-12 22:17:35.000000000 +0100
+++ new/google-resumable-media-0.4.0/tests/system/requests/test_upload.py       
2019-09-06 18:48:30.000000000 +0200
@@ -17,8 +17,6 @@
 import io
 import os
 
-import google.auth
-import google.auth.transport.requests as tr_requests
 import pytest
 from six.moves import http_client
 from six.moves import urllib_parse
@@ -42,12 +40,6 @@
     b'1024 bytes, which does not meet this requirement.')
 
 
[email protected](scope=u'module')
-def authorized_transport():
-    credentials, _ = google.auth.default(scopes=(utils.GCS_RW_SCOPE,))
-    yield tr_requests.AuthorizedSession(credentials)
-
-
 @pytest.fixture
 def cleanup():
     to_delete = []
@@ -116,7 +108,7 @@
     assert response.status_code == http_client.NOT_FOUND
 
 
-def test_simple_upload(authorized_transport, cleanup):
+def test_simple_upload(authorized_transport, bucket, cleanup):
     with open(ICO_FILE, u'rb') as file_obj:
         actual_contents = file_obj.read()
 
@@ -139,7 +131,7 @@
         upload, authorized_transport, actual_contents, ICO_CONTENT_TYPE)
 
 
-def test_simple_upload_with_headers(authorized_transport, cleanup):
+def test_simple_upload_with_headers(authorized_transport, bucket, cleanup):
     blob_name = u'some-stuff.bin'
     # Make sure to clean up the uploaded blob when we are done.
     cleanup(blob_name, authorized_transport)
@@ -163,7 +155,7 @@
         upload, authorized_transport, data, BYTES_CONTENT_TYPE)
 
 
-def test_multipart_upload(authorized_transport, cleanup):
+def test_multipart_upload(authorized_transport, bucket, cleanup):
     with open(ICO_FILE, u'rb') as file_obj:
         actual_contents = file_obj.read()
 
@@ -193,7 +185,7 @@
         metadata, ICO_CONTENT_TYPE)
 
 
-def test_multipart_upload_with_headers(authorized_transport, cleanup):
+def test_multipart_upload_with_headers(authorized_transport, bucket, cleanup):
     blob_name = u'some-multipart-stuff.bin'
     # Make sure to clean up the uploaded blob when we are done.
     cleanup(blob_name, authorized_transport)
@@ -308,12 +300,12 @@
     check_tombstoned(upload, authorized_transport)
 
 
-def test_resumable_upload(authorized_transport, img_stream, cleanup):
+def test_resumable_upload(authorized_transport, img_stream, bucket, cleanup):
     _resumable_upload_helper(authorized_transport, img_stream, cleanup)
 
 
 def test_resumable_upload_with_headers(
-        authorized_transport, img_stream, cleanup):
+        authorized_transport, img_stream, bucket, cleanup):
     headers = utils.get_encryption_headers()
     _resumable_upload_helper(
         authorized_transport, img_stream, cleanup, headers=headers)
@@ -405,11 +397,12 @@
     check_tombstoned(upload, authorized_transport)
 
 
-def test_resumable_upload_recover(authorized_transport, cleanup):
+def test_resumable_upload_recover(authorized_transport, bucket, cleanup):
     _resumable_upload_recover_helper(authorized_transport, cleanup)
 
 
-def test_resumable_upload_recover_with_headers(authorized_transport, cleanup):
+def test_resumable_upload_recover_with_headers(
+        authorized_transport, bucket, cleanup):
     headers = utils.get_encryption_headers()
     _resumable_upload_recover_helper(
         authorized_transport, cleanup, headers=headers)
@@ -445,7 +438,8 @@
         self._check_range_sent(response, start_byte, end_byte, u'*')
         self._check_range_received(response, end_byte + 1)
 
-    def test_smaller_than_chunk_size(self, authorized_transport, cleanup):
+    def test_smaller_than_chunk_size(
+            self, authorized_transport, bucket, cleanup):
         blob_name = os.path.basename(ICO_FILE)
         chunk_size = resumable_media.UPLOAD_CHUNK_SIZE
         # Make sure to clean up the uploaded blob when we are done.
@@ -480,7 +474,7 @@
             # Make sure the upload is tombstoned.
             check_tombstoned(upload, authorized_transport)
 
-    def test_finish_at_chunk(self, authorized_transport, cleanup):
+    def test_finish_at_chunk(self, authorized_transport, bucket, cleanup):
         blob_name = u'some-clean-stuff.bin'
         chunk_size = resumable_media.UPLOAD_CHUNK_SIZE
         # Make sure to clean up the uploaded blob when we are done.
@@ -526,7 +520,7 @@
         # Go back to where we were before the write.
         stream.seek(curr_pos)
 
-    def test_interleave_writes(self, authorized_transport, cleanup):
+    def test_interleave_writes(self, authorized_transport, bucket, cleanup):
         blob_name = u'some-moar-stuff.bin'
         chunk_size = resumable_media.UPLOAD_CHUNK_SIZE
         # Make sure to clean up the uploaded blob when we are done.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/google-resumable-media-0.3.2/tests/system/utils.py 
new/google-resumable-media-0.4.0/tests/system/utils.py
--- old/google-resumable-media-0.3.2/tests/system/utils.py      2018-12-12 
22:17:35.000000000 +0100
+++ new/google-resumable-media-0.4.0/tests/system/utils.py      2019-09-06 
18:48:30.000000000 +0200
@@ -14,25 +14,24 @@
 
 import base64
 import hashlib
-import os
+import time
 
 
-BUCKET_NAME = os.environ[u'GOOGLE_RESUMABLE_MEDIA_BUCKET']
-DOWNLOAD_URL_TEMPLATE = (
-    u'https://www.googleapis.com/download/storage/v1/b/' +
-    BUCKET_NAME +
-    u'/o/{blob_name}?alt=media')
-_UPLOAD_BASE = (
-    u'https://www.googleapis.com/upload/storage/v1/b/' +
-    BUCKET_NAME +
-    u'/o?uploadType=')
+BUCKET_NAME = u'grpm-systest-{}'.format(int(1000 * time.time()))
+BUCKET_POST_URL = u'https://www.googleapis.com/storage/v1/b/'
+BUCKET_URL = u'https://www.googleapis.com/storage/v1/b/{}'.format(BUCKET_NAME)
+
+_DOWNLOAD_BASE = u'https://www.googleapis.com/download/storage/v1/b/{}'.format(
+    BUCKET_NAME)
+DOWNLOAD_URL_TEMPLATE = _DOWNLOAD_BASE + u'/o/{blob_name}?alt=media'
+
+_UPLOAD_BASE = u'https://www.googleapis.com/upload/storage/v1/b/{}'.format(
+    BUCKET_NAME) + u'/o?uploadType='
 SIMPLE_UPLOAD_TEMPLATE = _UPLOAD_BASE + u'media&name={blob_name}'
 MULTIPART_UPLOAD = _UPLOAD_BASE + u'multipart'
 RESUMABLE_UPLOAD = _UPLOAD_BASE + u'resumable'
-METADATA_URL_TEMPLATE = (
-    u'https://www.googleapis.com/storage/v1/b/' +
-    BUCKET_NAME +
-    u'/o/{blob_name}')
+
+METADATA_URL_TEMPLATE = BUCKET_URL + u'/o/{blob_name}'
 
 GCS_RW_SCOPE = u'https://www.googleapis.com/auth/devstorage.read_write'
 # Generated using random.choice() with all 256 byte choices.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/google-resumable-media-0.3.2/tests/unit/requests/test__helpers.py 
new/google-resumable-media-0.4.0/tests/unit/requests/test__helpers.py
--- old/google-resumable-media-0.3.2/tests/unit/requests/test__helpers.py       
2018-12-12 22:17:35.000000000 +0100
+++ new/google-resumable-media-0.4.0/tests/unit/requests/test__helpers.py       
2019-09-06 18:48:30.000000000 +0200
@@ -17,6 +17,8 @@
 
 from google.resumable_media.requests import _helpers
 
+EXPECTED_TIMEOUT = (61, 60)
+
 
 class TestRequestsMixin(object):
 
@@ -27,12 +29,21 @@
 
     def test__get_headers(self):
         headers = {u'fruit': u'apple'}
-        response = mock.Mock(headers=headers, spec=[u'headers'])
+        response = mock.Mock(headers=headers, spec=['headers'])
         assert headers == _helpers.RequestsMixin._get_headers(response)
 
-    def test__get_body(self):
+    def test__get_body_wo_content_consumed(self):
+        body = b'This is the payload.'
+        raw = mock.Mock(spec=['stream'])
+        raw.stream.return_value = iter([body])
+        response = mock.Mock(raw=raw, _content=False, spec=['raw', '_content'])
+        assert body == _helpers.RequestsMixin._get_body(response)
+        raw.stream.assert_called_once_with(
+            _helpers._SINGLE_GET_CHUNK_SIZE, decode_content=False)
+
+    def test__get_body_w_content_consumed(self):
         body = b'This is the payload.'
-        response = mock.Mock(content=body, spec=[u'content'])
+        response = mock.Mock(_content=body, spec=['_content'])
         assert body == _helpers.RequestsMixin._get_body(response)
 
 
@@ -42,14 +53,27 @@
     url = u'http://test.invalid'
     data = mock.sentinel.data
     headers = {u'one': u'fish', u'blue': u'fish'}
+    timeout = mock.sentinel.timeout
     ret_val = _helpers.http_request(
         transport, method, url, data=data, headers=headers,
-        extra1=b'work', extra2=125.5)
+        extra1=b'work', extra2=125.5, timeout=timeout)
 
     assert ret_val is responses[0]
     transport.request.assert_called_once_with(
         method, url, data=data, headers=headers,
-        extra1=b'work', extra2=125.5)
+        extra1=b'work', extra2=125.5, timeout=timeout)
+
+
+def test_http_request_defaults():
+    transport, responses = _make_transport(http_client.OK)
+    method = u'POST'
+    url = u'http://test.invalid'
+    ret_val = _helpers.http_request(transport, method, url)
+
+    assert ret_val is responses[0]
+    transport.request.assert_called_once_with(
+        method, url, data=None, headers=None,
+        timeout=EXPECTED_TIMEOUT)
 
 
 def _make_response(status_code):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/google-resumable-media-0.3.2/tests/unit/requests/test_download.py 
new/google-resumable-media-0.4.0/tests/unit/requests/test_download.py
--- old/google-resumable-media-0.3.2/tests/unit/requests/test_download.py       
2018-12-12 22:17:35.000000000 +0100
+++ new/google-resumable-media-0.4.0/tests/unit/requests/test_download.py       
2019-09-06 18:48:30.000000000 +0200
@@ -19,17 +19,19 @@
 from six.moves import http_client
 
 from google.resumable_media import common
-import google.resumable_media.requests.download as download_mod
+from google.resumable_media.requests import download as download_mod
+from google.resumable_media.requests import _helpers
 
 
 EXAMPLE_URL = (
     u'https://www.googleapis.com/download/storage/v1/b/'
     u'{BUCKET}/o/{OBJECT}?alt=media')
+EXPECTED_TIMEOUT = (61, 60)
 
 
 class TestDownload(object):
 
-    @mock.patch(u'google.resumable_media.requests.download._LOGGER')
+    @mock.patch('google.resumable_media.requests.download._LOGGER')
     def test__get_expected_md5_present(self, _LOGGER):
         download = download_mod.Download(EXAMPLE_URL)
 
@@ -42,7 +44,7 @@
         assert expected_md5_hash == checksum
         _LOGGER.info.assert_not_called()
 
-    @mock.patch(u'google.resumable_media.requests.download._LOGGER')
+    @mock.patch('google.resumable_media.requests.download._LOGGER')
     def test__get_expected_md5_missing(self, _LOGGER):
         download = download_mod.Download(EXAMPLE_URL)
 
@@ -70,9 +72,8 @@
         # Check mocks.
         response.__enter__.assert_called_once_with()
         response.__exit__.assert_called_once_with(None, None, None)
-        response.iter_content.assert_called_once_with(
-            chunk_size=download_mod._SINGLE_GET_CHUNK_SIZE,
-            decode_unicode=False)
+        response.raw.stream.assert_called_once_with(
+            _helpers._SINGLE_GET_CHUNK_SIZE, decode_content=False)
 
     def test__write_to_stream_with_hash_check_success(self):
         stream = io.BytesIO()
@@ -94,9 +95,8 @@
         # Check mocks.
         response.__enter__.assert_called_once_with()
         response.__exit__.assert_called_once_with(None, None, None)
-        response.iter_content.assert_called_once_with(
-            chunk_size=download_mod._SINGLE_GET_CHUNK_SIZE,
-            decode_unicode=False)
+        response.raw.stream.assert_called_once_with(
+            _helpers._SINGLE_GET_CHUNK_SIZE, decode_content=False)
 
     def test__write_to_stream_with_hash_check_fail(self):
         stream = io.BytesIO()
@@ -127,16 +127,15 @@
         # Check mocks.
         response.__enter__.assert_called_once_with()
         response.__exit__.assert_called_once_with(None, None, None)
-        response.iter_content.assert_called_once_with(
-            chunk_size=download_mod._SINGLE_GET_CHUNK_SIZE,
-            decode_unicode=False)
+        response.raw.stream.assert_called_once_with(
+            _helpers._SINGLE_GET_CHUNK_SIZE, decode_content=False)
 
     def _consume_helper(
             self, stream=None, end=65536, headers=None, chunks=(),
             response_headers=None):
         download = download_mod.Download(
             EXAMPLE_URL, stream=stream, end=end, headers=headers)
-        transport = mock.Mock(spec=[u'request'])
+        transport = mock.Mock(spec=['request'])
         transport.request.return_value = _mock_response(
             chunks=chunks, headers=response_headers)
 
@@ -144,12 +143,17 @@
         ret_val = download.consume(transport)
         assert ret_val is transport.request.return_value
 
-        called_kwargs = {u'data': None, u'headers': download._headers}
         if chunks:
             assert stream is not None
-            called_kwargs[u'stream'] = True
+
         transport.request.assert_called_once_with(
-            u'GET', EXAMPLE_URL, **called_kwargs)
+            u'GET',
+            EXAMPLE_URL,
+            data=None,
+            headers=download._headers,
+            stream=True,
+            timeout=EXPECTED_TIMEOUT,
+        )
 
         range_bytes = u'bytes={:d}-{:d}'.format(0, end)
         assert download._headers[u'range'] == range_bytes
@@ -171,9 +175,8 @@
         response = transport.request.return_value
         response.__enter__.assert_called_once_with()
         response.__exit__.assert_called_once_with(None, None, None)
-        response.iter_content.assert_called_once_with(
-            chunk_size=download_mod._SINGLE_GET_CHUNK_SIZE,
-            decode_unicode=False)
+        response.raw.stream.assert_called_once_with(
+            _helpers._SINGLE_GET_CHUNK_SIZE, decode_content=False)
 
     def test_consume_with_stream_hash_check_success(self):
         stream = io.BytesIO()
@@ -189,9 +192,8 @@
         response = transport.request.return_value
         response.__enter__.assert_called_once_with()
         response.__exit__.assert_called_once_with(None, None, None)
-        response.iter_content.assert_called_once_with(
-            chunk_size=download_mod._SINGLE_GET_CHUNK_SIZE,
-            decode_unicode=False)
+        response.raw.stream.assert_called_once_with(
+            _helpers._SINGLE_GET_CHUNK_SIZE, decode_content=False)
 
     def test_consume_with_stream_hash_check_fail(self):
         stream = io.BytesIO()
@@ -202,7 +204,7 @@
         bad_checksum = u'anVzdCBub3QgdGhpcyAxLA=='
         header_value = u'crc32c=V0FUPw==,md5={}'.format(bad_checksum)
         headers = {download_mod._HASH_HEADER: header_value}
-        transport = mock.Mock(spec=[u'request'])
+        transport = mock.Mock(spec=['request'])
         transport.request.return_value = _mock_response(
             chunks=chunks, headers=headers)
 
@@ -224,7 +226,8 @@
 
         # Check mocks.
         transport.request.assert_called_once_with(
-            u'GET', EXAMPLE_URL, data=None, headers={}, stream=True)
+            u'GET', EXAMPLE_URL, data=None, headers={}, stream=True,
+            timeout=EXPECTED_TIMEOUT)
 
     def test_consume_with_headers(self):
         headers = {}  # Empty headers
@@ -256,11 +259,11 @@
         response_headers = self._response_headers(
             start_byte, end_byte, total_bytes)
         return mock.Mock(
-            content=content,
+            _content=content,
             headers=response_headers,
             status_code=status_code,
             spec=[
-                u'content',
+                u'_content',
                 u'headers',
                 u'status_code',
             ],
@@ -273,7 +276,7 @@
             download.consume_next_chunk(None)
 
     def _mock_transport(self, start, chunk_size, total_bytes, content=b''):
-        transport = mock.Mock(spec=[u'request'])
+        transport = mock.Mock(spec=['request'])
         assert len(content) == chunk_size
         transport.request.return_value = self._mock_response(
             start, start + chunk_size - 1, total_bytes,
@@ -302,7 +305,13 @@
         range_bytes = u'bytes={:d}-{:d}'.format(start, start + chunk_size - 1)
         download_headers = {u'range': range_bytes}
         transport.request.assert_called_once_with(
-            u'GET', EXAMPLE_URL, data=None, headers=download_headers)
+            u'GET',
+            EXAMPLE_URL,
+            data=None,
+            headers=download_headers,
+            stream=True,
+            timeout=EXPECTED_TIMEOUT,
+        )
         assert stream.getvalue() == data
         # Go back and check the internal state after consuming the chunk.
         assert not download.finished
@@ -362,75 +371,36 @@
     assert return_value is None
 
 
-class Test__add_decoder(object):
-
-    def test_non_gzipped(self):
-        response_raw = mock.Mock(headers={}, spec=[u'headers'])
-        md5_hash = download_mod._add_decoder(
-            response_raw, mock.sentinel.md5_hash)
-
-        assert md5_hash is mock.sentinel.md5_hash
-
-    def test_gzipped(self):
-        headers = {u'content-encoding': u'gzip'}
-        response_raw = mock.Mock(
-            headers=headers, spec=[u'headers', u'_decoder'])
-        md5_hash = download_mod._add_decoder(
-            response_raw, mock.sentinel.md5_hash)
-
-        assert md5_hash is not mock.sentinel.md5_hash
-        assert isinstance(md5_hash, download_mod._DoNothingHash)
-        assert isinstance(response_raw._decoder, download_mod._GzipDecoder)
-        assert response_raw._decoder._md5_hash is mock.sentinel.md5_hash
-
-
-class Test_GzipDecoder(object):
-
-    def test_constructor(self):
-        decoder = download_mod._GzipDecoder(mock.sentinel.md5_hash)
-        assert decoder._md5_hash is mock.sentinel.md5_hash
-
-    def test_decompress(self):
-        md5_hash = mock.Mock(spec=['update'])
-        decoder = download_mod._GzipDecoder(md5_hash)
-
-        data = b'\x1f\x8b\x08\x08'
-        result = decoder.decompress(data)
-
-        assert result == b''
-        md5_hash.update.assert_called_once_with(data)
-
-
 def _mock_response(status_code=http_client.OK, chunks=(), headers=None):
     if headers is None:
         headers = {}
 
     if chunks:
-        mock_raw = mock.Mock(headers=headers, spec=[u'headers'])
+        mock_raw = mock.Mock(headers=headers, spec=['headers', 'stream'])
+        mock_raw.stream.return_value = iter(chunks)
         response = mock.MagicMock(
             headers=headers,
             status_code=int(status_code),
             raw=mock_raw,
             spec=[
-                u'__enter__',
-                u'__exit__',
-                u'iter_content',
-                u'status_code',
-                u'headers',
-                u'raw',
+                '__enter__',
+                '__exit__',
+                'raw',
+                'status_code',
+                'headers',
+                'raw',
             ],
         )
         # i.e. context manager returns ``self``.
         response.__enter__.return_value = response
         response.__exit__.return_value = None
-        response.iter_content.return_value = iter(chunks)
         return response
     else:
         return mock.Mock(
             headers=headers,
             status_code=int(status_code),
             spec=[
-                u'status_code',
-                u'headers',
+                'status_code',
+                'headers',
             ],
         )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/google-resumable-media-0.3.2/tests/unit/requests/test_upload.py 
new/google-resumable-media-0.4.0/tests/unit/requests/test_upload.py
--- old/google-resumable-media-0.3.2/tests/unit/requests/test_upload.py 
2018-12-12 22:17:35.000000000 +0100
+++ new/google-resumable-media-0.4.0/tests/unit/requests/test_upload.py 
2019-09-06 18:48:30.000000000 +0200
@@ -35,6 +35,7 @@
 BASIC_CONTENT = u'text/plain'
 JSON_TYPE = u'application/json; charset=UTF-8'
 JSON_TYPE_LINE = b'content-type: application/json; charset=UTF-8\r\n'
+EXPECTED_TIMEOUT = (61, 60)
 
 
 class TestSimpleUpload(object):
@@ -51,7 +52,8 @@
         assert ret_val is transport.request.return_value
         upload_headers = {u'content-type': content_type}
         transport.request.assert_called_once_with(
-            u'POST', SIMPLE_URL, data=data, headers=upload_headers)
+            u'POST', SIMPLE_URL, data=data, headers=upload_headers,
+            timeout=EXPECTED_TIMEOUT)
         assert upload.finished
 
 
@@ -84,7 +86,7 @@
         upload_headers = {u'content-type': multipart_type}
         transport.request.assert_called_once_with(
             u'POST', MULTIPART_URL, data=expected_payload,
-            headers=upload_headers)
+            headers=upload_headers, timeout=EXPECTED_TIMEOUT)
         assert upload.finished
         mock_get_boundary.assert_called_once_with()
 
@@ -121,7 +123,8 @@
             u'x-upload-content-length': u'{:d}'.format(total_bytes),
         }
         transport.request.assert_called_once_with(
-            u'POST', RESUMABLE_URL, data=json_bytes, headers=expected_headers)
+            u'POST', RESUMABLE_URL, data=json_bytes, headers=expected_headers,
+            timeout=EXPECTED_TIMEOUT)
 
     @staticmethod
     def _upload_in_flight(data, headers=None):
@@ -170,7 +173,7 @@
         }
         transport.request.assert_called_once_with(
             u'PUT', upload.resumable_url, data=payload,
-            headers=expected_headers)
+            headers=expected_headers, timeout=EXPECTED_TIMEOUT)
 
     def test_recover(self):
         upload = upload_mod.ResumableUpload(RESUMABLE_URL, ONE_MB)
@@ -191,7 +194,8 @@
         upload._stream.seek.assert_called_once_with(end + 1)
         expected_headers = {u'content-range': u'bytes */*'}
         transport.request.assert_called_once_with(
-            u'PUT', upload.resumable_url, data=None, headers=expected_headers)
+            u'PUT', upload.resumable_url, data=None, headers=expected_headers,
+            timeout=EXPECTED_TIMEOUT)
 
 
 def _make_response(status_code=http_client.OK, headers=None):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/google-resumable-media-0.3.2/tests/unit/test__download.py 
new/google-resumable-media-0.4.0/tests/unit/test__download.py
--- old/google-resumable-media-0.3.2/tests/unit/test__download.py       
2018-12-12 22:17:35.000000000 +0100
+++ new/google-resumable-media-0.4.0/tests/unit/test__download.py       
2019-09-06 18:48:30.000000000 +0200
@@ -127,7 +127,7 @@
         # Make sure **not finished** before.
         assert not download.finished
         response = mock.Mock(
-            status_code=int(http_client.OK), spec=[u'status_code'])
+            status_code=int(http_client.OK), spec=['status_code'])
         ret_val = download._process_response(response)
         assert ret_val is None
         # Make sure **finished** after.
@@ -140,7 +140,7 @@
         # Make sure **not finished** before.
         assert not download.finished
         response = mock.Mock(
-            status_code=int(http_client.NOT_FOUND), spec=[u'status_code'])
+            status_code=int(http_client.NOT_FOUND), spec=['status_code'])
         with pytest.raises(common.InvalidResponse) as exc_info:
             download._process_response(response)
 
@@ -262,7 +262,7 @@
             start_byte, end_byte, total_bytes)
         return mock.Mock(
             content=content, headers=response_headers, status_code=status_code,
-            spec=[u'content', u'headers', u'status_code'])
+            spec=['content', 'headers', 'status_code'])
 
     def test__prepare_request_already_finished(self):
         download = _download.ChunkedDownload(EXAMPLE_URL, 64, None)
@@ -318,7 +318,8 @@
         assert download.invalid
 
     def test__process_response(self):
-        chunk_size = 333
+        data = b'1234xyztL' * 37
+        chunk_size = len(data)
         stream = io.BytesIO()
         download = _download.ChunkedDownload(
             EXAMPLE_URL, chunk_size, stream)
@@ -333,7 +334,6 @@
         assert download.bytes_downloaded == already
         assert download.total_bytes is None
         # Actually call the method to update.
-        data = b'1234xyztL' * 37  # 9 * 37 == 33
         response = self._mock_response(
             already, already + chunk_size - 1, total_bytes, content=data,
             status_code=int(http_client.PARTIAL_CONTENT))
@@ -344,9 +344,39 @@
         assert download.total_bytes == total_bytes
         assert stream.getvalue() == data
 
+    def test__process_response_transfer_encoding(self):
+        data = b'1234xyztL' * 37
+        chunk_size = len(data)
+        stream = io.BytesIO()
+        download = _download.ChunkedDownload(
+            EXAMPLE_URL, chunk_size, stream)
+        _fix_up_virtual(download)
+
+        already = 22
+        download._bytes_downloaded = already
+        total_bytes = 4444
+
+        # Check internal state before.
+        assert not download.finished
+        assert download.bytes_downloaded == already
+        assert download.total_bytes is None
+        assert not download.invalid
+        # Actually call the method to update.
+        response = self._mock_response(
+            already, already + chunk_size - 1, total_bytes, content=data,
+            status_code=int(http_client.PARTIAL_CONTENT))
+        response.headers[u'transfer-encoding'] = 'chunked'
+        del response.headers[u'content-length']
+        download._process_response(response)
+        # Check internal state after.
+        assert not download.finished
+        assert download.bytes_downloaded == already + chunk_size
+        assert download.total_bytes == total_bytes
+        assert stream.getvalue() == data
+
     def test__process_response_bad_status(self):
         chunk_size = 384
-        stream = mock.Mock(spec=[u'write'])
+        stream = mock.Mock(spec=['write'])
         download = _download.ChunkedDownload(
             EXAMPLE_URL, chunk_size, stream)
         _fix_up_virtual(download)
@@ -387,9 +417,15 @@
         assert download.total_bytes is None
         assert not download.invalid
         # Actually call the method to update.
+        headers = {
+            u'content-range': u'bytes 0-99/99',
+        }
         response = mock.Mock(
-            headers={}, status_code=int(http_client.PARTIAL_CONTENT),
-            spec=[u'headers', u'status_code'])
+            headers=headers,
+            status_code=int(http_client.PARTIAL_CONTENT),
+            content=b'DEADBEEF',
+            spec=['headers', 'status_code', 'content'],
+        )
         with pytest.raises(common.InvalidResponse) as exc_info:
             download._process_response(response)
 
@@ -421,7 +457,7 @@
         response = mock.Mock(
             content=data, headers=headers,
             status_code=int(http_client.PARTIAL_CONTENT),
-            spec=[u'content', u'headers', u'status_code'])
+            spec=['content', 'headers', 'status_code'])
         with pytest.raises(common.InvalidResponse) as exc_info:
             download._process_response(response)
 
@@ -437,7 +473,7 @@
 
     def test__process_response_body_wrong_length(self):
         chunk_size = 10
-        stream = mock.Mock(spec=[u'write'])
+        stream = mock.Mock(spec=['write'])
         download = _download.ChunkedDownload(
             EXAMPLE_URL, chunk_size, stream)
         _fix_up_virtual(download)
@@ -523,6 +559,25 @@
         assert download.total_bytes == 8 * chunk_size
         assert stream.getvalue() == data
 
+    def test__process_response_when_content_range_is_zero(self):
+        chunk_size = 10
+        stream = mock.Mock(spec=[u'write'])
+        download = _download.ChunkedDownload(
+            EXAMPLE_URL, chunk_size, stream)
+        _fix_up_virtual(download)
+
+        content_range = _download._ZERO_CONTENT_RANGE_HEADER
+        headers = {u'content-range': content_range}
+        status_code = http_client.REQUESTED_RANGE_NOT_SATISFIABLE
+        response = mock.Mock(headers=headers,
+                             status_code=status_code,
+                             spec=[u'headers', 'status_code'])
+        download._process_response(response)
+        stream.write.assert_not_called()
+        assert download.finished
+        assert download.bytes_downloaded == 0
+        assert download.total_bytes is None
+
     def test_consume_next_chunk(self):
         download = _download.ChunkedDownload(EXAMPLE_URL, 256, None)
         with pytest.raises(NotImplementedError) as exc_info:
@@ -569,7 +624,7 @@
     @staticmethod
     def _make_response(content_range):
         headers = {u'content-range': content_range}
-        return mock.Mock(headers=headers, spec=[u'headers'])
+        return mock.Mock(headers=headers, spec=['headers'])
 
     def _success_helper(self, **kwargs):
         content_range = u'Bytes 7-11/42'
@@ -608,7 +663,7 @@
         callback.assert_called_once_with()
 
     def _missing_header_helper(self, **kwargs):
-        response = mock.Mock(headers={}, spec=[u'headers'])
+        response = mock.Mock(headers={}, spec=['headers'])
         with pytest.raises(common.InvalidResponse) as exc_info:
             _download.get_range_info(response, _get_headers, **kwargs)
 
@@ -626,6 +681,37 @@
         callback.assert_called_once_with()
 
 
+class Test__check_for_zero_content_range(object):
+
+    @staticmethod
+    def _make_response(content_range, status_code):
+        headers = {u'content-range': content_range}
+        return mock.Mock(headers=headers,
+                         status_code=status_code,
+                         spec=[u'headers', 'status_code'])
+
+    def test_status_code_416_and_test_content_range_zero_both(self):
+        content_range = _download._ZERO_CONTENT_RANGE_HEADER
+        status_code = http_client.REQUESTED_RANGE_NOT_SATISFIABLE
+        response = self._make_response(content_range, status_code)
+        assert _download._check_for_zero_content_range(
+            response, _get_status_code, _get_headers)
+
+    def test_status_code_416_only(self):
+        content_range = u'bytes 2-5/3'
+        status_code = http_client.REQUESTED_RANGE_NOT_SATISFIABLE
+        response = self._make_response(content_range, status_code)
+        assert not _download._check_for_zero_content_range(
+            response, _get_status_code, _get_headers)
+
+    def test_content_range_zero_only(self):
+        content_range = _download._ZERO_CONTENT_RANGE_HEADER
+        status_code = http_client.OK
+        response = self._make_response(content_range, status_code)
+        assert not _download._check_for_zero_content_range(
+            response, _get_status_code, _get_headers)
+
+
 def _get_status_code(response):
     return response.status_code
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/google-resumable-media-0.3.2/tests/unit/test__upload.py 
new/google-resumable-media-0.4.0/tests/unit/test__upload.py
--- old/google-resumable-media-0.3.2/tests/unit/test__upload.py 2018-12-12 
22:17:35.000000000 +0100
+++ new/google-resumable-media-0.4.0/tests/unit/test__upload.py 2019-09-06 
18:48:30.000000000 +0200
@@ -423,25 +423,26 @@
         with pytest.raises(AttributeError):
             upload._prepare_initiate_request(None, {}, BASIC_CONTENT)
 
-    def test__process_initiate_response_bad_response(self):
+    def test__process_initiate_response_non_200(self):
         upload = _upload.ResumableUpload(RESUMABLE_URL, ONE_MB)
         _fix_up_virtual(upload)
 
-        response = _make_response()
+        response = _make_response(403)
         with pytest.raises(common.InvalidResponse) as exc_info:
             upload._process_initiate_response(response)
 
         error = exc_info.value
         assert error.response is response
-        assert len(error.args) == 2
-        assert error.args[1] == u'location'
+        assert len(error.args) == 4
+        assert error.args[1] == 403
+        assert error.args[3] == 200
 
     def test__process_initiate_response(self):
         upload = _upload.ResumableUpload(RESUMABLE_URL, ONE_MB)
         _fix_up_virtual(upload)
 
         headers = {u'location': u'http://test.invalid?upload_id=kmfeij3234'}
-        response = mock.Mock(headers=headers, spec=[u'headers'])
+        response = _make_response(headers=headers)
         # Check resumable_url before.
         assert upload._resumable_url is None
         # Process the actual headers.


Reply via email to