blob: ab7bc2c4b2f39d46bf4dab0cc65d28f84e8c1519 [file] [log] [blame]
#!/usr/bin/python2.4
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you 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.
"""Utility library containing various helpers used by the API."""
import re
CUSTOM_SERIALIZE_METHOD_NAME = 'serialize'
MARKUP_RE = re.compile(r'<([^>]*?)>')
RESERVED_PROXY_FOR_CHARS_RE = re.compile(ur'[\s\u0000-\u001F@,:<>\u007F]')
def force_unicode(object):
""" Return the Unicode string version of object, with UTF-8 encoding. """
if isinstance(object, unicode):
return object
return unicode(str(object), 'utf-8')
def parse_markup(markup):
"""Parses a bit of markup into robot compatible text.
For now this is a rough approximation.
"""
def replace_tag(group):
if not group.groups:
return ''
tag = group.groups()[0].split(' ', 1)[0]
if (tag == 'p' or tag == 'br'):
return '\n'
return ''
return MARKUP_RE.sub(replace_tag, markup)
def is_iterable(inst):
"""Returns whether or not this is a list, tuple, set or dict .
Note that this does not return true for strings.
"""
return hasattr(inst, '__iter__')
def is_dict(inst):
"""Returns whether or not the specified instance is a dict."""
return hasattr(inst, 'iteritems')
def is_user_defined_new_style_class(obj):
"""Returns whether or not the specified instance is a user-defined type."""
return type(obj).__module__ != '__builtin__'
def lower_camel_case(s):
"""Converts a string to lower camel case.
Examples:
foo => foo
foo_bar => fooBar
foo__bar => fooBar
foo_bar_baz => fooBarBaz
Args:
s: The string to convert to lower camel case.
Returns:
The lower camel cased string.
"""
return reduce(lambda a, b: a + (a and b.capitalize() or b), s.split('_'))
def non_none_dict(d):
"""return a copy of the dictionary without none values."""
return dict([a for a in d.items() if a[1] is not None])
def _serialize_attributes(obj):
"""Serializes attributes of an instance.
Iterates all attributes of an object and invokes serialize if they are
public and not callable.
Args:
obj: The instance to serialize.
Returns:
The serialized object.
"""
data = {}
for attr_name in dir(obj):
if attr_name.startswith('_'):
continue
attr = getattr(obj, attr_name)
if attr is None or callable(attr):
continue
# Looks okay, serialize it.
data[lower_camel_case(attr_name)] = serialize(attr)
return data
def _serialize_dict(d):
"""Invokes serialize on all of its key/value pairs.
Args:
d: The dict instance to serialize.
Returns:
The serialized dict.
"""
data = {}
for k, v in d.items():
data[lower_camel_case(k)] = serialize(v)
return data
def serialize(obj):
"""Serializes any instance.
If this is a user-defined instance
type, it will first check for a custom Serialize() function and use that
if it exists. Otherwise, it will invoke serialize all of its public
attributes. Lists and dicts are serialized trivially.
Args:
obj: The instance to serialize.
Returns:
The serialized object.
"""
if is_user_defined_new_style_class(obj):
if obj and hasattr(obj, CUSTOM_SERIALIZE_METHOD_NAME):
method = getattr(obj, CUSTOM_SERIALIZE_METHOD_NAME)
if callable(method):
return method()
return _serialize_attributes(obj)
elif is_dict(obj):
return _serialize_dict(obj)
elif is_iterable(obj):
return [serialize(v) for v in obj]
return obj
def is_valid_proxy_for_id(s):
""" Checks if the given string is a valid proxy id.
This method asserts whether the string contains reserved characters or not.
This check is to ensure that when we concatenate the robot id and the proxy
id, it doesn't result in an invalid participant id.
The reserved characters are:
- whitespaces
- non-printing characters: decimal 0 - 31 (hex 00 - 1F), and decimal 127
(hex 7F)
- @, comma, :, <, and >
If you need to pass in an arbitrary string as the proxy id, please consider
encoding the string with a URL encoder (for example, urllib.quote_plus) or
base64 encoder (for example, base64.b64encode) first.
Args:
s: the string to be checked.
Returns:
True if the string is a valid proxy for id.
"""
return RESERVED_PROXY_FOR_CHARS_RE.search(s) == None
def check_is_valid_proxy_for_id(s):
""" Checks if the given string is a valid proxy id.
Please see isValidProxyForId(String) for more details on the assertion. This
method raises a ValueError exception if the input string is not a valid proxy
id.
Args:
s: the string to be checked.
"""
if s != None and not is_valid_proxy_for_id(s):
raise ValueError(s + ' is not a valid proxy for id.')
class StringEnum(object):
"""Enum like class that is configured with a list of values.
This class effectively implements an enum for Elements, except for that
the actual values of the enums will be the string values.
"""
def __init__(self, *values):
for name in values:
setattr(self, name, name)