blob: 34eb7b87f65e6040f8b0517de5b6b580dea7cc3f [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
53
54class _EnumDict(dict):
55 """Keeps track of definition order of the enum items.
56
57 EnumMeta will use the names found in self._member_names as the
58 enumeration member names.
59
60 """
61 def __init__(self):
62 super().__init__()
63 self._member_names = []
64
65 def __setitem__(self, key, value):
66 """Changes anything not dundered or that doesn't have __get__.
67
68 If a descriptor is added with the same name as an enum member, the name
69 is removed from _member_names (this may leave a hole in the numerical
70 sequence of values).
71
72 If an enum member name is used twice, an error is raised; duplicate
73 values are not checked for.
74
75 Single underscore (sunder) names are reserved.
76
77 """
78 if _is_sunder(key):
79 raise ValueError('_names_ are reserved for future Enum use')
80 elif _is_dunder(key) or hasattr(value, '__get__'):
81 if key in self._member_names:
82 # overwriting an enum with a method? then remove the name from
83 # _member_names or it will become an enum anyway when the class
84 # is created
85 self._member_names.remove(key)
86 else:
87 if key in self._member_names:
88 raise TypeError('Attempted to reuse key: %r' % key)
89 self._member_names.append(key)
90 super().__setitem__(key, value)
91
92
93# Dummy value for Enum as EnumMeta explicity checks for it, but of course until
94# EnumMeta finishes running the first time the Enum class doesn't exist. This
95# is also why there are checks in EnumMeta like `if Enum is not None`
96Enum = None
97
98
99class EnumMeta(type):
100 """Metaclass for Enum"""
101 @classmethod
102 def __prepare__(metacls, cls, bases):
103 return _EnumDict()
104
105 def __new__(metacls, cls, bases, classdict):
106 # an Enum class is final once enumeration items have been defined; it
107 # cannot be mixed with other types (int, float, etc.) if it has an
108 # inherited __new__ unless a new __new__ is defined (or the resulting
109 # class will fail).
110 member_type, first_enum = metacls._get_mixins_(bases)
111 __new__, save_new, use_args = metacls._find_new_(classdict, member_type,
112 first_enum)
113
114 # save enum items into separate mapping so they don't get baked into
115 # the new class
116 members = {k: classdict[k] for k in classdict._member_names}
117 for name in classdict._member_names:
118 del classdict[name]
119
120 # check for illegal enum names (any others?)
121 invalid_names = set(members) & {'mro', }
122 if invalid_names:
123 raise ValueError('Invalid enum member name: {0}'.format(
124 ','.join(invalid_names)))
125
126 # create our new Enum type
127 enum_class = super().__new__(metacls, cls, bases, classdict)
Ethan Furman520ad572013-07-19 19:47:21 -0700128 enum_class._member_names_ = [] # names in definition order
129 enum_class._member_map_ = OrderedDict() # name->value map
Ethan Furman5e5a8232013-08-04 08:42:23 -0700130 enum_class._member_type_ = member_type
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700131
132 # Reverse value->name map for hashable values.
Ethan Furman520ad572013-07-19 19:47:21 -0700133 enum_class._value2member_map_ = {}
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700134
135 # check for a __getnewargs__, and if not present sabotage
136 # pickling, since it won't work anyway
137 if (member_type is not object and
138 member_type.__dict__.get('__getnewargs__') is None
139 ):
140 _make_class_unpicklable(enum_class)
141
142 # instantiate them, checking for duplicates as we go
143 # we instantiate first instead of checking for duplicates first in case
144 # a custom __new__ is doing something funky with the values -- such as
145 # auto-numbering ;)
146 for member_name in classdict._member_names:
147 value = members[member_name]
148 if not isinstance(value, tuple):
149 args = (value, )
150 else:
151 args = value
152 if member_type is tuple: # special case for tuple enums
153 args = (args, ) # wrap it one more time
154 if not use_args:
155 enum_member = __new__(enum_class)
Ethan Furmanb41803e2013-07-25 13:50:45 -0700156 if not hasattr(enum_member, '_value_'):
157 enum_member._value_ = value
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700158 else:
159 enum_member = __new__(enum_class, *args)
Ethan Furmanb41803e2013-07-25 13:50:45 -0700160 if not hasattr(enum_member, '_value_'):
161 enum_member._value_ = member_type(*args)
Ethan Furman520ad572013-07-19 19:47:21 -0700162 value = enum_member._value_
Ethan Furman520ad572013-07-19 19:47:21 -0700163 enum_member._name_ = member_name
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700164 enum_member.__init__(*args)
165 # If another member with the same value was already defined, the
166 # new member becomes an alias to the existing one.
Ethan Furman520ad572013-07-19 19:47:21 -0700167 for name, canonical_member in enum_class._member_map_.items():
168 if canonical_member.value == enum_member._value_:
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700169 enum_member = canonical_member
170 break
171 else:
172 # Aliases don't appear in member names (only in __members__).
Ethan Furman520ad572013-07-19 19:47:21 -0700173 enum_class._member_names_.append(member_name)
174 enum_class._member_map_[member_name] = enum_member
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700175 try:
176 # This may fail if value is not hashable. We can't add the value
177 # to the map, and by-value lookups for this value will be
178 # linear.
Ethan Furman520ad572013-07-19 19:47:21 -0700179 enum_class._value2member_map_[value] = enum_member
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700180 except TypeError:
181 pass
182
183 # double check that repr and friends are not the mixin's or various
184 # things break (such as pickle)
185 for name in ('__repr__', '__str__', '__getnewargs__'):
186 class_method = getattr(enum_class, name)
187 obj_method = getattr(member_type, name, None)
188 enum_method = getattr(first_enum, name, None)
189 if obj_method is not None and obj_method is class_method:
190 setattr(enum_class, name, enum_method)
191
192 # replace any other __new__ with our own (as long as Enum is not None,
193 # anyway) -- again, this is to support pickle
194 if Enum is not None:
195 # if the user defined their own __new__, save it before it gets
196 # clobbered in case they subclass later
197 if save_new:
198 enum_class.__new_member__ = __new__
199 enum_class.__new__ = Enum.__new__
200 return enum_class
201
202 def __call__(cls, value, names=None, *, module=None, type=None):
203 """Either returns an existing member, or creates a new enum class.
204
205 This method is used both when an enum class is given a value to match
206 to an enumeration member (i.e. Color(3)) and for the functional API
207 (i.e. Color = Enum('Color', names='red green blue')).
208
209 When used for the functional API: `module`, if set, will be stored in
210 the new class' __module__ attribute; `type`, if set, will be mixed in
211 as the first base class.
212
213 Note: if `module` is not set this routine will attempt to discover the
214 calling module by walking the frame stack; if this is unsuccessful
215 the resulting class will not be pickleable.
216
217 """
218 if names is None: # simple value lookup
219 return cls.__new__(cls, value)
220 # otherwise, functional API: we're creating a new Enum type
221 return cls._create_(value, names, module=module, type=type)
222
223 def __contains__(cls, member):
Ethan Furman520ad572013-07-19 19:47:21 -0700224 return isinstance(member, cls) and member.name in cls._member_map_
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700225
Ethan Furman388a3922013-08-12 06:51:41 -0700226 def __dir__(self):
227 return ['__class__', '__doc__', '__members__'] + self._member_names_
228
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700229 @property
230 def __members__(cls):
231 """Returns a mapping of member name->value.
232
233 This mapping lists all enum members, including aliases. Note that this
234 is a read-only view of the internal mapping.
235
236 """
Ethan Furman520ad572013-07-19 19:47:21 -0700237 return MappingProxyType(cls._member_map_)
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700238
239 def __getattr__(cls, name):
240 """Return the enum member matching `name`
241
242 We use __getattr__ instead of descriptors or inserting into the enum
243 class' __dict__ in order to support `name` and `value` being both
244 properties for enum members (which live in the class' __dict__) and
245 enum members themselves.
246
247 """
248 if _is_dunder(name):
249 raise AttributeError(name)
250 try:
Ethan Furman520ad572013-07-19 19:47:21 -0700251 return cls._member_map_[name]
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700252 except KeyError:
253 raise AttributeError(name) from None
254
255 def __getitem__(cls, name):
Ethan Furman520ad572013-07-19 19:47:21 -0700256 return cls._member_map_[name]
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700257
258 def __iter__(cls):
Ethan Furman520ad572013-07-19 19:47:21 -0700259 return (cls._member_map_[name] for name in cls._member_names_)
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700260
261 def __len__(cls):
Ethan Furman520ad572013-07-19 19:47:21 -0700262 return len(cls._member_names_)
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700263
264 def __repr__(cls):
265 return "<enum %r>" % cls.__name__
266
267 def _create_(cls, class_name, names=None, *, module=None, type=None):
268 """Convenience method to create a new Enum class.
269
270 `names` can be:
271
272 * A string containing member names, separated either with spaces or
273 commas. Values are auto-numbered from 1.
274 * An iterable of member names. Values are auto-numbered from 1.
275 * An iterable of (member name, value) pairs.
276 * A mapping of member name -> value.
277
278 """
279 metacls = cls.__class__
280 bases = (cls, ) if type is None else (type, cls)
281 classdict = metacls.__prepare__(class_name, bases)
282
283 # special processing needed for names?
284 if isinstance(names, str):
285 names = names.replace(',', ' ').split()
286 if isinstance(names, (tuple, list)) and isinstance(names[0], str):
287 names = [(e, i) for (i, e) in enumerate(names, 1)]
288
289 # Here, names is either an iterable of (name, value) or a mapping.
290 for item in names:
291 if isinstance(item, str):
292 member_name, member_value = item, names[item]
293 else:
294 member_name, member_value = item
295 classdict[member_name] = member_value
296 enum_class = metacls.__new__(metacls, class_name, bases, classdict)
297
298 # TODO: replace the frame hack if a blessed way to know the calling
299 # module is ever developed
300 if module is None:
301 try:
302 module = sys._getframe(2).f_globals['__name__']
303 except (AttributeError, ValueError) as exc:
304 pass
305 if module is None:
306 _make_class_unpicklable(enum_class)
307 else:
308 enum_class.__module__ = module
309
310 return enum_class
311
312 @staticmethod
313 def _get_mixins_(bases):
314 """Returns the type for creating enum members, and the first inherited
315 enum class.
316
317 bases: the tuple of bases that was given to __new__
318
319 """
320 if not bases:
321 return object, Enum
322
323 # double check that we are not subclassing a class with existing
324 # enumeration members; while we're at it, see if any other data
325 # type has been mixed in so we can use the correct __new__
326 member_type = first_enum = None
327 for base in bases:
328 if (base is not Enum and
329 issubclass(base, Enum) and
Ethan Furman520ad572013-07-19 19:47:21 -0700330 base._member_names_):
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700331 raise TypeError("Cannot extend enumerations")
332 # base is now the last base in bases
333 if not issubclass(base, Enum):
334 raise TypeError("new enumerations must be created as "
335 "`ClassName([mixin_type,] enum_type)`")
336
337 # get correct mix-in type (either mix-in type of Enum subclass, or
338 # first base if last base is Enum)
339 if not issubclass(bases[0], Enum):
340 member_type = bases[0] # first data type
341 first_enum = bases[-1] # enum type
342 else:
343 for base in bases[0].__mro__:
344 # most common: (IntEnum, int, Enum, object)
345 # possible: (<Enum 'AutoIntEnum'>, <Enum 'IntEnum'>,
346 # <class 'int'>, <Enum 'Enum'>,
347 # <class 'object'>)
348 if issubclass(base, Enum):
349 if first_enum is None:
350 first_enum = base
351 else:
352 if member_type is None:
353 member_type = base
354
355 return member_type, first_enum
356
357 @staticmethod
358 def _find_new_(classdict, member_type, first_enum):
359 """Returns the __new__ to be used for creating the enum members.
360
361 classdict: the class dictionary given to __new__
362 member_type: the data type whose __new__ will be used by default
363 first_enum: enumeration to check for an overriding __new__
364
365 """
366 # now find the correct __new__, checking to see of one was defined
367 # by the user; also check earlier enum classes in case a __new__ was
368 # saved as __new_member__
369 __new__ = classdict.get('__new__', None)
370
371 # should __new__ be saved as __new_member__ later?
372 save_new = __new__ is not None
373
374 if __new__ is None:
375 # check all possibles for __new_member__ before falling back to
376 # __new__
377 for method in ('__new_member__', '__new__'):
378 for possible in (member_type, first_enum):
379 target = getattr(possible, method, None)
380 if target not in {
381 None,
382 None.__new__,
383 object.__new__,
384 Enum.__new__,
385 }:
386 __new__ = target
387 break
388 if __new__ is not None:
389 break
390 else:
391 __new__ = object.__new__
392
393 # if a non-object.__new__ is used then whatever value/tuple was
394 # assigned to the enum member name will be passed to __new__ and to the
395 # new enum member's __init__
396 if __new__ is object.__new__:
397 use_args = False
398 else:
399 use_args = True
400
401 return __new__, save_new, use_args
402
403
404class Enum(metaclass=EnumMeta):
405 """Generic enumeration.
406
407 Derive from this class to define new enumerations.
408
409 """
410 def __new__(cls, value):
411 # all enum instances are actually created during class construction
412 # without calling this method; this method is called by the metaclass'
413 # __call__ (i.e. Color(3) ), and by pickle
414 if type(value) is cls:
415 # For lookups like Color(Color.red)
416 return value
417 # by-value search for a matching enum member
418 # see if it's in the reverse mapping (for hashable values)
Ethan Furman2aa27322013-07-19 19:35:56 -0700419 try:
Ethan Furman520ad572013-07-19 19:47:21 -0700420 if value in cls._value2member_map_:
421 return cls._value2member_map_[value]
Ethan Furman2aa27322013-07-19 19:35:56 -0700422 except TypeError:
423 # not there, now do long search -- O(n) behavior
Ethan Furman520ad572013-07-19 19:47:21 -0700424 for member in cls._member_map_.values():
Ethan Furman2aa27322013-07-19 19:35:56 -0700425 if member.value == value:
426 return member
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700427 raise ValueError("%s is not a valid %s" % (value, cls.__name__))
428
429 def __repr__(self):
430 return "<%s.%s: %r>" % (
Ethan Furman520ad572013-07-19 19:47:21 -0700431 self.__class__.__name__, self._name_, self._value_)
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700432
433 def __str__(self):
Ethan Furman520ad572013-07-19 19:47:21 -0700434 return "%s.%s" % (self.__class__.__name__, self._name_)
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700435
Ethan Furman388a3922013-08-12 06:51:41 -0700436 def __dir__(self):
437 return (['__class__', '__doc__', 'name', 'value'])
438
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700439 def __eq__(self, other):
440 if type(other) is self.__class__:
441 return self is other
442 return NotImplemented
443
444 def __getnewargs__(self):
Ethan Furman520ad572013-07-19 19:47:21 -0700445 return (self._value_, )
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700446
447 def __hash__(self):
Ethan Furman520ad572013-07-19 19:47:21 -0700448 return hash(self._name_)
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700449
450 # _RouteClassAttributeToGetattr is used to provide access to the `name`
451 # and `value` properties of enum members while keeping some measure of
452 # protection from modification, while still allowing for an enumeration
453 # to have members named `name` and `value`. This works because enumeration
454 # members are not set directly on the enum class -- __getattr__ is
455 # used to look them up.
456
457 @_RouteClassAttributeToGetattr
458 def name(self):
Ethan Furman520ad572013-07-19 19:47:21 -0700459 return self._name_
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700460
461 @_RouteClassAttributeToGetattr
462 def value(self):
Ethan Furman520ad572013-07-19 19:47:21 -0700463 return self._value_
Ethan Furman6b3d64a2013-06-14 16:55:46 -0700464
465
466class IntEnum(int, Enum):
467 """Enum where members are also (and must be) ints"""
Ethan Furmanf24bb352013-07-18 17:05:39 -0700468
469
470def unique(enumeration):
471 """Class decorator for enumerations ensuring unique member values."""
472 duplicates = []
473 for name, member in enumeration.__members__.items():
474 if name != member.name:
475 duplicates.append((name, member.name))
476 if duplicates:
477 alias_details = ', '.join(
478 ["%s -> %s" % (alias, name) for (alias, name) in duplicates])
479 raise ValueError('duplicate values found in %r: %s' %
480 (enumeration, alias_details))
481 return enumeration