Skip to content

Commit fa649f4

Browse files
author
Dean Troyer
committed
Sync oslo-incubator for py33 fixes
Change-Id: I261ec6bb34b29169ba3547305deab051f85a3d4d
1 parent bca4cf9 commit fa649f4

2 files changed

Lines changed: 156 additions & 88 deletions

File tree

openstackclient/openstack/common/gettextutils.py

Lines changed: 152 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# vim: tabstop=4 shiftwidth=4 softtabstop=4
22

33
# Copyright 2012 Red Hat, Inc.
4-
# All Rights Reserved.
54
# Copyright 2013 IBM Corp.
5+
# All Rights Reserved.
66
#
77
# Licensed under the Apache License, Version 2.0 (the "License"); you may
88
# not use this file except in compliance with the License. You may obtain
@@ -26,22 +26,46 @@
2626

2727
import copy
2828
import gettext
29-
import logging.handlers
29+
import logging
3030
import os
3131
import re
32-
import UserString
32+
try:
33+
import UserString as _userString
34+
except ImportError:
35+
import collections as _userString
3336

37+
from babel import localedata
3438
import six
3539

3640
_localedir = os.environ.get('openstackclient'.upper() + '_LOCALEDIR')
3741
_t = gettext.translation('openstackclient', localedir=_localedir, fallback=True)
3842

43+
_AVAILABLE_LANGUAGES = {}
44+
USE_LAZY = False
45+
46+
47+
def enable_lazy():
48+
"""Convenience function for configuring _() to use lazy gettext
49+
50+
Call this at the start of execution to enable the gettextutils._
51+
function to use lazy gettext functionality. This is useful if
52+
your project is importing _ directly instead of using the
53+
gettextutils.install() way of importing the _ function.
54+
"""
55+
global USE_LAZY
56+
USE_LAZY = True
57+
3958

4059
def _(msg):
41-
return _t.ugettext(msg)
60+
if USE_LAZY:
61+
return Message(msg, 'openstackclient')
62+
else:
63+
if six.PY3:
64+
return _t.gettext(msg)
65+
return _t.ugettext(msg)
4266

4367

44-
def install(domain):
68+
def install(domain, lazy=False):
4569
"""Install a _() function using the given translation domain.
4670
4771
Given a translation domain, install a _() function using gettext's
@@ -51,52 +75,60 @@ def install(domain):
5175
overriding the default localedir (e.g. /usr/share/locale) using
5276
a translation-domain-specific environment variable (e.g.
5377
NOVA_LOCALEDIR).
54-
"""
55-
gettext.install(domain,
56-
localedir=os.environ.get(domain.upper() + '_LOCALEDIR'),
57-
unicode=True)
58-
59-
60-
"""
61-
Lazy gettext functionality.
62-
63-
The following is an attempt to introduce a deferred way
64-
to do translations on messages in OpenStack. We attempt to
65-
override the standard _() function and % (format string) operation
66-
to build Message objects that can later be translated when we have
67-
more information. Also included is an example LogHandler that
68-
translates Messages to an associated locale, effectively allowing
69-
many logs, each with their own locale.
70-
"""
71-
72-
73-
def get_lazy_gettext(domain):
74-
"""Assemble and return a lazy gettext function for a given domain.
7578
76-
Factory method for a project/module to get a lazy gettext function
77-
for its own translation domain (i.e. nova, glance, cinder, etc.)
79+
:param domain: the translation domain
80+
:param lazy: indicates whether or not to install the lazy _() function.
81+
The lazy _() introduces a way to do deferred translation
82+
of messages by installing a _ that builds Message objects,
83+
instead of strings, which can then be lazily translated into
84+
any available locale.
7885
"""
79-
80-
def _lazy_gettext(msg):
81-
"""Create and return a Message object.
82-
83-
Message encapsulates a string so that we can translate it later when
84-
needed.
85-
"""
86-
return Message(msg, domain)
87-
88-
return _lazy_gettext
86+
if lazy:
87+
# NOTE(mrodden): Lazy gettext functionality.
88+
#
89+
# The following introduces a deferred way to do translations on
90+
# messages in OpenStack. We override the standard _() function
91+
# and % (format string) operation to build Message objects that can
92+
# later be translated when we have more information.
93+
#
94+
# Also included below is an example LocaleHandler that translates
95+
# Messages to an associated locale, effectively allowing many logs,
96+
# each with their own locale.
97+
98+
def _lazy_gettext(msg):
99+
"""Create and return a Message object.
100+
101+
Lazy gettext function for a given domain, it is a factory method
102+
for a project/module to get a lazy gettext function for its own
103+
translation domain (i.e. nova, glance, cinder, etc.)
104+
105+
Message encapsulates a string so that we can translate
106+
it later when needed.
107+
"""
108+
return Message(msg, domain)
109+
110+
from six import moves
111+
moves.builtins.__dict__['_'] = _lazy_gettext
112+
else:
113+
localedir = '%s_LOCALEDIR' % domain.upper()
114+
if six.PY3:
115+
gettext.install(domain,
116+
localedir=os.environ.get(localedir))
117+
else:
118+
gettext.install(domain,
119+
localedir=os.environ.get(localedir),
120+
unicode=True)
89121

