Browse Source

Update oslo

Update gettext, striutils, timeutils and install_venv_common
from oslo.

Change-Id: Ibd9067e3e2be335ef75f0e4a5e4000d143030ab7
Signed-off-by: Chuck Short <chuck.short@canonical.com>
tags/2.15.0
Chuck Short 5 years ago
parent
commit
626c480559

+ 259
- 4
novaclient/openstack/common/gettextutils.py View File

@@ -1,6 +1,7 @@
1 1
 # vim: tabstop=4 shiftwidth=4 softtabstop=4
2 2
 
3 3
 # Copyright 2012 Red Hat, Inc.
4
+# Copyright 2013 IBM Corp.
4 5
 # All Rights Reserved.
5 6
 #
6 7
 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -23,18 +24,27 @@ Usual usage in an openstack.common module:
23 24
     from novaclient.openstack.common.gettextutils import _
24 25
 """
25 26
 
27
+import copy
26 28
 import gettext
29
+import logging.handlers
27 30
 import os
31
+import re
32
+import UserString
33
+
34
+from babel import localedata
35
+import six
28 36
 
29 37
 _localedir = os.environ.get('novaclient'.upper() + '_LOCALEDIR')
30 38
 _t = gettext.translation('novaclient', localedir=_localedir, fallback=True)
31 39
 
40
+_AVAILABLE_LANGUAGES = []
41
+
32 42
 
33 43
 def _(msg):
34 44
     return _t.ugettext(msg)
35 45
 
36 46
 
37
-def install(domain):
47
+def install(domain, lazy=False):
38 48
     """Install a _() function using the given translation domain.
39 49
 
40 50
     Given a translation domain, install a _() function using gettext's
@@ -44,7 +54,252 @@ def install(domain):
44 54
     overriding the default localedir (e.g. /usr/share/locale) using
45 55
     a translation-domain-specific environment variable (e.g.
46 56
     NOVA_LOCALEDIR).
