blob: 38d95c5b4ccfa83f0e17dfde0f85b666a6f635ba [file] [log] [blame]
Ethan Furman6b3d64a2013-06-14 16:55:46 -07001"""Python Enumerations"""
2
3import sys
4from collections import OrderedDict
5from types import MappingProxyType
6
Ethan Furmanf24bb352013-07-18 17:05:39 -07007__all__ = ['Enum', 'IntEnum', 'unique']
Ethan Furman6b3d64a2013-06-14 16:55:46 -07008
9
10class _RouteClassAttributeToGetattr:
11 """Route attribute access on a class to __getattr__.
12
13 This is a descriptor, used to define attributes that act differently when
14 accessed through an instance and through a class. Instance access remains
15 normal, but access to an attribute through a class will be routed to the
16 class's __getattr__ method; this is done by raising AttributeError.
17
18 """
19 def __init__(self, fget=None):
20 self.fget = fget
21
22 def __get__(self, instance, ownerclass=None):
23 if instance is None:
24 raise AttributeError()
25 return self.fget(instance)
26
27 def __set__(self, instance, value):
28 raise AttributeError("can't set attribute")
29
30 def __delete__(self, instance):
31 raise AttributeError("can't delete attribute")
32
33
34def _is_dunder(name):
35 """Returns True if a __dunder__ name, False otherwise."""
36 return (name[:2] == name[-2:] == '__' and
37 name[2:3] != '_' and
38 name[-3:-2] != '_')
39
40
41def _is_sunder(name):
42 """Returns True if a _sunder_ name, False otherwise."""
43 return (name[0] == name[-1] == '_' and
44 name[1:2] != '_' and
45 name[-2:-1] != '_')
46
47
48def _make_class_unpicklable(cls):
49 """Make the given class un-picklable."""
50 def _break_on_call_reduce(self):
51 raise TypeError('%r cannot be pickled' % self)
52 cls.__reduce__ = _break_on_call_reduce
53 cls.__module__ = '<unknown>'
54
55
56class _EnumDict(dict):
57 """Keeps track of definition order of the enum items.
58
59 EnumMeta will use the names found in self._member_names as the
60 enumeration member names.
61
62 """
63 def __init__(self):
64 super().__init__()
65 self._member_names = []
66
67 def __setitem__(self, key, value):
68 """Changes anything not dundered or that doesn't have __get__.
69
70 If a descriptor is added with the same name as an enum member, the name
71 is removed from _member_names (this may leave a hole in the numerical
72 sequence of values).
73
74 If an enum member name is used twice, an error is raised; duplicate
75 values are not checked for.
76
77 Single underscore (sunder) names are reserved.
78
79 """
80 if _is_sunder(key):
81 raise ValueError('_names_ are reserved for future Enum use')
82 elif _is_dunder(key) or hasattr(value, '__get__'):
83 if key in self._member_names:
84 # overwriting an enum with a method? then remove the name from
85 # _member_names or it will become an enum anyway when the class
86 # is created
87 self._member_names.remove(key)
88 else:
89 if key in self._member_names:
90 raise TypeError('Attempted to reuse key: %r' % key)
91 self._member_names.append(key)
92 super().__setitem__(key, value)
93
94
95# Dummy value for Enum as EnumMeta explicity checks for it, but of course until
96# EnumMeta finishes running the first time the Enum class doesn't exist. This
97# is also why there are checks in EnumMeta like `if Enum is not None`
98Enum = None
99
100
101class EnumMeta(type):
102 """Metaclass for Enum"""
103 @classmethod
104 def __prepare__(metacls, cls, bases):
105 return _EnumDict()
106
107 def __new__(metacls, cls, bases, classdict):
108 # an Enum class is final once enumeration items have been defined; it
109 # cannot be mixed with other types (int, float, etc.) if it has an
110 # inherited __new__ unless a new __new__ is defined (or the resulting
111 # class will fail).
112 member_type, first_enum = metacls._get_mixins_(bases)
113 __new__, save_new, use_args = metacls._find_new_(classdict, member_type,
114 first_enum)
115
116 # save enum items into separate mapping so they don't get baked into
117 # the new class
118 members = {k: classdict[k] for k in classdict._member_names}
119 for name in classdict._member_names:
120 del classdict[name]
121
122 # check for illegal enum names (any others?)
123 invalid_names = set(members) & {'mro', }
124 if invalid_names:
125 raise ValueError('Invalid enum member name: {0}'.format(
126 ','.join(invalid_names)))
127
128 # create our new Enum type
129 enum_class = super().__new__(metacls, cls, bases, classdict)
130 enum_class._member_names = [] # names in definition order
131 enum_class._member_map = OrderedDict() # name->value map
132
133 # Reverse value->name map for hashable values.
134 enum_class._value2member_map = {}
135
136 # check for a __getnewargs__, and if not present sabotage
137 # pickling, since it won't work anyway
138 if (member_type is not object and
139 member_type.__dict__.get('__getnewargs__') is None
140 ):
141 _make_class_unpicklable(enum_class)
142
143 # instantiate them, checking for duplicates as we go
144 # we instantiate first instead of checking for duplicates first in case
145 # a custom __new__ is doing something funky with the values -- such as
146 # auto-numbering ;)
147 for member_name in classdict._member_names:
148 value = members[member_name]
149 if not isinstance(value, tuple):
150 args = (value, )
151 else:
152 args = value
153 if member_type is tuple: # special case for tuple enums
154 args = (args, ) # wrap it one more time
155 if not use_args:
156 enum_member = __new__(enum_class)
157 enum_member._value = value
158 else:
159 enum_member = __new__(enum_class, *args)
160 if not hasattr(enum_member, '_value'):
161 enum_member._value = member_type(*args)
162 enum_member._member_type = member_type
163 enum_member._name = member_name
164 enum_member.__init__(*args)
165 # If another member with the same value was already defined, the
166 # new member becomes an alias to the existing one.
167 for name, canonical_member in enum_class._member_map.items():
168 if canonical_member.value == enum_member._value:
169 enum_member = canonical_member
170 break
171 else:
172 # Aliases don't appear in member names (only in __members__).
173 enum_class._member_names.append(member_name)
174 enum_class._member_map[member_name] = enum_member
175 try:
176 # This may fail if value is not hashable. We can't add the value
177 # to the map, and by-value lookups for this value will be
178 # linear.
179 enum_class._value2member_map[value] = enum_member
180 except TypeError:
181 pass
182
183 # double check that repr and friends are not the mixin's or various
184 # things break (such as pickle)
185 for name in ('__repr__', '__str__', '__getnewargs__'):
186 class_method = getattr(enum_class, name)
187 obj_method = getattr(member_type, name, None)
188 enum_method = getattr(first_enum, name, None)
189 if obj_method is not None and obj_method is class_method:
190 setattr(enum_class, name, enum_method)
191
192 # replace any other __new__ with our own (as long as Enum is not None,
193 # anyway) -- again, this is to support pickle
194 if Enum is not None:
195 # if the user defined their own __new__, save it before it gets
196 # clobbered in case they subclass later
197 if save_new:
198 enum_class.__new_member__ = __new__
199 enum_class.__new__ = Enum.__new__
200 return enum_class
201
202 def __call__(cls, value, names=None, *, module=None, type=None):
203 """Either returns an existing member, or creates a new enum class.
204
205 This method is used both when an enum class is given a value to match
206 to an enumeration member (i.e. Color(3)) and for the functional API
207 (i.e. Color = Enum('Color', names='red green blue')).
208
209 When used for the functional API: `module`, if set, will be stored in
210 the new class' __module__ attribute; `type`, if set, will be mixed in
211 as the first base class.
212
213 Note: if `module` is not set this routine will attempt to discover the
214 calling module by walking the frame stack; if this is unsuccessful
215 the resulting class will not be pickleable.
216
217 """
218 if names is None: # simple value lookup
219 return cls.__new__(cls, value)
220 # otherwise, functional API: we're creating a new Enum type
221 return cls._create_(value, names, module=module, type=type)
222
223 def __contains__(cls, member):
224 return isinstance(member, cls) and member.name in cls._member_map
225
226 def __dir__(self):
227 return ['__class__', '__doc__', '__members__'] + self._member_names
228
229 @property
230 def __members__(cls):
231 """Returns a mapping of member name->value.
232
233 This mapping lists all enum members, including aliases. Note that this
234 is a read-only view of the internal mapping.
235
236 """
237 return MappingProxyType(cls._member_map)
238
239 def __getattr__(cls, name):
240 """Return the enum member matching `name`
241
242 We use __getattr__ instead of descriptors or inserting into the enum
243 class' __dict__ in order to support `name` and `value` being both
244 properties for enum members (which live in the class' __dict__) and
245 enum members themselves.
246
247 """
248 if _is_dunder(name):
249 raise AttributeError(name)
250 try:
251 return cls._member_map[name]
252 except KeyError:
253 raise AttributeError(name) from None
254
255 def __getitem__(cls, name):
256 return cls._member_map[name]
257
258 def __iter__(cls):
259 return (cls._member_map[name] for name in cls._member_names)
260
261 def __len__(cls):
262 return len(cls._member_names)
263
264 def __repr__(cls):
265 return "<enum %r>" % cls.__name__
266
267 def _create_(cls, class_name, names=None, *, module=None, type=None):
268 """Convenience method to create a new Enum class.
269
270 `names` can be:
271
272 * A string containing member names, separated either with spaces or
273 commas. Values are auto-numbered from 1.
274 * An iterable of member names. Values are auto-numbered from 1.
275 * An iterable of (member name, value) pairs.
276 * A mapping of member name -> value.
277
278 """
279 metacls = cls.__class__
280 bases = (cls, ) if type is None else (type, cls)
281 classdict = metacls.__prepare__(class_name, bases)
282
283 # special processing needed for names?
284 if isinstance(names, str):
285 names = names.replace(',', ' ').split()
286 if isinstance(names, (tuple, list)) and isinstance(names[0], str):
287 names = [(e, i) for (i, e) in enumerate(names, 1)]
288
289 # Here, names is either an iterable of (name, value) or a mapping.
290 for item in names:
291 if isinstance(item, str):
292 member_name, member_value = item, names[item]
293 else:
294 member_name, member_value = item
295 classdict[member_name] = member_value
296 enum_class = metacls.__new__(metacls, class_name, bases, classdict)
297
298 # TODO: replace the frame hack if a blessed way to know the calling
299 # module is ever developed
300 if module is None:
301 try:
302 module = sys._getframe(2).f_globals['__name__']
303 except (AttributeError, ValueError) as exc:
304 pass
305 if module is None:
306 _make_class_unpicklable(enum_class)
307 else:
308 enum_class.__module__ = module
309
310 return enum_class
311
312 @staticmethod
313 def _get_mixins_(bases):
314 """Returns the type for creating enum members, and the first inherited
315 enum class.
316
317 bases: the tuple of bases that was given to __new__
318
319 """
320 if not bases:
321 return object, Enum
322
323 # double check that we are not subclassing a class with existing
324 # enumeration members; while we're at it, see if any other data
325 # type has been mixed in so we can use the correct __new__
326 member_type = first_enum = None
327 for base in bases:
328 if (base is not Enum and
329 issubclass(base, Enum) and
330 base._member_names):
331 raise TypeError("Cannot extend enumerations")
332 # base is now the last base in bases
333 if not issubclass(base, Enum):
334 raise TypeError("new enumerations must be created as "
335 "`ClassName([mixin_type,] enum_type)`")
336
337 # get correct mix-in type (either mix-in type of Enum subclass, or
338 # first base if last base is Enum)
339 if not issubclass(bases[0], Enum):
340 member_type = bases[0] # first data type
341 first_enum = bases[-1] # enum type
342 else:
343 for base in bases[0].__mro__:
344 # most common: (IntEnum, int, Enum, object)
345 # possible: (<Enum 'AutoIntEnum'>, <Enum 'IntEnum'>,
346 # <class 'int'>, <Enum 'Enum'>,
347 # <class 'object'>)
348 if issubclass(base, Enum):
349 if first_enum is None:
350 first_enum = base
351 else:
352 if member_type is None:
353 member_type = base
354
355 return member_type, first_enum
356
357 @staticmethod
358 def _find_new_(classdict, member_type, first_enum):
359 """Returns the __new__ to be used for creating the enum members.
360
361 classdict: the class dictionary given to __new__
362 member_type: the data type whose __new__ will be used by default
363 first_enum: enumeration to check for an overriding __new__
364
365 """
366 # now find the correct __new__, checking to see of one was defined
367 # by the user; also check earlier enum classes in case a __new__ was
368 # saved as __new_member__
369 __new__ = classdict.get('__new__', None)
370
371 # should __new__ be saved as __new_member__ later?
372 save_new = __new__ is not None
373
374 if __new__ is None:
375 # check all possibles for __new_member__ before falling back to
376 # __new__
377 for method in ('__new_member__', '__new__'):
378 for possible in (member_type, first_enum):
379 target = getattr(possible, method, None)
380 if target not in {
381 None,
382 None.__new__,
383 object.__new__,
384 Enum.__new__,
385 }:
386 __new__ = target
387 break
388 if __new__ is not None:
389 break
390 else:
391 __new__ = object.__new__
392
393 # if a non-object.__new__ is used then whatever value/tuple was
394 # assigned to the enum member name will be passed to __new__ and to the
395 # new enum member's __init__
396 if __new__ is object.__new__:
397 use_args = False
398 else:
399 use_args = True
400
401 return __new__, save_new, use_args
402
403
404class Enum(metaclass=EnumMeta):
405 """Generic enumeration.
406
407 Derive from this class to define new enumerations.
408
409 """
410 def __new__(cls, value):
411 # all enum instances are actually created during class construction
412 # without calling this method; this method is called by the metaclass'
413 # __call__ (i.e. Color(3) ), and by pickle
414 if type(value) is cls:
415 # For lookups like Color(Color.red)
416 return value
417 # by-value search for a matching enum member
418 # see if it's in the reverse mapping (for hashable values)
419 if value in cls._value2member_map:
420 return cls._value2member_map[value]
421 # not there, now do long search -- O(n) behavior
422 for member in cls._member_map.values():
423 if member.value == value:
424 return member
425 raise ValueError("%s is not a valid %s" % (value, cls.__name__))
426
427 def __repr__(self):
428 return "<%s.%s: %r>" % (
429 self.__class__.__name__, self._name, self._value)
430
431 def __str__(self):
432 return "%s.%s" % (self.__class__.__name__, self._name)
433
434 def __dir__(self):
435 return (['__class__', '__doc__', 'name', 'value'])
436
437 def __eq__(self, other):
438 if type(other) is self.__class__:
439 return self is other
440 return NotImplemented
441
442 def __getnewargs__(self):
443 return (self._value, )
444
445 def __hash__(self):
446 return hash(self._name)
447
448 # _RouteClassAttributeToGetattr is used to provide access to the `name`
449 # and `value` properties of enum members while keeping some measure of
450 # protection from modification, while still allowing for an enumeration
451 # to have members named `name` and `value`. This works because enumeration
452 # members are not set directly on the enum class -- __getattr__ is
453 # used to look them up.
454
455 @_RouteClassAttributeToGetattr
456 def name(self):
457 return self._name
458
459 @_RouteClassAttributeToGetattr
460 def value(self):
461 return self._value
462
463
464class IntEnum(int, Enum):
465 """Enum where members are also (and must be) ints"""
Ethan Furmanf24bb352013-07-18 17:05:39 -0700466
467
468def unique(enumeration):
469 """Class decorator for enumerations ensuring unique member values."""
470 duplicates = []
471 for name, member in enumeration.__members__.items():
472 if name != member.name:
473 duplicates.append((name, member.name))
474 if duplicates:
475 alias_details = ', '.join(
476 ["%s -> %s" % (alias, name) for (alias, name) in duplicates])
477 raise ValueError('duplicate values found in %r: %s' %
478 (enumeration, alias_details))
479 return enumeration