90122

91-
class Message(UserString.UserString, object):
123+
class Message(_userString.UserString, object):
92124
"""Class used to encapsulate translatable messages."""
93125
def __init__(self, msg, domain):
94126
# _msg is the gettext msgid and should never change
95127
self._msg = msg
96128
self._left_extra_msg = ''
97129
self._right_extra_msg = ''
130+
self._locale = None
98131
self.params = None
99-
self.locale = None
100132
self.domain = domain
101133

102134
@property
@@ -116,21 +148,53 @@ def data(self):
116148
localedir=localedir,
117149
fallback=True)
118150

151+
if six.PY3:
152+
ugettext = lang.gettext
153+
else:
154+
ugettext = lang.ugettext
155+
119156
full_msg = (self._left_extra_msg +
120-
lang.ugettext(self._msg) +
157+
ugettext(self._msg) +
121158
self._right_extra_msg)
122159

123160
if self.params is not None:
124161
full_msg = full_msg % self.params
125162

126163
return six.text_type(full_msg)
127164

165+
@property
166+
def locale(self):
167+
return self._locale
168+
169+
@locale.setter
170+
def locale(self, value):
171+
self._locale = value
172+
if not self.params:
173+
return
174+
175+
# This Message object may have been constructed with one or more
176+
# Message objects as substitution parameters, given as a single
177+
# Message, or a tuple or Map containing some, so when setting the
178+
# locale for this Message we need to set it for those Messages too.
179+
if isinstance(self.params, Message):
180+
self.params.locale = value
181+
return
182+
if isinstance(self.params, tuple):
183+
for param in self.params:
184+
if isinstance(param, Message):
185+
param.locale = value
186+
return
187+
if isinstance(self.params, dict):
188+
for param in self.params.values():
189+
if isinstance(param, Message):
190+
param.locale = value
191+
128192
def _save_dictionary_parameter(self, dict_param):
129193
full_msg = self.data
130194
# look for %(blah) fields in string;
131195
# ignore %% and deal with the
132196
# case where % is first character on the line
133-
keys = re.findall('(?:[^%]|^)%\((\w*)\)[a-z]', full_msg)
197+
keys = re.findall('(?:[^%]|^)?%\((\w*)\)[a-z]', full_msg)
134198

135199
# if we don't find any %(blah) blocks but have a %s
136200
if not keys and re.findall('(?:[^%]|^)%[a-z]', full_msg):
@@ -143,7 +207,7 @@ def _save_dictionary_parameter(self, dict_param):
143207
params[key] = copy.deepcopy(dict_param[key])
144208
except TypeError:
145209
# cast uncopyable thing to unicode string
146-
params[key] = unicode(dict_param[key])
210+
params[key] = six.text_type(dict_param[key])
147211

148212
return params
149213

@@ -162,7 +226,7 @@ def _save_parameters(self, other):
162226
try:
163227
self.params = copy.deepcopy(other)
164228
except TypeError:
165-
self.params = unicode(other)
229+
self.params = six.text_type(other)
166230

167231
return self
168232

@@ -171,11 +235,13 @@ def __unicode__(self):
171235
return self.data
172236

173237
def __str__(self):
238+
if six.PY3:
239+
return self.__unicode__()
174240
return self.data.encode('utf-8')
175241

