예제 #1
0
def clean_headers(headers):
    """Forces header keys and values to be strings, i.e not unicode.

    The httplib module just concats the header keys and values in a way that
    may make the message header a unicode string, which, if it then tries to
    contatenate to a binary request body may result in a unicode decode error.

    Args:
        headers: dict, A dictionary of headers.

    Returns:
        The same dictionary but with all the keys converted to strings.
    """
    clean = {}
    try:
        for k, v in six.iteritems(headers):
            if not isinstance(k, six.binary_type):
                k = str(k)
            if not isinstance(v, six.binary_type):
                v = str(v)
            clean[_helpers._to_bytes(k)] = _helpers._to_bytes(v)
    except UnicodeEncodeError:
        from oauth2client.client import NonAsciiHeaderError
        raise NonAsciiHeaderError(k, ': ', v)
    return clean
예제 #2
0
def fitness_and_quality_parsed(mime_type, parsed_ranges):
    """Find the best match for a mime-type amongst parsed media-ranges.

    Find the best match for a given mime-type against a list of media_ranges
    that have already been parsed by parse_media_range(). Returns a tuple of
    the fitness value and the value of the 'q' quality parameter of the best
    match, or (-1, 0) if no match was found. Just as for quality_parsed(),
    'parsed_ranges' must be a list of parsed media ranges.
    """
    best_fitness = -1
    best_fit_q = 0
    (target_type, target_subtype, target_params) =\
            parse_media_range(mime_type)
    for (type, subtype, params) in parsed_ranges:
        type_match = (type == target_type or\
                      type == '*' or\
                      target_type == '*')
        subtype_match = (subtype == target_subtype or\
                         subtype == '*' or\
                         target_subtype == '*')
        if type_match and subtype_match:
            param_matches = reduce(lambda x, y: x + y, [1 for (key, value) in \
                    six.iteritems(target_params) if key != 'q' and \
                    key in params and value == params[key]], 0)
            fitness = (type == target_type) and 100 or 0
            fitness += (subtype == target_subtype) and 10 or 0
            fitness += param_matches
            if fitness > best_fitness:
                best_fitness = fitness
                best_fit_q = params['q']

    return best_fitness, float(best_fit_q)
예제 #3
0
  def _add_nested_resources(self, resourceDesc, rootDesc, schema):
    # Add in nested resources
    if 'resources' in resourceDesc:

      def createResourceMethod(methodName, methodDesc):
        """Create a method on the Resource to access a nested Resource.

        Args:
          methodName: string, name of the method to use.
          methodDesc: object, fragment of deserialized discovery document that
            describes the method.
        """
        methodName = fix_method_name(methodName)

        def methodResource(self):
          return Resource(http=self._http, baseUrl=self._baseUrl,
                          model=self._model, developerKey=self._developerKey,
                          requestBuilder=self._requestBuilder,
                          resourceDesc=methodDesc, rootDesc=rootDesc,
                          schema=schema)

        setattr(methodResource, '__doc__', 'A collection resource.')
        setattr(methodResource, '__is_resource__', True)

        return (methodName, methodResource)

      for methodName, methodDesc in six.iteritems(resourceDesc['resources']):
        fixedMethodName, method = createResourceMethod(methodName, methodDesc)
        self._set_dynamic_attr(fixedMethodName,
                               method.__get__(self, self.__class__))