57
+
58
+    :param domain: the translation domain
59
+    :param lazy: indicates whether or not to install the lazy _() function.
60
+                 The lazy _() introduces a way to do deferred translation
61
+                 of messages by installing a _ that builds Message objects,
62
+                 instead of strings, which can then be lazily translated into
63
+                 any available locale.
64
+    """
65
+    if lazy:
66
+        # NOTE(mrodden): Lazy gettext functionality.
67
+        #
68
+        # The following introduces a deferred way to do translations on
69
+        # messages in OpenStack. We override the standard _() function
70
+        # and % (format string) operation to build Message objects that can
71
+        # later be translated when we have more information.
72
+        #
73
+        # Also included below is an example LocaleHandler that translates
74
+        # Messages to an associated locale, effectively allowing many logs,
75
+        # each with their own locale.
76
+
77
+        def _lazy_gettext(msg):
78
+            """Create and return a Message object.
79
+
80
+            Lazy gettext function for a given domain, it is a factory method
81
+            for a project/module to get a lazy gettext function for its own
82
+            translation domain (i.e. nova, glance, cinder, etc.)
83
+
84
+            Message encapsulates a string so that we can translate
85
+            it later when needed.
86
+            """
87
+            return Message(msg, domain)
88
+
89
+        import __builtin__
90
+        __builtin__.__dict__['_'] = _lazy_gettext
91
+    else:
92
+        localedir = '%s_LOCALEDIR' % domain.upper()
93
+        gettext.install(domain,
94
+                        localedir=os.environ.get(localedir),
95
+                        unicode=True)
96
+
97
+
98
+class Message(UserString.UserString, object):
99
+    """Class used to encapsulate translatable messages."""
100
+    def __init__(self, msg, domain):
101
+        # _msg is the gettext msgid and should never change
102
+        self._msg = msg
103
+        self._left_extra_msg = ''
104
+        self._right_extra_msg = ''
105
+        self.params = None
106
+        self.locale = None
107
+        self.domain = domain
108
+
109
+    @property
110
+    def data(self):
111
+        # NOTE(mrodden): this should always resolve to a unicode string
112
+        # that best represents the state of the message currently
113
+
114
+        localedir = os.environ.get(self.domain.upper() + '_LOCALEDIR')
115
+        if self.locale:
116
+            lang = gettext.translation(self.domain,
117
+                                       localedir=localedir,
118
+                                       languages=[self.locale],
119
+                                       fallback=True)
120
+        else:
121
+            # use system locale for translations
122
+            lang = gettext.translation(self.domain,
123
+                                       localedir=localedir,
124
+                                       fallback=True)
125
+
126
+        full_msg = (self._left_extra_msg +
127
+                    lang.ugettext(self._msg) +
128
+                    self._right_extra_msg)
129
+
130
+        if self.params is not None:
131
+            full_msg = full_msg % self.params
132
+
133
+        return six.text_type(full_msg)
134
+
135
+    def _save_dictionary_parameter(self, dict_param):
136
+        full_msg = self.data
137
+        # look for %(blah) fields in string;
138
+        # ignore %% and deal with the
139
+        # case where % is first character on the line
140
+        keys = re.findall('(?:[^%]|^)?%\((\w*)\)[a-z]', full_msg)
141
+
142
+        # if we don't find any %(blah) blocks but have a %s
143
+        if not keys and re.findall('(?:[^%]|^)%[a-z]', full_msg):
144
+            # apparently the full dictionary is the parameter
145
+            params = copy.deepcopy(dict_param)
146
+        else:
147
+            params = {}
148
+            for key in keys:
149
+                try:
150
+                    params[key] = copy.deepcopy(dict_param[key])
151
+                except TypeError:
152
+                    # cast uncopyable thing to unicode string
153
+                    params[key] = unicode(dict_param[key])
154
+
155
+        return params
156
+
157
+    def _save_parameters(self, other):
158
+        # we check for None later to see if
159
+        # we actually have parameters to inject,
160
+        # so encapsulate if our parameter is actually None
161
+        if other is None:
162
+            self.params = (other, )
163
+        elif isinstance(other, dict):
164
+            self.params = self._save_dictionary_parameter(other)
165
+        else:
166
+            # fallback to casting to unicode,
167
+            # this will handle the problematic python code-like
168
+            # objects that cannot be deep-copied
169
+            try:
170
+                self.params = copy.deepcopy(other)
171
+            except TypeError:
172
+                self.params = unicode(other)
173
+
174
+        return self
175
+
176
+    # overrides to be more string-like
177
+    def __unicode__(self):
178
+        return self.data
179
+
180
+    def __str__(self):
181
+        return self.data.encode('utf-8')
182
+
183
+    def __getstate__(self):
184
+        to_copy = ['_msg', '_right_extra_msg', '_left_extra_msg',
185
+                   'domain', 'params', 'locale']
186
+        new_dict = self.__dict__.fromkeys(to_copy)
187
+        for attr in to_copy:
188
+            new_dict[attr] = copy.deepcopy(self.__dict__[attr])
189
+
190
+        return new_dict
191
+
192
+    def __setstate__(self, state):
193
+        for (k, v) in state.items():
194
+            setattr(self, k, v)
195
+
196
+    # operator overloads
197
+    def __add__(self, other):
198
+        copied = copy.deepcopy(self)
199
+        copied._right_extra_msg += other.__str__()
200
+        return copied
201
+
202
+    def __radd__(self, other):
203
+        copied = copy.deepcopy(self)
204
+        copied._left_extra_msg += other.__str__()
205
+        return copied
206
+
207
+    def __mod__(self, other):
208
+        # do a format string to catch and raise
209
+        # any possible KeyErrors from missing parameters
210
+        self.data % other
211
+        copied = copy.deepcopy(self)
212
+        return copied._save_parameters(other)
213
+
214
+    def __mul__(self, other):
215
+        return self.data * other
216
+
217
+    def __rmul__(self, other):
218
+        return other * self.data
219
+
220
+    def __getitem__(self, key):
221
+        return self.data[key]
222
+
223
+    def __getslice__(self, start, end):
224
+        return self.data.__getslice__(start, end)
225
+
226
+    def __getattribute__(self, name):
227
+        # NOTE(mrodden): handle lossy operations that we can't deal with yet
228
+        # These override the UserString implementation, since UserString
229
+        # uses our __class__ attribute to try and build a new message
230
+        # after running the inner data string through the operation.
231
+        # At that point, we have lost the gettext message id and can just
232
+        # safely resolve to a string instead.
233
+        ops = ['capitalize', 'center', 'decode', 'encode',
234
+               'expandtabs', 'ljust', 'lstrip', 'replace', 'rjust', 'rstrip',
235
+               'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
236
+        if name in ops:
237
+            return getattr(self.data, name)
238
+        else:
239
+            return UserString.UserString.__getattribute__(self, name)
240
+
241
+
242
+def get_available_languages(domain):
243
+    """Lists the available languages for the given translation domain.
244
+
245
+    :param domain: the domain to get languages for
47 246
     """
48
-    gettext.install(domain,
49
-                    localedir=os.environ.get(domain.upper() + '_LOCALEDIR'),
50
-                    unicode=True)
247
+    if _AVAILABLE_LANGUAGES:
248
+        return _AVAILABLE_LANGUAGES
249
+
250
+    localedir = '%s_LOCALEDIR' % domain.upper()
251
+    find = lambda x: gettext.find(domain,
252
+                                  localedir=os.environ.get(localedir),
253
+                                  languages=[x])
254
+
255
+    # NOTE(mrodden): en_US should always be available (and first in case
256
+    # order matters) since our in-line message strings are en_US
257
+    _AVAILABLE_LANGUAGES.append('en_US')
258
+    # NOTE(luisg): Babel <1.0 used a function called list(), which was
259
+    # renamed to locale_identifiers() in >=1.0, the requirements master list
260
+    # requires >=0.9.6, uncapped, so defensively work with both. We can remove
261
+    # this check when the master list updates to >=1.0, and all projects udpate
262
+    list_identifiers = (getattr(localedata, 'list', None) or
263
+                        getattr(localedata, 'locale_identifiers'))
264
+    locale_identifiers = list_identifiers()
265
+    for i in locale_identifiers:
266
+        if find(i) is not None:
267
+            _AVAILABLE_LANGUAGES.append(i)
268
+    return _AVAILABLE_LANGUAGES
269
+
270
+
271
+def get_localized_message(message, user_locale):
272
+    """Gets a localized version of the given message in the given locale."""
273
+    if (isinstance(message, Message)):
274
+        if user_locale:
275
+            message.locale = user_locale
276
+        return unicode(message)
277
+    else:
278
+        return message
279
+
280
+
281
+class LocaleHandler(logging.Handler):
282
+    """Handler that can have a locale associated to translate Messages.
283
+
284
+    A quick example of how to utilize the Message class above.
285
+    LocaleHandler takes a locale and a target logging.Handler object
286
+    to forward LogRecord objects to after translating the internal Message.
287
+    """
288
+
289
+    def __init__(self, locale, target):
290
+        """Initialize a LocaleHandler
291
+
292
+        :param locale: locale to use for translating messages
293
+        :param target: logging.Handler object to forward
294
+                       LogRecord objects to after translation
295
+        """
296
+        logging.Handler.__init__(self)
297
+        self.locale = locale
298
+        self.target = target
299
+
300
+    def emit(self, record):
301
+        if isinstance(record.msg, Message):
302
+            # set the locale and resolve to a string
303
+            record.msg.locale = self.locale
304
+
305
+        self.target.emit(record)

+ 88
- 20
novaclient/openstack/common/strutils.py View File

@@ -19,18 +19,35 @@
19 19
 System-level utilities and helper functions.
20 20
 """
