diff options
-rw-r--r-- | cinderclient/openstack/common/apiclient/__init__.py | 16 | ||||
-rw-r--r-- | cinderclient/openstack/common/apiclient/auth.py | 16 | ||||
-rw-r--r-- | cinderclient/openstack/common/apiclient/base.py | 17 | ||||
-rw-r--r-- | cinderclient/openstack/common/apiclient/client.py | 4 | ||||
-rw-r--r-- | cinderclient/openstack/common/apiclient/exceptions.py | 30 | ||||
-rw-r--r-- | cinderclient/openstack/common/apiclient/fake_client.py | 9 | ||||
-rw-r--r-- | cinderclient/openstack/common/gettextutils.py | 463 | ||||
-rw-r--r-- | cinderclient/openstack/common/importutils.py | 2 | ||||
-rw-r--r-- | cinderclient/openstack/common/py3kcompat/__init__.py | 0 | ||||
-rw-r--r-- | cinderclient/openstack/common/py3kcompat/urlutils.py | 67 | ||||
-rw-r--r-- | cinderclient/openstack/common/strutils.py | 24 | ||||
-rw-r--r-- | tools/install_venv_common.py | 41 |
12 files changed, 383 insertions, 306 deletions
diff --git a/cinderclient/openstack/common/apiclient/__init__.py b/cinderclient/openstack/common/apiclient/__init__.py index d5d0022..e69de29 100644 --- a/cinderclient/openstack/common/apiclient/__init__.py +++ b/cinderclient/openstack/common/apiclient/__init__.py @@ -1,16 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2013 OpenStack Foundation -# All Rights Reserved. -# -# 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. diff --git a/cinderclient/openstack/common/apiclient/auth.py b/cinderclient/openstack/common/apiclient/auth.py index 374d20b..1a713b0 100644 --- a/cinderclient/openstack/common/apiclient/auth.py +++ b/cinderclient/openstack/common/apiclient/auth.py @@ -1,5 +1,3 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - # Copyright 2013 OpenStack Foundation # Copyright 2013 Spanish National Research Council. # All Rights Reserved. @@ -21,17 +19,14 @@ import abc import argparse -import logging import os +import six from stevedore import extension from cinderclient.openstack.common.apiclient import exceptions -logger = logging.getLogger(__name__) - - _discovered_plugins = {} @@ -59,7 +54,7 @@ def load_auth_system_opts(parser): """ group = parser.add_argument_group("Common auth options") BaseAuthPlugin.add_common_opts(group) - for name, auth_plugin in _discovered_plugins.iteritems(): + for name, auth_plugin in six.iteritems(_discovered_plugins): group = parser.add_argument_group( "Auth-system '%s' options" % name, conflict_handler="resolve") @@ -75,7 +70,7 @@ def load_plugin(auth_system): def load_plugin_from_args(args): - """Load requred plugin and populate it with options. + """Load required plugin and populate it with options. Try to guess auth system if it is not specified. Systems are tried in alphabetical order. @@ -90,7 +85,7 @@ def load_plugin_from_args(args): plugin.sufficient_options() return plugin - for plugin_auth_system in sorted(_discovered_plugins.iterkeys()): + for plugin_auth_system in sorted(six.iterkeys(_discovered_plugins)): plugin_class = _discovered_plugins[plugin_auth_system] plugin = plugin_class() plugin.parse_opts(args) @@ -102,6 +97,7 @@ def load_plugin_from_args(args): raise exceptions.AuthPluginOptionsMissing(["auth_system"]) +@six.add_metaclass(abc.ABCMeta) class BaseAuthPlugin(object): """Base class for authentication plugins. @@ -109,8 +105,6 @@ class BaseAuthPlugin(object): method to be a valid plugin. """ - __metaclass__ = abc.ABCMeta - auth_system = None opt_names = [] common_opt_names = [ diff --git a/cinderclient/openstack/common/apiclient/base.py b/cinderclient/openstack/common/apiclient/base.py index caef843..e101f4c 100644 --- a/cinderclient/openstack/common/apiclient/base.py +++ b/cinderclient/openstack/common/apiclient/base.py @@ -1,5 +1,3 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - # Copyright 2010 Jacob Kaplan-Moss # Copyright 2011 OpenStack Foundation # Copyright 2012 Grid Dynamics @@ -26,9 +24,11 @@ Base utilities to build API operation managers and objects on top of. # pylint: disable=E1102 import abc -import urllib + +import six from cinderclient.openstack.common.apiclient import exceptions +from cinderclient.openstack.common.py3kcompat import urlutils from cinderclient.openstack.common import strutils @@ -201,11 +201,10 @@ class BaseManager(HookableMixin): return self.client.delete(url) +@six.add_metaclass(abc.ABCMeta) class ManagerWithFind(BaseManager): """Manager with additional `find()`/`findall()` methods.""" - __metaclass__ = abc.ABCMeta - @abc.abstractmethod def list(self): pass @@ -292,7 +291,7 @@ class CrudManager(BaseManager): def _filter_kwargs(self, kwargs): """Drop null values and handle ids.""" - for key, ref in kwargs.copy().iteritems(): + for key, ref in six.iteritems(kwargs.copy()): if ref is None: kwargs.pop(key) else: @@ -328,7 +327,7 @@ class CrudManager(BaseManager): return self._list( '%(base_url)s%(query)s' % { 'base_url': self.build_url(base_url=base_url, **kwargs), - 'query': '?%s' % urllib.urlencode(kwargs) if kwargs else '', + 'query': '?%s' % urlutils.urlencode(kwargs) if kwargs else '', }, self.collection_key) @@ -367,7 +366,7 @@ class CrudManager(BaseManager): rl = self._list( '%(base_url)s%(query)s' % { 'base_url': self.build_url(base_url=base_url, **kwargs), - 'query': '?%s' % urllib.urlencode(kwargs) if kwargs else '', + 'query': '?%s' % urlutils.urlencode(kwargs) if kwargs else '', }, self.collection_key) num = len(rl) @@ -446,7 +445,7 @@ class Resource(object): return None def _add_details(self, info): - for (k, v) in info.iteritems(): + for (k, v) in six.iteritems(info): try: setattr(self, k, v) self._info[k] = v diff --git a/cinderclient/openstack/common/apiclient/client.py b/cinderclient/openstack/common/apiclient/client.py index 77d4579..da2e177 100644 --- a/cinderclient/openstack/common/apiclient/client.py +++ b/cinderclient/openstack/common/apiclient/client.py @@ -1,5 +1,3 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - # Copyright 2010 Jacob Kaplan-Moss # Copyright 2011 OpenStack Foundation # Copyright 2011 Piston Cloud Computing, Inc. @@ -52,7 +50,7 @@ class HTTPClient(object): services (e.g., for compute and image clients); - reissue authentication request for expired tokens; - encode/decode JSON bodies; - - raise exeptions on HTTP errors; + - raise exceptions on HTTP errors; - pluggable authentication; - store authentication information in a keyring; - store time spent for requests; diff --git a/cinderclient/openstack/common/apiclient/exceptions.py b/cinderclient/openstack/common/apiclient/exceptions.py index b03def7..4776d58 100644 --- a/cinderclient/openstack/common/apiclient/exceptions.py +++ b/cinderclient/openstack/common/apiclient/exceptions.py @@ -1,5 +1,3 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - # Copyright 2010 Jacob Kaplan-Moss # Copyright 2011 Nebula, Inc. # Copyright 2013 Alessio Ababilov @@ -22,8 +20,11 @@ Exception definitions. """ +import inspect import sys +import six + class ClientException(Exception): """The base exception class for all exceptions this library raises. @@ -59,6 +60,11 @@ class AuthorizationFailure(ClientException): pass +class ConnectionRefused(ClientException): + """Cannot connect to API service.""" + pass + + class AuthPluginOptionsMissing(AuthorizationFailure): """Auth plugin misses some options.""" def __init__(self, opt_names): @@ -387,20 +393,12 @@ class HttpVersionNotSupported(HttpServerError): message = "HTTP Version Not Supported" -# In Python 2.4 Exception is old-style and thus doesn't have a __subclasses__() -# so we can do this: -# _code_map = dict((c.http_status, c) -# for c in HttpError.__subclasses__()) -_code_map = {} -for obj in sys.modules[__name__].__dict__.values(): - if isinstance(obj, type): - try: - http_status = obj.http_status - except AttributeError: - pass - else: - if http_status: - _code_map[http_status] = obj +# _code_map contains all the classes that have http_status attribute. +_code_map = dict( + (getattr(obj, 'http_status', None), obj) + for name, obj in six.iteritems(vars(sys.modules[__name__])) + if inspect.isclass(obj) and getattr(obj, 'http_status', False) +) def from_response(response, method, url): diff --git a/cinderclient/openstack/common/apiclient/fake_client.py b/cinderclient/openstack/common/apiclient/fake_client.py index 914cebd..2d1c0aa 100644 --- a/cinderclient/openstack/common/apiclient/fake_client.py +++ b/cinderclient/openstack/common/apiclient/fake_client.py @@ -1,5 +1,3 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - # Copyright 2013 OpenStack Foundation # All Rights Reserved. # @@ -27,11 +25,12 @@ places where actual behavior differs from the spec. # pylint: disable=W0102 import json -import urlparse import requests +import six from cinderclient.openstack.common.apiclient import client +from cinderclient.openstack.common.py3kcompat import urlutils def assert_has_keys(dct, required=[], optional=[]): @@ -63,6 +62,8 @@ class TestResponse(requests.Response): else: self._content = text default_headers = {} + if six.PY3 and isinstance(self._content, six.string_types): + self._content = self._content.encode('utf-8', 'strict') self.headers = data.get('headers') or default_headers else: self.status_code = data @@ -146,7 +147,7 @@ class FakeHTTPClient(client.HTTPClient): "text": fixture[1]}) # Call the method - args = urlparse.parse_qsl(urlparse.urlparse(url)[4]) + args = urlutils.parse_qsl(urlutils.urlparse(url)[4]) kwargs.update(args) munged_url = url.rsplit('?', 1)[0] munged_url = munged_url.strip('/').replace('/', '_').replace('.', '_') diff --git a/cinderclient/openstack/common/gettextutils.py b/cinderclient/openstack/common/gettextutils.py index ce57f89..240ac05 100644 --- a/cinderclient/openstack/common/gettextutils.py +++ b/cinderclient/openstack/common/gettextutils.py @@ -1,5 +1,3 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - # Copyright 2012 Red Hat, Inc. # Copyright 2013 IBM Corp. # All Rights Reserved. @@ -26,13 +24,10 @@ Usual usage in an openstack.common module: import copy import gettext -import logging +import locale +from logging import handlers import os import re -try: - import UserString as _userString -except ImportError: - import collections as _userString from babel import localedata import six @@ -58,7 +53,7 @@ def enable_lazy(): def _(msg): if USE_LAZY: - return Message(msg, 'cinderclient') + return Message(msg, domain='cinderclient') else: if six.PY3: return _t.gettext(msg) @@ -90,11 +85,6 @@ def install(domain, lazy=False): # 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. @@ -105,7 +95,7 @@ def install(domain, lazy=False): Message encapsulates a string so that we can translate it later when needed. """ - return Message(msg, domain) + return Message(msg, domain=domain) from six import moves moves.builtins.__dict__['_'] = _lazy_gettext @@ -120,182 +110,169 @@ def install(domain, lazy=False): 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._locale = None - self.params = 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) +class Message(six.text_type): + """A Message object is a unicode object that can be translated. + + Translation of Message is done explicitly using the translate() method. + For all non-translation intents and purposes, a Message is simply unicode, + and can be treated as such. + """ + def __new__(cls, msgid, msgtext=None, params=None, + domain='cinderclient', *args): + """Create a new Message object. + + In order for translation to work gettext requires a message ID, this + msgid will be used as the base unicode text. It is also possible + for the msgid and the base unicode text to be different by passing + the msgtext parameter. + """ + # If the base msgtext is not given, we use the default translation + # of the msgid (which is in English) just in case the system locale is + # not English, so that the base text will be in that locale by default. + if not msgtext: + msgtext = Message._translate_msgid(msgid, domain) + # We want to initialize the parent unicode with the actual object that + # would have been plain unicode if 'Message' was not enabled. + msg = super(Message, cls).__new__(cls, msgtext) + msg.msgid = msgid + msg.domain = domain + msg.params = params + return msg + + def translate(self, desired_locale=None): + """Translate this message to the desired locale. + + :param desired_locale: The desired locale to translate the message to, + if no locale is provided the message will be + translated to the system's default locale. + + :returns: the translated message in unicode + """ + + translated_message = Message._translate_msgid(self.msgid, + self.domain, + desired_locale) + if self.params is None: + # No need for more translation + return translated_message + + # This Message object may have been formatted with one or more + # Message objects as substitution arguments, given either as a single + # argument, part of a tuple, or as one or more values in a dictionary. + # When translating this Message we need to translate those Messages too + translated_params = _translate_args(self.params, desired_locale) + + translated_message = translated_message % translated_params + + return translated_message + + @staticmethod + def _translate_msgid(msgid, domain, desired_locale=None): + if not desired_locale: + system_locale = locale.getdefaultlocale() + # If the system locale is not available to the runtime use English + if not system_locale[0]: + desired_locale = 'en_US' + else: + desired_locale = system_locale[0] + + locale_dir = os.environ.get(domain.upper() + '_LOCALEDIR') + lang = gettext.translation(domain, + localedir=locale_dir, + languages=[desired_locale], + fallback=True) if six.PY3: - ugettext = lang.gettext - else: - ugettext = lang.ugettext - - full_msg = (self._left_extra_msg + - 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) - - @property - def locale(self): - return self._locale - - @locale.setter - def locale(self, value): - self._locale = value - if not self.params: - return - - # This Message object may have been constructed with one or more - # Message objects as substitution parameters, given as a single - # Message, or a tuple or Map containing some, so when setting the - # locale for this Message we need to set it for those Messages too. - if isinstance(self.params, Message): - self.params.locale = value - return - if isinstance(self.params, tuple): - for param in self.params: - if isinstance(param, Message): - param.locale = value - return - if isinstance(self.params, dict): - for param in self.params.values(): - if isinstance(param, Message): - param.locale = value - - 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) + translator = lang.gettext else: - params = {} - for key in keys: - try: - params[key] = copy.deepcopy(dict_param[key]) - except TypeError: - # cast uncopyable thing to unicode string - params[key] = six.text_type(dict_param[key]) + translator = lang.ugettext - return params + translated_message = translator(msgid) + return translated_message - 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 + def __mod__(self, other): + # When we mod a Message we want the actual operation to be performed + # by the parent class (i.e. unicode()), the only thing we do here is + # save the original msgid and the parameters in case of a translation + params = self._sanitize_mod_params(other) + unicode_mod = super(Message, self).__mod__(params) + modded = Message(self.msgid, + msgtext=unicode_mod, + params=params, + domain=self.domain) + return modded + + def _sanitize_mod_params(self, other): + """Sanitize the object being modded with this Message. + + - Add support for modding 'None' so translation supports it + - Trim the modded object, which can be a large dictionary, to only + those keys that would actually be used in a translation + - Snapshot the object being modded, in case the message is + translated, it will be used as it was when the Message was created + """ if other is None: - self.params = (other, ) + params = (other,) elif isinstance(other, dict): - self.params = self._save_dictionary_parameter(other) + params = self._trim_dictionary_parameters(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 = six.text_type(other) - - return self - - # overrides to be more string-like - def __unicode__(self): - return self.data - - def __str__(self): - if six.PY3: - return self.__unicode__() - return self.data.encode('utf-8') + params = self._copy_param(other) + return params - 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]) + def _trim_dictionary_parameters(self, dict_param): + """Return a dict that only has matching entries in the msgid.""" + # NOTE(luisg): Here we trim down the dictionary passed as parameters + # to avoid carrying a lot of unnecessary weight around in the message + # object, for example if someone passes in Message() % locals() but + # only some params are used, and additionally we prevent errors for + # non-deepcopyable objects by unicoding() them. + + # Look for %(param) keys in msgid; + # Skip %% and deal with the case where % is first character on the line + keys = re.findall('(?:[^%]|^)?%\((\w*)\)[a-z]', self.msgid) + + # If we don't find any %(param) keys but have a %s + if not keys and re.findall('(?:[^%]|^)%[a-z]', self.msgid): + # Apparently the full dictionary is the parameter + params = self._copy_param(dict_param) + else: + params = {} + # Save our existing parameters as defaults to protect + # ourselves from losing values if we are called through an + # (erroneous) chain that builds a valid Message with + # arguments, and then does something like "msg % kwds" + # where kwds is an empty dictionary. + src = {} + if isinstance(self.params, dict): + src.update(self.params) + src.update(dict_param) + for key in keys: + params[key] = self._copy_param(src[key]) - return new_dict + return params - def __setstate__(self, state): - for (k, v) in state.items(): - setattr(self, k, v) + def _copy_param(self, param): + try: + return copy.deepcopy(param) + except TypeError: + # Fallback to casting to unicode this will handle the + # python code-like objects that can't be deep-copied + return six.text_type(param) - # operator overloads def __add__(self, other): - copied = copy.deepcopy(self) - copied._right_extra_msg += other.__str__() - return copied + msg = _('Message objects do not support addition.') + raise TypeError(msg) def __radd__(self, other): - copied = copy.deepcopy(self) - copied._left_extra_msg += other.__str__() - return copied + return self.__add__(other) - 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 __str__(self): + # NOTE(luisg): Logging in python 2.6 tries to str() log records, + # and it expects specifically a UnicodeError in order to proceed. + msg = _('Message objects do not support str() because they may ' + 'contain non-ascii characters. ' + 'Please use unicode() or translate() instead.') + raise UnicodeError(msg) def get_available_languages(domain): @@ -317,49 +294,147 @@ def get_available_languages(domain): # 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 + # this check when the master list updates to >=1.0, and update all projects 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: language_list.append(i) + + # NOTE(luisg): Babel>=1.0,<1.3 has a bug where some OpenStack supported + # locales (e.g. 'zh_CN', and 'zh_TW') aren't supported even though they + # are perfectly legitimate locales: + # https://github.com/mitsuhiko/babel/issues/37 + # In Babel 1.3 they fixed the bug and they support these locales, but + # they are still not explicitly "listed" by locale_identifiers(). + # That is why we add the locales here explicitly if necessary so that + # they are listed as supported. + aliases = {'zh': 'zh_CN', + 'zh_Hant_HK': 'zh_HK', + 'zh_Hant': 'zh_TW', + 'fil': 'tl_PH'} + for (locale, alias) in six.iteritems(aliases): + if locale in language_list and alias not in language_list: + language_list.append(alias) + _AVAILABLE_LANGUAGES[domain] = language_list return copy.copy(language_list) -def get_localized_message(message, user_locale): - """Gets a localized version of the given message in the given locale.""" +def translate(obj, desired_locale=None): + """Gets the translated unicode representation of the given object. + + If the object is not translatable it is returned as-is. + If the locale is None the object is translated to the system locale. + + :param obj: the object to translate + :param desired_locale: the locale to translate the message to, if None the + default system locale will be used + :returns: the translated object in unicode, or the original object if + it could not be translated + """ + message = obj + if not isinstance(message, Message): + # If the object to translate is not already translatable, + # let's first get its unicode representation + message = six.text_type(obj) if isinstance(message, Message): - if user_locale: - message.locale = user_locale - return six.text_type(message) - else: - return message + # Even after unicoding() we still need to check if we are + # running with translatable unicode before translating + return message.translate(desired_locale) + return obj -class LocaleHandler(logging.Handler): - """Handler that can have a locale associated to translate Messages. +def _translate_args(args, desired_locale=None): + """Translates all the translatable elements of the given arguments object. - 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. + This method is used for translating the translatable values in method + arguments which include values of tuples or dictionaries. + If the object is not a tuple or a dictionary the object itself is + translated if it is translatable. + + If the locale is None the object is translated to the system locale. + + :param args: the args to translate + :param desired_locale: the locale to translate the args to, if None the + default system locale will be used + :returns: a new args object with the translated contents of the original + """ + if isinstance(args, tuple): + return tuple(translate(v, desired_locale) for v in args) + if isinstance(args, dict): + translated_dict = {} + for (k, v) in six.iteritems(args): + translated_v = translate(v, desired_locale) + translated_dict[k] = translated_v + return translated_dict + return translate(args, desired_locale) + + +class TranslationHandler(handlers.MemoryHandler): + """Handler that translates records before logging them. + + The TranslationHandler takes a locale and a target logging.Handler object + to forward LogRecord objects to after translating them. This handler + depends on Message objects being logged, instead of regular strings. + + The handler can be configured declaratively in the logging.conf as follows: + + [handlers] + keys = translatedlog, translator + + [handler_translatedlog] + class = handlers.WatchedFileHandler + args = ('/var/log/api-localized.log',) + formatter = context + + [handler_translator] + class = openstack.common.log.TranslationHandler + target = translatedlog + args = ('zh_CN',) + + If the specified locale is not available in the system, the handler will + log in the default locale. """ - def __init__(self, locale, target): - """Initialize a LocaleHandler + def __init__(self, locale=None, target=None): + """Initialize a TranslationHandler :param locale: locale to use for translating messages :param target: logging.Handler object to forward LogRecord objects to after translation """ - logging.Handler.__init__(self) + # NOTE(luisg): In order to allow this handler to be a wrapper for + # other handlers, such as a FileHandler, and still be able to + # configure it using logging.conf, this handler has to extend + # MemoryHandler because only the MemoryHandlers' logging.conf + # parsing is implemented such that it accepts a target handler. + handlers.MemoryHandler.__init__(self, capacity=0, target=target) self.locale = locale - self.target = target + + def setFormatter(self, fmt): + self.target.setFormatter(fmt) def emit(self, record): - if isinstance(record.msg, Message): - # set the locale and resolve to a string - record.msg.locale = self.locale + # We save the message from the original record to restore it + # after translation, so other handlers are not affected by this + original_msg = record.msg + original_args = record.args + + try: + self._translate_and_log_record(record) + finally: + record.msg = original_msg + record.args = original_args + + def _translate_and_log_record(self, record): + record.msg = translate(record.msg, self.locale) + + # In addition to translating the message, we also need to translate + # arguments that were passed to the log method that were not part + # of the main message e.g., log.info(_('Some message %s'), this_one)) + record.args = _translate_args(record.args, self.locale) self.target.emit(record) diff --git a/cinderclient/openstack/common/importutils.py b/cinderclient/openstack/common/importutils.py index 7a303f9..4fd9ae2 100644 --- a/cinderclient/openstack/common/importutils.py +++ b/cinderclient/openstack/common/importutils.py @@ -1,5 +1,3 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - # Copyright 2011 OpenStack Foundation. # All Rights Reserved. # diff --git a/cinderclient/openstack/common/py3kcompat/__init__.py b/cinderclient/openstack/common/py3kcompat/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/cinderclient/openstack/common/py3kcompat/__init__.py diff --git a/cinderclient/openstack/common/py3kcompat/urlutils.py b/cinderclient/openstack/common/py3kcompat/urlutils.py new file mode 100644 index 0000000..84e457a --- /dev/null +++ b/cinderclient/openstack/common/py3kcompat/urlutils.py @@ -0,0 +1,67 @@ +# +# Copyright 2013 Canonical Ltd. +# All Rights Reserved. +# +# 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. +# + +""" +Python2/Python3 compatibility layer for OpenStack +""" + +import six + +if six.PY3: + # python3 + import urllib.error + import urllib.parse + import urllib.request + + urlencode = urllib.parse.urlencode + urljoin = urllib.parse.urljoin + quote = urllib.parse.quote + quote_plus = urllib.parse.quote_plus + parse_qsl = urllib.parse.parse_qsl + unquote = urllib.parse.unquote + unquote_plus = urllib.parse.unquote_plus + urlparse = urllib.parse.urlparse + urlsplit = urllib.parse.urlsplit + urlunsplit = urllib.parse.urlunsplit + SplitResult = urllib.parse.SplitResult + + urlopen = urllib.request.urlopen + URLError = urllib.error.URLError + pathname2url = urllib.request.pathname2url +else: + # python2 + import urllib + import urllib2 + import urlparse + + urlencode = urllib.urlencode + quote = urllib.quote + quote_plus = urllib.quote_plus + unquote = urllib.unquote + unquote_plus = urllib.unquote_plus + + parse = urlparse + parse_qsl = parse.parse_qsl + urljoin = parse.urljoin + urlparse = parse.urlparse + urlsplit = parse.urlsplit + urlunsplit = parse.urlunsplit + SplitResult = parse.SplitResult + + urlopen = urllib2.urlopen + URLError = urllib2.URLError + pathname2url = urllib.pathname2url diff --git a/cinderclient/openstack/common/strutils.py b/cinderclient/openstack/common/strutils.py index 7c9fcec..ee71db0 100644 --- a/cinderclient/openstack/common/strutils.py +++ b/cinderclient/openstack/common/strutils.py @@ -1,5 +1,3 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - # Copyright 2011 OpenStack Foundation. # All Rights Reserved. # @@ -25,7 +23,7 @@ import unicodedata import six -from cinderclient.openstack.common.gettextutils import _ # noqa +from cinderclient.openstack.common.gettextutils import _ # Used for looking up extensions of text @@ -60,12 +58,12 @@ def int_from_bool_as_string(subject): return bool_from_string(subject) and 1 or 0 -def bool_from_string(subject, strict=False): +def bool_from_string(subject, strict=False, default=False): """Interpret a string as a boolean. A case-insensitive match is performed such that strings matching 't', 'true', 'on', 'y', 'yes', or '1' are considered True and, when - `strict=False`, anything else is considered False. + `strict=False`, anything else returns the value specified by 'default'. Useful for JSON-decoded stuff and config file parsing. @@ -90,7 +88,7 @@ def bool_from_string(subject, strict=False): 'acceptable': acceptable} raise ValueError(msg) else: - return False + return default def safe_decode(text, incoming=None, errors='strict'): @@ -101,7 +99,7 @@ def safe_decode(text, incoming=None, errors='strict'): values http://docs.python.org/2/library/codecs.html :returns: text or a unicode `incoming` encoded representation of it. - :raises TypeError: If text is not an isntance of str + :raises TypeError: If text is not an instance of str """ if not isinstance(text, six.string_types): raise TypeError("%s can't be decoded" % type(text)) @@ -144,7 +142,7 @@ def safe_encode(text, incoming=None, values http://docs.python.org/2/library/codecs.html :returns: text or a bytestring `encoding` encoded representation of it. - :raises TypeError: If text is not an isntance of str + :raises TypeError: If text is not an instance of str """ if not isinstance(text, six.string_types): raise TypeError("%s can't be encoded" % type(text)) @@ -154,11 +152,17 @@ def safe_encode(text, incoming=None, sys.getdefaultencoding()) if isinstance(text, six.text_type): - return text.encode(encoding, errors) + if six.PY3: + return text.encode(encoding, errors).decode(incoming) + else: + return text.encode(encoding, errors) elif text and encoding != incoming: # Decode text before encoding it with `encoding` text = safe_decode(text, incoming, errors) - return text.encode(encoding, errors) + if six.PY3: + return text.encode(encoding, errors).decode(incoming) + else: + return text.encode(encoding, errors) return text diff --git a/tools/install_venv_common.py b/tools/install_venv_common.py index 92d66ae..46822e3 100644 --- a/tools/install_venv_common.py +++ b/tools/install_venv_common.py @@ -1,5 +1,3 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - # Copyright 2013 OpenStack Foundation # Copyright 2013 IBM Corp. # @@ -121,9 +119,6 @@ class InstallVenv(object): self.pip_install('-r', self.requirements, '-r', self.test_requirements) - def post_process(self): - self.get_distro().post_process() - def parse_args(self, argv): """Parses command-line arguments.""" parser = optparse.OptionParser() @@ -156,14 +151,6 @@ class Distro(InstallVenv): ' requires virtualenv, please install it using your' ' favorite package management tool' % self.project) - def post_process(self): - """Any distribution-specific post-processing gets done here. - - In particular, this is useful for applying patches to code inside - the venv. - """ - pass - class Fedora(Distro): """This covers all Fedora-based distributions. @@ -175,10 +162,6 @@ class Fedora(Distro): return self.run_command_with_code(['rpm', '-q', pkg], check_exit_code=False)[1] == 0 - def apply_patch(self, originalfile, patchfile): - self.run_command(['patch', '-N', originalfile, patchfile], - check_exit_code=False) - def install_virtualenv(self): if self.check_cmd('virtualenv'): return @@ -187,27 +170,3 @@ class Fedora(Distro): self.die("Please install 'python-virtualenv'.") super(Fedora, self).install_virtualenv() - - def post_process(self): - """Workaround for a bug in eventlet. - - This currently affects RHEL6.1, but the fix can safely be - applied to all RHEL and Fedora distributions. - - This can be removed when the fix is applied upstream. - - Nova: https://bugs.launchpad.net/nova/+bug/884915 - Upstream: https://bitbucket.org/eventlet/eventlet/issue/89 - RHEL: https://bugzilla.redhat.com/958868 - """ - - if os.path.exists('contrib/redhat-eventlet.patch'): - # Install "patch" program if it's not there - if not self.check_pkg('patch'): - self.die("Please install 'patch'.") - - # Apply the eventlet patch - self.apply_patch(os.path.join(self.venv, 'lib', self.py_version, - 'site-packages', - 'eventlet/green/subprocess.py'), - 'contrib/redhat-eventlet.patch') |