Update oslo

Update gettext, striutils, timeutils and install_venv_common
from oslo.

Change-Id: Ibd9067e3e2be335ef75f0e4a5e4000d143030ab7
Signed-off-by: Chuck Short <chuck.short@canonical.com>
This commit is contained in:
Chuck Short 2013-08-14 15:05:52 -04:00
parent 23ac42a43f
commit 626c480559
5 changed files with 377 additions and 49 deletions

View File

@ -1,6 +1,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4 # vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 Red Hat, Inc. # Copyright 2012 Red Hat, Inc.
# Copyright 2013 IBM Corp.
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -23,18 +24,27 @@ Usual usage in an openstack.common module:
from novaclient.openstack.common.gettextutils import _ from novaclient.openstack.common.gettextutils import _
""" """
import copy
import gettext import gettext
import logging.handlers
import os import os
import re
import UserString
from babel import localedata
import six
_localedir = os.environ.get('novaclient'.upper() + '_LOCALEDIR') _localedir = os.environ.get('novaclient'.upper() + '_LOCALEDIR')
_t = gettext.translation('novaclient', localedir=_localedir, fallback=True) _t = gettext.translation('novaclient', localedir=_localedir, fallback=True)
_AVAILABLE_LANGUAGES = []
def _(msg): def _(msg):
return _t.ugettext(msg) return _t.ugettext(msg)
def install(domain): def install(domain, lazy=False):
"""Install a _() function using the given translation domain. """Install a _() function using the given translation domain.
Given a translation domain, install a _() function using gettext's Given a translation domain, install a _() function using gettext's
@ -44,7 +54,252 @@ def install(domain):
overriding the default localedir (e.g. /usr/share/locale) using overriding the default localedir (e.g. /usr/share/locale) using
a translation-domain-specific environment variable (e.g. a translation-domain-specific environment variable (e.g.
NOVA_LOCALEDIR). NOVA_LOCALEDIR).
:param domain: the translation domain
:param lazy: indicates whether or not to install the lazy _() function.
The lazy _() introduces a way to do deferred translation
of messages by installing a _ that builds Message objects,
instead of strings, which can then be lazily translated into
any available locale.
""" """
gettext.install(domain, if lazy:
localedir=os.environ.get(domain.upper() + '_LOCALEDIR'), # NOTE(mrodden): Lazy gettext functionality.
unicode=True) #
# The following introduces a deferred way to do translations on
# messages in OpenStack. We override the standard _() function
# and % (format string) operation to build Message objects that can
# later be translated when we have more information.
#
# Also included below is an example LocaleHandler that translates
# Messages to an associated locale, effectively allowing many logs,
# each with their own locale.
def _lazy_gettext(msg):
"""Create and return a Message object.
Lazy gettext function for a given domain, it is a factory method
for a project/module to get a lazy gettext function for its own
translation domain (i.e. nova, glance, cinder, etc.)
Message encapsulates a string so that we can translate
it later when needed.
"""
return Message(msg, domain)
import __builtin__
__builtin__.__dict__['_'] = _lazy_gettext
else:
localedir = '%s_LOCALEDIR' % domain.upper()
gettext.install(domain,
localedir=os.environ.get(localedir),
unicode=True)
class Message(UserString.UserString, object):
"""Class used to encapsulate translatable messages."""
def __init__(self, msg, domain):
# _msg is the gettext msgid and should never change
self._msg = msg
self._left_extra_msg = ''
self._right_extra_msg = ''
self.params = None
self.locale = None
self.domain = domain
@property
def data(self):
# NOTE(mrodden): this should always resolve to a unicode string
# that best represents the state of the message currently
localedir = os.environ.get(self.domain.upper() + '_LOCALEDIR')
if self.locale:
lang = gettext.translation(self.domain,
localedir=localedir,
languages=[self.locale],
fallback=True)
else:
# use system locale for translations
lang = gettext.translation(self.domain,
localedir=localedir,
fallback=True)
full_msg = (self._left_extra_msg +
lang.ugettext(self._msg) +
self._right_extra_msg)
if self.params is not None:
full_msg = full_msg % self.params
return six.text_type(full_msg)
def _save_dictionary_parameter(self, dict_param):
full_msg = self.data
# look for %(blah) fields in string;
# ignore %% and deal with the
# case where % is first character on the line
keys = re.findall('(?:[^%]|^)?%\((\w*)\)[a-z]', full_msg)
# if we don't find any %(blah) blocks but have a %s
if not keys and re.findall('(?:[^%]|^)%[a-z]', full_msg):
# apparently the full dictionary is the parameter
params = copy.deepcopy(dict_param)
else:
params = {}
for key in keys:
try:
params[key] = copy.deepcopy(dict_param[key])
except TypeError:
# cast uncopyable thing to unicode string
params[key] = unicode(dict_param[key])
return params
def _save_parameters(self, other):
# we check for None later to see if
# we actually have parameters to inject,
# so encapsulate if our parameter is actually None
if other is None:
self.params = (other, )
elif isinstance(other, dict):
self.params = self._save_dictionary_parameter(other)
else:
# fallback to casting to unicode,
# this will handle the problematic python code-like
# objects that cannot be deep-copied
try:
self.params = copy.deepcopy(other)
except TypeError:
self.params = unicode(other)
return self
# overrides to be more string-like
def __unicode__(self):
return self.data
def __str__(self):
return self.data.encode('utf-8')
def __getstate__(self):
to_copy = ['_msg', '_right_extra_msg', '_left_extra_msg',
'domain', 'params', 'locale']
new_dict = self.__dict__.fromkeys(to_copy)
for attr in to_copy:
new_dict[attr] = copy.deepcopy(self.__dict__[attr])
return new_dict
def __setstate__(self, state):
for (k, v) in state.items():
setattr(self, k, v)
# operator overloads
def __add__(self, other):
copied = copy.deepcopy(self)
copied._right_extra_msg += other.__str__()
return copied
def __radd__(self, other):
copied = copy.deepcopy(self)
copied._left_extra_msg += other.__str__()
return copied
def __mod__(self, other):
# do a format string to catch and raise
# any possible KeyErrors from missing parameters
self.data % other
copied = copy.deepcopy(self)
return copied._save_parameters(other)
def __mul__(self, other):
return self.data * other
def __rmul__(self, other):
return other * self.data
def __getitem__(self, key):
return self.data[key]
def __getslice__(self, start, end):
return self.data.__getslice__(start, end)
def __getattribute__(self, name):
# NOTE(mrodden): handle lossy operations that we can't deal with yet
# These override the UserString implementation, since UserString
# uses our __class__ attribute to try and build a new message
# after running the inner data string through the operation.
# At that point, we have lost the gettext message id and can just
# safely resolve to a string instead.
ops = ['capitalize', 'center', 'decode', 'encode',
'expandtabs', 'ljust', 'lstrip', 'replace', 'rjust', 'rstrip',
'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
if name in ops:
return getattr(self.data, name)
else:
return UserString.UserString.__getattribute__(self, name)
def get_available_languages(domain):
"""Lists the available languages for the given translation domain.
:param domain: the domain to get languages for
"""
if _AVAILABLE_LANGUAGES:
return _AVAILABLE_LANGUAGES
localedir = '%s_LOCALEDIR' % domain.upper()
find = lambda x: gettext.find(domain,
localedir=os.environ.get(localedir),
languages=[x])
# NOTE(mrodden): en_US should always be available (and first in case
# order matters) since our in-line message strings are en_US
_AVAILABLE_LANGUAGES.append('en_US')
# NOTE(luisg): Babel <1.0 used a function called list(), which was
# renamed to locale_identifiers() in >=1.0, the requirements master list
# requires >=0.9.6, uncapped, so defensively work with both. We can remove
# this check when the master list updates to >=1.0, and all projects udpate
list_identifiers = (getattr(localedata, 'list', None) or
getattr(localedata, 'locale_identifiers'))
locale_identifiers = list_identifiers()
for i in locale_identifiers:
if find(i) is not None:
_AVAILABLE_LANGUAGES.append(i)
return _AVAILABLE_LANGUAGES
def get_localized_message(message, user_locale):
"""Gets a localized version of the given message in the given locale."""
if (isinstance(message, Message)):
if user_locale:
message.locale = user_locale
return unicode(message)
else:
return message
class LocaleHandler(logging.Handler):
"""Handler that can have a locale associated to translate Messages.
A quick example of how to utilize the Message class above.
LocaleHandler takes a locale and a target logging.Handler object
to forward LogRecord objects to after translating the internal Message.
"""
def __init__(self, locale, target):
"""Initialize a LocaleHandler
:param locale: locale to use for translating messages
:param target: logging.Handler object to forward
LogRecord objects to after translation
"""
logging.Handler.__init__(self)
self.locale = locale
self.target = target
def emit(self, record):
if isinstance(record.msg, Message):
# set the locale and resolve to a string
record.msg.locale = self.locale
self.target.emit(record)

View File

@ -19,18 +19,35 @@
System-level utilities and helper functions. System-level utilities and helper functions.
""" """
import re
import sys import sys
import unicodedata
from novaclient.openstack.common.gettextutils import _ import six
from novaclient.openstack.common.gettextutils import _ # noqa
# Used for looking up extensions of text
# to their 'multiplied' byte amount
BYTE_MULTIPLIERS = {
'': 1,
't': 1024 ** 4,
'g': 1024 ** 3,
'm': 1024 ** 2,
'k': 1024,
}
BYTE_REGEX = re.compile(r'(^-?\d+)(\D*)')
TRUE_STRINGS = ('1', 't', 'true', 'on', 'y', 'yes') TRUE_STRINGS = ('1', 't', 'true', 'on', 'y', 'yes')
FALSE_STRINGS = ('0', 'f', 'false', 'off', 'n', 'no') FALSE_STRINGS = ('0', 'f', 'false', 'off', 'n', 'no')
SLUGIFY_STRIP_RE = re.compile(r"[^\w\s-]")
SLUGIFY_HYPHENATE_RE = re.compile(r"[-\s]+")
def int_from_bool_as_string(subject): def int_from_bool_as_string(subject):
""" """Interpret a string as a boolean and return either 1 or 0.
Interpret a string as a boolean and return either 1 or 0.
Any string value in: Any string value in:
@ -44,8 +61,7 @@ def int_from_bool_as_string(subject):
def bool_from_string(subject, strict=False): def bool_from_string(subject, strict=False):
""" """Interpret a string as a boolean.
Interpret a string as a boolean.
A case-insensitive match is performed such that strings matching 't', A case-insensitive match is performed such that strings matching 't',
'true', 'on', 'y', 'yes', or '1' are considered True and, when 'true', 'on', 'y', 'yes', or '1' are considered True and, when
@ -57,7 +73,7 @@ def bool_from_string(subject, strict=False):
ValueError which is useful when parsing values passed in from an API call. ValueError which is useful when parsing values passed in from an API call.
Strings yielding False are 'f', 'false', 'off', 'n', 'no', or '0'. Strings yielding False are 'f', 'false', 'off', 'n', 'no', or '0'.
""" """
if not isinstance(subject, basestring): if not isinstance(subject, six.string_types):
subject = str(subject) subject = str(subject)
lowered = subject.strip().lower() lowered = subject.strip().lower()
@ -78,21 +94,19 @@ def bool_from_string(subject, strict=False):
def safe_decode(text, incoming=None, errors='strict'): def safe_decode(text, incoming=None, errors='strict'):
""" """Decodes incoming str using `incoming` if they're not already unicode.
Decodes incoming str using `incoming` if they're
not already unicode.
:param incoming: Text's current encoding :param incoming: Text's current encoding
:param errors: Errors handling policy. See here for valid :param errors: Errors handling policy. See here for valid
values http://docs.python.org/2/library/codecs.html values http://docs.python.org/2/library/codecs.html
:returns: text or a unicode `incoming` encoded :returns: text or a unicode `incoming` encoded
representation of it. representation of it.
:raises TypeError: If text is not an isntance of basestring :raises TypeError: If text is not an isntance of str
""" """
if not isinstance(text, basestring): if not isinstance(text, six.string_types):
raise TypeError("%s can't be decoded" % type(text)) raise TypeError("%s can't be decoded" % type(text))
if isinstance(text, unicode): if isinstance(text, six.text_type):
return text return text
if not incoming: if not incoming:
@ -119,11 +133,10 @@ def safe_decode(text, incoming=None, errors='strict'):
def safe_encode(text, incoming=None, def safe_encode(text, incoming=None,
encoding='utf-8', errors='strict'): encoding='utf-8', errors='strict'):
""" """Encodes incoming str/unicode using `encoding`.
Encodes incoming str/unicode using `encoding`. If
incoming is not specified, text is expected to If incoming is not specified, text is expected to be encoded with
be encoded with current python's default encoding. current python's default encoding. (`sys.getdefaultencoding`)
(`sys.getdefaultencoding`)
:param incoming: Text's current encoding :param incoming: Text's current encoding
:param encoding: Expected encoding for text (Default UTF-8) :param encoding: Expected encoding for text (Default UTF-8)
@ -131,16 +144,16 @@ def safe_encode(text, incoming=None,
values http://docs.python.org/2/library/codecs.html values http://docs.python.org/2/library/codecs.html
:returns: text or a bytestring `encoding` encoded :returns: text or a bytestring `encoding` encoded
representation of it. representation of it.
:raises TypeError: If text is not an isntance of basestring :raises TypeError: If text is not an isntance of str
""" """
if not isinstance(text, basestring): if not isinstance(text, six.string_types):
raise TypeError("%s can't be encoded" % type(text)) raise TypeError("%s can't be encoded" % type(text))
if not incoming: if not incoming:
incoming = (sys.stdin.encoding or incoming = (sys.stdin.encoding or
sys.getdefaultencoding()) sys.getdefaultencoding())
if isinstance(text, unicode): if isinstance(text, six.text_type):
return text.encode(encoding, errors) return text.encode(encoding, errors)
elif text and encoding != incoming: elif text and encoding != incoming:
# Decode text before encoding it with `encoding` # Decode text before encoding it with `encoding`
@ -148,3 +161,58 @@ def safe_encode(text, incoming=None,
return text.encode(encoding, errors) return text.encode(encoding, errors)
return text return text
def to_bytes(text, default=0):
"""Converts a string into an integer of bytes.
Looks at the last characters of the text to determine
what conversion is needed to turn the input text into a byte number.
Supports "B, K(B), M(B), G(B), and T(B)". (case insensitive)
:param text: String input for bytes size conversion.
:param default: Default return value when text is blank.
"""
match = BYTE_REGEX.search(text)
if match:
magnitude = int(match.group(1))
mult_key_org = match.group(2)
if not mult_key_org:
return magnitude
elif text:
msg = _('Invalid string format: %s') % text
raise TypeError(msg)
else:
return default
mult_key = mult_key_org.lower().replace('b', '', 1)
multiplier = BYTE_MULTIPLIERS.get(mult_key)
if multiplier is None:
msg = _('Unknown byte multiplier: %s') % mult_key_org
raise TypeError(msg)
return magnitude * multiplier
def to_slug(value, incoming=None, errors="strict"):
"""Normalize string.
Convert to lowercase, remove non-word characters, and convert spaces
to hyphens.
Inspired by Django's `slugify` filter.
:param value: Text to slugify
:param incoming: Text's current encoding
:param errors: Errors handling policy. See here for valid
values http://docs.python.org/2/library/codecs.html
:returns: slugified unicode representation of `value`
:raises TypeError: If text is not an instance of str
"""
value = safe_decode(value, incoming, errors)
# NOTE(aababilov): no need to use safe_(encode|decode) here:
# encodings are always "ascii", error handling is always "ignore"
# and types are always known (first: unicode; second: str)
value = unicodedata.normalize("NFKD", value).encode(
"ascii", "ignore").decode("ascii")
value = SLUGIFY_STRIP_RE.sub("", value).strip().lower()
return SLUGIFY_HYPHENATE_RE.sub("-", value)

View File

@ -23,6 +23,7 @@ import calendar
import datetime import datetime
import iso8601 import iso8601
import six
# ISO 8601 extended time format with microseconds # ISO 8601 extended time format with microseconds
@ -32,7 +33,7 @@ PERFECT_TIME_FORMAT = _ISO8601_TIME_FORMAT_SUBSECOND
def isotime(at=None, subsecond=False): def isotime(at=None, subsecond=False):
"""Stringify time in ISO 8601 format""" """Stringify time in ISO 8601 format."""
if not at: if not at:
at = utcnow() at = utcnow()
st = at.strftime(_ISO8601_TIME_FORMAT st = at.strftime(_ISO8601_TIME_FORMAT
@ -44,13 +45,13 @@ def isotime(at=None, subsecond=False):
def parse_isotime(timestr): def parse_isotime(timestr):
"""Parse time from ISO 8601 format""" """Parse time from ISO 8601 format."""
try: try:
return iso8601.parse_date(timestr) return iso8601.parse_date(timestr)
except iso8601.ParseError as e: except iso8601.ParseError as e:
raise ValueError(e.message) raise ValueError(unicode(e))
except TypeError as e: except TypeError as e:
raise ValueError(e.message) raise ValueError(unicode(e))
def strtime(at=None, fmt=PERFECT_TIME_FORMAT): def strtime(at=None, fmt=PERFECT_TIME_FORMAT):
@ -66,7 +67,7 @@ def parse_strtime(timestr, fmt=PERFECT_TIME_FORMAT):
def normalize_time(timestamp): def normalize_time(timestamp):
"""Normalize time in arbitrary timezone to UTC naive object""" """Normalize time in arbitrary timezone to UTC naive object."""
offset = timestamp.utcoffset() offset = timestamp.utcoffset()
if offset is None: if offset is None:
return timestamp return timestamp
@ -75,14 +76,14 @@ def normalize_time(timestamp):
def is_older_than(before, seconds): def is_older_than(before, seconds):
"""Return True if before is older than seconds.""" """Return True if before is older than seconds."""
if isinstance(before, basestring): if isinstance(before, six.string_types):
before = parse_strtime(before).replace(tzinfo=None) before = parse_strtime(before).replace(tzinfo=None)
return utcnow() - before > datetime.timedelta(seconds=seconds) return utcnow() - before > datetime.timedelta(seconds=seconds)
def is_newer_than(after, seconds): def is_newer_than(after, seconds):
"""Return True if after is newer than seconds.""" """Return True if after is newer than seconds."""
if isinstance(after, basestring): if isinstance(after, six.string_types):
after = parse_strtime(after).replace(tzinfo=None) after = parse_strtime(after).replace(tzinfo=None)
return after - utcnow() > datetime.timedelta(seconds=seconds) return after - utcnow() > datetime.timedelta(seconds=seconds)
@ -103,7 +104,7 @@ def utcnow():
def iso8601_from_timestamp(timestamp): def iso8601_from_timestamp(timestamp):
"""Returns a iso8601 formated date from timestamp""" """Returns a iso8601 formated date from timestamp."""
return isotime(datetime.datetime.utcfromtimestamp(timestamp)) return isotime(datetime.datetime.utcfromtimestamp(timestamp))
@ -111,9 +112,9 @@ utcnow.override_time = None
def set_time_override(override_time=datetime.datetime.utcnow()): def set_time_override(override_time=datetime.datetime.utcnow()):
""" """Overrides utils.utcnow.
Override utils.utcnow to return a constant time or a list thereof,
one at a time. Make it return a constant time or a list thereof, one at a time.
""" """
utcnow.override_time = override_time utcnow.override_time = override_time
@ -141,7 +142,8 @@ def clear_time_override():
def marshall_now(now=None): def marshall_now(now=None):
"""Make an rpc-safe datetime with microseconds. """Make an rpc-safe datetime with microseconds.
Note: tzinfo is stripped, but not required for relative times.""" Note: tzinfo is stripped, but not required for relative times.
"""
if not now: if not now:
now = utcnow() now = utcnow()
return dict(day=now.day, month=now.month, year=now.year, hour=now.hour, return dict(day=now.day, month=now.month, year=now.year, hour=now.hour,
@ -161,7 +163,8 @@ def unmarshall_time(tyme):
def delta_seconds(before, after): def delta_seconds(before, after):
""" """Return the difference between two timing objects.
Compute the difference in seconds between two date, time, or Compute the difference in seconds between two date, time, or
datetime objects (as a float, to microsecond resolution). datetime objects (as a float, to microsecond resolution).
""" """
@ -174,8 +177,7 @@ def delta_seconds(before, after):
def is_soon(dt, window): def is_soon(dt, window):
""" """Determines if time is going to happen in the next window seconds.
Determines if time is going to happen in the next window seconds.
:params dt: the time :params dt: the time
:params window: minimum seconds to remain to consider the time not soon :params window: minimum seconds to remain to consider the time not soon

View File

@ -5,3 +5,4 @@ PrettyTable>=0.6,<0.8
requests>=1.1 requests>=1.1
simplejson>=2.0.9 simplejson>=2.0.9
six six
Babel>=0.9.6

View File

@ -114,9 +114,10 @@ class InstallVenv(object):
print('Installing dependencies with pip (this can take a while)...') print('Installing dependencies with pip (this can take a while)...')
# First things first, make sure our venv has the latest pip and # First things first, make sure our venv has the latest pip and
# setuptools. # setuptools and pbr
self.pip_install('pip>=1.3') self.pip_install('pip>=1.4')
self.pip_install('setuptools') self.pip_install('setuptools')
self.pip_install('pbr')
self.pip_install('-r', self.requirements) self.pip_install('-r', self.requirements)
self.pip_install('-r', self.test_requirements) self.pip_install('-r', self.test_requirements)
@ -201,12 +202,13 @@ class Fedora(Distro):
RHEL: https://bugzilla.redhat.com/958868 RHEL: https://bugzilla.redhat.com/958868
""" """
# Install "patch" program if it's not there if os.path.exists('contrib/redhat-eventlet.patch'):
if not self.check_pkg('patch'): # Install "patch" program if it's not there
self.die("Please install 'patch'.") if not self.check_pkg('patch'):
self.die("Please install 'patch'.")
# Apply the eventlet patch # Apply the eventlet patch
self.apply_patch(os.path.join(self.venv, 'lib', self.py_version, self.apply_patch(os.path.join(self.venv, 'lib', self.py_version,
'site-packages', 'site-packages',
'eventlet/green/subprocess.py'), 'eventlet/green/subprocess.py'),
'contrib/redhat-eventlet.patch') 'contrib/redhat-eventlet.patch')