2323import io
2424import json
2525
26- import httplib2
26+ import requests
2727import six
2828
29- from google .cloud . exceptions import make_exception
29+ from google .cloud import exceptions
3030from google .cloud .storage ._http import Connection
3131
3232
@@ -70,11 +70,6 @@ def __init__(self, method, uri, headers, body):
7070 super_init (payload , 'http' , encode_noop )
7171
7272
73- class NoContent (object ):
74- """Emulate an HTTP '204 No Content' response."""
75- status = 204
76-
77-
7873class _FutureDict (object ):
7974 """Class to hold a future value for a deferred request.
8075
@@ -123,6 +118,19 @@ def __setitem__(self, key, value):
123118 raise KeyError ('Cannot set %r -> %r on a future' % (key , value ))
124119
125120
121+ class _FutureResponse (requests .Response ):
122+ def __init__ (self , future_dict ):
123+ super (_FutureResponse , self ).__init__ ()
124+ self ._future_dict = future_dict
125+ self .status_code = 204
126+
127+ def json (self ):
128+ raise ValueError ()
129+
130+ def content (self ):
131+ return self ._future_dict
132+
133+
126134class Batch (Connection ):
127135 """Proxy an underlying connection, batching up change operations.
128136
@@ -171,7 +179,7 @@ def _do_request(self, method, url, headers, data, target_object):
171179 self ._target_objects .append (target_object )
172180 if target_object is not None :
173181 target_object ._properties = result
174- return NoContent (), result
182+ return _FutureResponse ( result )
175183
176184 def _prepare_batch_request (self ):
177185 """Prepares headers and body for a batch request.
@@ -218,17 +226,18 @@ def _finish_futures(self, responses):
218226 if len (self ._target_objects ) != len (responses ):
219227 raise ValueError ('Expected a response for every request.' )
220228
221- for target_object , sub_response in zip (self ._target_objects ,
222- responses ):
223- resp_headers , sub_payload = sub_response
224- if not 200 <= resp_headers .status < 300 :
225- exception_args = exception_args or (resp_headers ,
226- sub_payload )
229+ for target_object , subresponse in zip (
230+ self ._target_objects , responses ):
231+ if not 200 <= subresponse .status_code < 300 :
232+ exception_args = exception_args or subresponse
227233 elif target_object is not None :
228- target_object ._properties = sub_payload
234+ try :
235+ target_object ._properties = subresponse .json ()
236+ except ValueError :
237+ target_object ._properties = subresponse .content
229238
230239 if exception_args is not None :
231- raise make_exception ( * exception_args )
240+ raise exceptions . from_http_response ( exception_args )
232241
233242 def finish (self ):
234243 """Submit a single `multipart/mixed` request with deferred requests.
@@ -243,9 +252,9 @@ def finish(self):
243252 # Use the private ``_base_connection`` rather than the property
244253 # ``_connection``, since the property may be this
245254 # current batch.
246- response , content = self ._client ._base_connection ._make_request (
255+ response = self ._client ._base_connection ._make_request (
247256 'POST' , url , data = body , headers = headers )
248- responses = list (_unpack_batch_response (response , content ))
257+ responses = list (_unpack_batch_response (response ))
249258 self ._finish_futures (responses )
250259 return responses
251260
@@ -265,24 +274,23 @@ def __exit__(self, exc_type, exc_val, exc_tb):
265274 self ._client ._pop_batch ()
266275
267276
268- def _generate_faux_mime_message (parser , response , content ):
277+ def _generate_faux_mime_message (parser , response ):
269278 """Convert response, content -> (multipart) email.message.
270279
271280 Helper for _unpack_batch_response.
272281 """
273282 # We coerce to bytes to get consistent concat across
274283 # Py2 and Py3. Percent formatting is insufficient since
275284 # it includes the b in Py3.
276- if not isinstance (content , six .binary_type ):
277- content = content .encode ('utf-8' )
278- content_type = response ['content-type' ]
285+ content_type = response .headers .get ('content-type' , '' )
279286 if not isinstance (content_type , six .binary_type ):
280287 content_type = content_type .encode ('utf-8' )
288+
281289 faux_message = b'' .join ([
282290 b'Content-Type: ' ,
283291 content_type ,
284292 b'\n MIME-Version: 1.0\n \n ' ,
285- content ,
293+ response . content ,
286294 ])
287295
288296 if six .PY2 :
@@ -291,20 +299,17 @@ def _generate_faux_mime_message(parser, response, content):
291299 return parser .parsestr (faux_message .decode ('utf-8' ))
292300
293301
294- def _unpack_batch_response (response , content ):
295- """Convert response, content -> [(headers, payload)].
302+ def _unpack_batch_response (response ):
303+ """Convert requests.Response -> [(headers, payload)].
296304
297305 Creates a generator of tuples of emulating the responses to
298306 :meth:`httplib2.Http.request` (a pair of headers and payload).
299307
300- :type response: :class:`httplib2 .Response`
308+ :type response: :class:`requests .Response`
301309 :param response: HTTP response / headers from a request.
302-
303- :type content: str
304- :param content: Response payload with a batch response.
305310 """
306311 parser = Parser ()
307- message = _generate_faux_mime_message (parser , response , content )
312+ message = _generate_faux_mime_message (parser , response )
308313
309314 if not isinstance (message ._payload , list ):
310315 raise ValueError ('Bad response: not multi-part' )
@@ -314,10 +319,15 @@ def _unpack_batch_response(response, content):
314319 _ , status , _ = status_line .split (' ' , 2 )
315320 sub_message = parser .parsestr (rest )
316321 payload = sub_message ._payload
317- ctype = sub_message ['Content-Type' ]
318322 msg_headers = dict (sub_message ._headers )
319- msg_headers ['status' ] = status
320- headers = httplib2 .Response (msg_headers )
321- if ctype and ctype .startswith ('application/json' ):
322- payload = json .loads (payload )
323- yield headers , payload
323+ content_id = msg_headers .get ('Content-ID' )
324+
325+ subresponse = requests .Response ()
326+ subresponse .request = requests .Request (
327+ method = 'BATCH' ,
328+ url = 'contentid://{}' .format (content_id )).prepare ()
329+ subresponse .status_code = int (status )
330+ subresponse .headers .update (msg_headers )
331+ subresponse ._content = payload .encode ('utf-8' )
332+
333+ yield subresponse
0 commit comments