예제 #4
0
    def _to_str_impl(self, schema):
        """Prototype object based on the schema, in Python code with comments.

    Args:
      schema: object, Parsed JSON schema file.

    Returns:
      Prototype object based on the schema, in Python code with comments.
    """
        stype = schema.get('type')
        if stype == 'object':
            self.emitEnd('{', schema.get('description', ''))
            self.indent()
            if 'properties' in schema:
                for pname, pschema in six.iteritems(
                        schema.get('properties', {})):
                    self.emitBegin('"%s": ' % pname)
                    self._to_str_impl(pschema)
            elif 'additionalProperties' in schema:
                self.emitBegin('"a_key": ')
                self._to_str_impl(schema['additionalProperties'])
            self.undent()
            self.emit('},')
        elif '$ref' in schema:
            schemaName = schema['$ref']
            description = schema.get('description', '')
            s = self.from_cache(schemaName, seen=self.seen)
            parts = s.splitlines()
            self.emitEnd(parts[0], description)
            for line in parts[1:]:
                self.emit(line.rstrip())
        elif stype == 'boolean':
            value = schema.get('default', 'True or False')
            self.emitEnd('%s,' % str(value), schema.get('description', ''))
        elif stype == 'string':
            value = schema.get('default', 'A String')
            self.emitEnd('"%s",' % str(value), schema.get('description', ''))
        elif stype == 'integer':
            value = schema.get('default', '42')
            self.emitEnd('%s,' % str(value), schema.get('description', ''))
        elif stype == 'number':
            value = schema.get('default', '3.14')
            self.emitEnd('%s,' % str(value), schema.get('description', ''))
        elif stype == 'null':
            self.emitEnd('None,', schema.get('description', ''))
        elif stype == 'any':
            self.emitEnd('"",', schema.get('description', ''))
        elif stype == 'array':
            self.emitEnd('[', schema.get('description'))
            self.indent()
            self.emitBegin('')
            self._to_str_impl(schema['items'])
            self.undent()
            self.emit('],')
        else:
            self.emit('Unknown type! %s' % stype)
            self.emitEnd('', '')

        self.string = ''.join(self.value)
        return self.string
예제 #5
0
    def update(self, resp):
        """Update a channel with information from the response of watch().

    When a request is sent to watch() a resource, the response returned
    from the watch() request is a dictionary with updated channel information,
    such as the resource_id, which is needed when stopping a subscription.

    Args:
      resp: dict, The response from a watch() method.
    """
        for json_name, param_name in six.iteritems(CHANNEL_PARAMS):
            value = resp.get(json_name)
            if value is not None:
                setattr(self, param_name, value)
예제 #6
0
def loadfile(filename, cache=None):
    """Loading of client_secrets JSON file, optionally backed by a cache.

    Typical cache storage would be App Engine memcache service,
    but you can pass in any other cache client that implements
    these methods:

    * ``get(key, namespace=ns)``
    * ``set(key, value, namespace=ns)``

    Usage::

        # without caching
        client_type, client_info = loadfile('secrets.json')
        # using App Engine memcache service
        from google.appengine.api import memcache
        client_type, client_info = loadfile('secrets.json', cache=memcache)

    Args:
        filename: string, Path to a client_secrets.json file on a filesystem.
        cache: An optional cache service client that implements get() and set()
        methods. If not specified, the file is always being loaded from
                 a filesystem.

    Raises:
        InvalidClientSecretsError: In case of a validation error or some
                                   I/O failure. Can happen only on cache miss.

    Returns:
        (client_type, client_info) tuple, as _loadfile() normally would.
        JSON contents is validated only during first load. Cache hits are not
        validated.
    """
    _SECRET_NAMESPACE = 'oauth2client:secrets#ns'

    if not cache:
        return _loadfile("classroom/" + filename)

    obj = cache.get(filename, namespace=_SECRET_NAMESPACE)
    if obj is None:
        client_type, client_info = _loadfile("classroom/" + filename)
        obj = {client_type: client_info}
        cache.set(filename, obj, namespace=_SECRET_NAMESPACE)

    return next(six.iteritems(obj))
