blob: 7d58f8d17a6ed39dc1982a2811a81d3ab843fb39 [file] [log] [blame]
Ethan Furman6b3d64a2013-06-14 16:55:46 -07001import sys
2from collections import OrderedDict
Ethan Furmane03ea372013-09-25 07:14:41 -07003from types import MappingProxyType, DynamicClassAttribute
Ethan Furman6b3d64a2013-06-14 16:55:46 -07004
Ethan Furmanf24bb352013-07-18 17:05:39 -07005__all__ = ['Enum', 'IntEnum', 'unique']
Ethan Furman6b3d64a2013-06-14 16:55:46 -07006
7
Ethan Furman101e0742013-09-15 12:34:36 -07008def _is_descriptor(obj):
9 """Returns True if obj is a descriptor, False otherwise."""
10 return (
11 hasattr(obj, '__get__') or
12 hasattr(obj, '__set__') or
13 hasattr(obj, '__delete__'))
14
15
Ethan Furman6b3d64a2013-06-14 16:55:46 -070016def _is_dunder(name):
17 """Returns True if a __dunder__ name, False otherwise."""
18 return (name[:2] == name[-2:] == '__' and
19 name[2:3] != '_' and
Ethan Furman648f8602013-10-06 17:19:54 -070020 name[-3:-2] != '_' and
21 len(name) > 4)
Ethan Furman6b3d64a2013-06-14 16:55:46 -070022
23
24def _is_sunder(name):
25 """Returns True if a _sunder_ name, False otherwise."""
26 return (name[0] == name[-1] == '_' and
27 name[1:2] != '_' and
Ethan Furman648f8602013-10-06 17:19:54 -070028 name[-2:-1] != '_' and
29 len(name) > 2)
Ethan Furman6b3d64a2013-06-14 16:55:46 -070030
31
32def _make_class_unpicklable(cls):
33 """Make the given class un-picklable."""
34 def _break_on_call_reduce(self):
35 raise TypeError('%r cannot be pickled' % self)
36 cls.__reduce__ = _break_on_call_reduce
37 cls.__module__ = '<unknown>'
38
Ethan Furman101e0742013-09-15 12:34:36 -070039
Ethan Furman6b3d64a2013-06-14 16:55:46 -070040class _EnumDict(dict):
Ethan Furman101e0742013-09-15 12:34:36 -070041 """Track enum member order and ensure member names are not reused.
Ethan Furman6b3d64a2013-06-14 16:55:46 -070042
43 EnumMeta will use the names found in self._member_names as the
44 enumeration member names.
45
46 """
47 def __init__(self):
48 super().__init__()
49 self._member_names = []
50
51 def __setitem__(self, key, value):
Ethan Furman101e0742013-09-15 12:34:36 -070052 """Changes anything not dundered or not a descriptor.
Ethan Furman6b3d64a2013-06-14 16:55:46 -070053
54 If an enum member name is used twice, an error is raised; duplicate
55 values are not checked for.
56
57 Single underscore (sunder) names are reserved.
58
59 """
60 if _is_sunder(key):
61 raise ValueError('_names_ are reserved for future Enum use')
Ethan Furman101e0742013-09-15 12:34:36 -070062 elif _is_dunder(key):
63 pass
64 elif key in self._member_names:
65 # descriptor overwriting an enum?
66 raise TypeError('Attempted to reuse key: %r' % key)
67 elif not _is_descriptor(value):
68 if key in self:
69 # enum overwriting a descriptor?
70 raise TypeError('Key already defined as: %r' % self[key])
Ethan Furman6b3d64a2013-06-14 16:55:46 -070071 self._member_names.append(key)
72 super().__setitem__(key, value)
73
74
Ethan Furman101e0742013-09-15 12:34:36 -070075
Ezio Melotti9a3777e2013-08-17 15:53:55 +030076# Dummy value for Enum as EnumMeta explicitly checks for it, but of course
77# until EnumMeta finishes running the first time the Enum class doesn't exist.
78# This is also why there are checks in EnumMeta like `if Enum is not None`
Ethan Furman6b3d64a2013-06-14 16:55:46 -070079Enum = None
80
81
82class EnumMeta(type):
83 """Metaclass for Enum"""
84 @classmethod
85 def __prepare__(metacls, cls, bases):
86 return _EnumDict()
87
88 def __new__(metacls, cls, bases, classdict):
89 # an Enum class is final once enumeration items have been defined; it
90 # cannot be mixed with other types (int, float, etc.) if it has an
91 # inherited __new__ unless a new __new__ is defined (or the resulting
92 # class will fail).
93 member_type, first_enum = metacls._get_mixins_(bases)
94 __new__, save_new, use_args = metacls._find_new_(classdict, member_type,
95 first_enum)
96
97 # save enum items into separate mapping so they don't get baked into
98 # the new class
99 members = {k: classdict[k] for k in classdict._member_names}
100 for name in classdict._member_names:
101 del classdict[name]
102
103 # check for illegal enum names (any others?)
104 invalid_names = set(members) & {'mro', }
105 if invalid_names:
106 raise ValueError('Invalid enum member name: {0}'.format(
107 ','.join(invalid_names)))
108
109 # create our new Enum type
110 enum_class = super().__new__(metacls, cls, bases, classdict)
Ethan Furman520ad572013-07-19 19:47:21 -0700111 enum_class._member_names_ = [] # names in definition order
112 enum_class._member_map_ = OrderedDict() # name->value map
Ethan Furman5e5a8232013-08-04 08:42:23 -0700113 enum_class._member_type_ = member_type
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700114
115 # Reverse value->name map for hashable values.
Ethan Furman520ad572013-07-19 19:47:21 -0700116 enum_class._value2member_map_ = {}
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700117
118 # check for a __getnewargs__, and if not present sabotage
119 # pickling, since it won't work anyway
120 if (member_type is not object and
121 member_type.__dict__.get('__getnewargs__') is None
122 ):
123 _make_class_unpicklable(enum_class)
124
125 # instantiate them, checking for duplicates as we go
126 # we instantiate first instead of checking for duplicates first in case
127 # a custom __new__ is doing something funky with the values -- such as
128 # auto-numbering ;)
129 for member_name in classdict._member_names:
130 value = members[member_name]
131 if not isinstance(value, tuple):
132 args = (value, )
133 else:
134 args = value
135 if member_type is tuple: # special case for tuple enums
136 args = (args, ) # wrap it one more time
137 if not use_args:
138 enum_member = __new__(enum_class)
Ethan Furmanb41803e2013-07-25 13:50:45 -0700139 if not hasattr(enum_member, '_value_'):
140 enum_member._value_ = value
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700141 else:
142 enum_member = __new__(enum_class, *args)
Ethan Furmanb41803e2013-07-25 13:50:45 -0700143 if not hasattr(enum_member, '_value_'):
144 enum_member._value_ = member_type(*args)
Ethan Furman520ad572013-07-19 19:47:21 -0700145 value = enum_member._value_
Ethan Furman520ad572013-07-19 19:47:21 -0700146 enum_member._name_ = member_name
Ethan Furmanc850f342013-09-15 16:59:35 -0700147 enum_member.__objclass__ = enum_class
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700148 enum_member.__init__(*args)
149 # If another member with the same value was already defined, the
150 # new member becomes an alias to the existing one.
Ethan Furman520ad572013-07-19 19:47:21 -0700151 for name, canonical_member in enum_class._member_map_.items():
152 if canonical_member.value == enum_member._value_:
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700153 enum_member = canonical_member
154 break
155 else:
156 # Aliases don't appear in member names (only in __members__).
Ethan Furman520ad572013-07-19 19:47:21 -0700157 enum_class._member_names_.append(member_name)
158 enum_class._member_map_[member_name] = enum_member
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700159 try:
160 # This may fail if value is not hashable. We can't add the value
161 # to the map, and by-value lookups for this value will be
162 # linear.
Ethan Furman520ad572013-07-19 19:47:21 -0700163 enum_class._value2member_map_[value] = enum_member
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700164 except TypeError:
165 pass
166
167 # double check that repr and friends are not the mixin's or various
168 # things break (such as pickle)
Ethan Furmanec15a822013-08-31 19:17:41 -0700169 for name in ('__repr__', '__str__', '__format__', '__getnewargs__'):
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700170 class_method = getattr(enum_class, name)
171 obj_method = getattr(member_type, name, None)
172 enum_method = getattr(first_enum, name, None)
173 if obj_method is not None and obj_method is class_method:
174 setattr(enum_class, name, enum_method)
175
176 # replace any other __new__ with our own (as long as Enum is not None,
177 # anyway) -- again, this is to support pickle
178 if Enum is not None:
179 # if the user defined their own __new__, save it before it gets
180 # clobbered in case they subclass later
181 if save_new:
182 enum_class.__new_member__ = __new__
183 enum_class.__new__ = Enum.__new__
184 return enum_class
185
186 def __call__(cls, value, names=None, *, module=None, type=None):
187 """Either returns an existing member, or creates a new enum class.
188
189 This method is used both when an enum class is given a value to match
190 to an enumeration member (i.e. Color(3)) and for the functional API
191 (i.e. Color = Enum('Color', names='red green blue')).
192
193 When used for the functional API: `module`, if set, will be stored in
194 the new class' __module__ attribute; `type`, if set, will be mixed in
195 as the first base class.
196
197 Note: if `module` is not set this routine will attempt to discover the
198 calling module by walking the frame stack; if this is unsuccessful
199 the resulting class will not be pickleable.
200
201 """
202 if names is None: # simple value lookup
203 return cls.__new__(cls, value)
204 # otherwise, functional API: we're creating a new Enum type
205 return cls._create_(value, names, module=module, type=type)
206
207 def __contains__(cls, member):
Ethan Furman520ad572013-07-19 19:47:21 -0700208 return isinstance(member, cls) and member.name in cls._member_map_
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700209
Ethan Furman64a99722013-09-22 16:18:19 -0700210 def __delattr__(cls, attr):
211 # nicer error message when someone tries to delete an attribute
212 # (see issue19025).
213 if attr in cls._member_map_:
214 raise AttributeError(
215 "%s: cannot delete Enum member." % cls.__name__)
216 super().__delattr__(attr)
217
Ethan Furman388a3922013-08-12 06:51:41 -0700218 def __dir__(self):
Ethan Furman64a99722013-09-22 16:18:19 -0700219 return (['__class__', '__doc__', '__members__', '__module__'] +
220 self._member_names_)
Ethan Furman388a3922013-08-12 06:51:41 -0700221
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700222 def __getattr__(cls, name):
223 """Return the enum member matching `name`
224
225 We use __getattr__ instead of descriptors or inserting into the enum
226 class' __dict__ in order to support `name` and `value` being both
227 properties for enum members (which live in the class' __dict__) and
228 enum members themselves.
229
230 """
231 if _is_dunder(name):
232 raise AttributeError(name)
233 try:
Ethan Furman520ad572013-07-19 19:47:21 -0700234 return cls._member_map_[name]
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700235 except KeyError:
236 raise AttributeError(name) from None
237
238 def __getitem__(cls, name):
Ethan Furman520ad572013-07-19 19:47:21 -0700239 return cls._member_map_[name]
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700240
241 def __iter__(cls):
Ethan Furman520ad572013-07-19 19:47:21 -0700242 return (cls._member_map_[name] for name in cls._member_names_)
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700243
244 def __len__(cls):
Ethan Furman520ad572013-07-19 19:47:21 -0700245 return len(cls._member_names_)
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700246
Ethan Furman2131a4a2013-09-14 18:11:24 -0700247 @property
248 def __members__(cls):
249 """Returns a mapping of member name->value.
250
251 This mapping lists all enum members, including aliases. Note that this
252 is a read-only view of the internal mapping.
253
254 """
255 return MappingProxyType(cls._member_map_)
256
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700257 def __repr__(cls):
258 return "<enum %r>" % cls.__name__
259
Ethan Furman2131a4a2013-09-14 18:11:24 -0700260 def __reversed__(cls):
261 return (cls._member_map_[name] for name in reversed(cls._member_names_))
262
Ethan Furmanf203f2d2013-09-06 07:16:48 -0700263 def __setattr__(cls, name, value):
264 """Block attempts to reassign Enum members.
265
266 A simple assignment to the class namespace only changes one of the
267 several possible ways to get an Enum member from the Enum class,
268 resulting in an inconsistent Enumeration.
269
270 """
271 member_map = cls.__dict__.get('_member_map_', {})
272 if name in member_map:
273 raise AttributeError('Cannot reassign members.')
274 super().__setattr__(name, value)
275
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700276 def _create_(cls, class_name, names=None, *, module=None, type=None):
277 """Convenience method to create a new Enum class.
278
279 `names` can be:
280
281 * A string containing member names, separated either with spaces or
282 commas. Values are auto-numbered from 1.
283 * An iterable of member names. Values are auto-numbered from 1.
284 * An iterable of (member name, value) pairs.
285 * A mapping of member name -> value.
286
287 """
288 metacls = cls.__class__
289 bases = (cls, ) if type is None else (type, cls)
290 classdict = metacls.__prepare__(class_name, bases)
291
292 # special processing needed for names?
293 if isinstance(names, str):
294 names = names.replace(',', ' ').split()
295 if isinstance(names, (tuple, list)) and isinstance(names[0], str):
296 names = [(e, i) for (i, e) in enumerate(names, 1)]
297
298 # Here, names is either an iterable of (name, value) or a mapping.
299 for item in names:
300 if isinstance(item, str):
301 member_name, member_value = item, names[item]
302 else:
303 member_name, member_value = item
304 classdict[member_name] = member_value
305 enum_class = metacls.__new__(metacls, class_name, bases, classdict)
306
307 # TODO: replace the frame hack if a blessed way to know the calling
308 # module is ever developed
309 if module is None:
310 try:
311 module = sys._getframe(2).f_globals['__name__']
312 except (AttributeError, ValueError) as exc:
313 pass
314 if module is None:
315 _make_class_unpicklable(enum_class)
316 else:
317 enum_class.__module__ = module
318
319 return enum_class
320
321 @staticmethod
322 def _get_mixins_(bases):
323 """Returns the type for creating enum members, and the first inherited
324 enum class.
325
326 bases: the tuple of bases that was given to __new__
327
328 """
329 if not bases:
330 return object, Enum
331
332 # double check that we are not subclassing a class with existing
333 # enumeration members; while we're at it, see if any other data
334 # type has been mixed in so we can use the correct __new__
335 member_type = first_enum = None
336 for base in bases:
337 if (base is not Enum and
338 issubclass(base, Enum) and
Ethan Furman520ad572013-07-19 19:47:21 -0700339 base._member_names_):
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700340 raise TypeError("Cannot extend enumerations")
341 # base is now the last base in bases
342 if not issubclass(base, Enum):
343 raise TypeError("new enumerations must be created as "
344 "`ClassName([mixin_type,] enum_type)`")
345
346 # get correct mix-in type (either mix-in type of Enum subclass, or
347 # first base if last base is Enum)
348 if not issubclass(bases[0], Enum):
349 member_type = bases[0] # first data type
350 first_enum = bases[-1] # enum type
351 else:
352 for base in bases[0].__mro__:
353 # most common: (IntEnum, int, Enum, object)
354 # possible: (<Enum 'AutoIntEnum'>, <Enum 'IntEnum'>,
355 # <class 'int'>, <Enum 'Enum'>,
356 # <class 'object'>)
357 if issubclass(base, Enum):
358 if first_enum is None:
359 first_enum = base
360 else:
361 if member_type is None:
362 member_type = base
363
364 return member_type, first_enum
365
366 @staticmethod
367 def _find_new_(classdict, member_type, first_enum):
368 """Returns the __new__ to be used for creating the enum members.
369
370 classdict: the class dictionary given to __new__
371 member_type: the data type whose __new__ will be used by default
372 first_enum: enumeration to check for an overriding __new__
373
374 """
375 # now find the correct __new__, checking to see of one was defined
376 # by the user; also check earlier enum classes in case a __new__ was
377 # saved as __new_member__
378 __new__ = classdict.get('__new__', None)
379
380 # should __new__ be saved as __new_member__ later?
381 save_new = __new__ is not None
382
383 if __new__ is None:
384 # check all possibles for __new_member__ before falling back to
385 # __new__
386 for method in ('__new_member__', '__new__'):
387 for possible in (member_type, first_enum):
388 target = getattr(possible, method, None)
389 if target not in {
390 None,
391 None.__new__,
392 object.__new__,
393 Enum.__new__,
394 }:
395 __new__ = target
396 break
397 if __new__ is not None:
398 break
399 else:
400 __new__ = object.__new__
401
402 # if a non-object.__new__ is used then whatever value/tuple was
403 # assigned to the enum member name will be passed to __new__ and to the
404 # new enum member's __init__
405 if __new__ is object.__new__:
406 use_args = False
407 else:
408 use_args = True
409
410 return __new__, save_new, use_args
411
412
413class Enum(metaclass=EnumMeta):
414 """Generic enumeration.
415
416 Derive from this class to define new enumerations.
417
418 """
419 def __new__(cls, value):
420 # all enum instances are actually created during class construction
421 # without calling this method; this method is called by the metaclass'
422 # __call__ (i.e. Color(3) ), and by pickle
423 if type(value) is cls:
424 # For lookups like Color(Color.red)
425 return value
426 # by-value search for a matching enum member
427 # see if it's in the reverse mapping (for hashable values)
Ethan Furman2aa27322013-07-19 19:35:56 -0700428 try:
Ethan Furman520ad572013-07-19 19:47:21 -0700429 if value in cls._value2member_map_:
430 return cls._value2member_map_[value]
Ethan Furman2aa27322013-07-19 19:35:56 -0700431 except TypeError:
432 # not there, now do long search -- O(n) behavior
Ethan Furman520ad572013-07-19 19:47:21 -0700433 for member in cls._member_map_.values():
Ethan Furman2aa27322013-07-19 19:35:56 -0700434 if member.value == value:
435 return member
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700436 raise ValueError("%s is not a valid %s" % (value, cls.__name__))
437
438 def __repr__(self):
439 return "<%s.%s: %r>" % (
Ethan Furman520ad572013-07-19 19:47:21 -0700440 self.__class__.__name__, self._name_, self._value_)
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700441
442 def __str__(self):
Ethan Furman520ad572013-07-19 19:47:21 -0700443 return "%s.%s" % (self.__class__.__name__, self._name_)
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700444
Ethan Furman388a3922013-08-12 06:51:41 -0700445 def __dir__(self):
Ethan Furmanc850f342013-09-15 16:59:35 -0700446 added_behavior = [m for m in self.__class__.__dict__ if m[0] != '_']
Ethan Furman64a99722013-09-22 16:18:19 -0700447 return (['__class__', '__doc__', '__module__', 'name', 'value'] +
448 added_behavior)
Ethan Furman388a3922013-08-12 06:51:41 -0700449
Ethan Furmanec15a822013-08-31 19:17:41 -0700450 def __format__(self, format_spec):
451 # mixed-in Enums should use the mixed-in type's __format__, otherwise
452 # we can get strange results with the Enum name showing up instead of
453 # the value
454
455 # pure Enum branch
456 if self._member_type_ is object:
457 cls = str
458 val = str(self)
459 # mix-in branch
460 else:
461 cls = self._member_type_
462 val = self.value
463 return cls.__format__(val, format_spec)
464
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700465 def __getnewargs__(self):
Ethan Furman520ad572013-07-19 19:47:21 -0700466 return (self._value_, )
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700467
468 def __hash__(self):
Ethan Furman520ad572013-07-19 19:47:21 -0700469 return hash(self._name_)
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700470
Ethan Furman33918c12013-09-27 23:02:02 -0700471 # DynamicClassAttribute is used to provide access to the `name` and
472 # `value` properties of enum members while keeping some measure of
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700473 # protection from modification, while still allowing for an enumeration
474 # to have members named `name` and `value`. This works because enumeration
475 # members are not set directly on the enum class -- __getattr__ is
476 # used to look them up.
477
Ethan Furmane03ea372013-09-25 07:14:41 -0700478 @DynamicClassAttribute
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700479 def name(self):
Ethan Furmanc850f342013-09-15 16:59:35 -0700480 """The name of the Enum member."""
Ethan Furman520ad572013-07-19 19:47:21 -0700481 return self._name_
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700482
Ethan Furmane03ea372013-09-25 07:14:41 -0700483 @DynamicClassAttribute
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700484 def value(self):
Ethan Furmanc850f342013-09-15 16:59:35 -0700485 """The value of the Enum member."""
Ethan Furman520ad572013-07-19 19:47:21 -0700486 return self._value_
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700487
488
489class IntEnum(int, Enum):
490 """Enum where members are also (and must be) ints"""
Ethan Furmanf24bb352013-07-18 17:05:39 -0700491
492
493def unique(enumeration):
494 """Class decorator for enumerations ensuring unique member values."""
495 duplicates = []
496 for name, member in enumeration.__members__.items():
497 if name != member.name:
498 duplicates.append((name, member.name))
499 if duplicates:
500 alias_details = ', '.join(
501 ["%s -> %s" % (alias, name) for (alias, name) in duplicates])
502 raise ValueError('duplicate values found in %r: %s' %
503 (enumeration, alias_details))
504 return enumeration