14a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair# Copyright 2014 Google Inc. All Rights Reserved.
24a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair#
34a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair# Licensed under the Apache License, Version 2.0 (the "License");
44a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair# you may not use this file except in compliance with the License.
54a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair# You may obtain a copy of the License at
64a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair#
74a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair#      http://www.apache.org/licenses/LICENSE-2.0
84a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair#
94a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair# Unless required by applicable law or agreed to in writing, software
104a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair# distributed under the License is distributed on an "AS IS" BASIS,
114a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
124a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair# See the License for the specific language governing permissions and
134a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair# limitations under the License.
144a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
154a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair"""Classes to encapsulate a single HTTP request.
164a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
174a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan SinclairThe classes implement a command pattern, with every
184a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairobject supporting an execute() method that does the
194a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairactuall HTTP request.
204a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair"""
214a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairfrom __future__ import absolute_import
224a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairimport six
234a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairfrom six.moves import range
244a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
254a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair__author__ = 'jcgregorio@google.com (Joe Gregorio)'
264a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
274a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairfrom six import BytesIO, StringIO
284a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairfrom six.moves.urllib.parse import urlparse, urlunparse, quote, unquote
294a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
304a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairimport base64
314a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairimport copy
324a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairimport gzip
334a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairimport httplib2
344a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairimport json
354a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairimport logging
364a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairimport mimetypes
374a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairimport os
384a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairimport random
394a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairimport sys
404a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairimport time
414a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairimport uuid
424a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
434a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairfrom email.generator import Generator
444a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairfrom email.mime.multipart import MIMEMultipart
454a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairfrom email.mime.nonmultipart import MIMENonMultipart
464a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairfrom email.parser import FeedParser
474a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
484a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairfrom googleapiclient import mimeparse
494a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairfrom googleapiclient.errors import BatchError
504a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairfrom googleapiclient.errors import HttpError
514a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairfrom googleapiclient.errors import InvalidChunkSizeError
524a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairfrom googleapiclient.errors import ResumableUploadError
534a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairfrom googleapiclient.errors import UnexpectedBodyError
544a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairfrom googleapiclient.errors import UnexpectedMethodError
554a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairfrom googleapiclient.model import JsonModel
564a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairfrom oauth2client import util
574a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
584a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
594a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan SinclairDEFAULT_CHUNK_SIZE = 512*1024
604a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
614a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan SinclairMAX_URI_LENGTH = 2048
624a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
634a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
644a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairclass MediaUploadProgress(object):
654a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  """Status of a resumable upload."""
664a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
674a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def __init__(self, resumable_progress, total_size):
684a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Constructor.
694a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
704a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Args:
714a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      resumable_progress: int, bytes sent so far.
724a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      total_size: int, total bytes in complete upload, or None if the total
734a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        upload size isn't known ahead of time.
744a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
754a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self.resumable_progress = resumable_progress
764a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self.total_size = total_size
774a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
784a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def progress(self):
794a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Percent of upload completed, as a float.
804a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
814a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Returns:
824a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      the percentage complete as a float, returning 0.0 if the total size of
834a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      the upload is unknown.
844a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
854a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if self.total_size is not None:
864a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      return float(self.resumable_progress) / float(self.total_size)
874a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    else:
884a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      return 0.0
894a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
904a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
914a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairclass MediaDownloadProgress(object):
924a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  """Status of a resumable download."""
934a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
944a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def __init__(self, resumable_progress, total_size):
954a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Constructor.
964a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
974a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Args:
984a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      resumable_progress: int, bytes received so far.
994a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      total_size: int, total bytes in complete download.
1004a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
1014a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self.resumable_progress = resumable_progress
1024a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self.total_size = total_size
1034a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1044a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def progress(self):
1054a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Percent of download completed, as a float.
1064a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1074a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Returns:
1084a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      the percentage complete as a float, returning 0.0 if the total size of
1094a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      the download is unknown.
1104a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
1114a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if self.total_size is not None:
1124a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      return float(self.resumable_progress) / float(self.total_size)
1134a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    else:
1144a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      return 0.0
1154a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1164a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1174a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairclass MediaUpload(object):
1184a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  """Describes a media object to upload.
1194a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1204a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  Base class that defines the interface of MediaUpload subclasses.
1214a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1224a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  Note that subclasses of MediaUpload may allow you to control the chunksize
1234a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  when uploading a media object. It is important to keep the size of the chunk
1244a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  as large as possible to keep the upload efficient. Other factors may influence
1254a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  the size of the chunk you use, particularly if you are working in an
1264a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  environment where individual HTTP requests may have a hardcoded time limit,
1274a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  such as under certain classes of requests under Google App Engine.
1284a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1294a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  Streams are io.Base compatible objects that support seek(). Some MediaUpload
1304a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  subclasses support using streams directly to upload data. Support for
1314a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  streaming may be indicated by a MediaUpload sub-class and if appropriate for a
1324a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  platform that stream will be used for uploading the media object. The support
1334a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  for streaming is indicated by has_stream() returning True. The stream() method
1344a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  should return an io.Base object that supports seek(). On platforms where the
1354a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  underlying httplib module supports streaming, for example Python 2.6 and
1364a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  later, the stream will be passed into the http library which will result in
1374a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  less memory being used and possibly faster uploads.
1384a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1394a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  If you need to upload media that can't be uploaded using any of the existing
1404a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  MediaUpload sub-class then you can sub-class MediaUpload for your particular
1414a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  needs.
1424a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  """
1434a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1444a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def chunksize(self):
1454a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Chunk size for resumable uploads.
1464a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1474a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Returns:
1484a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      Chunk size in bytes.
1494a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
1504a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    raise NotImplementedError()
1514a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1524a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def mimetype(self):
1534a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Mime type of the body.
1544a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1554a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Returns:
1564a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      Mime type.
1574a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
1584a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    return 'application/octet-stream'
1594a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1604a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def size(self):
1614a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Size of upload.
1624a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1634a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Returns:
1644a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      Size of the body, or None of the size is unknown.
1654a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
1664a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    return None
1674a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1684a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def resumable(self):
1694a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Whether this upload is resumable.
1704a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1714a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Returns:
1724a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      True if resumable upload or False.
1734a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
1744a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    return False
1754a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1764a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def getbytes(self, begin, end):
1774a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Get bytes from the media.
1784a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1794a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Args:
1804a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      begin: int, offset from beginning of file.
1814a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      length: int, number of bytes to read, starting at begin.
1824a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1834a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Returns:
1844a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      A string of bytes read. May be shorter than length if EOF was reached
1854a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      first.
1864a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
1874a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    raise NotImplementedError()
1884a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1894a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def has_stream(self):
1904a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Does the underlying upload support a streaming interface.
1914a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1924a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Streaming means it is an io.IOBase subclass that supports seek, i.e.
1934a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    seekable() returns True.
1944a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1954a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Returns:
1964a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      True if the call to stream() will return an instance of a seekable io.Base
1974a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      subclass.
1984a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
1994a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    return False
2004a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
2014a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def stream(self):
2024a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """A stream interface to the data being uploaded.
2034a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
2044a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Returns:
2054a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      The returned value is an io.IOBase subclass that supports seek, i.e.
2064a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      seekable() returns True.
2074a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
2084a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    raise NotImplementedError()
2094a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
2104a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  @util.positional(1)
2114a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def _to_json(self, strip=None):
2124a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Utility function for creating a JSON representation of a MediaUpload.
2134a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
2144a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Args:
2154a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      strip: array, An array of names of members to not include in the JSON.
2164a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
2174a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Returns:
2184a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair       string, a JSON representation of this instance, suitable to pass to
2194a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair       from_json().
2204a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
2214a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    t = type(self)
2224a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    d = copy.copy(self.__dict__)
2234a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if strip is not None:
2244a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      for member in strip:
2254a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        del d[member]
2264a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    d['_class'] = t.__name__
2274a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    d['_module'] = t.__module__
2284a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    return json.dumps(d)
2294a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
2304a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def to_json(self):
2314a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Create a JSON representation of an instance of MediaUpload.
2324a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
2334a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Returns:
2344a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair       string, a JSON representation of this instance, suitable to pass to
2354a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair       from_json().
2364a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
2374a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    return self._to_json()
2384a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
2394a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  @classmethod
2404a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def new_from_json(cls, s):
2414a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Utility class method to instantiate a MediaUpload subclass from a JSON
2424a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    representation produced by to_json().
2434a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
2444a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Args:
2454a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      s: string, JSON from to_json().
2464a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
2474a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Returns:
2484a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      An instance of the subclass of MediaUpload that was serialized with
2494a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      to_json().
2504a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
2514a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    data = json.loads(s)
2524a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # Find and call the right classmethod from_json() to restore the object.
2534a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    module = data['_module']
2544a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    m = __import__(module, fromlist=module.split('.')[:-1])
2554a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    kls = getattr(m, data['_class'])
2564a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    from_json = getattr(kls, 'from_json')
2574a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    return from_json(s)
2584a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
2594a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
2604a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairclass MediaIoBaseUpload(MediaUpload):
2614a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  """A MediaUpload for a io.Base objects.
2624a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
2634a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  Note that the Python file object is compatible with io.Base and can be used
2644a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  with this class also.
2654a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
2664a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    fh = BytesIO('...Some data to upload...')
2674a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    media = MediaIoBaseUpload(fh, mimetype='image/png',
2684a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      chunksize=1024*1024, resumable=True)
2694a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    farm.animals().insert(
2704a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        id='cow',
2714a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        name='cow.png',
2724a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        media_body=media).execute()
2734a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
2744a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  Depending on the platform you are working on, you may pass -1 as the
2754a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  chunksize, which indicates that the entire file should be uploaded in a single
2764a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  request. If the underlying platform supports streams, such as Python 2.6 or
2774a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  later, then this can be very efficient as it avoids multiple connections, and
2784a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  also avoids loading the entire file into memory before sending it. Note that
2794a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  Google App Engine has a 5MB limit on request size, so you should never set
2804a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  your chunksize larger than 5MB, or to -1.
2814a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  """
2824a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
2834a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  @util.positional(3)
2844a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def __init__(self, fd, mimetype, chunksize=DEFAULT_CHUNK_SIZE,
2854a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      resumable=False):
2864a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Constructor.
2874a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
2884a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Args:
2894a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      fd: io.Base or file object, The source of the bytes to upload. MUST be
2904a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        opened in blocking mode, do not use streams opened in non-blocking mode.
2914a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        The given stream must be seekable, that is, it must be able to call
2924a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        seek() on fd.
2934a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      mimetype: string, Mime-type of the file.
2944a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      chunksize: int, File will be uploaded in chunks of this many bytes. Only
2954a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        used if resumable=True. Pass in a value of -1 if the file is to be
2964a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        uploaded as a single chunk. Note that Google App Engine has a 5MB limit
2974a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        on request size, so you should never set your chunksize larger than 5MB,
2984a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        or to -1.
2994a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      resumable: bool, True if this is a resumable upload. False means upload
3004a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        in a single request.
3014a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
3024a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    super(MediaIoBaseUpload, self).__init__()
3034a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self._fd = fd
3044a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self._mimetype = mimetype
3054a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if not (chunksize == -1 or chunksize > 0):
3064a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      raise InvalidChunkSizeError()
3074a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self._chunksize = chunksize
3084a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self._resumable = resumable
3094a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
3104a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self._fd.seek(0, os.SEEK_END)
3114a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self._size = self._fd.tell()
3124a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
3134a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def chunksize(self):
3144a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Chunk size for resumable uploads.
3154a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
3164a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Returns:
3174a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      Chunk size in bytes.
3184a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
3194a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    return self._chunksize
3204a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
3214a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def mimetype(self):
3224a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Mime type of the body.
3234a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
3244a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Returns:
3254a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      Mime type.
3264a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
3274a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    return self._mimetype
3284a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
3294a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def size(self):
3304a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Size of upload.
3314a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
3324a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Returns:
3334a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      Size of the body, or None of the size is unknown.
3344a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
3354a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    return self._size
3364a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
3374a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def resumable(self):
3384a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Whether this upload is resumable.
3394a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
3404a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Returns:
3414a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      True if resumable upload or False.
3424a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
3434a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    return self._resumable
3444a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
3454a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def getbytes(self, begin, length):
3464a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Get bytes from the media.
3474a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
3484a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Args:
3494a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      begin: int, offset from beginning of file.
3504a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      length: int, number of bytes to read, starting at begin.
3514a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
3524a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Returns:
3534a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      A string of bytes read. May be shorted than length if EOF was reached
3544a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      first.
3554a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
3564a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self._fd.seek(begin)
3574a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    return self._fd.read(length)
3584a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
3594a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def has_stream(self):
3604a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Does the underlying upload support a streaming interface.
3614a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
3624a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Streaming means it is an io.IOBase subclass that supports seek, i.e.
3634a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    seekable() returns True.
3644a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
3654a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Returns:
3664a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      True if the call to stream() will return an instance of a seekable io.Base
3674a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      subclass.
3684a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
3694a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    return True
3704a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
3714a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def stream(self):
3724a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """A stream interface to the data being uploaded.
3734a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
3744a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Returns:
3754a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      The returned value is an io.IOBase subclass that supports seek, i.e.
3764a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      seekable() returns True.
3774a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
3784a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    return self._fd
3794a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
3804a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def to_json(self):
3814a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """This upload type is not serializable."""
3824a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    raise NotImplementedError('MediaIoBaseUpload is not serializable.')
3834a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
3844a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
3854a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairclass MediaFileUpload(MediaIoBaseUpload):
3864a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  """A MediaUpload for a file.
3874a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
3884a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  Construct a MediaFileUpload and pass as the media_body parameter of the
3894a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  method. For example, if we had a service that allowed uploading images:
3904a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
3914a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
3924a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    media = MediaFileUpload('cow.png', mimetype='image/png',
3934a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      chunksize=1024*1024, resumable=True)
3944a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    farm.animals().insert(
3954a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        id='cow',
3964a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        name='cow.png',
3974a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        media_body=media).execute()
3984a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
3994a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  Depending on the platform you are working on, you may pass -1 as the
4004a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  chunksize, which indicates that the entire file should be uploaded in a single
4014a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  request. If the underlying platform supports streams, such as Python 2.6 or
4024a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  later, then this can be very efficient as it avoids multiple connections, and
4034a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  also avoids loading the entire file into memory before sending it. Note that
4044a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  Google App Engine has a 5MB limit on request size, so you should never set
4054a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  your chunksize larger than 5MB, or to -1.
4064a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  """
4074a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
4084a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  @util.positional(2)
4094a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def __init__(self, filename, mimetype=None, chunksize=DEFAULT_CHUNK_SIZE,
4104a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair               resumable=False):
4114a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Constructor.
4124a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
4134a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Args:
4144a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      filename: string, Name of the file.
4154a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      mimetype: string, Mime-type of the file. If None then a mime-type will be
4164a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        guessed from the file extension.
4174a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      chunksize: int, File will be uploaded in chunks of this many bytes. Only
4184a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        used if resumable=True. Pass in a value of -1 if the file is to be
4194a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        uploaded in a single chunk. Note that Google App Engine has a 5MB limit
4204a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        on request size, so you should never set your chunksize larger than 5MB,
4214a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        or to -1.
4224a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      resumable: bool, True if this is a resumable upload. False means upload
4234a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        in a single request.
4244a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
4254a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self._filename = filename
4264a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    fd = open(self._filename, 'rb')
4274a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if mimetype is None:
4284a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      (mimetype, encoding) = mimetypes.guess_type(filename)
4294a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    super(MediaFileUpload, self).__init__(fd, mimetype, chunksize=chunksize,
4304a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                                          resumable=resumable)
4314a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
4324a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def to_json(self):
4334a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Creating a JSON representation of an instance of MediaFileUpload.
4344a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
4354a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Returns:
4364a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair       string, a JSON representation of this instance, suitable to pass to
4374a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair       from_json().
4384a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
4394a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    return self._to_json(strip=['_fd'])
4404a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
4414a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  @staticmethod
4424a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def from_json(s):
4434a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    d = json.loads(s)
4444a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    return MediaFileUpload(d['_filename'], mimetype=d['_mimetype'],
4454a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                           chunksize=d['_chunksize'], resumable=d['_resumable'])
4464a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
4474a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
4484a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairclass MediaInMemoryUpload(MediaIoBaseUpload):
4494a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  """MediaUpload for a chunk of bytes.
4504a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
4514a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  DEPRECATED: Use MediaIoBaseUpload with either io.TextIOBase or StringIO for
4524a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  the stream.
4534a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  """
4544a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
4554a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  @util.positional(2)
4564a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def __init__(self, body, mimetype='application/octet-stream',
4574a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair               chunksize=DEFAULT_CHUNK_SIZE, resumable=False):
4584a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Create a new MediaInMemoryUpload.
4594a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
4604a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  DEPRECATED: Use MediaIoBaseUpload with either io.TextIOBase or StringIO for
4614a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  the stream.
4624a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
4634a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  Args:
4644a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    body: string, Bytes of body content.
4654a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    mimetype: string, Mime-type of the file or default of
4664a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      'application/octet-stream'.
4674a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    chunksize: int, File will be uploaded in chunks of this many bytes. Only
4684a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      used if resumable=True.
4694a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    resumable: bool, True if this is a resumable upload. False means upload
4704a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      in a single request.
4714a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
4724a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    fd = BytesIO(body)
4734a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    super(MediaInMemoryUpload, self).__init__(fd, mimetype, chunksize=chunksize,
4744a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                                              resumable=resumable)
4754a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
4764a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
4774a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairclass MediaIoBaseDownload(object):
4784a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  """"Download media resources.
4794a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
4804a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  Note that the Python file object is compatible with io.Base and can be used
4814a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  with this class also.
4824a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
4834a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
4844a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  Example:
4854a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    request = farms.animals().get_media(id='cow')
4864a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    fh = io.FileIO('cow.png', mode='wb')
4874a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    downloader = MediaIoBaseDownload(fh, request, chunksize=1024*1024)
4884a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
4894a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    done = False
4904a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    while done is False:
4914a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      status, done = downloader.next_chunk()
4924a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      if status:
4934a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        print "Download %d%%." % int(status.progress() * 100)
4944a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    print "Download Complete!"
4954a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  """
4964a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
4974a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  @util.positional(3)
4984a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def __init__(self, fd, request, chunksize=DEFAULT_CHUNK_SIZE):
4994a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Constructor.
5004a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
5014a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Args:
5024a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      fd: io.Base or file object, The stream in which to write the downloaded
5034a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        bytes.
5044a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      request: googleapiclient.http.HttpRequest, the media request to perform in
5054a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        chunks.
5064a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      chunksize: int, File will be downloaded in chunks of this many bytes.
5074a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
5084a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self._fd = fd
5094a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self._request = request
5104a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self._uri = request.uri
5114a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self._chunksize = chunksize
5124a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self._progress = 0
5134a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self._total_size = None
5144a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self._done = False
5154a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
5164a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # Stubs for testing.
5174a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self._sleep = time.sleep
5184a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self._rand = random.random
5194a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
5204a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  @util.positional(1)
5214a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def next_chunk(self, num_retries=0):
5224a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Get the next chunk of the download.
5234a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
5244a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Args:
5254a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      num_retries: Integer, number of times to retry 500's with randomized
5264a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            exponential backoff. If all retries fail, the raised HttpError
5274a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            represents the last request. If zero (default), we attempt the
5284a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            request only once.
5294a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
5304a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Returns:
5314a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      (status, done): (MediaDownloadStatus, boolean)
5324a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair         The value of 'done' will be True when the media has been fully
5334a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair         downloaded.
5344a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
5354a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Raises:
5364a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      googleapiclient.errors.HttpError if the response was not a 2xx.
5374a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      httplib2.HttpLib2Error if a transport error has occured.
5384a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
5394a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    headers = {
5404a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        'range': 'bytes=%d-%d' % (
5414a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            self._progress, self._progress + self._chunksize)
5424a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        }
5434a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    http = self._request.http
5444a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
5454a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    for retry_num in range(num_retries + 1):
5464a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      if retry_num > 0:
5474a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        self._sleep(self._rand() * 2**retry_num)
5484a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        logging.warning(
5494a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            'Retry #%d for media download: GET %s, following status: %d'
5504a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            % (retry_num, self._uri, resp.status))
5514a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
5524a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      resp, content = http.request(self._uri, headers=headers)
5534a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      if resp.status < 500:
5544a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        break
5554a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
5564a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if resp.status in [200, 206]:
5574a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      if 'content-location' in resp and resp['content-location'] != self._uri:
5584a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        self._uri = resp['content-location']
5594a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      self._progress += len(content)
5604a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      self._fd.write(content)
5614a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
5624a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      if 'content-range' in resp:
5634a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        content_range = resp['content-range']
5644a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        length = content_range.rsplit('/', 1)[1]
5654a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        self._total_size = int(length)
5664a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      elif 'content-length' in resp:
5674a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        self._total_size = int(resp['content-length'])
5684a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
5694a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      if self._progress == self._total_size:
5704a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        self._done = True
5714a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      return MediaDownloadProgress(self._progress, self._total_size), self._done
5724a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    else:
5734a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      raise HttpError(resp, content, uri=self._uri)
5744a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
5754a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
5764a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairclass _StreamSlice(object):
5774a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  """Truncated stream.
5784a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
5794a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  Takes a stream and presents a stream that is a slice of the original stream.
5804a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  This is used when uploading media in chunks. In later versions of Python a
5814a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  stream can be passed to httplib in place of the string of data to send. The
5824a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  problem is that httplib just blindly reads to the end of the stream. This
5834a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  wrapper presents a virtual stream that only reads to the end of the chunk.
5844a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  """
5854a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
5864a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def __init__(self, stream, begin, chunksize):
5874a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Constructor.
5884a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
5894a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Args:
5904a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      stream: (io.Base, file object), the stream to wrap.
5914a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      begin: int, the seek position the chunk begins at.
5924a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      chunksize: int, the size of the chunk.
5934a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
5944a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self._stream = stream
5954a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self._begin = begin
5964a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self._chunksize = chunksize
5974a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self._stream.seek(begin)
5984a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
5994a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def read(self, n=-1):
6004a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Read n bytes.
6014a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
6024a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Args:
6034a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      n, int, the number of bytes to read.
6044a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
6054a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Returns:
6064a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      A string of length 'n', or less if EOF is reached.
6074a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
6084a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # The data left available to read sits in [cur, end)
6094a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    cur = self._stream.tell()
6104a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    end = self._begin + self._chunksize
6114a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if n == -1 or cur + n > end:
6124a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      n = end - cur
6134a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    return self._stream.read(n)
6144a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
6154a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
6164a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairclass HttpRequest(object):
6174a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  """Encapsulates a single HTTP request."""
6184a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
6194a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  @util.positional(4)
6204a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def __init__(self, http, postproc, uri,
6214a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair               method='GET',
6224a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair               body=None,
6234a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair               headers=None,
6244a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair               methodId=None,
6254a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair               resumable=None):
6264a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Constructor for an HttpRequest.
6274a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
6284a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Args:
6294a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      http: httplib2.Http, the transport object to use to make a request
6304a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      postproc: callable, called on the HTTP response and content to transform
6314a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                it into a data object before returning, or raising an exception
6324a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                on an error.
6334a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      uri: string, the absolute URI to send the request to
6344a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      method: string, the HTTP method to use
6354a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      body: string, the request body of the HTTP request,
6364a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      headers: dict, the HTTP request headers
6374a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      methodId: string, a unique identifier for the API method being called.
6384a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      resumable: MediaUpload, None if this is not a resumbale request.
6394a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
6404a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self.uri = uri
6414a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self.method = method
6424a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self.body = body
6434a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self.headers = headers or {}
6444a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self.methodId = methodId
6454a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self.http = http
6464a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self.postproc = postproc
6474a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self.resumable = resumable
6484a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self.response_callbacks = []
6494a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self._in_error_state = False
6504a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
6514a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # Pull the multipart boundary out of the content-type header.
6524a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    major, minor, params = mimeparse.parse_mime_type(
6534a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        headers.get('content-type', 'application/json'))
6544a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
6554a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # The size of the non-media part of the request.
6564a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self.body_size = len(self.body or '')
6574a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
6584a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # The resumable URI to send chunks to.
6594a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self.resumable_uri = None
6604a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
6614a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # The bytes that have been uploaded.
6624a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self.resumable_progress = 0
6634a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
6644a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # Stubs for testing.
6654a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self._rand = random.random
6664a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self._sleep = time.sleep
6674a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
6684a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  @util.positional(1)
6694a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def execute(self, http=None, num_retries=0):
6704a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Execute the request.
6714a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
6724a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Args:
6734a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      http: httplib2.Http, an http object to be used in place of the
6744a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            one the HttpRequest request object was constructed with.
6754a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      num_retries: Integer, number of times to retry 500's with randomized
6764a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            exponential backoff. If all retries fail, the raised HttpError
6774a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            represents the last request. If zero (default), we attempt the
6784a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            request only once.
6794a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
6804a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Returns:
6814a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      A deserialized object model of the response body as determined
6824a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      by the postproc.
6834a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
6844a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Raises:
6854a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      googleapiclient.errors.HttpError if the response was not a 2xx.
6864a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      httplib2.HttpLib2Error if a transport error has occured.
6874a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
6884a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if http is None:
6894a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      http = self.http
6904a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
6914a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if self.resumable:
6924a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      body = None
6934a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      while body is None:
6944a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        _, body = self.next_chunk(http=http, num_retries=num_retries)
6954a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      return body
6964a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
6974a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # Non-resumable case.
6984a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
6994a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if 'content-length' not in self.headers:
7004a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      self.headers['content-length'] = str(self.body_size)
7014a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # If the request URI is too long then turn it into a POST request.
7024a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if len(self.uri) > MAX_URI_LENGTH and self.method == 'GET':
7034a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      self.method = 'POST'
7044a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      self.headers['x-http-method-override'] = 'GET'
7054a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      self.headers['content-type'] = 'application/x-www-form-urlencoded'
7064a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      parsed = urlparse(self.uri)
7074a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      self.uri = urlunparse(
7084a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair          (parsed.scheme, parsed.netloc, parsed.path, parsed.params, None,
7094a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair           None)
7104a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair          )
7114a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      self.body = parsed.query
7124a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      self.headers['content-length'] = str(len(self.body))
7134a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
7144a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # Handle retries for server-side errors.
7154a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    for retry_num in range(num_retries + 1):
7164a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      if retry_num > 0:
7174a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        self._sleep(self._rand() * 2**retry_num)
7184a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        logging.warning('Retry #%d for request: %s %s, following status: %d'
7194a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                        % (retry_num, self.method, self.uri, resp.status))
7204a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
7214a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      resp, content = http.request(str(self.uri), method=str(self.method),
7224a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                                   body=self.body, headers=self.headers)
7234a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      if resp.status < 500:
7244a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        break
7254a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
7264a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    for callback in self.response_callbacks:
7274a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      callback(resp)
7284a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if resp.status >= 300:
7294a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      raise HttpError(resp, content, uri=self.uri)
7304a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    return self.postproc(resp, content)
7314a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
7324a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  @util.positional(2)
7334a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def add_response_callback(self, cb):
7344a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """add_response_headers_callback
7354a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
7364a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Args:
7374a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      cb: Callback to be called on receiving the response headers, of signature:
7384a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
7394a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      def cb(resp):
7404a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        # Where resp is an instance of httplib2.Response
7414a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
7424a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self.response_callbacks.append(cb)
7434a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
7444a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  @util.positional(1)
7454a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def next_chunk(self, http=None, num_retries=0):
7464a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Execute the next step of a resumable upload.
7474a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
7484a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Can only be used if the method being executed supports media uploads and
7494a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    the MediaUpload object passed in was flagged as using resumable upload.
7504a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
7514a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Example:
7524a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
7534a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      media = MediaFileUpload('cow.png', mimetype='image/png',
7544a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                              chunksize=1000, resumable=True)
7554a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      request = farm.animals().insert(
7564a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair          id='cow',
7574a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair          name='cow.png',
7584a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair          media_body=media)
7594a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
7604a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      response = None
7614a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      while response is None:
7624a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        status, response = request.next_chunk()
7634a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        if status:
7644a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair          print "Upload %d%% complete." % int(status.progress() * 100)
7654a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
7664a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
7674a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Args:
7684a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      http: httplib2.Http, an http object to be used in place of the
7694a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            one the HttpRequest request object was constructed with.
7704a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      num_retries: Integer, number of times to retry 500's with randomized
7714a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            exponential backoff. If all retries fail, the raised HttpError
7724a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            represents the last request. If zero (default), we attempt the
7734a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            request only once.
7744a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
7754a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Returns:
7764a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      (status, body): (ResumableMediaStatus, object)
7774a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair         The body will be None until the resumable media is fully uploaded.
7784a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
7794a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Raises:
7804a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      googleapiclient.errors.HttpError if the response was not a 2xx.
7814a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      httplib2.HttpLib2Error if a transport error has occured.
7824a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
7834a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if http is None:
7844a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      http = self.http
7854a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
7864a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if self.resumable.size() is None:
7874a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      size = '*'
7884a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    else:
7894a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      size = str(self.resumable.size())
7904a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
7914a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if self.resumable_uri is None:
7924a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      start_headers = copy.copy(self.headers)
7934a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      start_headers['X-Upload-Content-Type'] = self.resumable.mimetype()
7944a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      if size != '*':
7954a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        start_headers['X-Upload-Content-Length'] = size
7964a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      start_headers['content-length'] = str(self.body_size)
7974a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
7984a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      for retry_num in range(num_retries + 1):
7994a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        if retry_num > 0:
8004a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair          self._sleep(self._rand() * 2**retry_num)
8014a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair          logging.warning(
8024a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair              'Retry #%d for resumable URI request: %s %s, following status: %d'
8034a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair              % (retry_num, self.method, self.uri, resp.status))
8044a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
8054a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        resp, content = http.request(self.uri, method=self.method,
8064a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                                     body=self.body,
8074a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                                     headers=start_headers)
8084a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        if resp.status < 500:
8094a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair          break
8104a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
8114a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      if resp.status == 200 and 'location' in resp:
8124a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        self.resumable_uri = resp['location']
8134a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      else:
8144a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        raise ResumableUploadError(resp, content)
8154a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    elif self._in_error_state:
8164a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      # If we are in an error state then query the server for current state of
8174a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      # the upload by sending an empty PUT and reading the 'range' header in
8184a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      # the response.
8194a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      headers = {
8204a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair          'Content-Range': 'bytes */%s' % size,
8214a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair          'content-length': '0'
8224a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair          }
8234a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      resp, content = http.request(self.resumable_uri, 'PUT',
8244a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                                   headers=headers)
8254a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      status, body = self._process_response(resp, content)
8264a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      if body:
8274a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        # The upload was complete.
8284a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        return (status, body)
8294a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
8304a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # The httplib.request method can take streams for the body parameter, but
8314a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # only in Python 2.6 or later. If a stream is available under those
8324a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # conditions then use it as the body argument.
8334a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if self.resumable.has_stream() and sys.version_info[1] >= 6:
8344a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      data = self.resumable.stream()
8354a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      if self.resumable.chunksize() == -1:
8364a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        data.seek(self.resumable_progress)
8374a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        chunk_end = self.resumable.size() - self.resumable_progress - 1
8384a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      else:
8394a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        # Doing chunking with a stream, so wrap a slice of the stream.
8404a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        data = _StreamSlice(data, self.resumable_progress,
8414a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                            self.resumable.chunksize())
8424a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        chunk_end = min(
8434a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            self.resumable_progress + self.resumable.chunksize() - 1,
8444a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            self.resumable.size() - 1)
8454a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    else:
8464a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      data = self.resumable.getbytes(
8474a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair          self.resumable_progress, self.resumable.chunksize())
8484a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
8494a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      # A short read implies that we are at EOF, so finish the upload.
8504a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      if len(data) < self.resumable.chunksize():
8514a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        size = str(self.resumable_progress + len(data))
8524a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
8534a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      chunk_end = self.resumable_progress + len(data) - 1
8544a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
8554a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    headers = {
8564a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        'Content-Range': 'bytes %d-%d/%s' % (
8574a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            self.resumable_progress, chunk_end, size),
8584a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        # Must set the content-length header here because httplib can't
8594a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        # calculate the size when working with _StreamSlice.
8604a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        'Content-Length': str(chunk_end - self.resumable_progress + 1)
8614a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        }
8624a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
8634a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    for retry_num in range(num_retries + 1):
8644a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      if retry_num > 0:
8654a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        self._sleep(self._rand() * 2**retry_num)
8664a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        logging.warning(
8674a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            'Retry #%d for media upload: %s %s, following status: %d'
8684a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            % (retry_num, self.method, self.uri, resp.status))
8694a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
8704a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      try:
8714a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        resp, content = http.request(self.resumable_uri, method='PUT',
8724a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                                     body=data,
8734a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                                     headers=headers)
8744a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      except:
8754a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        self._in_error_state = True
8764a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        raise
8774a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      if resp.status < 500:
8784a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        break
8794a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
8804a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    return self._process_response(resp, content)
8814a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
8824a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def _process_response(self, resp, content):
8834a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Process the response from a single chunk upload.
8844a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
8854a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Args:
8864a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      resp: httplib2.Response, the response object.
8874a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      content: string, the content of the response.
8884a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
8894a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Returns:
8904a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      (status, body): (ResumableMediaStatus, object)
8914a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair         The body will be None until the resumable media is fully uploaded.
8924a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
8934a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Raises:
8944a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      googleapiclient.errors.HttpError if the response was not a 2xx or a 308.
8954a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
8964a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if resp.status in [200, 201]:
8974a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      self._in_error_state = False
8984a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      return None, self.postproc(resp, content)
8994a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    elif resp.status == 308:
9004a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      self._in_error_state = False
9014a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      # A "308 Resume Incomplete" indicates we are not done.
9024a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      self.resumable_progress = int(resp['range'].split('-')[1]) + 1
9034a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      if 'location' in resp:
9044a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        self.resumable_uri = resp['location']
9054a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    else:
9064a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      self._in_error_state = True
9074a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      raise HttpError(resp, content, uri=self.uri)
9084a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
9094a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    return (MediaUploadProgress(self.resumable_progress, self.resumable.size()),
9104a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            None)
9114a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
9124a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def to_json(self):
9134a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Returns a JSON representation of the HttpRequest."""
9144a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    d = copy.copy(self.__dict__)
9154a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if d['resumable'] is not None:
9164a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      d['resumable'] = self.resumable.to_json()
9174a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    del d['http']
9184a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    del d['postproc']
9194a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    del d['_sleep']
9204a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    del d['_rand']
9214a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
9224a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    return json.dumps(d)
9234a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
9244a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  @staticmethod
9254a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def from_json(s, http, postproc):
9264a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Returns an HttpRequest populated with info from a JSON object."""
9274a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    d = json.loads(s)
9284a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if d['resumable'] is not None:
9294a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      d['resumable'] = MediaUpload.new_from_json(d['resumable'])
9304a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    return HttpRequest(
9314a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        http,
9324a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        postproc,
9334a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        uri=d['uri'],
9344a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        method=d['method'],
9354a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        body=d['body'],
9364a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        headers=d['headers'],
9374a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        methodId=d['methodId'],
9384a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        resumable=d['resumable'])
9394a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
9404a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
9414a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairclass BatchHttpRequest(object):
9424a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  """Batches multiple HttpRequest objects into a single HTTP request.
9434a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
9444a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  Example:
9454a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    from googleapiclient.http import BatchHttpRequest
9464a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
9474a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    def list_animals(request_id, response, exception):
9484a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      \"\"\"Do something with the animals list response.\"\"\"
9494a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      if exception is not None:
9504a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        # Do something with the exception.
9514a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        pass
9524a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      else:
9534a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        # Do something with the response.
9544a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        pass
9554a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
9564a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    def list_farmers(request_id, response, exception):
9574a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      \"\"\"Do something with the farmers list response.\"\"\"
9584a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      if exception is not None:
9594a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        # Do something with the exception.
9604a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        pass
9614a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      else:
9624a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        # Do something with the response.
9634a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        pass
9644a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
9654a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    service = build('farm', 'v2')
9664a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
9674a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    batch = BatchHttpRequest()
9684a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
9694a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    batch.add(service.animals().list(), list_animals)
9704a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    batch.add(service.farmers().list(), list_farmers)
9714a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    batch.execute(http=http)
9724a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  """
9734a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
9744a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  @util.positional(1)
9754a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def __init__(self, callback=None, batch_uri=None):
9764a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Constructor for a BatchHttpRequest.
9774a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
9784a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Args:
9794a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      callback: callable, A callback to be called for each response, of the
9804a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        form callback(id, response, exception). The first parameter is the
9814a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        request id, and the second is the deserialized response object. The
9824a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        third is an googleapiclient.errors.HttpError exception object if an HTTP error
9834a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        occurred while processing the request, or None if no error occurred.
9844a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      batch_uri: string, URI to send batch requests to.
9854a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
9864a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if batch_uri is None:
9874a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      batch_uri = 'https://www.googleapis.com/batch'
9884a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self._batch_uri = batch_uri
9894a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
9904a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # Global callback to be called for each individual response in the batch.
9914a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self._callback = callback
9924a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
9934a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # A map from id to request.
9944a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self._requests = {}
9954a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
9964a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # A map from id to callback.
9974a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self._callbacks = {}
9984a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
9994a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # List of request ids, in the order in which they were added.
10004a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self._order = []
10014a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
10024a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # The last auto generated id.
10034a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self._last_auto_id = 0
10044a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
10054a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # Unique ID on which to base the Content-ID headers.
10064a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self._base_id = None
10074a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
10084a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # A map from request id to (httplib2.Response, content) response pairs
10094a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self._responses = {}
10104a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
10114a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # A map of id(Credentials) that have been refreshed.
10124a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self._refreshed_credentials = {}
10134a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
10144a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def _refresh_and_apply_credentials(self, request, http):
10154a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Refresh the credentials and apply to the request.
10164a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
10174a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Args:
10184a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      request: HttpRequest, the request.
10194a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      http: httplib2.Http, the global http object for the batch.
10204a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
10214a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # For the credentials to refresh, but only once per refresh_token
10224a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # If there is no http per the request then refresh the http passed in
10234a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # via execute()
10244a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    creds = None
10254a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if request.http is not None and hasattr(request.http.request,
10264a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        'credentials'):
10274a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      creds = request.http.request.credentials
10284a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    elif http is not None and hasattr(http.request, 'credentials'):
10294a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      creds = http.request.credentials
10304a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if creds is not None:
10314a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      if id(creds) not in self._refreshed_credentials:
10324a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        creds.refresh(http)
10334a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        self._refreshed_credentials[id(creds)] = 1
10344a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
10354a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # Only apply the credentials if we are using the http object passed in,
10364a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # otherwise apply() will get called during _serialize_request().
10374a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if request.http is None or not hasattr(request.http.request,
10384a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        'credentials'):
10394a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      creds.apply(request.headers)
10404a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
10414a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def _id_to_header(self, id_):
10424a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Convert an id to a Content-ID header value.
10434a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
10444a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Args:
10454a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      id_: string, identifier of individual request.
10464a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
10474a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Returns:
10484a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      A Content-ID header with the id_ encoded into it. A UUID is prepended to
10494a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      the value because Content-ID headers are supposed to be universally
10504a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      unique.
10514a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
10524a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if self._base_id is None:
10534a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      self._base_id = uuid.uuid4()
10544a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
10554a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    return '<%s+%s>' % (self._base_id, quote(id_))
10564a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
10574a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def _header_to_id(self, header):
10584a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Convert a Content-ID header value to an id.
10594a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
10604a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Presumes the Content-ID header conforms to the format that _id_to_header()
10614a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    returns.
10624a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
10634a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Args:
10644a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      header: string, Content-ID header value.
10654a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
10664a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Returns:
10674a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      The extracted id value.
10684a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
10694a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Raises:
10704a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      BatchError if the header is not in the expected format.
10714a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
10724a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if header[0] != '<' or header[-1] != '>':
10734a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      raise BatchError("Invalid value for Content-ID: %s" % header)
10744a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if '+' not in header:
10754a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      raise BatchError("Invalid value for Content-ID: %s" % header)
10764a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    base, id_ = header[1:-1].rsplit('+', 1)
10774a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
10784a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    return unquote(id_)
10794a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
10804a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def _serialize_request(self, request):
10814a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Convert an HttpRequest object into a string.
10824a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
10834a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Args:
10844a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      request: HttpRequest, the request to serialize.
10854a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
10864a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Returns:
10874a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      The request as a string in application/http format.
10884a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
10894a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # Construct status line
10904a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    parsed = urlparse(request.uri)
10914a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    request_line = urlunparse(
10924a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        ('', '', parsed.path, parsed.params, parsed.query, '')
10934a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        )
10944a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    status_line = request.method + ' ' + request_line + ' HTTP/1.1\n'
10954a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    major, minor = request.headers.get('content-type', 'application/json').split('/')
10964a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    msg = MIMENonMultipart(major, minor)
10974a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    headers = request.headers.copy()
10984a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
10994a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if request.http is not None and hasattr(request.http.request,
11004a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        'credentials'):
11014a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      request.http.request.credentials.apply(headers)
11024a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
11034a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # MIMENonMultipart adds its own Content-Type header.
11044a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if 'content-type' in headers:
11054a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      del headers['content-type']
11064a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
11074a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    for key, value in six.iteritems(headers):
11084a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      msg[key] = value
11094a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    msg['Host'] = parsed.netloc
11104a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    msg.set_unixfrom(None)
11114a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
11124a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if request.body is not None:
11134a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      msg.set_payload(request.body)
11144a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      msg['content-length'] = str(len(request.body))
11154a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
11164a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # Serialize the mime message.
11174a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    fp = StringIO()
11184a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # maxheaderlen=0 means don't line wrap headers.
11194a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    g = Generator(fp, maxheaderlen=0)
11204a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    g.flatten(msg, unixfrom=False)
11214a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    body = fp.getvalue()
11224a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
11234a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # Strip off the \n\n that the MIME lib tacks onto the end of the payload.
11244a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if request.body is None:
11254a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      body = body[:-2]
11264a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
11274a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    return status_line + body
11284a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
11294a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def _deserialize_response(self, payload):
11304a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Convert string into httplib2 response and content.
11314a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
11324a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Args:
11334a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      payload: string, headers and body as a string.
11344a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
11354a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Returns:
11364a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      A pair (resp, content), such as would be returned from httplib2.request.
11374a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
11384a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # Strip off the status line
11394a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    status_line, payload = payload.split('\n', 1)
11404a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    protocol, status, reason = status_line.split(' ', 2)
11414a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
11424a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # Parse the rest of the response
11434a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    parser = FeedParser()
11444a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    parser.feed(payload)
11454a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    msg = parser.close()
11464a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    msg['status'] = status
11474a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
11484a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # Create httplib2.Response from the parsed headers.
11494a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    resp = httplib2.Response(msg)
11504a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    resp.reason = reason
11514a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    resp.version = int(protocol.split('/', 1)[1].replace('.', ''))
11524a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
11534a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    content = payload.split('\r\n\r\n', 1)[1]
11544a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
11554a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    return resp, content
11564a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
11574a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def _new_id(self):
11584a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Create a new id.
11594a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
11604a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Auto incrementing number that avoids conflicts with ids already used.
11614a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
11624a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Returns:
11634a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair       string, a new unique id.
11644a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
11654a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self._last_auto_id += 1
11664a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    while str(self._last_auto_id) in self._requests:
11674a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      self._last_auto_id += 1
11684a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    return str(self._last_auto_id)
11694a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
11704a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  @util.positional(2)
11714a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def add(self, request, callback=None, request_id=None):
11724a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Add a new request.
11734a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
11744a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Every callback added will be paired with a unique id, the request_id. That
11754a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    unique id will be passed back to the callback when the response comes back
11764a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    from the server. The default behavior is to have the library generate it's
11774a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    own unique id. If the caller passes in a request_id then they must ensure
11784a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    uniqueness for each request_id, and if they are not an exception is
11794a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    raised. Callers should either supply all request_ids or nevery supply a
11804a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    request id, to avoid such an error.
11814a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
11824a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Args:
11834a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      request: HttpRequest, Request to add to the batch.
11844a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      callback: callable, A callback to be called for this response, of the
11854a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        form callback(id, response, exception). The first parameter is the
11864a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        request id, and the second is the deserialized response object. The
11874a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        third is an googleapiclient.errors.HttpError exception object if an HTTP error
11884a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        occurred while processing the request, or None if no errors occurred.
11894a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      request_id: string, A unique id for the request. The id will be passed to
11904a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        the callback with the response.
11914a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
11924a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Returns:
11934a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      None
11944a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
11954a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Raises:
11964a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      BatchError if a media request is added to a batch.
11974a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      KeyError is the request_id is not unique.
11984a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
11994a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if request_id is None:
12004a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      request_id = self._new_id()
12014a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if request.resumable is not None:
12024a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      raise BatchError("Media requests cannot be used in a batch request.")
12034a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if request_id in self._requests:
12044a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      raise KeyError("A request with this ID already exists: %s" % request_id)
12054a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self._requests[request_id] = request
12064a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self._callbacks[request_id] = callback
12074a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self._order.append(request_id)
12084a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
12094a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def _execute(self, http, order, requests):
12104a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Serialize batch request, send to server, process response.
12114a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
12124a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Args:
12134a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      http: httplib2.Http, an http object to be used to make the request with.
12144a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      order: list, list of request ids in the order they were added to the
12154a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        batch.
12164a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      request: list, list of request objects to send.
12174a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
12184a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Raises:
12194a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      httplib2.HttpLib2Error if a transport error has occured.
12204a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      googleapiclient.errors.BatchError if the response is the wrong format.
12214a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
12224a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    message = MIMEMultipart('mixed')
12234a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # Message should not write out it's own headers.
12244a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    setattr(message, '_write_headers', lambda self: None)
12254a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
12264a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # Add all the individual requests.
12274a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    for request_id in order:
12284a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      request = requests[request_id]
12294a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
12304a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      msg = MIMENonMultipart('application', 'http')
12314a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      msg['Content-Transfer-Encoding'] = 'binary'
12324a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      msg['Content-ID'] = self._id_to_header(request_id)
12334a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
12344a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      body = self._serialize_request(request)
12354a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      msg.set_payload(body)
12364a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      message.attach(msg)
12374a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
12384a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # encode the body: note that we can't use `as_string`, because
12394a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # it plays games with `From ` lines.
12404a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    fp = StringIO()
12414a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    g = Generator(fp, mangle_from_=False)
12424a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    g.flatten(message, unixfrom=False)
12434a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    body = fp.getvalue()
12444a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
12454a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    headers = {}
12464a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    headers['content-type'] = ('multipart/mixed; '
12474a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                               'boundary="%s"') % message.get_boundary()
12484a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
12494a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    resp, content = http.request(self._batch_uri, method='POST', body=body,
12504a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                                 headers=headers)
12514a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
12524a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if resp.status >= 300:
12534a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      raise HttpError(resp, content, uri=self._batch_uri)
12544a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
12554a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # Now break out the individual responses and store each one.
12564a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    boundary, _ = content.split(None, 1)
12574a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
12584a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # Prepend with a content-type header so FeedParser can handle it.
12594a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    header = 'content-type: %s\r\n\r\n' % resp['content-type']
12604a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    for_parser = header + content
12614a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
12624a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    parser = FeedParser()
12634a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    parser.feed(for_parser)
12644a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    mime_response = parser.close()
12654a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
12664a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if not mime_response.is_multipart():
12674a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      raise BatchError("Response not in multipart/mixed format.", resp=resp,
12684a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                       content=content)
12694a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
12704a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    for part in mime_response.get_payload():
12714a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      request_id = self._header_to_id(part['Content-ID'])
12724a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      response, content = self._deserialize_response(part.get_payload())
12734a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      self._responses[request_id] = (response, content)
12744a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
12754a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  @util.positional(1)
12764a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def execute(self, http=None):
12774a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Execute all the requests as a single batched HTTP request.
12784a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
12794a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Args:
12804a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      http: httplib2.Http, an http object to be used in place of the one the
12814a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        HttpRequest request object was constructed with. If one isn't supplied
12824a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        then use a http object from the requests in this batch.
12834a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
12844a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Returns:
12854a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      None
12864a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
12874a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Raises:
12884a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      httplib2.HttpLib2Error if a transport error has occured.
12894a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      googleapiclient.errors.BatchError if the response is the wrong format.
12904a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
12914a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
12924a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # If http is not supplied use the first valid one given in the requests.
12934a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if http is None:
12944a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      for request_id in self._order:
12954a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        request = self._requests[request_id]
12964a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        if request is not None:
12974a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair          http = request.http
12984a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair          break
12994a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
13004a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if http is None:
13014a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      raise ValueError("Missing a valid http object.")
13024a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
13034a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self._execute(http, self._order, self._requests)
13044a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
13054a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # Loop over all the requests and check for 401s. For each 401 request the
13064a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # credentials should be refreshed and then sent again in a separate batch.
13074a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    redo_requests = {}
13084a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    redo_order = []
13094a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
13104a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    for request_id in self._order:
13114a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      resp, content = self._responses[request_id]
13124a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      if resp['status'] == '401':
13134a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        redo_order.append(request_id)
13144a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        request = self._requests[request_id]
13154a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        self._refresh_and_apply_credentials(request, http)
13164a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        redo_requests[request_id] = request
13174a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
13184a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if redo_requests:
13194a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      self._execute(http, redo_order, redo_requests)
13204a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
13214a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # Now process all callbacks that are erroring, and raise an exception for
13224a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # ones that return a non-2xx response? Or add extra parameter to callback
13234a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    # that contains an HttpError?
13244a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
13254a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    for request_id in self._order:
13264a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      resp, content = self._responses[request_id]
13274a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
13284a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      request = self._requests[request_id]
13294a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      callback = self._callbacks[request_id]
13304a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
13314a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      response = None
13324a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      exception = None
13334a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      try:
13344a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        if resp.status >= 300:
13354a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair          raise HttpError(resp, content, uri=request.uri)
13364a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        response = request.postproc(resp, content)
13374a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      except HttpError as e:
13384a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        exception = e
13394a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
13404a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      if callback is not None:
13414a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        callback(request_id, response, exception)
13424a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      if self._callback is not None:
13434a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        self._callback(request_id, response, exception)
13444a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
13454a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
13464a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairclass HttpRequestMock(object):
13474a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  """Mock of HttpRequest.
13484a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
13494a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  Do not construct directly, instead use RequestMockBuilder.
13504a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  """
13514a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
13524a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def __init__(self, resp, content, postproc):
13534a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Constructor for HttpRequestMock
13544a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
13554a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Args:
13564a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      resp: httplib2.Response, the response to emulate coming from the request
13574a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      content: string, the response body
13584a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      postproc: callable, the post processing function usually supplied by
13594a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                the model class. See model.JsonModel.response() as an example.
13604a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
13614a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self.resp = resp
13624a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self.content = content
13634a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self.postproc = postproc
13644a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if resp is None:
13654a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      self.resp = httplib2.Response({'status': 200, 'reason': 'OK'})
13664a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if 'reason' in self.resp:
13674a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      self.resp.reason = self.resp['reason']
13684a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
13694a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def execute(self, http=None):
13704a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Execute the request.
13714a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
13724a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Same behavior as HttpRequest.execute(), but the response is
13734a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    mocked and not really from an HTTP request/response.
13744a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
13754a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    return self.postproc(self.resp, self.content)
13764a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
13774a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
13784a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairclass RequestMockBuilder(object):
13794a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  """A simple mock of HttpRequest
13804a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
13814a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Pass in a dictionary to the constructor that maps request methodIds to
13824a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    tuples of (httplib2.Response, content, opt_expected_body) that should be
13834a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    returned when that method is called. None may also be passed in for the
13844a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    httplib2.Response, in which case a 200 OK response will be generated.
13854a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    If an opt_expected_body (str or dict) is provided, it will be compared to
13864a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    the body and UnexpectedBodyError will be raised on inequality.
13874a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
13884a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Example:
13894a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      response = '{"data": {"id": "tag:google.c...'
13904a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      requestBuilder = RequestMockBuilder(
13914a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        {
13924a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair          'plus.activities.get': (None, response),
13934a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        }
13944a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      )
13954a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      googleapiclient.discovery.build("plus", "v1", requestBuilder=requestBuilder)
13964a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
13974a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Methods that you do not supply a response for will return a
13984a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    200 OK with an empty string as the response content or raise an excpetion
13994a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if check_unexpected is set to True. The methodId is taken from the rpcName
14004a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    in the discovery document.
14014a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
14024a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    For more details see the project wiki.
14034a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  """
14044a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
14054a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def __init__(self, responses, check_unexpected=False):
14064a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Constructor for RequestMockBuilder
14074a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
14084a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    The constructed object should be a callable object
14094a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    that can replace the class HttpResponse.
14104a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
14114a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    responses - A dictionary that maps methodIds into tuples
14124a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                of (httplib2.Response, content). The methodId
14134a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                comes from the 'rpcName' field in the discovery
14144a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                document.
14154a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    check_unexpected - A boolean setting whether or not UnexpectedMethodError
14164a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                       should be raised on unsupplied method.
14174a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
14184a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self.responses = responses
14194a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self.check_unexpected = check_unexpected
14204a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
14214a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def __call__(self, http, postproc, uri, method='GET', body=None,
14224a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair               headers=None, methodId=None, resumable=None):
14234a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Implements the callable interface that discovery.build() expects
14244a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    of requestBuilder, which is to build an object compatible with
14254a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    HttpRequest.execute(). See that method for the description of the
14264a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    parameters and the expected response.
14274a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
14284a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if methodId in self.responses:
14294a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      response = self.responses[methodId]
14304a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      resp, content = response[:2]
14314a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      if len(response) > 2:
14324a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        # Test the body against the supplied expected_body.
14334a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        expected_body = response[2]
14344a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        if bool(expected_body) != bool(body):
14354a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair          # Not expecting a body and provided one
14364a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair          # or expecting a body and not provided one.
14374a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair          raise UnexpectedBodyError(expected_body, body)
14384a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        if isinstance(expected_body, str):
14394a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair          expected_body = json.loads(expected_body)
14404a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        body = json.loads(body)
14414a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        if body != expected_body:
14424a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair          raise UnexpectedBodyError(expected_body, body)
14434a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      return HttpRequestMock(resp, content, postproc)
14444a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    elif self.check_unexpected:
14454a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      raise UnexpectedMethodError(methodId=methodId)
14464a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    else:
14474a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      model = JsonModel(False)
14484a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      return HttpRequestMock(None, '{}', model.response)
14494a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
14504a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
14514a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairclass HttpMock(object):
14524a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  """Mock of httplib2.Http"""
14534a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
14544a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def __init__(self, filename=None, headers=None):
14554a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
14564a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Args:
14574a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      filename: string, absolute filename to read response from
14584a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      headers: dict, header to return with response
14594a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
14604a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if headers is None:
14614a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      headers = {'status': '200 OK'}
14624a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if filename:
14634a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      f = open(filename, 'r')
14644a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      self.data = f.read()
14654a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      f.close()
14664a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    else:
14674a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      self.data = None
14684a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self.response_headers = headers
14694a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self.headers = None
14704a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self.uri = None
14714a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self.method = None
14724a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self.body = None
14734a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self.headers = None
14744a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
14754a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
14764a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def request(self, uri,
14774a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair              method='GET',
14784a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair              body=None,
14794a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair              headers=None,
14804a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair              redirections=1,
14814a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair              connection_type=None):
14824a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self.uri = uri
14834a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self.method = method
14844a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self.body = body
14854a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self.headers = headers
14864a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    return httplib2.Response(self.response_headers), self.data
14874a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
14884a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
14894a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairclass HttpMockSequence(object):
14904a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  """Mock of httplib2.Http
14914a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
14924a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  Mocks a sequence of calls to request returning different responses for each
14934a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  call. Create an instance initialized with the desired response headers
14944a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  and content and then use as if an httplib2.Http instance.
14954a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
14964a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    http = HttpMockSequence([
14974a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      ({'status': '401'}, ''),
14984a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      ({'status': '200'}, '{"access_token":"1/3w","expires_in":3600}'),
14994a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      ({'status': '200'}, 'echo_request_headers'),
15004a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      ])
15014a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    resp, content = http.request("http://examples.com")
15024a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
15034a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  There are special values you can pass in for content to trigger
15044a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  behavours that are helpful in testing.
15054a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
15064a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  'echo_request_headers' means return the request headers in the response body
15074a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  'echo_request_headers_as_json' means return the request headers in
15084a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair     the response body
15094a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  'echo_request_body' means return the request body in the response body
15104a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  'echo_request_uri' means return the request uri in the response body
15114a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  """
15124a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
15134a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def __init__(self, iterable):
15144a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
15154a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Args:
15164a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      iterable: iterable, a sequence of pairs of (headers, body)
15174a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """
15184a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self._iterable = iterable
15194a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    self.follow_redirects = True
15204a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
15214a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def request(self, uri,
15224a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair              method='GET',
15234a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair              body=None,
15244a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair              headers=None,
15254a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair              redirections=1,
15264a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair              connection_type=None):
15274a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    resp, content = self._iterable.pop(0)
15284a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if content == 'echo_request_headers':
15294a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      content = headers
15304a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    elif content == 'echo_request_headers_as_json':
15314a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      content = json.dumps(headers)
15324a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    elif content == 'echo_request_body':
15334a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      if hasattr(body, 'read'):
15344a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        content = body.read()
15354a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      else:
15364a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        content = body
15374a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    elif content == 'echo_request_uri':
15384a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      content = uri
15394a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    return httplib2.Response(resp), content
15404a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
15414a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
15424a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairdef set_user_agent(http, user_agent):
15434a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  """Set the user-agent on every request.
15444a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
15454a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  Args:
15464a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair     http - An instance of httplib2.Http
15474a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair         or something that acts like it.
15484a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair     user_agent: string, the value for the user-agent header.
15494a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
15504a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  Returns:
15514a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair     A modified instance of http that was passed in.
15524a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
15534a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  Example:
15544a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
15554a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    h = httplib2.Http()
15564a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    h = set_user_agent(h, "my-app-name/6.0")
15574a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
15584a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  Most of the time the user-agent will be set doing auth, this is for the rare
15594a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  cases where you are accessing an unauthenticated endpoint.
15604a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  """
15614a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  request_orig = http.request
15624a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
15634a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  # The closure that will replace 'httplib2.Http.request'.
15644a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def new_request(uri, method='GET', body=None, headers=None,
15654a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                  redirections=httplib2.DEFAULT_MAX_REDIRECTS,
15664a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                  connection_type=None):
15674a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Modify the request headers to add the user-agent."""
15684a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if headers is None:
15694a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      headers = {}
15704a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if 'user-agent' in headers:
15714a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      headers['user-agent'] = user_agent + ' ' + headers['user-agent']
15724a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    else:
15734a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      headers['user-agent'] = user_agent
15744a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    resp, content = request_orig(uri, method, body, headers,
15754a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                        redirections, connection_type)
15764a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    return resp, content
15774a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
15784a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  http.request = new_request
15794a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  return http
15804a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
15814a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
15824a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairdef tunnel_patch(http):
15834a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  """Tunnel PATCH requests over POST.
15844a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  Args:
15854a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair     http - An instance of httplib2.Http
15864a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair         or something that acts like it.
15874a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
15884a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  Returns:
15894a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair     A modified instance of http that was passed in.
15904a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
15914a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  Example:
15924a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
15934a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    h = httplib2.Http()
15944a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    h = tunnel_patch(h, "my-app-name/6.0")
15954a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
15964a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  Useful if you are running on a platform that doesn't support PATCH.
15974a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  Apply this last if you are using OAuth 1.0, as changing the method
15984a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  will result in a different signature.
15994a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  """
16004a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  request_orig = http.request
16014a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
16024a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  # The closure that will replace 'httplib2.Http.request'.
16034a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  def new_request(uri, method='GET', body=None, headers=None,
16044a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                  redirections=httplib2.DEFAULT_MAX_REDIRECTS,
16054a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                  connection_type=None):
16064a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    """Modify the request headers to add the user-agent."""
16074a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if headers is None:
16084a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      headers = {}
16094a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    if method == 'PATCH':
16104a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      if 'oauth_token' in headers.get('authorization', ''):
16114a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        logging.warning(
16124a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            'OAuth 1.0 request made with Credentials after tunnel_patch.')
16134a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      headers['x-http-method-override'] = "PATCH"
16144a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair      method = 'POST'
16154a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    resp, content = request_orig(uri, method, body, headers,
16164a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                        redirections, connection_type)
16174a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    return resp, content
16184a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
16194a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  http.request = new_request
16204a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  return http
1621