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