blob: 86f94d905ff1f58389dcb9aced3b5dcf417508f4 [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
Ethan Furmanc850f342013-09-15 16:59:35 -070019 if fget.__doc__ is not None:
20 self.__doc__ = fget.__doc__
Ethan Furman6b3d64a2013-06-14 16:55:46 -070021
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
Ethan Furman101e0742013-09-15 12:34:36 -070034def _is_descriptor(obj):
35 """Returns True if obj is a descriptor, False otherwise."""
36 return (
37 hasattr(obj, '__get__') or
38 hasattr(obj, '__set__') or
39 hasattr(obj, '__delete__'))
40
41
Ethan Furman6b3d64a2013-06-14 16:55:46 -070042def _is_dunder(name):
43 """Returns True if a __dunder__ name, False otherwise."""
44 return (name[:2] == name[-2:] == '__' and
45 name[2:3] != '_' and
46 name[-3:-2] != '_')
47
48
49def _is_sunder(name):
50 """Returns True if a _sunder_ name, False otherwise."""
51 return (name[0] == name[-1] == '_' and
52 name[1:2] != '_' and
53 name[-2:-1] != '_')
54
55
56def _make_class_unpicklable(cls):
57 """Make the given class un-picklable."""
58 def _break_on_call_reduce(self):
59 raise TypeError('%r cannot be pickled' % self)
60 cls.__reduce__ = _break_on_call_reduce
61 cls.__module__ = '<unknown>'
62
Ethan Furman101e0742013-09-15 12:34:36 -070063
Ethan Furman6b3d64a2013-06-14 16:55:46 -070064class _EnumDict(dict):
Ethan Furman101e0742013-09-15 12:34:36 -070065 """Track enum member order and ensure member names are not reused.
Ethan Furman6b3d64a2013-06-14 16:55:46 -070066
67 EnumMeta will use the names found in self._member_names as the
68 enumeration member names.
69
70 """
71 def __init__(self):
72 super().__init__()
73 self._member_names = []
74
75 def __setitem__(self, key, value):
Ethan Furman101e0742013-09-15 12:34:36 -070076 """Changes anything not dundered or not a descriptor.
Ethan Furman6b3d64a2013-06-14 16:55:46 -070077
78 If an enum member name is used twice, an error is raised; duplicate
79 values are not checked for.
80
81 Single underscore (sunder) names are reserved.
82
83 """
84 if _is_sunder(key):
85 raise ValueError('_names_ are reserved for future Enum use')
Ethan Furman101e0742013-09-15 12:34:36 -070086 elif _is_dunder(key):
87 pass
88 elif key in self._member_names:
89 # descriptor overwriting an enum?
90 raise TypeError('Attempted to reuse key: %r' % key)
91 elif not _is_descriptor(value):
92 if key in self:
93 # enum overwriting a descriptor?
94 raise TypeError('Key already defined as: %r' % self[key])
Ethan Furman6b3d64a2013-06-14 16:55:46 -070095 self._member_names.append(key)
96 super().__setitem__(key, value)
97
98
Ethan Furman101e0742013-09-15 12:34:36 -070099
Ezio Melotti9a3777e2013-08-17 15:53:55 +0300100# Dummy value for Enum as EnumMeta explicitly checks for it, but of course
101# until EnumMeta finishes running the first time the Enum class doesn't exist.
102# This is also why there are checks in EnumMeta like `if Enum is not None`
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700103Enum = None
104
105
106class EnumMeta(type):
107 """Metaclass for Enum"""
108 @classmethod
109 def __prepare__(metacls, cls, bases):
110 return _EnumDict()
111
112 def __new__(metacls, cls, bases, classdict):
113 # an Enum class is final once enumeration items have been defined; it
114 # cannot be mixed with other types (int, float, etc.) if it has an
115 # inherited __new__ unless a new __new__ is defined (or the resulting
116 # class will fail).
117 member_type, first_enum = metacls._get_mixins_(bases)
118 __new__, save_new, use_args = metacls._find_new_(classdict, member_type,
119 first_enum)
120
121 # save enum items into separate mapping so they don't get baked into
122 # the new class
123 members = {k: classdict[k] for k in classdict._member_names}
124 for name in classdict._member_names:
125 del classdict[name]
126
127 # check for illegal enum names (any others?)
128 invalid_names = set(members) & {'mro', }
129 if invalid_names:
130 raise ValueError('Invalid enum member name: {0}'.format(
131 ','.join(invalid_names)))
132
133 # create our new Enum type
134 enum_class = super().__new__(metacls, cls, bases, classdict)
Ethan Furman520ad572013-07-19 19:47:21 -0700135 enum_class._member_names_ = [] # names in definition order
136 enum_class._member_map_ = OrderedDict() # name->value map
Ethan Furman5e5a8232013-08-04 08:42:23 -0700137 enum_class._member_type_ = member_type
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700138
139 # Reverse value->name map for hashable values.
Ethan Furman520ad572013-07-19 19:47:21 -0700140 enum_class._value2member_map_ = {}
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700141
142 # check for a __getnewargs__, and if not present sabotage
143 # pickling, since it won't work anyway
144 if (member_type is not object and
145 member_type.__dict__.get('__getnewargs__') is None
146 ):
147 _make_class_unpicklable(enum_class)
148
149 # instantiate them, checking for duplicates as we go
150 # we instantiate first instead of checking for duplicates first in case
151 # a custom __new__ is doing something funky with the values -- such as
152 # auto-numbering ;)
153 for member_name in classdict._member_names:
154 value = members[member_name]
155 if not isinstance(value, tuple):
156 args = (value, )
157 else:
158 args = value
159 if member_type is tuple: # special case for tuple enums
160 args = (args, ) # wrap it one more time
161 if not use_args:
162 enum_member = __new__(enum_class)
Ethan Furmanb41803e2013-07-25 13:50:45 -0700163 if not hasattr(enum_member, '_value_'):
164 enum_member._value_ = value
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700165 else:
166 enum_member = __new__(enum_class, *args)
Ethan Furmanb41803e2013-07-25 13:50:45 -0700167 if not hasattr(enum_member, '_value_'):
168 enum_member._value_ = member_type(*args)
Ethan Furman520ad572013-07-19 19:47:21 -0700169 value = enum_member._value_
Ethan Furman520ad572013-07-19 19:47:21 -0700170 enum_member._name_ = member_name
Ethan Furmanc850f342013-09-15 16:59:35 -0700171 enum_member.__objclass__ = enum_class
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700172 enum_member.__init__(*args)
173 # If another member with the same value was already defined, the
174 # new member becomes an alias to the existing one.
Ethan Furman520ad572013-07-19 19:47:21 -0700175 for name, canonical_member in enum_class._member_map_.items():
176 if canonical_member.value == enum_member._value_:
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700177 enum_member = canonical_member
178 break
179 else:
180 # Aliases don't appear in member names (only in __members__).
Ethan Furman520ad572013-07-19 19:47:21 -0700181 enum_class._member_names_.append(member_name)
182 enum_class._member_map_[member_name] = enum_member
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700183 try:
184 # This may fail if value is not hashable. We can't add the value
185 # to the map, and by-value lookups for this value will be
186 # linear.
Ethan Furman520ad572013-07-19 19:47:21 -0700187 enum_class._value2member_map_[value] = enum_member
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700188 except TypeError:
189 pass
190
191 # double check that repr and friends are not the mixin's or various
192 # things break (such as pickle)
Ethan Furmanec15a822013-08-31 19:17:41 -0700193 for name in ('__repr__', '__str__', '__format__', '__getnewargs__'):
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700194 class_method = getattr(enum_class, name)
195 obj_method = getattr(member_type, name, None)
196 enum_method = getattr(first_enum, name, None)
197 if obj_method is not None and obj_method is class_method:
198 setattr(enum_class, name, enum_method)
199
200 # replace any other __new__ with our own (as long as Enum is not None,
201 # anyway) -- again, this is to support pickle
202 if Enum is not None:
203 # if the user defined their own __new__, save it before it gets
204 # clobbered in case they subclass later
205 if save_new:
206 enum_class.__new_member__ = __new__
207 enum_class.__new__ = Enum.__new__
208 return enum_class
209
210 def __call__(cls, value, names=None, *, module=None, type=None):
211 """Either returns an existing member, or creates a new enum class.
212
213 This method is used both when an enum class is given a value to match
214 to an enumeration member (i.e. Color(3)) and for the functional API
215 (i.e. Color = Enum('Color', names='red green blue')).
216
217 When used for the functional API: `module`, if set, will be stored in
218 the new class' __module__ attribute; `type`, if set, will be mixed in
219 as the first base class.
220
221 Note: if `module` is not set this routine will attempt to discover the
222 calling module by walking the frame stack; if this is unsuccessful
223 the resulting class will not be pickleable.
224
225 """
226 if names is None: # simple value lookup
227 return cls.__new__(cls, value)
228 # otherwise, functional API: we're creating a new Enum type
229 return cls._create_(value, names, module=module, type=type)
230
231 def __contains__(cls, member):
Ethan Furman520ad572013-07-19 19:47:21 -0700232 return isinstance(member, cls) and member.name in cls._member_map_
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700233
Ethan Furman388a3922013-08-12 06:51:41 -0700234 def __dir__(self):
Ethan Furmanc850f342013-09-15 16:59:35 -0700235 return ['__class__', '__doc__', '__members__', '__module__'] + self._member_names_
Ethan Furman388a3922013-08-12 06:51:41 -0700236
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700237 def __getattr__(cls, name):
238 """Return the enum member matching `name`
239
240 We use __getattr__ instead of descriptors or inserting into the enum
241 class' __dict__ in order to support `name` and `value` being both
242 properties for enum members (which live in the class' __dict__) and
243 enum members themselves.
244
245 """
246 if _is_dunder(name):
247 raise AttributeError(name)
248 try:
Ethan Furman520ad572013-07-19 19:47:21 -0700249 return cls._member_map_[name]
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700250 except KeyError:
251 raise AttributeError(name) from None
252
253 def __getitem__(cls, name):
Ethan Furman520ad572013-07-19 19:47:21 -0700254 return cls._member_map_[name]
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700255
256 def __iter__(cls):
Ethan Furman520ad572013-07-19 19:47:21 -0700257 return (cls._member_map_[name] for name in cls._member_names_)
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700258
259 def __len__(cls):
Ethan Furman520ad572013-07-19 19:47:21 -0700260 return len(cls._member_names_)
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700261
Ethan Furman2131a4a2013-09-14 18:11:24 -0700262 @property
263 def __members__(cls):
264 """Returns a mapping of member name->value.
265
266 This mapping lists all enum members, including aliases. Note that this
267 is a read-only view of the internal mapping.
268
269 """
270 return MappingProxyType(cls._member_map_)
271
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700272 def __repr__(cls):
273 return "<enum %r>" % cls.__name__
274
Ethan Furman2131a4a2013-09-14 18:11:24 -0700275 def __reversed__(cls):
276 return (cls._member_map_[name] for name in reversed(cls._member_names_))
277
Ethan Furmanf203f2d2013-09-06 07:16:48 -0700278 def __setattr__(cls, name, value):
279 """Block attempts to reassign Enum members.
280
281 A simple assignment to the class namespace only changes one of the
282 several possible ways to get an Enum member from the Enum class,
283 resulting in an inconsistent Enumeration.
284
285 """
286 member_map = cls.__dict__.get('_member_map_', {})
287 if name in member_map:
288 raise AttributeError('Cannot reassign members.')
289 super().__setattr__(name, value)
290
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700291 def _create_(cls, class_name, names=None, *, module=None, type=None):
292 """Convenience method to create a new Enum class.
293
294 `names` can be:
295
296 * A string containing member names, separated either with spaces or
297 commas. Values are auto-numbered from 1.
298 * An iterable of member names. Values are auto-numbered from 1.
299 * An iterable of (member name, value) pairs.
300 * A mapping of member name -> value.
301
302 """
303 metacls = cls.__class__
304 bases = (cls, ) if type is None else (type, cls)
305 classdict = metacls.__prepare__(class_name, bases)
306
307 # special processing needed for names?
308 if isinstance(names, str):
309 names = names.replace(',', ' ').split()
310 if isinstance(names, (tuple, list)) and isinstance(names[0], str):
311 names = [(e, i) for (i, e) in enumerate(names, 1)]
312
313 # Here, names is either an iterable of (name, value) or a mapping.
314 for item in names:
315 if isinstance(item, str):
316 member_name, member_value = item, names[item]
317 else:
318 member_name, member_value = item
319 classdict[member_name] = member_value
320 enum_class = metacls.__new__(metacls, class_name, bases, classdict)
321
322 # TODO: replace the frame hack if a blessed way to know the calling
323 # module is ever developed
324 if module is None:
325 try:
326 module = sys._getframe(2).f_globals['__name__']
327 except (AttributeError, ValueError) as exc:
328 pass
329 if module is None:
330 _make_class_unpicklable(enum_class)
331 else:
332 enum_class.__module__ = module
333
334 return enum_class
335
336 @staticmethod
337 def _get_mixins_(bases):
338 """Returns the type for creating enum members, and the first inherited
339 enum class.
340
341 bases: the tuple of bases that was given to __new__
342
343 """
344 if not bases:
345 return object, Enum
346
347 # double check that we are not subclassing a class with existing
348 # enumeration members; while we're at it, see if any other data
349 # type has been mixed in so we can use the correct __new__
350 member_type = first_enum = None
351 for base in bases:
352 if (base is not Enum and
353 issubclass(base, Enum) and
Ethan Furman520ad572013-07-19 19:47:21 -0700354 base._member_names_):
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700355 raise TypeError("Cannot extend enumerations")
356 # base is now the last base in bases
357 if not issubclass(base, Enum):
358 raise TypeError("new enumerations must be created as "
359 "`ClassName([mixin_type,] enum_type)`")
360
361 # get correct mix-in type (either mix-in type of Enum subclass, or
362 # first base if last base is Enum)
363 if not issubclass(bases[0], Enum):
364 member_type = bases[0] # first data type
365 first_enum = bases[-1] # enum type
366 else:
367 for base in bases[0].__mro__:
368 # most common: (IntEnum, int, Enum, object)
369 # possible: (<Enum 'AutoIntEnum'>, <Enum 'IntEnum'>,
370 # <class 'int'>, <Enum 'Enum'>,
371 # <class 'object'>)
372 if issubclass(base, Enum):
373 if first_enum is None:
374 first_enum = base
375 else:
376 if member_type is None:
377 member_type = base
378
379 return member_type, first_enum
380
381 @staticmethod
382 def _find_new_(classdict, member_type, first_enum):
383 """Returns the __new__ to be used for creating the enum members.
384
385 classdict: the class dictionary given to __new__
386 member_type: the data type whose __new__ will be used by default
387 first_enum: enumeration to check for an overriding __new__
388
389 """
390 # now find the correct __new__, checking to see of one was defined
391 # by the user; also check earlier enum classes in case a __new__ was
392 # saved as __new_member__
393 __new__ = classdict.get('__new__', None)
394
395 # should __new__ be saved as __new_member__ later?
396 save_new = __new__ is not None
397
398 if __new__ is None:
399 # check all possibles for __new_member__ before falling back to
400 # __new__
401 for method in ('__new_member__', '__new__'):
402 for possible in (member_type, first_enum):
403 target = getattr(possible, method, None)
404 if target not in {
405 None,
406 None.__new__,
407 object.__new__,
408 Enum.__new__,
409 }:
410 __new__ = target
411 break
412 if __new__ is not None:
413 break
414 else:
415 __new__ = object.__new__
416
417 # if a non-object.__new__ is used then whatever value/tuple was
418 # assigned to the enum member name will be passed to __new__ and to the
419 # new enum member's __init__
420 if __new__ is object.__new__:
421 use_args = False
422 else:
423 use_args = True
424
425 return __new__, save_new, use_args
426
427
428class Enum(metaclass=EnumMeta):
429 """Generic enumeration.
430
431 Derive from this class to define new enumerations.
432
433 """
434 def __new__(cls, value):
435 # all enum instances are actually created during class construction
436 # without calling this method; this method is called by the metaclass'
437 # __call__ (i.e. Color(3) ), and by pickle
438 if type(value) is cls:
439 # For lookups like Color(Color.red)
440 return value
441 # by-value search for a matching enum member
442 # see if it's in the reverse mapping (for hashable values)
Ethan Furman2aa27322013-07-19 19:35:56 -0700443 try:
Ethan Furman520ad572013-07-19 19:47:21 -0700444 if value in cls._value2member_map_:
445 return cls._value2member_map_[value]
Ethan Furman2aa27322013-07-19 19:35:56 -0700446 except TypeError:
447 # not there, now do long search -- O(n) behavior
Ethan Furman520ad572013-07-19 19:47:21 -0700448 for member in cls._member_map_.values():
Ethan Furman2aa27322013-07-19 19:35:56 -0700449 if member.value == value:
450 return member
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700451 raise ValueError("%s is not a valid %s" % (value, cls.__name__))
452
453 def __repr__(self):
454 return "<%s.%s: %r>" % (
Ethan Furman520ad572013-07-19 19:47:21 -0700455 self.__class__.__name__, self._name_, self._value_)
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700456
457 def __str__(self):
Ethan Furman520ad572013-07-19 19:47:21 -0700458 return "%s.%s" % (self.__class__.__name__, self._name_)
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700459
Ethan Furman388a3922013-08-12 06:51:41 -0700460 def __dir__(self):
Ethan Furmanc850f342013-09-15 16:59:35 -0700461 added_behavior = [m for m in self.__class__.__dict__ if m[0] != '_']
462 return ['__class__', '__doc__', '__module__', 'name', 'value'] + added_behavior
Ethan Furman388a3922013-08-12 06:51:41 -0700463
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700464 def __eq__(self, other):
465 if type(other) is self.__class__:
466 return self is other
467 return NotImplemented
468
Ethan Furmanec15a822013-08-31 19:17:41 -0700469 def __format__(self, format_spec):
470 # mixed-in Enums should use the mixed-in type's __format__, otherwise
471 # we can get strange results with the Enum name showing up instead of
472 # the value
473
474 # pure Enum branch
475 if self._member_type_ is object:
476 cls = str
477 val = str(self)
478 # mix-in branch
479 else:
480 cls = self._member_type_
481 val = self.value
482 return cls.__format__(val, format_spec)
483
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700484 def __getnewargs__(self):
Ethan Furman520ad572013-07-19 19:47:21 -0700485 return (self._value_, )
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700486
487 def __hash__(self):
Ethan Furman520ad572013-07-19 19:47:21 -0700488 return hash(self._name_)
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700489
490 # _RouteClassAttributeToGetattr is used to provide access to the `name`
491 # and `value` properties of enum members while keeping some measure of
492 # protection from modification, while still allowing for an enumeration
493 # to have members named `name` and `value`. This works because enumeration
494 # members are not set directly on the enum class -- __getattr__ is
495 # used to look them up.
496
497 @_RouteClassAttributeToGetattr
498 def name(self):
Ethan Furmanc850f342013-09-15 16:59:35 -0700499 """The name of the Enum member."""
Ethan Furman520ad572013-07-19 19:47:21 -0700500 return self._name_
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700501
502 @_RouteClassAttributeToGetattr
503 def value(self):
Ethan Furmanc850f342013-09-15 16:59:35 -0700504 """The value of the Enum member."""
Ethan Furman520ad572013-07-19 19:47:21 -0700505 return self._value_
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700506
507
508class IntEnum(int, Enum):
509 """Enum where members are also (and must be) ints"""
Ethan Furmanf24bb352013-07-18 17:05:39 -0700510
511
512def unique(enumeration):
513 """Class decorator for enumerations ensuring unique member values."""
514 duplicates = []
515 for name, member in enumeration.__members__.items():
516 if name != member.name:
517 duplicates.append((name, member.name))
518 if duplicates:
519 alias_details = ', '.join(
520 ["%s -> %s" % (alias, name) for (alias, name) in duplicates])
521 raise ValueError('duplicate values found in %r: %s' %
522 (enumeration, alias_details))
523 return enumeration