From 1553a1e78ec262b044ce99b418103c91b7b580f6 Mon Sep 17 00:00:00 2001 From: john-griffith Date: Wed, 12 Mar 2014 10:59:45 -0600 Subject: [PATCH] Sync log.py from oslo-incubator This pulls in the latest version of log.py from oslo-incubator which elminates the constant user_identity key error. Note that this sync does a full sync, in other words it uses the oslo update script and pulls in the log.py file and all of the associated dependendencies for that module. Current HEAD in OSLO: -------------------- Merge: d9ea4f0 fd33d1e Date: Wed Mar 12 17:00:13 2014 +0000 Merge "Fix gettextutil.Message handling of deep copy failures" -------------------- Change-Id: I7c9f8acd22787f9649a5a1e796238c7788a0484a Fixes-Bug: 1290503 --- cinder/openstack/common/__init__.py | 17 +++++ cinder/openstack/common/gettextutils.py | 95 +++++++++++++++++-------- cinder/openstack/common/importutils.py | 7 ++ cinder/openstack/common/jsonutils.py | 10 +-- cinder/openstack/common/log.py | 4 +- cinder/openstack/common/timeutils.py | 2 +- 6 files changed, 94 insertions(+), 41 deletions(-) diff --git a/cinder/openstack/common/__init__.py b/cinder/openstack/common/__init__.py index e69de29bb2d..d1223eaf765 100644 --- a/cinder/openstack/common/__init__.py +++ b/cinder/openstack/common/__init__.py @@ -0,0 +1,17 @@ +# +# 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. + +import six + + +six.add_move(six.MovedModule('mox', 'mox', 'mox3.mox')) diff --git a/cinder/openstack/common/gettextutils.py b/cinder/openstack/common/gettextutils.py index 4a4b1b69754..4957e37a94d 100644 --- a/cinder/openstack/common/gettextutils.py +++ b/cinder/openstack/common/gettextutils.py @@ -23,11 +23,11 @@ Usual usage in an openstack.common module: """ import copy +import functools import gettext import locale from logging import handlers import os -import re from babel import localedata import six @@ -35,6 +35,17 @@ import six _localedir = os.environ.get('cinder'.upper() + '_LOCALEDIR') _t = gettext.translation('cinder', localedir=_localedir, fallback=True) +# We use separate translation catalogs for each log level, so set up a +# mapping between the log level name and the translator. The domain +# for the log level is project_name + "-log-" + log_level so messages +# for each level end up in their own catalog. +_t_log_levels = dict( + (level, gettext.translation('cinder' + '-log-' + level, + localedir=_localedir, + fallback=True)) + for level in ['info', 'warning', 'error', 'critical'] +) + _AVAILABLE_LANGUAGES = {} USE_LAZY = False @@ -60,6 +71,28 @@ def _(msg): return _t.ugettext(msg) +def _log_translation(msg, level): + """Build a single translation of a log message + """ + if USE_LAZY: + return Message(msg, domain='cinder' + '-log-' + level) + else: + translator = _t_log_levels[level] + if six.PY3: + return translator.gettext(msg) + return translator.ugettext(msg) + +# Translators for log levels. +# +# The abbreviated names are meant to reflect the usual use of a short +# name like '_'. The "L" is for "log" and the other letter comes from +# the level. +_LI = functools.partial(_log_translation, level='info') +_LW = functools.partial(_log_translation, level='warning') +_LE = functools.partial(_log_translation, level='error') +_LC = functools.partial(_log_translation, level='critical') + + def install(domain, lazy=False): """Install a _() function using the given translation domain. @@ -118,7 +151,8 @@ class Message(six.text_type): and can be treated as such. """ - def __new__(cls, msgid, msgtext=None, params=None, domain='cinder', *args): + def __new__(cls, msgid, msgtext=None, params=None, + domain='cinder', *args): """Create a new Message object. In order for translation to work gettext requires a message ID, this @@ -193,10 +227,11 @@ class Message(six.text_type): # 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 - unicode_mod = super(Message, self).__mod__(other) + params = self._sanitize_mod_params(other) + unicode_mod = super(Message, self).__mod__(params) modded = Message(self.msgid, msgtext=unicode_mod, - params=self._sanitize_mod_params(other), + params=params, domain=self.domain) return modded @@ -212,38 +247,22 @@ class Message(six.text_type): if other is None: params = (other,) elif isinstance(other, dict): - params = self._trim_dictionary_parameters(other) + # Merge the dictionaries + # Copy each item in case one does not support deep copy. + params = {} + if isinstance(self.params, dict): + for key, val in self.params.items(): + params[key] = self._copy_param(val) + for key, val in other.items(): + params[key] = self._copy_param(val) else: params = self._copy_param(other) return params - 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 = {} - for key in keys: - params[key] = self._copy_param(dict_param[key]) - - return params - def _copy_param(self, param): try: return copy.deepcopy(param) - except TypeError: + except Exception: # Fallback to casting to unicode this will handle the # python code-like objects that can't be deep-copied return six.text_type(param) @@ -287,9 +306,27 @@ def get_available_languages(domain): 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) diff --git a/cinder/openstack/common/importutils.py b/cinder/openstack/common/importutils.py index 4fd9ae2bc26..edc38050a5d 100644 --- a/cinder/openstack/common/importutils.py +++ b/cinder/openstack/common/importutils.py @@ -58,6 +58,13 @@ def import_module(import_str): return sys.modules[import_str] +def import_versioned_module(version, submodule=None): + module = 'cinder.v%s' % version + if submodule: + module = '.'.join((module, submodule)) + return import_module(module) + + def try_import(import_str, default=None): """Try to import a module and if it fails return default.""" try: diff --git a/cinder/openstack/common/jsonutils.py b/cinder/openstack/common/jsonutils.py index 205f710f098..dbfa46821f2 100644 --- a/cinder/openstack/common/jsonutils.py +++ b/cinder/openstack/common/jsonutils.py @@ -36,17 +36,9 @@ import functools import inspect import itertools import json -try: - import xmlrpclib -except ImportError: - # NOTE(jaypipes): xmlrpclib was renamed to xmlrpc.client in Python3 - # however the function and object call signatures - # remained the same. This whole try/except block should - # be removed and replaced with a call to six.moves once - # six 1.4.2 is released. See http://bit.ly/1bqrVzu - import xmlrpc.client as xmlrpclib import six +import six.moves.xmlrpc_client as xmlrpclib from cinder.openstack.common import gettextutils from cinder.openstack.common import importutils diff --git a/cinder/openstack/common/log.py b/cinder/openstack/common/log.py index fed683cf656..59d08a2f189 100644 --- a/cinder/openstack/common/log.py +++ b/cinder/openstack/common/log.py @@ -537,7 +537,7 @@ def _setup_logging_from_conf(project, version): if CONF.publish_errors: handler = importutils.import_object( - "openstack.common.log_handler.PublishErrorsHandler", + "cinder.openstack.common.log_handler.PublishErrorsHandler", logging.ERROR) log_root.addHandler(handler) @@ -650,7 +650,7 @@ class ContextFormatter(logging.Formatter): # NOTE(sdague): default the fancier formatting params # to an empty string so we don't throw an exception if # they get used - for key in ('instance', 'color'): + for key in ('instance', 'color', 'user_identity'): if key not in record.__dict__: record.__dict__[key] = '' diff --git a/cinder/openstack/common/timeutils.py b/cinder/openstack/common/timeutils.py index d5ed81d3e3e..52688a02687 100644 --- a/cinder/openstack/common/timeutils.py +++ b/cinder/openstack/common/timeutils.py @@ -114,7 +114,7 @@ def utcnow(): def iso8601_from_timestamp(timestamp): - """Returns a iso8601 formated date from timestamp.""" + """Returns a iso8601 formatted date from timestamp.""" return isotime(datetime.datetime.utcfromtimestamp(timestamp))