Merge branch 'stable-2.16' into stable-2.17
[ganeti-github.git] / lib / compat.py
1 #
2 #
3
4 # Copyright (C) 2010, 2011 Google Inc.
5 # All rights reserved.
6 #
7 # Redistribution and use in source and binary forms, with or without
8 # modification, are permitted provided that the following conditions are
9 # met:
10 #
11 # 1. Redistributions of source code must retain the above copyright notice,
12 # this list of conditions and the following disclaimer.
13 #
14 # 2. Redistributions in binary form must reproduce the above copyright
15 # notice, this list of conditions and the following disclaimer in the
16 # documentation and/or other materials provided with the distribution.
17 #
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
19 # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
20 # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
22 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23 # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24 # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
25 # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26 # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27 # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30
31 """Module containing backported language/library functionality.
32
33 """
34
35 import itertools
36 import operator
37
38 try:
39 # pylint: disable=F0401
40 import functools
41 except ImportError:
42 functools = None
43
44 try:
45 # pylint: disable=F0401
46 import roman
47 except ImportError:
48 roman = None
49
50
51 # compat.md5_hash and compat.sha1_hash can be called to generate and md5 and a
52 # sha1 hashing modules, under python 2.4, 2.5 and 2.6, even though some changes
53 # went on. compat.sha1 is python-version specific and is used for python
54 # modules (hmac, for example) which have changed their behavior as well from
55 # one version to the other.
56 try:
57 # Yes, these don't always exist, that's why we're testing
58 # Yes, we're not using the imports in this module.
59 from hashlib import md5 as md5_hash # pylint: disable=W0611,E0611,F0401
60 from hashlib import sha1 as sha1_hash # pylint: disable=W0611,E0611,F0401
61 # this additional version is needed for compatibility with the hmac module
62 sha1 = sha1_hash # pylint: disable=C0103
63 except ImportError:
64 from md5 import new as md5_hash # pylint: disable=W0611
65 import sha
66 sha1 = sha
67 sha1_hash = sha.new # pylint: disable=C0103
68
69
70 def _all(seq):
71 """Returns True if all elements in the iterable are True.
72
73 """
74 for _ in itertools.ifilterfalse(bool, seq):
75 return False
76 return True
77
78
79 def _any(seq):
80 """Returns True if any element of the iterable are True.
81
82 """
83 for _ in itertools.ifilter(bool, seq):
84 return True
85 return False
86
87
88 try:
89 # pylint: disable=E0601
90 # pylint: disable=W0622
91 all = all
92 except NameError:
93 all = _all
94
95 try:
96 # pylint: disable=E0601
97 # pylint: disable=W0622
98 any = any
99 except NameError:
100 any = _any
101
102
103 def partition(seq, pred=bool): # pylint: disable=W0622
104 """Partition a list in two, based on the given predicate.
105
106 """
107 return (list(itertools.ifilter(pred, seq)),
108 list(itertools.ifilterfalse(pred, seq)))
109
110
111 # Even though we're using Python's built-in "partial" function if available,
112 # this one is always defined for testing.
113 def _partial(func, *args, **keywords): # pylint: disable=W0622
114 """Decorator with partial application of arguments and keywords.
115
116 This function was copied from Python's documentation.
117
118 """
119 def newfunc(*fargs, **fkeywords):
120 newkeywords = keywords.copy()
121 newkeywords.update(fkeywords)
122 return func(*(args + fargs), **newkeywords)
123
124 newfunc.func = func
125 newfunc.args = args
126 newfunc.keywords = keywords
127 return newfunc
128
129
130 if functools is None:
131 partial = _partial
132 else:
133 partial = functools.partial
134
135
136 def RomanOrRounded(value, rounding, convert=True):
137 """Try to round the value to the closest integer and return it as a roman
138 numeral. If the conversion is disabled, or if the roman module could not be
139 loaded, round the value to the specified level and return it.
140
141 @type value: number
142 @param value: value to convert
143 @type rounding: integer
144 @param rounding: how many decimal digits the number should be rounded to
145 @type convert: boolean
146 @param convert: if False, don't try conversion at all
147 @rtype: string
148 @return: roman numeral for val, or formatted string representing val if
149 conversion didn't succeed
150
151 """
152 def _FormatOutput(val, r):
153 format_string = "%0." + str(r) + "f"
154 return format_string % val
155
156 if roman is not None and convert:
157 try:
158 return roman.toRoman(round(value, 0))
159 except roman.RomanError:
160 return _FormatOutput(value, rounding)
161 return _FormatOutput(value, rounding)
162
163
164 def TryToRoman(val, convert=True):
165 """Try to convert a value to roman numerals
166
167 If the roman module could be loaded convert the given value to a roman
168 numeral. Gracefully fail back to leaving the value untouched.
169
170 @type val: integer
171 @param val: value to convert
172 @type convert: boolean
173 @param convert: if False, don't try conversion at all
174 @rtype: string or typeof(val)
175 @return: roman numeral for val, or val if conversion didn't succeed
176
177 """
178 if roman is not None and convert:
179 try:
180 return roman.toRoman(val)
181 except roman.RomanError:
182 return val
183 else:
184 return val
185
186
187 def UniqueFrozenset(seq):
188 """Makes C{frozenset} from sequence after checking for duplicate elements.
189
190 @raise ValueError: When there are duplicate elements
191
192 """
193 if isinstance(seq, (list, tuple)):
194 items = seq
195 else:
196 items = list(seq)
197
198 result = frozenset(items)
199
200 if len(items) != len(result):
201 raise ValueError("Duplicate values found")
202
203 return result
204
205
206 #: returns the first element of a list-like value
207 fst = operator.itemgetter(0)
208
209 #: returns the second element of a list-like value
210 snd = operator.itemgetter(1)