21 21
 
22
+import re
22 23
 import sys
24
+import unicodedata
23 25
 
24
-from novaclient.openstack.common.gettextutils import _
26
+import six
25 27
 
28
+from novaclient.openstack.common.gettextutils import _  # noqa
29
+
30
+
31
+# Used for looking up extensions of text
32
+# to their 'multiplied' byte amount
33
+BYTE_MULTIPLIERS = {
34
+    '': 1,
35
+    't': 1024 ** 4,
36
+    'g': 1024 ** 3,
37
+    'm': 1024 ** 2,
38
+    'k': 1024,
39
+}
40
+BYTE_REGEX = re.compile(r'(^-?\d+)(\D*)')
26 41
 
27 42
 TRUE_STRINGS = ('1', 't', 'true', 'on', 'y', 'yes')
28 43
 FALSE_STRINGS = ('0', 'f', 'false', 'off', 'n', 'no')
29 44
 
45
+SLUGIFY_STRIP_RE = re.compile(r"[^\w\s-]")
46
+SLUGIFY_HYPHENATE_RE = re.compile(r"[-\s]+")
47
+
30 48
 
31 49
 def int_from_bool_as_string(subject):
32
-    """
33
-    Interpret a string as a boolean and return either 1 or 0.
50
+    """Interpret a string as a boolean and return either 1 or 0.
34 51
 
