diff --git a/beets/autotag/match.py b/beets/autotag/match.py index a4bc47fa8..f80b41789 100644 --- a/beets/autotag/match.py +++ b/beets/autotag/match.py @@ -25,11 +25,12 @@ from munkres import Munkres from beets import plugins from beets import config from beets.util import plurality -from beets.util.enumeration import enum +from beets.util.enumeration import OrderedEnum from beets.autotag import hooks # Recommendation enumeration. -recommendation = enum('none', 'low', 'medium', 'strong', name='recommendation') +recommendation = OrderedEnum('recommendation', ['none', 'low', 'medium', + 'strong']) # Artist signals that indicate "various artists". These are used at the # album level to determine whether a given release is likely a VA diff --git a/beets/importer.py b/beets/importer.py index f997770c4..409a2c549 100644 --- a/beets/importer.py +++ b/beets/importer.py @@ -31,13 +31,12 @@ from beets import util from beets import config from beets.util import pipeline from beets.util import syspath, normpath, displayable_path -from beets.util.enumeration import enum +from enum import Enum from beets import mediafile -action = enum( - 'SKIP', 'ASIS', 'TRACKS', 'MANUAL', 'APPLY', 'MANUAL_ID', - 'ALBUMS', name='action' -) +action = Enum('action', + ['SKIP', 'ASIS', 'TRACKS', 'MANUAL', 'APPLY', 'MANUAL_ID', + 'ALBUMS']) QUEUE_SIZE = 128 SINGLE_ARTIST_THRESH = 0.25 diff --git a/beets/mediafile.py b/beets/mediafile.py index 301e0f370..288d2340d 100644 --- a/beets/mediafile.py +++ b/beets/mediafile.py @@ -49,7 +49,7 @@ import imghdr import os import logging import traceback -from beets.util.enumeration import enum +from beets.util.enumeration import IndexableEnum __all__ = ['UnreadableFileError', 'FileTypeError', 'MediaFile'] @@ -295,29 +295,31 @@ class Image(object): * ``mime_type`` Read-only property that contains the mime type of the binary data """ - TYPES = enum([ + # By default, enums are 1-indexed. We make this enum zero-indexed + # by explicitly specifying the indicies of field + TYPES = IndexableEnum('TageImage.TYPES', [(k, i) for (i, k) in enumerate([ 'other', 'icon', - 'other icon', + 'other_icon', 'front', 'back', 'leaflet', 'media', - 'lead artist', + 'lead_artist', 'artist', 'conductor', 'group', 'composer', 'lyricist', - 'recording location', - 'recording session', + 'recording_location', + 'recording_session', 'performance', - 'screen capture', + 'screen_capture', 'fish', 'illustration', - 'artist logo', - 'publisher logo', - ], name='TageImage.TYPES') + 'artist_logo', + 'publisher_logo', + ])]) def __init__(self, data, desc=None, type=None): self.data = data diff --git a/beets/util/enumeration.py b/beets/util/enumeration.py index e6ec07665..531b406bb 100644 --- a/beets/util/enumeration.py +++ b/beets/util/enumeration.py @@ -12,167 +12,41 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""A metaclass for enumerated types that really are types. +from enum import Enum, EnumMeta -You can create enumerations with `enum(values, [name])` and they work -how you would expect them to. - - >>> from enumeration import enum - >>> Direction = enum('north east south west', name='Direction') - >>> Direction.west - Direction.west - >>> Direction.west == Direction.west - True - >>> Direction.west == Direction.east - False - >>> isinstance(Direction.west, Direction) - True - >>> Direction[3] - Direction.west - >>> Direction['west'] - Direction.west - >>> Direction.west.name - 'west' - >>> Direction.north < Direction.west - True - -Enumerations are classes; their instances represent the possible values -of the enumeration. Because Python classes must have names, you may -provide a `name` parameter to `enum`; if you don't, a meaningless one -will be chosen for you. -""" -import random - -class Enumeration(type): - """A metaclass whose classes are enumerations. - - The `values` attribute of the class is used to populate the - enumeration. Values may either be a list of enumerated names or a - string containing a space-separated list of names. When the class - is created, it is instantiated for each name value in `values`. - Each such instance is the name of the enumerated item as the sole - argument. - - The `Enumerated` class is a good choice for a superclass. +class OrderedEnum(Enum): """ - - def __init__(cls, name, bases, dic): - super(Enumeration, cls).__init__(name, bases, dic) - - if 'values' not in dic: - # Do nothing if no values are provided (i.e., with - # Enumerated itself). - return - - # May be called with a single string, in which case we split on - # whitespace for convenience. - values = dic['values'] - if isinstance(values, basestring): - values = values.split() - - # Create the Enumerated instances for each value. We have to use - # super's __setattr__ here because we disallow setattr below. - super(Enumeration, cls).__setattr__('_items_dict', {}) - super(Enumeration, cls).__setattr__('_items_list', []) - for value in values: - item = cls(value, len(cls._items_list)) - cls._items_dict[value] = item - cls._items_list.append(item) - - def __getattr__(cls, key): - try: - return cls._items_dict[key] - except KeyError: - raise AttributeError("enumeration '" + cls.__name__ + - "' has no item '" + key + "'") - - def __setattr__(cls, key, val): - raise TypeError("enumerations do not support attribute assignment") - - def __getitem__(cls, key): - if isinstance(key, int): - return cls._items_list[key] - else: - return getattr(cls, key) - - def __len__(cls): - return len(cls._items_list) - - def __iter__(cls): - return iter(cls._items_list) - - def __nonzero__(cls): - # Ensures that __len__ doesn't get called before __init__ by - # pydoc. - return True - -class Enumerated(object): - """An item in an enumeration. - - Contains instance methods inherited by enumerated objects. The - metaclass is preset to `Enumeration` for your convenience. - - Instance attributes: - name -- The name of the item. - index -- The index of the item in its enumeration. - - >>> from enumeration import Enumerated - >>> class Garment(Enumerated): - ... values = 'hat glove belt poncho lederhosen suspenders' - ... def wear(self): - ... print('now wearing a ' + self.name) - ... - >>> Garment.poncho.wear() - now wearing a poncho + An Enum subclass that allows comparison of members. """ + def __ge__(self, other): + if self.__class__ is other.__class__: + return self._value_ >= other._value_ + return NotImplemented + def __gt__(self, other): + if self.__class__ is other.__class__: + return self._value_ > other._value_ + return NotImplemented + def __le__(self, other): + if self.__class__ is other.__class__: + return self._value_ <= other._value_ + return NotImplemented + def __lt__(self, other): + if self.__class__ is other.__class__: + return self._value_ < other._value_ + return NotImplemented - __metaclass__ = Enumeration - - def __init__(self, name, index): - self.name = name - self.index = index - - def __str__(self): - return type(self).__name__ + '.' + self.name - - def __repr__(self): - return str(self) - - def __cmp__(self, other): - if type(self) is type(other): - # Note that we're assuming that the items are direct - # instances of the same Enumeration (i.e., no fancy - # subclassing), which is probably okay. - return cmp(self.index, other.index) - else: - return NotImplemented - -def enum(*values, **kwargs): - """Shorthand for creating a new Enumeration class. - - Call with enumeration values as a list, a space-delimited string, or - just an argument list. To give the class a name, pass it as the - `name` keyword argument. Otherwise, a name will be chosen for you. - - The following are all equivalent: - - enum('pinkie ring middle index thumb') - enum('pinkie', 'ring', 'middle', 'index', 'thumb') - enum(['pinkie', 'ring', 'middle', 'index', 'thumb']) +class IndexableEnumMeta(EnumMeta): """ + Metaclass for Enums that support indexing by value + """ + def __getitem__(obj, x): + if isinstance(x, int): + return obj._value2member_map_[x] + return super(IndexableEnumMeta, EnumMeta).__getitem__(obj, x) - if ('name' not in kwargs) or kwargs['name'] is None: - # Create a probably-unique name. It doesn't really have to be - # unique, but getting distinct names each time helps with - # identification in debugging. - name = 'Enumeration' + hex(random.randint(0,0xfffffff))[2:].upper() - else: - name = kwargs['name'] +class IndexableEnum(Enum): + """ + An Enum subclass that suports indexing by value. + """ + __metaclass__ = IndexableEnumMeta - if len(values) == 1: - # If there's only one value, we have a couple of alternate calling - # styles. - if isinstance(values[0], basestring) or hasattr(values[0], '__iter__'): - values = values[0] - - return type(name, (Enumerated,), {'values': values})