def test_failure(self): status_codes = (http_client.CREATED, http_client.NO_CONTENT) response = _make_response(http_client.OK) with pytest.raises(common.InvalidResponse) as exc_info: _helpers.require_status_code(response, status_codes, self._get_status_code) error = exc_info.value assert error.response is response assert len(error.args) == 5 assert error.args[1] == response.status_code assert error.args[3:] == status_codes
def test_failure_with_callback(self): status_codes = (http_client.OK,) response = _make_response(http_client.NOT_FOUND) callback = mock.Mock(spec=[]) with pytest.raises(common.InvalidResponse) as exc_info: _helpers.require_status_code( response, status_codes, self._get_status_code, callback=callback ) error = exc_info.value assert error.response is response assert len(error.args) == 4 assert error.args[1] == response.status_code assert error.args[3:] == status_codes callback.assert_called_once_with()
def _process_response(self, response): """Process the response from an HTTP request. This is everything that must be done after a request that doesn't require network I/O (or other I/O). This is based on the `sans-I/O`_ philosophy. Args: response (object): The HTTP response object. .. _sans-I/O: https://sans-io.readthedocs.io/ """ # Tombstone the current Download so it cannot be used again. self._finished = True _helpers.require_status_code(response, _ACCEPTABLE_STATUS_CODES, self._get_status_code)
async def _process_response(self, response, bytes_sent): """Process the response from an HTTP request. This is everything that must be done after a request that doesn't require network I/O (or other I/O). This is based on the `sans-I/O`_ philosophy. Args: response (object): The HTTP response object. bytes_sent (int): The number of bytes sent in the request that ``response`` was returned for. Raises: ~google.resumable_media.common.InvalidResponse: If the status code is 308 and the ``range`` header is not of the form ``bytes 0-{end}``. ~google.resumable_media.common.InvalidResponse: If the status code is not 200 or 308. .. _sans-I/O: https://sans-io.readthedocs.io/ """ status_code = _helpers.require_status_code( response, (http.client.OK, http.client.PERMANENT_REDIRECT), self._get_status_code, callback=self._make_invalid, ) if status_code == http.client.OK: # NOTE: We use the "local" information of ``bytes_sent`` to update # ``bytes_uploaded``, but do not verify this against other # state. However, there may be some other information: # # * a ``size`` key in JSON response body # * the ``total_bytes`` attribute (if set) # * ``stream.tell()`` (relying on fact that ``initiate()`` # requires stream to be at the beginning) self._bytes_uploaded = self._bytes_uploaded + bytes_sent # Tombstone the current upload so it cannot be used again. self._finished = True # Validate the checksum. This can raise an exception on failure. await self._validate_checksum(response) else: bytes_range = _helpers.header_required( response, _helpers.RANGE_HEADER, self._get_headers, callback=self._make_invalid, ) match = _BYTES_RANGE_RE.match(bytes_range) if match is None: self._make_invalid() raise common.InvalidResponse( response, 'Unexpected "range" header', bytes_range, 'Expected to be of the form "bytes=0-{end}"', ) self._bytes_uploaded = int(match.group("end_byte")) + 1
def test_success_with_callback(self): status_codes = (http_client.OK,) response = _make_response(http_client.OK) callback = mock.Mock(spec=[]) status_code = _helpers.require_status_code( response, status_codes, self._get_status_code, callback=callback ) assert status_code == http_client.OK callback.assert_not_called()
def _process_recover_response(self, response): """Process the response from an HTTP request to recover from failure. This is everything that must be done after a request that doesn't require network I/O (or other I/O). This is based on the `sans-I/O`_ philosophy. Args: response (object): The HTTP response object. Raises: ~google.resumable_media.common.InvalidResponse: If the status code is not 308. ~google.resumable_media.common.InvalidResponse: If the status code is 308 and the ``range`` header is not of the form ``bytes 0-{end}``. .. _sans-I/O: https://sans-io.readthedocs.io/ """ _helpers.require_status_code( response, (http.client.PERMANENT_REDIRECT, ), self._get_status_code, ) headers = self._get_headers(response) if _helpers.RANGE_HEADER in headers: bytes_range = headers[_helpers.RANGE_HEADER] match = _BYTES_RANGE_RE.match(bytes_range) if match is None: raise common.InvalidResponse( response, 'Unexpected "range" header', bytes_range, 'Expected to be of the form "bytes=0-{end}"', ) self._bytes_uploaded = int(match.group("end_byte")) + 1 else: # In this case, the upload has not "begun". self._bytes_uploaded = 0 self._stream.seek(self._bytes_uploaded) self._invalid = False
def _process_response(self, response): """Process the response from an HTTP request. This is everything that must be done after a request that doesn't require network I/O (or other I/O). This is based on the `sans-I/O`_ philosophy. Args: response (object): The HTTP response object. Raises: ~google.resumable_media.common.InvalidResponse: If the status code is not 200. .. _sans-I/O: https://sans-io.readthedocs.io/ """ # Tombstone the current upload so it cannot be used again (in either # failure or success). self._finished = True _helpers.require_status_code(response, (http.client.OK, ), self._get_status_code)
def test_success(self): status_codes = (http_client.OK, http_client.CREATED) acceptable = ( http_client.OK, int(http_client.OK), http_client.CREATED, int(http_client.CREATED), ) for value in acceptable: response = _make_response(value) status_code = _helpers.require_status_code(response, status_codes, self._get_status_code) assert value == status_code
def _process_initiate_response(self, response): """Process the response from an HTTP request that initiated upload. This is everything that must be done after a request that doesn't require network I/O (or other I/O). This is based on the `sans-I/O`_ philosophy. This method takes the URL from the ``Location`` header and stores it for future use. Within that URL, we assume the ``upload_id`` query parameter has been included, but we do not check. Args: response (object): The HTTP response object (need headers). .. _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, "location", self._get_headers)
async def _process_response(self, response): """Process the response from an HTTP request. This is everything that must be done after a request that doesn't require network I/O. This is based on the `sans-I/O`_ philosophy. For the time being, this **does require** some form of I/O to write a chunk to ``stream``. However, this will (almost) certainly not be network I/O. Updates the current state after consuming a chunk. First, increments ``bytes_downloaded`` by the number of bytes in the ``content-length`` header. If ``total_bytes`` is already set, this assumes (but does not check) that we already have the correct value and doesn't bother to check that it agrees with the headers. We expect the **total** length to be in the ``content-range`` header, but this header is only present on requests which sent the ``range`` header. This response header should be of the form ``bytes {start}-{end}/{total}`` and ``{end} - {start} + 1`` should be the same as the ``Content-Length``. Args: response (object): The HTTP response object (need headers). Raises: ~google.resumable_media.common.InvalidResponse: If the number of bytes in the body doesn't match the content length header. .. _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, ) headers = self._get_headers(response) response_body = await self._get_body(response) 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 # If the end byte is past ``end`` or ``total_bytes - 1`` we are done. if self.end is not None and end_byte >= self.end: self._finished = True elif end_byte >= total_bytes - 1: self._finished = True # NOTE: We only use ``total_bytes`` if not already known. if self.total_bytes is None: self._total_bytes = total_bytes # Write the response body to the stream. self._stream.write(response_body)