35 52
     Any string value in:
36 53
 
@@ -44,8 +61,7 @@ def int_from_bool_as_string(subject):
44 61
 
45 62
 
46 63
 def bool_from_string(subject, strict=False):
47
-    """
48
-    Interpret a string as a boolean.
64
+    """Interpret a string as a boolean.
49 65
 
50 66
     A case-insensitive match is performed such that strings matching 't',
51 67
     'true', 'on', 'y', 'yes', or '1' are considered True and, when
@@ -57,7 +73,7 @@ def bool_from_string(subject, strict=False):
57 73
     ValueError which is useful when parsing values passed in from an API call.
58 74
     Strings yielding False are 'f', 'false', 'off', 'n', 'no', or '0'.
59 75
     """
60
-    if not isinstance(subject, basestring):
76
+    if not isinstance(subject, six.string_types):
61 77
         subject = str(subject)
62 78
 
63 79
     lowered = subject.strip().lower()
@@ -78,21 +94,19 @@ def bool_from_string(subject, strict=False):
78 94
 
79 95
 
80 96
 def safe_decode(text, incoming=None, errors='strict'):
81
-    """
82
-    Decodes incoming str using `incoming` if they're
83
-    not already unicode.
97
+    """Decodes incoming str using `incoming` if they're not already unicode.
84 98
 
85 99
     :param incoming: Text's current encoding
86 100
     :param errors: Errors handling policy. See here for valid
87 101
         values http://docs.python.org/2/library/codecs.html
88 102
     :returns: text or a unicode `incoming` encoded
89 103
                 representation of it.
90
-    :raises TypeError: If text is not an isntance of basestring
104
+    :raises TypeError: If text is not an isntance of str
91 105
     """
92
-    if not isinstance(text, basestring):
106
+    if not isinstance(text, six.string_types):
93 107
         raise TypeError("%s can't be decoded" % type(text))
94 108
 
95
-    if isinstance(text, unicode):
109
+    if isinstance(text, six.text_type):
96 110
         return text
97 111
 
98 112
     if not incoming:
@@ -119,11 +133,10 @@ def safe_decode(text, incoming=None, errors='strict'):
119 133
 
120 134
 def safe_encode(text, incoming=None,
121 135
                 encoding='utf-8', errors='strict'):
122
-    """
123
-    Encodes incoming str/unicode using `encoding`. If
124
-    incoming is not specified, text is expected to
125
-    be encoded with current python's default encoding.
126
-    (`sys.getdefaultencoding`)
136
+    """Encodes incoming str/unicode using `encoding`.
137
+
138
+    If incoming is not specified, text is expected to be encoded with
139
+    current python's default encoding. (`sys.getdefaultencoding`)
127 140
 
128 141
     :param incoming: Text's current encoding
129 142
     :param encoding: Expected encoding for text (Default UTF-8)