176242
def __getstate__(self):
177243
to_copy = ['_msg', '_right_extra_msg', '_left_extra_msg',
178-
'domain', 'params', 'locale']
244+
'domain', 'params', '_locale']
179245
new_dict = self.__dict__.fromkeys(to_copy)
180246
for attr in to_copy:
181247
new_dict[attr] = copy.deepcopy(self.__dict__[attr])
@@ -229,7 +295,47 @@ def __getattribute__(self, name):
229295
if name in ops:
230296
return getattr(self.data, name)
231297
else:
232-
return UserString.UserString.__getattribute__(self, name)
298+
return _userString.UserString.__getattribute__(self, name)
299+
300+
301+
def get_available_languages(domain):
302+
"""Lists the available languages for the given translation domain.
303+
304+
:param domain: the domain to get languages for
305+
"""
306+
if domain in _AVAILABLE_LANGUAGES:
307+
return copy.copy(_AVAILABLE_LANGUAGES[domain])
308+
309+
localedir = '%s_LOCALEDIR' % domain.upper()
310+
find = lambda x: gettext.find(domain,
311+
localedir=os.environ.get(localedir),
312+
languages=[x])
313+
314+
# NOTE(mrodden): en_US should always be available (and first in case
315+
# order matters) since our in-line message strings are en_US
316+
language_list = ['en_US']
317+
# NOTE(luisg): Babel <1.0 used a function called list(), which was
318+
# renamed to locale_identifiers() in >=1.0, the requirements master list
319+
# requires >=0.9.6, uncapped, so defensively work with both. We can remove
320+
# this check when the master list updates to >=1.0, and all projects udpate
321+
list_identifiers = (getattr(localedata, 'list', None) or
322+
getattr(localedata, 'locale_identifiers'))
323+
locale_identifiers = list_identifiers()
324+
for i in locale_identifiers:
325+
if find(i) is not None:
326+
language_list.append(i)
327+
_AVAILABLE_LANGUAGES[domain] = language_list
328+
return copy.copy(language_list)
329+
330+
331+
def get_localized_message(message, user_locale):
332+
"""Gets a localized version of the given message in the given locale."""
333+
if isinstance(message, Message):
334+
if user_locale:
335+
message.locale = user_locale
336+
return six.text_type(message)
337+
else:
338+
return message
233339

234340

235341
class LocaleHandler(logging.Handler):

tools/install_venv_common.py

Lines changed: 4 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -114,15 +114,12 @@ def install_dependencies(self):
114114
print('Installing dependencies with pip (this can take a while)...')
115115

116116
# First things first, make sure our venv has the latest pip and
117-
# setuptools.
118-
self.pip_install('pip>=1.3')
117+
# setuptools and pbr
118+
self.pip_install('pip>=1.4')
119119
self.pip_install('setuptools')
120+
self.pip_install('pbr')
120121

121-
self.pip_install('-r', self.requirements)
122-
self.pip_install('-r', self.test_requirements)
123-
124-
def post_process(self):
125-
self.get_distro().post_process()
122+
self.pip_install('-r', self.requirements, '-r', self.test_requirements)
126123

127124
def parse_args(self, argv):
128125
"""Parses command-line arguments."""
@@ -156,14 +153,6 @@ def install_virtualenv(self):
156153
' requires virtualenv, please install it using your'
157154
' favorite package management tool' % self.project)
158155

159-
def post_process(self):
160-
"""Any distribution-specific post-processing gets done here.
161-
162-
In particular, this is useful for applying patches to code inside
163-
the venv.
164-
"""
165-
pass
166-
167156

168157
class Fedora(Distro):
169158
"""This covers all Fedora-based distributions.
@@ -175,10 +164,6 @@ def check_pkg(self, pkg):
175164
return self.run_command_with_code(['rpm', '-q', pkg],
176165
check_exit_code=False)[1] == 0
177166

178-
def apply_patch(self, originalfile, patchfile):
179-
self.run_command(['patch', '-N', originalfile, patchfile],
180-
check_exit_code=False)
181-
182167
def install_virtualenv(self):
183168
if self.check_cmd('virtualenv'):
184169
return
@@ -187,26 +172,3 @@ def install_virtualenv(self):
187172
self.die("Please install 'python-virtualenv'.")
188173

189174
super(Fedora, self).install_virtualenv()
190-
191-
def post_process(self):
192-
"""Workaround for a bug in eventlet.
193-
194-
This currently affects RHEL6.1, but the fix can safely be
195-
applied to all RHEL and Fedora distributions.
196-
197-
This can be removed when the fix is applied upstream.
198-
199-
Nova: https://bugs.launchpad.net/nova/+bug/884915
200-
Upstream: https://bitbucket.org/eventlet/eventlet/issue/89
201-
RHEL: https://bugzilla.redhat.com/958868
202-
"""
203-
204-
# Install "patch" program if it's not there
205-
if not self.check_pkg('patch'):
206-
self.die("Please install 'patch'.")
207-
208-
# Apply the eventlet patch
209-
self.apply_patch(os.path.join(self.venv, 'lib', self.py_version,
210-
'site-packages',
211-
'eventlet/green/subprocess.py'),
212-
'contrib/redhat-eventlet.patch')

0 commit comments

Comments
 (0)