1
# This file is part of Checkbox.
3
# Copyright 2015 Canonical Ltd.
5
# Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
7
# Checkbox is free software: you can redistribute it and/or modify
8
# it under the terms of the GNU General Public License version 3,
9
# as published by the Free Software Foundation.
11
# Checkbox is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
# GNU General Public License for more details.
16
# You should have received a copy of the GNU General Public License
17
# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
19
# The enum module was copied from Python trunk, originally written by Ethan
20
# Furman <ethan@stoneleaf.us>.
22
# The DynamicClassAttribute class was copied from Python trunk, originally
23
# written by Ethan Furman <ethan@stoneleaf.us>, as committed in git mirror
24
# commit c621f59146d0f3cd9bd78daa9dbf7784f5f7bcab "Close #19030: improvements
25
# to inspect and Enum."
27
# The back-ported MappingProxyType was written by
28
# Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
30
# PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
31
# --------------------------------------------
33
# 1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"),
34
# and the Individual or Organization ("Licensee") accessing and otherwise
35
# using this software ("Python") in source or binary form and its associated
38
# 2. Subject to the terms and conditions of this License Agreement, PSF hereby
39
# grants Licensee a nonexclusive, royalty-free, world-wide license to
40
# reproduce, analyze, test, perform and/or display publicly, prepare
41
# derivative works, distribute, and otherwise use Python alone or in any
42
# derivative version, provided, however, that PSF's License Agreement and
43
# PSF's notice of copyright, i.e., "Copyright (c) 2001, 2002, 2003, 2004,
44
# 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 Python Software
45
# Foundation; All Rights Reserved" are retained in Python alone or in any
46
# derivative version prepared by Licensee.
48
# 3. In the event Licensee prepares a derivative work that is based on or
49
# incorporates Python or any part thereof, and wants to make the derivative
50
# work available to others as provided herein, then Licensee hereby agrees
51
# to include in any such work a brief summary of the changes made to Python.
53
# 4. PSF is making Python available to Licensee on an "AS IS" basis. PSF MAKES
54
# NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE,
55
# BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR
56
# WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT
57
# THE USE OF PYTHON WILL NOT INFRINGE ANY THIRD PARTY RIGHTS.
59
# 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON FOR ANY
60
# INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF
61
# MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, OR ANY DERIVATIVE
62
# THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
64
# 6. This License Agreement will automatically terminate upon a material breach
65
# of its terms and conditions.
67
# 7. Nothing in this License Agreement shall be deemed to create any
68
# relationship of agency, partnership, or joint venture between PSF and
69
# Licensee. This License Agreement does not grant permission to use PSF
70
# trademarks or trade name in a trademark sense to endorse or promote
71
# products or services of Licensee, or any third party.
73
# 8. By copying, installing or otherwise using Python, Licensee agrees to be
74
# bound by the terms and conditions of this License Agreement.
77
from collections import OrderedDict
79
from types import MappingProxyType
81
# NOTE: this code is specific to Plainbox
83
PyDictProxy_New = ctypes.pythonapi.PyDictProxy_New
84
PyDictProxy_New.argtypes = (ctypes.py_object,)
85
PyDictProxy_New.restype = ctypes.py_object
87
class MappingProxyType:
88
def __new__(cls, dict):
89
return PyDictProxy_New(dict)
91
from types import DynamicClassAttribute
93
# NOTE: this code is copied from Python trunk
94
class DynamicClassAttribute:
95
"""Route attribute access on a class to __getattr__.
97
This is a descriptor, used to define attributes that act differently when
98
accessed through an instance and through a class. Instance access remains
99
normal, but access to an attribute through a class will be routed to the
100
class's __getattr__ method; this is done by raising AttributeError.
102
This allows one to have properties active on an instance, and have virtual
103
attributes on the class with the same name (see Enum for an example).
106
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
110
# next two lines make DynamicClassAttribute act the same as property
111
self.__doc__ = doc or fget.__doc__
112
self.overwrite_doc = doc is None
113
# support for abstract methods
114
self.__isabstractmethod__ = bool(getattr(fget, '__isabstractmethod__', False))
116
def __get__(self, instance, ownerclass=None):
118
if self.__isabstractmethod__:
120
raise AttributeError()
121
elif self.fget is None:
122
raise AttributeError("unreadable attribute")
123
return self.fget(instance)
125
def __set__(self, instance, value):
126
if self.fset is None:
127
raise AttributeError("can't set attribute")
128
self.fset(instance, value)
130
def __delete__(self, instance):
131
if self.fdel is None:
132
raise AttributeError("can't delete attribute")
135
def getter(self, fget):
136
fdoc = fget.__doc__ if self.overwrite_doc else None
137
result = type(self)(fget, self.fset, self.fdel, fdoc or self.__doc__)
138
result.overwrite_doc = self.overwrite_doc
141
def setter(self, fset):
142
result = type(self)(self.fget, fset, self.fdel, self.__doc__)
143
result.overwrite_doc = self.overwrite_doc
146
def deleter(self, fdel):
147
result = type(self)(self.fget, self.fset, fdel, self.__doc__)
148
result.overwrite_doc = self.overwrite_doc
151
__all__ = ['Enum', 'IntEnum', 'unique']
154
def _is_descriptor(obj):
155
"""Returns True if obj is a descriptor, False otherwise."""
157
hasattr(obj, '__get__') or
158
hasattr(obj, '__set__') or
159
hasattr(obj, '__delete__'))
162
def _is_dunder(name):
163
"""Returns True if a __dunder__ name, False otherwise."""
164
return (name[:2] == name[-2:] == '__' and
166
name[-3:-2] != '_' and
170
def _is_sunder(name):
171
"""Returns True if a _sunder_ name, False otherwise."""
172
return (name[0] == name[-1] == '_' and
174
name[-2:-1] != '_' and
178
def _make_class_unpicklable(cls):
179
"""Make the given class un-picklable."""
180
def _break_on_call_reduce(self, proto):
181
raise TypeError('%r cannot be pickled' % self)
182
cls.__reduce_ex__ = _break_on_call_reduce
183
cls.__module__ = '<unknown>'
186
class _EnumDict(dict):
187
"""Track enum member order and ensure member names are not reused.
189
EnumMeta will use the names found in self._member_names as the
190
enumeration member names.
195
self._member_names = []
197
def __setitem__(self, key, value):
198
"""Changes anything not dundered or not a descriptor.
200
If an enum member name is used twice, an error is raised; duplicate
201
values are not checked for.
203
Single underscore (sunder) names are reserved.
207
raise ValueError('_names_ are reserved for future Enum use')
208
elif _is_dunder(key):
210
elif key in self._member_names:
211
# descriptor overwriting an enum?
212
raise TypeError('Attempted to reuse key: %r' % key)
213
elif not _is_descriptor(value):
215
# enum overwriting a descriptor?
216
raise TypeError('Key already defined as: %r' % self[key])
217
self._member_names.append(key)
218
super().__setitem__(key, value)
222
# Dummy value for Enum as EnumMeta explicitly checks for it, but of course
223
# until EnumMeta finishes running the first time the Enum class doesn't exist.
224
# This is also why there are checks in EnumMeta like `if Enum is not None`
228
class EnumMeta(type):
229
"""Metaclass for Enum"""
231
def __prepare__(metacls, cls, bases):
234
def __new__(metacls, cls, bases, classdict):
235
# an Enum class is final once enumeration items have been defined; it
236
# cannot be mixed with other types (int, float, etc.) if it has an
237
# inherited __new__ unless a new __new__ is defined (or the resulting
239
member_type, first_enum = metacls._get_mixins_(bases)
240
__new__, save_new, use_args = metacls._find_new_(classdict, member_type,
243
# save enum items into separate mapping so they don't get baked into
245
members = {k: classdict[k] for k in classdict._member_names}
246
for name in classdict._member_names:
249
# check for illegal enum names (any others?)
250
invalid_names = set(members) & {'mro', }
252
raise ValueError('Invalid enum member name: {0}'.format(
253
','.join(invalid_names)))
255
# create our new Enum type
256
enum_class = super().__new__(metacls, cls, bases, classdict)
257
enum_class._member_names_ = [] # names in definition order
258
enum_class._member_map_ = OrderedDict() # name->value map
259
enum_class._member_type_ = member_type
261
# Reverse value->name map for hashable values.
262
enum_class._value2member_map_ = {}
264
# If a custom type is mixed into the Enum, and it does not know how
265
# to pickle itself, pickle.dumps will succeed but pickle.loads will
266
# fail. Rather than have the error show up later and possibly far
267
# from the source, sabotage the pickle protocol for this class so
268
# that pickle.dumps also fails.
270
# However, if the new class implements its own __reduce_ex__, do not
271
# sabotage -- it's on them to make sure it works correctly. We use
272
# __reduce_ex__ instead of any of the others as it is preferred by
273
# pickle over __reduce__, and it handles all pickle protocols.
274
if '__reduce_ex__' not in classdict:
275
if member_type is not object:
276
methods = ('__getnewargs_ex__', '__getnewargs__',
277
'__reduce_ex__', '__reduce__')
278
if not any(m in member_type.__dict__ for m in methods):
279
_make_class_unpicklable(enum_class)
281
# instantiate them, checking for duplicates as we go
282
# we instantiate first instead of checking for duplicates first in case
283
# a custom __new__ is doing something funky with the values -- such as
285
for member_name in classdict._member_names:
286
value = members[member_name]
287
if not isinstance(value, tuple):
291
if member_type is tuple: # special case for tuple enums
292
args = (args, ) # wrap it one more time
294
enum_member = __new__(enum_class)
295
if not hasattr(enum_member, '_value_'):
296
enum_member._value_ = value
298
enum_member = __new__(enum_class, *args)
299
if not hasattr(enum_member, '_value_'):
300
enum_member._value_ = member_type(*args)
301
value = enum_member._value_
302
enum_member._name_ = member_name
303
enum_member.__objclass__ = enum_class
304
enum_member.__init__(*args)
305
# If another member with the same value was already defined, the
306
# new member becomes an alias to the existing one.
307
for name, canonical_member in enum_class._member_map_.items():
308
if canonical_member._value_ == enum_member._value_:
309
enum_member = canonical_member
312
# Aliases don't appear in member names (only in __members__).
313
enum_class._member_names_.append(member_name)
314
enum_class._member_map_[member_name] = enum_member
316
# This may fail if value is not hashable. We can't add the value
317
# to the map, and by-value lookups for this value will be
319
enum_class._value2member_map_[value] = enum_member
323
# double check that repr and friends are not the mixin's or various
324
# things break (such as pickle)
325
for name in ('__repr__', '__str__', '__format__', '__reduce_ex__'):
326
class_method = getattr(enum_class, name)
327
obj_method = getattr(member_type, name, None)
328
enum_method = getattr(first_enum, name, None)
329
if obj_method is not None and obj_method is class_method:
330
setattr(enum_class, name, enum_method)
332
# replace any other __new__ with our own (as long as Enum is not None,
333
# anyway) -- again, this is to support pickle
335
# if the user defined their own __new__, save it before it gets
336
# clobbered in case they subclass later
338
enum_class.__new_member__ = __new__
339
enum_class.__new__ = Enum.__new__
342
def __call__(cls, value, names=None, *, module=None, qualname=None, type=None, start=1):
343
"""Either returns an existing member, or creates a new enum class.
345
This method is used both when an enum class is given a value to match
346
to an enumeration member (i.e. Color(3)) and for the functional API
347
(i.e. Color = Enum('Color', names='red green blue')).
349
When used for the functional API:
351
`value` will be the name of the new class.
353
`names` should be either a string of white-space/comma delimited names
354
(values will start at `start`), or an iterator/mapping of name, value pairs.
356
`module` should be set to the module this class is being created in;
357
if it is not set, an attempt to find that module will be made, but if
358
it fails the class will not be picklable.
360
`qualname` should be set to the actual location this class can be found
361
at in its module; by default it is set to the global scope. If this is
362
not correct, unpickling will fail in some circumstances.
364
`type`, if set, will be mixed in as the first base class.
367
if names is None: # simple value lookup
368
return cls.__new__(cls, value)
369
# otherwise, functional API: we're creating a new Enum type
370
return cls._create_(value, names, module=module, qualname=qualname, type=type, start=start)
372
def __contains__(cls, member):
373
return isinstance(member, cls) and member._name_ in cls._member_map_
375
def __delattr__(cls, attr):
376
# nicer error message when someone tries to delete an attribute
378
if attr in cls._member_map_:
379
raise AttributeError(
380
"%s: cannot delete Enum member." % cls.__name__)
381
super().__delattr__(attr)
384
return (['__class__', '__doc__', '__members__', '__module__'] +
387
def __getattr__(cls, name):
388
"""Return the enum member matching `name`
390
We use __getattr__ instead of descriptors or inserting into the enum
391
class' __dict__ in order to support `name` and `value` being both
392
properties for enum members (which live in the class' __dict__) and
393
enum members themselves.
397
raise AttributeError(name)
399
return cls._member_map_[name]
401
# NOTE: patched on python 3.2
402
raise AttributeError(name)
404
def __getitem__(cls, name):
405
return cls._member_map_[name]
408
return (cls._member_map_[name] for name in cls._member_names_)
411
return len(cls._member_names_)
414
def __members__(cls):
415
"""Returns a mapping of member name->value.
417
This mapping lists all enum members, including aliases. Note that this
418
is a read-only view of the internal mapping.
421
return MappingProxyType(cls._member_map_)
424
return "<enum %r>" % cls.__name__
426
def __reversed__(cls):
427
return (cls._member_map_[name] for name in reversed(cls._member_names_))
429
def __setattr__(cls, name, value):
430
"""Block attempts to reassign Enum members.
432
A simple assignment to the class namespace only changes one of the
433
several possible ways to get an Enum member from the Enum class,
434
resulting in an inconsistent Enumeration.
437
member_map = cls.__dict__.get('_member_map_', {})
438
if name in member_map:
439
raise AttributeError('Cannot reassign members.')
440
super().__setattr__(name, value)
442
def _create_(cls, class_name, names=None, *, module=None, qualname=None, type=None, start=1):
443
"""Convenience method to create a new Enum class.
447
* A string containing member names, separated either with spaces or
448
commas. Values are incremented by 1 from `start`.
449
* An iterable of member names. Values are incremented by 1 from `start`.
450
* An iterable of (member name, value) pairs.
451
* A mapping of member name -> value pairs.
454
metacls = cls.__class__
455
bases = (cls, ) if type is None else (type, cls)
456
classdict = metacls.__prepare__(class_name, bases)
458
# special processing needed for names?
459
if isinstance(names, str):
460
names = names.replace(',', ' ').split()
461
if isinstance(names, (tuple, list)) and isinstance(names[0], str):
462
names = [(e, i) for (i, e) in enumerate(names, start)]
464
# Here, names is either an iterable of (name, value) or a mapping.
466
if isinstance(item, str):
467
member_name, member_value = item, names[item]
469
member_name, member_value = item
470
classdict[member_name] = member_value
471
enum_class = metacls.__new__(metacls, class_name, bases, classdict)
473
# TODO: replace the frame hack if a blessed way to know the calling
474
# module is ever developed
477
module = sys._getframe(2).f_globals['__name__']
478
except (AttributeError, ValueError) as exc:
481
_make_class_unpicklable(enum_class)
483
enum_class.__module__ = module
484
if qualname is not None:
485
enum_class.__qualname__ = qualname
490
def _get_mixins_(bases):
491
"""Returns the type for creating enum members, and the first inherited
494
bases: the tuple of bases that was given to __new__
500
# double check that we are not subclassing a class with existing
501
# enumeration members; while we're at it, see if any other data
502
# type has been mixed in so we can use the correct __new__
503
member_type = first_enum = None
505
if (base is not Enum and
506
issubclass(base, Enum) and
507
base._member_names_):
508
raise TypeError("Cannot extend enumerations")
509
# base is now the last base in bases
510
if not issubclass(base, Enum):
511
raise TypeError("new enumerations must be created as "
512
"`ClassName([mixin_type,] enum_type)`")
514
# get correct mix-in type (either mix-in type of Enum subclass, or
515
# first base if last base is Enum)
516
if not issubclass(bases[0], Enum):
517
member_type = bases[0] # first data type
518
first_enum = bases[-1] # enum type
520
for base in bases[0].__mro__:
521
# most common: (IntEnum, int, Enum, object)
522
# possible: (<Enum 'AutoIntEnum'>, <Enum 'IntEnum'>,
523
# <class 'int'>, <Enum 'Enum'>,
525
if issubclass(base, Enum):
526
if first_enum is None:
529
if member_type is None:
532
return member_type, first_enum
535
def _find_new_(classdict, member_type, first_enum):
536
"""Returns the __new__ to be used for creating the enum members.
538
classdict: the class dictionary given to __new__
539
member_type: the data type whose __new__ will be used by default
540
first_enum: enumeration to check for an overriding __new__
543
# now find the correct __new__, checking to see of one was defined
544
# by the user; also check earlier enum classes in case a __new__ was
545
# saved as __new_member__
546
__new__ = classdict.get('__new__', None)
548
# should __new__ be saved as __new_member__ later?
549
save_new = __new__ is not None
552
# check all possibles for __new_member__ before falling back to
554
for method in ('__new_member__', '__new__'):
555
for possible in (member_type, first_enum):
556
target = getattr(possible, method, None)
565
if __new__ is not None:
568
__new__ = object.__new__
570
# if a non-object.__new__ is used then whatever value/tuple was
571
# assigned to the enum member name will be passed to __new__ and to the
572
# new enum member's __init__
573
if __new__ is object.__new__:
578
return __new__, save_new, use_args
581
class Enum(metaclass=EnumMeta):
582
"""Generic enumeration.
584
Derive from this class to define new enumerations.
587
def __new__(cls, value):
588
# all enum instances are actually created during class construction
589
# without calling this method; this method is called by the metaclass'
590
# __call__ (i.e. Color(3) ), and by pickle
591
if type(value) is cls:
592
# For lookups like Color(Color.red)
594
# by-value search for a matching enum member
595
# see if it's in the reverse mapping (for hashable values)
597
if value in cls._value2member_map_:
598
return cls._value2member_map_[value]
600
# not there, now do long search -- O(n) behavior
601
for member in cls._member_map_.values():
602
if member._value_ == value:
604
raise ValueError("%r is not a valid %s" % (value, cls.__name__))
607
return "<%s.%s: %r>" % (
608
self.__class__.__name__, self._name_, self._value_)
611
return "%s.%s" % (self.__class__.__name__, self._name_)
616
for cls in self.__class__.mro()
617
for m in cls.__dict__
620
return (['__class__', '__doc__', '__module__'] + added_behavior)
622
def __format__(self, format_spec):
623
# mixed-in Enums should use the mixed-in type's __format__, otherwise
624
# we can get strange results with the Enum name showing up instead of
628
if self._member_type_ is object:
633
cls = self._member_type_
635
return cls.__format__(val, format_spec)
638
return hash(self._name_)
640
def __reduce_ex__(self, proto):
641
return self.__class__, (self._value_, )
643
# DynamicClassAttribute is used to provide access to the `name` and
644
# `value` properties of enum members while keeping some measure of
645
# protection from modification, while still allowing for an enumeration
646
# to have members named `name` and `value`. This works because enumeration
647
# members are not set directly on the enum class -- __getattr__ is
648
# used to look them up.
650
@DynamicClassAttribute
652
"""The name of the Enum member."""
655
@DynamicClassAttribute
657
"""The value of the Enum member."""
661
class IntEnum(int, Enum):
662
"""Enum where members are also (and must be) ints"""
665
def unique(enumeration):
666
"""Class decorator for enumerations ensuring unique member values."""
668
for name, member in enumeration.__members__.items():
669
if name != member.name:
670
duplicates.append((name, member.name))
672
alias_details = ', '.join(
673
["%s -> %s" % (alias, name) for (alias, name) in duplicates])
674
raise ValueError('duplicate values found in %r: %s' %
675
(enumeration, alias_details))