Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(560)

Unified Diff: tools/telemetry/third_party/gsutilz/third_party/protorpc/protorpc/protourlencode.py

Issue 1264873003: Add gsutil/third_party to telemetry/third_party/gsutilz/third_party. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Remove httplib2 Created 5 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: tools/telemetry/third_party/gsutilz/third_party/protorpc/protorpc/protourlencode.py
diff --git a/tools/telemetry/third_party/gsutilz/third_party/protorpc/protorpc/protourlencode.py b/tools/telemetry/third_party/gsutilz/third_party/protorpc/protorpc/protourlencode.py
new file mode 100755
index 0000000000000000000000000000000000000000..9f6059e090178f6770a9c84f2a2b7af0a1171585
--- /dev/null
+++ b/tools/telemetry/third_party/gsutilz/third_party/protorpc/protorpc/protourlencode.py
@@ -0,0 +1,563 @@
+#!/usr/bin/env python
+#
+# Copyright 2010 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+"""URL encoding support for messages types.
+
+Protocol support for URL encoded form parameters.
+
+Nested Fields:
+ Nested fields are repesented by dot separated names. For example, consider
+ the following messages:
+
+ class WebPage(Message):
+
+ title = StringField(1)
+ tags = StringField(2, repeated=True)
+
+ class WebSite(Message):
+
+ name = StringField(1)
+ home = MessageField(WebPage, 2)
+ pages = MessageField(WebPage, 3, repeated=True)
+
+ And consider the object:
+
+ page = WebPage()
+ page.title = 'Welcome to NewSite 2010'
+
+ site = WebSite()
+ site.name = 'NewSite 2010'
+ site.home = page
+
+ The URL encoded representation of this constellation of objects is.
+
+ name=NewSite+2010&home.title=Welcome+to+NewSite+2010
+
+ An object that exists but does not have any state can be represented with
+ a reference to its name alone with no value assigned to it. For example:
+
+ page = WebSite()
+ page.name = 'My Empty Site'
+ page.home = WebPage()
+
+ is represented as:
+
+ name=My+Empty+Site&home=
+
+ This represents a site with an empty uninitialized home page.
+
+Repeated Fields:
+ Repeated fields are represented by the name of and the index of each value
+ separated by a dash. For example, consider the following message:
+
+ home = Page()
+ home.title = 'Nome'
+
+ news = Page()
+ news.title = 'News'
+ news.tags = ['news', 'articles']
+
+ instance = WebSite()
+ instance.name = 'Super fun site'
+ instance.pages = [home, news, preferences]
+
+ An instance of this message can be represented as:
+
+ name=Super+fun+site&page-0.title=Home&pages-1.title=News&...
+ pages-1.tags-0=new&pages-1.tags-1=articles
+
+Helper classes:
+
+ URLEncodedRequestBuilder: Used for encapsulating the logic used for building
+ a request message from a URL encoded RPC.
+"""
+import six
+
+__author__ = 'rafek@google.com (Rafe Kaplan)'
+
+import cgi
+import re
+import urllib
+
+from . import message_types
+from . import messages
+from . import util
+
+__all__ = ['CONTENT_TYPE',
+ 'URLEncodedRequestBuilder',
+ 'encode_message',
+ 'decode_message',
+ ]
+
+CONTENT_TYPE = 'application/x-www-form-urlencoded'
+
+_FIELD_NAME_REGEX = re.compile(r'^([a-zA-Z_][a-zA-Z_0-9]*)(?:-([0-9]+))?$')
+
+
+class URLEncodedRequestBuilder(object):
+ """Helper that encapsulates the logic used for building URL encoded messages.
+
+ This helper is used to map query parameters from a URL encoded RPC to a
+ message instance.
+ """
+
+ @util.positional(2)
+ def __init__(self, message, prefix=''):
+ """Constructor.
+
+ Args:
+ message: Message instance to build from parameters.
+ prefix: Prefix expected at the start of valid parameters.
+ """
+ self.__parameter_prefix = prefix
+
+ # The empty tuple indicates the root message, which has no path.
+ # __messages is a full cache that makes it very easy to look up message
+ # instances by their paths. See make_path for details about what a path
+ # is.
+ self.__messages = {(): message}
+
+ # This is a cache that stores paths which have been checked for
+ # correctness. Correctness means that an index is present for repeated
+ # fields on the path and absent for non-repeated fields. The cache is
+ # also used to check that indexes are added in the right order so that
+ # dicontiguous ranges of indexes are ignored.
+ self.__checked_indexes = set([()])
+
+ def make_path(self, parameter_name):
+ """Parse a parameter name and build a full path to a message value.
+
+ The path of a method is a tuple of 2-tuples describing the names and
+ indexes within repeated fields from the root message (the message being
+ constructed by the builder) to an arbitrarily nested message within it.
+
+ Each 2-tuple node of a path (name, index) is:
+ name: The name of the field that refers to the message instance.
+ index: The index within a repeated field that refers to the message
+ instance, None if not a repeated field.
+
+ For example, consider:
+
+ class VeryInner(messages.Message):
+ ...
+
+ class Inner(messages.Message):
+
+ very_inner = messages.MessageField(VeryInner, 1, repeated=True)
+
+ class Outer(messages.Message):
+
+ inner = messages.MessageField(Inner, 1)
+
+ If this builder is building an instance of Outer, that instance is
+ referred to in the URL encoded parameters without a path. Therefore
+ its path is ().
+
+ The child 'inner' is referred to by its path (('inner', None)).
+
+ The first child of repeated field 'very_inner' on the Inner instance
+ is referred to by (('inner', None), ('very_inner', 0)).
+
+ Examples:
+ # Correct reference to model where nation is a Message, district is
+ # repeated Message and county is any not repeated field type.
+ >>> make_path('nation.district-2.county')
+ (('nation', None), ('district', 2), ('county', None))
+
+ # Field is not part of model.
+ >>> make_path('nation.made_up_field')
+ None
+
+ # nation field is not repeated and index provided.
+ >>> make_path('nation-1')
+ None
+
+ # district field is repeated and no index provided.
+ >>> make_path('nation.district')
+ None
+
+ Args:
+ parameter_name: Name of query parameter as passed in from the request.
+ in order to make a path, this parameter_name must point to a valid
+ field within the message structure. Nodes of the path that refer to
+ repeated fields must be indexed with a number, non repeated nodes must
+ not have an index.
+
+ Returns:
+ Parsed version of the parameter_name as a tuple of tuples:
+ attribute: Name of attribute associated with path.
+ index: Postitive integer index when it is a repeated field, else None.
+ Will return None if the parameter_name does not have the right prefix,
+ does not point to a field within the message structure, does not have
+ an index if it is a repeated field or has an index but is not a repeated
+ field.
+ """
+ if parameter_name.startswith(self.__parameter_prefix):
+ parameter_name = parameter_name[len(self.__parameter_prefix):]
+ else:
+ return None
+
+ path = []
+ name = []
+ message_type = type(self.__messages[()]) # Get root message.
+
+ for item in parameter_name.split('.'):
+ # This will catch sub_message.real_message_field.not_real_field
+ if not message_type:
+ return None
+
+ item_match = _FIELD_NAME_REGEX.match(item)
+ if not item_match:
+ return None
+ attribute = item_match.group(1)
+ index = item_match.group(2)
+ if index:
+ index = int(index)
+
+ try:
+ field = message_type.field_by_name(attribute)
+ except KeyError:
+ return None
+
+ if field.repeated != (index is not None):
+ return None
+
+ if isinstance(field, messages.MessageField):
+ message_type = field.message_type
+ else:
+ message_type = None
+
+ # Path is valid so far. Append node and continue.
+ path.append((attribute, index))
+
+ return tuple(path)
+
+ def __check_index(self, parent_path, name, index):
+ """Check correct index use and value relative to a given path.
+
+ Check that for a given path the index is present for repeated fields
+ and that it is in range for the existing list that it will be inserted
+ in to or appended to.
+
+ Args:
+ parent_path: Path to check against name and index.
+ name: Name of field to check for existance.
+ index: Index to check. If field is repeated, should be a number within
+ range of the length of the field, or point to the next item for
+ appending.
+ """
+ # Don't worry about non-repeated fields.
+ # It's also ok if index is 0 because that means next insert will append.
+ if not index:
+ return True
+
+ parent = self.__messages.get(parent_path, None)
+ value_list = getattr(parent, name, None)
+ # If the list does not exist then the index should be 0. Since it is
+ # not, path is not valid.
+ if not value_list:
+ return False
+
+ # The index must either point to an element of the list or to the tail.
+ return len(value_list) >= index
+
+ def __check_indexes(self, path):
+ """Check that all indexes are valid and in the right order.
+
+ This method must iterate over the path and check that all references
+ to indexes point to an existing message or to the end of the list, meaning
+ the next value should be appended to the repeated field.
+
+ Args:
+ path: Path to check indexes for. Tuple of 2-tuples (name, index). See
+ make_path for more information.
+
+ Returns:
+ True if all the indexes of the path are within range, else False.
+ """
+ if path in self.__checked_indexes:
+ return True
+
+ # Start with the root message.
+ parent_path = ()
+
+ for name, index in path:
+ next_path = parent_path + ((name, index),)
+ # First look in the checked indexes cache.
+ if next_path not in self.__checked_indexes:
+ if not self.__check_index(parent_path, name, index):
+ return False
+ self.__checked_indexes.add(next_path)
+
+ parent_path = next_path
+
+ return True
+
+ def __get_or_create_path(self, path):
+ """Get a message from the messages cache or create it and add it.
+
+ This method will also create any parent messages based on the path.
+
+ When a new instance of a given message is created, it is stored in
+ __message by its path.
+
+ Args:
+ path: Path of message to get. Path must be valid, in other words
+ __check_index(path) returns true. Tuple of 2-tuples (name, index).
+ See make_path for more information.
+
+ Returns:
+ Message instance if the field being pointed to by the path is a
+ message, else will return None for non-message fields.
+ """
+ message = self.__messages.get(path, None)
+ if message:
+ return message
+
+ parent_path = ()
+ parent = self.__messages[()] # Get the root object
+
+ for name, index in path:
+ field = parent.field_by_name(name)
+ next_path = parent_path + ((name, index),)
+ next_message = self.__messages.get(next_path, None)
+ if next_message is None:
+ next_message = field.message_type()
+ self.__messages[next_path] = next_message
+ if not field.repeated:
+ setattr(parent, field.name, next_message)
+ else:
+ list_value = getattr(parent, field.name, None)
+ if list_value is None:
+ setattr(parent, field.name, [next_message])
+ else:
+ list_value.append(next_message)
+
+ parent_path = next_path
+ parent = next_message
+
+ return parent
+
+ def add_parameter(self, parameter, values):
+ """Add a single parameter.
+
+ Adds a single parameter and its value to the request message.
+
+ Args:
+ parameter: Query string parameter to map to request.
+ values: List of values to assign to request message.
+
+ Returns:
+ True if parameter was valid and added to the message, else False.
+
+ Raises:
+ DecodeError if the parameter refers to a valid field, and the values
+ parameter does not have one and only one value. Non-valid query
+ parameters may have multiple values and should not cause an error.
+ """
+ path = self.make_path(parameter)
+
+ if not path:
+ return False
+
+ # Must check that all indexes of all items in the path are correct before
+ # instantiating any of them. For example, consider:
+ #
+ # class Repeated(object):
+ # ...
+ #
+ # class Inner(object):
+ #
+ # repeated = messages.MessageField(Repeated, 1, repeated=True)
+ #
+ # class Outer(object):
+ #
+ # inner = messages.MessageField(Inner, 1)
+ #
+ # instance = Outer()
+ # builder = URLEncodedRequestBuilder(instance)
+ # builder.add_parameter('inner.repeated')
+ #
+ # assert not hasattr(instance, 'inner')
+ #
+ # The check is done relative to the instance of Outer pass in to the
+ # constructor of the builder. This instance is not referred to at all
+ # because all names are assumed to be relative to it.
+ #
+ # The 'repeated' part of the path is not correct because it is missing an
+ # index. Because it is missing an index, it should not create an instance
+ # of Repeated. In this case add_parameter will return False and have no
+ # side effects.
+ #
+ # A correct path that would cause a new Inner instance to be inserted at
+ # instance.inner and a new Repeated instance to be appended to the
+ # instance.inner.repeated list would be 'inner.repeated-0'.
+ if not self.__check_indexes(path):
+ return False
+
+ # Ok to build objects.
+ parent_path = path[:-1]
+ parent = self.__get_or_create_path(parent_path)
+ name, index = path[-1]
+ field = parent.field_by_name(name)
+
+ if len(values) != 1:
+ raise messages.DecodeError(
+ 'Found repeated values for field %s.' % field.name)
+
+ value = values[0]
+
+ if isinstance(field, messages.IntegerField):
+ converted_value = int(value)
+ elif isinstance(field, message_types.DateTimeField):
+ try:
+ converted_value = util.decode_datetime(value)
+ except ValueError as e:
+ raise messages.DecodeError(e)
+ elif isinstance(field, messages.MessageField):
+ # Just make sure it's instantiated. Assignment to field or
+ # appending to list is done in __get_or_create_path.
+ self.__get_or_create_path(path)
+ return True
+ elif isinstance(field, messages.StringField):
+ converted_value = value.decode('utf-8')
+ elif isinstance(field, messages.BooleanField):
+ converted_value = value.lower() == 'true' and True or False
+ else:
+ try:
+ converted_value = field.type(value)
+ except TypeError:
+ raise messages.DecodeError('Invalid enum value "%s"' % value)
+
+ if field.repeated:
+ value_list = getattr(parent, field.name, None)
+ if value_list is None:
+ setattr(parent, field.name, [converted_value])
+ else:
+ if index == len(value_list):
+ value_list.append(converted_value)
+ else:
+ # Index should never be above len(value_list) because it was
+ # verified during the index check above.
+ value_list[index] = converted_value
+ else:
+ setattr(parent, field.name, converted_value)
+
+ return True
+
+
+@util.positional(1)
+def encode_message(message, prefix=''):
+ """Encode Message instance to url-encoded string.
+
+ Args:
+ message: Message instance to encode in to url-encoded string.
+ prefix: Prefix to append to field names of contained values.
+
+ Returns:
+ String encoding of Message in URL encoded format.
+
+ Raises:
+ messages.ValidationError if message is not initialized.
+ """
+ message.check_initialized()
+
+ parameters = []
+ def build_message(parent, prefix):
+ """Recursively build parameter list for URL response.
+
+ Args:
+ parent: Message to build parameters for.
+ prefix: Prefix to append to field names of contained values.
+
+ Returns:
+ True if some value of parent was added to the parameters list,
+ else False, meaning the object contained no values.
+ """
+ has_any_values = False
+ for field in sorted(parent.all_fields(), key=lambda f: f.number):
+ next_value = parent.get_assigned_value(field.name)
+ if next_value is None:
+ continue
+
+ # Found a value. Ultimate return value should be True.
+ has_any_values = True
+
+ # Normalize all values in to a list.
+ if not field.repeated:
+ next_value = [next_value]
+
+ for index, item in enumerate(next_value):
+ # Create a name with an index if it is a repeated field.
+ if field.repeated:
+ field_name = '%s%s-%s' % (prefix, field.name, index)
+ else:
+ field_name = prefix + field.name
+
+ if isinstance(field, message_types.DateTimeField):
+ # DateTimeField stores its data as a RFC 3339 compliant string.
+ parameters.append((field_name, item.isoformat()))
+ elif isinstance(field, messages.MessageField):
+ # Message fields must be recursed in to in order to construct
+ # their component parameter values.
+ if not build_message(item, field_name + '.'):
+ # The nested message is empty. Append an empty value to
+ # represent it.
+ parameters.append((field_name, ''))
+ elif isinstance(field, messages.BooleanField):
+ parameters.append((field_name, item and 'true' or 'false'))
+ else:
+ if isinstance(item, six.text_type):
+ item = item.encode('utf-8')
+ parameters.append((field_name, str(item)))
+
+ return has_any_values
+
+ build_message(message, prefix)
+
+ # Also add any unrecognized values from the decoded string.
+ for key in message.all_unrecognized_fields():
+ values, _ = message.get_unrecognized_field_info(key)
+ if not isinstance(values, (list, tuple)):
+ values = (values,)
+ for value in values:
+ parameters.append((key, value))
+
+ return urllib.urlencode(parameters)
+
+
+def decode_message(message_type, encoded_message, **kwargs):
+ """Decode urlencoded content to message.
+
+ Args:
+ message_type: Message instance to merge URL encoded content into.
+ encoded_message: URL encoded message.
+ prefix: Prefix to append to field names of contained values.
+
+ Returns:
+ Decoded instance of message_type.
+ """
+ message = message_type()
+ builder = URLEncodedRequestBuilder(message, **kwargs)
+ arguments = cgi.parse_qs(encoded_message, keep_blank_values=True)
+ for argument, values in sorted(six.iteritems(arguments)):
+ added = builder.add_parameter(argument, values)
+ # Save off any unknown values, so they're still accessible.
+ if not added:
+ message.set_unrecognized_field(argument, values, messages.Variant.STRING)
+ message.check_initialized()
+ return message

Powered by Google App Engine
This is Rietveld 408576698