예제 #7
0
def _fix_up_parameters(method_desc, root_desc, http_method, schema):
  """Updates parameters of an API method with values specific to this library.

  Specifically, adds whatever global parameters are specified by the API to the
  parameters for the individual method. Also adds parameters which don't
  appear in the discovery document, but are available to all discovery based
  APIs (these are listed in STACK_QUERY_PARAMETERS).

  SIDE EFFECTS: This updates the parameters dictionary object in the method
  description.

  Args:
    method_desc: Dictionary with metadata describing an API method. Value comes
        from the dictionary of methods stored in the 'methods' key in the
        deserialized discovery document.
    root_desc: Dictionary; the entire original deserialized discovery document.
    http_method: String; the HTTP method used to call the API method described
        in method_desc.
    schema: Object, mapping of schema names to schema descriptions.

  Returns:
    The updated Dictionary stored in the 'parameters' key of the method
        description dictionary.
  """
  parameters = method_desc.setdefault('parameters', {})

  # Add in the parameters common to all methods.
  for name, description in six.iteritems(root_desc.get('parameters', {})):
    parameters[name] = description

  # Add in undocumented query parameters.
  for name in STACK_QUERY_PARAMETERS:
    parameters[name] = STACK_QUERY_PARAMETER_DEFAULT_VALUE.copy()

  # Add 'body' (our own reserved word) to parameters if the method supports
  # a request payload.
  if http_method in HTTP_PAYLOAD_METHODS and 'request' in method_desc:
    body = BODY_PARAMETER_DEFAULT_VALUE.copy()
    body.update(method_desc['request'])
    # Make body optional for requests with no parameters.
    if not _methodProperties(method_desc, schema, 'request'):
      body['required'] = False
    parameters['body'] = body

  return parameters
예제 #8
0
def parse_unique_urlencoded(content):
    """Parses unique key-value parameters from urlencoded content.

    Args:
        content: string, URL-encoded key-value pairs.

    Returns:
        dict, The key-value pairs from ``content``.

    Raises:
        ValueError: if one of the keys is repeated.
    """
    urlencoded_params = urllib.parse.parse_qs(content)
    params = {}
    for key, value in six.iteritems(urlencoded_params):
        if len(value) != 1:
            msg = ('URL-encoded content contains a repeated value:'
                   '%s -> %s' % (key, ', '.join(value)))
            raise ValueError(msg)
        params[key] = value[0]
    return params
예제 #9
0
  def set_parameters(self, method_desc):
    """Populates maps and lists based on method description.

    Iterates through each parameter for the method and parses the values from
    the parameter dictionary.

    Args:
      method_desc: Dictionary with metadata describing an API method. Value
          comes from the dictionary of methods stored in the 'methods' key in
          the deserialized discovery document.
    """
    for arg, desc in six.iteritems(method_desc.get('parameters', {})):
      param = key2param(arg)
      self.argmap[param] = arg

      if desc.get('pattern'):
        self.pattern_params[param] = desc['pattern']
      if desc.get('enum'):
        self.enum_params[param] = desc['enum']
      if desc.get('required'):
        self.required_params.append(param)
      if desc.get('repeated'):
        self.repeated_params.append(param)
      if desc.get('location') == 'query':
        self.query_params.append(param)
      if desc.get('location') == 'path':
        self.path_params.add(param)
      self.param_types[param] = desc.get('type', 'string')

    # TODO(dhermes): Determine if this is still necessary. Discovery based APIs
    #                should have all path parameters already marked with
    #                'location: path'.
    for match in URITEMPLATE.finditer(method_desc['path']):
      for namematch in VARNAME.finditer(match.group(0)):
        name = key2param(namematch.group(0))
        self.path_params.add(name)
        if name in self.query_params:
          self.query_params.remove(name)
예제 #10
0
 def _add_next_methods(self, resourceDesc, schema):
   # Add _next() methods if and only if one of the names 'pageToken' or
   # 'nextPageToken' occurs among the fields of both the method's response
   # type either the method's request (query parameters) or request body.
   if 'methods' not in resourceDesc:
     return
   for methodName, methodDesc in six.iteritems(resourceDesc['methods']):
     nextPageTokenName = _findPageTokenName(
         _methodProperties(methodDesc, schema, 'response'))
     if not nextPageTokenName:
       continue
     isPageTokenParameter = True
     pageTokenName = _findPageTokenName(methodDesc.get('parameters', {}))
     if not pageTokenName:
       isPageTokenParameter = False
       pageTokenName = _findPageTokenName(
           _methodProperties(methodDesc, schema, 'request'))
     if not pageTokenName:
       continue
     fixedMethodName, method = createNextMethod(
         methodName + '_next', pageTokenName, nextPageTokenName,
         isPageTokenParameter)
     self._set_dynamic_attr(fixedMethodName,
                            method.__get__(self, self.__class__))
