docs: def request(url, args=None, data=None, headers=None, method=None,
                credentials=None, raw_response=False, stats=None):
      """Issues HTTP requests.
  
      Args:
        url: the URL to request.
        args: optional query string arguments.
        data: optional data to be sent within the request.
        headers: optional headers to include in the request.
        method: optional HTTP method to use. If unspecified this is inferred
            (GET or POST) based on the existence of request data.
        credentials: optional set of credentials to authorize the request.
        raw_response: whether the raw response content should be returned as-is.
        stats: an optional dictionary that, if provided, will be populated with some
            useful info about the request, like 'duration' in seconds and 'data_size' in
            bytes. These may be useful optimizing the access to rate-limited APIs.
      Returns:
        The parsed response object.
      Raises:
        Exception when the HTTP request fails or the response cannot be processed.
      """
      if headers is None:
        headers = {}
  
      headers['user-agent'] = 'GoogleCloudDataLab/1.0'
      # Add querystring to the URL if there are any arguments.
      if args is not None:
        qs = urllib.parse.urlencode(args)
        url = url + '?' + qs
  
      # Setup method to POST if unspecified, and appropriate request headers
      # if there is data to be sent within the request.
      if data is not None:
        if method is None:
          method = 'POST'
  
        if data != '':
          # If there is a content type specified, use it (and the data) as-is.
          # Otherwise, assume JSON, and serialize the data object.
          if 'Content-Type' not in headers:
            data = json.dumps(data)
            headers['Content-Type'] = 'application/json'
        headers['Content-Length'] = str(len(data))
      else:
        if method == 'POST':
          headers['Content-Length'] = '0'
  
      # If the method is still unset, i.e. it was unspecified, and there
      # was no data to be POSTed, then default to GET request.
      if method is None:
        method = 'GET'
  
      http = Http.http
  
      # Authorize with credentials if given
      if credentials is not None:
        # Make a copy of the shared http instance before we modify it.
        http = copy.copy(http)
        http = google_auth_httplib2.AuthorizedHttp(credentials)
      if stats is not None:
        stats['duration'] = datetime.datetime.utcnow()
  
      response = None
      try:
        log.debug('request: method[%(method)s], url[%(url)s], body[%(data)s]' % locals())
        response, content = http.request(url,
                                         method=method,
                                         body=data,
                                         headers=headers)
        if 200 <= response.status < 300:
          if raw_response:
            return content
          if type(content) == str:
            return json.loads(content)
          else:
            return json.loads(str(content, encoding='UTF-8'))
        else:
          raise RequestException(response.status, content)
      except ValueError:
        raise Exception('Failed to process HTTP response.')
      except httplib2.HttpLib2Error:
        raise Exception('Failed to send HTTP request.')
      finally:
        if stats is not None:
          stats['data_size'] = len(data)
          stats['status'] = response.status
          stats['duration'] = (datetime.datetime.utcnow() - stats['duration']).total_seconds()