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.
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.
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.
17
>>> # Using Element values
18
>>> Colors = Enumeration.create('red', 'green', 'blue')
20
>>> # Using explicitly specified values
21
>>>Borders = Enumeration.create(('SUNKEN', 1),
29
>>> assert x == Colors('red')
30
>>> assert Borders.FLAT == 2:
31
>>> assert 1 in Borders
33
:note: slightly modified by Sebastian Thiel to be more flexible and suitable as
36
__docformat__ = "restructuredtext"
37
__contact__='garret at bgb dot cc'
42
__all__ = ("Element", "Enumeration", "create")
45
"""Internal helper class used to represent an ordered abstract value.
47
The values have string representations, have strictly defined ordering
48
(inside the set) and are never equal to anything but themselves.
50
They are usually created through the create factory method as values
53
They assume that the enumeration member will be filled in before any
54
comparisons are used. This is done by the Enumeration constructor.
56
# we do not define slots to stay pickable ( without reimplementing things )
57
def __init__(self, name, value):
60
self.enumeration = None # Will be filled in later
65
def _checkType( self, other ):
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 ) )
70
def __cmp__(self, other):
71
"""We override cmp only because we want the ordering of elements
72
in an enumeration to reflect their position in the enumeration.
75
self._checkType( other )
77
return NotImplemented # to make cmp fail
79
# If we are both elements in the same enumeration, compare
81
return cmp(self._value, other._value)
83
def _checkBitflag( self ):
84
if not self.enumeration._supports_bitflags:
85
raise TypeError( "Enumeration %s of element %s has no bitflag support" % ( self.enumeration, self ) )
87
def __or__( self, other ):
88
"""Allows oring values together - only works if the values are actually orable
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 )
95
return self.value() | other.value()
97
def __xor__( self, other ):
98
"""Allows to x-or values together - only works if element's values are xorable
101
:param other: integer
102
:return: integer with the xored result"""
104
return self.value() ^ other
107
def __and__( self, other ):
108
"""Allow and with integers
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 ) )
115
if self.value() & other:
121
""":return: own value"""
125
""":return: name of the element"""
129
class Enumeration(tuple):
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.
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.
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.
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
149
_slots_ = ( "_nameMap", "_valueMap", "_supports_bitflags" )
151
def __new__(self, names, values, **kwargs ):
152
"""This method is needed to get the tuple parent class to do the
154
return tuple.__new__(self, values)
156
def __setattr__( self, name, value ):
157
"""Do not allow to change this instance"""
158
if name in self._slots_:
159
return super( Enumeration, self ).__setattr__( name, value )
161
raise AttributeError( "No assignments allowed" )
163
def __getattr__( self , attr ):
164
"""Prefer to return value from our value map"""
166
return self.valueFromName( attr )
168
raise AttributeError( "Element %s is not part of the enumeration" % attr )
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.
177
This constructor is normally called through the create factory (which
178
will create Elements for you), but that is not a requirement.
181
assert len(names) == len(values)
183
# We are a tuple of our values, plus more....
188
self._supports_bitflags = kwargs.get( "_is_bitflag", False ) # insurance for bitflags
191
for i in xrange(len(names)):
195
# Tell the elements which enumeration they belong too
196
if isinstance( value, Element ):
197
value.enumeration = self
199
# Prove that all names are unique
200
assert not name in self._nameMap
202
# create mappings from name to value, and vice versa
203
self._nameMap[name] = value
204
self._valueMap[value] = name
207
def valueFromName(self, name):
208
"""Look up the enumeration value for a given element name.
210
:raise ValueError:"""
212
return self._nameMap[name]
214
raise ValueError("Name %r not found in enumeration, pick one of %s" % (name, ', '.join(str(e) for e in self)))
215
# END exception handling
217
def nameFromValue(self, value):
218
"""Look up the name of an enumeration element, given it's value.
220
If there are multiple elements with the same value, you will only
221
get a single matching name back. Which name is undefined.
223
:raise ValueError: if value is not a part of our enumeration"""
225
return self._valueMap[value]
227
raise ValueError("Value %r is not a member of this enumeration" % value)
228
# END exception handling
230
def _nextOrPrevious( self, element, direction, wrap_around ):
231
"""do-it method, see `next` and `previous`
233
:param direction: -1 = previous, 1 = next """
235
for i,elm in enumerate( self ):
240
# END for each element
242
assert curindex != -1
244
nextindex = curindex + direction
245
validnextindex = nextindex
247
if nextindex >= len( self ):
250
validnextindex = len( self ) - 1
252
if not wrap_around and ( validnextindex != nextindex ):
253
raise ValueError( "'%s' has no element in direction %i" % ( element, direction ) )
255
return self[ validnextindex ]
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
263
:raise ValueError: if wrap_around is False and there is no next element"""
264
return self._nextOrPrevious( element, 1, wrap_around )
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 )
273
__call__ = valueFromName
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.
283
Example: Enumeration.create('fred', 'bob', ('joe', 42))
284
Example: Enumeration.create('fred', cls = EnumerationSubClass )
285
Example: Enumeration.create(Element('fred', Marge), ...)
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 )
299
assert Enumeration in cls.mro()
300
assert Element in elmcls.mro()
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 ) ) )
309
kwargs[ '_is_bitflag' ] = True
310
# END bitflag assertion
315
for element in elements:
316
# we explicitly check this per element !
317
if isinstance( element, tuple ):
318
assert len(element) == 2
320
raise TypeError( "If bitflag support is required, tuples are not allowed: %s" % str( element ) )
322
names.append(element[0])
323
values.append(element[1])
325
elif isinstance( element, basestring ):
329
# END bitflag value generation
330
values.append( elmcls( element, val ) ) # zero based ids
331
names.append(element)
332
elif isinstance(element, elmcls):
333
values.append(element)
334
names.append(element.name())
336
raise "Unsupported element type: %s" % type( element )
337
# END for each element
339
return cls( names, values, **kwargs )