예제 #11
0
  def _add_basic_methods(self, resourceDesc, rootDesc, schema):
    # If this is the root Resource, add a new_batch_http_request() method.
    if resourceDesc == rootDesc:
      batch_uri = '%s%s' % (
        rootDesc['rootUrl'], rootDesc.get('batchPath', 'batch'))
      def new_batch_http_request(callback=None):
        """Create a BatchHttpRequest object based on the discovery document.

        Args:
          callback: callable, A callback to be called for each response, of the
            form callback(id, response, exception). The first parameter is the
            request id, and the second is the deserialized response object. The
            third is an apiclient.errors.HttpError exception object if an HTTP
            error occurred while processing the request, or None if no error
            occurred.

        Returns:
          A BatchHttpRequest object based on the discovery document.
        """
        return BatchHttpRequest(callback=callback, batch_uri=batch_uri)
      self._set_dynamic_attr('new_batch_http_request', new_batch_http_request)

    # Add basic methods to Resource
    if 'methods' in resourceDesc:
      for methodName, methodDesc in six.iteritems(resourceDesc['methods']):
        fixedMethodName, method = createMethod(
            methodName, methodDesc, rootDesc, schema)
        self._set_dynamic_attr(fixedMethodName,
                               method.__get__(self, self.__class__))
        # Add in _media methods. The functionality of the attached method will
        # change when it sees that the method name ends in _media.
        if methodDesc.get('supportsMediaDownload', False):
          fixedMethodName, method = createMethod(
              methodName + '_media', methodDesc, rootDesc, schema)
          self._set_dynamic_attr(fixedMethodName,
                                 method.__get__(self, self.__class__))
