Merge "Sync with oslo-incubator 2640847"
This commit is contained in:
		| @@ -30,6 +30,7 @@ import six | ||||
| from six.moves.urllib import parse | ||||
|  | ||||
| from keystoneclient.openstack.common.apiclient import exceptions | ||||
| from keystoneclient.openstack.common.gettextutils import _ | ||||
| from keystoneclient.openstack.common import strutils | ||||
|  | ||||
|  | ||||
| @@ -219,7 +220,10 @@ class ManagerWithFind(BaseManager): | ||||
|         matches = self.findall(**kwargs) | ||||
|         num_matches = len(matches) | ||||
|         if num_matches == 0: | ||||
|             msg = "No %s matching %s." % (self.resource_class.__name__, kwargs) | ||||
|             msg = _("No %(name)s matching %(args)s.") % { | ||||
|                 'name': self.resource_class.__name__, | ||||
|                 'args': kwargs | ||||
|             } | ||||
|             raise exceptions.NotFound(msg) | ||||
|         elif num_matches > 1: | ||||
|             raise exceptions.NoUniqueMatch() | ||||
| @@ -373,7 +377,10 @@ class CrudManager(BaseManager): | ||||
|         num = len(rl) | ||||
|  | ||||
|         if num == 0: | ||||
|             msg = "No %s matching %s." % (self.resource_class.__name__, kwargs) | ||||
|             msg = _("No %(name)s matching %(args)s.") % { | ||||
|                 'name': self.resource_class.__name__, | ||||
|                 'args': kwargs | ||||
|             } | ||||
|             raise exceptions.NotFound(404, msg) | ||||
|         elif num > 1: | ||||
|             raise exceptions.NoUniqueMatch | ||||
| @@ -457,17 +464,22 @@ class Resource(object): | ||||
|     def __getattr__(self, k): | ||||
|         if k not in self.__dict__: | ||||
|             #NOTE(bcwaldon): disallow lazy-loading if already loaded once | ||||
|             if not self.is_loaded: | ||||
|                 self._get() | ||||
|             if not self.is_loaded(): | ||||
|                 self.get() | ||||
|                 return self.__getattr__(k) | ||||
|  | ||||
|             raise AttributeError(k) | ||||
|         else: | ||||
|             return self.__dict__[k] | ||||
|  | ||||
|     def _get(self): | ||||
|         # set _loaded first ... so if we have to bail, we know we tried. | ||||
|         self._loaded = True | ||||
|     def get(self): | ||||
|         """Support for lazy loading details. | ||||
|  | ||||
|         Some clients, such as novaclient have the option to lazy load the | ||||
|         details, details which can be loaded with this function. | ||||
|         """ | ||||
|         # set_loaded() first ... so if we have to bail, we know we tried. | ||||
|         self.set_loaded(True) | ||||
|         if not hasattr(self.manager, 'get'): | ||||
|             return | ||||
|  | ||||
| @@ -485,9 +497,11 @@ class Resource(object): | ||||
|             return self.id == other.id | ||||
|         return self._info == other._info | ||||
|  | ||||
|     @property | ||||
|     def is_loaded(self): | ||||
|         return self._loaded | ||||
|  | ||||
|     def set_loaded(self, val): | ||||
|         self._loaded = val | ||||
|  | ||||
|     def to_dict(self): | ||||
|         return copy.deepcopy(self._info) | ||||
|   | ||||
| @@ -36,6 +36,7 @@ except ImportError: | ||||
| import requests | ||||
|  | ||||
| from keystoneclient.openstack.common.apiclient import exceptions | ||||
| from keystoneclient.openstack.common.gettextutils import _ | ||||
| from keystoneclient.openstack.common import importutils | ||||
|  | ||||
|  | ||||
| @@ -228,7 +229,7 @@ class HTTPClient(object): | ||||
|                     **filter_args) | ||||
|                 if not (token and endpoint): | ||||
|                     raise exceptions.AuthorizationFailure( | ||||
|                         "Cannot find endpoint or token for request") | ||||
|                         _("Cannot find endpoint or token for request")) | ||||
|  | ||||
|         old_token_endpoint = (token, endpoint) | ||||
|         kwargs.setdefault("headers", {})["X-Auth-Token"] = token | ||||
| @@ -351,8 +352,12 @@ class BaseClient(object): | ||||
|         try: | ||||
|             client_path = version_map[str(version)] | ||||
|         except (KeyError, ValueError): | ||||
|             msg = "Invalid %s client version '%s'. must be one of: %s" % ( | ||||
|                   (api_name, version, ', '.join(version_map.keys()))) | ||||
|             msg = _("Invalid %(api_name)s client version '%(version)s'. " | ||||
|                     "Must be one of: %(version_map)s") % { | ||||
|                         'api_name': api_name, | ||||
|                         'version': version, | ||||
|                         'version_map': ', '.join(version_map.keys()) | ||||
|                     } | ||||
|             raise exceptions.UnsupportedVersion(msg) | ||||
|  | ||||
|         return importutils.import_class(client_path) | ||||
|   | ||||
| @@ -25,6 +25,8 @@ import sys | ||||
|  | ||||
| import six | ||||
|  | ||||
| from keystoneclient.openstack.common.gettextutils import _ | ||||
|  | ||||
|  | ||||
| class ClientException(Exception): | ||||
|     """The base exception class for all exceptions this library raises. | ||||
| @@ -36,7 +38,7 @@ class MissingArgs(ClientException): | ||||
|     """Supplied arguments are not sufficient for calling a function.""" | ||||
|     def __init__(self, missing): | ||||
|         self.missing = missing | ||||
|         msg = "Missing argument(s): %s" % ", ".join(missing) | ||||
|         msg = _("Missing arguments: %s") % ", ".join(missing) | ||||
|         super(MissingArgs, self).__init__(msg) | ||||
|  | ||||
|  | ||||
| @@ -69,7 +71,7 @@ class AuthPluginOptionsMissing(AuthorizationFailure): | ||||
|     """Auth plugin misses some options.""" | ||||
|     def __init__(self, opt_names): | ||||
|         super(AuthPluginOptionsMissing, self).__init__( | ||||
|             "Authentication failed. Missing options: %s" % | ||||
|             _("Authentication failed. Missing options: %s") % | ||||
|             ", ".join(opt_names)) | ||||
|         self.opt_names = opt_names | ||||
|  | ||||
| @@ -78,7 +80,7 @@ class AuthSystemNotFound(AuthorizationFailure): | ||||
|     """User has specified a AuthSystem that is not installed.""" | ||||
|     def __init__(self, auth_system): | ||||
|         super(AuthSystemNotFound, self).__init__( | ||||
|             "AuthSystemNotFound: %s" % repr(auth_system)) | ||||
|             _("AuthSystemNotFound: %s") % repr(auth_system)) | ||||
|         self.auth_system = auth_system | ||||
|  | ||||
|  | ||||
| @@ -101,7 +103,7 @@ class AmbiguousEndpoints(EndpointException): | ||||
|     """Found more than one matching endpoint in Service Catalog.""" | ||||
|     def __init__(self, endpoints=None): | ||||
|         super(AmbiguousEndpoints, self).__init__( | ||||
|             "AmbiguousEndpoints: %s" % repr(endpoints)) | ||||
|             _("AmbiguousEndpoints: %s") % repr(endpoints)) | ||||
|         self.endpoints = endpoints | ||||
|  | ||||
|  | ||||
| @@ -109,7 +111,7 @@ class HttpError(ClientException): | ||||
|     """The base exception class for all HTTP exceptions. | ||||
|     """ | ||||
|     http_status = 0 | ||||
|     message = "HTTP Error" | ||||
|     message = _("HTTP Error") | ||||
|  | ||||
|     def __init__(self, message=None, details=None, | ||||
|                  response=None, request_id=None, | ||||
| @@ -129,7 +131,7 @@ class HttpError(ClientException): | ||||
|  | ||||
| class HTTPRedirection(HttpError): | ||||
|     """HTTP Redirection.""" | ||||
|     message = "HTTP Redirection" | ||||
|     message = _("HTTP Redirection") | ||||
|  | ||||
|  | ||||
| class HTTPClientError(HttpError): | ||||
| @@ -137,7 +139,7 @@ class HTTPClientError(HttpError): | ||||
|  | ||||
|     Exception for cases in which the client seems to have erred. | ||||
|     """ | ||||
|     message = "HTTP Client Error" | ||||
|     message = _("HTTP Client Error") | ||||
|  | ||||
|  | ||||
| class HttpServerError(HttpError): | ||||
| @@ -146,7 +148,7 @@ class HttpServerError(HttpError): | ||||
|     Exception for cases in which the server is aware that it has | ||||
|     erred or is incapable of performing the request. | ||||
|     """ | ||||
|     message = "HTTP Server Error" | ||||
|     message = _("HTTP Server Error") | ||||
|  | ||||
|  | ||||
| class MultipleChoices(HTTPRedirection): | ||||
| @@ -156,7 +158,7 @@ class MultipleChoices(HTTPRedirection): | ||||
|     """ | ||||
|  | ||||
|     http_status = 300 | ||||
|     message = "Multiple Choices" | ||||
|     message = _("Multiple Choices") | ||||
|  | ||||
|  | ||||
| class BadRequest(HTTPClientError): | ||||
| @@ -165,7 +167,7 @@ class BadRequest(HTTPClientError): | ||||
|     The request cannot be fulfilled due to bad syntax. | ||||
|     """ | ||||
|     http_status = 400 | ||||
|     message = "Bad Request" | ||||
|     message = _("Bad Request") | ||||
|  | ||||
|  | ||||
| class Unauthorized(HTTPClientError): | ||||
| @@ -175,7 +177,7 @@ class Unauthorized(HTTPClientError): | ||||
|     is required and has failed or has not yet been provided. | ||||
|     """ | ||||
|     http_status = 401 | ||||
|     message = "Unauthorized" | ||||
|     message = _("Unauthorized") | ||||
|  | ||||
|  | ||||
| class PaymentRequired(HTTPClientError): | ||||
| @@ -184,7 +186,7 @@ class PaymentRequired(HTTPClientError): | ||||
|     Reserved for future use. | ||||
|     """ | ||||
|     http_status = 402 | ||||
|     message = "Payment Required" | ||||
|     message = _("Payment Required") | ||||
|  | ||||
|  | ||||
| class Forbidden(HTTPClientError): | ||||
| @@ -194,7 +196,7 @@ class Forbidden(HTTPClientError): | ||||
|     to it. | ||||
|     """ | ||||
|     http_status = 403 | ||||
|     message = "Forbidden" | ||||
|     message = _("Forbidden") | ||||
|  | ||||
|  | ||||
| class NotFound(HTTPClientError): | ||||
| @@ -204,7 +206,7 @@ class NotFound(HTTPClientError): | ||||
|     in the future. | ||||
|     """ | ||||
|     http_status = 404 | ||||
|     message = "Not Found" | ||||
|     message = _("Not Found") | ||||
|  | ||||
|  | ||||
| class MethodNotAllowed(HTTPClientError): | ||||
| @@ -214,7 +216,7 @@ class MethodNotAllowed(HTTPClientError): | ||||
|     by that resource. | ||||
|     """ | ||||
|     http_status = 405 | ||||
|     message = "Method Not Allowed" | ||||
|     message = _("Method Not Allowed") | ||||
|  | ||||
|  | ||||
| class NotAcceptable(HTTPClientError): | ||||
| @@ -224,7 +226,7 @@ class NotAcceptable(HTTPClientError): | ||||
|     acceptable according to the Accept headers sent in the request. | ||||
|     """ | ||||
|     http_status = 406 | ||||
|     message = "Not Acceptable" | ||||
|     message = _("Not Acceptable") | ||||
|  | ||||
|  | ||||
| class ProxyAuthenticationRequired(HTTPClientError): | ||||
| @@ -233,7 +235,7 @@ class ProxyAuthenticationRequired(HTTPClientError): | ||||
|     The client must first authenticate itself with the proxy. | ||||
|     """ | ||||
|     http_status = 407 | ||||
|     message = "Proxy Authentication Required" | ||||
|     message = _("Proxy Authentication Required") | ||||
|  | ||||
|  | ||||
| class RequestTimeout(HTTPClientError): | ||||
| @@ -242,7 +244,7 @@ class RequestTimeout(HTTPClientError): | ||||
|     The server timed out waiting for the request. | ||||
|     """ | ||||
|     http_status = 408 | ||||
|     message = "Request Timeout" | ||||
|     message = _("Request Timeout") | ||||
|  | ||||
|  | ||||
| class Conflict(HTTPClientError): | ||||
| @@ -252,7 +254,7 @@ class Conflict(HTTPClientError): | ||||
|     in the request, such as an edit conflict. | ||||
|     """ | ||||
|     http_status = 409 | ||||
|     message = "Conflict" | ||||
|     message = _("Conflict") | ||||
|  | ||||
|  | ||||
| class Gone(HTTPClientError): | ||||
| @@ -262,7 +264,7 @@ class Gone(HTTPClientError): | ||||
|     not be available again. | ||||
|     """ | ||||
|     http_status = 410 | ||||
|     message = "Gone" | ||||
|     message = _("Gone") | ||||
|  | ||||
|  | ||||
| class LengthRequired(HTTPClientError): | ||||
| @@ -272,7 +274,7 @@ class LengthRequired(HTTPClientError): | ||||
|     required by the requested resource. | ||||
|     """ | ||||
|     http_status = 411 | ||||
|     message = "Length Required" | ||||
|     message = _("Length Required") | ||||
|  | ||||
|  | ||||
| class PreconditionFailed(HTTPClientError): | ||||
| @@ -282,7 +284,7 @@ class PreconditionFailed(HTTPClientError): | ||||
|     put on the request. | ||||
|     """ | ||||
|     http_status = 412 | ||||
|     message = "Precondition Failed" | ||||
|     message = _("Precondition Failed") | ||||
|  | ||||
|  | ||||
| class RequestEntityTooLarge(HTTPClientError): | ||||
| @@ -291,7 +293,7 @@ class RequestEntityTooLarge(HTTPClientError): | ||||
|     The request is larger than the server is willing or able to process. | ||||
|     """ | ||||
|     http_status = 413 | ||||
|     message = "Request Entity Too Large" | ||||
|     message = _("Request Entity Too Large") | ||||
|  | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         try: | ||||
| @@ -308,7 +310,7 @@ class RequestUriTooLong(HTTPClientError): | ||||
|     The URI provided was too long for the server to process. | ||||
|     """ | ||||
|     http_status = 414 | ||||
|     message = "Request-URI Too Long" | ||||
|     message = _("Request-URI Too Long") | ||||
|  | ||||
|  | ||||
| class UnsupportedMediaType(HTTPClientError): | ||||
| @@ -318,7 +320,7 @@ class UnsupportedMediaType(HTTPClientError): | ||||
|     not support. | ||||
|     """ | ||||
|     http_status = 415 | ||||
|     message = "Unsupported Media Type" | ||||
|     message = _("Unsupported Media Type") | ||||
|  | ||||
|  | ||||
| class RequestedRangeNotSatisfiable(HTTPClientError): | ||||
| @@ -328,7 +330,7 @@ class RequestedRangeNotSatisfiable(HTTPClientError): | ||||
|     supply that portion. | ||||
|     """ | ||||
|     http_status = 416 | ||||
|     message = "Requested Range Not Satisfiable" | ||||
|     message = _("Requested Range Not Satisfiable") | ||||
|  | ||||
|  | ||||
| class ExpectationFailed(HTTPClientError): | ||||
| @@ -337,7 +339,7 @@ class ExpectationFailed(HTTPClientError): | ||||
|     The server cannot meet the requirements of the Expect request-header field. | ||||
|     """ | ||||
|     http_status = 417 | ||||
|     message = "Expectation Failed" | ||||
|     message = _("Expectation Failed") | ||||
|  | ||||
|  | ||||
| class UnprocessableEntity(HTTPClientError): | ||||
| @@ -347,7 +349,7 @@ class UnprocessableEntity(HTTPClientError): | ||||
|     errors. | ||||
|     """ | ||||
|     http_status = 422 | ||||
|     message = "Unprocessable Entity" | ||||
|     message = _("Unprocessable Entity") | ||||
|  | ||||
|  | ||||
| class InternalServerError(HttpServerError): | ||||
| @@ -356,7 +358,7 @@ class InternalServerError(HttpServerError): | ||||
|     A generic error message, given when no more specific message is suitable. | ||||
|     """ | ||||
|     http_status = 500 | ||||
|     message = "Internal Server Error" | ||||
|     message = _("Internal Server Error") | ||||
|  | ||||
|  | ||||
| # NotImplemented is a python keyword. | ||||
| @@ -367,7 +369,7 @@ class HttpNotImplemented(HttpServerError): | ||||
|     the ability to fulfill the request. | ||||
|     """ | ||||
|     http_status = 501 | ||||
|     message = "Not Implemented" | ||||
|     message = _("Not Implemented") | ||||
|  | ||||
|  | ||||
| class BadGateway(HttpServerError): | ||||
| @@ -377,7 +379,7 @@ class BadGateway(HttpServerError): | ||||
|     response from the upstream server. | ||||
|     """ | ||||
|     http_status = 502 | ||||
|     message = "Bad Gateway" | ||||
|     message = _("Bad Gateway") | ||||
|  | ||||
|  | ||||
| class ServiceUnavailable(HttpServerError): | ||||
| @@ -386,7 +388,7 @@ class ServiceUnavailable(HttpServerError): | ||||
|     The server is currently unavailable. | ||||
|     """ | ||||
|     http_status = 503 | ||||
|     message = "Service Unavailable" | ||||
|     message = _("Service Unavailable") | ||||
|  | ||||
|  | ||||
| class GatewayTimeout(HttpServerError): | ||||
| @@ -396,7 +398,7 @@ class GatewayTimeout(HttpServerError): | ||||
|     response from the upstream server. | ||||
|     """ | ||||
|     http_status = 504 | ||||
|     message = "Gateway Timeout" | ||||
|     message = _("Gateway Timeout") | ||||
|  | ||||
|  | ||||
| class HttpVersionNotSupported(HttpServerError): | ||||
| @@ -405,7 +407,7 @@ class HttpVersionNotSupported(HttpServerError): | ||||
|     The server does not support the HTTP protocol version used in the request. | ||||
|     """ | ||||
|     http_status = 505 | ||||
|     message = "HTTP Version Not Supported" | ||||
|     message = _("HTTP Version Not Supported") | ||||
|  | ||||
|  | ||||
| # _code_map contains all the classes that have http_status attribute. | ||||
| @@ -423,12 +425,17 @@ def from_response(response, method, url): | ||||
|     :param method: HTTP method used for request | ||||
|     :param url: URL used for request | ||||
|     """ | ||||
|  | ||||
|     req_id = response.headers.get("x-openstack-request-id") | ||||
|     #NOTE(hdd) true for older versions of nova and cinder | ||||
|     if not req_id: | ||||
|         req_id = response.headers.get("x-compute-request-id") | ||||
|     kwargs = { | ||||
|         "http_status": response.status_code, | ||||
|         "response": response, | ||||
|         "method": method, | ||||
|         "url": url, | ||||
|         "request_id": response.headers.get("x-compute-request-id"), | ||||
|         "request_id": req_id, | ||||
|     } | ||||
|     if "retry-after" in response.headers: | ||||
|         kwargs["retry_after"] = response.headers["retry-after"] | ||||
|   | ||||
| @@ -23,25 +23,122 @@ Usual usage in an openstack.common module: | ||||
| """ | ||||
|  | ||||
| import copy | ||||
| import functools | ||||
| 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 | ||||
|  | ||||
| _localedir = os.environ.get('keystoneclient'.upper() + '_LOCALEDIR') | ||||
| _t = gettext.translation('keystoneclient', localedir=_localedir, fallback=True) | ||||
|  | ||||
| _AVAILABLE_LANGUAGES = {} | ||||
|  | ||||
| # FIXME(dhellmann): Remove this when moving to oslo.i18n. | ||||
| USE_LAZY = False | ||||
|  | ||||
|  | ||||
| class TranslatorFactory(object): | ||||
|     """Create translator functions | ||||
|     """ | ||||
|  | ||||
|     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 _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. | ||||
|  | ||||
|         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 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('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 | ||||
|  | ||||
| # 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 | ||||
|  | ||||
| @@ -50,19 +147,18 @@ def enable_lazy(): | ||||
|     your project is importing _ directly instead of using the | ||||
|     gettextutils.install() way of importing the _ function. | ||||
|     """ | ||||
|     global USE_LAZY | ||||
|     # 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('keystoneclient', lazy=True) | ||||
|     _ = tf.primary | ||||
|     _LI = tf.log_info | ||||
|     _LW = tf.log_warning | ||||
|     _LE = tf.log_error | ||||
|     _LC = tf.log_critical | ||||
|     USE_LAZY = True | ||||
|  | ||||
|  | ||||
| def _(msg): | ||||
|     if USE_LAZY: | ||||
|         return Message(msg, 'keystoneclient') | ||||
|     else: | ||||
|         if six.PY3: | ||||
|             return _t.gettext(msg) | ||||
|         return _t.ugettext(msg) | ||||
|  | ||||
|  | ||||
| def install(domain, lazy=False): | ||||
|     """Install a _() function using the given translation domain. | ||||
|  | ||||
| @@ -82,31 +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. | ||||
|         # | ||||
|         # 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) | ||||
|  | ||||
|         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: | ||||
| @@ -118,182 +192,145 @@ 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 | ||||
| class Message(six.text_type): | ||||
|     """A Message object is a unicode object that can be translated. | ||||
|  | ||||
|     @property | ||||
|     def data(self): | ||||
|         # NOTE(mrodden): this should always resolve to a unicode string | ||||
|         # that best represents the state of the message currently | ||||
|     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. | ||||
|     """ | ||||
|  | ||||
|         localedir = os.environ.get(self.domain.upper() + '_LOCALEDIR') | ||||
|         if self.locale: | ||||
|             lang = gettext.translation(self.domain, | ||||
|                                        localedir=localedir, | ||||
|                                        languages=[self.locale], | ||||
|     def __new__(cls, msgid, msgtext=None, params=None, | ||||
|                 domain='keystoneclient', *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) | ||||
|         else: | ||||
|             # use system locale for translations | ||||
|             lang = gettext.translation(self.domain, | ||||
|                                        localedir=localedir, | ||||
|                                        fallback=True) | ||||
|  | ||||
|         if six.PY3: | ||||
|             ugettext = lang.gettext | ||||
|             translator = lang.gettext | ||||
|         else: | ||||
|             ugettext = lang.ugettext | ||||
|             translator = 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) | ||||
|         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]) | ||||
|  | ||||
|         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 = 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') | ||||
|  | ||||
|     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 | ||||
|         translated_message = translator(msgid) | ||||
|         return translated_message | ||||
|  | ||||
|     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) | ||||
|         # 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 __mul__(self, other): | ||||
|         return self.data * other | ||||
|     def _sanitize_mod_params(self, other): | ||||
|         """Sanitize the object being modded with this Message. | ||||
|  | ||||
|     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) | ||||
|         - 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: | ||||
|             params = (other,) | ||||
|         elif isinstance(other, dict): | ||||
|             # 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: | ||||
|             return _userString.UserString.__getattribute__(self, name) | ||||
|             params = self._copy_param(other) | ||||
|         return params | ||||
|  | ||||
|     def _copy_param(self, param): | ||||
|         try: | ||||
|             return copy.deepcopy(param) | ||||
|         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) | ||||
|  | ||||
|     def __add__(self, other): | ||||
|         msg = _('Message objects do not support addition.') | ||||
|         raise TypeError(msg) | ||||
|  | ||||
|     def __radd__(self, other): | ||||
|         return self.__add__(other) | ||||
|  | ||||
|     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): | ||||
| @@ -319,53 +356,143 @@ 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) | ||||
|  | ||||
|  | ||||
| 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 message is not a Message object the message is returned as-is. | ||||
|     If the locale is None the message is translated to the default locale. | ||||
|     If the object is not translatable it is returned as-is. | ||||
|     If the locale is None the object is translated to the system locale. | ||||
|  | ||||
|     :returns: the translated message in unicode, or the original message if | ||||
|     :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 | ||||
|     """ | ||||
|     translated = message | ||||
|     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): | ||||
|         original_locale = message.locale | ||||
|         message.locale = user_locale | ||||
|         translated = six.text_type(message) | ||||
|         message.locale = original_locale | ||||
|     return translated | ||||
|         # 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) | ||||
|   | ||||
| @@ -58,6 +58,13 @@ def import_module(import_str): | ||||
|     return sys.modules[import_str] | ||||
|  | ||||
|  | ||||
| def import_versioned_module(version, submodule=None): | ||||
|     module = 'keystoneclient.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: | ||||
|   | ||||
| @@ -170,8 +170,8 @@ def loads(s): | ||||
|     return json.loads(s) | ||||
|  | ||||
|  | ||||
| def load(s): | ||||
|     return json.load(s) | ||||
| def load(fp): | ||||
|     return json.load(fp) | ||||
|  | ||||
|  | ||||
| try: | ||||
|   | ||||
| @@ -36,11 +36,8 @@ def get_client(memcached_servers=None): | ||||
|     if not memcached_servers: | ||||
|         memcached_servers = CONF.memcached_servers | ||||
|     if memcached_servers: | ||||
|         try: | ||||
|         import memcache | ||||
|         client_cls = memcache.Client | ||||
|         except ImportError: | ||||
|             pass | ||||
|  | ||||
|     return client_cls(memcached_servers, debug=0) | ||||
|  | ||||
|   | ||||
| @@ -17,25 +17,31 @@ | ||||
| System-level utilities and helper functions. | ||||
| """ | ||||
|  | ||||
| import math | ||||
| import re | ||||
| import sys | ||||
| import unicodedata | ||||
|  | ||||
| import six | ||||
|  | ||||
| from keystoneclient.openstack.common.gettextutils import _  # noqa | ||||
| from keystoneclient.openstack.common.gettextutils import _ | ||||
|  | ||||
|  | ||||
| # 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, | ||||
| UNIT_PREFIX_EXPONENT = { | ||||
|     'k': 1, | ||||
|     'K': 1, | ||||
|     'Ki': 1, | ||||
|     'M': 2, | ||||
|     'Mi': 2, | ||||
|     'G': 3, | ||||
|     'Gi': 3, | ||||
|     'T': 4, | ||||
|     'Ti': 4, | ||||
| } | ||||
| UNIT_SYSTEM_INFO = { | ||||
|     'IEC': (1024, re.compile(r'(^[-+]?\d*\.?\d+)([KMGT]i?)?(b|bit|B)$')), | ||||
|     'SI': (1000, re.compile(r'(^[-+]?\d*\.?\d+)([kMGT])?(b|bit|B)$')), | ||||
| } | ||||
| BYTE_REGEX = re.compile(r'(^-?\d+)(\D*)') | ||||
|  | ||||
| TRUE_STRINGS = ('1', 't', 'true', 'on', 'y', 'yes') | ||||
| FALSE_STRINGS = ('0', 'f', 'false', 'off', 'n', 'no') | ||||
| @@ -58,12 +64,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. | ||||
|  | ||||
| @@ -72,7 +78,7 @@ def bool_from_string(subject, strict=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() | ||||
|  | ||||
| @@ -88,11 +94,12 @@ 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'): | ||||
|     """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 | ||||
| @@ -101,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): | ||||
| @@ -131,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`) | ||||
| @@ -144,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: | ||||
| @@ -152,49 +159,59 @@ 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) | ||||
|     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) | ||||
|  | ||||
|     else: | ||||
|         return text | ||||
|  | ||||
|  | ||||
| def to_bytes(text, default=0): | ||||
|     """Converts a string into an integer of bytes. | ||||
| def string_to_bytes(text, unit_system='IEC', return_int=False): | ||||
|     """Converts a string into an float representation 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) | ||||
|     The units supported for IEC :: | ||||
|  | ||||
|         Kb(it), Kib(it), Mb(it), Mib(it), Gb(it), Gib(it), Tb(it), Tib(it) | ||||
|         KB, KiB, MB, MiB, GB, GiB, TB, TiB | ||||
|  | ||||
|     The units supported for SI :: | ||||
|  | ||||
|         kb(it), Mb(it), Gb(it), Tb(it) | ||||
|         kB, MB, GB, TB | ||||
|  | ||||
|     Note that the SI unit system does not support capital letter 'K' | ||||
|  | ||||
|     :param text: String input for bytes size conversion. | ||||
|     :param default: Default return value when text is blank. | ||||
|     :param unit_system: Unit system for byte size conversion. | ||||
|     :param return_int: If True, returns integer representation of text | ||||
|                        in bytes. (default: decimal) | ||||
|     :returns: Numerical representation of text in bytes. | ||||
|     :raises ValueError: If text has an invalid value. | ||||
|  | ||||
|     """ | ||||
|     match = BYTE_REGEX.search(text) | ||||
|     try: | ||||
|         base, reg_ex = UNIT_SYSTEM_INFO[unit_system] | ||||
|     except KeyError: | ||||
|         msg = _('Invalid unit system: "%s"') % unit_system | ||||
|         raise ValueError(msg) | ||||
|     match = reg_ex.match(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) | ||||
|         magnitude = float(match.group(1)) | ||||
|         unit_prefix = match.group(2) | ||||
|         if match.group(3) in ['b', 'bit']: | ||||
|             magnitude /= 8 | ||||
|     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 | ||||
|         msg = _('Invalid string format: %s') % text | ||||
|         raise ValueError(msg) | ||||
|     if not unit_prefix: | ||||
|         res = magnitude | ||||
|     else: | ||||
|         res = magnitude * pow(base, UNIT_PREFIX_EXPONENT[unit_prefix]) | ||||
|     if return_int: | ||||
|         return int(math.ceil(res)) | ||||
|     return res | ||||
|  | ||||
|  | ||||
| def to_slug(value, incoming=None, errors="strict"): | ||||
|   | ||||
| @@ -77,6 +77,9 @@ def is_older_than(before, seconds): | ||||
|     """Return True if before is older than seconds.""" | ||||
|     if isinstance(before, six.string_types): | ||||
|         before = parse_strtime(before).replace(tzinfo=None) | ||||
|     else: | ||||
|         before = before.replace(tzinfo=None) | ||||
|  | ||||
|     return utcnow() - before > datetime.timedelta(seconds=seconds) | ||||
|  | ||||
|  | ||||
| @@ -84,6 +87,9 @@ def is_newer_than(after, seconds): | ||||
|     """Return True if after is newer than seconds.""" | ||||
|     if isinstance(after, six.string_types): | ||||
|         after = parse_strtime(after).replace(tzinfo=None) | ||||
|     else: | ||||
|         after = after.replace(tzinfo=None) | ||||
|  | ||||
|     return after - utcnow() > datetime.timedelta(seconds=seconds) | ||||
|  | ||||
|  | ||||
| @@ -108,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)) | ||||
|  | ||||
|  | ||||
| @@ -195,8 +201,8 @@ def total_seconds(delta): | ||||
| def is_soon(dt, window): | ||||
|     """Determines if time is going to happen in the next window seconds. | ||||
|  | ||||
|     :params dt: the time | ||||
|     :params window: minimum seconds to remain to consider the time not soon | ||||
|     :param dt: the time | ||||
|     :param window: minimum seconds to remain to consider the time not soon | ||||
|  | ||||
|     :return: True if expiration is within the given duration | ||||
|     """ | ||||
|   | ||||
| @@ -61,7 +61,10 @@ def print_list(objs, fields, formatters={}, order_by=None): | ||||
|  | ||||
|     if order_by is None: | ||||
|         order_by = fields[0] | ||||
|     print(strutils.safe_encode(pt.get_string(sortby=order_by))) | ||||
|     encoded = strutils.safe_encode(pt.get_string(sortby=order_by)) | ||||
|     if six.PY3: | ||||
|         encoded = encoded.decode() | ||||
|     print(encoded) | ||||
|  | ||||
|  | ||||
| def _word_wrap(string, max_length=0): | ||||
| @@ -85,7 +88,10 @@ def print_dict(d, wrap=0): | ||||
|             value = '' | ||||
|         value = _word_wrap(value, max_length=wrap) | ||||
|         pt.add_row([prop, value]) | ||||
|     print(strutils.safe_encode(pt.get_string(sortby='Property'))) | ||||
|     encoded = strutils.safe_encode(pt.get_string(sortby='Property')) | ||||
|     if six.PY3: | ||||
|         encoded = encoded.decode() | ||||
|     print(encoded) | ||||
|  | ||||
|  | ||||
| def find_resource(manager, name_or_id): | ||||
|   | ||||
| @@ -125,7 +125,7 @@ class InstallVenv(object): | ||||
|         parser.add_option('-n', '--no-site-packages', | ||||
|                           action='store_true', | ||||
|                           help="Do not inherit packages from global Python " | ||||
|                                "install") | ||||
|                                "install.") | ||||
|         return parser.parse_args(argv[1:])[0] | ||||
|  | ||||
|  | ||||
|   | ||||
							
								
								
									
										2
									
								
								tox.ini
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								tox.ini
									
									
									
									
									
								
							| @@ -38,7 +38,7 @@ commands = | ||||
| # H803  Commit message should not end with a period (do not remove per list discussion) | ||||
| ignore = F821,H304,H803 | ||||
| show-source = True | ||||
| exclude = .venv,.tox,dist,doc,*egg,build | ||||
| exclude = .venv,.tox,dist,doc,*egg,build,*openstack/common* | ||||
|  | ||||
| [testenv:docs] | ||||
| commands= | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Jenkins
					Jenkins