Package mrv :: Module enum
[hide private]
[frames] | no frames]

Source Code for Module mrv.enum

  1  # -*- coding: utf-8 -*-
 
  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  
 
44 -class Element(object):
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 # we do not define slots to stay pickable ( without reimplementing things )
57 - def __init__(self, name, value):
58 self._name = name 59 self._value = value 60 self.enumeration = None # Will be filled in later
61
62 - def __repr__(self):
63 return self._name
64
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 ) )
69
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. 73 """ 74 try: 75 self._checkType( other ) 76 except TypeError: 77 return NotImplemented # to make cmp fail 78 79 # If we are both elements in the same enumeration, compare 80 # values for ordering 81 return cmp(self._value, other._value)
82
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 ) )
86
87 - def __or__( self, other ):
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
97 - def __xor__( self, other ):
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
107 - def __and__( self, other ):
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
120 - def value( self ):
121 """:return: own value""" 122 return self._value
123
124 - def name( self ):
125 """:return: name of the element""" 126 return self._name
127 128
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. 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
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 ) 160 161 raise AttributeError( "No assignments allowed" )
162
163 - def __getattr__( self , attr ):
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 # We are a tuple of our values, plus more.... 184 tuple.__init__(self) 185 186 self._nameMap = {} 187 self._valueMap = {} 188 self._supports_bitflags = kwargs.get( "_is_bitflag", False ) # insurance for bitflags 189 190 191 for i in xrange(len(names)): 192 name = names[i] 193 value = values[i] 194 195 # Tell the elements which enumeration they belong too 196 if isinstance( value, Element ): 197 value.enumeration = self 198 199 # Prove that all names are unique 200 assert not name in self._nameMap 201 202 # create mappings from name to value, and vice versa 203 self._nameMap[name] = value 204 self._valueMap[value] = name
205 206
207 - def valueFromName(self, name):
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 # END exception handling 216
217 - def nameFromValue(self, value):
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 # END exception handling 229
230 - def _nextOrPrevious( self, element, direction, wrap_around ):
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 # END if elms match 240 # END for each element 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 # check range 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 # prepare enum args 309 kwargs[ '_is_bitflag' ] = True 310 # END bitflag assertion 311 312 names = list() 313 values = list() 314 315 for element in elements: 316 # we explicitly check this per element ! 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 # 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()) 335 else: 336 raise "Unsupported element type: %s" % type( element ) 337 # END for each element 338 339 return cls( names, values, **kwargs )
340