blob: 8219005bb61996d82445decb3cb3aa936f6bdba8 [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
266 def _create_(cls, class_name, names=None, *, module=None, type=None):
267 """Convenience method to create a new Enum class.
268
269 `names` can be:
270
271 * A string containing member names, separated either with spaces or
272 commas. Values are auto-numbered from 1.
273 * An iterable of member names. Values are auto-numbered from 1.
274 * An iterable of (member name, value) pairs.
275 * A mapping of member name -> value.
276
277 """
278 metacls = cls.__class__
279 bases = (cls, ) if type is None else (type, cls)
280 classdict = metacls.__prepare__(class_name, bases)
281
282 # special processing needed for names?
283 if isinstance(names, str):
284 names = names.replace(',', ' ').split()
285 if isinstance(names, (tuple, list)) and isinstance(names[0], str):
286 names = [(e, i) for (i, e) in enumerate(names, 1)]
287
288 # Here, names is either an iterable of (name, value) or a mapping.
289 for item in names:
290 if isinstance(item, str):
291 member_name, member_value = item, names[item]
292 else:
293 member_name, member_value = item
294 classdict[member_name] = member_value
295 enum_class = metacls.__new__(metacls, class_name, bases, classdict)
296
297 # TODO: replace the frame hack if a blessed way to know the calling
298 # module is ever developed
299 if module is None:
300 try:
301 module = sys._getframe(2).f_globals['__name__']
302 except (AttributeError, ValueError) as exc:
303 pass
304 if module is None:
305 _make_class_unpicklable(enum_class)
306 else:
307 enum_class.__module__ = module
308
309 return enum_class
310
311 @staticmethod
312 def _get_mixins_(bases):
313 """Returns the type for creating enum members, and the first inherited
314 enum class.
315
316 bases: the tuple of bases that was given to __new__
317
318 """
319 if not bases:
320 return object, Enum
321
322 # double check that we are not subclassing a class with existing
323 # enumeration members; while we're at it, see if any other data
324 # type has been mixed in so we can use the correct __new__
325 member_type = first_enum = None
326 for base in bases:
327 if (base is not Enum and
328 issubclass(base, Enum) and
Ethan Furman520ad572013-07-19 19:47:21 -0700329 base._member_names_):
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700330 raise TypeError("Cannot extend enumerations")
331 # base is now the last base in bases
332 if not issubclass(base, Enum):
333 raise TypeError("new enumerations must be created as "
334 "`ClassName([mixin_type,] enum_type)`")
335
336 # get correct mix-in type (either mix-in type of Enum subclass, or
337 # first base if last base is Enum)
338 if not issubclass(bases[0], Enum):
339 member_type = bases[0] # first data type
340 first_enum = bases[-1] # enum type
341 else:
342 for base in bases[0].__mro__:
343 # most common: (IntEnum, int, Enum, object)
344 # possible: (<Enum 'AutoIntEnum'>, <Enum 'IntEnum'>,
345 # <class 'int'>, <Enum 'Enum'>,
346 # <class 'object'>)
347 if issubclass(base, Enum):
348 if first_enum is None:
349 first_enum = base
350 else:
351 if member_type is None:
352 member_type = base
353
354 return member_type, first_enum
355
356 @staticmethod
357 def _find_new_(classdict, member_type, first_enum):
358 """Returns the __new__ to be used for creating the enum members.
359
360 classdict: the class dictionary given to __new__
361 member_type: the data type whose __new__ will be used by default
362 first_enum: enumeration to check for an overriding __new__
363
364 """
365 # now find the correct __new__, checking to see of one was defined
366 # by the user; also check earlier enum classes in case a __new__ was
367 # saved as __new_member__
368 __new__ = classdict.get('__new__', None)
369
370 # should __new__ be saved as __new_member__ later?
371 save_new = __new__ is not None
372
373 if __new__ is None:
374 # check all possibles for __new_member__ before falling back to
375 # __new__
376 for method in ('__new_member__', '__new__'):
377 for possible in (member_type, first_enum):
378 target = getattr(possible, method, None)
379 if target not in {
380 None,
381 None.__new__,
382 object.__new__,
383 Enum.__new__,
384 }:
385 __new__ = target
386 break
387 if __new__ is not None:
388 break
389 else:
390 __new__ = object.__new__
391
392 # if a non-object.__new__ is used then whatever value/tuple was
393 # assigned to the enum member name will be passed to __new__ and to the
394 # new enum member's __init__
395 if __new__ is object.__new__:
396 use_args = False
397 else:
398 use_args = True
399
400 return __new__, save_new, use_args
401
402
403class Enum(metaclass=EnumMeta):
404 """Generic enumeration.
405
406 Derive from this class to define new enumerations.
407
408 """
409 def __new__(cls, value):
410 # all enum instances are actually created during class construction
411 # without calling this method; this method is called by the metaclass'
412 # __call__ (i.e. Color(3) ), and by pickle
413 if type(value) is cls:
414 # For lookups like Color(Color.red)
415 return value
416 # by-value search for a matching enum member
417 # see if it's in the reverse mapping (for hashable values)
Ethan Furman2aa27322013-07-19 19:35:56 -0700418 try:
Ethan Furman520ad572013-07-19 19:47:21 -0700419 if value in cls._value2member_map_:
420 return cls._value2member_map_[value]
Ethan Furman2aa27322013-07-19 19:35:56 -0700421 except TypeError:
422 # not there, now do long search -- O(n) behavior
Ethan Furman520ad572013-07-19 19:47:21 -0700423 for member in cls._member_map_.values():
Ethan Furman2aa27322013-07-19 19:35:56 -0700424 if member.value == value:
425 return member
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700426 raise ValueError("%s is not a valid %s" % (value, cls.__name__))
427
428 def __repr__(self):
429 return "<%s.%s: %r>" % (
Ethan Furman520ad572013-07-19 19:47:21 -0700430 self.__class__.__name__, self._name_, self._value_)
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700431
432 def __str__(self):
Ethan Furman520ad572013-07-19 19:47:21 -0700433 return "%s.%s" % (self.__class__.__name__, self._name_)
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700434
Ethan Furman388a3922013-08-12 06:51:41 -0700435 def __dir__(self):
436 return (['__class__', '__doc__', 'name', 'value'])
437
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700438 def __eq__(self, other):
439 if type(other) is self.__class__:
440 return self is other
441 return NotImplemented
442
Ethan Furmanec15a822013-08-31 19:17:41 -0700443 def __format__(self, format_spec):
444 # mixed-in Enums should use the mixed-in type's __format__, otherwise
445 # we can get strange results with the Enum name showing up instead of
446 # the value
447
448 # pure Enum branch
449 if self._member_type_ is object:
450 cls = str
451 val = str(self)
452 # mix-in branch
453 else:
454 cls = self._member_type_
455 val = self.value
456 return cls.__format__(val, format_spec)
457
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700458 def __getnewargs__(self):
Ethan Furman520ad572013-07-19 19:47:21 -0700459 return (self._value_, )
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700460
461 def __hash__(self):
Ethan Furman520ad572013-07-19 19:47:21 -0700462 return hash(self._name_)
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700463
464 # _RouteClassAttributeToGetattr is used to provide access to the `name`
465 # and `value` properties of enum members while keeping some measure of
466 # protection from modification, while still allowing for an enumeration
467 # to have members named `name` and `value`. This works because enumeration
468 # members are not set directly on the enum class -- __getattr__ is
469 # used to look them up.
470
471 @_RouteClassAttributeToGetattr
472 def name(self):
Ethan Furman520ad572013-07-19 19:47:21 -0700473 return self._name_
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700474
475 @_RouteClassAttributeToGetattr
476 def value(self):
Ethan Furman520ad572013-07-19 19:47:21 -0700477 return self._value_
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700478
479
480class IntEnum(int, Enum):
481 """Enum where members are also (and must be) ints"""
Ethan Furmanf24bb352013-07-18 17:05:39 -0700482
483
484def unique(enumeration):
485 """Class decorator for enumerations ensuring unique member values."""
486 duplicates = []
487 for name, member in enumeration.__members__.items():
488 if name != member.name:
489 duplicates.append((name, member.name))
490 if duplicates:
491 alias_details = ', '.join(
492 ["%s -> %s" % (alias, name) for (alias, name) in duplicates])
493 raise ValueError('duplicate values found in %r: %s' %
494 (enumeration, alias_details))
495 return enumeration