From 55c68536c2adcc75216e2e2f9c76f08d6fb4fb56 Mon Sep 17 00:00:00 2001 From: Dave Hayes Date: Wed, 13 Feb 2013 09:59:58 -0600 Subject: [PATCH 01/33] Fix removal of suffixes for values that have them --- beets/mediafile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beets/mediafile.py b/beets/mediafile.py index 71e429864..927cb389b 100644 --- a/beets/mediafile.py +++ b/beets/mediafile.py @@ -547,7 +547,7 @@ class MediaField(object): # Remove suffix. if style.suffix and isinstance(out, (str, unicode)): if out.endswith(style.suffix): - out = out[:len(style.suffix)] + out = out[:-len(style.suffix)] # MPEG-4 freeform frames are (should be?) encoded as UTF-8. if obj.type == 'mp4' and style.key.startswith('----:') and \ From c27907b043bf4480de18994a6f19ff90ac5ed251 Mon Sep 17 00:00:00 2001 From: wordofglass Date: Sat, 16 Apr 2016 14:10:54 +0200 Subject: [PATCH 02/33] enable fetchart to store the artworks origin in a flexible field; as of now untested --- beetsplug/fetchart.py | 36 +++++++++++++++++++++++------------- docs/plugins/fetchart.rst | 13 +++++++++++++ 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/beetsplug/fetchart.py b/beetsplug/fetchart.py index c00124623..5c005656a 100644 --- a/beetsplug/fetchart.py +++ b/beetsplug/fetchart.py @@ -660,9 +660,9 @@ class FetchArtPlugin(plugins.BeetsPlugin, RequestMixin): def __init__(self): super(FetchArtPlugin, self).__init__() - # Holds paths to downloaded images between fetching them and - # placing them in the filesystem. - self.art_paths = {} + # Holds candidates corresponding to downloaded images between + # fetching them and placing them in the filesystem. + self.art_candidates = {} self.config.add({ 'auto': True, @@ -675,7 +675,8 @@ class FetchArtPlugin(plugins.BeetsPlugin, RequestMixin): 'coverart', 'itunes', 'amazon', 'albumart'], 'google_key': None, 'google_engine': u'001442825323518660753:hrh5ch1gjzm', - 'fanarttv_key': None + 'fanarttv_key': None, + 'store_origin': False }) self.config['google_key'].redact = True self.config['fanarttv_key'].redact = True @@ -703,6 +704,7 @@ class FetchArtPlugin(plugins.BeetsPlugin, RequestMixin): cover_names = self.config['cover_names'].as_str_seq() self.cover_names = map(util.bytestring_path, cover_names) self.cautious = self.config['cautious'].get(bool) + self.store_origin = self.config['store_origin'].get(bool) self.src_removed = (config['import']['delete'].get(bool) or config['import']['move'].get(bool)) @@ -753,19 +755,28 @@ class FetchArtPlugin(plugins.BeetsPlugin, RequestMixin): candidate = self.art_for_album(task.album, task.paths, local) if candidate: - self.art_paths[task] = candidate.path + self.art_candidates[task] = candidate + + def _set_art(self, album, candidate, delete=False): + album.set_art(candidate.path, delete) + if self.store_origin: + # store the origin of the chosen artwork in a flexible field + self._log.debug( + u"Storing artorigin for {0.albumartist} - {0.album}", + album) + album.artorigin = candidate.source + album.store() # Synchronous; after music files are put in place. def assign_art(self, session, task): """Place the discovered art in the filesystem.""" - if task in self.art_paths: - path = self.art_paths.pop(task) + if task in self.art_candidates: + candidate = self.art_candidates.pop(task) + + self._set_art(task.album, candidate, not self.src_removed) - album = task.album - album.set_art(path, not self.src_removed) - album.store() if self.src_removed: - task.prune(path) + task.prune(candidate.path) # Manual album art fetching. def commands(self): @@ -842,8 +853,7 @@ class FetchArtPlugin(plugins.BeetsPlugin, RequestMixin): candidate = self.art_for_album(album, local_paths) if candidate: - album.set_art(candidate.path, False) - album.store() + self._set_art(album, candidate) message = ui.colorize('text_success', u'found album art') else: message = ui.colorize('text_error', u'no art found') diff --git a/docs/plugins/fetchart.rst b/docs/plugins/fetchart.rst index 591dbc4b4..72745e73a 100644 --- a/docs/plugins/fetchart.rst +++ b/docs/plugins/fetchart.rst @@ -62,6 +62,9 @@ file. The available options are: Default: The `beets custom search engine`_, which searches the entire web. **fanarttv_key**: The personal API key for requesting art from fanart.tv. See below. + **store_origin**: If enabled, fetchart store the artwork's source in a + flexible tag. See below for the rationale behind this. + Default: ``no``. Note: ``minwidth`` and ``enforce_ratio`` options require either `ImageMagick`_ or `Pillow`_. @@ -182,6 +185,16 @@ personal key will give you earlier access to new art. .. _on their blog: https://fanart.tv/2015/01/personal-api-keys/ +Storing the artwork's origin +---------------------------- + +Storing the current artwork's source gives the opportunity to selectively +search for new art. For example, if some albums have artwork placed manually in +their directories that should not be replaced by a forced album art fetch, you +could do + +``beet fetchart -f ^artorigin:filesystem`` + Embedding Album Art ------------------- From 740efc0a0e14fab57abf9d86e8b1b9cb0d488c8d Mon Sep 17 00:00:00 2001 From: wordofglass Date: Sat, 16 Apr 2016 21:32:04 +0200 Subject: [PATCH 03/33] small fixes; consistently use 'source' instead of 'origin' --- beetsplug/fetchart.py | 12 ++++++------ docs/plugins/fetchart.rst | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/beetsplug/fetchart.py b/beetsplug/fetchart.py index 5c005656a..291181db7 100644 --- a/beetsplug/fetchart.py +++ b/beetsplug/fetchart.py @@ -676,7 +676,7 @@ class FetchArtPlugin(plugins.BeetsPlugin, RequestMixin): 'google_key': None, 'google_engine': u'001442825323518660753:hrh5ch1gjzm', 'fanarttv_key': None, - 'store_origin': False + 'store_source': False, }) self.config['google_key'].redact = True self.config['fanarttv_key'].redact = True @@ -704,7 +704,7 @@ class FetchArtPlugin(plugins.BeetsPlugin, RequestMixin): cover_names = self.config['cover_names'].as_str_seq() self.cover_names = map(util.bytestring_path, cover_names) self.cautious = self.config['cautious'].get(bool) - self.store_origin = self.config['store_origin'].get(bool) + self.store_source = self.config['store_source'].get(bool) self.src_removed = (config['import']['delete'].get(bool) or config['import']['move'].get(bool)) @@ -759,12 +759,12 @@ class FetchArtPlugin(plugins.BeetsPlugin, RequestMixin): def _set_art(self, album, candidate, delete=False): album.set_art(candidate.path, delete) - if self.store_origin: - # store the origin of the chosen artwork in a flexible field + if self.store_source: + # store the source of the chosen artwork in a flexible field self._log.debug( - u"Storing artorigin for {0.albumartist} - {0.album}", + u"Storing art_source for {0.albumartist} - {0.album}", album) - album.artorigin = candidate.source + album.art_source = candidate.source album.store() # Synchronous; after music files are put in place. diff --git a/docs/plugins/fetchart.rst b/docs/plugins/fetchart.rst index 72745e73a..04ee5d699 100644 --- a/docs/plugins/fetchart.rst +++ b/docs/plugins/fetchart.rst @@ -62,8 +62,8 @@ file. The available options are: Default: The `beets custom search engine`_, which searches the entire web. **fanarttv_key**: The personal API key for requesting art from fanart.tv. See below. - **store_origin**: If enabled, fetchart store the artwork's source in a - flexible tag. See below for the rationale behind this. +- **store_source**: If enabled, fetchart store the artwork's source in a + flexible tag named ``art_source``. See below for the rationale behind this. Default: ``no``. Note: ``minwidth`` and ``enforce_ratio`` options require either `ImageMagick`_ @@ -185,7 +185,7 @@ personal key will give you earlier access to new art. .. _on their blog: https://fanart.tv/2015/01/personal-api-keys/ -Storing the artwork's origin +Storing the Artwork's Source ---------------------------- Storing the current artwork's source gives the opportunity to selectively @@ -193,7 +193,7 @@ search for new art. For example, if some albums have artwork placed manually in their directories that should not be replaced by a forced album art fetch, you could do -``beet fetchart -f ^artorigin:filesystem`` +``beet fetchart -f ^art_source:filesystem`` Embedding Album Art ------------------- From ec4595677064de4e264d3ff35757e94d1d891d8e Mon Sep 17 00:00:00 2001 From: wordofglass Date: Sat, 16 Apr 2016 21:36:46 +0200 Subject: [PATCH 04/33] update changelog --- docs/changelog.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index c9e8ed67b..721c9ab8a 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -18,6 +18,8 @@ New features: * :doc:`/plugins/fetchart`: The ``enforce_ratio`` option was enhanced and now allows specifying a certain deviation that a valid image may have from being exactly square. +* :doc:`/plugins/fetchart`: The plugin can now optionally save the artwork's + source in a flexible field; for a usecase see the documentation. * :doc:`/plugins/export`: A new plugin to export the data from queries to a json format. Thanks to :user:`GuilhermeHideki`. From 7a9c3e592b37fffa24b6f5a9fa952f4b2beae197 Mon Sep 17 00:00:00 2001 From: wordofglass Date: Sun, 17 Apr 2016 14:18:01 +0200 Subject: [PATCH 05/33] document values written to art_source --- docs/plugins/fetchart.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/plugins/fetchart.rst b/docs/plugins/fetchart.rst index 04ee5d699..f59761bbb 100644 --- a/docs/plugins/fetchart.rst +++ b/docs/plugins/fetchart.rst @@ -195,6 +195,12 @@ could do ``beet fetchart -f ^art_source:filesystem`` +The values written to ``art_source`` are: + +``'Cover Art Archive'``, ``'Amazon'``, ``'AlbumArt.org scraper'``, ``'Google Images'``, +``'fanart.tv'``, ``'iTunes Store'``, ``'Wikipedia (queried through DBpedia)'``, +``'Filesystem'`` + Embedding Album Art ------------------- From 5fbca32ea44cb0c4490b95ab516c52aa097682d9 Mon Sep 17 00:00:00 2001 From: wordofglass Date: Sun, 17 Apr 2016 20:15:12 +0200 Subject: [PATCH 06/33] reuse the 'sources' names from the configuration for art_source --- beetsplug/fetchart.py | 4 ++-- docs/plugins/fetchart.rst | 17 +++++++---------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/beetsplug/fetchart.py b/beetsplug/fetchart.py index 291181db7..db4d0034e 100644 --- a/beetsplug/fetchart.py +++ b/beetsplug/fetchart.py @@ -197,7 +197,7 @@ class ArtSource(RequestMixin): raise NotImplementedError() def _candidate(self, **kwargs): - return Candidate(source=self.NAME, log=self._log, **kwargs) + return Candidate(source=self, log=self._log, **kwargs) def fetch_image(self, candidate, extra): raise NotImplementedError() @@ -764,7 +764,7 @@ class FetchArtPlugin(plugins.BeetsPlugin, RequestMixin): self._log.debug( u"Storing art_source for {0.albumartist} - {0.album}", album) - album.art_source = candidate.source + album.art_source = SOURCE_NAMES[type(candidate.source)] album.store() # Synchronous; after music files are put in place. diff --git a/docs/plugins/fetchart.rst b/docs/plugins/fetchart.rst index f59761bbb..2812c7745 100644 --- a/docs/plugins/fetchart.rst +++ b/docs/plugins/fetchart.rst @@ -62,7 +62,7 @@ file. The available options are: Default: The `beets custom search engine`_, which searches the entire web. **fanarttv_key**: The personal API key for requesting art from fanart.tv. See below. -- **store_source**: If enabled, fetchart store the artwork's source in a +- **store_source**: If enabled, fetchart stores the artwork's source in a flexible tag named ``art_source``. See below for the rationale behind this. Default: ``no``. @@ -188,18 +188,15 @@ personal key will give you earlier access to new art. Storing the Artwork's Source ---------------------------- -Storing the current artwork's source gives the opportunity to selectively -search for new art. For example, if some albums have artwork placed manually in -their directories that should not be replaced by a forced album art fetch, you -could do +Storing the current artwork's source might be used to narrow down +``fetchart`` commands. For example, if some albums have artwork placed +manually in their directories that should not be replaced by a forced +album art fetch, you could do ``beet fetchart -f ^art_source:filesystem`` -The values written to ``art_source`` are: - -``'Cover Art Archive'``, ``'Amazon'``, ``'AlbumArt.org scraper'``, ``'Google Images'``, -``'fanart.tv'``, ``'iTunes Store'``, ``'Wikipedia (queried through DBpedia)'``, -``'Filesystem'`` +The values written to ``art_source`` are the same names used in the ``sources`` +configuration value. Embedding Album Art ------------------- From ac2f7fe7127150d2ce0f84890380a2aedd79bc16 Mon Sep 17 00:00:00 2001 From: wordofglass Date: Wed, 20 Apr 2016 12:59:18 +0200 Subject: [PATCH 07/33] Fix the fanarttv source failing when there were images found, but no cover art --- beetsplug/fetchart.py | 12 ++++++------ test/test_art.py | 25 +++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/beetsplug/fetchart.py b/beetsplug/fetchart.py index db4d0034e..797d774c4 100644 --- a/beetsplug/fetchart.py +++ b/beetsplug/fetchart.py @@ -395,12 +395,12 @@ class FanartTV(RemoteArtSource): return matches = [] - # can there be more than one releasegroupid per responce? - for mb_releasegroupid in data.get(u'albums', dict()): - if album.mb_releasegroupid == mb_releasegroupid: - # note: there might be more art referenced, e.g. cdart - matches.extend( - data[u'albums'][mb_releasegroupid][u'albumcover']) + # can there be more than one releasegroupid per response? + for mbid, art in data.get(u'albums', dict()).items(): + # there might be more art referenced, e.g. cdart, and an albumcover + # might not be present, even if the request was succesful + if album.mb_releasegroupid == mbid and u'albumcover' in art: + matches.extend(art[u'albumcover']) # can this actually occur? else: self._log.debug(u'fanart.tv: unexpected mb_releasegroupid in ' diff --git a/test/test_art.py b/test/test_art.py index 2e7db8439..3014707a8 100644 --- a/test/test_art.py +++ b/test/test_art.py @@ -315,6 +315,23 @@ class FanartTVTest(UseThePlugin): } } }""" + RESPONSE_NO_ART = u"""{ + "name": "artistname", + "mbid_id": "artistid", + "albums": { + "thereleasegroupid": { + "cdart": [ + { + "id": "123", + "url": "http://example.com/4.jpg", + "likes": "0", + "disc": "1", + "size": "1000" + } + ] + } + } + }""" RESPONSE_ERROR = u"""{ "status": "error", "error message": "the error message" @@ -355,6 +372,14 @@ class FanartTVTest(UseThePlugin): with self.assertRaises(StopIteration): next(self.source.get(album, self.extra)) + def test_fanarttv_only_other_images(self): + # The source used to fail when there were images present, but no cover + album = _common.Bag(mb_releasegroupid=u'thereleasegroupid') + self.mock_response(fetchart.FanartTV.API_ALBUMS + u'thereleasegroupid', + self.RESPONSE_NO_ART) + with self.assertRaises(StopIteration): + next(self.source.get(album, self.extra)) + @_common.slow_test() class ArtImporterTest(UseThePlugin): From bf1b06f0c7f28b18a147ab6f15d6b65e7156a5b7 Mon Sep 17 00:00:00 2001 From: Guilherme Danno Date: Fri, 22 Apr 2016 17:30:06 -0300 Subject: [PATCH 08/33] don't print entire lyrics during import --- beetsplug/lyrics.py | 1 - 1 file changed, 1 deletion(-) diff --git a/beetsplug/lyrics.py b/beetsplug/lyrics.py index 6a6bc7729..afda54851 100644 --- a/beetsplug/lyrics.py +++ b/beetsplug/lyrics.py @@ -692,7 +692,6 @@ class LyricsPlugin(plugins.BeetsPlugin): item.lyrics = lyrics if write: item.try_write() - print(lyrics) item.store() def get_lyrics(self, artist, title): From 172498fd33fca44b5089688491044a0b257c5256 Mon Sep 17 00:00:00 2001 From: Guilherme Danno Date: Fri, 22 Apr 2016 19:02:58 -0300 Subject: [PATCH 09/33] use _out_encoding() to let the user override the encoding (e.g. cp65001/utf-8 throws Exception) --- beets/ui/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index ac919d7d2..89e09c05e 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -193,7 +193,7 @@ def input_(prompt=None): except EOFError: raise UserError(u'stdin stream ended while input required') - return resp.decode(sys.stdin.encoding or 'utf8', 'ignore') + return resp.decode(_out_encoding(), 'ignore') def input_options(options, require=False, prompt=None, fallback_prompt=None, From f18fe05e150ef0d44dc01657b37bc0146dd65bcb Mon Sep 17 00:00:00 2001 From: Guilherme Danno Date: Fri, 22 Apr 2016 20:18:02 -0300 Subject: [PATCH 10/33] fix: use a _in_encoding(): instead _out_encoding(): --- beets/ui/__init__.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index 89e09c05e..9096a87da 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -71,6 +71,15 @@ class UserError(Exception): # Encoding utilities. +def _in_encoding(): + """Get the encoding to use for *inputting* strings to the console. + """ + try: + return sys.stdin.encoding or 'utf-8' + except LookupError: + # TODO: create user config + return 'utf-8' + def _out_encoding(): """Get the encoding to use for *outputting* strings to the console. @@ -193,7 +202,7 @@ def input_(prompt=None): except EOFError: raise UserError(u'stdin stream ended while input required') - return resp.decode(_out_encoding(), 'ignore') + return resp.decode(_in_encoding(), 'ignore') def input_options(options, require=False, prompt=None, fallback_prompt=None, From 271f7c8d1756050e95e916f6de9fbdc779f141b1 Mon Sep 17 00:00:00 2001 From: Guilherme Danno Date: Sat, 23 Apr 2016 13:59:25 -0300 Subject: [PATCH 11/33] new template path functions: %first{} and %ifdef{} (#1951) * New template functions: %first{} and %ifdef{} * Add documentation * Add to changelog --- beets/library.py | 29 +++++++++++++++++++++++++ docs/changelog.rst | 1 + docs/reference/pathformat.rst | 10 +++++++++ test/test_library.py | 40 +++++++++++++++++++++++++++++++++++ 4 files changed, 80 insertions(+) diff --git a/beets/library.py b/beets/library.py index f2c3875b0..ed1938671 100644 --- a/beets/library.py +++ b/beets/library.py @@ -1466,6 +1466,35 @@ class DefaultTemplateFunctions(object): self.lib._memotable[memokey] = res return res + @staticmethod + def tmpl_first(s, count=1, skip=0, sep=u'; ', join_str=u'; '): + """ Gets the item(s) from x to y in a string separated by something + and join then with something + + :param s: the string + :param count: The number of items included + :param skip: The number of items skipped + :param sep: the separator. Usually is '; ' (default) or '/ ' + :param join_str: the string which will join the items, default '; '. + """ + skip = int(skip) + count = skip + int(count) + return join_str.join(s.split(sep)[skip:count]) + + def tmpl_ifdef(self, field, trueval=u'', falseval=u''): + """ If field exists return trueval or the field (default) + otherwise, emit return falseval (if provided). + + :param field: The name of the field + :param trueval: The string if the condition is true + :param falseval: The string if the condition is false + :return: The string, based on condition + """ + if self.item.formatted().get(field): + return trueval if trueval else self.item.formatted().get(field) + else: + return falseval + # Get the name of tmpl_* functions in the above class. DefaultTemplateFunctions._func_names = \ diff --git a/docs/changelog.rst b/docs/changelog.rst index 721c9ab8a..4908219a7 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -22,6 +22,7 @@ New features: source in a flexible field; for a usecase see the documentation. * :doc:`/plugins/export`: A new plugin to export the data from queries to a json format. Thanks to :user:`GuilhermeHideki`. +* :doc:`/reference/pathformat`: new functions: %first{} and %ifdef{} .. _fanart.tv: https://fanart.tv/ diff --git a/docs/reference/pathformat.rst b/docs/reference/pathformat.rst index 8efe54cd2..b5d754bd4 100644 --- a/docs/reference/pathformat.rst +++ b/docs/reference/pathformat.rst @@ -76,6 +76,16 @@ These functions are built in to beets: * ``%time{date_time,format}``: Return the date and time in any format accepted by `strftime`_. For example, to get the year some music was added to your library, use ``%time{$added,%Y}``. +* ``%first{text}``: Returns the first item, separated by ``; ``. + You can use ``%first{text,count,skip}``, where ``count`` is the number of + items (default 1) and ``skip`` is number to skip (default 0). You can also use + ``%first{text,count,skip,sep,join}`` where ``sep`` is the separator, like + ``;`` or ``/`` and join is the text to concatenate the items. + For example, +* ``%ifdef{field}``, ``%ifdef{field,truetext}`` or + ``%ifdef{field,truetext,falsetext}``: If ``field`` exists, then return + ``truetext`` or ``field`` (default). Otherwise, returns ``falsetext``. + The ``field`` should be entered without ``$``. .. _unidecode module: http://pypi.python.org/pypi/Unidecode .. _strftime: http://docs.python.org/2/library/time.html#time.strftime diff --git a/test/test_library.py b/test/test_library.py index d57566f5f..4d6a51b93 100644 --- a/test/test_library.py +++ b/test/test_library.py @@ -617,6 +617,46 @@ class DestinationFunctionTest(_common.TestCase, PathFormattingMixin): self._setf(u'%foo{bar}') self._assert_dest('/base/%foo{bar}') + def test_if_def_field_return_self(self): + self.i.bar = 3 + self._setf(u'%ifdef{bar}') + self._assert_dest('/base/3') + + def test_if_def_field_not_defined(self): + self._setf(u' %ifdef{bar}/$artist') + self._assert_dest('/base/the artist') + + def test_if_def_field_not_defined_2(self): + self._setf(u'$artist/%ifdef{bar}') + self._assert_dest('/base/the artist') + + def test_if_def_true(self): + self._setf(u'%ifdef{artist,cool}') + self._assert_dest('/base/cool') + + def test_if_def_true_complete(self): + self.i.series = "Now" + self._setf(u'%ifdef{series,$series Series,Albums}/$album') + self._assert_dest('/base/Now Series/the album') + + def test_if_def_false_complete(self): + self._setf(u'%ifdef{plays,$plays,not_played}') + self._assert_dest('/base/not_played') + + def test_first(self): + self.i.genres = "Pop; Rock; Classical Crossover" + self._setf(u'%first{$genres}') + self._assert_dest('/base/Pop') + + def test_first_skip(self): + self.i.genres = "Pop; Rock; Classical Crossover" + self._setf(u'%first{$genres,1,2}') + self._assert_dest('/base/Classical Crossover') + + def test_first_different_sep(self): + self._setf(u'%first{Alice / Bob / Eve,2,0, / , & }') + self._assert_dest('/base/Alice & Bob') + class DisambiguationTest(_common.TestCase, PathFormattingMixin): def setUp(self): From 4a5b886944a08542dbcbfbb59f37ad1715c24e2b Mon Sep 17 00:00:00 2001 From: wordofglass Date: Sun, 24 Apr 2016 00:35:15 +0200 Subject: [PATCH 12/33] Fix two non-guarded import statements in the lyrics plugin These could make the import process crash with a traceback. --- beetsplug/lyrics.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/beetsplug/lyrics.py b/beetsplug/lyrics.py index afda54851..177bf7c1d 100644 --- a/beetsplug/lyrics.py +++ b/beetsplug/lyrics.py @@ -415,7 +415,12 @@ def scrape_lyrics_from_html(html): """Scrape lyrics from a URL. If no lyrics can be found, return None instead. """ - from bs4 import SoupStrainer, BeautifulSoup + try: + from bs4 import SoupStrainer, BeautifulSoup + except ImportError: + # TODO: refactor the plugin to get access to a logger here and log + # a warning + pass if not html: return None @@ -673,7 +678,12 @@ class LyricsPlugin(plugins.BeetsPlugin): if lyrics: self._log.info(u'fetched lyrics: {0}', item) if self.config['bing_client_secret'].get(): - from langdetect import detect + try: + from langdetect import detect + except ImportError: + self._log.warn(u'To use bing translations, you need to ' + u'install the langdetect module. See the ' + u'documentation for further details.') lang_from = detect(lyrics) if self.config['bing_lang_to'].get() != lang_from and ( From 607f41be430507dc5f7522c19b5ee95605e0995c Mon Sep 17 00:00:00 2001 From: wordofglass Date: Sun, 24 Apr 2016 00:42:31 +0200 Subject: [PATCH 13/33] Fix the previous fix... --- beetsplug/lyrics.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/beetsplug/lyrics.py b/beetsplug/lyrics.py index 177bf7c1d..de9e1b313 100644 --- a/beetsplug/lyrics.py +++ b/beetsplug/lyrics.py @@ -420,7 +420,7 @@ def scrape_lyrics_from_html(html): except ImportError: # TODO: refactor the plugin to get access to a logger here and log # a warning - pass + return None if not html: return None @@ -675,16 +675,19 @@ class LyricsPlugin(plugins.BeetsPlugin): lyrics = u"\n\n---\n\n".join([l for l in lyrics if l]) + has_langdetect = False + if self.config['bing_client_secret'].get(): + try: + from langdetect import detect + has_langdetect = True + except ImportError: + self._log.warn(u'To use bing translations, you need to ' + u'install the langdetect module. See the ' + u'documentation for further details.') + if lyrics: self._log.info(u'fetched lyrics: {0}', item) - if self.config['bing_client_secret'].get(): - try: - from langdetect import detect - except ImportError: - self._log.warn(u'To use bing translations, you need to ' - u'install the langdetect module. See the ' - u'documentation for further details.') - + if has_langdetect: lang_from = detect(lyrics) if self.config['bing_lang_to'].get() != lang_from and ( not self.config['bing_lang_from'] or ( From 9c835715a3a60bad7eccc88079635caec7b86d84 Mon Sep 17 00:00:00 2001 From: Fabrice Laporte Date: Sun, 24 Apr 2016 22:28:52 +0200 Subject: [PATCH 14/33] Update lyrics.rst --- docs/plugins/lyrics.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/plugins/lyrics.rst b/docs/plugins/lyrics.rst index b922b747f..b55811d0b 100644 --- a/docs/plugins/lyrics.rst +++ b/docs/plugins/lyrics.rst @@ -133,10 +133,11 @@ using `pip`_ by typing:: pip install langdetect You also need to register for a Microsoft Azure Marketplace free account and -to the `Microsoft Translator API`_. Follow the four steps process, specifically -at step 3 enter `beets`` as *Client ID* and copy/paste the generated -*Client secret*. into your ``bing_client_secret`` configuration, alongside -``bing_lang_to`` target `language code`_. +to the `Microsoft Translator API`_. +Follow the four steps process, specifically at step 3 enter `beets` as +*Client ID* and copy/paste the generated *Client secret* into your +``bing_client_secret`` configuration, alongside ``bing_lang_to`` target +`language code`_. .. _langdetect: https://pypi.python.org/pypi/langdetect .. _Microsoft Translator API: https://www.microsoft.com/en-us/translator/getstarted.aspx From d3af2d53755acb07ba05a2e1fe58174c9d9a146f Mon Sep 17 00:00:00 2001 From: Fabrice Laporte Date: Sun, 24 Apr 2016 22:29:10 +0200 Subject: [PATCH 15/33] Update lyrics.rst --- docs/plugins/lyrics.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/plugins/lyrics.rst b/docs/plugins/lyrics.rst index b55811d0b..c1d1d67cd 100644 --- a/docs/plugins/lyrics.rst +++ b/docs/plugins/lyrics.rst @@ -134,6 +134,7 @@ using `pip`_ by typing:: You also need to register for a Microsoft Azure Marketplace free account and to the `Microsoft Translator API`_. + Follow the four steps process, specifically at step 3 enter `beets` as *Client ID* and copy/paste the generated *Client secret* into your ``bing_client_secret`` configuration, alongside ``bing_lang_to`` target From 3c10be117605b48fa381389448fc9d1e548dece2 Mon Sep 17 00:00:00 2001 From: Fabrice Laporte Date: Sun, 24 Apr 2016 22:30:55 +0200 Subject: [PATCH 16/33] Update lyrics.rst --- docs/plugins/lyrics.rst | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/plugins/lyrics.rst b/docs/plugins/lyrics.rst index c1d1d67cd..8b88c7ef8 100644 --- a/docs/plugins/lyrics.rst +++ b/docs/plugins/lyrics.rst @@ -133,12 +133,10 @@ using `pip`_ by typing:: pip install langdetect You also need to register for a Microsoft Azure Marketplace free account and -to the `Microsoft Translator API`_. - -Follow the four steps process, specifically at step 3 enter `beets` as -*Client ID* and copy/paste the generated *Client secret* into your -``bing_client_secret`` configuration, alongside ``bing_lang_to`` target -`language code`_. +to the `Microsoft Translator API`_. Follow the four steps process, specifically +at step 3 enter ``beets`` as *Client ID* and copy/paste the generated +*Client secret* into your ``bing_client_secret`` configuration, alongside +``bing_lang_to`` target `language code`_. .. _langdetect: https://pypi.python.org/pypi/langdetect .. _Microsoft Translator API: https://www.microsoft.com/en-us/translator/getstarted.aspx From 2f1dd9451b9b189a6518ec775363fe9283245200 Mon Sep 17 00:00:00 2001 From: Dave Hayes Date: Mon, 25 Apr 2016 13:04:05 -0500 Subject: [PATCH 17/33] Add notes for installing the ZSH completion script --- docs/reference/cli.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/reference/cli.rst b/docs/reference/cli.rst index fcef6d23f..faae67ee6 100644 --- a/docs/reference/cli.rst +++ b/docs/reference/cli.rst @@ -435,7 +435,10 @@ later on you will want to re-generate the script. zsh ``` -If you use zsh, take a look at the included `completion script`_. +If you use zsh, take a look at the included `completion script`_. The script +should be placed in a directory that is part of your ``fpath``, and `not` +sourced in your ``.zshrc``. Running ``echo $fpath`` will give you a list of +valid directories. Another approach is to use zsh's bash completion compatibility. This snippet defines some bash-specific functions to make this work without errors:: From 1be9c3003ef297374abd9dfc6bee00811286570f Mon Sep 17 00:00:00 2001 From: Jack Wilsdon Date: Mon, 25 Apr 2016 19:14:30 +0100 Subject: [PATCH 18/33] Use different method to remove junk from LyricsWiki Use `_scrape_strip_cruft` instead of `scrape_lyrics_from_html` so that LyricsWiki does not depend on Beautiful Soup. --- beetsplug/lyrics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/lyrics.py b/beetsplug/lyrics.py index de9e1b313..70ce9c38b 100644 --- a/beetsplug/lyrics.py +++ b/beetsplug/lyrics.py @@ -335,7 +335,7 @@ class LyricsWiki(SymbolsReplaced): # Get the HTML fragment inside the appropriate HTML element and then # extract the text from it. html_frag = extract_text_in(unescape(html), u"
") - lyrics = scrape_lyrics_from_html(html_frag) + lyrics = _scrape_strip_cruft(html_frag, True) if lyrics and 'Unfortunately, we are not licensed' not in lyrics: return lyrics From c5e2334fb523052693669b71f49fb9443e74acbb Mon Sep 17 00:00:00 2001 From: Jack Wilsdon Date: Mon, 25 Apr 2016 19:24:26 +0100 Subject: [PATCH 19/33] Remove useless unescape Remove useless unescape as _scrape_script_cruft does it for us. --- beetsplug/lyrics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/lyrics.py b/beetsplug/lyrics.py index 70ce9c38b..afd536394 100644 --- a/beetsplug/lyrics.py +++ b/beetsplug/lyrics.py @@ -334,7 +334,7 @@ class LyricsWiki(SymbolsReplaced): # Get the HTML fragment inside the appropriate HTML element and then # extract the text from it. - html_frag = extract_text_in(unescape(html), u"
") + html_frag = extract_text_in(html, u"
") lyrics = _scrape_strip_cruft(html_frag, True) if lyrics and 'Unfortunately, we are not licensed' not in lyrics: From 0395e7fd8095a1c03d2b9d1c3c0576ea4b95ce6e Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Mon, 25 Apr 2016 19:49:46 -0700 Subject: [PATCH 20/33] Configure CodeCov Apparently, CodeCov switched to a new configuration scheme without telling us. :cry: This turned on annoying pull-request comments. I *hope* this turns them off again. --- codecov.yaml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 codecov.yaml diff --git a/codecov.yaml b/codecov.yaml new file mode 100644 index 000000000..d01e442e5 --- /dev/null +++ b/codecov.yaml @@ -0,0 +1,12 @@ +# Don't post a comment on pull requests. +comment: off + +# I think this disables commit statuses? +coverage: + status: + project: + enabled: no + patch: + enabled: no + changes: + enabled: no From 144c732ab0f9c9294da13bd5e49b2bd55611bd62 Mon Sep 17 00:00:00 2001 From: Jack Wilsdon Date: Tue, 26 Apr 2016 23:33:52 +0100 Subject: [PATCH 21/33] Add flake8 check for pep8 naming Add flake8 check for pep8 naming using pep8-naming. --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index cd29693a1..6eba8395d 100644 --- a/tox.ini +++ b/tox.ini @@ -48,4 +48,5 @@ commands = deps = flake8 flake8-future-import + pep8-naming commands = flake8 beets beetsplug beet test setup.py docs From 09bc2504133150ab19eda133565b9e513ac81544 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Tue, 26 Apr 2016 16:59:23 -0700 Subject: [PATCH 22/33] Fix #1960: Unicode in fetchart Wikipedia source The SparQL query needed to use a Unicode literal. --- beetsplug/fetchart.py | 2 +- docs/changelog.rst | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/beetsplug/fetchart.py b/beetsplug/fetchart.py index 797d774c4..a18e362e5 100644 --- a/beetsplug/fetchart.py +++ b/beetsplug/fetchart.py @@ -454,7 +454,7 @@ class Wikipedia(RemoteArtSource): NAME = u"Wikipedia (queried through DBpedia)" DBPEDIA_URL = 'http://dbpedia.org/sparql' WIKIPEDIA_URL = 'http://en.wikipedia.org/w/api.php' - SPARQL_QUERY = '''PREFIX rdf: + SPARQL_QUERY = u'''PREFIX rdf: PREFIX dbpprop: PREFIX owl: PREFIX rdfs: diff --git a/docs/changelog.rst b/docs/changelog.rst index 4908219a7..1844c4d87 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -41,6 +41,8 @@ Fixes: guess the URL for lyrics. :bug:`1880` * :doc:`/plugins/edit`: Fail gracefully when the configured text editor command can't be invoked. :bug:`1927` +* :doc:`/plugins/fetchart`: Fix a crash in the Wikipedia backend on non-ASCII + artist and album names. :bug:`1960` 1.3.17 (February 7, 2016) From 91c8a30e55b38b507a763c3750a7dea94c5c1147 Mon Sep 17 00:00:00 2001 From: Adrian Sampson Date: Tue, 26 Apr 2016 17:16:52 -0700 Subject: [PATCH 23/33] codecov.yaml -> codecov.yml :angry: :cry: --- codecov.yaml => codecov.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename codecov.yaml => codecov.yml (100%) diff --git a/codecov.yaml b/codecov.yml similarity index 100% rename from codecov.yaml rename to codecov.yml From b1c58e99ecbaa5fca5d2eb7dc98d2a213d44e7e3 Mon Sep 17 00:00:00 2001 From: Jack Wilsdon Date: Wed, 27 Apr 2016 20:15:10 +0100 Subject: [PATCH 24/33] Update code to match pep8 naming standards --- beets/autotag/hooks.py | 2 +- beets/dbcore/db.py | 8 ++++---- beets/dbcore/query.py | 8 ++++---- beets/logging.py | 2 +- beets/util/artresizer.py | 22 +++++++++++----------- beetsplug/bucket.py | 13 ++++++++----- beetsplug/export.py | 2 +- beetsplug/fuzzy.py | 6 +++--- test/_common.py | 4 ++-- test/test_art.py | 4 ++-- test/test_autotag.py | 14 +++++++------- test/test_convert.py | 4 ++-- test/test_datequery.py | 4 ++-- test/test_edit.py | 4 ++-- test/test_importadded.py | 12 ++++++------ test/test_importer.py | 6 +++--- test/test_lastgenre.py | 18 +++++++++--------- test/test_lyrics.py | 8 ++++---- test/test_mediafile.py | 6 +++--- test/test_mpdstats.py | 14 +++++++------- test/test_permissions.py | 12 ++++++------ test/test_query.py | 6 +++--- test/test_smartplaylist.py | 10 +++++----- test/test_ui.py | 16 ++++++++-------- 24 files changed, 104 insertions(+), 101 deletions(-) diff --git a/beets/autotag/hooks.py b/beets/autotag/hooks.py index 26b7f5819..4a89e6dc8 100644 --- a/beets/autotag/hooks.py +++ b/beets/autotag/hooks.py @@ -297,7 +297,7 @@ class Distance(object): self._penalties = {} @LazyClassProperty - def _weights(cls): + def _weights(self): """A dictionary from keys to floating-point weights. """ weights_view = config['match']['distance_weights'] diff --git a/beets/dbcore/db.py b/beets/dbcore/db.py index 09a96d8c2..ba67dd6d1 100644 --- a/beets/dbcore/db.py +++ b/beets/dbcore/db.py @@ -209,13 +209,13 @@ class Model(object): # Essential field accessors. @classmethod - def _type(self, key): + def _type(cls, key): """Get the type of a field, a `Type` instance. If the field has no explicit type, it is given the base `Type`, which does no conversion. """ - return self._fields.get(key) or self._types.get(key) or types.DEFAULT + return cls._fields.get(key) or cls._types.get(key) or types.DEFAULT def __getitem__(self, key): """Get the value for a field. Raise a KeyError if the field is @@ -274,11 +274,11 @@ class Model(object): return base_keys @classmethod - def all_keys(self): + def all_keys(cls): """Get a list of available keys for objects of this type. Includes fixed and computed fields. """ - return list(self._fields) + self._getters().keys() + return list(cls._fields) + cls._getters().keys() # Act like a dictionary. diff --git a/beets/dbcore/query.py b/beets/dbcore/query.py index 8e3b7ca16..ed20d320d 100644 --- a/beets/dbcore/query.py +++ b/beets/dbcore/query.py @@ -146,9 +146,9 @@ class NoneQuery(FieldQuery): return self.field + " IS NULL", () @classmethod - def match(self, item): + def match(cls, item): try: - return item[self.field] is None + return item[cls.field] is None except KeyError: return True @@ -841,8 +841,8 @@ class SlowFieldSort(FieldSort): class NullSort(Sort): """No sorting. Leave results unsorted.""" - def sort(items): - return items + def sort(self): + return self def __nonzero__(self): return self.__bool__() diff --git a/beets/logging.py b/beets/logging.py index e4e628d69..a94da1c62 100644 --- a/beets/logging.py +++ b/beets/logging.py @@ -126,7 +126,7 @@ my_manager = copy(Logger.manager) my_manager.loggerClass = BeetsLogger -def getLogger(name=None): +def getLogger(name=None): # noqa if name: return my_manager.getLogger(name) else: diff --git a/beets/util/artresizer.py b/beets/util/artresizer.py index 9d2ee5952..e94f8c380 100644 --- a/beets/util/artresizer.py +++ b/beets/util/artresizer.py @@ -149,15 +149,15 @@ class Shareable(type): lazily-created shared instance of ``MyClass`` while calling ``MyClass()`` to construct a new object works as usual. """ - def __init__(cls, name, bases, dict): - super(Shareable, cls).__init__(name, bases, dict) - cls._instance = None + def __init__(self, name, bases, dict): + super(Shareable, self).__init__(name, bases, dict) + self._instance = None @property - def shared(cls): - if cls._instance is None: - cls._instance = cls() - return cls._instance + def shared(self): + if self._instance is None: + self._instance = self() + return self._instance class ArtResizer(object): @@ -218,18 +218,18 @@ class ArtResizer(object): @staticmethod def _check_method(): """Return a tuple indicating an available method and its version.""" - version = has_IM() + version = get_image_magick_version() if version: return IMAGEMAGICK, version - version = has_PIL() + version = get_pil_version() if version: return PIL, version return WEBPROXY, (0) -def has_IM(): +def get_image_magick_version(): """Return Image Magick version or None if it is unavailable Try invoking ImageMagick's "convert".""" try: @@ -248,7 +248,7 @@ def has_IM(): return None -def has_PIL(): +def get_pil_version(): """Return Image Magick version or None if it is unavailable Try importing PIL.""" try: diff --git a/beetsplug/bucket.py b/beetsplug/bucket.py index cfd4106d4..21acb1f1e 100644 --- a/beetsplug/bucket.py +++ b/beetsplug/bucket.py @@ -26,6 +26,9 @@ from itertools import tee, izip from beets import plugins, ui +ASCII_DIGITS = string.digits + string.ascii_lowercase + + class BucketError(Exception): pass @@ -155,23 +158,23 @@ def build_alpha_spans(alpha_spans_str, alpha_regexs): [from...to] """ spans = [] - ASCII_DIGITS = string.digits + string.ascii_lowercase + for elem in alpha_spans_str: if elem in alpha_regexs: spans.append(re.compile(alpha_regexs[elem])) else: bucket = sorted([x for x in elem.lower() if x.isalnum()]) if bucket: - beginIdx = ASCII_DIGITS.index(bucket[0]) - endIdx = ASCII_DIGITS.index(bucket[-1]) + begin_index = ASCII_DIGITS.index(bucket[0]) + end_index = ASCII_DIGITS.index(bucket[-1]) else: raise ui.UserError(u"invalid range defined for alpha bucket " u"'%s': no alphanumeric character found" % elem) spans.append( re.compile( - "^[" + ASCII_DIGITS[beginIdx:endIdx + 1] + - ASCII_DIGITS[beginIdx:endIdx + 1].upper() + "]" + "^[" + ASCII_DIGITS[begin_index:end_index + 1] + + ASCII_DIGITS[begin_index:end_index + 1].upper() + "]" ) ) return spans diff --git a/beetsplug/export.py b/beetsplug/export.py index 93362550f..641b9fefc 100644 --- a/beetsplug/export.py +++ b/beetsplug/export.py @@ -119,7 +119,7 @@ class ExportFormat(object): """The output format type""" @classmethod - def factory(self, type, **kwargs): + def factory(cls, type, **kwargs): if type == "json": if kwargs['file_path']: return JsonFileFormat(**kwargs) diff --git a/beetsplug/fuzzy.py b/beetsplug/fuzzy.py index 4a631a887..3decdc602 100644 --- a/beetsplug/fuzzy.py +++ b/beetsplug/fuzzy.py @@ -26,13 +26,13 @@ import difflib class FuzzyQuery(StringFieldQuery): @classmethod - def string_match(self, pattern, val): + def string_match(cls, pattern, val): # smartcase if pattern.islower(): val = val.lower() - queryMatcher = difflib.SequenceMatcher(None, pattern, val) + query_matcher = difflib.SequenceMatcher(None, pattern, val) threshold = config['fuzzy']['threshold'].as_number() - return queryMatcher.quick_ratio() >= threshold + return query_matcher.quick_ratio() >= threshold class FuzzyPlugin(BeetsPlugin): diff --git a/test/_common.py b/test/_common.py index c64ff0b71..33928fb66 100644 --- a/test/_common.py +++ b/test/_common.py @@ -167,11 +167,11 @@ class TestCase(unittest.TestCase): beets.config.clear() beets.config._materialized = False - def assertExists(self, path): + def assertExists(self, path): # noqa self.assertTrue(os.path.exists(path), u'file does not exist: {!r}'.format(path)) - def assertNotExists(self, path): + def assertNotExists(self, path): # noqa self.assertFalse(os.path.exists(path), u'file exists: {!r}'.format((path))) diff --git a/test/test_art.py b/test/test_art.py index 3014707a8..44b3b9bd4 100644 --- a/test/test_art.py +++ b/test/test_art.py @@ -518,7 +518,7 @@ class ArtForAlbumTest(UseThePlugin): fetchart.FileSystem.get = self.old_fs_source_get super(ArtForAlbumTest, self).tearDown() - def _assertImageIsValidArt(self, image_file, should_exist): + def _assertImageIsValidArt(self, image_file, should_exist): # noqa self.assertExists(image_file) self.image_file = image_file @@ -531,7 +531,7 @@ class ArtForAlbumTest(UseThePlugin): else: self.assertIsNone(candidate) - def _assertImageResized(self, image_file, should_resize): + def _assertImageResized(self, image_file, should_resize): # noqa self.image_file = image_file with patch.object(ArtResizer.shared, 'resize') as mock_resize: self.plugin.art_for_album(self.album, [''], True) diff --git a/test/test_autotag.py b/test/test_autotag.py index 30d60f8c4..bdc3ee2d4 100644 --- a/test/test_autotag.py +++ b/test/test_autotag.py @@ -940,13 +940,13 @@ class EnumTest(_common.TestCase): Test Enum Subclasses defined in beets.util.enumeration """ def test_ordered_enum(self): - OrderedEnumTest = match.OrderedEnum('OrderedEnumTest', ['a', 'b', 'c']) - self.assertLess(OrderedEnumTest.a, OrderedEnumTest.b) - self.assertLess(OrderedEnumTest.a, OrderedEnumTest.c) - self.assertLess(OrderedEnumTest.b, OrderedEnumTest.c) - self.assertGreater(OrderedEnumTest.b, OrderedEnumTest.a) - self.assertGreater(OrderedEnumTest.c, OrderedEnumTest.a) - self.assertGreater(OrderedEnumTest.c, OrderedEnumTest.b) + ordered_enum = match.OrderedEnum('OrderedEnumTest', ['a', 'b', 'c']) + self.assertLess(ordered_enum.a, ordered_enum.b) + self.assertLess(ordered_enum.a, ordered_enum.c) + self.assertLess(ordered_enum.b, ordered_enum.c) + self.assertGreater(ordered_enum.b, ordered_enum.a) + self.assertGreater(ordered_enum.c, ordered_enum.a) + self.assertGreater(ordered_enum.c, ordered_enum.b) def suite(): diff --git a/test/test_convert.py b/test/test_convert.py index 20ed6c743..679b70a4f 100644 --- a/test/test_convert.py +++ b/test/test_convert.py @@ -40,7 +40,7 @@ class TestHelper(helper.TestHelper): return u'sh -c "cp \'$source\' \'$dest\'; ' \ u'printf {0} >> \'$dest\'"'.format(tag) - def assertFileTag(self, path, tag): + def assertFileTag(self, path, tag): # noqa """Assert that the path is a file and the files content ends with `tag`. """ self.assertTrue(os.path.isfile(path), @@ -50,7 +50,7 @@ class TestHelper(helper.TestHelper): self.assertEqual(f.read(), tag, u'{0} is not tagged with {1}'.format(path, tag)) - def assertNoFileTag(self, path, tag): + def assertNoFileTag(self, path, tag): # noqa """Assert that the path is a file and the files content does not end with `tag`. """ diff --git a/test/test_datequery.py b/test/test_datequery.py index 6c9818b77..b978d7304 100644 --- a/test/test_datequery.py +++ b/test/test_datequery.py @@ -62,14 +62,14 @@ class DateIntervalTest(unittest.TestCase): self.assertContains('..', date=datetime.min) self.assertContains('..', '1000-01-01T00:00:00') - def assertContains(self, interval_pattern, date_pattern=None, date=None): + def assertContains(self, interval_pattern, date_pattern=None, date=None): # noqa if date is None: date = _date(date_pattern) (start, end) = _parse_periods(interval_pattern) interval = DateInterval.from_periods(start, end) self.assertTrue(interval.contains(date)) - def assertExcludes(self, interval_pattern, date_pattern): + def assertExcludes(self, interval_pattern, date_pattern): # noqa date = _date(date_pattern) (start, end) = _parse_periods(interval_pattern) interval = DateInterval.from_periods(start, end) diff --git a/test/test_edit.py b/test/test_edit.py index ab0ae046a..94bb2d937 100644 --- a/test/test_edit.py +++ b/test/test_edit.py @@ -71,7 +71,7 @@ class ModifyFileMocker(object): class EditMixin(object): """Helper containing some common functionality used for the Edit tests.""" - def assertItemFieldsModified(self, library_items, items, fields=[], + def assertItemFieldsModified(self, library_items, items, fields=[], # noqa allowed=['path']): """Assert that items in the library (`lib_items`) have different values on the specified `fields` (and *only* on those fields), compared to @@ -133,7 +133,7 @@ class EditCommandTest(unittest.TestCase, TestHelper, EditMixin): self.teardown_beets() self.unload_plugins() - def assertCounts(self, album_count=ALBUM_COUNT, track_count=TRACK_COUNT, + def assertCounts(self, album_count=ALBUM_COUNT, track_count=TRACK_COUNT, # noqa write_call_count=TRACK_COUNT, title_starts_with=''): """Several common assertions on Album, Track and call counts.""" self.assertEqual(len(self.lib.albums()), album_count) diff --git a/test/test_importadded.py b/test/test_importadded.py index 9fa30f523..6e003ed0f 100644 --- a/test/test_importadded.py +++ b/test/test_importadded.py @@ -67,7 +67,7 @@ class ImportAddedTest(unittest.TestCase, ImportHelper): self.teardown_beets() self.matcher.restore() - def findMediaFile(self, item): + def find_media_file(self, item): """Find the pre-import MediaFile for an Item""" for m in self.media_files: if m.title.replace('Tag', 'Applied') == item.title: @@ -75,11 +75,11 @@ class ImportAddedTest(unittest.TestCase, ImportHelper): raise AssertionError(u"No MediaFile found for Item " + util.displayable_path(item.path)) - def assertEqualTimes(self, first, second, msg=None): + def assertEqualTimes(self, first, second, msg=None): # noqa """For comparing file modification times at a sufficient precision""" self.assertAlmostEqual(first, second, places=4, msg=msg) - def assertAlbumImport(self): + def assertAlbumImport(self): # noqa self.importer.run() album = self.lib.albums().get() self.assertEqual(album.added, self.min_mtime) @@ -102,7 +102,7 @@ class ImportAddedTest(unittest.TestCase, ImportHelper): self.assertEqual(album.added, self.min_mtime) for item in album.items(): self.assertEqualTimes(item.added, self.min_mtime) - mediafile_mtime = os.path.getmtime(self.findMediaFile(item).path) + mediafile_mtime = os.path.getmtime(self.find_media_file(item).path) self.assertEqualTimes(item.mtime, mediafile_mtime) self.assertEqualTimes(os.path.getmtime(item.path), mediafile_mtime) @@ -133,7 +133,7 @@ class ImportAddedTest(unittest.TestCase, ImportHelper): self.config['import']['singletons'] = True self.importer.run() for item in self.lib.items(): - mfile = self.findMediaFile(item) + mfile = self.find_media_file(item) self.assertEqualTimes(item.added, os.path.getmtime(mfile.path)) def test_import_singletons_with_preserved_mtimes(self): @@ -141,7 +141,7 @@ class ImportAddedTest(unittest.TestCase, ImportHelper): self.config['importadded']['preserve_mtimes'] = True self.importer.run() for item in self.lib.items(): - mediafile_mtime = os.path.getmtime(self.findMediaFile(item).path) + mediafile_mtime = os.path.getmtime(self.find_media_file(item).path) self.assertEqualTimes(item.added, mediafile_mtime) self.assertEqualTimes(item.mtime, mediafile_mtime) self.assertEqualTimes(os.path.getmtime(item.path), diff --git a/test/test_importer.py b/test/test_importer.py index d1ef6ef18..ff2020ef8 100644 --- a/test/test_importer.py +++ b/test/test_importer.py @@ -125,14 +125,14 @@ class AutotagStub(object): artist = artist.replace('Tag', 'Applied') + id album = album.replace('Tag', 'Applied') + id - trackInfos = [] + track_infos = [] for i in range(tracks - missing): - trackInfos.append(self._make_track_match(artist, album, i + 1)) + track_infos.append(self._make_track_match(artist, album, i + 1)) return AlbumInfo( artist=artist, album=album, - tracks=trackInfos, + tracks=track_infos, va=False, album_id=u'albumid' + id, artist_id=u'artistid' + id, diff --git a/test/test_lastgenre.py b/test/test_lastgenre.py index 82396f7c7..b397d2c21 100644 --- a/test/test_lastgenre.py +++ b/test/test_lastgenre.py @@ -167,16 +167,16 @@ class LastGenrePluginTest(unittest.TestCase, TestHelper): self.assertEqual(res, [u'pop']) def test_get_genre(self): - MOCK_GENRES = {'track': u'1', 'album': u'2', 'artist': u'3'} + mock_genres = {'track': u'1', 'album': u'2', 'artist': u'3'} def mock_fetch_track_genre(self, obj=None): - return MOCK_GENRES['track'] + return mock_genres['track'] def mock_fetch_album_genre(self, obj): - return MOCK_GENRES['album'] + return mock_genres['album'] def mock_fetch_artist_genre(self, obj): - return MOCK_GENRES['artist'] + return mock_genres['artist'] lastgenre.LastGenrePlugin.fetch_track_genre = mock_fetch_track_genre lastgenre.LastGenrePlugin.fetch_album_genre = mock_fetch_album_genre @@ -184,7 +184,7 @@ class LastGenrePluginTest(unittest.TestCase, TestHelper): self._setup_config(whitelist=False) item = _common.item() - item.genre = MOCK_GENRES['track'] + item.genre = mock_genres['track'] config['lastgenre'] = {'force': False} res = self.plugin._get_genre(item) @@ -192,17 +192,17 @@ class LastGenrePluginTest(unittest.TestCase, TestHelper): config['lastgenre'] = {'force': True, 'source': u'track'} res = self.plugin._get_genre(item) - self.assertEqual(res, (MOCK_GENRES['track'], u'track')) + self.assertEqual(res, (mock_genres['track'], u'track')) config['lastgenre'] = {'source': u'album'} res = self.plugin._get_genre(item) - self.assertEqual(res, (MOCK_GENRES['album'], u'album')) + self.assertEqual(res, (mock_genres['album'], u'album')) config['lastgenre'] = {'source': u'artist'} res = self.plugin._get_genre(item) - self.assertEqual(res, (MOCK_GENRES['artist'], u'artist')) + self.assertEqual(res, (mock_genres['artist'], u'artist')) - MOCK_GENRES['artist'] = None + mock_genres['artist'] = None res = self.plugin._get_genre(item) self.assertEqual(res, (item.genre, u'original')) diff --git a/test/test_lyrics.py b/test/test_lyrics.py index 2658232ae..13bffacdb 100644 --- a/test/test_lyrics.py +++ b/test/test_lyrics.py @@ -365,14 +365,14 @@ class LyricsGooglePluginTest(unittest.TestCase): not present in the title.""" s = self.source url = s['url'] + s['path'] - urlTitle = u'example.com | Beats song by John doe' + url_title = u'example.com | Beats song by John doe' # very small diffs (typo) are ok eg 'beats' vs 'beets' with same artist - self.assertEqual(google.is_page_candidate(url, urlTitle, s['title'], + self.assertEqual(google.is_page_candidate(url, url_title, s['title'], s['artist']), True, url) # reject different title - urlTitle = u'example.com | seets bong lyrics by John doe' - self.assertEqual(google.is_page_candidate(url, urlTitle, s['title'], + url_title = u'example.com | seets bong lyrics by John doe' + self.assertEqual(google.is_page_candidate(url, url_title, s['title'], s['artist']), False, url) def test_is_page_candidate_special_chars(self): diff --git a/test/test_mediafile.py b/test/test_mediafile.py index 71fd796bd..ad83573da 100644 --- a/test/test_mediafile.py +++ b/test/test_mediafile.py @@ -168,7 +168,7 @@ class ImageStructureTestMixin(ArtTestMixin): self.assertEqual(cover.desc, u'album cover') self.assertEqual(mediafile.art, cover.data) - def assertExtendedImageAttributes(self, image, **kwargs): + def assertExtendedImageAttributes(self, image, **kwargs): # noqa """Ignore extended image attributes in the base tests. """ pass @@ -177,7 +177,7 @@ class ImageStructureTestMixin(ArtTestMixin): class ExtendedImageStructureTestMixin(ImageStructureTestMixin): """Checks for additional attributes in the image structure.""" - def assertExtendedImageAttributes(self, image, desc=None, type=None): + def assertExtendedImageAttributes(self, image, desc=None, type=None): # noqa self.assertEqual(image.desc, desc) self.assertEqual(image.type, type) @@ -660,7 +660,7 @@ class ReadWriteTestBase(ArtTestMixin, GenreListTestMixin, self.assertIsNone(mediafile.date) self.assertIsNone(mediafile.year) - def assertTags(self, mediafile, tags): + def assertTags(self, mediafile, tags): # noqa errors = [] for key, value in tags.items(): try: diff --git a/test/test_mpdstats.py b/test/test_mpdstats.py index 0fa800dfd..4ad9d00c4 100644 --- a/test/test_mpdstats.py +++ b/test/test_mpdstats.py @@ -43,14 +43,14 @@ class MPDStatsTest(unittest.TestCase, TestHelper): self.assertFalse(mpdstats.update_rating(None, True)) def test_get_item(self): - ITEM_PATH = '/foo/bar.flac' - item = Item(title=u'title', path=ITEM_PATH, id=1) + item_path = '/foo/bar.flac' + item = Item(title=u'title', path=item_path, id=1) item.add(self.lib) log = Mock() mpdstats = MPDStats(self.lib, log) - self.assertEqual(str(mpdstats.get_item(ITEM_PATH)), str(item)) + self.assertEqual(str(mpdstats.get_item(item_path)), str(item)) self.assertIsNone(mpdstats.get_item('/some/non-existing/path')) self.assertIn(u'item not found:', log.info.call_args[0][0]) @@ -60,13 +60,13 @@ class MPDStatsTest(unittest.TestCase, TestHelper): {'state': u'play', 'songid': 1, 'time': u'0:1'}, {'state': u'stop'}] EVENTS = [["player"]] * (len(STATUSES) - 1) + [KeyboardInterrupt] - ITEM_PATH = '/foo/bar.flac' + item_path = '/foo/bar.flac' @patch("beetsplug.mpdstats.MPDClientWrapper", return_value=Mock(**{ "events.side_effect": EVENTS, "status.side_effect": STATUSES, - "playlist.return_value": {1: ITEM_PATH}})) - def test_run_MPDStats(self, mpd_mock): - item = Item(title=u'title', path=self.ITEM_PATH, id=1) + "playlist.return_value": {1: item_path}})) + def test_run_mpdstats(self, mpd_mock): + item = Item(title=u'title', path=self.item_path, id=1) item.add(self.lib) log = Mock() diff --git a/test/test_permissions.py b/test/test_permissions.py index 6dcf4d01c..a1b7ddd19 100644 --- a/test/test_permissions.py +++ b/test/test_permissions.py @@ -38,7 +38,7 @@ class PermissionsPluginTest(unittest.TestCase, TestHelper): def test_failing_to_set_permissions(self): self.do_thing(False) - def do_thing(self, expectSuccess): + def do_thing(self, expect_success): def get_stat(v): return os.stat( os.path.join(self.temp_dir, 'import', *v)).st_mode & 0o777 @@ -53,14 +53,14 @@ class PermissionsPluginTest(unittest.TestCase, TestHelper): self.importer.run() item = self.lib.items().get() - self.assertPerms(item.path, 'file', expectSuccess) + self.assertPerms(item.path, 'file', expect_success) for path in dirs_in_library(self.lib.directory, item.path): - self.assertPerms(path, 'dir', expectSuccess) + self.assertPerms(path, 'dir', expect_success) - def assertPerms(self, path, typ, expectSuccess): - for x in [(True, self.exp_perms[expectSuccess][typ], '!='), - (False, self.exp_perms[not expectSuccess][typ], '==')]: + def assertPerms(self, path, typ, expect_success): # noqa + for x in [(True, self.exp_perms[expect_success][typ], '!='), + (False, self.exp_perms[not expect_success][typ], '==')]: self.assertEqual(x[0], check_permissions(path, x[1]), msg=u'{} : {} {} {}'.format( path, oct(os.stat(path).st_mode), x[2], oct(x[1]))) diff --git a/test/test_query.py b/test/test_query.py index 7dd659313..5fb717c9c 100644 --- a/test/test_query.py +++ b/test/test_query.py @@ -35,11 +35,11 @@ from beets.library import Library, Item class TestHelper(helper.TestHelper): - def assertInResult(self, item, results): + def assertInResult(self, item, results): # noqa result_ids = map(lambda i: i.id, results) self.assertIn(item.id, result_ids) - def assertNotInResult(self, item, results): + def assertNotInResult(self, item, results): # noqa result_ids = map(lambda i: i.id, results) self.assertNotIn(item.id, result_ids) @@ -805,7 +805,7 @@ class NotQueryTest(DummyDataTestCase): - `test_type_xxx`: tests for the negation of a particular XxxQuery class. - `test_get_yyy`: tests on query strings (similar to `GetTest`) """ - def assertNegationProperties(self, q): + def assertNegationProperties(self, q): # noqa """Given a Query `q`, assert that: - q OR not(q) == all items - q AND not(q) == 0 diff --git a/test/test_smartplaylist.py b/test/test_smartplaylist.py index c30ba24d0..25f6727f0 100644 --- a/test/test_smartplaylist.py +++ b/test/test_smartplaylist.py @@ -88,15 +88,15 @@ class SmartPlaylistTest(unittest.TestCase): for name, (_, sort), _ in spl._unmatched_playlists) asseq = self.assertEqual # less cluttered code - S = FixedFieldSort # short cut since we're only dealing with this + sort = FixedFieldSort # short cut since we're only dealing with this asseq(sorts["no_sort"], NullSort()) - asseq(sorts["one_sort"], S(u'year')) + asseq(sorts["one_sort"], sort(u'year')) asseq(sorts["only_empty_sorts"], None) - asseq(sorts["one_non_empty_sort"], S(u'year')) + asseq(sorts["one_non_empty_sort"], sort(u'year')) asseq(sorts["multiple_sorts"], - MultipleSort([S('year'), S(u'genre', False)])) + MultipleSort([sort('year'), sort(u'genre', False)])) asseq(sorts["mixed"], - MultipleSort([S('year'), S(u'genre'), S(u'id', False)])) + MultipleSort([sort('year'), sort(u'genre'), sort(u'id', False)])) def test_matches(self): spl = SmartPlaylistPlugin() diff --git a/test/test_ui.py b/test/test_ui.py index fdc16aff9..4de5e9ed5 100644 --- a/test/test_ui.py +++ b/test/test_ui.py @@ -233,18 +233,18 @@ class ModifyTest(unittest.TestCase, TestHelper): def test_selective_modify(self): title = u"Tracktitle" album = u"album" - origArtist = u"composer" - newArtist = u"coverArtist" + original_artist = u"composer" + new_artist = u"coverArtist" for i in range(0, 10): self.add_item_fixture(title=u"{0}{1}".format(title, i), - artist=origArtist, + artist=original_artist, album=album) self.modify_inp('s\ny\ny\ny\nn\nn\ny\ny\ny\ny\nn', - title, u"artist={0}".format(newArtist)) - origItems = self.lib.items(u"artist:{0}".format(origArtist)) - newItems = self.lib.items(u"artist:{0}".format(newArtist)) - self.assertEqual(len(list(origItems)), 3) - self.assertEqual(len(list(newItems)), 7) + title, u"artist={0}".format(new_artist)) + original_items = self.lib.items(u"artist:{0}".format(original_artist)) + new_items = self.lib.items(u"artist:{0}".format(new_artist)) + self.assertEqual(len(list(original_items)), 3) + self.assertEqual(len(list(new_items)), 7) # Album Tests From 73924064edf92aa30bd43496d40c458dbcd4cfb5 Mon Sep 17 00:00:00 2001 From: Jack Wilsdon Date: Wed, 27 Apr 2016 20:22:39 +0100 Subject: [PATCH 25/33] Rename ImageMagick version getter --- beets/util/artresizer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/beets/util/artresizer.py b/beets/util/artresizer.py index e94f8c380..6970a7da1 100644 --- a/beets/util/artresizer.py +++ b/beets/util/artresizer.py @@ -218,7 +218,7 @@ class ArtResizer(object): @staticmethod def _check_method(): """Return a tuple indicating an available method and its version.""" - version = get_image_magick_version() + version = get_im_version() if version: return IMAGEMAGICK, version @@ -229,7 +229,7 @@ class ArtResizer(object): return WEBPROXY, (0) -def get_image_magick_version(): +def get_im_version(): """Return Image Magick version or None if it is unavailable Try invoking ImageMagick's "convert".""" try: From 8317a20bcd555e15f12a4f97897a31719bb19c57 Mon Sep 17 00:00:00 2001 From: Jack Wilsdon Date: Wed, 27 Apr 2016 20:30:58 +0100 Subject: [PATCH 26/33] Use correct methods from art resizer --- beetsplug/thumbnails.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beetsplug/thumbnails.py b/beetsplug/thumbnails.py index da4a4027b..3977c78ef 100644 --- a/beetsplug/thumbnails.py +++ b/beetsplug/thumbnails.py @@ -34,7 +34,7 @@ from xdg import BaseDirectory from beets.plugins import BeetsPlugin from beets.ui import Subcommand, decargs from beets import util -from beets.util.artresizer import ArtResizer, has_IM, has_PIL +from beets.util.artresizer import ArtResizer, get_im_version, get_pil_version BASE_DIR = os.path.join(BaseDirectory.xdg_cache_home, "thumbnails") From 79d602b2a01c60575c10153f8f4d24db5d053fb3 Mon Sep 17 00:00:00 2001 From: Jack Wilsdon Date: Wed, 27 Apr 2016 20:33:21 +0100 Subject: [PATCH 27/33] Use correct methods for ImageMagick and PIL info --- beetsplug/thumbnails.py | 4 ++-- test/test_thumbnails.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/beetsplug/thumbnails.py b/beetsplug/thumbnails.py index 3977c78ef..0e7fbc6e0 100644 --- a/beetsplug/thumbnails.py +++ b/beetsplug/thumbnails.py @@ -92,11 +92,11 @@ class ThumbnailsPlugin(BeetsPlugin): if not os.path.exists(dir): os.makedirs(dir) - if has_IM(): + if get_im_version(): self.write_metadata = write_metadata_im tool = "IM" else: - assert has_PIL() # since we're local + assert get_pil_version() # since we're local self.write_metadata = write_metadata_pil tool = "PIL" self._log.debug(u"using {0} to write metadata", tool) diff --git a/test/test_thumbnails.py b/test/test_thumbnails.py index 5dd9551b7..30c5bb234 100644 --- a/test/test_thumbnails.py +++ b/test/test_thumbnails.py @@ -66,8 +66,8 @@ class ThumbnailsTest(unittest.TestCase, TestHelper): @patch('beetsplug.thumbnails.os') @patch('beetsplug.thumbnails.ArtResizer') - @patch('beetsplug.thumbnails.has_IM') - @patch('beetsplug.thumbnails.has_PIL') + @patch('beetsplug.thumbnails.get_im_version') + @patch('beetsplug.thumbnails.get_pil_version') @patch('beetsplug.thumbnails.GioURI') def test_check_local_ok(self, mock_giouri, mock_pil, mock_im, mock_artresizer, mock_os): From 64e16b5004dce37aa7aefcc30c50d632cc13a089 Mon Sep 17 00:00:00 2001 From: Jack Wilsdon Date: Thu, 28 Apr 2016 00:59:45 +0100 Subject: [PATCH 28/33] Update confit to match pep8 naming standards --- beets/util/confit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beets/util/confit.py b/beets/util/confit.py index 0b29928c8..28847e760 100644 --- a/beets/util/confit.py +++ b/beets/util/confit.py @@ -136,7 +136,7 @@ class ConfigSource(dict): ) @classmethod - def of(self, value): + def of(cls, value): """Given either a dictionary or a `ConfigSource` object, return a `ConfigSource` object. This lets a function accept either type of object as an argument. From 2a9305fba5b9d114fdc90cbbe507ea11f940d92f Mon Sep 17 00:00:00 2001 From: Jack Wilsdon Date: Thu, 28 Apr 2016 02:26:14 +0100 Subject: [PATCH 29/33] Reorder shields and add new shields Reorder shields and add weekly PyPI downloads, GitHub issues and GitHub license shields. --- README.rst | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 1d865ec79..a0cd203a9 100644 --- a/README.rst +++ b/README.rst @@ -1,11 +1,21 @@ -.. image:: https://travis-ci.org/beetbox/beets.svg?branch=master - :target: https://travis-ci.org/beetbox/beets +.. image:: https://img.shields.io/github/license/beetbox/beets.svg + :target: https://github.com/beetbox/beets/blob/master/LICENSE + +.. image:: https://img.shields.io/github/issues/beetbox/beets.svg + :target: https://github.com/beetbox/beets/issues + +.. image:: http://img.shields.io/pypi/v/beets.svg + :target: https://pypi.python.org/pypi/beets + +.. image:: https://img.shields.io/pypi/dw/beets.svg + :target: https://pypi.python.org/pypi/beets#downloads .. image:: http://img.shields.io/codecov/c/github/beetbox/beets.svg :target: https://codecov.io/github/beetbox/beets -.. image:: http://img.shields.io/pypi/v/beets.svg - :target: https://pypi.python.org/pypi/beets +.. image:: https://travis-ci.org/beetbox/beets.svg?branch=master + :target: https://travis-ci.org/beetbox/beets + Beets is the media library management system for obsessive-compulsive music geeks. From 77b48bac7e63442f26f288942fd95a1f43c9f211 Mon Sep 17 00:00:00 2001 From: Jack Wilsdon Date: Thu, 28 Apr 2016 03:28:54 +0100 Subject: [PATCH 30/33] Disable pep8 name check and correct argument name --- beets/autotag/hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beets/autotag/hooks.py b/beets/autotag/hooks.py index 4a89e6dc8..5c8e0e2c5 100644 --- a/beets/autotag/hooks.py +++ b/beets/autotag/hooks.py @@ -297,7 +297,7 @@ class Distance(object): self._penalties = {} @LazyClassProperty - def _weights(self): + def _weights(cls): # noqa """A dictionary from keys to floating-point weights. """ weights_view = config['match']['distance_weights'] From 46179850c504d0798f897df8c96fb9523a3f1fbd Mon Sep 17 00:00:00 2001 From: Jack Wilsdon Date: Thu, 28 Apr 2016 03:30:27 +0100 Subject: [PATCH 31/33] Fix null sort broken by b1c58e9 --- beets/dbcore/query.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/beets/dbcore/query.py b/beets/dbcore/query.py index ed20d320d..d344f9fef 100644 --- a/beets/dbcore/query.py +++ b/beets/dbcore/query.py @@ -841,8 +841,8 @@ class SlowFieldSort(FieldSort): class NullSort(Sort): """No sorting. Leave results unsorted.""" - def sort(self): - return self + def sort(self, items): + return items def __nonzero__(self): return self.__bool__() From 730e1ef175bd0d64faf28b2ba0fcdb4bd645c286 Mon Sep 17 00:00:00 2001 From: Jack Wilsdon Date: Thu, 28 Apr 2016 03:34:25 +0100 Subject: [PATCH 32/33] Revert change made to ordered enum test --- test/test_autotag.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/test_autotag.py b/test/test_autotag.py index bdc3ee2d4..71fbdad73 100644 --- a/test/test_autotag.py +++ b/test/test_autotag.py @@ -940,13 +940,13 @@ class EnumTest(_common.TestCase): Test Enum Subclasses defined in beets.util.enumeration """ def test_ordered_enum(self): - ordered_enum = match.OrderedEnum('OrderedEnumTest', ['a', 'b', 'c']) - self.assertLess(ordered_enum.a, ordered_enum.b) - self.assertLess(ordered_enum.a, ordered_enum.c) - self.assertLess(ordered_enum.b, ordered_enum.c) - self.assertGreater(ordered_enum.b, ordered_enum.a) - self.assertGreater(ordered_enum.c, ordered_enum.a) - self.assertGreater(ordered_enum.c, ordered_enum.b) + OrderedEnumClass = match.OrderedEnum('OrderedEnumTest', ['a', 'b', 'c']) # noqa + self.assertLess(OrderedEnumClass.a, OrderedEnumClass.b) + self.assertLess(OrderedEnumClass.a, OrderedEnumClass.c) + self.assertLess(OrderedEnumClass.b, OrderedEnumClass.c) + self.assertGreater(OrderedEnumClass.b, OrderedEnumClass.a) + self.assertGreater(OrderedEnumClass.c, OrderedEnumClass.a) + self.assertGreater(OrderedEnumClass.c, OrderedEnumClass.b) def suite(): From 3166fc44fe2ba66b6dbec0ecfe7b4232f876ee4b Mon Sep 17 00:00:00 2001 From: Jack Wilsdon Date: Thu, 28 Apr 2016 03:41:23 +0100 Subject: [PATCH 33/33] Remove license and issue shields --- README.rst | 6 ------ 1 file changed, 6 deletions(-) diff --git a/README.rst b/README.rst index a0cd203a9..ad477beee 100644 --- a/README.rst +++ b/README.rst @@ -1,9 +1,3 @@ -.. image:: https://img.shields.io/github/license/beetbox/beets.svg - :target: https://github.com/beetbox/beets/blob/master/LICENSE - -.. image:: https://img.shields.io/github/issues/beetbox/beets.svg - :target: https://github.com/beetbox/beets/issues - .. image:: http://img.shields.io/pypi/v/beets.svg :target: https://pypi.python.org/pypi/beets