1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 """Classes to encapsulate a single HTTP request.
16
17 The classes implement a command pattern, with every
18 object supporting an execute() method that does the
19 actuall HTTP request.
20 """
21
22 __author__ = 'jcgregorio@google.com (Joe Gregorio)'
23
24 import StringIO
25 import base64
26 import copy
27 import gzip
28 import httplib2
29 import mimeparse
30 import mimetypes
31 import os
32 import urllib
33 import urlparse
34 import uuid
35
36 from email.generator import Generator
37 from email.mime.multipart import MIMEMultipart
38 from email.mime.nonmultipart import MIMENonMultipart
39 from email.parser import FeedParser
40 from errors import BatchError
41 from errors import HttpError
42 from errors import ResumableUploadError
43 from errors import UnexpectedBodyError
44 from errors import UnexpectedMethodError
45 from model import JsonModel
46 from oauth2client.anyjson import simplejson
47
48
49 DEFAULT_CHUNK_SIZE = 512*1024
77
103
210
310
406
499
576
579 """Encapsulates a single HTTP request."""
580
581 - def __init__(self, http, postproc, uri,
582 method='GET',
583 body=None,
584 headers=None,
585 methodId=None,
586 resumable=None):
587 """Constructor for an HttpRequest.
588
589 Args:
590 http: httplib2.Http, the transport object to use to make a request
591 postproc: callable, called on the HTTP response and content to transform
592 it into a data object before returning, or raising an exception
593 on an error.
594 uri: string, the absolute URI to send the request to
595 method: string, the HTTP method to use
596 body: string, the request body of the HTTP request,
597 headers: dict, the HTTP request headers
598 methodId: string, a unique identifier for the API method being called.
599 resumable: MediaUpload, None if this is not a resumbale request.
600 """
601 self.uri = uri
602 self.method = method
603 self.body = body
604 self.headers = headers or {}
605 self.methodId = methodId
606 self.http = http
607 self.postproc = postproc
608 self.resumable = resumable
609 self._in_error_state = False
610
611
612 major, minor, params = mimeparse.parse_mime_type(
613 headers.get('content-type', 'application/json'))
614
615
616 self.body_size = len(self.body or '')
617
618
619 self.resumable_uri = None
620
621
622 self.resumable_progress = 0
623
625 """Execute the request.
626
627 Args:
628 http: httplib2.Http, an http object to be used in place of the
629 one the HttpRequest request object was constructed with.
630
631 Returns:
632 A deserialized object model of the response body as determined
633 by the postproc.
634
635 Raises:
636 apiclient.errors.HttpError if the response was not a 2xx.
637 httplib2.Error if a transport error has occured.
638 """
639 if http is None:
640 http = self.http
641 if self.resumable:
642 body = None
643 while body is None:
644 _, body = self.next_chunk(http)
645 return body
646 else:
647 if 'content-length' not in self.headers:
648 self.headers['content-length'] = str(self.body_size)
649 resp, content = http.request(self.uri, self.method,
650 body=self.body,
651 headers=self.headers)
652
653 if resp.status >= 300:
654 raise HttpError(resp, content, self.uri)
655 return self.postproc(resp, content)
656
658 """Execute the next step of a resumable upload.
659
660 Can only be used if the method being executed supports media uploads and
661 the MediaUpload object passed in was flagged as using resumable upload.
662
663 Example:
664
665 media = MediaFileUpload('cow.png', mimetype='image/png',
666 chunksize=1000, resumable=True)
667 request = farm.animals().insert(
668 id='cow',
669 name='cow.png',
670 media_body=media)
671
672 response = None
673 while response is None:
674 status, response = request.next_chunk()
675 if status:
676 print "Upload %d%% complete." % int(status.progress() * 100)
677
678
679 Returns:
680 (status, body): (ResumableMediaStatus, object)
681 The body will be None until the resumable media is fully uploaded.
682
683 Raises:
684 apiclient.errors.HttpError if the response was not a 2xx.
685 httplib2.Error if a transport error has occured.
686 """
687 if http is None:
688 http = self.http
689
690 if self.resumable.size() is None:
691 size = '*'
692 else:
693 size = str(self.resumable.size())
694
695 if self.resumable_uri is None:
696 start_headers = copy.copy(self.headers)
697 start_headers['X-Upload-Content-Type'] = self.resumable.mimetype()
698 if size != '*':
699 start_headers['X-Upload-Content-Length'] = size
700 start_headers['content-length'] = str(self.body_size)
701
702 resp, content = http.request(self.uri, self.method,
703 body=self.body,
704 headers=start_headers)
705 if resp.status == 200 and 'location' in resp:
706 self.resumable_uri = resp['location']
707 else:
708 raise ResumableUploadError("Failed to retrieve starting URI.")
709 elif self._in_error_state:
710
711
712
713 headers = {
714 'Content-Range': 'bytes */%s' % size,
715 'content-length': '0'
716 }
717 resp, content = http.request(self.resumable_uri, 'PUT',
718 headers=headers)
719 status, body = self._process_response(resp, content)
720 if body:
721
722 return (status, body)
723
724 data = self.resumable.getbytes(
725 self.resumable_progress, self.resumable.chunksize())
726
727
728 if len(data) < self.resumable.chunksize():
729 size = str(self.resumable_progress + len(data))
730
731 headers = {
732 'Content-Range': 'bytes %d-%d/%s' % (
733 self.resumable_progress, self.resumable_progress + len(data) - 1,
734 size)
735 }
736 try:
737 resp, content = http.request(self.resumable_uri, 'PUT',
738 body=data,
739 headers=headers)
740 except:
741 self._in_error_state = True
742 raise
743
744 return self._process_response(resp, content)
745
747 """Process the response from a single chunk upload.
748
749 Args:
750 resp: httplib2.Response, the response object.
751 content: string, the content of the response.
752
753 Returns:
754 (status, body): (ResumableMediaStatus, object)
755 The body will be None until the resumable media is fully uploaded.
756
757 Raises:
758 apiclient.errors.HttpError if the response was not a 2xx or a 308.
759 """
760 if resp.status in [200, 201]:
761 self._in_error_state = False
762 return None, self.postproc(resp, content)
763 elif resp.status == 308:
764 self._in_error_state = False
765
766 self.resumable_progress = int(resp['range'].split('-')[1]) + 1
767 if 'location' in resp:
768 self.resumable_uri = resp['location']
769 else:
770 self._in_error_state = True
771 raise HttpError(resp, content, self.uri)
772
773 return (MediaUploadProgress(self.resumable_progress, self.resumable.size()),
774 None)
775
777 """Returns a JSON representation of the HttpRequest."""
778 d = copy.copy(self.__dict__)
779 if d['resumable'] is not None:
780 d['resumable'] = self.resumable.to_json()
781 del d['http']
782 del d['postproc']
783
784 return simplejson.dumps(d)
785
786 @staticmethod
788 """Returns an HttpRequest populated with info from a JSON object."""
789 d = simplejson.loads(s)
790 if d['resumable'] is not None:
791 d['resumable'] = MediaUpload.new_from_json(d['resumable'])
792 return HttpRequest(
793 http,
794 postproc,
795 uri=d['uri'],
796 method=d['method'],
797 body=d['body'],
798 headers=d['headers'],
799 methodId=d['methodId'],
800 resumable=d['resumable'])
801
804 """Batches multiple HttpRequest objects into a single HTTP request.
805
806 Example:
807 from apiclient.http import BatchHttpRequest
808
809 def list_animals(request_id, response):
810 \"\"\"Do something with the animals list response.\"\"\"
811 pass
812
813 def list_farmers(request_id, response):
814 \"\"\"Do something with the farmers list response.\"\"\"
815 pass
816
817 service = build('farm', 'v2')
818
819 batch = BatchHttpRequest()
820
821 batch.add(service.animals().list(), list_animals)
822 batch.add(service.farmers().list(), list_farmers)
823 batch.execute(http)
824 """
825
826 - def __init__(self, callback=None, batch_uri=None):
827 """Constructor for a BatchHttpRequest.
828
829 Args:
830 callback: callable, A callback to be called for each response, of the
831 form callback(id, response). The first parameter is the request id, and
832 the second is the deserialized response object.
833 batch_uri: string, URI to send batch requests to.
834 """
835 if batch_uri is None:
836 batch_uri = 'https://www.googleapis.com/batch'
837 self._batch_uri = batch_uri
838
839
840 self._callback = callback
841
842
843 self._requests = {}
844
845
846 self._callbacks = {}
847
848
849 self._order = []
850
851
852 self._last_auto_id = 0
853
854
855 self._base_id = None
856
857
858 self._responses = {}
859
860
861 self._refreshed_credentials = {}
862
889
891 """Convert an id to a Content-ID header value.
892
893 Args:
894 id_: string, identifier of individual request.
895
896 Returns:
897 A Content-ID header with the id_ encoded into it. A UUID is prepended to
898 the value because Content-ID headers are supposed to be universally
899 unique.
900 """
901 if self._base_id is None:
902 self._base_id = uuid.uuid4()
903
904 return '<%s+%s>' % (self._base_id, urllib.quote(id_))
905
907 """Convert a Content-ID header value to an id.
908
909 Presumes the Content-ID header conforms to the format that _id_to_header()
910 returns.
911
912 Args:
913 header: string, Content-ID header value.
914
915 Returns:
916 The extracted id value.
917
918 Raises:
919 BatchError if the header is not in the expected format.
920 """
921 if header[0] != '<' or header[-1] != '>':
922 raise BatchError("Invalid value for Content-ID: %s" % header)
923 if '+' not in header:
924 raise BatchError("Invalid value for Content-ID: %s" % header)
925 base, id_ = header[1:-1].rsplit('+', 1)
926
927 return urllib.unquote(id_)
928
930 """Convert an HttpRequest object into a string.
931
932 Args:
933 request: HttpRequest, the request to serialize.
934
935 Returns:
936 The request as a string in application/http format.
937 """
938
939 parsed = urlparse.urlparse(request.uri)
940 request_line = urlparse.urlunparse(
941 (None, None, parsed.path, parsed.params, parsed.query, None)
942 )
943 status_line = request.method + ' ' + request_line + ' HTTP/1.1\n'
944 major, minor = request.headers.get('content-type', 'application/json').split('/')
945 msg = MIMENonMultipart(major, minor)
946 headers = request.headers.copy()
947
948 if request.http is not None and hasattr(request.http.request,
949 'credentials'):
950 request.http.request.credentials.apply(headers)
951
952
953 if 'content-type' in headers:
954 del headers['content-type']
955
956 for key, value in headers.iteritems():
957 msg[key] = value
958 msg['Host'] = parsed.netloc
959 msg.set_unixfrom(None)
960
961 if request.body is not None:
962 msg.set_payload(request.body)
963 msg['content-length'] = str(len(request.body))
964
965
966 fp = StringIO.StringIO()
967
968 g = Generator(fp, maxheaderlen=0)
969 g.flatten(msg, unixfrom=False)
970 body = fp.getvalue()
971
972
973 if request.body is None:
974 body = body[:-2]
975
976 return status_line.encode('utf-8') + body
977
979 """Convert string into httplib2 response and content.
980
981 Args:
982 payload: string, headers and body as a string.
983
984 Returns:
985 A pair (resp, content) like would be returned from httplib2.request.
986 """
987
988 status_line, payload = payload.split('\n', 1)
989 protocol, status, reason = status_line.split(' ', 2)
990
991
992 parser = FeedParser()
993 parser.feed(payload)
994 msg = parser.close()
995 msg['status'] = status
996
997
998 resp = httplib2.Response(msg)
999 resp.reason = reason
1000 resp.version = int(protocol.split('/', 1)[1].replace('.', ''))
1001
1002 content = payload.split('\r\n\r\n', 1)[1]
1003
1004 return resp, content
1005
1007 """Create a new id.
1008
1009 Auto incrementing number that avoids conflicts with ids already used.
1010
1011 Returns:
1012 string, a new unique id.
1013 """
1014 self._last_auto_id += 1
1015 while str(self._last_auto_id) in self._requests:
1016 self._last_auto_id += 1
1017 return str(self._last_auto_id)
1018
1019 - def add(self, request, callback=None, request_id=None):
1020 """Add a new request.
1021
1022 Every callback added will be paired with a unique id, the request_id. That
1023 unique id will be passed back to the callback when the response comes back
1024 from the server. The default behavior is to have the library generate it's
1025 own unique id. If the caller passes in a request_id then they must ensure
1026 uniqueness for each request_id, and if they are not an exception is
1027 raised. Callers should either supply all request_ids or nevery supply a
1028 request id, to avoid such an error.
1029
1030 Args:
1031 request: HttpRequest, Request to add to the batch.
1032 callback: callable, A callback to be called for this response, of the
1033 form callback(id, response). The first parameter is the request id, and
1034 the second is the deserialized response object.
1035 request_id: string, A unique id for the request. The id will be passed to
1036 the callback with the response.
1037
1038 Returns:
1039 None
1040
1041 Raises:
1042 BatchError if a media request is added to a batch.
1043 KeyError is the request_id is not unique.
1044 """
1045 if request_id is None:
1046 request_id = self._new_id()
1047 if request.resumable is not None:
1048 raise BatchError("Media requests cannot be used in a batch request.")
1049 if request_id in self._requests:
1050 raise KeyError("A request with this ID already exists: %s" % request_id)
1051 self._requests[request_id] = request
1052 self._callbacks[request_id] = callback
1053 self._order.append(request_id)
1054
1055 - def _execute(self, http, order, requests):
1056 """Serialize batch request, send to server, process response.
1057
1058 Args:
1059 http: httplib2.Http, an http object to be used to make the request with.
1060 order: list, list of request ids in the order they were added to the
1061 batch.
1062 request: list, list of request objects to send.
1063
1064 Raises:
1065 httplib2.Error if a transport error has occured.
1066 apiclient.errors.BatchError if the response is the wrong format.
1067 """
1068 message = MIMEMultipart('mixed')
1069
1070 setattr(message, '_write_headers', lambda self: None)
1071
1072
1073 for request_id in order:
1074 request = requests[request_id]
1075
1076 msg = MIMENonMultipart('application', 'http')
1077 msg['Content-Transfer-Encoding'] = 'binary'
1078 msg['Content-ID'] = self._id_to_header(request_id)
1079
1080 body = self._serialize_request(request)
1081 msg.set_payload(body)
1082 message.attach(msg)
1083
1084 body = message.as_string()
1085
1086 headers = {}
1087 headers['content-type'] = ('multipart/mixed; '
1088 'boundary="%s"') % message.get_boundary()
1089
1090 resp, content = http.request(self._batch_uri, 'POST', body=body,
1091 headers=headers)
1092
1093 if resp.status >= 300:
1094 raise HttpError(resp, content, self._batch_uri)
1095
1096
1097 boundary, _ = content.split(None, 1)
1098
1099
1100 header = 'content-type: %s\r\n\r\n' % resp['content-type']
1101 for_parser = header + content
1102
1103 parser = FeedParser()
1104 parser.feed(for_parser)
1105 mime_response = parser.close()
1106
1107 if not mime_response.is_multipart():
1108 raise BatchError("Response not in multipart/mixed format.", resp,
1109 content)
1110
1111 for part in mime_response.get_payload():
1112 request_id = self._header_to_id(part['Content-ID'])
1113 headers, content = self._deserialize_response(part.get_payload())
1114 self._responses[request_id] = (headers, content)
1115
1117 """Execute all the requests as a single batched HTTP request.
1118
1119 Args:
1120 http: httplib2.Http, an http object to be used in place of the one the
1121 HttpRequest request object was constructed with. If one isn't supplied
1122 then use a http object from the requests in this batch.
1123
1124 Returns:
1125 None
1126
1127 Raises:
1128 httplib2.Error if a transport error has occured.
1129 apiclient.errors.BatchError if the response is the wrong format.
1130 """
1131
1132
1133 if http is None:
1134 for request_id in self._order:
1135 request = self._requests[request_id]
1136 if request is not None:
1137 http = request.http
1138 break
1139
1140 if http is None:
1141 raise ValueError("Missing a valid http object.")
1142
1143 self._execute(http, self._order, self._requests)
1144
1145
1146
1147 redo_requests = {}
1148 redo_order = []
1149
1150 for request_id in self._order:
1151 headers, content = self._responses[request_id]
1152 if headers['status'] == '401':
1153 redo_order.append(request_id)
1154 request = self._requests[request_id]
1155 self._refresh_and_apply_credentials(request, http)
1156 redo_requests[request_id] = request
1157
1158 if redo_requests:
1159 self._execute(http, redo_order, redo_requests)
1160
1161
1162
1163
1164
1165 for request_id in self._order:
1166 headers, content = self._responses[request_id]
1167
1168 request = self._requests[request_id]
1169 callback = self._callbacks[request_id]
1170
1171 response = None
1172 exception = None
1173 try:
1174 r = httplib2.Response(headers)
1175 response = request.postproc(r, content)
1176 except HttpError, e:
1177 exception = e
1178
1179 if callback is not None:
1180 callback(request_id, response, exception)
1181 if self._callback is not None:
1182 self._callback(request_id, response, exception)
1183
1186 """Mock of HttpRequest.
1187
1188 Do not construct directly, instead use RequestMockBuilder.
1189 """
1190
1191 - def __init__(self, resp, content, postproc):
1192 """Constructor for HttpRequestMock
1193
1194 Args:
1195 resp: httplib2.Response, the response to emulate coming from the request
1196 content: string, the response body
1197 postproc: callable, the post processing function usually supplied by
1198 the model class. See model.JsonModel.response() as an example.
1199 """
1200 self.resp = resp
1201 self.content = content
1202 self.postproc = postproc
1203 if resp is None:
1204 self.resp = httplib2.Response({'status': 200, 'reason': 'OK'})
1205 if 'reason' in self.resp:
1206 self.resp.reason = self.resp['reason']
1207
1209 """Execute the request.
1210
1211 Same behavior as HttpRequest.execute(), but the response is
1212 mocked and not really from an HTTP request/response.
1213 """
1214 return self.postproc(self.resp, self.content)
1215
1218 """A simple mock of HttpRequest
1219
1220 Pass in a dictionary to the constructor that maps request methodIds to
1221 tuples of (httplib2.Response, content, opt_expected_body) that should be
1222 returned when that method is called. None may also be passed in for the
1223 httplib2.Response, in which case a 200 OK response will be generated.
1224 If an opt_expected_body (str or dict) is provided, it will be compared to
1225 the body and UnexpectedBodyError will be raised on inequality.
1226
1227 Example:
1228 response = '{"data": {"id": "tag:google.c...'
1229 requestBuilder = RequestMockBuilder(
1230 {
1231 'plus.activities.get': (None, response),
1232 }
1233 )
1234 apiclient.discovery.build("plus", "v1", requestBuilder=requestBuilder)
1235
1236 Methods that you do not supply a response for will return a
1237 200 OK with an empty string as the response content or raise an excpetion
1238 if check_unexpected is set to True. The methodId is taken from the rpcName
1239 in the discovery document.
1240
1241 For more details see the project wiki.
1242 """
1243
1244 - def __init__(self, responses, check_unexpected=False):
1245 """Constructor for RequestMockBuilder
1246
1247 The constructed object should be a callable object
1248 that can replace the class HttpResponse.
1249
1250 responses - A dictionary that maps methodIds into tuples
1251 of (httplib2.Response, content). The methodId
1252 comes from the 'rpcName' field in the discovery
1253 document.
1254 check_unexpected - A boolean setting whether or not UnexpectedMethodError
1255 should be raised on unsupplied method.
1256 """
1257 self.responses = responses
1258 self.check_unexpected = check_unexpected
1259
1260 - def __call__(self, http, postproc, uri, method='GET', body=None,
1261 headers=None, methodId=None, resumable=None):
1262 """Implements the callable interface that discovery.build() expects
1263 of requestBuilder, which is to build an object compatible with
1264 HttpRequest.execute(). See that method for the description of the
1265 parameters and the expected response.
1266 """
1267 if methodId in self.responses:
1268 response = self.responses[methodId]
1269 resp, content = response[:2]
1270 if len(response) > 2:
1271
1272 expected_body = response[2]
1273 if bool(expected_body) != bool(body):
1274
1275
1276 raise UnexpectedBodyError(expected_body, body)
1277 if isinstance(expected_body, str):
1278 expected_body = simplejson.loads(expected_body)
1279 body = simplejson.loads(body)
1280 if body != expected_body:
1281 raise UnexpectedBodyError(expected_body, body)
1282 return HttpRequestMock(resp, content, postproc)
1283 elif self.check_unexpected:
1284 raise UnexpectedMethodError(methodId)
1285 else:
1286 model = JsonModel(False)
1287 return HttpRequestMock(None, '{}', model.response)
1288
1291 """Mock of httplib2.Http"""
1292
1293 - def __init__(self, filename, headers=None):
1294 """
1295 Args:
1296 filename: string, absolute filename to read response from
1297 headers: dict, header to return with response
1298 """
1299 if headers is None:
1300 headers = {'status': '200 OK'}
1301 f = file(filename, 'r')
1302 self.data = f.read()
1303 f.close()
1304 self.headers = headers
1305
1306 - def request(self, uri,
1307 method='GET',
1308 body=None,
1309 headers=None,
1310 redirections=1,
1311 connection_type=None):
1312 return httplib2.Response(self.headers), self.data
1313
1316 """Mock of httplib2.Http
1317
1318 Mocks a sequence of calls to request returning different responses for each
1319 call. Create an instance initialized with the desired response headers
1320 and content and then use as if an httplib2.Http instance.
1321
1322 http = HttpMockSequence([
1323 ({'status': '401'}, ''),
1324 ({'status': '200'}, '{"access_token":"1/3w","expires_in":3600}'),
1325 ({'status': '200'}, 'echo_request_headers'),
1326 ])
1327 resp, content = http.request("http://examples.com")
1328
1329 There are special values you can pass in for content to trigger
1330 behavours that are helpful in testing.
1331
1332 'echo_request_headers' means return the request headers in the response body
1333 'echo_request_headers_as_json' means return the request headers in
1334 the response body
1335 'echo_request_body' means return the request body in the response body
1336 'echo_request_uri' means return the request uri in the response body
1337 """
1338
1340 """
1341 Args:
1342 iterable: iterable, a sequence of pairs of (headers, body)
1343 """
1344 self._iterable = iterable
1345 self.follow_redirects = True
1346
1347 - def request(self, uri,
1348 method='GET',
1349 body=None,
1350 headers=None,
1351 redirections=1,
1352 connection_type=None):
1353 resp, content = self._iterable.pop(0)
1354 if content == 'echo_request_headers':
1355 content = headers
1356 elif content == 'echo_request_headers_as_json':
1357 content = simplejson.dumps(headers)
1358 elif content == 'echo_request_body':
1359 content = body
1360 elif content == 'echo_request_uri':
1361 content = uri
1362 return httplib2.Response(resp), content
1363
1366 """Set the user-agent on every request.
1367
1368 Args:
1369 http - An instance of httplib2.Http
1370 or something that acts like it.
1371 user_agent: string, the value for the user-agent header.
1372
1373 Returns:
1374 A modified instance of http that was passed in.
1375
1376 Example:
1377
1378 h = httplib2.Http()
1379 h = set_user_agent(h, "my-app-name/6.0")
1380
1381 Most of the time the user-agent will be set doing auth, this is for the rare
1382 cases where you are accessing an unauthenticated endpoint.
1383 """
1384 request_orig = http.request
1385
1386
1387 def new_request(uri, method='GET', body=None, headers=None,
1388 redirections=httplib2.DEFAULT_MAX_REDIRECTS,
1389 connection_type=None):
1390 """Modify the request headers to add the user-agent."""
1391 if headers is None:
1392 headers = {}
1393 if 'user-agent' in headers:
1394 headers['user-agent'] = user_agent + ' ' + headers['user-agent']
1395 else:
1396 headers['user-agent'] = user_agent
1397 resp, content = request_orig(uri, method, body, headers,
1398 redirections, connection_type)
1399 return resp, content
1400
1401 http.request = new_request
1402 return http
1403
1406 """Tunnel PATCH requests over POST.
1407 Args:
1408 http - An instance of httplib2.Http
1409 or something that acts like it.
1410
1411 Returns:
1412 A modified instance of http that was passed in.
1413
1414 Example:
1415
1416 h = httplib2.Http()
1417 h = tunnel_patch(h, "my-app-name/6.0")
1418
1419 Useful if you are running on a platform that doesn't support PATCH.
1420 Apply this last if you are using OAuth 1.0, as changing the method
1421 will result in a different signature.
1422 """
1423 request_orig = http.request
1424
1425
1426 def new_request(uri, method='GET', body=None, headers=None,
1427 redirections=httplib2.DEFAULT_MAX_REDIRECTS,
1428 connection_type=None):
1429 """Modify the request headers to add the user-agent."""
1430 if headers is None:
1431 headers = {}
1432 if method == 'PATCH':
1433 if 'oauth_token' in headers.get('authorization', ''):
1434 logging.warning(
1435 'OAuth 1.0 request made with Credentials after tunnel_patch.')
1436 headers['x-http-method-override'] = "PATCH"
1437 method = 'POST'
1438 resp, content = request_orig(uri, method, body, headers,
1439 redirections, connection_type)
1440 return resp, content
1441
1442 http.request = new_request
1443 return http
1444