summaryrefslogtreecommitdiff
path: root/openstackclient
diff options
context:
space:
mode:
Diffstat (limited to 'openstackclient')
-rw-r--r--openstackclient/openstack/common/__init__.py17
-rw-r--r--openstackclient/openstack/common/gettextutils.py232
-rw-r--r--openstackclient/openstack/common/iniparser.py130
-rw-r--r--openstackclient/openstack/common/strutils.py25
4 files changed, 155 insertions, 249 deletions
diff --git a/openstackclient/openstack/common/__init__.py b/openstackclient/openstack/common/__init__.py
index e69de29b..d1223eaf 100644
--- a/openstackclient/openstack/common/__init__.py
+++ b/openstackclient/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/openstackclient/openstack/common/gettextutils.py b/openstackclient/openstack/common/gettextutils.py
index 2b158548..6f573a7f 100644
--- a/openstackclient/openstack/common/gettextutils.py
+++ b/openstackclient/openstack/common/gettextutils.py
@@ -28,70 +28,135 @@ import gettext
import locale
from logging import handlers
import os
-import re
from babel import localedata
import six
-_localedir = os.environ.get('openstackclient'.upper() + '_LOCALEDIR')
-_t = gettext.translation('openstackclient', 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('openstackclient' + '-log-' + level,
- localedir=_localedir,
- fallback=True))
- for level in ['info', 'warning', 'error', 'critical']
-)
-
_AVAILABLE_LANGUAGES = {}
+
+# FIXME(dhellmann): Remove this when moving to oslo.i18n.
USE_LAZY = False
-def enable_lazy():
- """Convenience function for configuring _() to use lazy gettext
-
- Call this at the start of execution to enable the gettextutils._
- function to use lazy gettext functionality. This is useful if
- your project is importing _ directly instead of using the
- gettextutils.install() way of importing the _ function.
+class TranslatorFactory(object):
+ """Create translator functions
"""
- global USE_LAZY
- USE_LAZY = True
+ def __init__(self, domain, lazy=False, localedir=None):
+ """Establish a set of translation functions for the domain.
+
+ :param domain: Name of translation domain,
+ specifying a message catalog.
+ :type domain: str
+ :param lazy: Delays translation until a message is emitted.
+ Defaults to False.
+ :type lazy: Boolean
+ :param localedir: Directory with translation catalogs.
+ :type localedir: str
+ """
+ self.domain = domain
+ self.lazy = lazy
+ if localedir is None:
+ localedir = os.environ.get(domain.upper() + '_LOCALEDIR')
+ self.localedir = localedir
-def _(msg):
- if USE_LAZY:
- return Message(msg, domain='openstackclient')
- else:
- if six.PY3:
- return _t.gettext(msg)
- return _t.ugettext(msg)
+ def _make_translation_func(self, domain=None):
+ """Return a new translation function ready for use.
+ Takes into account whether or not lazy translation is being
+ done.
-def _log_translation(msg, level):
- """Build a single translation of a log message
- """
- if USE_LAZY:
- return Message(msg, domain='openstackclient' + '-log-' + level)
- else:
- translator = _t_log_levels[level]
+ The domain can be specified to override the default from the
+ factory, but the localedir from the factory is always used
+ because we assume the log-level translation catalogs are
+ installed in the same directory as the main application
+ catalog.
+
+ """
+ if domain is None:
+ domain = self.domain
+ if self.lazy:
+ return functools.partial(Message, domain=domain)
+ t = gettext.translation(
+ domain,
+ localedir=self.localedir,
+ fallback=True,
+ )
if six.PY3:
- return translator.gettext(msg)
- return translator.ugettext(msg)
+ return t.gettext
+ return t.ugettext
+
+ @property
+ def primary(self):
+ "The default translation function."
+ return self._make_translation_func()
+
+ def _make_log_translation_func(self, level):
+ return self._make_translation_func(self.domain + '-log-' + level)
+
+ @property
+ def log_info(self):
+ "Translate info-level log messages."
+ return self._make_log_translation_func('info')
+
+ @property
+ def log_warning(self):
+ "Translate warning-level log messages."
+ return self._make_log_translation_func('warning')
+
+ @property
+ def log_error(self):
+ "Translate error-level log messages."
+ return self._make_log_translation_func('error')
+
+ @property
+ def log_critical(self):
+ "Translate critical-level log messages."
+ return self._make_log_translation_func('critical')
+
+
+# NOTE(dhellmann): When this module moves out of the incubator into
+# oslo.i18n, these global variables can be moved to an integration
+# module within each application.
+
+# Create the global translation functions.
+_translators = TranslatorFactory('openstackclient')
+
+# The primary translation function using the well-known name "_"
+_ = _translators.primary
# 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')
+_LI = _translators.log_info
+_LW = _translators.log_warning
+_LE = _translators.log_error
+_LC = _translators.log_critical
+
+# NOTE(dhellmann): End of globals that will move to the application's
+# integration module.
+
+
+def enable_lazy():
+ """Convenience function for configuring _() to use lazy gettext
+
+ Call this at the start of execution to enable the gettextutils._
+ function to use lazy gettext functionality. This is useful if
+ your project is importing _ directly instead of using the
+ gettextutils.install() way of importing the _ function.
+ """
+ # FIXME(dhellmann): This function will be removed in oslo.i18n,
+ # because the TranslatorFactory makes it superfluous.
+ global _, _LI, _LW, _LE, _LC, USE_LAZY
+ tf = TranslatorFactory('openstackclient', lazy=True)
+ _ = tf.primary
+ _LI = tf.log_info
+ _LW = tf.log_warning
+ _LE = tf.log_error
+ _LC = tf.log_critical
+ USE_LAZY = True
def install(domain, lazy=False):
@@ -113,26 +178,9 @@ def install(domain, lazy=False):
any available locale.
"""
if lazy:
- # NOTE(mrodden): Lazy gettext functionality.
- #
- # 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.
- 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=domain)
-
from six import moves
- moves.builtins.__dict__['_'] = _lazy_gettext
+ tf = TranslatorFactory(domain, lazy=True)
+ moves.builtins.__dict__['_'] = tf.primary
else:
localedir = '%s_LOCALEDIR' % domain.upper()
if six.PY3:
@@ -248,47 +296,22 @@ class Message(six.text_type):
if other is None:
params = (other,)
elif isinstance(other, dict):
- params = self._trim_dictionary_parameters(other)
- 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:
+ # Merge the dictionaries
+ # Copy each item in case one does not support deep copy.
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])
-
+ 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 _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)
@@ -300,13 +323,14 @@ class Message(six.text_type):
def __radd__(self, other):
return self.__add__(other)
- 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)
+ if six.PY2:
+ 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):
diff --git a/openstackclient/openstack/common/iniparser.py b/openstackclient/openstack/common/iniparser.py
deleted file mode 100644
index 9bf399f0..00000000
--- a/openstackclient/openstack/common/iniparser.py
+++ /dev/null
@@ -1,130 +0,0 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright 2012 OpenStack LLC.
-#
-# 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.
-
-
-class ParseError(Exception):
- def __init__(self, message, lineno, line):
- self.msg = message
- self.line = line
- self.lineno = lineno
-
- def __str__(self):
- return 'at line %d, %s: %r' % (self.lineno, self.msg, self.line)
-
-
-class BaseParser(object):
- lineno = 0
- parse_exc = ParseError
-
- def _assignment(self, key, value):
- self.assignment(key, value)
- return None, []
-
- def _get_section(self, line):
- if line[-1] != ']':
- return self.error_no_section_end_bracket(line)
- if len(line) <= 2:
- return self.error_no_section_name(line)
-
- return line[1:-1]
-
- def _split_key_value(self, line):
- colon = line.find(':')
- equal = line.find('=')
- if colon < 0 and equal < 0:
- return self.error_invalid_assignment(line)
-
- if colon < 0 or (equal >= 0 and equal < colon):
- key, value = line[:equal], line[equal + 1:]
- else:
- key, value = line[:colon], line[colon + 1:]
-
- value = value.strip()
- if ((value and value[0] == value[-1]) and
- (value[0] == "\"" or value[0] == "'")):
- value = value[1:-1]
- return key.strip(), [value]
-
- def parse(self, lineiter):
- key = None
- value = []
-
- for line in lineiter:
- self.lineno += 1
-
- line = line.rstrip()
- if not line:
- # Blank line, ends multi-line values
- if key:
- key, value = self._assignment(key, value)
- continue
- elif line[0] in (' ', '\t'):
- # Continuation of previous assignment
- if key is None:
- self.error_unexpected_continuation(line)
- else:
- value.append(line.lstrip())
- continue
-
- if key:
- # Flush previous assignment, if any
- key, value = self._assignment(key, value)
-
- if line[0] == '[':
- # Section start
- section = self._get_section(line)
- if section:
- self.new_section(section)
- elif line[0] in '#;':
- self.comment(line[1:].lstrip())
- else:
- key, value = self._split_key_value(line)
- if not key:
- return self.error_empty_key(line)
-
- if key:
- # Flush previous assignment, if any
- self._assignment(key, value)
-
- def assignment(self, key, value):
- """Called when a full assignment is parsed"""
- raise NotImplementedError()
-
- def new_section(self, section):
- """Called when a new section is started"""
- raise NotImplementedError()
-
- def comment(self, comment):
- """Called when a comment is parsed"""
- pass
-
- def error_invalid_assignment(self, line):
- raise self.parse_exc("No ':' or '=' found in assignment",
- self.lineno, line)
-
- def error_empty_key(self, line):
- raise self.parse_exc('Key cannot be empty', self.lineno, line)
-
- def error_unexpected_continuation(self, line):
- raise self.parse_exc('Unexpected continuation line',
- self.lineno, line)
-
- def error_no_section_end_bracket(self, line):
- raise self.parse_exc('Invalid section (must end with ])',
- self.lineno, line)
-
- def error_no_section_name(self, line):
- raise self.parse_exc('Empty section name', self.lineno, line)
diff --git a/openstackclient/openstack/common/strutils.py b/openstackclient/openstack/common/strutils.py
index 33fca54b..9d70264f 100644
--- a/openstackclient/openstack/common/strutils.py
+++ b/openstackclient/openstack/common/strutils.py
@@ -78,7 +78,7 @@ def bool_from_string(subject, strict=False, default=False):
Strings yielding False are 'f', 'false', 'off', 'n', 'no', or '0'.
"""
if not isinstance(subject, six.string_types):
- subject = str(subject)
+ subject = six.text_type(subject)
lowered = subject.strip().lower()
@@ -98,7 +98,8 @@ def bool_from_string(subject, strict=False, default=False):
def safe_decode(text, incoming=None, errors='strict'):
- """Decodes incoming str using `incoming` if they're not already unicode.
+ """Decodes incoming text/bytes string using `incoming` if they're not
+ already unicode.
:param incoming: Text's current encoding
:param errors: Errors handling policy. See here for valid
@@ -107,7 +108,7 @@ def safe_decode(text, incoming=None, errors='strict'):
representation of it.
:raises TypeError: If text is not an instance of str
"""
- if not isinstance(text, six.string_types):
+ if not isinstance(text, (six.string_types, six.binary_type)):
raise TypeError("%s can't be decoded" % type(text))
if isinstance(text, six.text_type):
@@ -137,7 +138,7 @@ def safe_decode(text, incoming=None, errors='strict'):
def safe_encode(text, incoming=None,
encoding='utf-8', errors='strict'):
- """Encodes incoming str/unicode using `encoding`.
+ """Encodes incoming text/bytes string using `encoding`.
If incoming is not specified, text is expected to be encoded with
current python's default encoding. (`sys.getdefaultencoding`)
@@ -150,7 +151,7 @@ def safe_encode(text, incoming=None,
representation of it.
:raises TypeError: If text is not an instance of str
"""
- if not isinstance(text, six.string_types):
+ if not isinstance(text, (six.string_types, six.binary_type)):
raise TypeError("%s can't be encoded" % type(text))
if not incoming:
@@ -158,19 +159,13 @@ def safe_encode(text, incoming=None,
sys.getdefaultencoding())
if isinstance(text, six.text_type):
- if six.PY3:
- return text.encode(encoding, errors).decode(incoming)
- else:
- return text.encode(encoding, errors)
+ return text.encode(encoding, errors)
elif text and encoding != incoming:
# Decode text before encoding it with `encoding`
text = safe_decode(text, incoming, errors)
- if six.PY3:
- return text.encode(encoding, errors).decode(incoming)
- else:
- return text.encode(encoding, errors)
-
- return text
+ return text.encode(encoding, errors)
+ else:
+ return text
def string_to_bytes(text, unit_system='IEC', return_int=False):