blob: 40546da761c13bfd83607736e3d5babf169c0f05 [file] [log] [blame]
Ethan Furman6b3d64a2013-06-14 16:55:46 -07001import sys
2from collections import OrderedDict
3from types import MappingProxyType
4
Ethan Furmanf24bb352013-07-18 17:05:39 -07005__all__ = ['Enum', 'IntEnum', 'unique']
Ethan Furman6b3d64a2013-06-14 16:55:46 -07006
7
8class _RouteClassAttributeToGetattr:
9 """Route attribute access on a class to __getattr__.
10
11 This is a descriptor, used to define attributes that act differently when
12 accessed through an instance and through a class. Instance access remains
13 normal, but access to an attribute through a class will be routed to the
14 class's __getattr__ method; this is done by raising AttributeError.
15
16 """
17 def __init__(self, fget=None):
18 self.fget = fget
19
20 def __get__(self, instance, ownerclass=None):
21 if instance is None:
22 raise AttributeError()
23 return self.fget(instance)
24
25 def __set__(self, instance, value):
26 raise AttributeError("can't set attribute")
27
28 def __delete__(self, instance):
29 raise AttributeError("can't delete attribute")
30
31
Ethan Furman101e0742013-09-15 12:34:36 -070032def _is_descriptor(obj):
33 """Returns True if obj is a descriptor, False otherwise."""
34 return (
35 hasattr(obj, '__get__') or
36 hasattr(obj, '__set__') or
37 hasattr(obj, '__delete__'))
38
39
Ethan Furman6b3d64a2013-06-14 16:55:46 -070040def _is_dunder(name):
41 """Returns True if a __dunder__ name, False otherwise."""
42 return (name[:2] == name[-2:] == '__' and
43 name[2:3] != '_' and
44 name[-3:-2] != '_')
45
46
47def _is_sunder(name):
48 """Returns True if a _sunder_ name, False otherwise."""
49 return (name[0] == name[-1] == '_' and
50 name[1:2] != '_' and
51 name[-2:-1] != '_')
52
53
54def _make_class_unpicklable(cls):
55 """Make the given class un-picklable."""
56 def _break_on_call_reduce(self):
57 raise TypeError('%r cannot be pickled' % self)
58 cls.__reduce__ = _break_on_call_reduce
59 cls.__module__ = '<unknown>'
60
Ethan Furman101e0742013-09-15 12:34:36 -070061
Ethan Furman6b3d64a2013-06-14 16:55:46 -070062class _EnumDict(dict):
Ethan Furman101e0742013-09-15 12:34:36 -070063 """Track enum member order and ensure member names are not reused.
Ethan Furman6b3d64a2013-06-14 16:55:46 -070064
65 EnumMeta will use the names found in self._member_names as the
66 enumeration member names.
67
68 """
69 def __init__(self):
70 super().__init__()
71 self._member_names = []
72
73 def __setitem__(self, key, value):
Ethan Furman101e0742013-09-15 12:34:36 -070074 """Changes anything not dundered or not a descriptor.
Ethan Furman6b3d64a2013-06-14 16:55:46 -070075
76 If an enum member name is used twice, an error is raised; duplicate
77 values are not checked for.
78
79 Single underscore (sunder) names are reserved.
80
81 """
82 if _is_sunder(key):
83 raise ValueError('_names_ are reserved for future Enum use')
Ethan Furman101e0742013-09-15 12:34:36 -070084 elif _is_dunder(key):
85 pass
86 elif key in self._member_names:
87 # descriptor overwriting an enum?
88 raise TypeError('Attempted to reuse key: %r' % key)
89 elif not _is_descriptor(value):
90 if key in self:
91 # enum overwriting a descriptor?
92 raise TypeError('Key already defined as: %r' % self[key])
Ethan Furman6b3d64a2013-06-14 16:55:46 -070093 self._member_names.append(key)
94 super().__setitem__(key, value)
95
96
Ethan Furman101e0742013-09-15 12:34:36 -070097
Ezio Melotti9a3777e2013-08-17 15:53:55 +030098# Dummy value for Enum as EnumMeta explicitly checks for it, but of course
99# until EnumMeta finishes running the first time the Enum class doesn't exist.
100# This is also why there are checks in EnumMeta like `if Enum is not None`
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700101Enum = None
102
103
104class EnumMeta(type):
105 """Metaclass for Enum"""
106 @classmethod
107 def __prepare__(metacls, cls, bases):
108 return _EnumDict()
109
110 def __new__(metacls, cls, bases, classdict):
111 # an Enum class is final once enumeration items have been defined; it
112 # cannot be mixed with other types (int, float, etc.) if it has an
113 # inherited __new__ unless a new __new__ is defined (or the resulting
114 # class will fail).
115 member_type, first_enum = metacls._get_mixins_(bases)
116 __new__, save_new, use_args = metacls._find_new_(classdict, member_type,
117 first_enum)
118
119 # save enum items into separate mapping so they don't get baked into
120 # the new class
121 members = {k: classdict[k] for k in classdict._member_names}
122 for name in classdict._member_names:
123 del classdict[name]
124
125 # check for illegal enum names (any others?)
126 invalid_names = set(members) & {'mro', }
127 if invalid_names:
128 raise ValueError('Invalid enum member name: {0}'.format(
129 ','.join(invalid_names)))
130
131 # create our new Enum type
132 enum_class = super().__new__(metacls, cls, bases, classdict)
Ethan Furman520ad572013-07-19 19:47:21 -0700133 enum_class._member_names_ = [] # names in definition order
134 enum_class._member_map_ = OrderedDict() # name->value map
Ethan Furman5e5a8232013-08-04 08:42:23 -0700135 enum_class._member_type_ = member_type
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700136
137 # Reverse value->name map for hashable values.
Ethan Furman520ad572013-07-19 19:47:21 -0700138 enum_class._value2member_map_ = {}
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700139
140 # check for a __getnewargs__, and if not present sabotage
141 # pickling, since it won't work anyway
142 if (member_type is not object and
143 member_type.__dict__.get('__getnewargs__') is None
144 ):
145 _make_class_unpicklable(enum_class)
146
147 # instantiate them, checking for duplicates as we go
148 # we instantiate first instead of checking for duplicates first in case
149 # a custom __new__ is doing something funky with the values -- such as
150 # auto-numbering ;)
151 for member_name in classdict._member_names:
152 value = members[member_name]
153 if not isinstance(value, tuple):
154 args = (value, )
155 else:
156 args = value
157 if member_type is tuple: # special case for tuple enums
158 args = (args, ) # wrap it one more time
159 if not use_args:
160 enum_member = __new__(enum_class)
Ethan Furmanb41803e2013-07-25 13:50:45 -0700161 if not hasattr(enum_member, '_value_'):
162 enum_member._value_ = value
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700163 else:
164 enum_member = __new__(enum_class, *args)
Ethan Furmanb41803e2013-07-25 13:50:45 -0700165 if not hasattr(enum_member, '_value_'):
166 enum_member._value_ = member_type(*args)
Ethan Furman520ad572013-07-19 19:47:21 -0700167 value = enum_member._value_
Ethan Furman520ad572013-07-19 19:47:21 -0700168 enum_member._name_ = member_name
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700169 enum_member.__init__(*args)
170 # If another member with the same value was already defined, the
171 # new member becomes an alias to the existing one.
Ethan Furman520ad572013-07-19 19:47:21 -0700172 for name, canonical_member in enum_class._member_map_.items():
173 if canonical_member.value == enum_member._value_:
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700174 enum_member = canonical_member
175 break
176 else:
177 # Aliases don't appear in member names (only in __members__).
Ethan Furman520ad572013-07-19 19:47:21 -0700178 enum_class._member_names_.append(member_name)
179 enum_class._member_map_[member_name] = enum_member
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700180 try:
181 # This may fail if value is not hashable. We can't add the value
182 # to the map, and by-value lookups for this value will be
183 # linear.
Ethan Furman520ad572013-07-19 19:47:21 -0700184 enum_class._value2member_map_[value] = enum_member
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700185 except TypeError:
186 pass
187
188 # double check that repr and friends are not the mixin's or various
189 # things break (such as pickle)
Ethan Furmanec15a822013-08-31 19:17:41 -0700190 for name in ('__repr__', '__str__', '__format__', '__getnewargs__'):
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700191 class_method = getattr(enum_class, name)
192 obj_method = getattr(member_type, name, None)
193 enum_method = getattr(first_enum, name, None)
194 if obj_method is not None and obj_method is class_method:
195 setattr(enum_class, name, enum_method)
196
197 # replace any other __new__ with our own (as long as Enum is not None,
198 # anyway) -- again, this is to support pickle
199 if Enum is not None:
200 # if the user defined their own __new__, save it before it gets
201 # clobbered in case they subclass later
202 if save_new:
203 enum_class.__new_member__ = __new__
204 enum_class.__new__ = Enum.__new__
205 return enum_class
206
207 def __call__(cls, value, names=None, *, module=None, type=None):
208 """Either returns an existing member, or creates a new enum class.
209
210 This method is used both when an enum class is given a value to match
211 to an enumeration member (i.e. Color(3)) and for the functional API
212 (i.e. Color = Enum('Color', names='red green blue')).
213
214 When used for the functional API: `module`, if set, will be stored in
215 the new class' __module__ attribute; `type`, if set, will be mixed in
216 as the first base class.
217
218 Note: if `module` is not set this routine will attempt to discover the
219 calling module by walking the frame stack; if this is unsuccessful
220 the resulting class will not be pickleable.
221
222 """
223 if names is None: # simple value lookup
224 return cls.__new__(cls, value)
225 # otherwise, functional API: we're creating a new Enum type
226 return cls._create_(value, names, module=module, type=type)
227
228 def __contains__(cls, member):
Ethan Furman520ad572013-07-19 19:47:21 -0700229 return isinstance(member, cls) and member.name in cls._member_map_
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700230
Ethan Furman388a3922013-08-12 06:51:41 -0700231 def __dir__(self):
232 return ['__class__', '__doc__', '__members__'] + self._member_names_
233
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700234 def __getattr__(cls, name):
235 """Return the enum member matching `name`
236
237 We use __getattr__ instead of descriptors or inserting into the enum
238 class' __dict__ in order to support `name` and `value` being both
239 properties for enum members (which live in the class' __dict__) and
240 enum members themselves.
241
242 """
243 if _is_dunder(name):
244 raise AttributeError(name)
245 try:
Ethan Furman520ad572013-07-19 19:47:21 -0700246 return cls._member_map_[name]
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700247 except KeyError:
248 raise AttributeError(name) from None
249
250 def __getitem__(cls, name):
Ethan Furman520ad572013-07-19 19:47:21 -0700251 return cls._member_map_[name]
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700252
253 def __iter__(cls):
Ethan Furman520ad572013-07-19 19:47:21 -0700254 return (cls._member_map_[name] for name in cls._member_names_)
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700255
256 def __len__(cls):
Ethan Furman520ad572013-07-19 19:47:21 -0700257 return len(cls._member_names_)
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700258
Ethan Furman2131a4a2013-09-14 18:11:24 -0700259 @property
260 def __members__(cls):
261 """Returns a mapping of member name->value.
262
263 This mapping lists all enum members, including aliases. Note that this
264 is a read-only view of the internal mapping.
265
266 """
267 return MappingProxyType(cls._member_map_)
268
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700269 def __repr__(cls):
270 return "<enum %r>" % cls.__name__
271
Ethan Furman2131a4a2013-09-14 18:11:24 -0700272 def __reversed__(cls):
273 return (cls._member_map_[name] for name in reversed(cls._member_names_))
274
Ethan Furmanf203f2d2013-09-06 07:16:48 -0700275 def __setattr__(cls, name, value):
276 """Block attempts to reassign Enum members.
277
278 A simple assignment to the class namespace only changes one of the
279 several possible ways to get an Enum member from the Enum class,
280 resulting in an inconsistent Enumeration.
281
282 """
283 member_map = cls.__dict__.get('_member_map_', {})
284 if name in member_map:
285 raise AttributeError('Cannot reassign members.')
286 super().__setattr__(name, value)
287
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700288 def _create_(cls, class_name, names=None, *, module=None, type=None):
289 """Convenience method to create a new Enum class.
290
291 `names` can be:
292
293 * A string containing member names, separated either with spaces or
294 commas. Values are auto-numbered from 1.
295 * An iterable of member names. Values are auto-numbered from 1.
296 * An iterable of (member name, value) pairs.
297 * A mapping of member name -> value.
298
299 """
300 metacls = cls.__class__
301 bases = (cls, ) if type is None else (type, cls)
302 classdict = metacls.__prepare__(class_name, bases)
303
304 # special processing needed for names?
305 if isinstance(names, str):
306 names = names.replace(',', ' ').split()
307 if isinstance(names, (tuple, list)) and isinstance(names[0], str):
308 names = [(e, i) for (i, e) in enumerate(names, 1)]
309
310 # Here, names is either an iterable of (name, value) or a mapping.
311 for item in names:
312 if isinstance(item, str):
313 member_name, member_value = item, names[item]
314 else:
315 member_name, member_value = item
316 classdict[member_name] = member_value
317 enum_class = metacls.__new__(metacls, class_name, bases, classdict)
318
319 # TODO: replace the frame hack if a blessed way to know the calling
320 # module is ever developed
321 if module is None:
322 try:
323 module = sys._getframe(2).f_globals['__name__']
324 except (AttributeError, ValueError) as exc:
325 pass
326 if module is None:
327 _make_class_unpicklable(enum_class)
328 else:
329 enum_class.__module__ = module
330
331 return enum_class
332
333 @staticmethod
334 def _get_mixins_(bases):
335 """Returns the type for creating enum members, and the first inherited
336 enum class.
337
338 bases: the tuple of bases that was given to __new__
339
340 """
341 if not bases:
342 return object, Enum
343
344 # double check that we are not subclassing a class with existing
345 # enumeration members; while we're at it, see if any other data
346 # type has been mixed in so we can use the correct __new__
347 member_type = first_enum = None
348 for base in bases:
349 if (base is not Enum and
350 issubclass(base, Enum) and
Ethan Furman520ad572013-07-19 19:47:21 -0700351 base._member_names_):
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700352 raise TypeError("Cannot extend enumerations")
353 # base is now the last base in bases
354 if not issubclass(base, Enum):
355 raise TypeError("new enumerations must be created as "
356 "`ClassName([mixin_type,] enum_type)`")
357
358 # get correct mix-in type (either mix-in type of Enum subclass, or
359 # first base if last base is Enum)
360 if not issubclass(bases[0], Enum):
361 member_type = bases[0] # first data type
362 first_enum = bases[-1] # enum type
363 else:
364 for base in bases[0].__mro__:
365 # most common: (IntEnum, int, Enum, object)
366 # possible: (<Enum 'AutoIntEnum'>, <Enum 'IntEnum'>,
367 # <class 'int'>, <Enum 'Enum'>,
368 # <class 'object'>)
369 if issubclass(base, Enum):
370 if first_enum is None:
371 first_enum = base
372 else:
373 if member_type is None:
374 member_type = base
375
376 return member_type, first_enum
377
378 @staticmethod
379 def _find_new_(classdict, member_type, first_enum):
380 """Returns the __new__ to be used for creating the enum members.
381
382 classdict: the class dictionary given to __new__
383 member_type: the data type whose __new__ will be used by default
384 first_enum: enumeration to check for an overriding __new__
385
386 """
387 # now find the correct __new__, checking to see of one was defined
388 # by the user; also check earlier enum classes in case a __new__ was
389 # saved as __new_member__
390 __new__ = classdict.get('__new__', None)
391
392 # should __new__ be saved as __new_member__ later?
393 save_new = __new__ is not None
394
395 if __new__ is None:
396 # check all possibles for __new_member__ before falling back to
397 # __new__
398 for method in ('__new_member__', '__new__'):
399 for possible in (member_type, first_enum):
400 target = getattr(possible, method, None)
401 if target not in {
402 None,
403 None.__new__,
404 object.__new__,
405 Enum.__new__,
406 }:
407 __new__ = target
408 break
409 if __new__ is not None:
410 break
411 else:
412 __new__ = object.__new__
413
414 # if a non-object.__new__ is used then whatever value/tuple was
415 # assigned to the enum member name will be passed to __new__ and to the
416 # new enum member's __init__
417 if __new__ is object.__new__:
418 use_args = False
419 else:
420 use_args = True
421
422 return __new__, save_new, use_args
423
424
425class Enum(metaclass=EnumMeta):
426 """Generic enumeration.
427
428 Derive from this class to define new enumerations.
429
430 """
431 def __new__(cls, value):
432 # all enum instances are actually created during class construction
433 # without calling this method; this method is called by the metaclass'
434 # __call__ (i.e. Color(3) ), and by pickle
435 if type(value) is cls:
436 # For lookups like Color(Color.red)
437 return value
438 # by-value search for a matching enum member
439 # see if it's in the reverse mapping (for hashable values)
Ethan Furman2aa27322013-07-19 19:35:56 -0700440 try:
Ethan Furman520ad572013-07-19 19:47:21 -0700441 if value in cls._value2member_map_:
442 return cls._value2member_map_[value]
Ethan Furman2aa27322013-07-19 19:35:56 -0700443 except TypeError:
444 # not there, now do long search -- O(n) behavior
Ethan Furman520ad572013-07-19 19:47:21 -0700445 for member in cls._member_map_.values():
Ethan Furman2aa27322013-07-19 19:35:56 -0700446 if member.value == value:
447 return member
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700448 raise ValueError("%s is not a valid %s" % (value, cls.__name__))
449
450 def __repr__(self):
451 return "<%s.%s: %r>" % (
Ethan Furman520ad572013-07-19 19:47:21 -0700452 self.__class__.__name__, self._name_, self._value_)
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700453
454 def __str__(self):
Ethan Furman520ad572013-07-19 19:47:21 -0700455 return "%s.%s" % (self.__class__.__name__, self._name_)
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700456
Ethan Furman388a3922013-08-12 06:51:41 -0700457 def __dir__(self):
458 return (['__class__', '__doc__', 'name', 'value'])
459
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700460 def __eq__(self, other):
461 if type(other) is self.__class__:
462 return self is other
463 return NotImplemented
464
Ethan Furmanec15a822013-08-31 19:17:41 -0700465 def __format__(self, format_spec):
466 # mixed-in Enums should use the mixed-in type's __format__, otherwise
467 # we can get strange results with the Enum name showing up instead of
468 # the value
469
470 # pure Enum branch
471 if self._member_type_ is object:
472 cls = str
473 val = str(self)
474 # mix-in branch
475 else:
476 cls = self._member_type_
477 val = self.value
478 return cls.__format__(val, format_spec)
479
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700480 def __getnewargs__(self):
Ethan Furman520ad572013-07-19 19:47:21 -0700481 return (self._value_, )
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700482
483 def __hash__(self):
Ethan Furman520ad572013-07-19 19:47:21 -0700484 return hash(self._name_)
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700485
486 # _RouteClassAttributeToGetattr is used to provide access to the `name`
487 # and `value` properties of enum members while keeping some measure of
488 # protection from modification, while still allowing for an enumeration
489 # to have members named `name` and `value`. This works because enumeration
490 # members are not set directly on the enum class -- __getattr__ is
491 # used to look them up.
492
493 @_RouteClassAttributeToGetattr
494 def name(self):
Ethan Furman520ad572013-07-19 19:47:21 -0700495 return self._name_
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700496
497 @_RouteClassAttributeToGetattr
498 def value(self):
Ethan Furman520ad572013-07-19 19:47:21 -0700499 return self._value_
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700500
501
502class IntEnum(int, Enum):
503 """Enum where members are also (and must be) ints"""
Ethan Furmanf24bb352013-07-18 17:05:39 -0700504
505
506def unique(enumeration):
507 """Class decorator for enumerations ensuring unique member values."""
508 duplicates = []
509 for name, member in enumeration.__members__.items():
510 if name != member.name:
511 duplicates.append((name, member.name))
512 if duplicates:
513 alias_details = ', '.join(
514 ["%s -> %s" % (alias, name) for (alias, name) in duplicates])
515 raise ValueError('duplicate values found in %r: %s' %
516 (enumeration, alias_details))
517 return enumeration