1
2 """This module is designed to be the equivalent of the enum type in other
3 languages. An enumeration object is created at run time, and contains
4 named members that are the enumeration elements.
5
6 The enumeration is also a tuple of all of the values in it. You can iterate
7 through the values, perform 'in' tests, slicing, etc. It also includes
8 functions to lookup specific values by name, or names by value.
9
10 You can specify your own element values, or use the create factory method
11 to create default Elements. These elements are unique system wide, and are
12 ordered based on the order of the elements in the enumeration. They also
13 are _repr_'d by the name of the element, which is convenient for testing,
14 debugging, and generation text output.
15
16 Example Code:
17 >>> # Using Element values
18 >>> Colors = Enumeration.create('red', 'green', 'blue')
19
20 >>> # Using explicitly specified values
21 >>>Borders = Enumeration.create(('SUNKEN', 1),
22 >>> ('RAISED', 32),
23 >>> ('FLAT', 2))
24
25 >>> x = Colors.red
26 >>> y = Colors.blue
27
28 >>> assert x < y
29 >>> assert x == Colors('red')
30 >>> assert Borders.FLAT == 2:
31 >>> assert 1 in Borders
32
33 :note: slightly modified by Sebastian Thiel to be more flexible and suitable as
34 base class
35 """
36 __docformat__ = "restructuredtext"
37 __contact__='garret at bgb dot cc'
38 __license__='freeware'
39
40 import platform
41
42 __all__ = ("Element", "Enumeration", "create")
43
45 """Internal helper class used to represent an ordered abstract value.
46
47 The values have string representations, have strictly defined ordering
48 (inside the set) and are never equal to anything but themselves.
49
50 They are usually created through the create factory method as values
51 for Enumerations.
52
53 They assume that the enumeration member will be filled in before any
54 comparisons are used. This is done by the Enumeration constructor.
55 """
56
58 self._name = name
59 self._value = value
60 self.enumeration = None
61
64
66 """:raise TypeError: if other cannot be used with this element"""
67 if ( self.__class__ != other.__class__ ) or ( self.enumeration is not other.enumeration ):
68 raise TypeError( "%s is incompatible with %s" % ( other, self ) )
69
71 """We override cmp only because we want the ordering of elements
72 in an enumeration to reflect their position in the enumeration.
73 """
74 try:
75 self._checkType( other )
76 except TypeError:
77 return NotImplemented
78
79
80
81 return cmp(self._value, other._value)
82
84 if not self.enumeration._supports_bitflags:
85 raise TypeError( "Enumeration %s of element %s has no bitflag support" % ( self.enumeration, self ) )
86
88 """Allows oring values together - only works if the values are actually orable
89 integer values
90
91 :return: integer with the ored result
92 :raise TypeError: if we are not a bitflag or other is not an element of our enumeration"""
93 self._checkType( other )
94 self._checkBitflag()
95 return self.value() | other.value()
96
98 """Allows to x-or values together - only works if element's values are xorable
99 integer values.
100
101 :param other: integer
102 :return: integer with the xored result"""
103 self._checkBitflag()
104 return self.value() ^ other
105
106
108 """Allow and with integers
109
110 :return: self if self & other == self or None if our bit is not set in other
111 :raise TypeError: if other is not an int"""
112 if not isinstance( other, int ):
113 raise TypeError( "require integer, got %s" % type( other ) )
114
115 if self.value() & other:
116 return self
117
118 return None
119
121 """:return: own value"""
122 return self._value
123
125 """:return: name of the element"""
126 return self._name
127
128
130 """This class represents an enumeration. You should not normally create
131 multiple instances of the same enumeration, instead create one with
132 however many references are convenient.
133
134 The enumeration is a tuple of all of the values in it. You can iterate
135 through the values, perform 'in' tests, slicing, etc. It also includes
136 functions to lookup specific values by name, or names by value.
137
138 You can specify your own element values, or use the create factory method
139 to create default Elements. These elements are unique system wide, and are
140 ordered based on the order of the elements in the enumeration. They also
141 are _repr_'d by the name of the element, which is convenient for testing,
142 debugging, and generation text output.
143
144 :note: pickling this class with Elements will fail as they contain cyclic
145 references that it cannot deal with
146 :todo: implement proper pickle __getstate__ and __setstate__ that deal with
147 that problem
148 """
149 _slots_ = ( "_nameMap", "_valueMap", "_supports_bitflags" )
150
151 - def __new__(self, names, values, **kwargs ):
152 """This method is needed to get the tuple parent class to do the
153 Right Thing(tm). """
154 return tuple.__new__(self, values)
155
162
164 """Prefer to return value from our value map"""
165 try:
166 return self.valueFromName( attr )
167 except ValueError:
168 raise AttributeError( "Element %s is not part of the enumeration" % attr )
169
170
171 - def __init__(self, names, values, **kwargs ):
172 """The arguments needed to construct this class are a list of
173 element names (which must be unique strings), and element values
174 (which can be any type of value). If you don't have special needs,
175 then it's recommended that you use Element instances for the values.
176
177 This constructor is normally called through the create factory (which
178 will create Elements for you), but that is not a requirement.
179 """
180
181 assert len(names) == len(values)
182
183
184 tuple.__init__(self)
185
186 self._nameMap = {}
187 self._valueMap = {}
188 self._supports_bitflags = kwargs.get( "_is_bitflag", False )
189
190
191 for i in xrange(len(names)):
192 name = names[i]
193 value = values[i]
194
195
196 if isinstance( value, Element ):
197 value.enumeration = self
198
199
200 assert not name in self._nameMap
201
202
203 self._nameMap[name] = value
204 self._valueMap[value] = name
205
206
208 """Look up the enumeration value for a given element name.
209
210 :raise ValueError:"""
211 try:
212 return self._nameMap[name]
213 except KeyError:
214 raise ValueError("Name %r not found in enumeration, pick one of %s" % (name, ', '.join(str(e) for e in self)))
215
216
218 """Look up the name of an enumeration element, given it's value.
219
220 If there are multiple elements with the same value, you will only
221 get a single matching name back. Which name is undefined.
222
223 :raise ValueError: if value is not a part of our enumeration"""
224 try:
225 return self._valueMap[value]
226 except KeyError:
227 raise ValueError("Value %r is not a member of this enumeration" % value)
228
229
231 """do-it method, see `next` and `previous`
232
233 :param direction: -1 = previous, 1 = next """
234 curindex = -1
235 for i,elm in enumerate( self ):
236 if elm == element:
237 curindex = i
238 break
239
240
241
242 assert curindex != -1
243
244 nextindex = curindex + direction
245 validnextindex = nextindex
246
247 if nextindex >= len( self ):
248 validnextindex = 0
249 elif nextindex < 0:
250 validnextindex = len( self ) - 1
251
252 if not wrap_around and ( validnextindex != nextindex ):
253 raise ValueError( "'%s' has no element in direction %i" % ( element, direction ) )
254
255 return self[ validnextindex ]
256
257
258 - def next( self, element, wrap_around = False ):
259 """:return: element following after given element
260 :param element: element whose successor to return
261 :param wrap_around: if True, the first Element will be returned if there is
262 no next element
263 :raise ValueError: if wrap_around is False and there is no next element"""
264 return self._nextOrPrevious( element, 1, wrap_around )
265
266 - def previous( self, element, wrap_around = False ):
267 """:return: element coming before the given element
268 :param element: element whose predecessor to return
269 :param wrap_around: see `next`
270 :raise ValueError: if wrap_around is False and there is no previous element"""
271 return self._nextOrPrevious( element, -1, wrap_around )
272
273 __call__ = valueFromName
274
275
276 -def create(*elements, **kwargs ):
277 """Factory method for Enumerations. Accepts of list of values that
278 can either be strings or (name, value) tuple pairs. Strings will
279 have an Element created for use as their value.
280 If you provide elements, the member returned when you access the enumeration
281 will be the element itself.
282
283 Example: Enumeration.create('fred', 'bob', ('joe', 42))
284 Example: Enumeration.create('fred', cls = EnumerationSubClass )
285 Example: Enumeration.create(Element('fred', Marge), ...)
286
287 :param kwargs:
288 * cls: The class to create an enumeration with, must be an instance of Enumeration
289 * elmcls: The class to create elements from, must be instance of Element
290 * bitflag: if True, default False, the values created will be suitable as bitflags.
291 This will fail if you passed more items in than supported by the OS ( 32 , 64, etc ) or if
292 you pass in tuples and thus define the values yourself.
293 :raise TypeError,ValueError: if bitflags cannot be supported in your case"""
294 cls = kwargs.pop( "cls", Enumeration )
295 elmcls = kwargs.pop( "elmcls", Element )
296 bitflag = kwargs.pop( "bitflag", False )
297
298 assert elements
299 assert Enumeration in cls.mro()
300 assert Element in elmcls.mro()
301
302
303 if bitflag:
304 maxbits = int( platform.architecture()[0][:-3] )
305 if maxbits < len( elements ):
306 raise ValueError( "You system can only represent %i bits in one integer, %i tried" % ( maxbits, len( elements ) ) )
307
308
309 kwargs[ '_is_bitflag' ] = True
310
311
312 names = list()
313 values = list()
314
315 for element in elements:
316
317 if isinstance( element, tuple ):
318 assert len(element) == 2
319 if bitflag:
320 raise TypeError( "If bitflag support is required, tuples are not allowed: %s" % str( element ) )
321
322 names.append(element[0])
323 values.append(element[1])
324
325 elif isinstance( element, basestring ):
326 val = len( names )
327 if bitflag:
328 val = 2 ** val
329
330 values.append( elmcls( element, val ) )
331 names.append(element)
332 elif isinstance(element, elmcls):
333 values.append(element)
334 names.append(element.name())
335 else:
336 raise "Unsupported element type: %s" % type( element )
337
338
339 return cls( names, values, **kwargs )
340