diff --git a/keystoneclient/access.py b/keystoneclient/access.py index c7cd115c..6786674a 100644 --- a/keystoneclient/access.py +++ b/keystoneclient/access.py @@ -19,6 +19,7 @@ import datetime from oslo.utils import timeutils +from keystoneclient.i18n import _ from keystoneclient import service_catalog @@ -63,7 +64,7 @@ class AccessInfo(dict): else: auth_ref = AccessInfoV2(**kwargs) else: - raise NotImplementedError('Unrecognized auth response') + raise NotImplementedError(_('Unrecognized auth response')) else: auth_ref = AccessInfoV2(**kwargs) diff --git a/keystoneclient/auth/base.py b/keystoneclient/auth/base.py index 66e6a186..fda9d9a9 100644 --- a/keystoneclient/auth/base.py +++ b/keystoneclient/auth/base.py @@ -17,6 +17,8 @@ import six import stevedore from keystoneclient import exceptions +from keystoneclient.i18n import _ + # NOTE(jamielennox): The AUTH_INTERFACE is a special value that can be # requested from get_endpoint. If a plugin receives this as the value of @@ -40,7 +42,7 @@ def get_plugin_class(name): name=name, invoke_on_load=False) except RuntimeError: - msg = 'The plugin %s could not be found' % name + msg = _('The plugin %s could not be found') % name raise exceptions.NoMatchingPlugin(msg) return mgr.driver diff --git a/keystoneclient/auth/identity/base.py b/keystoneclient/auth/identity/base.py index a58de2a6..aae24c32 100644 --- a/keystoneclient/auth/identity/base.py +++ b/keystoneclient/auth/identity/base.py @@ -19,6 +19,7 @@ import six from keystoneclient import _discover from keystoneclient.auth import base from keystoneclient import exceptions +from keystoneclient.i18n import _LW from keystoneclient import utils LOG = logging.getLogger(__name__) @@ -181,9 +182,9 @@ class BaseIdentityPlugin(base.BaseAuthPlugin): return self.auth_url if not service_type: - LOG.warn('Plugin cannot return an endpoint without knowing the ' - 'service type that is required. Add service_type to ' - 'endpoint filtering data.') + LOG.warn(_LW('Plugin cannot return an endpoint without knowing ' + 'the service type that is required. Add service_type ' + 'to endpoint filtering data.')) return None if not interface: @@ -216,8 +217,9 @@ class BaseIdentityPlugin(base.BaseAuthPlugin): # NOTE(jamielennox): Again if we can't contact the server we fall # back to just returning the URL from the catalog. This may not be # the best default but we need it for now. - LOG.warn('Failed to contact the endpoint at %s for discovery. ' - 'Fallback to using that endpoint as the base url.', url) + LOG.warn(_LW('Failed to contact the endpoint at %s for discovery. ' + 'Fallback to using that endpoint as the base url.'), + url) else: url = disc.url_for(version) diff --git a/keystoneclient/auth/identity/generic/base.py b/keystoneclient/auth/identity/generic/base.py index 94d48ec8..631eebda 100644 --- a/keystoneclient/auth/identity/generic/base.py +++ b/keystoneclient/auth/identity/generic/base.py @@ -20,6 +20,8 @@ import six.moves.urllib.parse as urlparse from keystoneclient import _discover from keystoneclient.auth.identity import base from keystoneclient import exceptions +from keystoneclient.i18n import _, _LW + LOG = logging.getLogger(__name__) @@ -127,9 +129,9 @@ class BaseGenericPlugin(base.BaseIdentityPlugin): except (exceptions.DiscoveryFailure, exceptions.HTTPError, exceptions.ConnectionError): - LOG.warn('Discovering versions from the identity service failed ' - 'when creating the password plugin. Attempting to ' - 'determine version from URL.') + LOG.warn(_LW('Discovering versions from the identity service ' + 'failed when creating the password plugin. ' + 'Attempting to determine version from URL.')) url_parts = urlparse.urlparse(self.auth_url) path = url_parts.path.lower() @@ -163,7 +165,7 @@ class BaseGenericPlugin(base.BaseIdentityPlugin): return plugin # so there were no URLs that i could use for auth of any version. - msg = 'Could not determine a suitable URL for the plugin' + msg = _('Could not determine a suitable URL for the plugin') raise exceptions.DiscoveryFailure(msg) def get_auth_ref(self, session, **kwargs): diff --git a/keystoneclient/auth/identity/v3.py b/keystoneclient/auth/identity/v3.py index 99d7562f..e0f6b08c 100644 --- a/keystoneclient/auth/identity/v3.py +++ b/keystoneclient/auth/identity/v3.py @@ -19,6 +19,7 @@ import six from keystoneclient import access from keystoneclient.auth.identity import base from keystoneclient import exceptions +from keystoneclient.i18n import _ from keystoneclient import utils _logger = logging.getLogger(__name__) @@ -84,18 +85,17 @@ class Auth(base.BaseIdentityPlugin): ident[name] = auth_data if not ident: - raise exceptions.AuthorizationFailure('Authentication method ' - 'required (e.g. password)') + raise exceptions.AuthorizationFailure( + _('Authentication method required (e.g. password)')) mutual_exclusion = [bool(self.domain_id or self.domain_name), bool(self.project_id or self.project_name), bool(self.trust_id)] if sum(mutual_exclusion) > 1: - raise exceptions.AuthorizationFailure('Authentication cannot be ' - 'scoped to multiple ' - 'targets. Pick one of: ' - 'project, domain or trust') + raise exceptions.AuthorizationFailure( + _('Authentication cannot be scoped to multiple targets. Pick ' + 'one of: project, domain or trust')) if self.domain_id: body['auth']['scope'] = {'domain': {'id': self.domain_id}} @@ -165,7 +165,7 @@ class AuthMethod(object): setattr(self, param, kwargs.pop(param, None)) if kwargs: - msg = "Unexpected Attributes: %s" % ", ".join(kwargs.keys()) + msg = _("Unexpected Attributes: %s") % ", ".join(kwargs.keys()) raise AttributeError(msg) @classmethod diff --git a/keystoneclient/exceptions.py b/keystoneclient/exceptions.py index 0370dd6e..d516a251 100644 --- a/keystoneclient/exceptions.py +++ b/keystoneclient/exceptions.py @@ -16,6 +16,7 @@ Exception definitions. """ +from keystoneclient.i18n import _ from keystoneclient.openstack.common.apiclient.exceptions import * # noqa # NOTE(akurilin): This alias should be left here to support backwards @@ -31,7 +32,7 @@ class CertificateConfigError(Exception): """Error reading the certificate.""" def __init__(self, output): self.output = output - msg = 'Unable to load certificate.' + msg = _('Unable to load certificate.') super(CertificateConfigError, self).__init__(msg) @@ -39,7 +40,7 @@ class CMSError(Exception): """Error reading the certificate.""" def __init__(self, output): self.output = output - msg = 'Unable to sign or verify data.' + msg = _('Unable to sign or verify data.') super(CMSError, self).__init__(msg) diff --git a/keystoneclient/i18n.py b/keystoneclient/i18n.py new file mode 100644 index 00000000..fd1c03a0 --- /dev/null +++ b/keystoneclient/i18n.py @@ -0,0 +1,37 @@ +# Copyright 2014 IBM Corp. +# +# 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. + +"""oslo.i18n integration module. + +See http://docs.openstack.org/developer/oslo.i18n/usage.html . + +""" + +from oslo import i18n + + +_translators = i18n.TranslatorFactory(domain='keystoneclient') + +# 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 = _translators.log_info +_LW = _translators.log_warning +_LE = _translators.log_error +_LC = _translators.log_critical diff --git a/keystoneclient/service_catalog.py b/keystoneclient/service_catalog.py index cf85ed7a..7c9085b3 100644 --- a/keystoneclient/service_catalog.py +++ b/keystoneclient/service_catalog.py @@ -21,6 +21,7 @@ import abc import six from keystoneclient import exceptions +from keystoneclient.i18n import _ from keystoneclient import utils @@ -36,7 +37,7 @@ class ServiceCatalog(object): elif ServiceCatalogV2.is_valid(resource_dict): return ServiceCatalogV2(resource_dict, region_name) else: - raise NotImplementedError('Unrecognized auth response') + raise NotImplementedError(_('Unrecognized auth response')) def __init__(self, region_name=None): self._region_name = region_name @@ -208,7 +209,7 @@ class ServiceCatalog(object): """ if not self.get_data(): - raise exceptions.EmptyCatalog('The service catalog is empty.') + raise exceptions.EmptyCatalog(_('The service catalog is empty.')) urls = self.get_urls(attr=attr, filter_value=filter_value, @@ -222,12 +223,30 @@ class ServiceCatalog(object): except Exception: pass - msg = '%s endpoint for %s service' % (endpoint_type, service_type) - if service_name: - msg += ' named %s' % service_name - if region_name: - msg += ' in %s region' % region_name - msg += ' not found' + if service_name and region_name: + msg = (_('%(endpoint_type)s endpoint for %(service_type)s service ' + 'named %(service_name)s in %(region_name)s region not ' + 'found') % + {'endpoint_type': endpoint_type, + 'service_type': service_type, 'service_name': service_name, + 'region_name': region_name}) + elif service_name: + msg = (_('%(endpoint_type)s endpoint for %(service_type)s service ' + 'named %(service_name)s not found') % + {'endpoint_type': endpoint_type, + 'service_type': service_type, + 'service_name': service_name}) + elif region_name: + msg = (_('%(endpoint_type)s endpoint for %(service_type)s service ' + 'in %(region_name)s region not found') % + {'endpoint_type': endpoint_type, + 'service_type': service_type, 'region_name': region_name}) + else: + msg = (_('%(endpoint_type)s endpoint for %(service_type)s service ' + 'not found') % + {'endpoint_type': endpoint_type, + 'service_type': service_type}) + raise exceptions.EndpointNotFound(msg) @abc.abstractmethod diff --git a/keystoneclient/session.py b/keystoneclient/session.py index aab90f94..8a1aeb75 100644 --- a/keystoneclient/session.py +++ b/keystoneclient/session.py @@ -25,6 +25,7 @@ import six from six.moves import urllib from keystoneclient import exceptions +from keystoneclient.i18n import _, _LI, _LW from keystoneclient import utils osprofiler_web = importutils.try_import("osprofiler.web") @@ -40,10 +41,10 @@ def _positive_non_zero_float(argument_value): try: value = float(argument_value) except ValueError: - msg = "%s must be a float" % argument_value + msg = _("%s must be a float") % argument_value raise argparse.ArgumentTypeError(msg) if value <= 0: - msg = "%s must be greater than 0" % argument_value + msg = _("%s must be greater than 0") % argument_value raise argparse.ArgumentTypeError(msg) return value @@ -274,7 +275,7 @@ class Session(object): token = self.get_token(auth) if not token: - raise exceptions.AuthorizationFailure("No token Available") + raise exceptions.AuthorizationFailure(_("No token Available")) headers['X-Auth-Token'] = token @@ -372,20 +373,20 @@ class Session(object): try: resp = self.session.request(method, url, **kwargs) except requests.exceptions.SSLError: - msg = 'SSL exception connecting to %s' % url + msg = _('SSL exception connecting to %s') % url raise exceptions.SSLError(msg) except requests.exceptions.Timeout: - msg = 'Request to %s timed out' % url + msg = _('Request to %s timed out') % url raise exceptions.RequestTimeout(msg) except requests.exceptions.ConnectionError: - msg = 'Unable to establish connection to %s' % url + msg = _('Unable to establish connection to %s') % url raise exceptions.ConnectionRefused(msg) except (exceptions.RequestTimeout, exceptions.ConnectionRefused) as e: if connect_retries <= 0: raise - _logger.info('Failure: %s. Retrying in %.1fs.', - e, connect_retry_delay) + _logger.info(_LI('Failure: %(e)s. Retrying in %(delay).1fs.'), + {'e': e, 'delay': connect_retry_delay}) time.sleep(connect_retry_delay) return self._send_request( @@ -411,8 +412,8 @@ class Session(object): try: location = resp.headers['location'] except KeyError: - _logger.warn("Failed to redirect request to %s as new " - "location was not provided.", resp.url) + _logger.warn(_LW("Failed to redirect request to %s as new " + "location was not provided."), resp.url) else: # NOTE(jamielennox): We don't pass through connect_retry_delay. # This request actually worked so we can reset the delay count. @@ -508,13 +509,13 @@ class Session(object): auth = self.auth if not auth: - raise exceptions.MissingAuthPlugin("Token Required") + raise exceptions.MissingAuthPlugin(_("Token Required")) try: return auth.get_token(self) except exceptions.HttpError as exc: - raise exceptions.AuthorizationFailure("Authentication failure: " - "%s" % exc) + raise exceptions.AuthorizationFailure( + _("Authentication failure: %s") % exc) def get_endpoint(self, auth=None, **kwargs): """Get an endpoint as provided by the auth plugin. @@ -531,8 +532,9 @@ class Session(object): auth = self.auth if not auth: - raise exceptions.MissingAuthPlugin('An auth plugin is required to ' - 'determine the endpoint URL.') + raise exceptions.MissingAuthPlugin( + _('An auth plugin is required to determine the endpoint ' + 'URL.')) return auth.get_endpoint(self, **kwargs) @@ -543,7 +545,7 @@ class Session(object): auth = self.auth if not auth: - msg = 'Auth plugin not available to invalidate' + msg = _('Auth plugin not available to invalidate') raise exceptions.MissingAuthPlugin(msg) return auth.invalidate() diff --git a/requirements.txt b/requirements.txt index 99f6a23e..dfee64b3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,6 +9,7 @@ Babel>=1.3 iso8601>=0.1.9 netaddr>=0.7.12 oslo.config>=1.4.0 # Apache-2.0 +oslo.i18n>=1.0.0 # Apache-2.0 oslo.serialization>=1.0.0 # Apache-2.0 oslo.utils>=1.0.0 # Apache-2.0 PrettyTable>=0.7,<0.8 diff --git a/tox.ini b/tox.ini index f8a984dc..57db9c0a 100644 --- a/tox.ini +++ b/tox.ini @@ -45,3 +45,6 @@ exclude = .venv,.tox,dist,doc,*egg,build,*openstack/common* commands= python setup.py build_sphinx +[hacking] +import_exceptions = + keystoneclient.i18n