blob: 0d72f3e2d20a2a15c6e9e0eb5d5e71cb0ac54b86 [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 def __getattr__(cls, name):
229 """Return the enum member matching `name`
230
231 We use __getattr__ instead of descriptors or inserting into the enum
232 class' __dict__ in order to support `name` and `value` being both
233 properties for enum members (which live in the class' __dict__) and
234 enum members themselves.
235
236 """
237 if _is_dunder(name):
238 raise AttributeError(name)
239 try:
Ethan Furman520ad572013-07-19 19:47:21 -0700240 return cls._member_map_[name]
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700241 except KeyError:
242 raise AttributeError(name) from None
243
244 def __getitem__(cls, name):
Ethan Furman520ad572013-07-19 19:47:21 -0700245 return cls._member_map_[name]
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700246
247 def __iter__(cls):
Ethan Furman520ad572013-07-19 19:47:21 -0700248 return (cls._member_map_[name] for name in cls._member_names_)
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700249
250 def __len__(cls):
Ethan Furman520ad572013-07-19 19:47:21 -0700251 return len(cls._member_names_)
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700252
Ethan Furman2131a4a2013-09-14 18:11:24 -0700253 @property
254 def __members__(cls):
255 """Returns a mapping of member name->value.
256
257 This mapping lists all enum members, including aliases. Note that this
258 is a read-only view of the internal mapping.
259
260 """
261 return MappingProxyType(cls._member_map_)
262
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700263 def __repr__(cls):
264 return "<enum %r>" % cls.__name__
265
Ethan Furman2131a4a2013-09-14 18:11:24 -0700266 def __reversed__(cls):
267 return (cls._member_map_[name] for name in reversed(cls._member_names_))
268
Ethan Furmanf203f2d2013-09-06 07:16:48 -0700269 def __setattr__(cls, name, value):
270 """Block attempts to reassign Enum members.
271
272 A simple assignment to the class namespace only changes one of the
273 several possible ways to get an Enum member from the Enum class,
274 resulting in an inconsistent Enumeration.
275
276 """
277 member_map = cls.__dict__.get('_member_map_', {})
278 if name in member_map:
279 raise AttributeError('Cannot reassign members.')
280 super().__setattr__(name, value)
281
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700282 def _create_(cls, class_name, names=None, *, module=None, type=None):
283 """Convenience method to create a new Enum class.
284
285 `names` can be:
286
287 * A string containing member names, separated either with spaces or
288 commas. Values are auto-numbered from 1.
289 * An iterable of member names. Values are auto-numbered from 1.
290 * An iterable of (member name, value) pairs.
291 * A mapping of member name -> value.
292
293 """
294 metacls = cls.__class__
295 bases = (cls, ) if type is None else (type, cls)
296 classdict = metacls.__prepare__(class_name, bases)
297
298 # special processing needed for names?
299 if isinstance(names, str):
300 names = names.replace(',', ' ').split()
301 if isinstance(names, (tuple, list)) and isinstance(names[0], str):
302 names = [(e, i) for (i, e) in enumerate(names, 1)]
303
304 # Here, names is either an iterable of (name, value) or a mapping.
305 for item in names:
306 if isinstance(item, str):
307 member_name, member_value = item, names[item]
308 else:
309 member_name, member_value = item
310 classdict[member_name] = member_value
311 enum_class = metacls.__new__(metacls, class_name, bases, classdict)
312
313 # TODO: replace the frame hack if a blessed way to know the calling
314 # module is ever developed
315 if module is None:
316 try:
317 module = sys._getframe(2).f_globals['__name__']
318 except (AttributeError, ValueError) as exc:
319 pass
320 if module is None:
321 _make_class_unpicklable(enum_class)
322 else:
323 enum_class.__module__ = module
324
325 return enum_class
326
327 @staticmethod
328 def _get_mixins_(bases):
329 """Returns the type for creating enum members, and the first inherited
330 enum class.
331
332 bases: the tuple of bases that was given to __new__
333
334 """
335 if not bases:
336 return object, Enum
337
338 # double check that we are not subclassing a class with existing
339 # enumeration members; while we're at it, see if any other data
340 # type has been mixed in so we can use the correct __new__
341 member_type = first_enum = None
342 for base in bases:
343 if (base is not Enum and
344 issubclass(base, Enum) and
Ethan Furman520ad572013-07-19 19:47:21 -0700345 base._member_names_):
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700346 raise TypeError("Cannot extend enumerations")
347 # base is now the last base in bases
348 if not issubclass(base, Enum):
349 raise TypeError("new enumerations must be created as "
350 "`ClassName([mixin_type,] enum_type)`")
351
352 # get correct mix-in type (either mix-in type of Enum subclass, or
353 # first base if last base is Enum)
354 if not issubclass(bases[0], Enum):
355 member_type = bases[0] # first data type
356 first_enum = bases[-1] # enum type
357 else:
358 for base in bases[0].__mro__:
359 # most common: (IntEnum, int, Enum, object)
360 # possible: (<Enum 'AutoIntEnum'>, <Enum 'IntEnum'>,
361 # <class 'int'>, <Enum 'Enum'>,
362 # <class 'object'>)
363 if issubclass(base, Enum):
364 if first_enum is None:
365 first_enum = base
366 else:
367 if member_type is None:
368 member_type = base
369
370 return member_type, first_enum
371
372 @staticmethod
373 def _find_new_(classdict, member_type, first_enum):
374 """Returns the __new__ to be used for creating the enum members.
375
376 classdict: the class dictionary given to __new__
377 member_type: the data type whose __new__ will be used by default
378 first_enum: enumeration to check for an overriding __new__
379
380 """
381 # now find the correct __new__, checking to see of one was defined
382 # by the user; also check earlier enum classes in case a __new__ was
383 # saved as __new_member__
384 __new__ = classdict.get('__new__', None)
385
386 # should __new__ be saved as __new_member__ later?
387 save_new = __new__ is not None
388
389 if __new__ is None:
390 # check all possibles for __new_member__ before falling back to
391 # __new__
392 for method in ('__new_member__', '__new__'):
393 for possible in (member_type, first_enum):
394 target = getattr(possible, method, None)
395 if target not in {
396 None,
397 None.__new__,
398 object.__new__,
399 Enum.__new__,
400 }:
401 __new__ = target
402 break
403 if __new__ is not None:
404 break
405 else:
406 __new__ = object.__new__
407
408 # if a non-object.__new__ is used then whatever value/tuple was
409 # assigned to the enum member name will be passed to __new__ and to the
410 # new enum member's __init__
411 if __new__ is object.__new__:
412 use_args = False
413 else:
414 use_args = True
415
416 return __new__, save_new, use_args
417
418
419class Enum(metaclass=EnumMeta):
420 """Generic enumeration.
421
422 Derive from this class to define new enumerations.
423
424 """
425 def __new__(cls, value):
426 # all enum instances are actually created during class construction
427 # without calling this method; this method is called by the metaclass'
428 # __call__ (i.e. Color(3) ), and by pickle
429 if type(value) is cls:
430 # For lookups like Color(Color.red)
431 return value
432 # by-value search for a matching enum member
433 # see if it's in the reverse mapping (for hashable values)
Ethan Furman2aa27322013-07-19 19:35:56 -0700434 try:
Ethan Furman520ad572013-07-19 19:47:21 -0700435 if value in cls._value2member_map_:
436 return cls._value2member_map_[value]
Ethan Furman2aa27322013-07-19 19:35:56 -0700437 except TypeError:
438 # not there, now do long search -- O(n) behavior
Ethan Furman520ad572013-07-19 19:47:21 -0700439 for member in cls._member_map_.values():
Ethan Furman2aa27322013-07-19 19:35:56 -0700440 if member.value == value:
441 return member
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700442 raise ValueError("%s is not a valid %s" % (value, cls.__name__))
443
444 def __repr__(self):
445 return "<%s.%s: %r>" % (
Ethan Furman520ad572013-07-19 19:47:21 -0700446 self.__class__.__name__, self._name_, self._value_)
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700447
448 def __str__(self):
Ethan Furman520ad572013-07-19 19:47:21 -0700449 return "%s.%s" % (self.__class__.__name__, self._name_)
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700450
Ethan Furman388a3922013-08-12 06:51:41 -0700451 def __dir__(self):
452 return (['__class__', '__doc__', 'name', 'value'])
453
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700454 def __eq__(self, other):
455 if type(other) is self.__class__:
456 return self is other
457 return NotImplemented
458
Ethan Furmanec15a822013-08-31 19:17:41 -0700459 def __format__(self, format_spec):
460 # mixed-in Enums should use the mixed-in type's __format__, otherwise
461 # we can get strange results with the Enum name showing up instead of
462 # the value
463
464 # pure Enum branch
465 if self._member_type_ is object:
466 cls = str
467 val = str(self)
468 # mix-in branch
469 else:
470 cls = self._member_type_
471 val = self.value
472 return cls.__format__(val, format_spec)
473
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700474 def __getnewargs__(self):
Ethan Furman520ad572013-07-19 19:47:21 -0700475 return (self._value_, )
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700476
477 def __hash__(self):
Ethan Furman520ad572013-07-19 19:47:21 -0700478 return hash(self._name_)
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700479
480 # _RouteClassAttributeToGetattr is used to provide access to the `name`
481 # and `value` properties of enum members while keeping some measure of
482 # protection from modification, while still allowing for an enumeration
483 # to have members named `name` and `value`. This works because enumeration
484 # members are not set directly on the enum class -- __getattr__ is
485 # used to look them up.
486
487 @_RouteClassAttributeToGetattr
488 def name(self):
Ethan Furman520ad572013-07-19 19:47:21 -0700489 return self._name_
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700490
491 @_RouteClassAttributeToGetattr
492 def value(self):
Ethan Furman520ad572013-07-19 19:47:21 -0700493 return self._value_
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700494
495
496class IntEnum(int, Enum):
497 """Enum where members are also (and must be) ints"""
Ethan Furmanf24bb352013-07-18 17:05:39 -0700498
499
500def unique(enumeration):
501 """Class decorator for enumerations ensuring unique member values."""
502 duplicates = []
503 for name, member in enumeration.__members__.items():
504 if name != member.name:
505 duplicates.append((name, member.name))
506 if duplicates:
507 alias_details = ', '.join(
508 ["%s -> %s" % (alias, name) for (alias, name) in duplicates])
509 raise ValueError('duplicate values found in %r: %s' %
510 (enumeration, alias_details))
511 return enumeration