@@ -131,16 +144,16 @@ def safe_encode(text, incoming=None,
131 144
         values http://docs.python.org/2/library/codecs.html
132 145
     :returns: text or a bytestring `encoding` encoded
133 146
                 representation of it.
134
-    :raises TypeError: If text is not an isntance of basestring
147
+    :raises TypeError: If text is not an isntance of str
135 148
     """
136
-    if not isinstance(text, basestring):
149
+    if not isinstance(text, six.string_types):
137 150
         raise TypeError("%s can't be encoded" % type(text))
138 151
 
139 152
     if not incoming:
140 153
         incoming = (sys.stdin.encoding or
141 154
                     sys.getdefaultencoding())
142 155
 
143
-    if isinstance(text, unicode):
156
+    if isinstance(text, six.text_type):
144 157
         return text.encode(encoding, errors)
145 158
     elif text and encoding != incoming:
146 159
         # Decode text before encoding it with `encoding`
@@ -148,3 +161,58 @@ def safe_encode(text, incoming=None,
148 161
         return text.encode(encoding, errors)
149 162
 
150 163
     return text
164
+
165
+
166
+def to_bytes(text, default=0):
167
+    """Converts a string into an integer of bytes.
168
+
169
+    Looks at the last characters of the text to determine
170
+    what conversion is needed to turn the input text into a byte number.
171
+    Supports "B, K(B), M(B), G(B), and T(B)". (case insensitive)
172
+
173
+    :param text: String input for bytes size conversion.
174
+    :param default: Default return value when text is blank.
175
+
176
+    """
177
+    match = BYTE_REGEX.search(text)
178
+    if match:
179
+        magnitude = int(match.group(1))
180
+        mult_key_org = match.group(2)
181
+        if not mult_key_org:
182
+            return magnitude
183
+    elif text:
184
+        msg = _('Invalid string format: %s') % text
185
+        raise TypeError(msg)
186
+    else:
187
+        return default
188
+    mult_key = mult_key_org.lower().replace('b', '', 1)
189
+    multiplier = BYTE_MULTIPLIERS.get(mult_key)
190
+    if multiplier is None:
191
+        msg = _('Unknown byte multiplier: %s') % mult_key_org
192
+        raise TypeError(msg)
193
+    return magnitude * multiplier
194
+
195
+
196
+def to_slug(value, incoming=None, errors="strict"):
197
+    """Normalize string.
198
+
199
+    Convert to lowercase, remove non-word characters, and convert spaces
200
+    to hyphens.
201
+
202
+    Inspired by Django's `slugify` filter.
203
+
204
+    :param value: Text to slugify
205
+    :param incoming: Text's current encoding
206
+    :param errors: Errors handling policy. See here for valid
207
+        values http://docs.python.org/2/library/codecs.html
208
+    :returns: slugified unicode representation of `value`
209
+    :raises TypeError: If text is not an instance of str
210
+    """
211
+    value = safe_decode(value, incoming, errors)
212
+    # NOTE(aababilov): no need to use safe_(encode|decode) here:
213
+    # encodings are always "ascii", error handling is always "ignore"
214
+    # and types are always known (first: unicode; second: str)
215
+    value = unicodedata.normalize("NFKD", value).encode(
216
+        "ascii", "ignore").decode("ascii")
217
+    value = SLUGIFY_STRIP_RE.sub("", value).strip().lower()
218
+    return SLUGIFY_HYPHENATE_RE.sub("-", value)

+ 17
- 15
novaclient/openstack/common/timeutils.py View File

@@ -23,6 +23,7 @@ import calendar
23 23
 import datetime
24 24
 
25 25
 import iso8601
26
+import six
26 27
 
27 28
 
28 29
 # ISO 8601 extended time format with microseconds
@@ -32,7 +33,7 @@ PERFECT_TIME_FORMAT = _ISO8601_TIME_FORMAT_SUBSECOND
32 33
 
33 34
 
34 35
 def isotime(at=None, subsecond=False):
35
-    """Stringify time in ISO 8601 format"""
36
+    """Stringify time in ISO 8601 format."""
36 37
     if not at:
37 38
         at = utcnow()
38 39
     st = at.strftime(_ISO8601_TIME_FORMAT
@@ -44,13 +45,13 @@ def isotime(at=None, subsecond=False):
44 45
 
45 46
 
46 47
 def parse_isotime(timestr):
47
-    """Parse time from ISO 8601 format"""
48
+    """Parse time from ISO 8601 format."""
48 49
     try:
49 50
         return iso8601.parse_date(timestr)
50 51
     except iso8601.ParseError as e:
51
-        raise ValueError(e.message)
52
+        raise ValueError(unicode(e))
52 53
     except TypeError as e:
53
-        raise ValueError(e.message)
54
+        raise ValueError(unicode(e))
54 55
 
55 56
 
56 57
 def strtime(at=None, fmt=PERFECT_TIME_FORMAT):
@@ -66,7 +67,7 @@ def parse_strtime(timestr, fmt=PERFECT_TIME_FORMAT):
66 67
 
67 68
 
68 69
 def normalize_time(timestamp):
69
-    """Normalize time in arbitrary timezone to UTC naive object"""
70
+    """Normalize time in arbitrary timezone to UTC naive object."""
70 71
     offset = timestamp.utcoffset()
71 72
     if offset is None:
72 73
         return timestamp
@@ -75,14 +76,14 @@ def normalize_time(timestamp):
75 76
 
76 77
 def is_older_than(before, seconds):
77 78
     """Return True if before is older than seconds."""
78
-    if isinstance(before, basestring):
79
+    if isinstance(before, six.string_types):
79 80
         before = parse_strtime(before).replace(tzinfo=None)
80 81
     return utcnow() - before > datetime.timedelta(seconds=seconds)
81 82
 
82 83
 
83 84
 def is_newer_than(after, seconds):
84 85
     """Return True if after is newer than seconds."""
85
-    if isinstance(after, basestring):
86
+    if isinstance(after, six.string_types):
86 87
         after = parse_strtime(after).replace(tzinfo=None)
87 88
     return after - utcnow() > datetime.timedelta(seconds=seconds)
88 89
 
@@ -103,7 +104,7 @@ def utcnow():
103 104
 
104 105
 
105 106
 def iso8601_from_timestamp(timestamp):
106
-    """Returns a iso8601 formated date from timestamp"""
107
+    """Returns a iso8601 formated date from timestamp."""
107 108
     return isotime(datetime.datetime.utcfromtimestamp(timestamp))
108 109
 
109 110
 
@@ -111,9 +112,9 @@ utcnow.override_time = None
111 112
 
112 113
 
113 114
 def set_time_override(override_time=datetime.datetime.utcnow()):
114
-    """
115
-    Override utils.utcnow to return a constant time or a list thereof,
116
-    one at a time.
115
+    """Overrides utils.utcnow.
116
+
117
+    Make it return a constant time or a list thereof, one at a time.
117 118
     """
118 119
     utcnow.override_time = override_time
119 120
 
@@ -141,7 +142,8 @@ def clear_time_override():
141 142
 def marshall_now(now=None):
142 143
     """Make an rpc-safe datetime with microseconds.
143 144
 
144
-    Note: tzinfo is stripped, but not required for relative times."""
145
+    Note: tzinfo is stripped, but not required for relative times.
146
+    """
145 147
     if not now:
146 148
         now = utcnow()
147 149
     return dict(day=now.day, month=now.month, year=now.year, hour=now.hour,
@@ -161,7 +163,8 @@ def unmarshall_time(tyme):
161 163
 
162 164
 
163 165
 def delta_seconds(before, after):
164
-    """
166
+    """Return the difference between two timing objects.
167
+
165 168
     Compute the difference in seconds between two date, time, or
166 169
     datetime objects (as a float, to microsecond resolution).
167 170
     """
@@ -174,8 +177,7 @@ def delta_seconds(before, after):
174 177
 
175 178
 
176 179
 def is_soon(dt, window):
177
-    """
178
-    Determines if time is going to happen in the next window seconds.
180
+    """Determines if time is going to happen in the next window seconds.
179 181
 
180 182
     :params dt: the time
181 183
     :params window: minimum seconds to remain to consider the time not soon

+ 1
- 0
requirements.txt View File

@@ -5,3 +5,4 @@ PrettyTable>=0.6,<0.8
5 5
 requests>=1.1
6 6
 simplejson>=2.0.9
7 7
 six
8
+Babel>=0.9.6

+ 12
- 10
tools/install_venv_common.py View File

@@ -114,9 +114,10 @@ class InstallVenv(object):
114 114
         print('Installing dependencies with pip (this can take a while)...')
115 115
 
116 116
         # 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')
119 119
         self.pip_install('setuptools')
120
+        self.pip_install('pbr')
120 121
 
121 122
         self.pip_install('-r', self.requirements)
122 123
         self.pip_install('-r', self.test_requirements)
@@ -201,12 +202,13 @@ class Fedora(Distro):
201 202
         RHEL: https://bugzilla.redhat.com/958868
202 203
         """
203 204
 
204
-        # Install "patch" program if it's not there
205
-        if not self.check_pkg('patch'):
206
-            self.die("Please install 'patch'.")
205
+        if os.path.exists('contrib/redhat-eventlet.patch'):
206
+            # Install "patch" program if it's not there
207
+            if not self.check_pkg('patch'):
208
+                self.die("Please install 'patch'.")
207 209
 
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')
210
+            # Apply the eventlet patch
211
+            self.apply_patch(os.path.join(self.venv, 'lib', self.py_version,
212
+                                          'site-packages',
213
+                                          'eventlet/green/subprocess.py'),
214
+                             'contrib/redhat-eventlet.patch')

Loading…
Cancel
Save