def _process_response(self, resp, content): """Process the response from a single chunk upload. Args: resp: httplib2.Response, the response object. content: string, the content of the response. Returns: (status, body): (ResumableMediaStatus, object) The body will be None until the resumable media is fully uploaded. Raises: apiclient.errors.HttpError if the response was not a 2xx or a 308. """ if resp.status in [200, 201]: self._in_error_state = False return None, self.postproc(resp, content) elif resp.status == 308: self._in_error_state = False # A "308 Resume Incomplete" indicates we are not done. self.resumable_progress = int(resp['range'].split('-')[1]) + 1 if 'location' in resp: self.resumable_uri = resp['location'] else: self._in_error_state = True raise HttpError(resp, content, self.uri) return (MediaUploadProgress(self.resumable_progress, self.resumable.size()), None)
def build(serviceName, version, http=None, discoveryServiceUrl=DISCOVERY_URI, developerKey=None, model=None, requestBuilder=HttpRequest): """Construct a Resource for interacting with an API. Construct a Resource object for interacting with an API. The serviceName and version are the names from the Discovery service. Args: serviceName: string, name of the service version: string, the version of the service discoveryServiceUrl: string, a URI Template that points to the location of the discovery service. It should have two parameters {api} and {apiVersion} that when filled in produce an absolute URI to the discovery document for that service. developerKey: string, key obtained from https://code.google.com/apis/console model: apiclient.Model, converts to and from the wire format requestBuilder: apiclient.http.HttpRequest, encapsulator for an HTTP request Returns: A Resource object with methods for interacting with the service. """ params = {'api': serviceName, 'apiVersion': version} if http is None: http = httplib2.Http() requested_url = uritemplate.expand(discoveryServiceUrl, params) # REMOTE_ADDR is defined by the CGI spec [RFC3875] as the environment variable # that contains the network address of the client sending the request. If it # exists then add that to the request for the discovery document to avoid # exceeding the quota on discovery requests. if 'REMOTE_ADDR' in os.environ: requested_url = _add_query_parameter(requested_url, 'userIp', os.environ['REMOTE_ADDR']) logging.info('URL being requested: %s' % requested_url) resp, content = http.request(requested_url) if resp.status == 404: raise UnknownApiNameOrVersion("name: %s version: %s" % (serviceName, version)) if resp.status >= 400: raise HttpError(resp, content, requested_url) try: service = simplejson.loads(content) except ValueError, e: logging.error('Failed to parse as JSON: ' + content) raise InvalidJsonError()
def execute(self, http=None): """Execute the request. Args: http: httplib2.Http, an http object to be used in place of the one the HttpRequest request object was constructed with. Returns: A deserialized object model of the response body as determined by the postproc. Raises: apiclient.errors.HttpError if the response was not a 2xx. httplib2.Error if a transport error has occured. """ if http is None: http = self.http if self.resumable: body = None while body is None: _, body = self.next_chunk(http) return body else: resp, content = http.request(self.uri, self.method, body=self.body, headers=self.headers) if resp.status >= 300: raise HttpError(resp, content, self.uri) return self.postproc(resp, content)
def response(self, resp, content): """Convert the response wire format into a Python object. Args: resp: httplib2.Response, the HTTP response headers and status content: string, the body of the HTTP response Returns: The body de-serialized as a Python object. Raises: apiclient.errors.HttpError if a non 2xx response is received. """ # Error handling is TBD, for example, do we retry # for some operation/error combinations? if resp.status < 300: if resp.status == 204: # A 204: No Content response should be treated differently # to all the other success states return simplejson.loads('{}') body = simplejson.loads(content) if isinstance(body, dict) and 'data' in body: body = body['data'] return body else: logging.debug('Content from bad request was: %s' % content) raise HttpError(resp, content)
def _execute(self, http, order, requests): """Serialize batch request, send to server, process response. Args: http: httplib2.Http, an http object to be used to make the request with. order: list, list of request ids in the order they were added to the batch. request: list, list of request objects to send. Raises: httplib2.HttpLib2Error if a transport error has occured. apiclient.errors.BatchError if the response is the wrong format. """ message = MIMEMultipart('mixed') # Message should not write out it's own headers. setattr(message, '_write_headers', lambda self: None) # Add all the individual requests. for request_id in order: request = requests[request_id] msg = MIMENonMultipart('application', 'http') msg['Content-Transfer-Encoding'] = 'binary' msg['Content-ID'] = self._id_to_header(request_id) body = self._serialize_request(request) msg.set_payload(body) message.attach(msg) body = message.as_string() headers = {} headers['content-type'] = ('multipart/mixed; ' 'boundary="%s"') % message.get_boundary() resp, content = http.request(self._batch_uri, 'POST', body=body, headers=headers) if resp.status >= 300: raise HttpError(resp, content, uri=self._batch_uri) # Now break out the individual responses and store each one. boundary, _ = content.split(None, 1) # Prepend with a content-type header so FeedParser can handle it. header = 'content-type: %s\r\n\r\n' % resp['content-type'] for_parser = header + content parser = FeedParser() parser.feed(for_parser) mime_response = parser.close() if not mime_response.is_multipart(): raise BatchError("Response not in multipart/mixed format.", resp=resp, content=content) for part in mime_response.get_payload(): request_id = self._header_to_id(part['Content-ID']) response, content = self._deserialize_response(part.get_payload()) self._responses[request_id] = (response, content)
def response(self, resp, content): """Convert the response wire format into a Python object. Args: resp: httplib2.Response, the HTTP response headers and status content: string, the body of the HTTP response Returns: The body de-serialized as a Python object. Raises: apiclient.errors.HttpError if a non 2xx response is received. """ self._log_response(resp, content) # Error handling is TBD, for example, do we retry # for some operation/error combinations? if resp.status < 300: if resp.status == 204: # A 204: No Content response should be treated differently # to all the other success states return self.no_content_response return self.deserialize(content) else: logging.debug('Content from bad request was: %s' % content) raise HttpError(resp, content)
def subscribe_app(): subscription_endpoint = '/me/subscription' subscribe_uri = URIHandler(subscription_endpoint).configure() request = base.exec_request('POST', subscribe_uri) if request: return request else: raise HttpError('Unable to complete request.')
def validate_webhook(**kwargs): validation_endpoint = 'subscriptions_sample' validation_uri = URIHandler(validation_endpoint).configure( uri_format='appIDFirst', request_args=kwargs) request = base.exec_request('POST', validation_uri) if request: return request else: raise HttpError('Unable to complete request.')
def build(serviceName, version, http=None, discoveryServiceUrl=DISCOVERY_URI, developerKey=None, model=None, requestBuilder=HttpRequest): """Construct a Resource for interacting with an API. Construct a Resource object for interacting with an API. The serviceName and version are the names from the Discovery service. Args: serviceName: string, name of the service version: string, the version of the service discoveryServiceUrl: string, a URI Template that points to the location of the discovery service. It should have two parameters {api} and {apiVersion} that when filled in produce an absolute URI to the discovery document for that service. developerKey: string, key obtained from https://code.google.com/apis/console model: apiclient.Model, converts to and from the wire format requestBuilder: apiclient.http.HttpRequest, encapsulator for an HTTP request Returns: A Resource object with methods for interacting with the service. """ params = { 'api': serviceName, 'apiVersion': version } if http is None: http = httplib2.Http() requested_url = uritemplate.expand(discoveryServiceUrl, params) logging.info('URL being requested: %s' % requested_url) resp, content = http.request(requested_url) if resp.status > 400: raise HttpError(resp, content, requested_url) try: service = simplejson.loads(content) except ValueError, e: logging.error('Failed to parse as JSON: ' + content) raise InvalidJsonError()
def execute(self, http=None): """Execute the request. Args: http: httplib2.Http, an http object to be used in place of the one the HttpRequest request object was constructed with. Returns: A deserialized object model of the response body as determined by the postproc. Raises: apiclient.errors.HttpError if the response was not a 2xx. httplib2.HttpLib2Error if a transport error has occured. """ if http is None: http = self.http if self.resumable: body = None while body is None: _, body = self.next_chunk(http=http) return body else: if 'content-length' not in self.headers: self.headers['content-length'] = str(self.body_size) # If the request URI is too long then turn it into a POST request. if len(self.uri) > MAX_URI_LENGTH and self.method == 'GET': self.method = 'POST' self.headers['x-http-method-override'] = 'GET' self.headers['content-type'] = 'application/x-www-form-urlencoded' parsed = urlparse.urlparse(self.uri) self.uri = urlparse.urlunparse( (parsed.scheme, parsed.netloc, parsed.path, parsed.params, None, None) ) self.body = parsed.query self.headers['content-length'] = str(len(self.body)) resp, content = http.request(str(self.uri), method=str(self.method), body=self.body, headers=self.headers) for callback in self.response_callbacks: callback(resp) if resp.status >= 300: raise HttpError(resp, content, uri=self.uri) return self.postproc(resp, content)
def next_chunk(self): """Get the next chunk of the download. Returns: (status, done): (MediaDownloadStatus, boolean) The value of 'done' will be True when the media has been fully downloaded. Raises: apiclient.errors.HttpError if the response was not a 2xx. httplib2.Error if a transport error has occured. """ headers = { 'range': 'bytes=%d-%d' % (self.progress_, self.progress_ + self.chunksize_) } http = self.request_.http http.follow_redirects = False resp, content = http.request(self.uri_, headers=headers) if resp.status in [301, 302, 303, 307, 308] and 'location' in resp: self.uri_ = resp['location'] resp, content = http.request(self.uri_, headers=headers) if resp.status in [200, 206]: self.progress_ += len(content) self.fh_.write(content) if 'content-range' in resp: content_range = resp['content-range'] length = content_range.rsplit('/', 1)[1] self.total_size_ = int(length) if self.progress_ == self.total_size_: self.done_ = True return MediaDownloadProgress(self.progress_, self.total_size_), self.done_ else: raise HttpError(resp, content, self.uri_)
def execute(self, http=None): """Execute all the requests as a single batched HTTP request. Args: http: httplib2.Http, an http object to be used in place of the one the HttpRequest request object was constructed with. If one isn't supplied then use a http object from the requests in this batch. Returns: None Raises: apiclient.errors.HttpError if the response was not a 2xx. httplib2.Error if a transport error has occured. """ if http is None: for request_id in self._order: request, callback = self._requests[request_id] if request is not None: http = request.http break if http is None: raise ValueError("Missing a valid http object.") msgRoot = MIMEMultipart('mixed') # msgRoot should not write out it's own headers setattr(msgRoot, '_write_headers', lambda self: None) # Add all the individual requests. for request_id in self._order: request, callback = self._requests[request_id] msg = MIMENonMultipart('application', 'http') msg['Content-Transfer-Encoding'] = 'binary' msg['Content-ID'] = self._id_to_header(request_id) body = self._serialize_request(request) msg.set_payload(body) msgRoot.attach(msg) body = msgRoot.as_string() headers = {} headers['content-type'] = ('multipart/mixed; ' 'boundary="%s"') % msgRoot.get_boundary() resp, content = http.request(self._batch_uri, 'POST', body=body, headers=headers) if resp.status >= 300: raise HttpError(resp, content, self._batch_uri) # Now break up the response and process each one with the correct postproc # and trigger the right callbacks. boundary, _ = content.split(None, 1) # Prepend with a content-type header so FeedParser can handle it. header = 'Content-Type: %s\r\n\r\n' % resp['content-type'] content = header + content parser = FeedParser() parser.feed(content) respRoot = parser.close() if not respRoot.is_multipart(): raise BatchError("Response not in multipart/mixed format.") parts = respRoot.get_payload() for part in parts: request_id = self._header_to_id(part['Content-ID']) headers, content = self._deserialize_response(part.get_payload()) # TODO(jcgregorio) Remove this temporary hack once the server stops # gzipping individual response bodies. if content[0] != '{': gzipped_content = content content = gzip.GzipFile( fileobj=StringIO.StringIO(gzipped_content)).read() request, cb = self._requests[request_id] postproc = request.postproc response = postproc(resp, content) if cb is not None: cb(request_id, response) if self._callback is not None: self._callback(request_id, response)
def next_chunk(self, http=None): """Execute the next step of a resumable upload. Can only be used if the method being executed supports media uploads and the MediaUpload object passed in was flagged as using resumable upload. Example: media = MediaFileUpload('smiley.png', mimetype='image/png', chunksize=1000, resumable=True) request = service.objects().insert( bucket=buckets['items'][0]['id'], name='smiley.png', media_body=media) response = None while response is None: status, response = request.next_chunk() if status: print "Upload %d%% complete." % int(status.progress() * 100) Returns: (status, body): (ResumableMediaStatus, object) The body will be None until the resumable media is fully uploaded. """ if http is None: http = self.http if self.resumable_uri is None: start_headers = copy.copy(self.headers) start_headers['X-Upload-Content-Type'] = self.resumable.mimetype() start_headers['X-Upload-Content-Length'] = str( self.resumable.size()) start_headers['Content-Length'] = '0' resp, content = http.request(self.uri, self.method, body="", headers=start_headers) if resp.status == 200 and 'location' in resp: self.resumable_uri = resp['location'] else: raise ResumableUploadError("Failed to retrieve starting URI.") if self.body: begin = 0 data = self.body else: begin = self.resumable_progress - self.multipart_size data = self.resumable.getbytes(begin, self.resumable.chunksize()) # Tack on the multipart/related boundary if we are at the end of the file. if begin + self.resumable.chunksize() >= self.resumable.size(): data += self.multipart_boundary headers = { 'Content-Range': 'bytes %d-%d/%d' % (self.resumable_progress, self.resumable_progress + len(data) - 1, self.total_size), } resp, content = http.request(self.resumable_uri, 'PUT', body=data, headers=headers) if resp.status in [200, 201]: return None, self.postproc(resp, content) elif resp.status == 308: # A "308 Resume Incomplete" indicates we are not done. self.resumable_progress = int(resp['range'].split('-')[1]) + 1 if self.resumable_progress >= self.multipart_size: self.body = None if 'location' in resp: self.resumable_uri = resp['location'] else: raise HttpError(resp, content, self.uri) return MediaUploadProgress(self.resumable_progress, self.total_size), None
def get_text(self): request = base.exec_request('GET', self.graphAPIURLGET) if request: return request else: raise HttpError('Unable to complete request.')
def set_text(self, payload): request = base.exec_request('POST', self.graphAPIURL, data=payload) if request: print(request) else: raise HttpError('Unable to complete request.')
def delete_message(self): request = base.exec_request('DELETE', self.graphAPIURLGET) if request: return request else: raise HttpError('Unable to complete request.')
def execute(self, http=None): """Execute all the requests as a single batched HTTP request. Args: http: httplib2.Http, an http object to be used in place of the one the HttpRequest request object was constructed with. If one isn't supplied then use a http object from the requests in this batch. Returns: None Raises: httplib2.HttpLib2Error if a transport error has occured. apiclient.errors.BatchError if the response is the wrong format. """ # If http is not supplied use the first valid one given in the requests. if http is None: for request_id in self._order: request = self._requests[request_id] if request is not None: http = request.http break if http is None: raise ValueError("Missing a valid http object.") self._execute(http, self._order, self._requests) # Loop over all the requests and check for 401s. For each 401 request the # credentials should be refreshed and then sent again in a separate batch. redo_requests = {} redo_order = [] for request_id in self._order: resp, content = self._responses[request_id] if resp['status'] == '401': redo_order.append(request_id) request = self._requests[request_id] self._refresh_and_apply_credentials(request, http) redo_requests[request_id] = request if redo_requests: self._execute(http, redo_order, redo_requests) # Now process all callbacks that are erroring, and raise an exception for # ones that return a non-2xx response? Or add extra parameter to callback # that contains an HttpError? for request_id in self._order: resp, content = self._responses[request_id] request = self._requests[request_id] callback = self._callbacks[request_id] response = None exception = None try: if resp.status >= 300: raise HttpError(resp, content, uri=request.uri) response = request.postproc(resp, content) except HttpError, e: exception = e if callback is not None: callback(request_id, response, exception) if self._callback is not None: self._callback(request_id, response, exception)