blob: 82058ae9249a251d5494d56a68a551cdee442a99 [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
32def _is_dunder(name):
33 """Returns True if a __dunder__ name, False otherwise."""
34 return (name[:2] == name[-2:] == '__' and
35 name[2:3] != '_' and
36 name[-3:-2] != '_')
37
38
39def _is_sunder(name):
40 """Returns True if a _sunder_ name, False otherwise."""
41 return (name[0] == name[-1] == '_' and
42 name[1:2] != '_' and
43 name[-2:-1] != '_')
44
45
46def _make_class_unpicklable(cls):
47 """Make the given class un-picklable."""
48 def _break_on_call_reduce(self):
49 raise TypeError('%r cannot be pickled' % self)
50 cls.__reduce__ = _break_on_call_reduce
51 cls.__module__ = '<unknown>'
52
Ethan Furman6b3d64a2013-06-14 16:55:46 -070053class _EnumDict(dict):
54 """Keeps track of definition order of the enum items.
55
56 EnumMeta will use the names found in self._member_names as the
57 enumeration member names.
58
59 """
60 def __init__(self):
61 super().__init__()
62 self._member_names = []
63
64 def __setitem__(self, key, value):
65 """Changes anything not dundered or that doesn't have __get__.
66
67 If a descriptor is added with the same name as an enum member, the name
68 is removed from _member_names (this may leave a hole in the numerical
69 sequence of values).
70
71 If an enum member name is used twice, an error is raised; duplicate
72 values are not checked for.
73
74 Single underscore (sunder) names are reserved.
75
76 """
77 if _is_sunder(key):
78 raise ValueError('_names_ are reserved for future Enum use')
79 elif _is_dunder(key) or hasattr(value, '__get__'):
80 if key in self._member_names:
81 # overwriting an enum with a method? then remove the name from
82 # _member_names or it will become an enum anyway when the class
83 # is created
84 self._member_names.remove(key)
85 else:
86 if key in self._member_names:
87 raise TypeError('Attempted to reuse key: %r' % key)
88 self._member_names.append(key)
89 super().__setitem__(key, value)
90
91
Ezio Melotti9a3777e2013-08-17 15:53:55 +030092# Dummy value for Enum as EnumMeta explicitly checks for it, but of course
93# until EnumMeta finishes running the first time the Enum class doesn't exist.
94# This is also why there are checks in EnumMeta like `if Enum is not None`
Ethan Furman6b3d64a2013-06-14 16:55:46 -070095Enum = None
96
97
98class EnumMeta(type):
99 """Metaclass for Enum"""
100 @classmethod
101 def __prepare__(metacls, cls, bases):
102 return _EnumDict()
103
104 def __new__(metacls, cls, bases, classdict):
105 # an Enum class is final once enumeration items have been defined; it
106 # cannot be mixed with other types (int, float, etc.) if it has an
107 # inherited __new__ unless a new __new__ is defined (or the resulting
108 # class will fail).
109 member_type, first_enum = metacls._get_mixins_(bases)
110 __new__, save_new, use_args = metacls._find_new_(classdict, member_type,
111 first_enum)
112
113 # save enum items into separate mapping so they don't get baked into
114 # the new class
115 members = {k: classdict[k] for k in classdict._member_names}
116 for name in classdict._member_names:
117 del classdict[name]
118
119 # check for illegal enum names (any others?)
120 invalid_names = set(members) & {'mro', }
121 if invalid_names:
122 raise ValueError('Invalid enum member name: {0}'.format(
123 ','.join(invalid_names)))
124
125 # create our new Enum type
126 enum_class = super().__new__(metacls, cls, bases, classdict)
Ethan Furman520ad572013-07-19 19:47:21 -0700127 enum_class._member_names_ = [] # names in definition order
128 enum_class._member_map_ = OrderedDict() # name->value map
Ethan Furman5e5a8232013-08-04 08:42:23 -0700129 enum_class._member_type_ = member_type
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700130
131 # Reverse value->name map for hashable values.
Ethan Furman520ad572013-07-19 19:47:21 -0700132 enum_class._value2member_map_ = {}
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700133
134 # check for a __getnewargs__, and if not present sabotage
135 # pickling, since it won't work anyway
136 if (member_type is not object and
137 member_type.__dict__.get('__getnewargs__') is None
138 ):
139 _make_class_unpicklable(enum_class)
140
141 # instantiate them, checking for duplicates as we go
142 # we instantiate first instead of checking for duplicates first in case
143 # a custom __new__ is doing something funky with the values -- such as
144 # auto-numbering ;)
145 for member_name in classdict._member_names:
146 value = members[member_name]
147 if not isinstance(value, tuple):
148 args = (value, )
149 else:
150 args = value
151 if member_type is tuple: # special case for tuple enums
152 args = (args, ) # wrap it one more time
153 if not use_args:
154 enum_member = __new__(enum_class)
Ethan Furmanb41803e2013-07-25 13:50:45 -0700155 if not hasattr(enum_member, '_value_'):
156 enum_member._value_ = value
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700157 else:
158 enum_member = __new__(enum_class, *args)
Ethan Furmanb41803e2013-07-25 13:50:45 -0700159 if not hasattr(enum_member, '_value_'):
160 enum_member._value_ = member_type(*args)
Ethan Furman520ad572013-07-19 19:47:21 -0700161 value = enum_member._value_
Ethan Furman520ad572013-07-19 19:47:21 -0700162 enum_member._name_ = member_name
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700163 enum_member.__init__(*args)
164 # If another member with the same value was already defined, the
165 # new member becomes an alias to the existing one.
Ethan Furman520ad572013-07-19 19:47:21 -0700166 for name, canonical_member in enum_class._member_map_.items():
167 if canonical_member.value == enum_member._value_:
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700168 enum_member = canonical_member
169 break
170 else:
171 # Aliases don't appear in member names (only in __members__).
Ethan Furman520ad572013-07-19 19:47:21 -0700172 enum_class._member_names_.append(member_name)
173 enum_class._member_map_[member_name] = enum_member
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700174 try:
175 # This may fail if value is not hashable. We can't add the value
176 # to the map, and by-value lookups for this value will be
177 # linear.
Ethan Furman520ad572013-07-19 19:47:21 -0700178 enum_class._value2member_map_[value] = enum_member
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700179 except TypeError:
180 pass
181
182 # double check that repr and friends are not the mixin's or various
183 # things break (such as pickle)
Ethan Furmanec15a822013-08-31 19:17:41 -0700184 for name in ('__repr__', '__str__', '__format__', '__getnewargs__'):
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700185 class_method = getattr(enum_class, name)
186 obj_method = getattr(member_type, name, None)
187 enum_method = getattr(first_enum, name, None)
188 if obj_method is not None and obj_method is class_method:
189 setattr(enum_class, name, enum_method)
190
191 # replace any other __new__ with our own (as long as Enum is not None,
192 # anyway) -- again, this is to support pickle
193 if Enum is not None:
194 # if the user defined their own __new__, save it before it gets
195 # clobbered in case they subclass later
196 if save_new:
197 enum_class.__new_member__ = __new__
198 enum_class.__new__ = Enum.__new__
199 return enum_class
200
201 def __call__(cls, value, names=None, *, module=None, type=None):
202 """Either returns an existing member, or creates a new enum class.
203
204 This method is used both when an enum class is given a value to match
205 to an enumeration member (i.e. Color(3)) and for the functional API
206 (i.e. Color = Enum('Color', names='red green blue')).
207
208 When used for the functional API: `module`, if set, will be stored in
209 the new class' __module__ attribute; `type`, if set, will be mixed in
210 as the first base class.
211
212 Note: if `module` is not set this routine will attempt to discover the
213 calling module by walking the frame stack; if this is unsuccessful
214 the resulting class will not be pickleable.
215
216 """
217 if names is None: # simple value lookup
218 return cls.__new__(cls, value)
219 # otherwise, functional API: we're creating a new Enum type
220 return cls._create_(value, names, module=module, type=type)
221
222 def __contains__(cls, member):
Ethan Furman520ad572013-07-19 19:47:21 -0700223 return isinstance(member, cls) and member.name in cls._member_map_
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700224
Ethan Furman388a3922013-08-12 06:51:41 -0700225 def __dir__(self):
226 return ['__class__', '__doc__', '__members__'] + self._member_names_
227
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700228 @property
229 def __members__(cls):
230 """Returns a mapping of member name->value.
231
232 This mapping lists all enum members, including aliases. Note that this
233 is a read-only view of the internal mapping.
234
235 """
Ethan Furman520ad572013-07-19 19:47:21 -0700236 return MappingProxyType(cls._member_map_)
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700237
238 def __getattr__(cls, name):
239 """Return the enum member matching `name`
240
241 We use __getattr__ instead of descriptors or inserting into the enum
242 class' __dict__ in order to support `name` and `value` being both
243 properties for enum members (which live in the class' __dict__) and
244 enum members themselves.
245
246 """
247 if _is_dunder(name):
248 raise AttributeError(name)
249 try:
Ethan Furman520ad572013-07-19 19:47:21 -0700250 return cls._member_map_[name]
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700251 except KeyError:
252 raise AttributeError(name) from None
253
254 def __getitem__(cls, name):
Ethan Furman520ad572013-07-19 19:47:21 -0700255 return cls._member_map_[name]
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700256
257 def __iter__(cls):
Ethan Furman520ad572013-07-19 19:47:21 -0700258 return (cls._member_map_[name] for name in cls._member_names_)
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700259
260 def __len__(cls):
Ethan Furman520ad572013-07-19 19:47:21 -0700261 return len(cls._member_names_)
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700262
263 def __repr__(cls):
264 return "<enum %r>" % cls.__name__
265
Ethan Furmanf203f2d2013-09-06 07:16:48 -0700266 def __setattr__(cls, name, value):
267 """Block attempts to reassign Enum members.
268
269 A simple assignment to the class namespace only changes one of the
270 several possible ways to get an Enum member from the Enum class,
271 resulting in an inconsistent Enumeration.
272
273 """
274 member_map = cls.__dict__.get('_member_map_', {})
275 if name in member_map:
276 raise AttributeError('Cannot reassign members.')
277 super().__setattr__(name, value)
278
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700279 def _create_(cls, class_name, names=None, *, module=None, type=None):
280 """Convenience method to create a new Enum class.
281
282 `names` can be:
283
284 * A string containing member names, separated either with spaces or
285 commas. Values are auto-numbered from 1.
286 * An iterable of member names. Values are auto-numbered from 1.
287 * An iterable of (member name, value) pairs.
288 * A mapping of member name -> value.
289
290 """
291 metacls = cls.__class__
292 bases = (cls, ) if type is None else (type, cls)
293 classdict = metacls.__prepare__(class_name, bases)
294
295 # special processing needed for names?
296 if isinstance(names, str):
297 names = names.replace(',', ' ').split()
298 if isinstance(names, (tuple, list)) and isinstance(names[0], str):
299 names = [(e, i) for (i, e) in enumerate(names, 1)]
300
301 # Here, names is either an iterable of (name, value) or a mapping.
302 for item in names:
303 if isinstance(item, str):
304 member_name, member_value = item, names[item]
305 else:
306 member_name, member_value = item
307 classdict[member_name] = member_value
308 enum_class = metacls.__new__(metacls, class_name, bases, classdict)
309
310 # TODO: replace the frame hack if a blessed way to know the calling
311 # module is ever developed
312 if module is None:
313 try:
314 module = sys._getframe(2).f_globals['__name__']
315 except (AttributeError, ValueError) as exc:
316 pass
317 if module is None:
318 _make_class_unpicklable(enum_class)
319 else:
320 enum_class.__module__ = module
321
322 return enum_class
323
324 @staticmethod
325 def _get_mixins_(bases):
326 """Returns the type for creating enum members, and the first inherited
327 enum class.
328
329 bases: the tuple of bases that was given to __new__
330
331 """
332 if not bases:
333 return object, Enum
334
335 # double check that we are not subclassing a class with existing
336 # enumeration members; while we're at it, see if any other data
337 # type has been mixed in so we can use the correct __new__
338 member_type = first_enum = None
339 for base in bases:
340 if (base is not Enum and
341 issubclass(base, Enum) and
Ethan Furman520ad572013-07-19 19:47:21 -0700342 base._member_names_):
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700343 raise TypeError("Cannot extend enumerations")
344 # base is now the last base in bases
345 if not issubclass(base, Enum):
346 raise TypeError("new enumerations must be created as "
347 "`ClassName([mixin_type,] enum_type)`")
348
349 # get correct mix-in type (either mix-in type of Enum subclass, or
350 # first base if last base is Enum)
351 if not issubclass(bases[0], Enum):
352 member_type = bases[0] # first data type
353 first_enum = bases[-1] # enum type
354 else:
355 for base in bases[0].__mro__:
356 # most common: (IntEnum, int, Enum, object)
357 # possible: (<Enum 'AutoIntEnum'>, <Enum 'IntEnum'>,
358 # <class 'int'>, <Enum 'Enum'>,
359 # <class 'object'>)
360 if issubclass(base, Enum):
361 if first_enum is None:
362 first_enum = base
363 else:
364 if member_type is None:
365 member_type = base
366
367 return member_type, first_enum
368
369 @staticmethod
370 def _find_new_(classdict, member_type, first_enum):
371 """Returns the __new__ to be used for creating the enum members.
372
373 classdict: the class dictionary given to __new__
374 member_type: the data type whose __new__ will be used by default
375 first_enum: enumeration to check for an overriding __new__
376
377 """
378 # now find the correct __new__, checking to see of one was defined
379 # by the user; also check earlier enum classes in case a __new__ was
380 # saved as __new_member__
381 __new__ = classdict.get('__new__', None)
382
383 # should __new__ be saved as __new_member__ later?
384 save_new = __new__ is not None
385
386 if __new__ is None:
387 # check all possibles for __new_member__ before falling back to
388 # __new__
389 for method in ('__new_member__', '__new__'):
390 for possible in (member_type, first_enum):
391 target = getattr(possible, method, None)
392 if target not in {
393 None,
394 None.__new__,
395 object.__new__,
396 Enum.__new__,
397 }:
398 __new__ = target
399 break
400 if __new__ is not None:
401 break
402 else:
403 __new__ = object.__new__
404
405 # if a non-object.__new__ is used then whatever value/tuple was
406 # assigned to the enum member name will be passed to __new__ and to the
407 # new enum member's __init__
408 if __new__ is object.__new__:
409 use_args = False
410 else:
411 use_args = True
412
413 return __new__, save_new, use_args
414
415
416class Enum(metaclass=EnumMeta):
417 """Generic enumeration.
418
419 Derive from this class to define new enumerations.
420
421 """
422 def __new__(cls, value):
423 # all enum instances are actually created during class construction
424 # without calling this method; this method is called by the metaclass'
425 # __call__ (i.e. Color(3) ), and by pickle
426 if type(value) is cls:
427 # For lookups like Color(Color.red)
428 return value
429 # by-value search for a matching enum member
430 # see if it's in the reverse mapping (for hashable values)
Ethan Furman2aa27322013-07-19 19:35:56 -0700431 try:
Ethan Furman520ad572013-07-19 19:47:21 -0700432 if value in cls._value2member_map_:
433 return cls._value2member_map_[value]
Ethan Furman2aa27322013-07-19 19:35:56 -0700434 except TypeError:
435 # not there, now do long search -- O(n) behavior
Ethan Furman520ad572013-07-19 19:47:21 -0700436 for member in cls._member_map_.values():
Ethan Furman2aa27322013-07-19 19:35:56 -0700437 if member.value == value:
438 return member
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700439 raise ValueError("%s is not a valid %s" % (value, cls.__name__))
440
441 def __repr__(self):
442 return "<%s.%s: %r>" % (
Ethan Furman520ad572013-07-19 19:47:21 -0700443 self.__class__.__name__, self._name_, self._value_)
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700444
445 def __str__(self):
Ethan Furman520ad572013-07-19 19:47:21 -0700446 return "%s.%s" % (self.__class__.__name__, self._name_)
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700447
Ethan Furman388a3922013-08-12 06:51:41 -0700448 def __dir__(self):
449 return (['__class__', '__doc__', 'name', 'value'])
450
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700451 def __eq__(self, other):
452 if type(other) is self.__class__:
453 return self is other
454 return NotImplemented
455
Ethan Furmanec15a822013-08-31 19:17:41 -0700456 def __format__(self, format_spec):
457 # mixed-in Enums should use the mixed-in type's __format__, otherwise
458 # we can get strange results with the Enum name showing up instead of
459 # the value
460
461 # pure Enum branch
462 if self._member_type_ is object:
463 cls = str
464 val = str(self)
465 # mix-in branch
466 else:
467 cls = self._member_type_
468 val = self.value
469 return cls.__format__(val, format_spec)
470
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700471 def __getnewargs__(self):
Ethan Furman520ad572013-07-19 19:47:21 -0700472 return (self._value_, )
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700473
474 def __hash__(self):
Ethan Furman520ad572013-07-19 19:47:21 -0700475 return hash(self._name_)
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700476
477 # _RouteClassAttributeToGetattr is used to provide access to the `name`
478 # and `value` properties of enum members while keeping some measure of
479 # protection from modification, while still allowing for an enumeration
480 # to have members named `name` and `value`. This works because enumeration
481 # members are not set directly on the enum class -- __getattr__ is
482 # used to look them up.
483
484 @_RouteClassAttributeToGetattr
485 def name(self):
Ethan Furman520ad572013-07-19 19:47:21 -0700486 return self._name_
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700487
488 @_RouteClassAttributeToGetattr
489 def value(self):
Ethan Furman520ad572013-07-19 19:47:21 -0700490 return self._value_
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700491
492
493class IntEnum(int, Enum):
494 """Enum where members are also (and must be) ints"""
Ethan Furmanf24bb352013-07-18 17:05:39 -0700495
496
497def unique(enumeration):
498 """Class decorator for enumerations ensuring unique member values."""
499 duplicates = []
500 for name, member in enumeration.__members__.items():
501 if name != member.name:
502 duplicates.append((name, member.name))
503 if duplicates:
504 alias_details = ', '.join(
505 ["%s -> %s" % (alias, name) for (alias, name) in duplicates])
506 raise ValueError('duplicate values found in %r: %s' %
507 (enumeration, alias_details))
508 return enumeration