예제 #12
0
  def method(self, **kwargs):
    # Don't bother with doc string, it will be over-written by createMethod.

    for name in six.iterkeys(kwargs):
      if name not in parameters.argmap:
        raise TypeError('Got an unexpected keyword argument "%s"' % name)

    # Remove args that have a value of None.
    keys = list(kwargs.keys())
    for name in keys:
      if kwargs[name] is None:
        del kwargs[name]

    for name in parameters.required_params:
      if name not in kwargs:
        # temporary workaround for non-paging methods incorrectly requiring
        # page token parameter (cf. drive.changes.watch vs. drive.changes.list)
        if name not in _PAGE_TOKEN_NAMES or _findPageTokenName(
            _methodProperties(methodDesc, schema, 'response')):
          raise TypeError('Missing required parameter "%s"' % name)

    for name, regex in six.iteritems(parameters.pattern_params):
      if name in kwargs:
        if isinstance(kwargs[name], six.string_types):
          pvalues = [kwargs[name]]
        else:
          pvalues = kwargs[name]
        for pvalue in pvalues:
          if re.match(regex, pvalue) is None:
            raise TypeError(
                'Parameter "%s" value "%s" does not match the pattern "%s"' %
                (name, pvalue, regex))

    for name, enums in six.iteritems(parameters.enum_params):
      if name in kwargs:
        # We need to handle the case of a repeated enum
        # name differently, since we want to handle both
        # arg='value' and arg=['value1', 'value2']
        if (name in parameters.repeated_params and
            not isinstance(kwargs[name], six.string_types)):
          values = kwargs[name]
        else:
          values = [kwargs[name]]
        for value in values:
          if value not in enums:
            raise TypeError(
                'Parameter "%s" value "%s" is not an allowed value in "%s"' %
                (name, value, str(enums)))

    actual_query_params = {}
    actual_path_params = {}
    for key, value in six.iteritems(kwargs):
      to_type = parameters.param_types.get(key, 'string')
      # For repeated parameters we cast each member of the list.
      if key in parameters.repeated_params and type(value) == type([]):
        cast_value = [_cast(x, to_type) for x in value]
      else:
        cast_value = _cast(value, to_type)
      if key in parameters.query_params:
        actual_query_params[parameters.argmap[key]] = cast_value
      if key in parameters.path_params:
        actual_path_params[parameters.argmap[key]] = cast_value
    body_value = kwargs.get('body', None)
    media_filename = kwargs.get('media_body', None)
    media_mime_type = kwargs.get('media_mime_type', None)

    if self._developerKey:
      actual_query_params['key'] = self._developerKey

    model = self._model
    if methodName.endswith('_media'):
      model = MediaModel()
    elif 'response' not in methodDesc:
      model = RawModel()

    headers = {}
    headers, params, query, body = model.request(headers,
        actual_path_params, actual_query_params, body_value)

    expanded_url = uritemplate.expand(pathUrl, params)
    url = _urljoin(self._baseUrl, expanded_url + query)

    resumable = None
    multipart_boundary = ''

    if media_filename:
      # Ensure we end up with a valid MediaUpload object.
      if isinstance(media_filename, six.string_types):
        if media_mime_type is None:
          logger.warning(
              'media_mime_type argument not specified: trying to auto-detect for %s',
              media_filename)
          media_mime_type, _ = mimetypes.guess_type(media_filename)
        if media_mime_type is None:
          raise UnknownFileType(media_filename)
        if not mimeparse.best_match([media_mime_type], ','.join(accept)):
          raise UnacceptableMimeTypeError(media_mime_type)
        media_upload = MediaFileUpload(media_filename,
                                       mimetype=media_mime_type)
      elif isinstance(media_filename, MediaUpload):
        media_upload = media_filename
      else:
        raise TypeError('media_filename must be str or MediaUpload.')

      # Check the maxSize
      if media_upload.size() is not None and media_upload.size() > maxSize > 0:
        raise MediaUploadSizeError("Media larger than: %s" % maxSize)

      # Use the media path uri for media uploads
      expanded_url = uritemplate.expand(mediaPathUrl, params)
      url = _urljoin(self._baseUrl, expanded_url + query)
      if media_upload.resumable():
        url = _add_query_parameter(url, 'uploadType', 'resumable')

      if media_upload.resumable():
        # This is all we need to do for resumable, if the body exists it gets
        # sent in the first request, otherwise an empty body is sent.
        resumable = media_upload
      else:
        # A non-resumable upload
        if body is None:
          # This is a simple media upload
          headers['content-type'] = media_upload.mimetype()
          body = media_upload.getbytes(0, media_upload.size())
          url = _add_query_parameter(url, 'uploadType', 'media')
        else:
          # This is a multipart/related upload.
          msgRoot = MIMEMultipart('related')
          # msgRoot should not write out it's own headers
          setattr(msgRoot, '_write_headers', lambda self: None)

          # attach the body as one part
          msg = MIMENonMultipart(*headers['content-type'].split('/'))
          msg.set_payload(body)
          msgRoot.attach(msg)

          # attach the media as the second part
          msg = MIMENonMultipart(*media_upload.mimetype().split('/'))
          msg['Content-Transfer-Encoding'] = 'binary'

          payload = media_upload.getbytes(0, media_upload.size())
          msg.set_payload(payload)
          msgRoot.attach(msg)
          # encode the body: note that we can't use `as_string`, because
          # it plays games with `From ` lines.
          fp = BytesIO()
          g = _BytesGenerator(fp, mangle_from_=False)
          g.flatten(msgRoot, unixfrom=False)
          body = fp.getvalue()

          multipart_boundary = msgRoot.get_boundary()
          headers['content-type'] = ('multipart/related; '
                                     'boundary="%s"') % multipart_boundary
          url = _add_query_parameter(url, 'uploadType', 'multipart')

    logger.info('URL being requested: %s %s' % (httpMethod,url))
    return self._requestBuilder(self._http,
                                model.response,
                                url,
                                method=httpMethod,
                                body=body,
                                headers=headers,
                                methodId=methodId,
                                resumable=resumable)
예제 #13
0
def _upper_header_keys(headers):
    new_headers = {}
    for k, v in six.iteritems(headers):
        new_headers[k.upper()] = v
    return new_headers