From e26276658052947e9464d9726b703335304c7c13 Mon Sep 17 00:00:00 2001 From: Andrew Rogl Date: Wed, 25 Aug 2021 15:27:16 +1000 Subject: [PATCH 01/26] pyupgrade root --- beet | 2 -- setup.cfg | 14 +++++++------- setup.py | 2 -- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/beet b/beet index 21fa085b9..abf55ec27 100755 --- a/beet +++ b/beet @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. @@ -15,7 +14,6 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -from __future__ import division, absolute_import, print_function import beets.ui diff --git a/setup.cfg b/setup.cfg index 7bcd41620..778341a9b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [flake8] -min-version = 2.7 +min-version = 3.6 accept-encodings = utf-8 docstring-convention = google # errors we ignore; see https://www.flake8rules.com/ for more info @@ -20,12 +20,12 @@ ignore = # mccabe error C901, # function is too complex # future-import errors - FI12, # `__future__` import "with_statement" missing - FI14, # `__future__` import "unicode_literals" missing - FI15, # `__future__` import "generator_stop" missing - FI50, # `__future__` import "division" present - FI51, # `__future__` import "absolute_import" present - FI53, # `__future__` import "print_function" present + # FI12, # `__future__` import "with_statement" missing + # FI14, # `__future__` import "unicode_literals" missing + # FI15, # `__future__` import "generator_stop" missing + # FI50, # `__future__` import "division" present + # FI51, # `__future__` import "absolute_import" present + # FI53, # `__future__` import "print_function" present N818, # Exception subclasses should be named with an Error suffix per-file-ignores = ./beet:D diff --git a/setup.py b/setup.py index 88d88874f..1f10b345f 100755 --- a/setup.py +++ b/setup.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. @@ -15,7 +14,6 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -from __future__ import division, absolute_import, print_function import os import sys From 6d1316f463cb7c9390f85bf35b220e250a35004a Mon Sep 17 00:00:00 2001 From: Andrew Rogl Date: Wed, 25 Aug 2021 16:05:28 +1000 Subject: [PATCH 02/26] pyupgrade beets dir --- beets/__init__.py | 8 +-- beets/__main__.py | 2 - beets/art.py | 42 +++++++------- beets/importer.py | 142 ++++++++++++++++++++++----------------------- beets/library.py | 126 +++++++++++++++++++--------------------- beets/logging.py | 14 ++--- beets/mediafile.py | 2 - beets/plugins.py | 49 +++++++--------- beets/random.py | 2 - beets/vfs.py | 2 - 10 files changed, 179 insertions(+), 210 deletions(-) diff --git a/beets/__init__.py b/beets/__init__.py index 15e5f1395..285417773 100644 --- a/beets/__init__.py +++ b/beets/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -13,13 +12,12 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -from __future__ import division, absolute_import, print_function import confuse from sys import stderr -__version__ = u'1.5.1' -__author__ = u'Adrian Sampson ' +__version__ = '1.5.1' +__author__ = 'Adrian Sampson ' class IncludeLazyConfig(confuse.LazyConfig): @@ -27,7 +25,7 @@ class IncludeLazyConfig(confuse.LazyConfig): YAML files specified in an `include` setting. """ def read(self, user=True, defaults=True): - super(IncludeLazyConfig, self).read(user, defaults) + super().read(user, defaults) try: for view in self['include']: diff --git a/beets/__main__.py b/beets/__main__.py index 8010ca0dd..ac829de9f 100644 --- a/beets/__main__.py +++ b/beets/__main__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2017, Adrian Sampson. # @@ -17,7 +16,6 @@ `python -m beets`. """ -from __future__ import division, absolute_import, print_function import sys from .ui import main diff --git a/beets/art.py b/beets/art.py index 20b0e96d2..13d5dfbd4 100644 --- a/beets/art.py +++ b/beets/art.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -17,7 +16,6 @@ music and items' embedded album art. """ -from __future__ import division, absolute_import, print_function import subprocess import platform @@ -43,7 +41,7 @@ def get_art(log, item): try: mf = mediafile.MediaFile(syspath(item.path)) except mediafile.UnreadableFileError as exc: - log.warning(u'Could not extract art from {0}: {1}', + log.warning('Could not extract art from {0}: {1}', displayable_path(item.path), exc) return @@ -58,20 +56,20 @@ def embed_item(log, item, imagepath, maxwidth=None, itempath=None, # Conditions and filters. if compare_threshold: if not check_art_similarity(log, item, imagepath, compare_threshold): - log.info(u'Image not similar; skipping.') + log.info('Image not similar; skipping.') return if ifempty and get_art(log, item): - log.info(u'media file already contained art') + log.info('media file already contained art') return if maxwidth and not as_album: imagepath = resize_image(log, imagepath, maxwidth, quality) # Get the `Image` object from the file. try: - log.debug(u'embedding {0}', displayable_path(imagepath)) + log.debug('embedding {0}', displayable_path(imagepath)) image = mediafile_image(imagepath, maxwidth) - except IOError as exc: - log.warning(u'could not read image file: {0}', exc) + except OSError as exc: + log.warning('could not read image file: {0}', exc) return # Make sure the image kind is safe (some formats only support PNG @@ -90,16 +88,16 @@ def embed_album(log, album, maxwidth=None, quiet=False, compare_threshold=0, """ imagepath = album.artpath if not imagepath: - log.info(u'No album art present for {0}', album) + log.info('No album art present for {0}', album) return if not os.path.isfile(syspath(imagepath)): - log.info(u'Album art not found at {0} for {1}', + log.info('Album art not found at {0} for {1}', displayable_path(imagepath), album) return if maxwidth: imagepath = resize_image(log, imagepath, maxwidth, quality) - log.info(u'Embedding album art into {0}', album) + log.info('Embedding album art into {0}', album) for item in album.items(): embed_item(log, item, imagepath, maxwidth, None, compare_threshold, @@ -110,7 +108,7 @@ def resize_image(log, imagepath, maxwidth, quality): """Returns path to an image resized to maxwidth and encoded with the specified quality level. """ - log.debug(u'Resizing album art to {0} pixels wide and encoding at quality \ + log.debug('Resizing album art to {0} pixels wide and encoding at quality \ level {1}', maxwidth, quality) imagepath = ArtResizer.shared.resize(maxwidth, syspath(imagepath), quality=quality) @@ -135,7 +133,7 @@ def check_art_similarity(log, item, imagepath, compare_threshold): syspath(art, prefix=False), '-colorspace', 'gray', 'MIFF:-'] compare_cmd = ['compare', '-metric', 'PHASH', '-', 'null:'] - log.debug(u'comparing images with pipeline {} | {}', + log.debug('comparing images with pipeline {} | {}', convert_cmd, compare_cmd) convert_proc = subprocess.Popen( convert_cmd, @@ -159,7 +157,7 @@ def check_art_similarity(log, item, imagepath, compare_threshold): convert_proc.wait() if convert_proc.returncode: log.debug( - u'ImageMagick convert failed with status {}: {!r}', + 'ImageMagick convert failed with status {}: {!r}', convert_proc.returncode, convert_stderr, ) @@ -169,7 +167,7 @@ def check_art_similarity(log, item, imagepath, compare_threshold): stdout, stderr = compare_proc.communicate() if compare_proc.returncode: if compare_proc.returncode != 1: - log.debug(u'ImageMagick compare failed: {0}, {1}', + log.debug('ImageMagick compare failed: {0}, {1}', displayable_path(imagepath), displayable_path(art)) return @@ -180,10 +178,10 @@ def check_art_similarity(log, item, imagepath, compare_threshold): try: phash_diff = float(out_str) except ValueError: - log.debug(u'IM output is not a number: {0!r}', out_str) + log.debug('IM output is not a number: {0!r}', out_str) return - log.debug(u'ImageMagick compare score: {0}', phash_diff) + log.debug('ImageMagick compare score: {0}', phash_diff) return phash_diff <= compare_threshold return True @@ -193,18 +191,18 @@ def extract(log, outpath, item): art = get_art(log, item) outpath = bytestring_path(outpath) if not art: - log.info(u'No album art present in {0}, skipping.', item) + log.info('No album art present in {0}, skipping.', item) return # Add an extension to the filename. ext = mediafile.image_extension(art) if not ext: - log.warning(u'Unknown image type in {0}.', + log.warning('Unknown image type in {0}.', displayable_path(item.path)) return outpath += bytestring_path('.' + ext) - log.info(u'Extracting album art from: {0} to: {1}', + log.info('Extracting album art from: {0} to: {1}', item, displayable_path(outpath)) with open(syspath(outpath), 'wb') as f: f.write(art) @@ -220,7 +218,7 @@ def extract_first(log, outpath, items): def clear(log, lib, query): items = lib.items(query) - log.info(u'Clearing album art from {0} items', len(items)) + log.info('Clearing album art from {0} items', len(items)) for item in items: - log.debug(u'Clearing art for {0}', item) + log.debug('Clearing art for {0}', item) item.try_write(tags={'images': None}) diff --git a/beets/importer.py b/beets/importer.py index 3e288c271..cccf035c0 100644 --- a/beets/importer.py +++ b/beets/importer.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -13,7 +12,6 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -from __future__ import division, absolute_import, print_function """Provides the basic, interface-agnostic workflow for importing and autotagging music files. @@ -75,7 +73,7 @@ def _open_state(): # unpickling, including ImportError. We use a catch-all # exception to avoid enumerating them all (the docs don't even have a # full list!). - log.debug(u'state file could not be read: {0}', exc) + log.debug('state file could not be read: {0}', exc) return {} @@ -84,8 +82,8 @@ def _save_state(state): try: with open(config['statefile'].as_filename(), 'wb') as f: pickle.dump(state, f) - except IOError as exc: - log.error(u'state file could not be written: {0}', exc) + except OSError as exc: + log.error('state file could not be written: {0}', exc) # Utilities for reading and writing the beets progress file, which @@ -174,7 +172,7 @@ def history_get(): # Abstract session class. -class ImportSession(object): +class ImportSession: """Controls an import action. Subclasses should implement methods to communicate with the user or otherwise make decisions. """ @@ -258,7 +256,7 @@ class ImportSession(object): """Log a message about a given album to the importer log. The status should reflect the reason the album couldn't be tagged. """ - self.logger.info(u'{0} {1}', status, displayable_path(paths)) + self.logger.info('{0} {1}', status, displayable_path(paths)) def log_choice(self, task, duplicate=False): """Logs the task's current choice if it should be logged. If @@ -269,17 +267,17 @@ class ImportSession(object): if duplicate: # Duplicate: log all three choices (skip, keep both, and trump). if task.should_remove_duplicates: - self.tag_log(u'duplicate-replace', paths) + self.tag_log('duplicate-replace', paths) elif task.choice_flag in (action.ASIS, action.APPLY): - self.tag_log(u'duplicate-keep', paths) + self.tag_log('duplicate-keep', paths) elif task.choice_flag is (action.SKIP): - self.tag_log(u'duplicate-skip', paths) + self.tag_log('duplicate-skip', paths) else: # Non-duplicate: log "skip" and "asis" choices. if task.choice_flag is action.ASIS: - self.tag_log(u'asis', paths) + self.tag_log('asis', paths) elif task.choice_flag is action.SKIP: - self.tag_log(u'skip', paths) + self.tag_log('skip', paths) def should_resume(self, path): raise NotImplementedError @@ -296,7 +294,7 @@ class ImportSession(object): def run(self): """Run the import task. """ - self.logger.info(u'import started {0}', time.asctime()) + self.logger.info('import started {0}', time.asctime()) self.set_config(config['import']) # Set up the pipeline. @@ -380,8 +378,8 @@ class ImportSession(object): """Mark paths and directories as merged for future reimport tasks. """ self._merged_items.update(paths) - dirs = set([os.path.dirname(path) if os.path.isfile(path) else path - for path in paths]) + dirs = {os.path.dirname(path) if os.path.isfile(path) else path + for path in paths} self._merged_dirs.update(dirs) def is_resuming(self, toppath): @@ -401,7 +399,7 @@ class ImportSession(object): # Either accept immediately or prompt for input to decide. if self.want_resume is True or \ self.should_resume(toppath): - log.warning(u'Resuming interrupted import of {0}', + log.warning('Resuming interrupted import of {0}', util.displayable_path(toppath)) self._is_resuming[toppath] = True else: @@ -411,7 +409,7 @@ class ImportSession(object): # The importer task class. -class BaseImportTask(object): +class BaseImportTask: """An abstract base class for importer tasks. Tasks flow through the importer pipeline. Each stage can update @@ -470,7 +468,7 @@ class ImportTask(BaseImportTask): system. """ def __init__(self, toppath, paths, items): - super(ImportTask, self).__init__(toppath, paths, items) + super().__init__(toppath, paths, items) self.choice_flag = None self.cur_album = None self.cur_artist = None @@ -562,11 +560,11 @@ class ImportTask(BaseImportTask): def remove_duplicates(self, lib): duplicate_items = self.duplicate_items(lib) - log.debug(u'removing {0} old duplicated items', len(duplicate_items)) + log.debug('removing {0} old duplicated items', len(duplicate_items)) for item in duplicate_items: item.remove() if lib.directory in util.ancestry(item.path): - log.debug(u'deleting duplicate {0}', + log.debug('deleting duplicate {0}', util.displayable_path(item.path)) util.remove(item.path) util.prune_dirs(os.path.dirname(item.path), @@ -579,7 +577,7 @@ class ImportTask(BaseImportTask): items = self.imported_items() for field, view in config['import']['set_fields'].items(): value = view.get() - log.debug(u'Set field {1}={2} for {0}', + log.debug('Set field {1}={2} for {0}', displayable_path(self.paths), field, value) @@ -673,7 +671,7 @@ class ImportTask(BaseImportTask): return [] duplicates = [] - task_paths = set(i.path for i in self.items if i) + task_paths = {i.path for i in self.items if i} duplicate_query = dbcore.AndQuery(( dbcore.MatchQuery('albumartist', artist), dbcore.MatchQuery('album', album), @@ -683,7 +681,7 @@ class ImportTask(BaseImportTask): # Check whether the album paths are all present in the task # i.e. album is being completely re-imported by the task, # in which case it is not a duplicate (will be replaced). - album_paths = set(i.path for i in album.items()) + album_paths = {i.path for i in album.items()} if not (album_paths <= task_paths): duplicates.append(album) return duplicates @@ -809,8 +807,8 @@ class ImportTask(BaseImportTask): self.album.artpath = replaced_album.artpath self.album.store() log.debug( - u'Reimported album: added {0}, flexible ' - u'attributes {1} from album {2} for {3}', + 'Reimported album: added {0}, flexible ' + 'attributes {1} from album {2} for {3}', self.album.added, replaced_album._values_flex.keys(), replaced_album.id, @@ -823,16 +821,16 @@ class ImportTask(BaseImportTask): if dup_item.added and dup_item.added != item.added: item.added = dup_item.added log.debug( - u'Reimported item added {0} ' - u'from item {1} for {2}', + 'Reimported item added {0} ' + 'from item {1} for {2}', item.added, dup_item.id, displayable_path(item.path) ) item.update(dup_item._values_flex) log.debug( - u'Reimported item flexible attributes {0} ' - u'from item {1} for {2}', + 'Reimported item flexible attributes {0} ' + 'from item {1} for {2}', dup_item._values_flex.keys(), dup_item.id, displayable_path(item.path) @@ -845,10 +843,10 @@ class ImportTask(BaseImportTask): """ for item in self.imported_items(): for dup_item in self.replaced_items[item]: - log.debug(u'Replacing item {0}: {1}', + log.debug('Replacing item {0}: {1}', dup_item.id, displayable_path(item.path)) dup_item.remove() - log.debug(u'{0} of {1} items replaced', + log.debug('{0} of {1} items replaced', sum(bool(l) for l in self.replaced_items.values()), len(self.imported_items())) @@ -886,7 +884,7 @@ class SingletonImportTask(ImportTask): """ def __init__(self, toppath, item): - super(SingletonImportTask, self).__init__(toppath, [item.path], [item]) + super().__init__(toppath, [item.path], [item]) self.item = item self.is_album = False self.paths = [item.path] @@ -958,7 +956,7 @@ class SingletonImportTask(ImportTask): """ for field, view in config['import']['set_fields'].items(): value = view.get() - log.debug(u'Set field {1}={2} for {0}', + log.debug('Set field {1}={2} for {0}', displayable_path(self.paths), field, value) @@ -979,7 +977,7 @@ class SentinelImportTask(ImportTask): """ def __init__(self, toppath, paths): - super(SentinelImportTask, self).__init__(toppath, paths, ()) + super().__init__(toppath, paths, ()) # TODO Remove the remaining attributes eventually self.should_remove_duplicates = False self.is_album = True @@ -1023,7 +1021,7 @@ class ArchiveImportTask(SentinelImportTask): """ def __init__(self, toppath): - super(ArchiveImportTask, self).__init__(toppath, ()) + super().__init__(toppath, ()) self.extracted = False @classmethod @@ -1073,7 +1071,7 @@ class ArchiveImportTask(SentinelImportTask): """Removes the temporary directory the archive was extracted to. """ if self.extracted: - log.debug(u'Removing extracted directory: {0}', + log.debug('Removing extracted directory: {0}', displayable_path(self.toppath)) shutil.rmtree(self.toppath) @@ -1095,7 +1093,7 @@ class ArchiveImportTask(SentinelImportTask): self.toppath = extract_to -class ImportTaskFactory(object): +class ImportTaskFactory: """Generate album and singleton import tasks for all media files indicated by a path. """ @@ -1136,14 +1134,12 @@ class ImportTaskFactory(object): if self.session.config['singletons']: for path in paths: tasks = self._create(self.singleton(path)) - for task in tasks: - yield task + yield from tasks yield self.sentinel(dirs) else: tasks = self._create(self.album(paths, dirs)) - for task in tasks: - yield task + yield from tasks # Produce the final sentinel for this toppath to indicate that # it is finished. This is usually just a SentinelImportTask, but @@ -1191,7 +1187,7 @@ class ImportTaskFactory(object): """Return a `SingletonImportTask` for the music file. """ if self.session.already_imported(self.toppath, [path]): - log.debug(u'Skipping previously-imported path: {0}', + log.debug('Skipping previously-imported path: {0}', displayable_path(path)) self.skipped += 1 return None @@ -1212,10 +1208,10 @@ class ImportTaskFactory(object): return None if dirs is None: - dirs = list(set(os.path.dirname(p) for p in paths)) + dirs = list({os.path.dirname(p) for p in paths}) if self.session.already_imported(self.toppath, dirs): - log.debug(u'Skipping previously-imported path: {0}', + log.debug('Skipping previously-imported path: {0}', displayable_path(dirs)) self.skipped += 1 return None @@ -1245,22 +1241,22 @@ class ImportTaskFactory(object): if not (self.session.config['move'] or self.session.config['copy']): - log.warning(u"Archive importing requires either " - u"'copy' or 'move' to be enabled.") + log.warning("Archive importing requires either " + "'copy' or 'move' to be enabled.") return - log.debug(u'Extracting archive: {0}', + log.debug('Extracting archive: {0}', displayable_path(self.toppath)) archive_task = ArchiveImportTask(self.toppath) try: archive_task.extract() except Exception as exc: - log.error(u'extraction failed: {0}', exc) + log.error('extraction failed: {0}', exc) return # Now read albums from the extracted directory. self.toppath = archive_task.toppath - log.debug(u'Archive extracted to: {0}', self.toppath) + log.debug('Archive extracted to: {0}', self.toppath) return archive_task def read_item(self, path): @@ -1276,9 +1272,9 @@ class ImportTaskFactory(object): # Silently ignore non-music files. pass elif isinstance(exc.reason, mediafile.UnreadableFileError): - log.warning(u'unreadable file: {0}', displayable_path(path)) + log.warning('unreadable file: {0}', displayable_path(path)) else: - log.error(u'error reading {0}: {1}', + log.error('error reading {0}: {1}', displayable_path(path), exc) @@ -1317,17 +1313,16 @@ def read_tasks(session): # Generate tasks. task_factory = ImportTaskFactory(toppath, session) - for t in task_factory.tasks(): - yield t + yield from task_factory.tasks() skipped += task_factory.skipped if not task_factory.imported: - log.warning(u'No files imported from {0}', + log.warning('No files imported from {0}', displayable_path(toppath)) # Show skipped directories (due to incremental/resume). if skipped: - log.info(u'Skipped {0} paths.', skipped) + log.info('Skipped {0} paths.', skipped) def query_tasks(session): @@ -1345,7 +1340,7 @@ def query_tasks(session): else: # Search for albums. for album in session.lib.albums(session.query): - log.debug(u'yielding album {0}: {1} - {2}', + log.debug('yielding album {0}: {1} - {2}', album.id, album.albumartist, album.album) items = list(album.items()) _freshen_items(items) @@ -1368,7 +1363,7 @@ def lookup_candidates(session, task): return plugins.send('import_task_start', session=session, task=task) - log.debug(u'Looking up: {0}', displayable_path(task.paths)) + log.debug('Looking up: {0}', displayable_path(task.paths)) # Restrict the initial lookup to IDs specified by the user via the -m # option. Currently all the IDs are passed onto the tasks directly. @@ -1407,8 +1402,7 @@ def user_query(session, task): def emitter(task): for item in task.items: task = SingletonImportTask(task.toppath, item) - for new_task in task.handle_created(session): - yield new_task + yield from task.handle_created(session) yield SentinelImportTask(task.toppath, task.paths) return _extend_pipeline(emitter(task), @@ -1454,30 +1448,30 @@ def resolve_duplicates(session, task): if task.choice_flag in (action.ASIS, action.APPLY, action.RETAG): found_duplicates = task.find_duplicates(session.lib) if found_duplicates: - log.debug(u'found duplicates: {}'.format( + log.debug('found duplicates: {}'.format( [o.id for o in found_duplicates] )) # Get the default action to follow from config. duplicate_action = config['import']['duplicate_action'].as_choice({ - u'skip': u's', - u'keep': u'k', - u'remove': u'r', - u'merge': u'm', - u'ask': u'a', + 'skip': 's', + 'keep': 'k', + 'remove': 'r', + 'merge': 'm', + 'ask': 'a', }) - log.debug(u'default action for duplicates: {0}', duplicate_action) + log.debug('default action for duplicates: {0}', duplicate_action) - if duplicate_action == u's': + if duplicate_action == 's': # Skip new. task.set_choice(action.SKIP) - elif duplicate_action == u'k': + elif duplicate_action == 'k': # Keep both. Do nothing; leave the choice intact. pass - elif duplicate_action == u'r': + elif duplicate_action == 'r': # Remove old. task.should_remove_duplicates = True - elif duplicate_action == u'm': + elif duplicate_action == 'm': # Merge duplicates together task.should_merge_duplicates = True else: @@ -1497,7 +1491,7 @@ def import_asis(session, task): if task.skip: return - log.info(u'{}', displayable_path(task.paths)) + log.info('{}', displayable_path(task.paths)) task.set_choice(action.ASIS) apply_choice(session, task) @@ -1580,11 +1574,11 @@ def log_files(session, task): """A coroutine (pipeline stage) to log each file to be imported. """ if isinstance(task, SingletonImportTask): - log.info(u'Singleton: {0}', displayable_path(task.item['path'])) + log.info('Singleton: {0}', displayable_path(task.item['path'])) elif task.items: - log.info(u'Album: {0}', displayable_path(task.paths[0])) + log.info('Album: {0}', displayable_path(task.paths[0])) for item in task.items: - log.info(u' {0}', displayable_path(item['path'])) + log.info(' {0}', displayable_path(item['path'])) def group_albums(session): diff --git a/beets/library.py b/beets/library.py index 54ff7eae9..c16b320da 100644 --- a/beets/library.py +++ b/beets/library.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -15,7 +14,6 @@ """The core data store and collection logic for beets. """ -from __future__ import division, absolute_import, print_function import os import sys @@ -62,7 +60,7 @@ class PathQuery(dbcore.FieldQuery): `case_sensitive` can be a bool or `None`, indicating that the behavior should depend on the filesystem. """ - super(PathQuery, self).__init__(field, pattern, fast) + super().__init__(field, pattern, fast) # By default, the case sensitivity depends on the filesystem # that the query path is located on. @@ -147,7 +145,7 @@ class PathType(types.Type): `bytes` objects, in keeping with the Unix filesystem abstraction. """ - sql = u'BLOB' + sql = 'BLOB' query = PathQuery model_type = bytes @@ -171,7 +169,7 @@ class PathType(types.Type): return normpath(bytestring_path(string)) def normalize(self, value): - if isinstance(value, six.text_type): + if isinstance(value, str): # Paths stored internally as encoded bytes. return bytestring_path(value) @@ -280,7 +278,6 @@ PF_KEY_DEFAULT = 'default' # Exceptions. -@six.python_2_unicode_compatible class FileOperationError(Exception): """Indicates an error when interacting with a file on disk. Possibilities include an unsupported media type, a permissions @@ -290,7 +287,7 @@ class FileOperationError(Exception): """Create an exception describing an operation on the file at `path` with the underlying (chained) exception `reason`. """ - super(FileOperationError, self).__init__(path, reason) + super().__init__(path, reason) self.path = path self.reason = reason @@ -298,9 +295,9 @@ class FileOperationError(Exception): """Get a string representing the error. Describes both the underlying reason and the file path in question. """ - return u'{0}: {1}'.format( + return '{}: {}'.format( util.displayable_path(self.path), - six.text_type(self.reason) + str(self.reason) ) # define __str__ as text to avoid infinite loop on super() calls @@ -308,25 +305,22 @@ class FileOperationError(Exception): __str__ = text -@six.python_2_unicode_compatible class ReadError(FileOperationError): """An error while reading a file (i.e. in `Item.read`). """ def __str__(self): - return u'error reading ' + super(ReadError, self).text() + return 'error reading ' + super().text() -@six.python_2_unicode_compatible class WriteError(FileOperationError): """An error while writing a file (i.e. in `Item.write`). """ def __str__(self): - return u'error writing ' + super(WriteError, self).text() + return 'error writing ' + super().text() # Item and Album model classes. -@six.python_2_unicode_compatible class LibModel(dbcore.Model): """Shared concrete functionality for Items and Albums. """ @@ -341,21 +335,21 @@ class LibModel(dbcore.Model): return funcs def store(self, fields=None): - super(LibModel, self).store(fields) + super().store(fields) plugins.send('database_change', lib=self._db, model=self) def remove(self): - super(LibModel, self).remove() + super().remove() plugins.send('database_change', lib=self._db, model=self) def add(self, lib=None): - super(LibModel, self).add(lib) + super().add(lib) plugins.send('database_change', lib=self._db, model=self) def __format__(self, spec): if not spec: spec = beets.config[self._format_config_key].as_str() - assert isinstance(spec, six.text_type) + assert isinstance(spec, str) return self.evaluate_template(spec) def __str__(self): @@ -376,7 +370,7 @@ class FormattedItemMapping(dbcore.db.FormattedMapping): def __init__(self, item, included_keys=ALL_KEYS, for_path=False): # We treat album and item keys specially here, # so exclude transitive album keys from the model's keys. - super(FormattedItemMapping, self).__init__(item, included_keys=[], + super().__init__(item, included_keys=[], for_path=for_path) self.included_keys = included_keys if included_keys == self.ALL_KEYS: @@ -522,9 +516,9 @@ class Item(LibModel): 'initial_key': MusicalKey(), 'length': DurationType(), - 'bitrate': types.ScaledInt(1000, u'kbps'), + 'bitrate': types.ScaledInt(1000, 'kbps'), 'format': types.STRING, - 'samplerate': types.ScaledInt(1000, u'kHz'), + 'samplerate': types.ScaledInt(1000, 'kHz'), 'bitdepth': types.INTEGER, 'channels': types.INTEGER, 'mtime': DateType(), @@ -606,14 +600,14 @@ class Item(LibModel): """ # Encode unicode paths and read buffers. if key == 'path': - if isinstance(value, six.text_type): + if isinstance(value, str): value = bytestring_path(value) elif isinstance(value, BLOB_TYPE): value = bytes(value) elif key == 'album_id': self._cached_album = None - changed = super(Item, self)._setitem(key, value) + changed = super()._setitem(key, value) if changed and key in MediaFile.fields(): self.mtime = 0 # Reset mtime on dirty. @@ -623,7 +617,7 @@ class Item(LibModel): necessary. Raise a KeyError if the field is not available. """ try: - return super(Item, self).__getitem__(key) + return super().__getitem__(key) except KeyError: if self._cached_album: return self._cached_album[key] @@ -633,9 +627,9 @@ class Item(LibModel): # This must not use `with_album=True`, because that might access # the database. When debugging, that is not guaranteed to succeed, and # can even deadlock due to the database lock. - return '{0}({1})'.format( + return '{}({})'.format( type(self).__name__, - ', '.join('{0}={1!r}'.format(k, self[k]) + ', '.join('{}={!r}'.format(k, self[k]) for k in self.keys(with_album=False)), ) @@ -643,7 +637,7 @@ class Item(LibModel): """Get a list of available field names. `with_album` controls whether the album's fields are included. """ - keys = super(Item, self).keys(computed=computed) + keys = super().keys(computed=computed) if with_album and self._cached_album: keys = set(keys) keys.update(self._cached_album.keys(computed=computed)) @@ -665,7 +659,7 @@ class Item(LibModel): """Set all key/value pairs in the mapping. If mtime is specified, it is not reset (as it might otherwise be). """ - super(Item, self).update(values) + super().update(values) if self.mtime == 0 and 'mtime' in values: self.mtime = values['mtime'] @@ -705,7 +699,7 @@ class Item(LibModel): for key in self._media_fields: value = getattr(mediafile, key) - if isinstance(value, six.integer_types): + if isinstance(value, int): if value.bit_length() > 63: value = 0 self[key] = value @@ -777,7 +771,7 @@ class Item(LibModel): self.write(*args, **kwargs) return True except FileOperationError as exc: - log.error(u"{0}", exc) + log.error("{0}", exc) return False def try_sync(self, write, move, with_album=True): @@ -797,7 +791,7 @@ class Item(LibModel): if move: # Check whether this file is inside the library directory. if self._db and self._db.directory in util.ancestry(self.path): - log.debug(u'moving {0} to synchronize path', + log.debug('moving {0} to synchronize path', util.displayable_path(self.path)) self.move(with_album=with_album) self.store() @@ -860,7 +854,7 @@ class Item(LibModel): try: return os.path.getsize(syspath(self.path)) except (OSError, Exception) as exc: - log.warning(u'could not get filesize: {0}', exc) + log.warning('could not get filesize: {0}', exc) return 0 # Model methods. @@ -870,7 +864,7 @@ class Item(LibModel): removed from disk. If `with_album`, then the item's album (if any) is removed if it the item was the last in the album. """ - super(Item, self).remove() + super().remove() # Remove the album if it is empty. if with_album: @@ -966,7 +960,7 @@ class Item(LibModel): if query == PF_KEY_DEFAULT: break else: - assert False, u"no default path format" + assert False, "no default path format" if isinstance(path_format, Template): subpath_tmpl = path_format else: @@ -1000,9 +994,9 @@ class Item(LibModel): # Print an error message if legalization fell back to # default replacements because of the maximum length. log.warning( - u'Fell back to default replacements when naming ' - u'file {}. Configure replacements to avoid lengthening ' - u'the filename.', + 'Fell back to default replacements when naming ' + 'file {}. Configure replacements to avoid lengthening ' + 'the filename.', subpath ) @@ -1135,7 +1129,7 @@ class Album(LibModel): containing the album are also removed (recursively) if empty. Set with_items to False to avoid removing the album's items. """ - super(Album, self).remove() + super().remove() # Delete art file. if delete: @@ -1160,7 +1154,7 @@ class Album(LibModel): return if not os.path.exists(old_art): - log.error(u'removing reference to missing album art file {}', + log.error('removing reference to missing album art file {}', util.displayable_path(old_art)) self.artpath = None return @@ -1170,7 +1164,7 @@ class Album(LibModel): return new_art = util.unique_path(new_art) - log.debug(u'moving album art {0} to {1}', + log.debug('moving album art {0} to {1}', util.displayable_path(old_art), util.displayable_path(new_art)) if operation == MoveOperation.MOVE: @@ -1227,7 +1221,7 @@ class Album(LibModel): """ item = self.items().get() if not item: - raise ValueError(u'empty album for album id %d' % self.id) + raise ValueError('empty album for album id %d' % self.id) return os.path.dirname(item.path) def _albumtotal(self): @@ -1324,7 +1318,7 @@ class Album(LibModel): track_updates[key] = self[key] with self._db.transaction(): - super(Album, self).store(fields) + super().store(fields) if track_updates: for item in self.items(): for key, value in track_updates.items(): @@ -1389,8 +1383,8 @@ def parse_query_string(s, model_cls): The string is split into components using shell-like syntax. """ - message = u"Query is not unicode: {0!r}".format(s) - assert isinstance(s, six.text_type), message + message = f"Query is not unicode: {s!r}" + assert isinstance(s, str), message try: parts = shlex.split(s) except ValueError as exc: @@ -1421,7 +1415,7 @@ class Library(dbcore.Database): '$artist/$album/$track $title'),), replacements=None): timeout = beets.config['timeout'].as_number() - super(Library, self).__init__(path, timeout=timeout) + super().__init__(path, timeout=timeout) self.directory = bytestring_path(normpath(directory)) self.path_formats = path_formats @@ -1430,7 +1424,7 @@ class Library(dbcore.Database): self._memotable = {} # Used for template substitution performance. def _create_connection(self): - conn = super(Library, self)._create_connection() + conn = super()._create_connection() conn.create_function('bytelower', 1, _sqlite_bytelower) return conn @@ -1452,10 +1446,10 @@ class Library(dbcore.Database): be empty. """ if not items: - raise ValueError(u'need at least one item') + raise ValueError('need at least one item') # Create the album structure using metadata from the first item. - values = dict((key, items[0][key]) for key in Album.item_keys) + values = {key: items[0][key] for key in Album.item_keys} album = Album(self, **values) # Add the album structure and set the items' album_id fields. @@ -1480,7 +1474,7 @@ class Library(dbcore.Database): # Parse the query, if necessary. try: parsed_sort = None - if isinstance(query, six.string_types): + if isinstance(query, str): query, parsed_sort = parse_query_string(query, model_cls) elif isinstance(query, (list, tuple)): query, parsed_sort = parse_query_parts(query, model_cls) @@ -1492,7 +1486,7 @@ class Library(dbcore.Database): if parsed_sort and not isinstance(parsed_sort, dbcore.query.NullSort): sort = parsed_sort - return super(Library, self)._fetch( + return super()._fetch( model_cls, query, sort ) @@ -1551,7 +1545,7 @@ def _int_arg(s): return int(s.strip()) -class DefaultTemplateFunctions(object): +class DefaultTemplateFunctions: """A container class for the default functions provided to path templates. These functions are contained in an object to provide additional context to the functions -- specifically, the Item being @@ -1603,7 +1597,7 @@ class DefaultTemplateFunctions(object): return s[-_int_arg(chars):] @staticmethod - def tmpl_if(condition, trueval, falseval=u''): + def tmpl_if(condition, trueval, falseval=''): """If ``condition`` is nonempty and nonzero, emit ``trueval``; otherwise, emit ``falseval`` (if provided). """ @@ -1645,7 +1639,7 @@ class DefaultTemplateFunctions(object): """ # Fast paths: no album, no item or library, or memoized value. if not self.item or not self.lib: - return u'' + return '' if isinstance(self.item, Item): album_id = self.item.album_id @@ -1653,7 +1647,7 @@ class DefaultTemplateFunctions(object): album_id = self.item.id if album_id is None: - return u'' + return '' memokey = ('aunique', keys, disam, album_id) memoval = self.lib._memotable.get(memokey) @@ -1672,14 +1666,14 @@ class DefaultTemplateFunctions(object): bracket_l = bracket[0] bracket_r = bracket[1] else: - bracket_l = u'' - bracket_r = u'' + bracket_l = '' + bracket_r = '' album = self.lib.get_album(album_id) if not album: # Do nothing for singletons. - self.lib._memotable[memokey] = u'' - return u'' + self.lib._memotable[memokey] = '' + return '' # Find matching albums to disambiguate with. subqueries = [] @@ -1691,13 +1685,13 @@ class DefaultTemplateFunctions(object): # If there's only one album to matching these details, then do # nothing. if len(albums) == 1: - self.lib._memotable[memokey] = u'' - return u'' + self.lib._memotable[memokey] = '' + return '' # Find the first disambiguator that distinguishes the albums. for disambiguator in disam: # Get the value for each album for the current field. - disam_values = set([a.get(disambiguator, '') for a in albums]) + disam_values = {a.get(disambiguator, '') for a in albums} # If the set of unique values is equal to the number of # albums in the disambiguation set, we're done -- this is @@ -1707,7 +1701,7 @@ class DefaultTemplateFunctions(object): else: # No disambiguator distinguished all fields. - res = u' {1}{0}{2}'.format(album.id, bracket_l, bracket_r) + res = f' {bracket_l}{album.id}{bracket_r}' self.lib._memotable[memokey] = res return res @@ -1716,15 +1710,15 @@ class DefaultTemplateFunctions(object): # Return empty string if disambiguator is empty. if disam_value: - res = u' {1}{0}{2}'.format(disam_value, bracket_l, bracket_r) + res = f' {bracket_l}{disam_value}{bracket_r}' else: - res = u'' + res = '' self.lib._memotable[memokey] = res return res @staticmethod - def tmpl_first(s, count=1, skip=0, sep=u'; ', join_str=u'; '): + def tmpl_first(s, count=1, skip=0, sep='; ', join_str='; '): """ Gets the item(s) from x to y in a string separated by something and join then with something @@ -1738,7 +1732,7 @@ class DefaultTemplateFunctions(object): count = skip + int(count) return join_str.join(s.split(sep)[skip:count]) - def tmpl_ifdef(self, field, trueval=u'', falseval=u''): + def tmpl_ifdef(self, field, trueval='', falseval=''): """ If field exists return trueval or the field (default) otherwise, emit return falseval (if provided). diff --git a/beets/logging.py b/beets/logging.py index d5ec7b73f..16cd9b210 100644 --- a/beets/logging.py +++ b/beets/logging.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -21,7 +20,6 @@ that when getLogger(name) instantiates a logger that logger uses {}-style formatting. """ -from __future__ import division, absolute_import, print_function from copy import copy from logging import * # noqa @@ -43,7 +41,7 @@ def logsafe(val): example. """ # Already Unicode. - if isinstance(val, six.text_type): + if isinstance(val, str): return val # Bytestring: needs decoding. @@ -57,7 +55,7 @@ def logsafe(val): # A "problem" object: needs a workaround. elif isinstance(val, subprocess.CalledProcessError): try: - return six.text_type(val) + return str(val) except UnicodeDecodeError: # An object with a broken __unicode__ formatter. Use __str__ # instead. @@ -74,7 +72,7 @@ class StrFormatLogger(Logger): instead of %-style formatting. """ - class _LogMessage(object): + class _LogMessage: def __init__(self, msg, args, kwargs): self.msg = msg self.args = args @@ -82,13 +80,13 @@ class StrFormatLogger(Logger): def __str__(self): args = [logsafe(a) for a in self.args] - kwargs = dict((k, logsafe(v)) for (k, v) in self.kwargs.items()) + kwargs = {k: logsafe(v) for (k, v) in self.kwargs.items()} return self.msg.format(*args, **kwargs) def _log(self, level, msg, args, exc_info=None, extra=None, **kwargs): """Log msg.format(*args, **kwargs)""" m = self._LogMessage(msg, args, kwargs) - return super(StrFormatLogger, self)._log(level, m, (), exc_info, extra) + return super()._log(level, m, (), exc_info, extra) class ThreadLocalLevelLogger(Logger): @@ -97,7 +95,7 @@ class ThreadLocalLevelLogger(Logger): def __init__(self, name, level=NOTSET): self._thread_level = threading.local() self.default_level = NOTSET - super(ThreadLocalLevelLogger, self).__init__(name, level) + super().__init__(name, level) @property def level(self): diff --git a/beets/mediafile.py b/beets/mediafile.py index 373642b42..82bcc973d 100644 --- a/beets/mediafile.py +++ b/beets/mediafile.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -13,7 +12,6 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -from __future__ import division, absolute_import, print_function import mediafile diff --git a/beets/plugins.py b/beets/plugins.py index 159b9a560..c3342985f 100644 --- a/beets/plugins.py +++ b/beets/plugins.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -15,7 +14,6 @@ """Support for beets plugins.""" -from __future__ import division, absolute_import, print_function import traceback import re @@ -53,21 +51,21 @@ class PluginLogFilter(logging.Filter): message. """ def __init__(self, plugin): - self.prefix = u'{0}: '.format(plugin.name) + self.prefix = f'{plugin.name}: ' def filter(self, record): if hasattr(record.msg, 'msg') and isinstance(record.msg.msg, - six.string_types): + str): # A _LogMessage from our hacked-up Logging replacement. record.msg.msg = self.prefix + record.msg.msg - elif isinstance(record.msg, six.string_types): + elif isinstance(record.msg, str): record.msg = self.prefix + record.msg return True # Managing the plugins themselves. -class BeetsPlugin(object): +class BeetsPlugin: """The base class for all beets plugins. Plugins provide functionality by defining a subclass of BeetsPlugin and overriding the abstract methods defined here. @@ -139,8 +137,8 @@ class BeetsPlugin(object): log_level = max(logging.DEBUG, base_log_level - 10 * verbosity) self._log.setLevel(log_level) if argspec.varkw is None: - kwargs = dict((k, v) for k, v in kwargs.items() - if k in argspec.args) + kwargs = {k: v for k, v in kwargs.items() + if k in argspec.args} try: return func(*args, **kwargs) @@ -263,14 +261,14 @@ def load_plugins(names=()): BeetsPlugin subclasses desired. """ for name in names: - modname = '{0}.{1}'.format(PLUGIN_NAMESPACE, name) + modname = f'{PLUGIN_NAMESPACE}.{name}' try: try: namespace = __import__(modname, None, None) except ImportError as exc: # Again, this is hacky: if exc.args[0].endswith(' ' + name): - log.warning(u'** plugin {0} not found', name) + log.warning('** plugin {0} not found', name) else: raise else: @@ -281,7 +279,7 @@ def load_plugins(names=()): except Exception: log.warning( - u'** error loading plugin {}:\n{}', + '** error loading plugin {}:\n{}', name, traceback.format_exc(), ) @@ -333,16 +331,16 @@ def queries(): def types(model_cls): # Gives us `item_types` and `album_types` - attr_name = '{0}_types'.format(model_cls.__name__.lower()) + attr_name = f'{model_cls.__name__.lower()}_types' types = {} for plugin in find_plugins(): plugin_types = getattr(plugin, attr_name, {}) for field in plugin_types: if field in types and plugin_types[field] != types[field]: raise PluginConflictException( - u'Plugin {0} defines flexible field {1} ' - u'which has already been defined with ' - u'another type.'.format(plugin.name, field) + 'Plugin {} defines flexible field {} ' + 'which has already been defined with ' + 'another type.'.format(plugin.name, field) ) types.update(plugin_types) return types @@ -350,7 +348,7 @@ def types(model_cls): def named_queries(model_cls): # Gather `item_queries` and `album_queries` from the plugins. - attr_name = '{0}_queries'.format(model_cls.__name__.lower()) + attr_name = f'{model_cls.__name__.lower()}_queries' queries = {} for plugin in find_plugins(): plugin_queries = getattr(plugin, attr_name, {}) @@ -382,17 +380,15 @@ def candidates(items, artist, album, va_likely, extra_tags=None): """Gets MusicBrainz candidates for an album from each plugin. """ for plugin in find_plugins(): - for candidate in plugin.candidates(items, artist, album, va_likely, - extra_tags): - yield candidate + yield from plugin.candidates(items, artist, album, va_likely, + extra_tags) def item_candidates(item, artist, title): """Gets MusicBrainz candidates for an item from the plugins. """ for plugin in find_plugins(): - for item_candidate in plugin.item_candidates(item, artist, title): - yield item_candidate + yield from plugin.item_candidates(item, artist, title) def album_for_id(album_id): @@ -485,7 +481,7 @@ def send(event, **arguments): Return a list of non-None values returned from the handlers. """ - log.debug(u'Sending event: {0}', event) + log.debug('Sending event: {0}', event) results = [] for handler in event_handlers()[event]: result = handler(**arguments) @@ -503,7 +499,7 @@ def feat_tokens(for_artist=True): feat_words = ['ft', 'featuring', 'feat', 'feat.', 'ft.'] if for_artist: feat_words += ['with', 'vs', 'and', 'con', '&'] - return r'(?<=\s)(?:{0})(?=\s)'.format( + return r'(?<=\s)(?:{})(?=\s)'.format( '|'.join(re.escape(x) for x in feat_words) ) @@ -620,10 +616,9 @@ def apply_item_changes(lib, item, move, pretend, write): item.store() -@six.add_metaclass(abc.ABCMeta) -class MetadataSourcePlugin(object): +class MetadataSourcePlugin(metaclass=abc.ABCMeta): def __init__(self): - super(MetadataSourcePlugin, self).__init__() + super().__init__() self.config.add({'source_weight': 0.5}) @abc.abstractproperty @@ -705,7 +700,7 @@ class MetadataSourcePlugin(object): :rtype: str """ self._log.debug( - u"Searching {} for {} '{}'", self.data_source, url_type, id_ + "Searching {} for {} '{}'", self.data_source, url_type, id_ ) match = re.search(self.id_regex['pattern'].format(url_type), str(id_)) if match: diff --git a/beets/random.py b/beets/random.py index 5387da4da..eb4f55aff 100644 --- a/beets/random.py +++ b/beets/random.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Philippe Mongeau. # @@ -15,7 +14,6 @@ """Get a random song or album from the library. """ -from __future__ import division, absolute_import, print_function import random from operator import attrgetter diff --git a/beets/vfs.py b/beets/vfs.py index 7f9a049ee..aef696508 100644 --- a/beets/vfs.py +++ b/beets/vfs.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -16,7 +15,6 @@ """A simple utility for constructing filesystem-like trees from beets libraries. """ -from __future__ import division, absolute_import, print_function from collections import namedtuple from beets import util From f8b8938fd8bbe91898d0982552bc75d35703d3ef Mon Sep 17 00:00:00 2001 From: Andrew Rogl Date: Wed, 25 Aug 2021 18:18:07 +1000 Subject: [PATCH 03/26] pyupgrade autotag dir --- beets/autotag/__init__.py | 2 -- beets/autotag/hooks.py | 54 +++++++++++++++++---------------------- beets/autotag/match.py | 48 +++++++++++++++++----------------- beets/autotag/mb.py | 50 +++++++++++++++++------------------- 4 files changed, 70 insertions(+), 84 deletions(-) diff --git a/beets/autotag/__init__.py b/beets/autotag/__init__.py index 7ab0d57fd..e62f492c6 100644 --- a/beets/autotag/__init__.py +++ b/beets/autotag/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -16,7 +15,6 @@ """Facilities for automatically determining files' correct metadata. """ -from __future__ import division, absolute_import, print_function from beets import logging from beets import config diff --git a/beets/autotag/hooks.py b/beets/autotag/hooks.py index 065d88170..593a895f1 100644 --- a/beets/autotag/hooks.py +++ b/beets/autotag/hooks.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -14,7 +13,6 @@ # included in all copies or substantial portions of the Software. """Glue between metadata sources and the matching logic.""" -from __future__ import division, absolute_import, print_function from collections import namedtuple from functools import total_ordering @@ -236,8 +234,8 @@ def _string_dist_basic(str1, str2): transliteration/lowering to ASCII characters. Normalized by string length. """ - assert isinstance(str1, six.text_type) - assert isinstance(str2, six.text_type) + assert isinstance(str1, str) + assert isinstance(str2, str) str1 = as_string(unidecode(str1)) str2 = as_string(unidecode(str2)) str1 = re.sub(r'[^a-z0-9]', '', str1.lower()) @@ -265,9 +263,9 @@ def string_dist(str1, str2): # "something, the". for word in SD_END_WORDS: if str1.endswith(', %s' % word): - str1 = '%s %s' % (word, str1[:-len(word) - 2]) + str1 = '{} {}'.format(word, str1[:-len(word) - 2]) if str2.endswith(', %s' % word): - str2 = '%s %s' % (word, str2[:-len(word) - 2]) + str2 = '{} {}'.format(word, str2[:-len(word) - 2]) # Perform a couple of basic normalizing substitutions. for pat, repl in SD_REPLACE: @@ -305,7 +303,7 @@ def string_dist(str1, str2): return base_dist + penalty -class LazyClassProperty(object): +class LazyClassProperty: """A decorator implementing a read-only property that is *lazy* in the sense that the getter is only invoked once. Subsequent accesses through *any* instance use the cached result. @@ -322,8 +320,7 @@ class LazyClassProperty(object): @total_ordering -@six.python_2_unicode_compatible -class Distance(object): +class Distance: """Keeps track of multiple distance penalties. Provides a single weighted distance for all penalties as well as a weighted distance for each individual penalty. @@ -410,7 +407,7 @@ class Distance(object): return other - self.distance def __str__(self): - return "{0:.2f}".format(self.distance) + return f"{self.distance:.2f}" # Behave like a dict. @@ -437,7 +434,7 @@ class Distance(object): """ if not isinstance(dist, Distance): raise ValueError( - u'`dist` must be a Distance object, not {0}'.format(type(dist)) + '`dist` must be a Distance object, not {}'.format(type(dist)) ) for key, penalties in dist._penalties.items(): self._penalties.setdefault(key, []).extend(penalties) @@ -461,7 +458,7 @@ class Distance(object): """ if not 0.0 <= dist <= 1.0: raise ValueError( - u'`dist` must be between 0.0 and 1.0, not {0}'.format(dist) + f'`dist` must be between 0.0 and 1.0, not {dist}' ) self._penalties.setdefault(key, []).append(dist) @@ -557,7 +554,7 @@ def album_for_mbid(release_id): try: album = mb.album_for_id(release_id) if album: - plugins.send(u'albuminfo_received', info=album) + plugins.send('albuminfo_received', info=album) return album except mb.MusicBrainzAPIError as exc: exc.log(log) @@ -570,7 +567,7 @@ def track_for_mbid(recording_id): try: track = mb.track_for_id(recording_id) if track: - plugins.send(u'trackinfo_received', info=track) + plugins.send('trackinfo_received', info=track) return track except mb.MusicBrainzAPIError as exc: exc.log(log) @@ -583,7 +580,7 @@ def albums_for_id(album_id): yield a for a in plugins.album_for_id(album_id): if a: - plugins.send(u'albuminfo_received', info=a) + plugins.send('albuminfo_received', info=a) yield a @@ -594,11 +591,11 @@ def tracks_for_id(track_id): yield t for t in plugins.track_for_id(track_id): if t: - plugins.send(u'trackinfo_received', info=t) + plugins.send('trackinfo_received', info=t) yield t -@plugins.notify_info_yielded(u'albuminfo_received') +@plugins.notify_info_yielded('albuminfo_received') def album_candidates(items, artist, album, va_likely, extra_tags): """Search for album matches. ``items`` is a list of Item objects that make up the album. ``artist`` and ``album`` are the respective @@ -612,28 +609,25 @@ def album_candidates(items, artist, album, va_likely, extra_tags): # Base candidates if we have album and artist to match. if artist and album: try: - for candidate in mb.match_album(artist, album, len(items), - extra_tags): - yield candidate + yield from mb.match_album(artist, album, len(items), + extra_tags) except mb.MusicBrainzAPIError as exc: exc.log(log) # Also add VA matches from MusicBrainz where appropriate. if va_likely and album: try: - for candidate in mb.match_album(None, album, len(items), - extra_tags): - yield candidate + yield from mb.match_album(None, album, len(items), + extra_tags) except mb.MusicBrainzAPIError as exc: exc.log(log) # Candidates from plugins. - for candidate in plugins.candidates(items, artist, album, va_likely, - extra_tags): - yield candidate + yield from plugins.candidates(items, artist, album, va_likely, + extra_tags) -@plugins.notify_info_yielded(u'trackinfo_received') +@plugins.notify_info_yielded('trackinfo_received') def item_candidates(item, artist, title): """Search for item matches. ``item`` is the Item to be matched. ``artist`` and ``title`` are strings and either reflect the item or @@ -643,11 +637,9 @@ def item_candidates(item, artist, title): # MusicBrainz candidates. if artist and title: try: - for candidate in mb.match_track(artist, title): - yield candidate + yield from mb.match_track(artist, title) except mb.MusicBrainzAPIError as exc: exc.log(log) # Plugin candidates. - for candidate in plugins.item_candidates(item, artist, title): - yield candidate + yield from plugins.item_candidates(item, artist, title) diff --git a/beets/autotag/match.py b/beets/autotag/match.py index f57cac739..d352a013f 100644 --- a/beets/autotag/match.py +++ b/beets/autotag/match.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -17,7 +16,6 @@ releases and tracks. """ -from __future__ import division, absolute_import, print_function import datetime import re @@ -35,7 +33,7 @@ from beets.util.enumeration import OrderedEnum # album level to determine whether a given release is likely a VA # release and also on the track level to to remove the penalty for # differing artists. -VA_ARTISTS = (u'', u'various artists', u'various', u'va', u'unknown') +VA_ARTISTS = ('', 'various artists', 'various', 'va', 'unknown') # Global logger. log = logging.getLogger('beets') @@ -108,7 +106,7 @@ def assign_items(items, tracks): log.debug('...done.') # Produce the output matching. - mapping = dict((items[i], tracks[j]) for (i, j) in matching) + mapping = {items[i]: tracks[j] for (i, j) in matching} extra_items = list(set(items) - set(mapping.keys())) extra_items.sort(key=lambda i: (i.disc, i.track, i.title)) extra_tracks = list(set(tracks) - set(mapping.values())) @@ -276,16 +274,16 @@ def match_by_id(items): try: first = next(albumids) except StopIteration: - log.debug(u'No album ID found.') + log.debug('No album ID found.') return None # Is there a consensus on the MB album ID? for other in albumids: if other != first: - log.debug(u'No album ID consensus.') + log.debug('No album ID consensus.') return None # If all album IDs are equal, look up the album. - log.debug(u'Searching for discovered album ID: {0}', first) + log.debug('Searching for discovered album ID: {0}', first) return hooks.album_for_mbid(first) @@ -351,23 +349,23 @@ def _add_candidate(items, results, info): checking the track count, ordering the items, checking for duplicates, and calculating the distance. """ - log.debug(u'Candidate: {0} - {1} ({2})', + log.debug('Candidate: {0} - {1} ({2})', info.artist, info.album, info.album_id) # Discard albums with zero tracks. if not info.tracks: - log.debug(u'No tracks.') + log.debug('No tracks.') return # Don't duplicate. if info.album_id in results: - log.debug(u'Duplicate.') + log.debug('Duplicate.') return # Discard matches without required tags. for req_tag in config['match']['required'].as_str_seq(): if getattr(info, req_tag) is None: - log.debug(u'Ignored. Missing required tag: {0}', req_tag) + log.debug('Ignored. Missing required tag: {0}', req_tag) return # Find mapping between the items and the track info. @@ -380,10 +378,10 @@ def _add_candidate(items, results, info): penalties = [key for key, _ in dist] for penalty in config['match']['ignored'].as_str_seq(): if penalty in penalties: - log.debug(u'Ignored. Penalty: {0}', penalty) + log.debug('Ignored. Penalty: {0}', penalty) return - log.debug(u'Success. Distance: {0}', dist) + log.debug('Success. Distance: {0}', dist) results[info.album_id] = hooks.AlbumMatch(dist, info, mapping, extra_items, extra_tracks) @@ -411,7 +409,7 @@ def tag_album(items, search_artist=None, search_album=None, likelies, consensus = current_metadata(items) cur_artist = likelies['artist'] cur_album = likelies['album'] - log.debug(u'Tagging {0} - {1}', cur_artist, cur_album) + log.debug('Tagging {0} - {1}', cur_artist, cur_album) # The output result (distance, AlbumInfo) tuples (keyed by MB album # ID). @@ -420,7 +418,7 @@ def tag_album(items, search_artist=None, search_album=None, # Search by explicit ID. if search_ids: for search_id in search_ids: - log.debug(u'Searching for album ID: {0}', search_id) + log.debug('Searching for album ID: {0}', search_id) for id_candidate in hooks.albums_for_id(search_id): _add_candidate(items, candidates, id_candidate) @@ -431,13 +429,13 @@ def tag_album(items, search_artist=None, search_album=None, if id_info: _add_candidate(items, candidates, id_info) rec = _recommendation(list(candidates.values())) - log.debug(u'Album ID match recommendation is {0}', rec) + log.debug('Album ID match recommendation is {0}', rec) if candidates and not config['import']['timid']: # If we have a very good MBID match, return immediately. # Otherwise, this match will compete against metadata-based # matches. if rec == Recommendation.strong: - log.debug(u'ID match.') + log.debug('ID match.') return cur_artist, cur_album, \ Proposal(list(candidates.values()), rec) @@ -445,19 +443,19 @@ def tag_album(items, search_artist=None, search_album=None, if not (search_artist and search_album): # No explicit search terms -- use current metadata. search_artist, search_album = cur_artist, cur_album - log.debug(u'Search terms: {0} - {1}', search_artist, search_album) + log.debug('Search terms: {0} - {1}', search_artist, search_album) extra_tags = None if config['musicbrainz']['extra_tags']: tag_list = config['musicbrainz']['extra_tags'].get() extra_tags = {k: v for (k, v) in likelies.items() if k in tag_list} - log.debug(u'Additional search terms: {0}', extra_tags) + log.debug('Additional search terms: {0}', extra_tags) # Is this album likely to be a "various artist" release? va_likely = ((not consensus['artist']) or (search_artist.lower() in VA_ARTISTS) or any(item.comp for item in items)) - log.debug(u'Album might be VA: {0}', va_likely) + log.debug('Album might be VA: {0}', va_likely) # Get the results from the data sources. for matched_candidate in hooks.album_candidates(items, @@ -467,7 +465,7 @@ def tag_album(items, search_artist=None, search_album=None, extra_tags): _add_candidate(items, candidates, matched_candidate) - log.debug(u'Evaluating {0} candidates.', len(candidates)) + log.debug('Evaluating {0} candidates.', len(candidates)) # Sort and get the recommendation. candidates = _sort_candidates(candidates.values()) rec = _recommendation(candidates) @@ -492,7 +490,7 @@ def tag_item(item, search_artist=None, search_title=None, trackids = search_ids or [t for t in [item.mb_trackid] if t] if trackids: for trackid in trackids: - log.debug(u'Searching for track ID: {0}', trackid) + log.debug('Searching for track ID: {0}', trackid) for track_info in hooks.tracks_for_id(trackid): dist = track_distance(item, track_info, incl_artist=True) candidates[track_info.track_id] = \ @@ -501,7 +499,7 @@ def tag_item(item, search_artist=None, search_title=None, rec = _recommendation(_sort_candidates(candidates.values())) if rec == Recommendation.strong and \ not config['import']['timid']: - log.debug(u'Track ID match.') + log.debug('Track ID match.') return Proposal(_sort_candidates(candidates.values()), rec) # If we're searching by ID, don't proceed. @@ -514,7 +512,7 @@ def tag_item(item, search_artist=None, search_title=None, # Search terms. if not (search_artist and search_title): search_artist, search_title = item.artist, item.title - log.debug(u'Item search terms: {0} - {1}', search_artist, search_title) + log.debug('Item search terms: {0} - {1}', search_artist, search_title) # Get and evaluate candidate metadata. for track_info in hooks.item_candidates(item, search_artist, search_title): @@ -522,7 +520,7 @@ def tag_item(item, search_artist=None, search_title=None, candidates[track_info.track_id] = hooks.TrackMatch(dist, track_info) # Sort by distance and return with recommendation. - log.debug(u'Found {0} candidates.', len(candidates)) + log.debug('Found {0} candidates.', len(candidates)) candidates = _sort_candidates(candidates.values()) rec = _recommendation(candidates) return Proposal(candidates, rec) diff --git a/beets/autotag/mb.py b/beets/autotag/mb.py index 3e0658317..3b9434acd 100644 --- a/beets/autotag/mb.py +++ b/beets/autotag/mb.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -15,7 +14,6 @@ """Searches for albums in the MusicBrainz database. """ -from __future__ import division, absolute_import, print_function import musicbrainzngs import re @@ -58,11 +56,11 @@ class MusicBrainzAPIError(util.HumanReadableException): def __init__(self, reason, verb, query, tb=None): self.query = query if isinstance(reason, musicbrainzngs.WebServiceError): - reason = u'MusicBrainz not reachable' - super(MusicBrainzAPIError, self).__init__(reason, verb, tb) + reason = 'MusicBrainz not reachable' + super().__init__(reason, verb, tb) def get_message(self): - return u'{0} in {1} with query {2}'.format( + return '{} in {} with query {}'.format( self._reasonstr(), self.verb, repr(self.query) ) @@ -159,7 +157,7 @@ def _flatten_artist_credit(credit): artist_sort_parts = [] artist_credit_parts = [] for el in credit: - if isinstance(el, six.string_types): + if isinstance(el, str): # Join phrase. artist_parts.append(el) artist_credit_parts.append(el) @@ -212,7 +210,7 @@ def track_info(recording, index=None, medium=None, medium_index=None, medium=medium, medium_index=medium_index, medium_total=medium_total, - data_source=u'MusicBrainz', + data_source='MusicBrainz', data_url=track_url(recording['id']), ) @@ -255,10 +253,10 @@ def track_info(recording, index=None, medium=None, medium_index=None, composer_sort.append( artist_relation['artist']['sort-name']) if lyricist: - info.lyricist = u', '.join(lyricist) + info.lyricist = ', '.join(lyricist) if composer: - info.composer = u', '.join(composer) - info.composer_sort = u', '.join(composer_sort) + info.composer = ', '.join(composer) + info.composer_sort = ', '.join(composer_sort) arranger = [] for artist_relation in recording.get('artist-relation-list', ()): @@ -267,7 +265,7 @@ def track_info(recording, index=None, medium=None, medium_index=None, if type == 'arranger': arranger.append(artist_relation['artist']['name']) if arranger: - info.arranger = u', '.join(arranger) + info.arranger = ', '.join(arranger) # Supplementary fields provided by plugins extra_trackdatas = plugins.send('mb_track_extract', data=recording) @@ -312,10 +310,10 @@ def album_info(release): # when the release has more than 500 tracks. So we use browse_recordings # on chunks of tracks to recover the same information in this case. if ntracks > BROWSE_MAXTRACKS: - log.debug(u'Album {} has too many tracks', release['id']) + log.debug('Album {} has too many tracks', release['id']) recording_list = [] for i in range(0, ntracks, BROWSE_CHUNKSIZE): - log.debug(u'Retrieving tracks starting at {}', i) + log.debug('Retrieving tracks starting at {}', i) recording_list.extend(musicbrainzngs.browse_recordings( release=release['id'], limit=BROWSE_CHUNKSIZE, includes=BROWSE_INCLUDES, @@ -392,7 +390,7 @@ def album_info(release): mediums=len(release['medium-list']), artist_sort=artist_sort_name, artist_credit=artist_credit_name, - data_source=u'MusicBrainz', + data_source='MusicBrainz', data_url=album_url(release['id']), ) info.va = info.artist_id == VARIOUS_ARTISTS_ID @@ -486,15 +484,15 @@ def match_album(artist, album, tracks=None, extra_tags=None): # Various Artists search. criteria['arid'] = VARIOUS_ARTISTS_ID if tracks is not None: - criteria['tracks'] = six.text_type(tracks) + criteria['tracks'] = str(tracks) # Additional search cues from existing metadata. if extra_tags: for tag in extra_tags: key = FIELDS_TO_MB_KEYS[tag] - value = six.text_type(extra_tags.get(tag, '')).lower().strip() + value = str(extra_tags.get(tag, '')).lower().strip() if key == 'catno': - value = value.replace(u' ', '') + value = value.replace(' ', '') if value: criteria[key] = value @@ -503,7 +501,7 @@ def match_album(artist, album, tracks=None, extra_tags=None): return try: - log.debug(u'Searching for MusicBrainz releases with: {!r}', criteria) + log.debug('Searching for MusicBrainz releases with: {!r}', criteria) res = musicbrainzngs.search_releases( limit=config['musicbrainz']['searchlimit'].get(int), **criteria) except musicbrainzngs.MusicBrainzError as exc: @@ -544,7 +542,7 @@ def _parse_id(s): no ID can be found, return None. """ # Find the first thing that looks like a UUID/MBID. - match = re.search(u'[a-f0-9]{8}(-[a-f0-9]{4}){3}-[a-f0-9]{12}', s) + match = re.search('[a-f0-9]{8}(-[a-f0-9]{4}){3}-[a-f0-9]{12}', s) if match: return match.group() @@ -554,19 +552,19 @@ def album_for_id(releaseid): object or None if the album is not found. May raise a MusicBrainzAPIError. """ - log.debug(u'Requesting MusicBrainz release {}', releaseid) + log.debug('Requesting MusicBrainz release {}', releaseid) albumid = _parse_id(releaseid) if not albumid: - log.debug(u'Invalid MBID ({0}).', releaseid) + log.debug('Invalid MBID ({0}).', releaseid) return try: res = musicbrainzngs.get_release_by_id(albumid, RELEASE_INCLUDES) except musicbrainzngs.ResponseError: - log.debug(u'Album ID match failed.') + log.debug('Album ID match failed.') return None except musicbrainzngs.MusicBrainzError as exc: - raise MusicBrainzAPIError(exc, u'get release by ID', albumid, + raise MusicBrainzAPIError(exc, 'get release by ID', albumid, traceback.format_exc()) return album_info(res['release']) @@ -577,14 +575,14 @@ def track_for_id(releaseid): """ trackid = _parse_id(releaseid) if not trackid: - log.debug(u'Invalid MBID ({0}).', releaseid) + log.debug('Invalid MBID ({0}).', releaseid) return try: res = musicbrainzngs.get_recording_by_id(trackid, TRACK_INCLUDES) except musicbrainzngs.ResponseError: - log.debug(u'Track ID match failed.') + log.debug('Track ID match failed.') return None except musicbrainzngs.MusicBrainzError as exc: - raise MusicBrainzAPIError(exc, u'get recording by ID', trackid, + raise MusicBrainzAPIError(exc, 'get recording by ID', trackid, traceback.format_exc()) return track_info(res['recording']) From d288f872903c79a7ee7c5a7c9cc690809441196e Mon Sep 17 00:00:00 2001 From: Andrew Rogl Date: Wed, 25 Aug 2021 18:48:26 +1000 Subject: [PATCH 04/26] pyupgrade dbcore dir --- beets/dbcore/__init__.py | 2 - beets/dbcore/db.py | 90 +++++++++++++++++++------------------- beets/dbcore/query.py | 89 ++++++++++++++++++------------------- beets/dbcore/queryparse.py | 10 ++--- beets/dbcore/types.py | 35 +++++++-------- 5 files changed, 107 insertions(+), 119 deletions(-) diff --git a/beets/dbcore/__init__.py b/beets/dbcore/__init__.py index 689e7202c..923c34cac 100644 --- a/beets/dbcore/__init__.py +++ b/beets/dbcore/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -16,7 +15,6 @@ """DBCore is an abstract database package that forms the basis for beets' Library. """ -from __future__ import division, absolute_import, print_function from .db import Model, Database from .query import Query, FieldQuery, MatchQuery, AndQuery, OrQuery diff --git a/beets/dbcore/db.py b/beets/dbcore/db.py index 23f61d41c..acd131be2 100755 --- a/beets/dbcore/db.py +++ b/beets/dbcore/db.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -15,7 +14,6 @@ """The central Model and Database constructs for DBCore. """ -from __future__ import division, absolute_import, print_function import time import os @@ -30,7 +28,6 @@ from beets.util import functemplate from beets.util import py3_path from beets.dbcore import types from .query import MatchQuery, NullSort, TrueQuery -import six from collections.abc import Mapping @@ -83,7 +80,7 @@ class FormattedMapping(Mapping): def get(self, key, default=None): if default is None: default = self.model._type(key).format(None) - return super(FormattedMapping, self).get(key, default) + return super().get(key, default) def _get_formatted(self, model, key): value = model._type(key).format(model.get(key)) @@ -104,7 +101,7 @@ class FormattedMapping(Mapping): return value -class LazyConvertDict(object): +class LazyConvertDict: """Lazily convert types for attributes fetched from the database """ @@ -200,7 +197,7 @@ class LazyConvertDict(object): # Abstract base for model classes. -class Model(object): +class Model: """An abstract object representing an object in the database. Model objects act like dictionaries (i.e., they allow subscript access like ``obj['field']``). The same field set is available via attribute @@ -314,9 +311,9 @@ class Model(object): return obj def __repr__(self): - return '{0}({1})'.format( + return '{}({})'.format( type(self).__name__, - ', '.join('{0}={1!r}'.format(k, v) for k, v in dict(self).items()), + ', '.join(f'{k}={v!r}' for k, v in dict(self).items()), ) def clear_dirty(self): @@ -334,10 +331,10 @@ class Model(object): """ if not self._db: raise ValueError( - u'{0} has no database'.format(type(self).__name__) + '{} has no database'.format(type(self).__name__) ) if need_id and not self.id: - raise ValueError(u'{0} has no id'.format(type(self).__name__)) + raise ValueError('{} has no id'.format(type(self).__name__)) def copy(self): """Create a copy of the model object. @@ -428,9 +425,9 @@ class Model(object): elif key in self._fields: # Fixed setattr(self, key, self._type(key).null) elif key in self._getters(): # Computed. - raise KeyError(u'computed field {0} cannot be deleted'.format(key)) + raise KeyError(f'computed field {key} cannot be deleted') else: - raise KeyError(u'no such field {0}'.format(key)) + raise KeyError(f'no such field {key}') def keys(self, computed=False): """Get a list of available field names for this object. The @@ -480,22 +477,22 @@ class Model(object): def __getattr__(self, key): if key.startswith('_'): - raise AttributeError(u'model has no attribute {0!r}'.format(key)) + raise AttributeError(f'model has no attribute {key!r}') else: try: return self[key] except KeyError: - raise AttributeError(u'no such field {0!r}'.format(key)) + raise AttributeError(f'no such field {key!r}') def __setattr__(self, key, value): if key.startswith('_'): - super(Model, self).__setattr__(key, value) + super().__setattr__(key, value) else: self[key] = value def __delattr__(self, key): if key.startswith('_'): - super(Model, self).__delattr__(key) + super().__delattr__(key) else: del self[key] @@ -524,7 +521,7 @@ class Model(object): with self._db.transaction() as tx: # Main table update. if assignments: - query = 'UPDATE {0} SET {1} WHERE id=?'.format( + query = 'UPDATE {} SET {} WHERE id=?'.format( self._table, assignments ) subvars.append(self.id) @@ -535,7 +532,7 @@ class Model(object): if key in self._dirty: self._dirty.remove(key) tx.mutate( - 'INSERT INTO {0} ' + 'INSERT INTO {} ' '(entity_id, key, value) ' 'VALUES (?, ?, ?);'.format(self._flex_table), (self.id, key, value), @@ -544,7 +541,7 @@ class Model(object): # Deleted flexible attributes. for key in self._dirty: tx.mutate( - 'DELETE FROM {0} ' + 'DELETE FROM {} ' 'WHERE entity_id=? AND key=?'.format(self._flex_table), (self.id, key) ) @@ -562,7 +559,7 @@ class Model(object): # Exit early return stored_obj = self._db._get(type(self), self.id) - assert stored_obj is not None, u"object {0} not in DB".format(self.id) + assert stored_obj is not None, f"object {self.id} not in DB" self._values_fixed = LazyConvertDict(self) self._values_flex = LazyConvertDict(self) self.update(dict(stored_obj)) @@ -574,11 +571,11 @@ class Model(object): self._check_db() with self._db.transaction() as tx: tx.mutate( - 'DELETE FROM {0} WHERE id=?'.format(self._table), + f'DELETE FROM {self._table} WHERE id=?', (self.id,) ) tx.mutate( - 'DELETE FROM {0} WHERE entity_id=?'.format(self._flex_table), + f'DELETE FROM {self._flex_table} WHERE entity_id=?', (self.id,) ) @@ -596,7 +593,7 @@ class Model(object): with self._db.transaction() as tx: new_id = tx.mutate( - 'INSERT INTO {0} DEFAULT VALUES'.format(self._table) + f'INSERT INTO {self._table} DEFAULT VALUES' ) self.id = new_id self.added = time.time() @@ -623,7 +620,7 @@ class Model(object): separators will be added to the template. """ # Perform substitution. - if isinstance(template, six.string_types): + if isinstance(template, str): template = functemplate.template(template) return template.substitute(self.formatted(for_path=for_path), self._template_funcs()) @@ -634,8 +631,8 @@ class Model(object): def _parse(cls, key, string): """Parse a string as a value for the given key. """ - if not isinstance(string, six.string_types): - raise TypeError(u"_parse() argument must be a string") + if not isinstance(string, str): + raise TypeError("_parse() argument must be a string") return cls._type(key).parse(string) @@ -647,10 +644,11 @@ class Model(object): # Database controller and supporting interfaces. -class Results(object): +class Results: """An item query result set. Iterating over the collection lazily constructs LibModel objects that reflect database rows. """ + def __init__(self, model_class, rows, db, flex_rows, query=None, sort=None): """Create a result set that will construct objects of type @@ -748,8 +746,8 @@ class Results(object): """ Create a Model object for the given row """ cols = dict(row) - values = dict((k, v) for (k, v) in cols.items() - if not k[:4] == 'flex') + values = {k: v for (k, v) in cols.items() + if not k[:4] == 'flex'} # Construct the Python object obj = self.model_class._awaken(self.db, values, flex_values) @@ -798,7 +796,7 @@ class Results(object): next(it) return next(it) except StopIteration: - raise IndexError(u'result index {0} out of range'.format(n)) + raise IndexError(f'result index {n} out of range') def get(self): """Return the first matching object, or None if no objects @@ -811,7 +809,7 @@ class Results(object): return None -class Transaction(object): +class Transaction: """A context manager for safe, concurrent access to the database. All SQL commands should be executed through a transaction. """ @@ -886,7 +884,7 @@ class Transaction(object): self.db._connection().executescript(statements) -class Database(object): +class Database: """A container for Model objects that wraps an SQLite database as the backend. """ @@ -998,7 +996,7 @@ class Database(object): """Load an SQLite extension into all open connections.""" if not self.supports_extensions: raise ValueError( - 'this sqlite3 installation does not support extensions') + 'this sqlite3 installation does not support extensions') self._extensions.append(path) @@ -1015,7 +1013,7 @@ class Database(object): # Get current schema. with self.transaction() as tx: rows = tx.query('PRAGMA table_info(%s)' % table) - current_fields = set([row[1] for row in rows]) + current_fields = {row[1] for row in rows} field_names = set(fields.keys()) if current_fields.issuperset(field_names): @@ -1026,9 +1024,9 @@ class Database(object): # No table exists. columns = [] for name, typ in fields.items(): - columns.append('{0} {1}'.format(name, typ.sql)) - setup_sql = 'CREATE TABLE {0} ({1});\n'.format(table, - ', '.join(columns)) + columns.append(f'{name} {typ.sql}') + setup_sql = 'CREATE TABLE {} ({});\n'.format(table, + ', '.join(columns)) else: # Table exists does not match the field set. @@ -1036,7 +1034,7 @@ class Database(object): for name, typ in fields.items(): if name in current_fields: continue - setup_sql += 'ALTER TABLE {0} ADD COLUMN {1} {2};\n'.format( + setup_sql += 'ALTER TABLE {} ADD COLUMN {} {};\n'.format( table, name, typ.sql ) @@ -1072,23 +1070,23 @@ class Database(object): where, subvals = query.clause() order_by = sort.order_clause() - sql = ("SELECT * FROM {0} WHERE {1} {2}").format( + sql = ("SELECT * FROM {} WHERE {} {}").format( model_cls._table, where or '1', - "ORDER BY {0}".format(order_by) if order_by else '', + f"ORDER BY {order_by}" if order_by else '', ) # Fetch flexible attributes for items matching the main query. # Doing the per-item filtering in python is faster than issuing # one query per item to sqlite. flex_sql = (""" - SELECT * FROM {0} WHERE entity_id IN - (SELECT id FROM {1} WHERE {2}); + SELECT * FROM {} WHERE entity_id IN + (SELECT id FROM {} WHERE {}); """.format( - model_cls._flex_table, - model_cls._table, - where or '1', - ) + model_cls._flex_table, + model_cls._table, + where or '1', + ) ) with self.transaction() as tx: diff --git a/beets/dbcore/query.py b/beets/dbcore/query.py index b9f346a9e..e8e3d1f4a 100644 --- a/beets/dbcore/query.py +++ b/beets/dbcore/query.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -15,7 +14,6 @@ """The Query type hierarchy for DBCore. """ -from __future__ import division, absolute_import, print_function import re from operator import mul @@ -23,7 +21,6 @@ from beets import util from datetime import datetime, timedelta import unicodedata from functools import reduce -import six class ParsingError(ValueError): @@ -41,8 +38,8 @@ class InvalidQueryError(ParsingError): def __init__(self, query, explanation): if isinstance(query, list): query = " ".join(query) - message = u"'{0}': {1}".format(query, explanation) - super(InvalidQueryError, self).__init__(message) + message = f"'{query}': {explanation}" + super().__init__(message) class InvalidQueryArgumentValueError(ParsingError): @@ -53,13 +50,13 @@ class InvalidQueryArgumentValueError(ParsingError): """ def __init__(self, what, expected, detail=None): - message = u"'{0}' is not {1}".format(what, expected) + message = f"'{what}' is not {expected}" if detail: - message = u"{0}: {1}".format(message, detail) - super(InvalidQueryArgumentValueError, self).__init__(message) + message = f"{message}: {detail}" + super().__init__(message) -class Query(object): +class Query: """An abstract class representing a query into the item database. """ @@ -79,7 +76,7 @@ class Query(object): raise NotImplementedError def __repr__(self): - return "{0.__class__.__name__}()".format(self) + return f"{self.__class__.__name__}()" def __eq__(self, other): return type(self) == type(other) @@ -126,7 +123,7 @@ class FieldQuery(Query): "{0.fast})".format(self)) def __eq__(self, other): - return super(FieldQuery, self).__eq__(other) and \ + return super().__eq__(other) and \ self.field == other.field and self.pattern == other.pattern def __hash__(self): @@ -148,7 +145,7 @@ class NoneQuery(FieldQuery): """A query that checks whether a field is null.""" def __init__(self, field, fast=True): - super(NoneQuery, self).__init__(field, None, fast) + super().__init__(field, None, fast) def col_clause(self): return self.field + " IS NULL", () @@ -207,14 +204,14 @@ class RegexpQuery(StringFieldQuery): """ def __init__(self, field, pattern, fast=True): - super(RegexpQuery, self).__init__(field, pattern, fast) + super().__init__(field, pattern, fast) pattern = self._normalize(pattern) try: self.pattern = re.compile(self.pattern) except re.error as exc: # Invalid regular expression. raise InvalidQueryArgumentValueError(pattern, - u"a regular expression", + "a regular expression", format(exc)) @staticmethod @@ -235,8 +232,8 @@ class BooleanQuery(MatchQuery): """ def __init__(self, field, pattern, fast=True): - super(BooleanQuery, self).__init__(field, pattern, fast) - if isinstance(pattern, six.string_types): + super().__init__(field, pattern, fast) + if isinstance(pattern, str): self.pattern = util.str2bool(pattern) self.pattern = int(self.pattern) @@ -249,13 +246,13 @@ class BytesQuery(MatchQuery): """ def __init__(self, field, pattern): - super(BytesQuery, self).__init__(field, pattern) + super().__init__(field, pattern) # Use a buffer/memoryview representation of the pattern for SQLite # matching. This instructs SQLite to treat the blob as binary # rather than encoded Unicode. - if isinstance(self.pattern, (six.text_type, bytes)): - if isinstance(self.pattern, six.text_type): + if isinstance(self.pattern, (str, bytes)): + if isinstance(self.pattern, str): self.pattern = self.pattern.encode('utf-8') self.buf_pattern = memoryview(self.pattern) elif isinstance(self.pattern, memoryview): @@ -290,10 +287,10 @@ class NumericQuery(FieldQuery): try: return float(s) except ValueError: - raise InvalidQueryArgumentValueError(s, u"an int or a float") + raise InvalidQueryArgumentValueError(s, "an int or a float") def __init__(self, field, pattern, fast=True): - super(NumericQuery, self).__init__(field, pattern, fast) + super().__init__(field, pattern, fast) parts = pattern.split('..', 1) if len(parts) == 1: @@ -311,7 +308,7 @@ class NumericQuery(FieldQuery): if self.field not in item: return False value = item[self.field] - if isinstance(value, six.string_types): + if isinstance(value, str): value = self._convert(value) if self.point is not None: @@ -328,14 +325,14 @@ class NumericQuery(FieldQuery): return self.field + '=?', (self.point,) else: if self.rangemin is not None and self.rangemax is not None: - return (u'{0} >= ? AND {0} <= ?'.format(self.field), + return ('{0} >= ? AND {0} <= ?'.format(self.field), (self.rangemin, self.rangemax)) elif self.rangemin is not None: - return u'{0} >= ?'.format(self.field), (self.rangemin,) + return f'{self.field} >= ?', (self.rangemin,) elif self.rangemax is not None: - return u'{0} <= ?'.format(self.field), (self.rangemax,) + return f'{self.field} <= ?', (self.rangemax,) else: - return u'1', () + return '1', () class CollectionQuery(Query): @@ -380,7 +377,7 @@ class CollectionQuery(Query): return "{0.__class__.__name__}({0.subqueries!r})".format(self) def __eq__(self, other): - return super(CollectionQuery, self).__eq__(other) and \ + return super().__eq__(other) and \ self.subqueries == other.subqueries def __hash__(self): @@ -404,7 +401,7 @@ class AnyFieldQuery(CollectionQuery): subqueries = [] for field in self.fields: subqueries.append(cls(field, pattern, True)) - super(AnyFieldQuery, self).__init__(subqueries) + super().__init__(subqueries) def clause(self): return self.clause_with_joiner('or') @@ -420,7 +417,7 @@ class AnyFieldQuery(CollectionQuery): "{0.query_class.__name__})".format(self)) def __eq__(self, other): - return super(AnyFieldQuery, self).__eq__(other) and \ + return super().__eq__(other) and \ self.query_class == other.query_class def __hash__(self): @@ -470,7 +467,7 @@ class NotQuery(Query): def clause(self): clause, subvals = self.subquery.clause() if clause: - return 'not ({0})'.format(clause), subvals + return f'not ({clause})', subvals else: # If there is no clause, there is nothing to negate. All the logic # is handled by match() for slow queries. @@ -483,7 +480,7 @@ class NotQuery(Query): return "{0.__class__.__name__}({0.subquery!r})".format(self) def __eq__(self, other): - return super(NotQuery, self).__eq__(other) and \ + return super().__eq__(other) and \ self.subquery == other.subquery def __hash__(self): @@ -539,7 +536,7 @@ def _parse_periods(pattern): return (start, end) -class Period(object): +class Period: """A period of time given by a date, time and precision. Example: 2014-01-01 10:50:30 with precision 'month' represents all @@ -565,7 +562,7 @@ class Period(object): or "second"). """ if precision not in Period.precisions: - raise ValueError(u'Invalid precision {0}'.format(precision)) + raise ValueError(f'Invalid precision {precision}') self.date = date self.precision = precision @@ -646,10 +643,10 @@ class Period(object): elif 'second' == precision: return date + timedelta(seconds=1) else: - raise ValueError(u'unhandled precision {0}'.format(precision)) + raise ValueError(f'unhandled precision {precision}') -class DateInterval(object): +class DateInterval: """A closed-open interval of dates. A left endpoint of None means since the beginning of time. @@ -658,7 +655,7 @@ class DateInterval(object): def __init__(self, start, end): if start is not None and end is not None and not start < end: - raise ValueError(u"start date {0} is not before end date {1}" + raise ValueError("start date {} is not before end date {}" .format(start, end)) self.start = start self.end = end @@ -679,7 +676,7 @@ class DateInterval(object): return True def __str__(self): - return '[{0}, {1})'.format(self.start, self.end) + return f'[{self.start}, {self.end})' class DateQuery(FieldQuery): @@ -693,7 +690,7 @@ class DateQuery(FieldQuery): """ def __init__(self, field, pattern, fast=True): - super(DateQuery, self).__init__(field, pattern, fast) + super().__init__(field, pattern, fast) start, end = _parse_periods(pattern) self.interval = DateInterval.from_periods(start, end) @@ -752,12 +749,12 @@ class DurationQuery(NumericQuery): except ValueError: raise InvalidQueryArgumentValueError( s, - u"a M:SS string or a float") + "a M:SS string or a float") # Sorting. -class Sort(object): +class Sort: """An abstract class representing a sort operation for a query into the item database. """ @@ -844,13 +841,13 @@ class MultipleSort(Sort): return items def __repr__(self): - return 'MultipleSort({!r})'.format(self.sorts) + return f'MultipleSort({self.sorts!r})' def __hash__(self): return hash(tuple(self.sorts)) def __eq__(self, other): - return super(MultipleSort, self).__eq__(other) and \ + return super().__eq__(other) and \ self.sorts == other.sorts @@ -871,14 +868,14 @@ class FieldSort(Sort): def key(item): field_val = item.get(self.field, '') - if self.case_insensitive and isinstance(field_val, six.text_type): + if self.case_insensitive and isinstance(field_val, str): field_val = field_val.lower() return field_val return sorted(objs, key=key, reverse=not self.ascending) def __repr__(self): - return '<{0}: {1}{2}>'.format( + return '<{}: {}{}>'.format( type(self).__name__, self.field, '+' if self.ascending else '-', @@ -888,7 +885,7 @@ class FieldSort(Sort): return hash((self.field, self.ascending)) def __eq__(self, other): - return super(FieldSort, self).__eq__(other) and \ + return super().__eq__(other) and \ self.field == other.field and \ self.ascending == other.ascending @@ -906,7 +903,7 @@ class FixedFieldSort(FieldSort): 'ELSE {0} END)'.format(self.field) else: field = self.field - return "{0} {1}".format(field, order) + return f"{field} {order}" class SlowFieldSort(FieldSort): diff --git a/beets/dbcore/queryparse.py b/beets/dbcore/queryparse.py index fee38afdd..3bf02e4d2 100644 --- a/beets/dbcore/queryparse.py +++ b/beets/dbcore/queryparse.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -15,7 +14,6 @@ """Parsing of strings into DBCore queries. """ -from __future__ import division, absolute_import, print_function import re import itertools @@ -226,8 +224,8 @@ def parse_sorted_query(model_cls, parts, prefixes={}, # Split up query in to comma-separated subqueries, each representing # an AndQuery, which need to be joined together in one OrQuery subquery_parts = [] - for part in parts + [u',']: - if part.endswith(u','): + for part in parts + [',']: + if part.endswith(','): # Ensure we can catch "foo, bar" as well as "foo , bar" last_subquery_part = part[:-1] if last_subquery_part: @@ -241,8 +239,8 @@ def parse_sorted_query(model_cls, parts, prefixes={}, else: # Sort parts (1) end in + or -, (2) don't have a field, and # (3) consist of more than just the + or -. - if part.endswith((u'+', u'-')) \ - and u':' not in part \ + if part.endswith(('+', '-')) \ + and ':' not in part \ and len(part) > 1: sort_parts.append(part) else: diff --git a/beets/dbcore/types.py b/beets/dbcore/types.py index fc1f6e74e..40f6a0805 100644 --- a/beets/dbcore/types.py +++ b/beets/dbcore/types.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -15,22 +14,20 @@ """Representation of type information for DBCore model fields. """ -from __future__ import division, absolute_import, print_function from . import query from beets.util import str2bool -import six # Abstract base. -class Type(object): +class Type: """An object encapsulating the type of a model field. Includes information about how to store, query, format, and parse a given field. """ - sql = u'TEXT' + sql = 'TEXT' """The SQLite column type for the value. """ @@ -38,7 +35,7 @@ class Type(object): """The `Query` subclass to be used when querying the field. """ - model_type = six.text_type + model_type = str """The Python type that is used to represent the value in the model. The model is guaranteed to return a value of this type if the field @@ -60,11 +57,11 @@ class Type(object): value = self.null # `self.null` might be `None` if value is None: - value = u'' + value = '' if isinstance(value, bytes): value = value.decode('utf-8', 'ignore') - return six.text_type(value) + return str(value) def parse(self, string): """Parse a (possibly human-written) string and return the @@ -103,7 +100,7 @@ class Type(object): """ if isinstance(sql_value, memoryview): sql_value = bytes(sql_value).decode('utf-8', 'ignore') - if isinstance(sql_value, six.text_type): + if isinstance(sql_value, str): return self.parse(sql_value) else: return self.normalize(sql_value) @@ -124,7 +121,7 @@ class Default(Type): class Integer(Type): """A basic integer type. """ - sql = u'INTEGER' + sql = 'INTEGER' query = query.NumericQuery model_type = int @@ -145,7 +142,7 @@ class PaddedInt(Integer): self.digits = digits def format(self, value): - return u'{0:0{1}d}'.format(value or 0, self.digits) + return '{0:0{1}d}'.format(value or 0, self.digits) class NullPaddedInt(PaddedInt): @@ -158,12 +155,12 @@ class ScaledInt(Integer): """An integer whose formatting operation scales the number by a constant and adds a suffix. Good for units with large magnitudes. """ - def __init__(self, unit, suffix=u''): + def __init__(self, unit, suffix=''): self.unit = unit self.suffix = suffix def format(self, value): - return u'{0}{1}'.format((value or 0) // self.unit, self.suffix) + return '{}{}'.format((value or 0) // self.unit, self.suffix) class Id(Integer): @@ -174,14 +171,14 @@ class Id(Integer): def __init__(self, primary=True): if primary: - self.sql = u'INTEGER PRIMARY KEY' + self.sql = 'INTEGER PRIMARY KEY' class Float(Type): """A basic floating-point type. The `digits` parameter specifies how many decimal places to use in the human-readable representation. """ - sql = u'REAL' + sql = 'REAL' query = query.NumericQuery model_type = float @@ -189,7 +186,7 @@ class Float(Type): self.digits = digits def format(self, value): - return u'{0:.{1}f}'.format(value or 0, self.digits) + return '{0:.{1}f}'.format(value or 0, self.digits) class NullFloat(Float): @@ -201,7 +198,7 @@ class NullFloat(Float): class String(Type): """A Unicode string type. """ - sql = u'TEXT' + sql = 'TEXT' query = query.SubstringQuery def normalize(self, value): @@ -214,12 +211,12 @@ class String(Type): class Boolean(Type): """A boolean type. """ - sql = u'INTEGER' + sql = 'INTEGER' query = query.BooleanQuery model_type = bool def format(self, value): - return six.text_type(bool(value)) + return str(bool(value)) def parse(self, string): return str2bool(string) From 432fa557258d9ff01e23ed750f9a86a96239599e Mon Sep 17 00:00:00 2001 From: Andrew Rogl Date: Wed, 25 Aug 2021 18:56:32 +1000 Subject: [PATCH 05/26] pyupgrade ui directory --- beets/ui/__init__.py | 197 +++++++------- beets/ui/commands.py | 612 +++++++++++++++++++++---------------------- 2 files changed, 402 insertions(+), 407 deletions(-) diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index af5b77007..662528658 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -18,7 +17,6 @@ interface. To invoke the CLI, just call beets.ui.main(). The actual CLI commands are implemented in the ui.commands module. """ -from __future__ import division, absolute_import, print_function import optparse import textwrap @@ -30,7 +28,6 @@ import re import struct import traceback import os.path -from six.moves import input from beets import logging from beets import library @@ -62,8 +59,8 @@ log.propagate = False # Don't propagate to root handler. PF_KEY_QUERIES = { - 'comp': u'comp:true', - 'singleton': u'singleton:true', + 'comp': 'comp:true', + 'singleton': 'singleton:true', } @@ -128,11 +125,11 @@ def print_(*strings, **kwargs): (it defaults to a newline). """ if not strings: - strings = [u''] - assert isinstance(strings[0], six.text_type) + strings = [''] + assert isinstance(strings[0], str) - txt = u' '.join(strings) - txt += kwargs.get('end', u'\n') + txt = ' '.join(strings) + txt += kwargs.get('end', '\n') # Encode the string and write it to stdout. # On Python 3, sys.stdout expects text strings and uses the @@ -198,12 +195,12 @@ def input_(prompt=None): # use print_() explicitly to display prompts. # https://bugs.python.org/issue1927 if prompt: - print_(prompt, end=u' ') + print_(prompt, end=' ') try: resp = input() except EOFError: - raise UserError(u'stdin stream ended while input required') + raise UserError('stdin stream ended while input required') return resp @@ -249,7 +246,7 @@ def input_options(options, require=False, prompt=None, fallback_prompt=None, found_letter = letter break else: - raise ValueError(u'no unambiguous lettering found') + raise ValueError('no unambiguous lettering found') letters[found_letter.lower()] = option index = option.index(found_letter) @@ -257,7 +254,7 @@ def input_options(options, require=False, prompt=None, fallback_prompt=None, # Mark the option's shortcut letter for display. if not require and ( (default is None and not numrange and first) or - (isinstance(default, six.string_types) and + (isinstance(default, str) and found_letter.lower() == default.lower())): # The first option is the default; mark it. show_letter = '[%s]' % found_letter.upper() @@ -293,11 +290,11 @@ def input_options(options, require=False, prompt=None, fallback_prompt=None, prompt_part_lengths = [] if numrange: if isinstance(default, int): - default_name = six.text_type(default) + default_name = str(default) default_name = colorize('action_default', default_name) tmpl = '# selection (default %s)' prompt_parts.append(tmpl % default_name) - prompt_part_lengths.append(len(tmpl % six.text_type(default))) + prompt_part_lengths.append(len(tmpl % str(default))) else: prompt_parts.append('# selection') prompt_part_lengths.append(len(prompt_parts[-1])) @@ -332,9 +329,9 @@ def input_options(options, require=False, prompt=None, fallback_prompt=None, # Make a fallback prompt too. This is displayed if the user enters # something that is not recognized. if not fallback_prompt: - fallback_prompt = u'Enter one of ' + fallback_prompt = 'Enter one of ' if numrange: - fallback_prompt += u'%i-%i, ' % numrange + fallback_prompt += '%i-%i, ' % numrange fallback_prompt += ', '.join(display_letters) + ':' resp = input_(prompt) @@ -373,9 +370,9 @@ def input_yn(prompt, require=False): "yes" unless `require` is `True`, in which case there is no default. """ sel = input_options( - ('y', 'n'), require, prompt, u'Enter Y or N:' + ('y', 'n'), require, prompt, 'Enter Y or N:' ) - return sel == u'y' + return sel == 'y' def input_select_objects(prompt, objs, rep, prompt_all=None): @@ -389,24 +386,24 @@ def input_select_objects(prompt, objs, rep, prompt_all=None): objects individually. """ choice = input_options( - (u'y', u'n', u's'), False, - u'%s? (Yes/no/select)' % (prompt_all or prompt)) + ('y', 'n', 's'), False, + '%s? (Yes/no/select)' % (prompt_all or prompt)) print() # Blank line. - if choice == u'y': # Yes. + if choice == 'y': # Yes. return objs - elif choice == u's': # Select. + elif choice == 's': # Select. out = [] for obj in objs: rep(obj) answer = input_options( - ('y', 'n', 'q'), True, u'%s? (yes/no/quit)' % prompt, - u'Enter Y or N:' + ('y', 'n', 'q'), True, '%s? (yes/no/quit)' % prompt, + 'Enter Y or N:' ) - if answer == u'y': + if answer == 'y': out.append(obj) - elif answer == u'q': + elif answer == 'q': return out return out @@ -418,14 +415,14 @@ def input_select_objects(prompt, objs, rep, prompt_all=None): def human_bytes(size): """Formats size, a number of bytes, in a human-readable way.""" - powers = [u'', u'K', u'M', u'G', u'T', u'P', u'E', u'Z', u'Y', u'H'] + powers = ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y', 'H'] unit = 'B' for power in powers: if size < 1024: - return u"%3.1f %s%s" % (size, power, unit) + return f"{size:3.1f} {power}{unit}" size /= 1024.0 - unit = u'iB' - return u"big" + unit = 'iB' + return "big" def human_seconds(interval): @@ -433,13 +430,13 @@ def human_seconds(interval): interval using English words. """ units = [ - (1, u'second'), - (60, u'minute'), - (60, u'hour'), - (24, u'day'), - (7, u'week'), - (52, u'year'), - (10, u'decade'), + (1, 'second'), + (60, 'minute'), + (60, 'hour'), + (24, 'day'), + (7, 'week'), + (52, 'year'), + (10, 'decade'), ] for i in range(len(units) - 1): increment, suffix = units[i] @@ -452,7 +449,7 @@ def human_seconds(interval): increment, suffix = units[-1] interval /= float(increment) - return u"%3.1f %ss" % (interval, suffix) + return f"{interval:3.1f} {suffix}s" def human_seconds_short(interval): @@ -460,7 +457,7 @@ def human_seconds_short(interval): string. """ interval = int(interval) - return u'%i:%02i' % (interval // 60, interval % 60) + return '%i:%02i' % (interval // 60, interval % 60) # Colorization. @@ -513,7 +510,7 @@ def _colorize(color, text): elif color in LIGHT_COLORS: escape = COLOR_ESCAPE + "%i;01m" % (LIGHT_COLORS[color] + 30) else: - raise ValueError(u'no such color %s', color) + raise ValueError('no such color %s', color) return escape + text + RESET_COLOR @@ -526,14 +523,14 @@ def colorize(color_name, text): global COLORS if not COLORS: - COLORS = dict((name, - config['ui']['colors'][name].as_str()) - for name in COLOR_NAMES) + COLORS = {name: + config['ui']['colors'][name].as_str() + for name in COLOR_NAMES} # In case a 3rd party plugin is still passing the actual color ('red') # instead of the abstract color name ('text_error') color = COLORS.get(color_name) if not color: - log.debug(u'Invalid color_name: {0}', color_name) + log.debug('Invalid color_name: {0}', color_name) color = color_name return _colorize(color, text) @@ -545,11 +542,11 @@ def _colordiff(a, b, highlight='text_highlight', highlighted intelligently to show differences; other values are stringified and highlighted in their entirety. """ - if not isinstance(a, six.string_types) \ - or not isinstance(b, six.string_types): + if not isinstance(a, str) \ + or not isinstance(b, str): # Non-strings: use ordinary equality. - a = six.text_type(a) - b = six.text_type(b) + a = str(a) + b = str(b) if a == b: return a, b else: @@ -587,7 +584,7 @@ def _colordiff(a, b, highlight='text_highlight', else: assert(False) - return u''.join(a_out), u''.join(b_out) + return ''.join(a_out), ''.join(b_out) def colordiff(a, b, highlight='text_highlight'): @@ -597,7 +594,7 @@ def colordiff(a, b, highlight='text_highlight'): if config['ui']['color']: return _colordiff(a, b, highlight) else: - return six.text_type(a), six.text_type(b) + return str(a), str(b) def get_path_formats(subview=None): @@ -622,7 +619,7 @@ def get_replacements(): replacements.append((re.compile(pattern), repl)) except re.error: raise UserError( - u'malformed regular expression in replace: {0}'.format( + 'malformed regular expression in replace: {}'.format( pattern ) ) @@ -643,7 +640,7 @@ def term_width(): try: buf = fcntl.ioctl(0, termios.TIOCGWINSZ, ' ' * 4) - except IOError: + except OSError: return fallback try: height, width = struct.unpack('hh', buf) @@ -671,18 +668,18 @@ def _field_diff(field, old, old_fmt, new, new_fmt): return None # Get formatted values for output. - oldstr = old_fmt.get(field, u'') - newstr = new_fmt.get(field, u'') + oldstr = old_fmt.get(field, '') + newstr = new_fmt.get(field, '') # For strings, highlight changes. For others, colorize the whole # thing. - if isinstance(oldval, six.string_types): + if isinstance(oldval, str): oldstr, newstr = colordiff(oldval, newstr) else: oldstr = colorize('text_error', oldstr) newstr = colorize('text_error', newstr) - return u'{0} -> {1}'.format(oldstr, newstr) + return f'{oldstr} -> {newstr}' def show_model_changes(new, old=None, fields=None, always=False): @@ -712,14 +709,14 @@ def show_model_changes(new, old=None, fields=None, always=False): # Detect and show difference for this field. line = _field_diff(field, old, old_fmt, new, new_fmt) if line: - changes.append(u' {0}: {1}'.format(field, line)) + changes.append(f' {field}: {line}') # New fields. for field in set(new) - set(old): if fields and field not in fields: continue - changes.append(u' {0}: {1}'.format( + changes.append(' {}: {}'.format( field, colorize('text_highlight', new_fmt[field]) )) @@ -728,7 +725,7 @@ def show_model_changes(new, old=None, fields=None, always=False): if changes or always: print_(format(old)) if changes: - print_(u'\n'.join(changes)) + print_('\n'.join(changes)) return bool(changes) @@ -761,15 +758,15 @@ def show_path_changes(path_changes): if max_width > col_width: # Print every change over two lines for source, dest in zip(sources, destinations): - log.info(u'{0} \n -> {1}', source, dest) + log.info('{0} \n -> {1}', source, dest) else: # Print every change on a single line, and add a header title_pad = max_width - len('Source ') + len(' -> ') - log.info(u'Source {0} Destination', ' ' * title_pad) + log.info('Source {0} Destination', ' ' * title_pad) for source, dest in zip(sources, destinations): pad = max_width - len(source) - log.info(u'{0} {1} -> {2}', source, ' ' * pad, dest) + log.info('{0} {1} -> {2}', source, ' ' * pad, dest) # Helper functions for option parsing. @@ -797,13 +794,13 @@ def _store_dict(option, opt_str, value, parser): raise ValueError except ValueError: raise UserError( - "supplied argument `{0}' is not of the form `key=value'" + "supplied argument `{}' is not of the form `key=value'" .format(value)) option_values[key] = value -class CommonOptionsParser(optparse.OptionParser, object): +class CommonOptionsParser(optparse.OptionParser): """Offers a simple way to add common formatting options. Options available include: @@ -819,7 +816,7 @@ class CommonOptionsParser(optparse.OptionParser, object): Each method is fully documented in the related method. """ def __init__(self, *args, **kwargs): - super(CommonOptionsParser, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self._album_flags = False # this serves both as an indicator that we offer the feature AND allows # us to check whether it has been specified on the CLI - bypassing the @@ -833,7 +830,7 @@ class CommonOptionsParser(optparse.OptionParser, object): Sets the album property on the options extracted from the CLI. """ album = optparse.Option(*flags, action='store_true', - help=u'match albums instead of tracks') + help='match albums instead of tracks') self.add_option(album) self._album_flags = set(flags) @@ -851,7 +848,7 @@ class CommonOptionsParser(optparse.OptionParser, object): elif value: value, = decargs([value]) else: - value = u'' + value = '' parser.values.format = value if target: @@ -883,9 +880,9 @@ class CommonOptionsParser(optparse.OptionParser, object): """ path = optparse.Option(*flags, nargs=0, action='callback', callback=self._set_format, - callback_kwargs={'fmt': u'$path', + callback_kwargs={'fmt': '$path', 'store_true': True}, - help=u'print paths for matched items or albums') + help='print paths for matched items or albums') self.add_option(path) def add_format_option(self, flags=('-f', '--format'), target=None): @@ -905,7 +902,7 @@ class CommonOptionsParser(optparse.OptionParser, object): """ kwargs = {} if target: - if isinstance(target, six.string_types): + if isinstance(target, str): target = {'item': library.Item, 'album': library.Album}[target] kwargs['target'] = target @@ -913,7 +910,7 @@ class CommonOptionsParser(optparse.OptionParser, object): opt = optparse.Option(*flags, action='callback', callback=self._set_format, callback_kwargs=kwargs, - help=u'print with custom format') + help='print with custom format') self.add_option(opt) def add_all_common_options(self): @@ -932,7 +929,7 @@ class CommonOptionsParser(optparse.OptionParser, object): # There you will also find a better description of the code and a more # succinct example program. -class Subcommand(object): +class Subcommand: """A subcommand of a root command-line application that may be invoked by a SubcommandOptionParser. """ @@ -963,7 +960,7 @@ class Subcommand(object): @root_parser.setter def root_parser(self, root_parser): self._root_parser = root_parser - self.parser.prog = '{0} {1}'.format( + self.parser.prog = '{} {}'.format( as_string(root_parser.get_prog_name()), self.name) @@ -979,13 +976,13 @@ class SubcommandsOptionParser(CommonOptionsParser): """ # A more helpful default usage. if 'usage' not in kwargs: - kwargs['usage'] = u""" + kwargs['usage'] = """ %prog COMMAND [ARGS...] %prog help COMMAND""" kwargs['add_help_option'] = False # Super constructor. - super(SubcommandsOptionParser, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # Our root parser needs to stop on the first unrecognized argument. self.disable_interspersed_args() @@ -1002,7 +999,7 @@ class SubcommandsOptionParser(CommonOptionsParser): # Add the list of subcommands to the help message. def format_help(self, formatter=None): # Get the original help message, to which we will append. - out = super(SubcommandsOptionParser, self).format_help(formatter) + out = super().format_help(formatter) if formatter is None: formatter = self.formatter @@ -1088,7 +1085,7 @@ class SubcommandsOptionParser(CommonOptionsParser): cmdname = args.pop(0) subcommand = self._subcommand_for_name(cmdname) if not subcommand: - raise UserError(u"unknown command '{0}'".format(cmdname)) + raise UserError(f"unknown command '{cmdname}'") suboptions, subargs = subcommand.parse_args(args) return subcommand, suboptions, subargs @@ -1104,7 +1101,7 @@ def _load_plugins(options, config): """ paths = config['pluginpath'].as_str_seq(split=False) paths = [util.normpath(p) for p in paths] - log.debug(u'plugin paths: {0}', util.displayable_path(paths)) + log.debug('plugin paths: {0}', util.displayable_path(paths)) # On Python 3, the search paths need to be unicode. paths = [util.py3_path(p) for p in paths] @@ -1186,18 +1183,18 @@ def _configure(options): log.set_global_level(logging.INFO) if overlay_path: - log.debug(u'overlaying configuration: {0}', + log.debug('overlaying configuration: {0}', util.displayable_path(overlay_path)) config_path = config.user_config_path() if os.path.isfile(config_path): - log.debug(u'user configuration: {0}', + log.debug('user configuration: {0}', util.displayable_path(config_path)) else: - log.debug(u'no user configuration found at {0}', + log.debug('no user configuration found at {0}', util.displayable_path(config_path)) - log.debug(u'data directory: {0}', + log.debug('data directory: {0}', util.displayable_path(config.config_dir())) return config @@ -1215,13 +1212,13 @@ def _open_library(config): ) lib.get_item(0) # Test database connection. except (sqlite3.OperationalError, sqlite3.DatabaseError) as db_error: - log.debug(u'{}', traceback.format_exc()) - raise UserError(u"database file {0} cannot not be opened: {1}".format( + log.debug('{}', traceback.format_exc()) + raise UserError("database file {} cannot not be opened: {}".format( util.displayable_path(dbpath), db_error )) - log.debug(u'library database: {0}\n' - u'library directory: {1}', + log.debug('library database: {0}\n' + 'library directory: {1}', util.displayable_path(lib.path), util.displayable_path(lib.directory)) return lib @@ -1235,17 +1232,17 @@ def _raw_main(args, lib=None): parser.add_format_option(flags=('--format-item',), target=library.Item) parser.add_format_option(flags=('--format-album',), target=library.Album) parser.add_option('-l', '--library', dest='library', - help=u'library database file to use') + help='library database file to use') parser.add_option('-d', '--directory', dest='directory', - help=u"destination music directory") + help="destination music directory") parser.add_option('-v', '--verbose', dest='verbose', action='count', - help=u'log more details (use twice for even more)') + help='log more details (use twice for even more)') parser.add_option('-c', '--config', dest='config', - help=u'path to configuration file') + help='path to configuration file') parser.add_option('-p', '--plugins', dest='plugins', - help=u'a comma-separated list of plugins to load') + help='a comma-separated list of plugins to load') parser.add_option('-h', '--help', dest='help', action='store_true', - help=u'show this help message and exit') + help='show this help message and exit') parser.add_option('--version', dest='version', action='store_true', help=optparse.SUPPRESS_HELP) @@ -1280,7 +1277,7 @@ def main(args=None): _raw_main(args) except UserError as exc: message = exc.args[0] if exc.args else None - log.error(u'error: {0}', message) + log.error('error: {0}', message) sys.exit(1) except util.HumanReadableException as exc: exc.log(log) @@ -1292,12 +1289,12 @@ def main(args=None): log.error('{}', exc) sys.exit(1) except confuse.ConfigError as exc: - log.error(u'configuration error: {0}', exc) + log.error('configuration error: {0}', exc) sys.exit(1) except db_query.InvalidQueryError as exc: - log.error(u'invalid query: {0}', exc) + log.error('invalid query: {0}', exc) sys.exit(1) - except IOError as exc: + except OSError as exc: if exc.errno == errno.EPIPE: # "Broken pipe". End silently. sys.stderr.close() @@ -1305,11 +1302,11 @@ def main(args=None): raise except KeyboardInterrupt: # Silently ignore ^C except in verbose mode. - log.debug(u'{}', traceback.format_exc()) + log.debug('{}', traceback.format_exc()) except db.DBAccessError as exc: log.error( - u'database access error: {0}\n' - u'the library file might have a permissions problem', + 'database access error: {0}\n' + 'the library file might have a permissions problem', exc ) sys.exit(1) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 456e0afb7..9fd420aa2 100755 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -17,7 +16,6 @@ interface. """ -from __future__ import division, absolute_import, print_function import os import re @@ -42,7 +40,7 @@ from beets import logging import six from . import _store_dict -VARIOUS_ARTISTS = u'Various Artists' +VARIOUS_ARTISTS = 'Various Artists' PromptChoice = namedtuple('PromptChoice', ['short', 'long', 'callback']) # Global logger. @@ -74,9 +72,9 @@ def _do_query(lib, query, album, also_items=True): items = list(lib.items(query)) if album and not albums: - raise ui.UserError(u'No matching albums found.') + raise ui.UserError('No matching albums found.') elif not album and not items: - raise ui.UserError(u'No matching items found.') + raise ui.UserError('No matching items found.') return items, albums @@ -88,33 +86,33 @@ def _print_keys(query): returned row, with indentation of 2 spaces. """ for row in query: - print_(u' ' * 2 + row['key']) + print_(' ' * 2 + row['key']) def fields_func(lib, opts, args): def _print_rows(names): names.sort() - print_(u' ' + u'\n '.join(names)) + print_(' ' + '\n '.join(names)) - print_(u"Item fields:") + print_("Item fields:") _print_rows(library.Item.all_keys()) - print_(u"Album fields:") + print_("Album fields:") _print_rows(library.Album.all_keys()) with lib.transaction() as tx: # The SQL uses the DISTINCT to get unique values from the query unique_fields = 'SELECT DISTINCT key FROM (%s)' - print_(u"Item flexible attributes:") + print_("Item flexible attributes:") _print_keys(tx.query(unique_fields % library.Item._flex_table)) - print_(u"Album flexible attributes:") + print_("Album flexible attributes:") _print_keys(tx.query(unique_fields % library.Album._flex_table)) fields_cmd = ui.Subcommand( 'fields', - help=u'show fields available for queries and format strings' + help='show fields available for queries and format strings' ) fields_cmd.func = fields_func default_commands.append(fields_cmd) @@ -125,9 +123,9 @@ default_commands.append(fields_cmd) class HelpCommand(ui.Subcommand): def __init__(self): - super(HelpCommand, self).__init__( + super().__init__( 'help', aliases=('?',), - help=u'give detailed help on a specific sub-command', + help='give detailed help on a specific sub-command', ) def func(self, lib, opts, args): @@ -135,7 +133,7 @@ class HelpCommand(ui.Subcommand): cmdname = args[0] helpcommand = self.root_parser._subcommand_for_name(cmdname) if not helpcommand: - raise ui.UserError(u"unknown command '{0}'".format(cmdname)) + raise ui.UserError(f"unknown command '{cmdname}'") helpcommand.print_help() else: self.root_parser.print_help() @@ -160,13 +158,13 @@ def disambig_string(info): if isinstance(info, hooks.AlbumInfo): if info.media: if info.mediums and info.mediums > 1: - disambig.append(u'{0}x{1}'.format( + disambig.append('{}x{}'.format( info.mediums, info.media )) else: disambig.append(info.media) if info.year: - disambig.append(six.text_type(info.year)) + disambig.append(str(info.year)) if info.country: disambig.append(info.country) if info.label: @@ -177,14 +175,14 @@ def disambig_string(info): disambig.append(info.albumdisambig) if disambig: - return u', '.join(disambig) + return ', '.join(disambig) def dist_string(dist): """Formats a distance (a float) as a colorized similarity percentage string. """ - out = u'%.1f%%' % ((1 - dist) * 100) + out = '%.1f%%' % ((1 - dist) * 100) if dist <= config['match']['strong_rec_thresh'].as_number(): out = ui.colorize('text_success', out) elif dist <= config['match']['medium_rec_thresh'].as_number(): @@ -207,7 +205,7 @@ def penalty_string(distance, limit=None): if penalties: if limit and len(penalties) > limit: penalties = penalties[:limit] + ['...'] - return ui.colorize('text_warning', u'(%s)' % ', '.join(penalties)) + return ui.colorize('text_warning', '(%s)' % ', '.join(penalties)) def show_change(cur_artist, cur_album, match): @@ -217,11 +215,11 @@ def show_change(cur_artist, cur_album, match): """ def show_album(artist, album): if artist: - album_description = u' %s - %s' % (artist, album) + album_description = f' {artist} - {album}' elif album: - album_description = u' %s' % album + album_description = ' %s' % album else: - album_description = u' (unknown album)' + album_description = ' (unknown album)' print_(album_description) def format_index(track_info): @@ -239,12 +237,12 @@ def show_change(cur_artist, cur_album, match): mediums = track_info.disctotal if config['per_disc_numbering']: if mediums and mediums > 1: - return u'{0}-{1}'.format(medium, medium_index) + return f'{medium}-{medium_index}' else: - return six.text_type(medium_index if medium_index is not None + return str(medium_index if medium_index is not None else index) else: - return six.text_type(index) + return str(index) # Identify the album in question. if cur_artist != match.info.artist or \ @@ -254,7 +252,7 @@ def show_change(cur_artist, cur_album, match): album_l, album_r = cur_album or '', match.info.album if artist_r == VARIOUS_ARTISTS: # Hide artists for VA releases. - artist_l, artist_r = u'', u'' + artist_l, artist_r = '', '' if config['artist_credit']: artist_r = match.info.artist_credit @@ -262,21 +260,21 @@ def show_change(cur_artist, cur_album, match): artist_l, artist_r = ui.colordiff(artist_l, artist_r) album_l, album_r = ui.colordiff(album_l, album_r) - print_(u"Correcting tags from:") + print_("Correcting tags from:") show_album(artist_l, album_l) - print_(u"To:") + print_("To:") show_album(artist_r, album_r) else: - print_(u"Tagging:\n {0.artist} - {0.album}".format(match.info)) + print_("Tagging:\n {0.artist} - {0.album}".format(match.info)) # Data URL. if match.info.data_url: - print_(u'URL:\n %s' % match.info.data_url) + print_('URL:\n %s' % match.info.data_url) # Info line. info = [] # Similarity. - info.append(u'(Similarity: %s)' % dist_string(match.distance)) + info.append('(Similarity: %s)' % dist_string(match.distance)) # Penalties. penalties = penalty_string(match.distance) if penalties: @@ -284,7 +282,7 @@ def show_change(cur_artist, cur_album, match): # Disambiguation. disambig = disambig_string(match.info) if disambig: - info.append(ui.colorize('text_highlight_minor', u'(%s)' % disambig)) + info.append(ui.colorize('text_highlight_minor', '(%s)' % disambig)) print_(' '.join(info)) # Tracks. @@ -302,16 +300,16 @@ def show_change(cur_artist, cur_album, match): if medium != track_info.medium or disctitle != track_info.disctitle: media = match.info.media or 'Media' if match.info.mediums > 1 and track_info.disctitle: - lhs = u'%s %s: %s' % (media, track_info.medium, + lhs = '{} {}: {}'.format(media, track_info.medium, track_info.disctitle) elif match.info.mediums > 1: - lhs = u'%s %s' % (media, track_info.medium) + lhs = f'{media} {track_info.medium}' elif track_info.disctitle: - lhs = u'%s: %s' % (media, track_info.disctitle) + lhs = f'{media}: {track_info.disctitle}' else: lhs = None if lhs: - lines.append((lhs, u'', 0)) + lines.append((lhs, '', 0)) medium, disctitle = track_info.medium, track_info.disctitle # Titles. @@ -332,7 +330,7 @@ def show_change(cur_artist, cur_album, match): color = 'text_highlight_minor' else: color = 'text_highlight' - templ = ui.colorize(color, u' (#{0})') + templ = ui.colorize(color, ' (#{0})') lhs += templ.format(cur_track) rhs += templ.format(new_track) lhs_width += len(cur_track) + 4 @@ -343,7 +341,7 @@ def show_change(cur_artist, cur_album, match): config['ui']['length_diff_thresh'].as_number(): cur_length = ui.human_seconds_short(item.length) new_length = ui.human_seconds_short(track_info.length) - templ = ui.colorize('text_highlight', u' ({0})') + templ = ui.colorize('text_highlight', ' ({0})') lhs += templ.format(cur_length) rhs += templ.format(new_length) lhs_width += len(cur_length) + 3 @@ -354,9 +352,9 @@ def show_change(cur_artist, cur_album, match): rhs += ' %s' % penalties if lhs != rhs: - lines.append((u' * %s' % lhs, rhs, lhs_width)) + lines.append((' * %s' % lhs, rhs, lhs_width)) elif config['import']['detail']: - lines.append((u' * %s' % lhs, '', lhs_width)) + lines.append((' * %s' % lhs, '', lhs_width)) # Print each track in two columns, or across two lines. col_width = (ui.term_width() - len(''.join([' * ', ' -> ']))) // 2 @@ -366,14 +364,14 @@ def show_change(cur_artist, cur_album, match): if not rhs: print_(lhs) elif max_width > col_width: - print_(u'%s ->\n %s' % (lhs, rhs)) + print_(f'{lhs} ->\n {rhs}') else: pad = max_width - lhs_width - print_(u'%s%s -> %s' % (lhs, ' ' * pad, rhs)) + print_('{}{} -> {}'.format(lhs, ' ' * pad, rhs)) # Missing and unmatched tracks. if match.extra_tracks: - print_(u'Missing tracks ({0}/{1} - {2:.1%}):'.format( + print_('Missing tracks ({}/{} - {:.1%}):'.format( len(match.extra_tracks), len(match.info.tracks), len(match.extra_tracks) / len(match.info.tracks) @@ -381,21 +379,21 @@ def show_change(cur_artist, cur_album, match): pad_width = max(len(track_info.title) for track_info in match.extra_tracks) for track_info in match.extra_tracks: - line = u' ! {0: <{width}} (#{1: >2})'.format(track_info.title, + line = ' ! {0: <{width}} (#{1: >2})'.format(track_info.title, format_index(track_info), width=pad_width) if track_info.length: - line += u' (%s)' % ui.human_seconds_short(track_info.length) + line += ' (%s)' % ui.human_seconds_short(track_info.length) print_(ui.colorize('text_warning', line)) if match.extra_items: - print_(u'Unmatched tracks ({0}):'.format(len(match.extra_items))) + print_('Unmatched tracks ({}):'.format(len(match.extra_items))) pad_width = max(len(item.title) for item in match.extra_items) for item in match.extra_items: - line = u' ! {0: <{width}} (#{1: >2})'.format(item.title, + line = ' ! {0: <{width}} (#{1: >2})'.format(item.title, format_index(item), width=pad_width) if item.length: - line += u' (%s)' % ui.human_seconds_short(item.length) + line += ' (%s)' % ui.human_seconds_short(item.length) print_(ui.colorize('text_warning', line)) @@ -410,22 +408,22 @@ def show_item_change(item, match): cur_artist, new_artist = ui.colordiff(cur_artist, new_artist) cur_title, new_title = ui.colordiff(cur_title, new_title) - print_(u"Correcting track tags from:") - print_(u" %s - %s" % (cur_artist, cur_title)) - print_(u"To:") - print_(u" %s - %s" % (new_artist, new_title)) + print_("Correcting track tags from:") + print_(f" {cur_artist} - {cur_title}") + print_("To:") + print_(f" {new_artist} - {new_title}") else: - print_(u"Tagging track: %s - %s" % (cur_artist, cur_title)) + print_(f"Tagging track: {cur_artist} - {cur_title}") # Data URL. if match.info.data_url: - print_(u'URL:\n %s' % match.info.data_url) + print_('URL:\n %s' % match.info.data_url) # Info line. info = [] # Similarity. - info.append(u'(Similarity: %s)' % dist_string(match.distance)) + info.append('(Similarity: %s)' % dist_string(match.distance)) # Penalties. penalties = penalty_string(match.distance) if penalties: @@ -433,7 +431,7 @@ def show_item_change(item, match): # Disambiguation. disambig = disambig_string(match.info) if disambig: - info.append(ui.colorize('text_highlight_minor', u'(%s)' % disambig)) + info.append(ui.colorize('text_highlight_minor', '(%s)' % disambig)) print_(' '.join(info)) @@ -447,7 +445,7 @@ def summarize_items(items, singleton): """ summary_parts = [] if not singleton: - summary_parts.append(u"{0} items".format(len(items))) + summary_parts.append("{} items".format(len(items))) format_counts = {} for item in items: @@ -461,21 +459,21 @@ def summarize_items(items, singleton): format_counts.items(), key=lambda fmt_and_count: (-fmt_and_count[1], fmt_and_count[0]) ): - summary_parts.append('{0} {1}'.format(fmt, count)) + summary_parts.append(f'{fmt} {count}') if items: average_bitrate = sum([item.bitrate for item in items]) / len(items) total_duration = sum([item.length for item in items]) total_filesize = sum([item.filesize for item in items]) - summary_parts.append(u'{0}kbps'.format(int(average_bitrate / 1000))) + summary_parts.append('{}kbps'.format(int(average_bitrate / 1000))) if items[0].format == "FLAC": - sample_bits = u'{}kHz/{} bit'.format( + sample_bits = '{}kHz/{} bit'.format( round(int(items[0].samplerate) / 1000, 1), items[0].bitdepth) summary_parts.append(sample_bits) summary_parts.append(ui.human_seconds_short(total_duration)) summary_parts.append(ui.human_bytes(total_filesize)) - return u', '.join(summary_parts) + return ', '.join(summary_parts) def _summary_judgment(rec): @@ -506,9 +504,9 @@ def _summary_judgment(rec): return None if action == importer.action.SKIP: - print_(u'Skipping.') + print_('Skipping.') elif action == importer.action.ASIS: - print_(u'Importing as-is.') + print_('Importing as-is.') return action @@ -543,12 +541,12 @@ def choose_candidate(candidates, singleton, rec, cur_artist=None, # Zero candidates. if not candidates: if singleton: - print_(u"No matching recordings found.") + print_("No matching recordings found.") else: - print_(u"No matching release found for {0} tracks." + print_("No matching release found for {} tracks." .format(itemcount)) - print_(u'For help, see: ' - u'https://beets.readthedocs.org/en/latest/faq.html#nomatch') + print_('For help, see: ' + 'https://beets.readthedocs.org/en/latest/faq.html#nomatch') sel = ui.input_options(choice_opts) if sel in choice_actions: return choice_actions[sel] @@ -567,22 +565,22 @@ def choose_candidate(candidates, singleton, rec, cur_artist=None, if not bypass_candidates: # Display list of candidates. - print_(u'Finding tags for {0} "{1} - {2}".'.format( - u'track' if singleton else u'album', + print_('Finding tags for {} "{} - {}".'.format( + 'track' if singleton else 'album', item.artist if singleton else cur_artist, item.title if singleton else cur_album, )) - print_(u'Candidates:') + print_('Candidates:') for i, match in enumerate(candidates): # Index, metadata, and distance. line = [ - u'{0}.'.format(i + 1), - u'{0} - {1}'.format( + '{}.'.format(i + 1), + '{} - {}'.format( match.info.artist, match.info.title if singleton else match.info.album, ), - u'({0})'.format(dist_string(match.distance)), + '({})'.format(dist_string(match.distance)), ] # Penalties. @@ -594,14 +592,14 @@ def choose_candidate(candidates, singleton, rec, cur_artist=None, disambig = disambig_string(match.info) if disambig: line.append(ui.colorize('text_highlight_minor', - u'(%s)' % disambig)) + '(%s)' % disambig)) - print_(u' '.join(line)) + print_(' '.join(line)) # Ask the user for a choice. sel = ui.input_options(choice_opts, numrange=(1, len(candidates))) - if sel == u'm': + if sel == 'm': pass elif sel in choice_actions: return choice_actions[sel] @@ -625,19 +623,19 @@ def choose_candidate(candidates, singleton, rec, cur_artist=None, # Ask for confirmation. default = config['import']['default_action'].as_choice({ - u'apply': u'a', - u'skip': u's', - u'asis': u'u', - u'none': None, + 'apply': 'a', + 'skip': 's', + 'asis': 'u', + 'none': None, }) if default is None: require = True # Bell ring when user interaction is needed. if config['import']['bell']: - ui.print_(u'\a', end=u'') - sel = ui.input_options((u'Apply', u'More candidates') + choice_opts, + ui.print_('\a', end='') + sel = ui.input_options(('Apply', 'More candidates') + choice_opts, require=require, default=default) - if sel == u'a': + if sel == 'a': return match elif sel in choice_actions: return choice_actions[sel] @@ -649,8 +647,8 @@ def manual_search(session, task): Input either an artist and album (for full albums) or artist and track name (for singletons) for manual search. """ - artist = input_(u'Artist:').strip() - name = input_(u'Album:' if task.is_album else u'Track:').strip() + artist = input_('Artist:').strip() + name = input_('Album:' if task.is_album else 'Track:').strip() if task.is_album: _, _, prop = autotag.tag_album( @@ -666,8 +664,8 @@ def manual_id(session, task): Input an ID, either for an album ("release") or a track ("recording"). """ - prompt = u'Enter {0} ID:'.format(u'release' if task.is_album - else u'recording') + prompt = 'Enter {} ID:'.format('release' if task.is_album + else 'recording') search_id = input_(prompt).strip() if task.is_album: @@ -695,8 +693,8 @@ class TerminalImportSession(importer.ImportSession): """ # Show what we're tagging. print_() - print_(displayable_path(task.paths, u'\n') + - u' ({0} items)'.format(len(task.items))) + print_(displayable_path(task.paths, '\n') + + ' ({} items)'.format(len(task.items))) # Let plugins display info or prompt the user before we go through the # process of selecting candidate. @@ -708,8 +706,8 @@ class TerminalImportSession(importer.ImportSession): return actions[0] elif len(actions) > 1: raise plugins.PluginConflictException( - u'Only one handler for `import_task_before_choice` may return ' - u'an action.') + 'Only one handler for `import_task_before_choice` may return ' + 'an action.') # Take immediate action if appropriate. action = _summary_judgment(task.rec) @@ -798,48 +796,48 @@ class TerminalImportSession(importer.ImportSession): """Decide what to do when a new album or item seems similar to one that's already in the library. """ - log.warning(u"This {0} is already in the library!", - (u"album" if task.is_album else u"item")) + log.warning("This {0} is already in the library!", + ("album" if task.is_album else "item")) if config['import']['quiet']: # In quiet mode, don't prompt -- just skip. - log.info(u'Skipping.') - sel = u's' + log.info('Skipping.') + sel = 's' else: # Print some detail about the existing and new items so the # user can make an informed decision. for duplicate in found_duplicates: - print_(u"Old: " + summarize_items( + print_("Old: " + summarize_items( list(duplicate.items()) if task.is_album else [duplicate], not task.is_album, )) - print_(u"New: " + summarize_items( + print_("New: " + summarize_items( task.imported_items(), not task.is_album, )) sel = ui.input_options( - (u'Skip new', u'Keep all', u'Remove old', u'Merge all') + ('Skip new', 'Keep all', 'Remove old', 'Merge all') ) - if sel == u's': + if sel == 's': # Skip new. task.set_choice(importer.action.SKIP) - elif sel == u'k': + elif sel == 'k': # Keep both. Do nothing; leave the choice intact. pass - elif sel == u'r': + elif sel == 'r': # Remove old. task.should_remove_duplicates = True - elif sel == u'm': + elif sel == 'm': task.should_merge_duplicates = True else: assert False def should_resume(self, path): - return ui.input_yn(u"Import of the directory:\n{0}\n" - u"was interrupted. Resume (Y/n)?" + return ui.input_yn("Import of the directory:\n{}\n" + "was interrupted. Resume (Y/n)?" .format(displayable_path(path))) def _get_choices(self, task): @@ -860,22 +858,22 @@ class TerminalImportSession(importer.ImportSession): """ # Standard, built-in choices. choices = [ - PromptChoice(u's', u'Skip', + PromptChoice('s', 'Skip', lambda s, t: importer.action.SKIP), - PromptChoice(u'u', u'Use as-is', + PromptChoice('u', 'Use as-is', lambda s, t: importer.action.ASIS) ] if task.is_album: choices += [ - PromptChoice(u't', u'as Tracks', + PromptChoice('t', 'as Tracks', lambda s, t: importer.action.TRACKS), - PromptChoice(u'g', u'Group albums', + PromptChoice('g', 'Group albums', lambda s, t: importer.action.ALBUMS), ] choices += [ - PromptChoice(u'e', u'Enter search', manual_search), - PromptChoice(u'i', u'enter Id', manual_id), - PromptChoice(u'b', u'aBort', abort_action), + PromptChoice('e', 'Enter search', manual_search), + PromptChoice('i', 'enter Id', manual_id), + PromptChoice('b', 'aBort', abort_action), ] # Send the before_choose_candidate event and flatten list. @@ -885,7 +883,7 @@ class TerminalImportSession(importer.ImportSession): # Add a "dummy" choice for the other baked-in option, for # duplicate checking. all_choices = [ - PromptChoice(u'a', u'Apply', None), + PromptChoice('a', 'Apply', None), ] + choices + extra_choices # Check for conflicts. @@ -898,8 +896,8 @@ class TerminalImportSession(importer.ImportSession): # Keep the first of the choices, removing the rest. dup_choices = [c for c in all_choices if c.short == short] for c in dup_choices[1:]: - log.warning(u"Prompt choice '{0}' removed due to conflict " - u"with '{1}' (short letter: '{2}')", + log.warning("Prompt choice '{0}' removed due to conflict " + "with '{1}' (short letter: '{2}')", c.long, dup_choices[0].long, c.short) extra_choices.remove(c) @@ -916,21 +914,21 @@ def import_files(lib, paths, query): # Check the user-specified directories. for path in paths: if not os.path.exists(syspath(normpath(path))): - raise ui.UserError(u'no such file or directory: {0}'.format( + raise ui.UserError('no such file or directory: {}'.format( displayable_path(path))) # Check parameter consistency. if config['import']['quiet'] and config['import']['timid']: - raise ui.UserError(u"can't be both quiet and timid") + raise ui.UserError("can't be both quiet and timid") # Open the log. if config['import']['log'].get() is not None: logpath = syspath(config['import']['log'].as_filename()) try: loghandler = logging.FileHandler(logpath) - except IOError: - raise ui.UserError(u"could not open log file for writing: " - u"{0}".format(displayable_path(logpath))) + except OSError: + raise ui.UserError("could not open log file for writing: " + "{}".format(displayable_path(logpath))) else: loghandler = None @@ -961,7 +959,7 @@ def import_func(lib, opts, args): query = None paths = args if not paths: - raise ui.UserError(u'no path specified') + raise ui.UserError('no path specified') # On Python 2, we used to get filenames as raw bytes, which is # what we need. On Python 3, we need to undo the "helpful" @@ -974,98 +972,98 @@ def import_func(lib, opts, args): import_cmd = ui.Subcommand( - u'import', help=u'import new music', aliases=(u'imp', u'im') + 'import', help='import new music', aliases=('imp', 'im') ) import_cmd.parser.add_option( - u'-c', u'--copy', action='store_true', default=None, - help=u"copy tracks into library directory (default)" + '-c', '--copy', action='store_true', default=None, + help="copy tracks into library directory (default)" ) import_cmd.parser.add_option( - u'-C', u'--nocopy', action='store_false', dest='copy', - help=u"don't copy tracks (opposite of -c)" + '-C', '--nocopy', action='store_false', dest='copy', + help="don't copy tracks (opposite of -c)" ) import_cmd.parser.add_option( - u'-m', u'--move', action='store_true', dest='move', - help=u"move tracks into the library (overrides -c)" + '-m', '--move', action='store_true', dest='move', + help="move tracks into the library (overrides -c)" ) import_cmd.parser.add_option( - u'-w', u'--write', action='store_true', default=None, - help=u"write new metadata to files' tags (default)" + '-w', '--write', action='store_true', default=None, + help="write new metadata to files' tags (default)" ) import_cmd.parser.add_option( - u'-W', u'--nowrite', action='store_false', dest='write', - help=u"don't write metadata (opposite of -w)" + '-W', '--nowrite', action='store_false', dest='write', + help="don't write metadata (opposite of -w)" ) import_cmd.parser.add_option( - u'-a', u'--autotag', action='store_true', dest='autotag', - help=u"infer tags for imported files (default)" + '-a', '--autotag', action='store_true', dest='autotag', + help="infer tags for imported files (default)" ) import_cmd.parser.add_option( - u'-A', u'--noautotag', action='store_false', dest='autotag', - help=u"don't infer tags for imported files (opposite of -a)" + '-A', '--noautotag', action='store_false', dest='autotag', + help="don't infer tags for imported files (opposite of -a)" ) import_cmd.parser.add_option( - u'-p', u'--resume', action='store_true', default=None, - help=u"resume importing if interrupted" + '-p', '--resume', action='store_true', default=None, + help="resume importing if interrupted" ) import_cmd.parser.add_option( - u'-P', u'--noresume', action='store_false', dest='resume', - help=u"do not try to resume importing" + '-P', '--noresume', action='store_false', dest='resume', + help="do not try to resume importing" ) import_cmd.parser.add_option( - u'-q', u'--quiet', action='store_true', dest='quiet', - help=u"never prompt for input: skip albums instead" + '-q', '--quiet', action='store_true', dest='quiet', + help="never prompt for input: skip albums instead" ) import_cmd.parser.add_option( - u'-l', u'--log', dest='log', - help=u'file to log untaggable albums for later review' + '-l', '--log', dest='log', + help='file to log untaggable albums for later review' ) import_cmd.parser.add_option( - u'-s', u'--singletons', action='store_true', - help=u'import individual tracks instead of full albums' + '-s', '--singletons', action='store_true', + help='import individual tracks instead of full albums' ) import_cmd.parser.add_option( - u'-t', u'--timid', dest='timid', action='store_true', - help=u'always confirm all actions' + '-t', '--timid', dest='timid', action='store_true', + help='always confirm all actions' ) import_cmd.parser.add_option( - u'-L', u'--library', dest='library', action='store_true', - help=u'retag items matching a query' + '-L', '--library', dest='library', action='store_true', + help='retag items matching a query' ) import_cmd.parser.add_option( - u'-i', u'--incremental', dest='incremental', action='store_true', - help=u'skip already-imported directories' + '-i', '--incremental', dest='incremental', action='store_true', + help='skip already-imported directories' ) import_cmd.parser.add_option( - u'-I', u'--noincremental', dest='incremental', action='store_false', - help=u'do not skip already-imported directories' + '-I', '--noincremental', dest='incremental', action='store_false', + help='do not skip already-imported directories' ) import_cmd.parser.add_option( - u'--from-scratch', dest='from_scratch', action='store_true', - help=u'erase existing metadata before applying new metadata' + '--from-scratch', dest='from_scratch', action='store_true', + help='erase existing metadata before applying new metadata' ) import_cmd.parser.add_option( - u'--flat', dest='flat', action='store_true', - help=u'import an entire tree as a single album' + '--flat', dest='flat', action='store_true', + help='import an entire tree as a single album' ) import_cmd.parser.add_option( - u'-g', u'--group-albums', dest='group_albums', action='store_true', - help=u'group tracks in a folder into separate albums' + '-g', '--group-albums', dest='group_albums', action='store_true', + help='group tracks in a folder into separate albums' ) import_cmd.parser.add_option( - u'--pretend', dest='pretend', action='store_true', - help=u'just print the files to import' + '--pretend', dest='pretend', action='store_true', + help='just print the files to import' ) import_cmd.parser.add_option( - u'-S', u'--search-id', dest='search_ids', action='append', + '-S', '--search-id', dest='search_ids', action='append', metavar='ID', - help=u'restrict matching to a specific metadata backend ID' + help='restrict matching to a specific metadata backend ID' ) import_cmd.parser.add_option( - u'--set', dest='set_fields', action='callback', + '--set', dest='set_fields', action='callback', callback=_store_dict, metavar='FIELD=VALUE', - help=u'set the given fields to the supplied values' + help='set the given fields to the supplied values' ) import_cmd.func = import_func default_commands.append(import_cmd) @@ -1073,7 +1071,7 @@ default_commands.append(import_cmd) # list: Query and show library contents. -def list_items(lib, query, album, fmt=u''): +def list_items(lib, query, album, fmt=''): """Print out items in lib matching query. If album, then search for albums instead of single items. """ @@ -1089,9 +1087,9 @@ def list_func(lib, opts, args): list_items(lib, decargs(args), opts.album) -list_cmd = ui.Subcommand(u'list', help=u'query the library', aliases=(u'ls',)) -list_cmd.parser.usage += u"\n" \ - u'Example: %prog -f \'$album: $title\' artist:beatles' +list_cmd = ui.Subcommand('list', help='query the library', aliases=('ls',)) +list_cmd.parser.usage += "\n" \ + 'Example: %prog -f \'$album: $title\' artist:beatles' list_cmd.parser.add_all_common_options() list_cmd.func = list_func default_commands.append(list_cmd) @@ -1119,7 +1117,7 @@ def update_items(lib, query, album, move, pretend, fields): # Item deleted? if not os.path.exists(syspath(item.path)): ui.print_(format(item)) - ui.print_(ui.colorize('text_error', u' deleted')) + ui.print_(ui.colorize('text_error', ' deleted')) if not pretend: item.remove(True) affected_albums.add(item.album_id) @@ -1127,7 +1125,7 @@ def update_items(lib, query, album, move, pretend, fields): # Did the item change since last checked? if item.current_mtime() <= item.mtime: - log.debug(u'skipping {0} because mtime is up to date ({1})', + log.debug('skipping {0} because mtime is up to date ({1})', displayable_path(item.path), item.mtime) continue @@ -1135,7 +1133,7 @@ def update_items(lib, query, album, move, pretend, fields): try: item.read() except library.ReadError as exc: - log.error(u'error reading {0}: {1}', + log.error('error reading {0}: {1}', displayable_path(item.path), exc) continue @@ -1146,7 +1144,7 @@ def update_items(lib, query, album, move, pretend, fields): old_item = lib.get_item(item.id) if old_item.albumartist == old_item.artist == item.artist: item.albumartist = old_item.albumartist - item._dirty.discard(u'albumartist') + item._dirty.discard('albumartist') # Check for and display changes. changed = ui.show_model_changes( @@ -1179,7 +1177,7 @@ def update_items(lib, query, album, move, pretend, fields): continue album = lib.get_album(album_id) if not album: # Empty albums have already been removed. - log.debug(u'emptied album {0}', album_id) + log.debug('emptied album {0}', album_id) continue first_item = album.items().get() @@ -1190,7 +1188,7 @@ def update_items(lib, query, album, move, pretend, fields): # Move album art (and any inconsistent items). if move and lib.directory in ancestry(first_item.path): - log.debug(u'moving album {0}', album_id) + log.debug('moving album {0}', album_id) # Manually moving and storing the album. items = list(album.items()) @@ -1213,25 +1211,25 @@ def update_func(lib, opts, args): update_cmd = ui.Subcommand( - u'update', help=u'update the library', aliases=(u'upd', u'up',) + 'update', help='update the library', aliases=('upd', 'up',) ) update_cmd.parser.add_album_option() update_cmd.parser.add_format_option() update_cmd.parser.add_option( - u'-m', u'--move', action='store_true', dest='move', - help=u"move files in the library directory" + '-m', '--move', action='store_true', dest='move', + help="move files in the library directory" ) update_cmd.parser.add_option( - u'-M', u'--nomove', action='store_false', dest='move', - help=u"don't move files in library" + '-M', '--nomove', action='store_false', dest='move', + help="don't move files in library" ) update_cmd.parser.add_option( - u'-p', u'--pretend', action='store_true', - help=u"show all changes but do nothing" + '-p', '--pretend', action='store_true', + help="show all changes but do nothing" ) update_cmd.parser.add_option( - u'-F', u'--field', default=None, action='append', dest='fields', - help=u'list of fields to update' + '-F', '--field', default=None, action='append', dest='fields', + help='list of fields to update' ) update_cmd.func = update_func default_commands.append(update_cmd) @@ -1250,21 +1248,21 @@ def remove_items(lib, query, album, delete, force): # Confirm file removal if not forcing removal. if not force: # Prepare confirmation with user. - album_str = u" in {} album{}".format( - len(albums), u's' if len(albums) > 1 else u'' + album_str = " in {} album{}".format( + len(albums), 's' if len(albums) > 1 else '' ) if album else "" if delete: - fmt = u'$path - $title' - prompt = u'Really DELETE' - prompt_all = u'Really DELETE {} file{}{}'.format( - len(items), u's' if len(items) > 1 else u'', album_str + fmt = '$path - $title' + prompt = 'Really DELETE' + prompt_all = 'Really DELETE {} file{}{}'.format( + len(items), 's' if len(items) > 1 else '', album_str ) else: - fmt = u'' - prompt = u'Really remove from the library?' - prompt_all = u'Really remove {} item{}{} from the library?'.format( - len(items), u's' if len(items) > 1 else u'', album_str + fmt = '' + prompt = 'Really remove from the library?' + prompt_all = 'Really remove {} item{}{} from the library?'.format( + len(items), 's' if len(items) > 1 else '', album_str ) # Helpers for printing affected items @@ -1300,15 +1298,15 @@ def remove_func(lib, opts, args): remove_cmd = ui.Subcommand( - u'remove', help=u'remove matching items from the library', aliases=(u'rm',) + 'remove', help='remove matching items from the library', aliases=('rm',) ) remove_cmd.parser.add_option( - u"-d", u"--delete", action="store_true", - help=u"also remove files from disk" + "-d", "--delete", action="store_true", + help="also remove files from disk" ) remove_cmd.parser.add_option( - u"-f", u"--force", action="store_true", - help=u"do not ask when removing items" + "-f", "--force", action="store_true", + help="do not ask when removing items" ) remove_cmd.parser.add_album_option() remove_cmd.func = remove_func @@ -1333,7 +1331,7 @@ def show_stats(lib, query, exact): try: total_size += os.path.getsize(syspath(item.path)) except OSError as exc: - log.info(u'could not get size of {}: {}', item.path, exc) + log.info('could not get size of {}: {}', item.path, exc) else: total_size += int(item.length * item.bitrate / 8) total_time += item.length @@ -1343,20 +1341,20 @@ def show_stats(lib, query, exact): if item.album_id: albums.add(item.album_id) - size_str = u'' + ui.human_bytes(total_size) + size_str = '' + ui.human_bytes(total_size) if exact: - size_str += u' ({0} bytes)'.format(total_size) + size_str += f' ({total_size} bytes)' - print_(u"""Tracks: {0} -Total time: {1}{2} -{3}: {4} -Artists: {5} -Albums: {6} -Album artists: {7}""".format( + print_("""Tracks: {} +Total time: {}{} +{}: {} +Artists: {} +Albums: {} +Album artists: {}""".format( total_items, ui.human_seconds(total_time), - u' ({0:.2f} seconds)'.format(total_time) if exact else '', - u'Total size' if exact else u'Approximate total size', + f' ({total_time:.2f} seconds)' if exact else '', + 'Total size' if exact else 'Approximate total size', size_str, len(artists), len(albums), @@ -1369,11 +1367,11 @@ def stats_func(lib, opts, args): stats_cmd = ui.Subcommand( - u'stats', help=u'show statistics about the library or a query' + 'stats', help='show statistics about the library or a query' ) stats_cmd.parser.add_option( - u'-e', u'--exact', action='store_true', - help=u'exact size and time' + '-e', '--exact', action='store_true', + help='exact size and time' ) stats_cmd.func = stats_func default_commands.append(stats_cmd) @@ -1382,18 +1380,18 @@ default_commands.append(stats_cmd) # version: Show current beets version. def show_version(lib, opts, args): - print_(u'beets version %s' % beets.__version__) - print_(u'Python version {}'.format(python_version())) + print_('beets version %s' % beets.__version__) + print_(f'Python version {python_version()}') # Show plugins. names = sorted(p.name for p in plugins.find_plugins()) if names: - print_(u'plugins:', ', '.join(names)) + print_('plugins:', ', '.join(names)) else: - print_(u'no plugins loaded') + print_('no plugins loaded') version_cmd = ui.Subcommand( - u'version', help=u'output version information' + 'version', help='output version information' ) version_cmd.func = show_version default_commands.append(version_cmd) @@ -1420,8 +1418,8 @@ def modify_items(lib, mods, dels, query, write, move, album, confirm): # Apply changes *temporarily*, preview them, and collect modified # objects. - print_(u'Modifying {0} {1}s.' - .format(len(objs), u'album' if album else u'item')) + print_('Modifying {} {}s.' + .format(len(objs), 'album' if album else 'item')) changed = [] for obj in objs: if print_and_modify(obj, mods, dels) and obj not in changed: @@ -1429,22 +1427,22 @@ def modify_items(lib, mods, dels, query, write, move, album, confirm): # Still something to do? if not changed: - print_(u'No changes to make.') + print_('No changes to make.') return # Confirm action. if confirm: if write and move: - extra = u', move and write tags' + extra = ', move and write tags' elif write: - extra = u' and write tags' + extra = ' and write tags' elif move: - extra = u' and move' + extra = ' and move' else: - extra = u'' + extra = '' changed = ui.input_select_objects( - u'Really modify%s' % extra, changed, + 'Really modify%s' % extra, changed, lambda o: print_and_modify(o, mods, dels) ) @@ -1492,35 +1490,35 @@ def modify_parse_args(args): def modify_func(lib, opts, args): query, mods, dels = modify_parse_args(decargs(args)) if not mods and not dels: - raise ui.UserError(u'no modifications specified') + raise ui.UserError('no modifications specified') modify_items(lib, mods, dels, query, ui.should_write(opts.write), ui.should_move(opts.move), opts.album, not opts.yes) modify_cmd = ui.Subcommand( - u'modify', help=u'change metadata fields', aliases=(u'mod',) + 'modify', help='change metadata fields', aliases=('mod',) ) modify_cmd.parser.add_option( - u'-m', u'--move', action='store_true', dest='move', - help=u"move files in the library directory" + '-m', '--move', action='store_true', dest='move', + help="move files in the library directory" ) modify_cmd.parser.add_option( - u'-M', u'--nomove', action='store_false', dest='move', - help=u"don't move files in library" + '-M', '--nomove', action='store_false', dest='move', + help="don't move files in library" ) modify_cmd.parser.add_option( - u'-w', u'--write', action='store_true', default=None, - help=u"write new metadata to files' tags (default)" + '-w', '--write', action='store_true', default=None, + help="write new metadata to files' tags (default)" ) modify_cmd.parser.add_option( - u'-W', u'--nowrite', action='store_false', dest='write', - help=u"don't write metadata (opposite of -w)" + '-W', '--nowrite', action='store_false', dest='write', + help="don't write metadata (opposite of -w)" ) modify_cmd.parser.add_album_option() modify_cmd.parser.add_format_option(target='item') modify_cmd.parser.add_option( - u'-y', u'--yes', action='store_true', - help=u'skip confirmation' + '-y', '--yes', action='store_true', + help='skip confirmation' ) modify_cmd.func = modify_func default_commands.append(modify_cmd) @@ -1544,16 +1542,16 @@ def move_items(lib, dest, query, copy, album, pretend, confirm=False, objs = [o for o in objs if (isalbummoved if album else isitemmoved)(o)] num_unmoved = num_objs - len(objs) # Report unmoved files that match the query. - unmoved_msg = u'' + unmoved_msg = '' if num_unmoved > 0: - unmoved_msg = u' ({} already in place)'.format(num_unmoved) + unmoved_msg = f' ({num_unmoved} already in place)' copy = copy or export # Exporting always copies. - action = u'Copying' if copy else u'Moving' - act = u'copy' if copy else u'move' - entity = u'album' if album else u'item' - log.info(u'{0} {1} {2}{3}{4}.', action, len(objs), entity, - u's' if len(objs) != 1 else u'', unmoved_msg) + action = 'Copying' if copy else 'Moving' + act = 'copy' if copy else 'move' + entity = 'album' if album else 'item' + log.info('{0} {1} {2}{3}{4}.', action, len(objs), entity, + 's' if len(objs) != 1 else '', unmoved_msg) if not objs: return @@ -1567,12 +1565,12 @@ def move_items(lib, dest, query, copy, album, pretend, confirm=False, else: if confirm: objs = ui.input_select_objects( - u'Really %s' % act, objs, + 'Really %s' % act, objs, lambda o: show_path_changes( [(o.path, o.destination(basedir=dest))])) for obj in objs: - log.debug(u'moving: {0}', util.displayable_path(obj.path)) + log.debug('moving: {0}', util.displayable_path(obj.path)) if export: # Copy without affecting the database. @@ -1591,34 +1589,34 @@ def move_func(lib, opts, args): if dest is not None: dest = normpath(dest) if not os.path.isdir(dest): - raise ui.UserError(u'no such directory: %s' % dest) + raise ui.UserError('no such directory: %s' % dest) move_items(lib, dest, decargs(args), opts.copy, opts.album, opts.pretend, opts.timid, opts.export) move_cmd = ui.Subcommand( - u'move', help=u'move or copy items', aliases=(u'mv',) + 'move', help='move or copy items', aliases=('mv',) ) move_cmd.parser.add_option( - u'-d', u'--dest', metavar='DIR', dest='dest', - help=u'destination directory' + '-d', '--dest', metavar='DIR', dest='dest', + help='destination directory' ) move_cmd.parser.add_option( - u'-c', u'--copy', default=False, action='store_true', - help=u'copy instead of moving' + '-c', '--copy', default=False, action='store_true', + help='copy instead of moving' ) move_cmd.parser.add_option( - u'-p', u'--pretend', default=False, action='store_true', - help=u'show how files would be moved, but don\'t touch anything' + '-p', '--pretend', default=False, action='store_true', + help='show how files would be moved, but don\'t touch anything' ) move_cmd.parser.add_option( - u'-t', u'--timid', dest='timid', action='store_true', - help=u'always confirm all actions' + '-t', '--timid', dest='timid', action='store_true', + help='always confirm all actions' ) move_cmd.parser.add_option( - u'-e', u'--export', default=False, action='store_true', - help=u'copy without changing the database path' + '-e', '--export', default=False, action='store_true', + help='copy without changing the database path' ) move_cmd.parser.add_album_option() move_cmd.func = move_func @@ -1636,14 +1634,14 @@ def write_items(lib, query, pretend, force): for item in items: # Item deleted? if not os.path.exists(syspath(item.path)): - log.info(u'missing file: {0}', util.displayable_path(item.path)) + log.info('missing file: {0}', util.displayable_path(item.path)) continue # Get an Item object reflecting the "clean" (on-disk) state. try: clean_item = library.Item.from_path(item.path) except library.ReadError as exc: - log.error(u'error reading {0}: {1}', + log.error('error reading {0}: {1}', displayable_path(item.path), exc) continue @@ -1660,14 +1658,14 @@ def write_func(lib, opts, args): write_items(lib, decargs(args), opts.pretend, opts.force) -write_cmd = ui.Subcommand(u'write', help=u'write tag information to files') +write_cmd = ui.Subcommand('write', help='write tag information to files') write_cmd.parser.add_option( - u'-p', u'--pretend', action='store_true', - help=u"show all changes but do nothing" + '-p', '--pretend', action='store_true', + help="show all changes but do nothing" ) write_cmd.parser.add_option( - u'-f', u'--force', action='store_true', - help=u"write tags even if the existing tags match the database" + '-f', '--force', action='store_true', + help="write tags even if the existing tags match the database" ) write_cmd.func = write_func default_commands.append(write_cmd) @@ -1721,29 +1719,29 @@ def config_edit(): open(path, 'w+').close() util.interactive_open([path], editor) except OSError as exc: - message = u"Could not edit configuration: {0}".format(exc) + message = f"Could not edit configuration: {exc}" if not editor: - message += u". Please set the EDITOR environment variable" + message += ". Please set the EDITOR environment variable" raise ui.UserError(message) -config_cmd = ui.Subcommand(u'config', - help=u'show or edit the user configuration') +config_cmd = ui.Subcommand('config', + help='show or edit the user configuration') config_cmd.parser.add_option( - u'-p', u'--paths', action='store_true', - help=u'show files that configuration was loaded from' + '-p', '--paths', action='store_true', + help='show files that configuration was loaded from' ) config_cmd.parser.add_option( - u'-e', u'--edit', action='store_true', - help=u'edit user configuration with $EDITOR' + '-e', '--edit', action='store_true', + help='edit user configuration with $EDITOR' ) config_cmd.parser.add_option( - u'-d', u'--defaults', action='store_true', - help=u'include the default configuration' + '-d', '--defaults', action='store_true', + help='include the default configuration' ) config_cmd.parser.add_option( - u'-c', u'--clear', action='store_false', + '-c', '--clear', action='store_false', dest='redact', default=True, - help=u'do not redact sensitive fields' + help='do not redact sensitive fields' ) config_cmd.func = config_func default_commands.append(config_cmd) @@ -1753,19 +1751,19 @@ default_commands.append(config_cmd) def print_completion(*args): for line in completion_script(default_commands + plugins.commands()): - print_(line, end=u'') + print_(line, end='') if not any(map(os.path.isfile, BASH_COMPLETION_PATHS)): - log.warning(u'Warning: Unable to find the bash-completion package. ' - u'Command line completion might not work.') + log.warning('Warning: Unable to find the bash-completion package. ' + 'Command line completion might not work.') BASH_COMPLETION_PATHS = map(syspath, [ - u'/etc/bash_completion', - u'/usr/share/bash-completion/bash_completion', - u'/usr/local/share/bash-completion/bash_completion', + '/etc/bash_completion', + '/usr/share/bash-completion/bash_completion', + '/usr/local/share/bash-completion/bash_completion', # SmartOS - u'/opt/local/share/bash-completion/bash_completion', + '/opt/local/share/bash-completion/bash_completion', # Homebrew (before bash-completion2) - u'/usr/local/etc/bash_completion', + '/usr/local/etc/bash_completion', ]) @@ -1776,7 +1774,7 @@ def completion_script(commands): completion data for. """ base_script = os.path.join(os.path.dirname(__file__), 'completion_base.sh') - with open(base_script, 'r') as base_script: + with open(base_script) as base_script: yield util.text_string(base_script.read()) options = {} @@ -1792,12 +1790,12 @@ def completion_script(commands): if re.match(r'^\w+$', alias): aliases[alias] = name - options[name] = {u'flags': [], u'opts': []} + options[name] = {'flags': [], 'opts': []} for opts in cmd.parser._get_all_options()[1:]: if opts.action in ('store_true', 'store_false'): - option_type = u'flags' + option_type = 'flags' else: - option_type = u'opts' + option_type = 'opts' options[name][option_type].extend( opts._short_opts + opts._long_opts @@ -1805,31 +1803,31 @@ def completion_script(commands): # Add global options options['_global'] = { - u'flags': [u'-v', u'--verbose'], - u'opts': - u'-l --library -c --config -d --directory -h --help'.split(u' ') + 'flags': ['-v', '--verbose'], + 'opts': + '-l --library -c --config -d --directory -h --help'.split(' ') } # Add flags common to all commands options['_common'] = { - u'flags': [u'-h', u'--help'] + 'flags': ['-h', '--help'] } # Start generating the script - yield u"_beet() {\n" + yield "_beet() {\n" # Command names - yield u" local commands='%s'\n" % ' '.join(command_names) - yield u"\n" + yield " local commands='%s'\n" % ' '.join(command_names) + yield "\n" # Command aliases - yield u" local aliases='%s'\n" % ' '.join(aliases.keys()) + yield " local aliases='%s'\n" % ' '.join(aliases.keys()) for alias, cmd in aliases.items(): - yield u" local alias__%s=%s\n" % (alias.replace('-', '_'), cmd) - yield u'\n' + yield " local alias__{}={}\n".format(alias.replace('-', '_'), cmd) + yield '\n' # Fields - yield u" fields='%s'\n" % ' '.join( + yield " fields='%s'\n" % ' '.join( set( list(library.Item._fields.keys()) + list(library.Album._fields.keys()) @@ -1840,17 +1838,17 @@ def completion_script(commands): for cmd, opts in options.items(): for option_type, option_list in opts.items(): if option_list: - option_list = u' '.join(option_list) - yield u" local %s__%s='%s'\n" % ( + option_list = ' '.join(option_list) + yield " local {}__{}='{}'\n".format( option_type, cmd.replace('-', '_'), option_list) - yield u' _beet_dispatch\n' - yield u'}\n' + yield ' _beet_dispatch\n' + yield '}\n' completion_cmd = ui.Subcommand( 'completion', - help=u'print shell script that provides command line completion' + help='print shell script that provides command line completion' ) completion_cmd.func = print_completion completion_cmd.hide = True From af102c3e2f1c7a49e99839e2825906fe01780eec Mon Sep 17 00:00:00 2001 From: Andrew Rogl Date: Wed, 25 Aug 2021 19:02:31 +1000 Subject: [PATCH 06/26] pyupgrade util dir --- beets/util/__init__.py | 105 ++++++++++++++++++------------------- beets/util/artresizer.py | 46 ++++++++-------- beets/util/bluelet.py | 19 +++---- beets/util/confit.py | 2 - beets/util/enumeration.py | 2 - beets/util/functemplate.py | 80 ++++++++++++++-------------- beets/util/hidden.py | 2 - beets/util/pipeline.py | 36 ++++++------- 8 files changed, 137 insertions(+), 155 deletions(-) diff --git a/beets/util/__init__.py b/beets/util/__init__.py index affdff12f..7a47ad48e 100644 --- a/beets/util/__init__.py +++ b/beets/util/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -15,7 +14,6 @@ """Miscellaneous utility functions.""" -from __future__ import division, absolute_import, print_function import os import sys import errno @@ -37,7 +35,7 @@ from enum import Enum MAX_FILENAME_LENGTH = 200 -WINDOWS_MAGIC_PREFIX = u'\\\\?\\' +WINDOWS_MAGIC_PREFIX = '\\\\?\\' SNI_SUPPORTED = sys.version_info >= (2, 7, 9) @@ -60,27 +58,27 @@ class HumanReadableException(Exception): self.reason = reason self.verb = verb self.tb = tb - super(HumanReadableException, self).__init__(self.get_message()) + super().__init__(self.get_message()) def _gerund(self): """Generate a (likely) gerund form of the English verb. """ - if u' ' in self.verb: + if ' ' in self.verb: return self.verb - gerund = self.verb[:-1] if self.verb.endswith(u'e') else self.verb - gerund += u'ing' + gerund = self.verb[:-1] if self.verb.endswith('e') else self.verb + gerund += 'ing' return gerund def _reasonstr(self): """Get the reason as a string.""" - if isinstance(self.reason, six.text_type): + if isinstance(self.reason, str): return self.reason elif isinstance(self.reason, bytes): return self.reason.decode('utf-8', 'ignore') elif hasattr(self.reason, 'strerror'): # i.e., EnvironmentError return self.reason.strerror else: - return u'"{0}"'.format(six.text_type(self.reason)) + return '"{}"'.format(str(self.reason)) def get_message(self): """Create the human-readable description of the error, sans @@ -94,7 +92,7 @@ class HumanReadableException(Exception): """ if self.tb: logger.debug(self.tb) - logger.error(u'{0}: {1}', self.error_kind, self.args[0]) + logger.error('{0}: {1}', self.error_kind, self.args[0]) class FilesystemError(HumanReadableException): @@ -104,27 +102,27 @@ class FilesystemError(HumanReadableException): """ def __init__(self, reason, verb, paths, tb=None): self.paths = paths - super(FilesystemError, self).__init__(reason, verb, tb) + super().__init__(reason, verb, tb) def get_message(self): # Use a nicer English phrasing for some specific verbs. if self.verb in ('move', 'copy', 'rename'): - clause = u'while {0} {1} to {2}'.format( + clause = 'while {} {} to {}'.format( self._gerund(), displayable_path(self.paths[0]), displayable_path(self.paths[1]) ) elif self.verb in ('delete', 'write', 'create', 'read'): - clause = u'while {0} {1}'.format( + clause = 'while {} {}'.format( self._gerund(), displayable_path(self.paths[0]) ) else: - clause = u'during {0} of paths {1}'.format( - self.verb, u', '.join(displayable_path(p) for p in self.paths) + clause = 'during {} of paths {}'.format( + self.verb, ', '.join(displayable_path(p) for p in self.paths) ) - return u'{0} {1}'.format(self._reasonstr(), clause) + return f'{self._reasonstr()} {clause}' class MoveOperation(Enum): @@ -186,7 +184,7 @@ def sorted_walk(path, ignore=(), ignore_hidden=False, logger=None): contents = os.listdir(syspath(path)) except OSError as exc: if logger: - logger.warning(u'could not list directory {0}: {1}'.format( + logger.warning('could not list directory {}: {}'.format( displayable_path(path), exc.strerror )) return @@ -200,7 +198,7 @@ def sorted_walk(path, ignore=(), ignore_hidden=False, logger=None): for pat in ignore: if fnmatch.fnmatch(base, pat): if logger: - logger.debug(u'ignoring {0} due to ignore rule {1}'.format( + logger.debug('ignoring {} due to ignore rule {}'.format( base, pat )) skip = True @@ -225,8 +223,7 @@ def sorted_walk(path, ignore=(), ignore_hidden=False, logger=None): for base in dirs: cur = os.path.join(path, base) # yield from sorted_walk(...) - for res in sorted_walk(cur, ignore, ignore_hidden, logger): - yield res + yield from sorted_walk(cur, ignore, ignore_hidden, logger) def path_as_posix(path): @@ -244,7 +241,7 @@ def mkdirall(path): if not os.path.isdir(syspath(ancestor)): try: os.mkdir(syspath(ancestor)) - except (OSError, IOError) as exc: + except OSError as exc: raise FilesystemError(exc, 'create', (ancestor,), traceback.format_exc()) @@ -382,18 +379,18 @@ def bytestring_path(path): PATH_SEP = bytestring_path(os.sep) -def displayable_path(path, separator=u'; '): +def displayable_path(path, separator='; '): """Attempts to decode a bytestring path to a unicode object for the purpose of displaying it to the user. If the `path` argument is a list or a tuple, the elements are joined with `separator`. """ if isinstance(path, (list, tuple)): return separator.join(displayable_path(p) for p in path) - elif isinstance(path, six.text_type): + elif isinstance(path, str): return path elif not isinstance(path, bytes): # A non-string object: just get its unicode representation. - return six.text_type(path) + return str(path) try: return path.decode(_fsencoding(), 'ignore') @@ -412,7 +409,7 @@ def syspath(path, prefix=True): if os.path.__name__ != 'ntpath': return path - if not isinstance(path, six.text_type): + if not isinstance(path, str): # Beets currently represents Windows paths internally with UTF-8 # arbitrarily. But earlier versions used MBCS because it is # reported as the FS encoding by Windows. Try both. @@ -427,9 +424,9 @@ def syspath(path, prefix=True): # Add the magic prefix if it isn't already there. # https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx if prefix and not path.startswith(WINDOWS_MAGIC_PREFIX): - if path.startswith(u'\\\\'): + if path.startswith('\\\\'): # UNC path. Final path should look like \\?\UNC\... - path = u'UNC' + path[1:] + path = 'UNC' + path[1:] path = WINDOWS_MAGIC_PREFIX + path return path @@ -451,7 +448,7 @@ def remove(path, soft=True): return try: os.remove(path) - except (OSError, IOError) as exc: + except OSError as exc: raise FilesystemError(exc, 'delete', (path,), traceback.format_exc()) @@ -466,10 +463,10 @@ def copy(path, dest, replace=False): path = syspath(path) dest = syspath(dest) if not replace and os.path.exists(dest): - raise FilesystemError(u'file exists', 'copy', (path, dest)) + raise FilesystemError('file exists', 'copy', (path, dest)) try: shutil.copyfile(path, dest) - except (OSError, IOError) as exc: + except OSError as exc: raise FilesystemError(exc, 'copy', (path, dest), traceback.format_exc()) @@ -487,7 +484,7 @@ def move(path, dest, replace=False): path = syspath(path) dest = syspath(dest) if os.path.exists(dest) and not replace: - raise FilesystemError(u'file exists', 'rename', (path, dest)) + raise FilesystemError('file exists', 'rename', (path, dest)) # First, try renaming the file. try: @@ -497,7 +494,7 @@ def move(path, dest, replace=False): try: shutil.copyfile(path, dest) os.remove(path) - except (OSError, IOError) as exc: + except OSError as exc: raise FilesystemError(exc, 'move', (path, dest), traceback.format_exc()) @@ -511,18 +508,18 @@ def link(path, dest, replace=False): return if os.path.exists(syspath(dest)) and not replace: - raise FilesystemError(u'file exists', 'rename', (path, dest)) + raise FilesystemError('file exists', 'rename', (path, dest)) try: os.symlink(syspath(path), syspath(dest)) except NotImplementedError: # raised on python >= 3.2 and Windows versions before Vista - raise FilesystemError(u'OS does not support symbolic links.' + raise FilesystemError('OS does not support symbolic links.' 'link', (path, dest), traceback.format_exc()) except OSError as exc: # TODO: Windows version checks can be removed for python 3 if hasattr('sys', 'getwindowsversion'): if sys.getwindowsversion()[0] < 6: # is before Vista - exc = u'OS does not support symbolic links.' + exc = 'OS does not support symbolic links.' raise FilesystemError(exc, 'link', (path, dest), traceback.format_exc()) @@ -536,15 +533,15 @@ def hardlink(path, dest, replace=False): return if os.path.exists(syspath(dest)) and not replace: - raise FilesystemError(u'file exists', 'rename', (path, dest)) + raise FilesystemError('file exists', 'rename', (path, dest)) try: os.link(syspath(path), syspath(dest)) except NotImplementedError: - raise FilesystemError(u'OS does not support hard links.' + raise FilesystemError('OS does not support hard links.' 'link', (path, dest), traceback.format_exc()) except OSError as exc: if exc.errno == errno.EXDEV: - raise FilesystemError(u'Cannot hard link across devices.' + raise FilesystemError('Cannot hard link across devices.' 'link', (path, dest), traceback.format_exc()) else: raise FilesystemError(exc, 'link', (path, dest), @@ -568,7 +565,7 @@ def reflink(path, dest, replace=False, fallback=False): return if os.path.exists(syspath(dest)) and not replace: - raise FilesystemError(u'file exists', 'rename', (path, dest)) + raise FilesystemError('file exists', 'rename', (path, dest)) try: pyreflink.reflink(path, dest) @@ -576,7 +573,7 @@ def reflink(path, dest, replace=False, fallback=False): if fallback: copy(path, dest, replace) else: - raise FilesystemError(u'OS/filesystem does not support reflinks.', + raise FilesystemError('OS/filesystem does not support reflinks.', 'link', (path, dest), traceback.format_exc()) @@ -597,7 +594,7 @@ def unique_path(path): num = 0 while True: num += 1 - suffix = u'.{}'.format(num).encode() + ext + suffix = f'.{num}'.encode() + ext new_path = base + suffix if not os.path.exists(new_path): return new_path @@ -607,12 +604,12 @@ def unique_path(path): # shares, which are sufficiently common as to cause frequent problems. # https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx CHAR_REPLACE = [ - (re.compile(r'[\\/]'), u'_'), # / and \ -- forbidden everywhere. - (re.compile(r'^\.'), u'_'), # Leading dot (hidden files on Unix). - (re.compile(r'[\x00-\x1f]'), u''), # Control characters. - (re.compile(r'[<>:"\?\*\|]'), u'_'), # Windows "reserved characters". - (re.compile(r'\.$'), u'_'), # Trailing dots. - (re.compile(r'\s+$'), u''), # Trailing whitespace. + (re.compile(r'[\\/]'), '_'), # / and \ -- forbidden everywhere. + (re.compile(r'^\.'), '_'), # Leading dot (hidden files on Unix). + (re.compile(r'[\x00-\x1f]'), ''), # Control characters. + (re.compile(r'[<>:"\?\*\|]'), '_'), # Windows "reserved characters". + (re.compile(r'\.$'), '_'), # Trailing dots. + (re.compile(r'\s+$'), ''), # Trailing whitespace. ] @@ -736,7 +733,7 @@ def py3_path(path): it is. So this function helps us "smuggle" the true bytes data through APIs that took Python 3's Unicode mandate too seriously. """ - if isinstance(path, six.text_type): + if isinstance(path, str): return path assert isinstance(path, bytes) if six.PY2: @@ -746,7 +743,7 @@ def py3_path(path): def str2bool(value): """Returns a boolean reflecting a human-entered string.""" - return value.lower() in (u'yes', u'1', u'true', u't', u'y') + return value.lower() in ('yes', '1', 'true', 't', 'y') def as_string(value): @@ -754,13 +751,13 @@ def as_string(value): None becomes the empty string. Bytestrings are silently decoded. """ if value is None: - return u'' + return '' elif isinstance(value, memoryview): return bytes(value).decode('utf-8', 'ignore') elif isinstance(value, bytes): return value.decode('utf-8', 'ignore') else: - return six.text_type(value) + return str(value) def text_string(value, encoding='utf-8'): @@ -783,7 +780,7 @@ def plurality(objs): """ c = Counter(objs) if not c: - raise ValueError(u'sequence must be non-empty') + raise ValueError('sequence must be non-empty') return c.most_common(1)[0] @@ -948,7 +945,7 @@ def _windows_long_path_name(short_path): """Use Windows' `GetLongPathNameW` via ctypes to get the canonical, long path given a short filename. """ - if not isinstance(short_path, six.text_type): + if not isinstance(short_path, str): short_path = short_path.decode(_fsencoding()) import ctypes @@ -1009,7 +1006,7 @@ def raw_seconds_short(string): """ match = re.match(r'^(\d+):([0-5]\d)$', string) if not match: - raise ValueError(u'String not in M:SS format') + raise ValueError('String not in M:SS format') minutes, seconds = map(int, match.groups()) return float(minutes * 60 + seconds) diff --git a/beets/util/artresizer.py b/beets/util/artresizer.py index bf6254c81..3c2db08da 100644 --- a/beets/util/artresizer.py +++ b/beets/util/artresizer.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Fabrice Laporte # @@ -16,7 +15,6 @@ """Abstraction layer to resize images using PIL, ImageMagick, or a public resizing proxy if neither is available. """ -from __future__ import division, absolute_import, print_function import subprocess import os @@ -52,7 +50,7 @@ def resize_url(url, maxwidth, quality=0): if quality > 0: params['q'] = quality - return '{0}?{1}'.format(PROXY_URL, urlencode(params)) + return '{}?{}'.format(PROXY_URL, urlencode(params)) def temp_file_for(path): @@ -71,7 +69,7 @@ def pil_resize(maxwidth, path_in, path_out=None, quality=0, max_filesize=0): path_out = path_out or temp_file_for(path_in) from PIL import Image - log.debug(u'artresizer: PIL resizing {0} to {1}', + log.debug('artresizer: PIL resizing {0} to {1}', util.displayable_path(path_in), util.displayable_path(path_out)) try: @@ -95,7 +93,7 @@ def pil_resize(maxwidth, path_in, path_out=None, quality=0, max_filesize=0): for i in range(5): # 5 attempts is an abitrary choice filesize = os.stat(util.syspath(path_out)).st_size - log.debug(u"PIL Pass {0} : Output size: {1}B", i, filesize) + log.debug("PIL Pass {0} : Output size: {1}B", i, filesize) if filesize <= max_filesize: return path_out # The relationship between filesize & quality will be @@ -108,14 +106,14 @@ def pil_resize(maxwidth, path_in, path_out=None, quality=0, max_filesize=0): im.save( util.py3_path(path_out), quality=lower_qual, optimize=True ) - log.warning(u"PIL Failed to resize file to below {0}B", + log.warning("PIL Failed to resize file to below {0}B", max_filesize) return path_out else: return path_out - except IOError: - log.error(u"PIL cannot create thumbnail for '{0}'", + except OSError: + log.error("PIL cannot create thumbnail for '{0}'", util.displayable_path(path_in)) return path_in @@ -127,7 +125,7 @@ def im_resize(maxwidth, path_in, path_out=None, quality=0, max_filesize=0): the output path of resized image. """ path_out = path_out or temp_file_for(path_in) - log.debug(u'artresizer: ImageMagick resizing {0} to {1}', + log.debug('artresizer: ImageMagick resizing {0} to {1}', util.displayable_path(path_in), util.displayable_path(path_out)) # "-resize WIDTHx>" shrinks images with the width larger @@ -135,23 +133,23 @@ def im_resize(maxwidth, path_in, path_out=None, quality=0, max_filesize=0): # with regards to the height. cmd = ArtResizer.shared.im_convert_cmd + [ util.syspath(path_in, prefix=False), - '-resize', '{0}x>'.format(maxwidth), + '-resize', f'{maxwidth}x>', ] if quality > 0: - cmd += ['-quality', '{0}'.format(quality)] + cmd += ['-quality', f'{quality}'] # "-define jpeg:extent=SIZEb" sets the target filesize for imagemagick to # SIZE in bytes. if max_filesize > 0: - cmd += ['-define', 'jpeg:extent={0}b'.format(max_filesize)] + cmd += ['-define', f'jpeg:extent={max_filesize}b'] cmd.append(util.syspath(path_out, prefix=False)) try: util.command_output(cmd) except subprocess.CalledProcessError: - log.warning(u'artresizer: IM convert failed for {0}', + log.warning('artresizer: IM convert failed for {0}', util.displayable_path(path_in)) return path_in @@ -170,8 +168,8 @@ def pil_getsize(path_in): try: im = Image.open(util.syspath(path_in)) return im.size - except IOError as exc: - log.error(u"PIL could not read file {}: {}", + except OSError as exc: + log.error("PIL could not read file {}: {}", util.displayable_path(path_in), exc) @@ -182,17 +180,17 @@ def im_getsize(path_in): try: out = util.command_output(cmd).stdout except subprocess.CalledProcessError as exc: - log.warning(u'ImageMagick size query failed') + log.warning('ImageMagick size query failed') log.debug( - u'`convert` exited with (status {}) when ' - u'getting size with command {}:\n{}', + '`convert` exited with (status {}) when ' + 'getting size with command {}:\n{}', exc.returncode, cmd, exc.output.strip() ) return try: return tuple(map(int, out.split(b' '))) except IndexError: - log.warning(u'Could not understand IM output: {0!r}', out) + log.warning('Could not understand IM output: {0!r}', out) BACKEND_GET_SIZE = { @@ -209,7 +207,7 @@ class Shareable(type): """ def __init__(cls, name, bases, dict): - super(Shareable, cls).__init__(name, bases, dict) + super().__init__(name, bases, dict) cls._instance = None @property @@ -219,7 +217,7 @@ class Shareable(type): return cls._instance -class ArtResizer(six.with_metaclass(Shareable, object)): +class ArtResizer(metaclass=Shareable): """A singleton class that performs image resizes. """ @@ -227,7 +225,7 @@ class ArtResizer(six.with_metaclass(Shareable, object)): """Create a resizer object with an inferred method. """ self.method = self._check_method() - log.debug(u"artresizer: method is {0}", self.method) + log.debug("artresizer: method is {0}", self.method) self.can_compare = self._can_compare() # Use ImageMagick's magick binary when it's available. If it's @@ -323,7 +321,7 @@ def get_im_version(): try: out = util.command_output(cmd).stdout except (subprocess.CalledProcessError, OSError) as exc: - log.debug(u'ImageMagick version check failed: {}', exc) + log.debug('ImageMagick version check failed: {}', exc) else: if b'imagemagick' in out.lower(): pattern = br".+ (\d+)\.(\d+)\.(\d+).*" @@ -341,7 +339,7 @@ def get_pil_version(): """Get the PIL/Pillow version, or None if it is unavailable. """ try: - __import__('PIL', fromlist=[str('Image')]) + __import__('PIL', fromlist=['Image']) return (0,) except ImportError: return None diff --git a/beets/util/bluelet.py b/beets/util/bluelet.py index dcc80e041..bd2b2838b 100644 --- a/beets/util/bluelet.py +++ b/beets/util/bluelet.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - """Extremely simple pure-Python implementation of coroutine-style asynchronous socket I/O. Inspired by, but inferior to, Eventlet. Bluelet can also be thought of as a less-terrible replacement for @@ -7,7 +5,6 @@ asyncore. Bluelet: easy concurrency without all the messy parallelism. """ -from __future__ import division, absolute_import, print_function import six import socket @@ -22,7 +19,7 @@ import collections # Basic events used for thread scheduling. -class Event(object): +class Event: """Just a base class identifying Bluelet events. An event is an object yielded from a Bluelet thread coroutine to suspend operation and communicate with the scheduler. @@ -201,7 +198,7 @@ class ThreadException(Exception): self.exc_info = exc_info def reraise(self): - six.reraise(self.exc_info[0], self.exc_info[1], self.exc_info[2]) + raise self.exc_info[1].with_traceback(self.exc_info[2]) SUSPENDED = Event() # Special sentinel placeholder for suspended threads. @@ -336,12 +333,12 @@ def run(root_coro): break # Wait and fire. - event2coro = dict((v, k) for k, v in threads.items()) + event2coro = {v: k for k, v in threads.items()} for event in _event_select(threads.values()): # Run the IO operation, but catch socket errors. try: value = event.fire() - except socket.error as exc: + except OSError as exc: if isinstance(exc.args, tuple) and \ exc.args[0] == errno.EPIPE: # Broken pipe. Remote host disconnected. @@ -390,7 +387,7 @@ class SocketClosedError(Exception): pass -class Listener(object): +class Listener: """A socket wrapper object for listening sockets. """ def __init__(self, host, port): @@ -420,7 +417,7 @@ class Listener(object): self.sock.close() -class Connection(object): +class Connection: """A socket wrapper object for connected sockets. """ def __init__(self, sock, addr): @@ -545,7 +542,7 @@ def spawn(coro): and child coroutines run concurrently. """ if not isinstance(coro, types.GeneratorType): - raise ValueError(u'%s is not a coroutine' % coro) + raise ValueError('%s is not a coroutine' % coro) return SpawnEvent(coro) @@ -555,7 +552,7 @@ def call(coro): returns a value using end(), then this event returns that value. """ if not isinstance(coro, types.GeneratorType): - raise ValueError(u'%s is not a coroutine' % coro) + raise ValueError('%s is not a coroutine' % coro) return DelegationEvent(coro) diff --git a/beets/util/confit.py b/beets/util/confit.py index 450e37210..dd912c444 100644 --- a/beets/util/confit.py +++ b/beets/util/confit.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016-2019, Adrian Sampson. # @@ -13,7 +12,6 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -from __future__ import division, absolute_import, print_function import confuse diff --git a/beets/util/enumeration.py b/beets/util/enumeration.py index 3e9467185..e49f6fddb 100644 --- a/beets/util/enumeration.py +++ b/beets/util/enumeration.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -13,7 +12,6 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -from __future__ import division, absolute_import, print_function from enum import Enum diff --git a/beets/util/functemplate.py b/beets/util/functemplate.py index 2621ebe30..63726d4cc 100644 --- a/beets/util/functemplate.py +++ b/beets/util/functemplate.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -27,7 +26,6 @@ This is sort of like a tiny, horrible degeneration of a real templating engine like Jinja2 or Mustache. """ -from __future__ import division, absolute_import, print_function import re import ast @@ -37,18 +35,18 @@ import sys import six import functools -SYMBOL_DELIM = u'$' -FUNC_DELIM = u'%' -GROUP_OPEN = u'{' -GROUP_CLOSE = u'}' -ARG_SEP = u',' -ESCAPE_CHAR = u'$' +SYMBOL_DELIM = '$' +FUNC_DELIM = '%' +GROUP_OPEN = '{' +GROUP_CLOSE = '}' +ARG_SEP = ',' +ESCAPE_CHAR = '$' VARIABLE_PREFIX = '__var_' FUNCTION_PREFIX = '__func_' -class Environment(object): +class Environment: """Contains the values and functions to be substituted into a template. """ @@ -76,21 +74,21 @@ def ex_literal(val): if sys.version_info[:2] < (3, 4): if val is None: return ast.Name('None', ast.Load()) - elif isinstance(val, six.integer_types): + elif isinstance(val, int): return ast.Num(val) elif isinstance(val, bool): return ast.Name(bytes(val), ast.Load()) - elif isinstance(val, six.string_types): + elif isinstance(val, str): return ast.Str(val) - raise TypeError(u'no literal for {0}'.format(type(val))) + raise TypeError('no literal for {}'.format(type(val))) elif sys.version_info[:2] < (3, 6): if val in [None, True, False]: return ast.NameConstant(val) - elif isinstance(val, six.integer_types): + elif isinstance(val, int): return ast.Num(val) - elif isinstance(val, six.string_types): + elif isinstance(val, str): return ast.Str(val) - raise TypeError(u'no literal for {0}'.format(type(val))) + raise TypeError('no literal for {}'.format(type(val))) else: return ast.Constant(val) @@ -109,7 +107,7 @@ def ex_call(func, args): function may be an expression or the name of a function. Each argument may be an expression or a value to be used as a literal. """ - if isinstance(func, six.string_types): + if isinstance(func, str): func = ex_rvalue(func) args = list(args) @@ -170,14 +168,14 @@ def compile_func(arg_names, statements, name='_the_func', debug=False): # AST nodes for the template language. -class Symbol(object): +class Symbol: """A variable-substitution symbol in a template.""" def __init__(self, ident, original): self.ident = ident self.original = original def __repr__(self): - return u'Symbol(%s)' % repr(self.ident) + return 'Symbol(%s)' % repr(self.ident) def evaluate(self, env): """Evaluate the symbol in the environment, returning a Unicode @@ -194,10 +192,10 @@ class Symbol(object): """Compile the variable lookup.""" ident = self.ident expr = ex_rvalue(VARIABLE_PREFIX + ident) - return [expr], set([ident]), set() + return [expr], {ident}, set() -class Call(object): +class Call: """A function call in a template.""" def __init__(self, ident, args, original): self.ident = ident @@ -205,7 +203,7 @@ class Call(object): self.original = original def __repr__(self): - return u'Call(%s, %s, %s)' % (repr(self.ident), repr(self.args), + return 'Call({}, {}, {})'.format(repr(self.ident), repr(self.args), repr(self.original)) def evaluate(self, env): @@ -219,15 +217,15 @@ class Call(object): except Exception as exc: # Function raised exception! Maybe inlining the name of # the exception will help debug. - return u'<%s>' % six.text_type(exc) - return six.text_type(out) + return '<%s>' % str(exc) + return str(out) else: return self.original def translate(self): """Compile the function call.""" varnames = set() - funcnames = set([self.ident]) + funcnames = {self.ident} arg_exprs = [] for arg in self.args: @@ -238,11 +236,11 @@ class Call(object): # Create a subexpression that joins the result components of # the arguments. arg_exprs.append(ex_call( - ast.Attribute(ex_literal(u''), 'join', ast.Load()), + ast.Attribute(ex_literal(''), 'join', ast.Load()), [ex_call( 'map', [ - ex_rvalue(six.text_type.__name__), + ex_rvalue(str.__name__), ast.List(subexprs, ast.Load()), ] )], @@ -255,7 +253,7 @@ class Call(object): return [subexpr_call], varnames, funcnames -class Expression(object): +class Expression: """Top-level template construct: contains a list of text blobs, Symbols, and Calls. """ @@ -263,7 +261,7 @@ class Expression(object): self.parts = parts def __repr__(self): - return u'Expression(%s)' % (repr(self.parts)) + return 'Expression(%s)' % (repr(self.parts)) def evaluate(self, env): """Evaluate the entire expression in the environment, returning @@ -271,11 +269,11 @@ class Expression(object): """ out = [] for part in self.parts: - if isinstance(part, six.string_types): + if isinstance(part, str): out.append(part) else: out.append(part.evaluate(env)) - return u''.join(map(six.text_type, out)) + return ''.join(map(str, out)) def translate(self): """Compile the expression to a list of Python AST expressions, a @@ -285,7 +283,7 @@ class Expression(object): varnames = set() funcnames = set() for part in self.parts: - if isinstance(part, six.string_types): + if isinstance(part, str): expressions.append(ex_literal(part)) else: e, v, f = part.translate() @@ -301,7 +299,7 @@ class ParseError(Exception): pass -class Parser(object): +class Parser: """Parses a template expression string. Instantiate the class with the template source and call ``parse_expression``. The ``pos`` field will indicate the character after the expression finished and @@ -329,7 +327,7 @@ class Parser(object): special_chars = (SYMBOL_DELIM, FUNC_DELIM, GROUP_OPEN, GROUP_CLOSE, ESCAPE_CHAR) special_char_re = re.compile(r'[%s]|\Z' % - u''.join(re.escape(c) for c in special_chars)) + ''.join(re.escape(c) for c in special_chars)) escapable_chars = (SYMBOL_DELIM, FUNC_DELIM, GROUP_CLOSE, ARG_SEP) terminator_chars = (GROUP_CLOSE,) @@ -346,7 +344,7 @@ class Parser(object): if self.in_argument: extra_special_chars = (ARG_SEP,) special_char_re = re.compile( - r'[%s]|\Z' % u''.join( + r'[%s]|\Z' % ''.join( re.escape(c) for c in self.special_chars + extra_special_chars ) @@ -390,7 +388,7 @@ class Parser(object): # Shift all characters collected so far into a single string. if text_parts: - self.parts.append(u''.join(text_parts)) + self.parts.append(''.join(text_parts)) text_parts = [] if char == SYMBOL_DELIM: @@ -412,7 +410,7 @@ class Parser(object): # If any parsed characters remain, shift them into a string. if text_parts: - self.parts.append(u''.join(text_parts)) + self.parts.append(''.join(text_parts)) def parse_symbol(self): """Parse a variable reference (like ``$foo`` or ``${foo}``) @@ -567,7 +565,7 @@ def template(fmt): # External interface. -class Template(object): +class Template: """A string template, including text, Symbols, and Calls. """ def __init__(self, template): @@ -618,7 +616,7 @@ class Template(object): for funcname in funcnames: args[FUNCTION_PREFIX + funcname] = functions[funcname] parts = func(**args) - return u''.join(parts) + return ''.join(parts) return wrapper_func @@ -627,9 +625,9 @@ class Template(object): if __name__ == '__main__': import timeit - _tmpl = Template(u'foo $bar %baz{foozle $bar barzle} $bar') + _tmpl = Template('foo $bar %baz{foozle $bar barzle} $bar') _vars = {'bar': 'qux'} - _funcs = {'baz': six.text_type.upper} + _funcs = {'baz': str.upper} interp_time = timeit.timeit('_tmpl.interpret(_vars, _funcs)', 'from __main__ import _tmpl, _vars, _funcs', number=10000) @@ -638,4 +636,4 @@ if __name__ == '__main__': 'from __main__ import _tmpl, _vars, _funcs', number=10000) print(comp_time) - print(u'Speedup:', interp_time / comp_time) + print('Speedup:', interp_time / comp_time) diff --git a/beets/util/hidden.py b/beets/util/hidden.py index ed97f2bfc..881de1acd 100644 --- a/beets/util/hidden.py +++ b/beets/util/hidden.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -14,7 +13,6 @@ # included in all copies or substantial portions of the Software. """Simple library to work out if a file is hidden on different platforms.""" -from __future__ import division, absolute_import, print_function import os import stat diff --git a/beets/util/pipeline.py b/beets/util/pipeline.py index 71ac8ffe6..7e5e2ba2a 100644 --- a/beets/util/pipeline.py +++ b/beets/util/pipeline.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -32,7 +31,6 @@ To do so, pass an iterable of coroutines to the Pipeline constructor in place of any single coroutine. """ -from __future__ import division, absolute_import, print_function from six.moves import queue from threading import Thread, Lock @@ -135,7 +133,7 @@ class CountedQueue(queue.Queue): _invalidate_queue(self, POISON, False) -class MultiMessage(object): +class MultiMessage: """A message yielded by a pipeline stage encapsulating multiple values to be sent to the next stage. """ @@ -211,7 +209,7 @@ def _allmsgs(obj): class PipelineThread(Thread): """Abstract base class for pipeline-stage threads.""" def __init__(self, all_threads): - super(PipelineThread, self).__init__() + super().__init__() self.abort_lock = Lock() self.abort_flag = False self.all_threads = all_threads @@ -242,7 +240,7 @@ class FirstPipelineThread(PipelineThread): The coroutine should just be a generator. """ def __init__(self, coro, out_queue, all_threads): - super(FirstPipelineThread, self).__init__(all_threads) + super().__init__(all_threads) self.coro = coro self.out_queue = out_queue self.out_queue.acquire() @@ -280,7 +278,7 @@ class MiddlePipelineThread(PipelineThread): last. """ def __init__(self, coro, in_queue, out_queue, all_threads): - super(MiddlePipelineThread, self).__init__(all_threads) + super().__init__(all_threads) self.coro = coro self.in_queue = in_queue self.out_queue = out_queue @@ -328,7 +326,7 @@ class LastPipelineThread(PipelineThread): should yield nothing. """ def __init__(self, coro, in_queue, all_threads): - super(LastPipelineThread, self).__init__(all_threads) + super().__init__(all_threads) self.coro = coro self.in_queue = in_queue @@ -359,7 +357,7 @@ class LastPipelineThread(PipelineThread): return -class Pipeline(object): +class Pipeline: """Represents a staged pattern of work. Each stage in the pipeline is a coroutine that receives messages from the previous stage and yields messages to be sent to the next stage. @@ -369,7 +367,7 @@ class Pipeline(object): be at least two stages. """ if len(stages) < 2: - raise ValueError(u'pipeline must have at least two stages') + raise ValueError('pipeline must have at least two stages') self.stages = [] for stage in stages: if isinstance(stage, (list, tuple)): @@ -439,7 +437,7 @@ class Pipeline(object): exc_info = thread.exc_info if exc_info: # Make the exception appear as it was raised originally. - six.reraise(exc_info[0], exc_info[1], exc_info[2]) + raise exc_info[1].with_traceback(exc_info[2]) def pull(self): """Yield elements from the end of the pipeline. Runs the stages @@ -474,14 +472,14 @@ if __name__ == '__main__': # in parallel. def produce(): for i in range(5): - print(u'generating %i' % i) + print('generating %i' % i) time.sleep(1) yield i def work(): num = yield while True: - print(u'processing %i' % num) + print('processing %i' % num) time.sleep(2) num = yield num * 2 @@ -489,7 +487,7 @@ if __name__ == '__main__': while True: num = yield time.sleep(1) - print(u'received %i' % num) + print('received %i' % num) ts_start = time.time() Pipeline([produce(), work(), consume()]).run_sequential() @@ -498,22 +496,22 @@ if __name__ == '__main__': ts_par = time.time() Pipeline([produce(), (work(), work()), consume()]).run_parallel() ts_end = time.time() - print(u'Sequential time:', ts_seq - ts_start) - print(u'Parallel time:', ts_par - ts_seq) - print(u'Multiply-parallel time:', ts_end - ts_par) + print('Sequential time:', ts_seq - ts_start) + print('Parallel time:', ts_par - ts_seq) + print('Multiply-parallel time:', ts_end - ts_par) print() # Test a pipeline that raises an exception. def exc_produce(): for i in range(10): - print(u'generating %i' % i) + print('generating %i' % i) time.sleep(1) yield i def exc_work(): num = yield while True: - print(u'processing %i' % num) + print('processing %i' % num) time.sleep(3) if num == 3: raise Exception() @@ -522,6 +520,6 @@ if __name__ == '__main__': def exc_consume(): while True: num = yield - print(u'received %i' % num) + print('received %i' % num) Pipeline([exc_produce(), exc_work(), exc_consume()]).run_parallel(1) From 910354a6c617ed5aa643cff666205b43e1557373 Mon Sep 17 00:00:00 2001 From: Andrew Rogl Date: Wed, 25 Aug 2021 19:07:55 +1000 Subject: [PATCH 07/26] fix unused import and flake8 --- beets/autotag/hooks.py | 11 +- beets/autotag/mb.py | 9 +- beets/importer.py | 6 +- beets/library.py | 232 +++++++++++++++++++------------------ beets/logging.py | 2 +- beets/plugins.py | 7 +- beets/ui/__init__.py | 7 +- beets/ui/commands.py | 38 +++--- beets/util/artresizer.py | 1 - beets/util/bluelet.py | 1 - beets/util/functemplate.py | 9 +- beets/util/pipeline.py | 9 +- 12 files changed, 183 insertions(+), 149 deletions(-) diff --git a/beets/autotag/hooks.py b/beets/autotag/hooks.py index 593a895f1..9cd6f2cd8 100644 --- a/beets/autotag/hooks.py +++ b/beets/autotag/hooks.py @@ -25,7 +25,6 @@ from beets.util import as_string from beets.autotag import mb from jellyfish import levenshtein_distance from unidecode import unidecode -import six log = logging.getLogger('beets') @@ -68,6 +67,7 @@ class AlbumInfo(AttrDict): ``mediums`` along with the fields up through ``tracks`` are required. The others are optional and may be None. """ + def __init__(self, tracks, album=None, album_id=None, artist=None, artist_id=None, asin=None, albumtype=None, va=False, year=None, month=None, day=None, label=None, mediums=None, @@ -153,6 +153,7 @@ class TrackInfo(AttrDict): may be None. The indices ``index``, ``medium``, and ``medium_index`` are all 1-based. """ + def __init__(self, title=None, track_id=None, release_track_id=None, artist=None, artist_id=None, length=None, index=None, medium=None, medium_index=None, medium_total=None, @@ -308,6 +309,7 @@ class LazyClassProperty: the sense that the getter is only invoked once. Subsequent accesses through *any* instance use the cached result. """ + def __init__(self, getter): self.getter = getter self.computed = False @@ -325,6 +327,7 @@ class Distance: weighted distance for all penalties as well as a weighted distance for each individual penalty. """ + def __init__(self): self._penalties = {} @@ -610,7 +613,7 @@ def album_candidates(items, artist, album, va_likely, extra_tags): if artist and album: try: yield from mb.match_album(artist, album, len(items), - extra_tags) + extra_tags) except mb.MusicBrainzAPIError as exc: exc.log(log) @@ -618,13 +621,13 @@ def album_candidates(items, artist, album, va_likely, extra_tags): if va_likely and album: try: yield from mb.match_album(None, album, len(items), - extra_tags) + extra_tags) except mb.MusicBrainzAPIError as exc: exc.log(log) # Candidates from plugins. yield from plugins.candidates(items, artist, album, va_likely, - extra_tags) + extra_tags) @plugins.notify_info_yielded('trackinfo_received') diff --git a/beets/autotag/mb.py b/beets/autotag/mb.py index 3b9434acd..cb74d5b30 100644 --- a/beets/autotag/mb.py +++ b/beets/autotag/mb.py @@ -26,7 +26,6 @@ import beets.autotag.hooks import beets from beets import util from beets import config -import six VARIOUS_ARTISTS_ID = '89ad4ac3-39f7-470e-963a-56509c546377' @@ -53,6 +52,7 @@ class MusicBrainzAPIError(util.HumanReadableException): """An error while talking to MusicBrainz. The `query` field is the parameter to the action and may have any type. """ + def __init__(self, reason, verb, query, tb=None): self.query = query if isinstance(reason, musicbrainzngs.WebServiceError): @@ -64,6 +64,7 @@ class MusicBrainzAPIError(util.HumanReadableException): self._reasonstr(), self.verb, repr(self.query) ) + log = logging.getLogger('beets') RELEASE_INCLUDES = ['artists', 'media', 'recordings', 'release-groups', @@ -315,9 +316,9 @@ def album_info(release): for i in range(0, ntracks, BROWSE_CHUNKSIZE): log.debug('Retrieving tracks starting at {}', i) recording_list.extend(musicbrainzngs.browse_recordings( - release=release['id'], limit=BROWSE_CHUNKSIZE, - includes=BROWSE_INCLUDES, - offset=i)['recording-list']) + release=release['id'], limit=BROWSE_CHUNKSIZE, + includes=BROWSE_INCLUDES, + offset=i)['recording-list']) track_map = {r['id']: r for r in recording_list} for medium in release['medium-list']: for recording in medium['track-list']: diff --git a/beets/importer.py b/beets/importer.py index cccf035c0..561cedd2c 100644 --- a/beets/importer.py +++ b/beets/importer.py @@ -176,6 +176,7 @@ class ImportSession: """Controls an import action. Subclasses should implement methods to communicate with the user or otherwise make decisions. """ + def __init__(self, lib, loghandler, paths, query): """Create a session. `lib` is a Library object. `loghandler` is a logging.Handler. Either `paths` or `query` is non-null and indicates @@ -379,7 +380,7 @@ class ImportSession: """ self._merged_items.update(paths) dirs = {os.path.dirname(path) if os.path.isfile(path) else path - for path in paths} + for path in paths} self._merged_dirs.update(dirs) def is_resuming(self, toppath): @@ -414,6 +415,7 @@ class BaseImportTask: Tasks flow through the importer pipeline. Each stage can update them. """ + def __init__(self, toppath, paths, items): """Create a task. The primary fields that define a task are: @@ -467,6 +469,7 @@ class ImportTask(BaseImportTask): * `finalize()` Update the import progress and cleanup the file system. """ + def __init__(self, toppath, paths, items): super().__init__(toppath, paths, items) self.choice_flag = None @@ -1097,6 +1100,7 @@ class ImportTaskFactory: """Generate album and singleton import tasks for all media files indicated by a path. """ + def __init__(self, toppath, session): """Create a new task factory. diff --git a/beets/library.py b/beets/library.py index c16b320da..38b129cab 100644 --- a/beets/library.py +++ b/beets/library.py @@ -20,7 +20,6 @@ import sys import unicodedata import time import re -import six import string import shlex @@ -247,6 +246,7 @@ class SmartArtistSort(dbcore.query.Sort): """Sort by artist (either album artist or track artist), prioritizing the sort field over the raw field. """ + def __init__(self, model_cls, ascending=True, case_insensitive=True): self.album = model_cls is Album self.ascending = ascending @@ -262,12 +262,15 @@ class SmartArtistSort(dbcore.query.Sort): def sort(self, objs): if self.album: - field = lambda a: a.albumartist_sort or a.albumartist + def field(a): + return a.albumartist_sort or a.albumartist else: - field = lambda i: i.artist_sort or i.artist + def field(i): + return i.artist_sort or i.artist if self.case_insensitive: - key = lambda x: field(x).lower() + def key(x): + return field(x).lower() else: key = field return sorted(objs, key=key, reverse=not self.ascending) @@ -283,6 +286,7 @@ class FileOperationError(Exception): Possibilities include an unsupported media type, a permissions error, and an unhandled Mutagen exception. """ + def __init__(self, path, reason): """Create an exception describing an operation on the file at `path` with the underlying (chained) exception `reason`. @@ -308,6 +312,7 @@ class FileOperationError(Exception): class ReadError(FileOperationError): """An error while reading a file (i.e. in `Item.read`). """ + def __str__(self): return 'error reading ' + super().text() @@ -315,6 +320,7 @@ class ReadError(FileOperationError): class WriteError(FileOperationError): """An error while writing a file (i.e. in `Item.write`). """ + def __str__(self): return 'error writing ' + super().text() @@ -371,7 +377,7 @@ class FormattedItemMapping(dbcore.db.FormattedMapping): # We treat album and item keys specially here, # so exclude transitive album keys from the model's keys. super().__init__(item, included_keys=[], - for_path=for_path) + for_path=for_path) self.included_keys = included_keys if included_keys == self.ALL_KEYS: # Performance note: this triggers a database query. @@ -445,84 +451,84 @@ class Item(LibModel): _table = 'items' _flex_table = 'item_attributes' _fields = { - 'id': types.PRIMARY_ID, - 'path': PathType(), + 'id': types.PRIMARY_ID, + 'path': PathType(), 'album_id': types.FOREIGN_ID, - 'title': types.STRING, - 'artist': types.STRING, - 'artist_sort': types.STRING, - 'artist_credit': types.STRING, - 'album': types.STRING, - 'albumartist': types.STRING, - 'albumartist_sort': types.STRING, - 'albumartist_credit': types.STRING, - 'genre': types.STRING, - 'style': types.STRING, - 'discogs_albumid': types.INTEGER, - 'discogs_artistid': types.INTEGER, - 'discogs_labelid': types.INTEGER, - 'lyricist': types.STRING, - 'composer': types.STRING, - 'composer_sort': types.STRING, - 'work': types.STRING, - 'mb_workid': types.STRING, - 'work_disambig': types.STRING, - 'arranger': types.STRING, - 'grouping': types.STRING, - 'year': types.PaddedInt(4), - 'month': types.PaddedInt(2), - 'day': types.PaddedInt(2), - 'track': types.PaddedInt(2), - 'tracktotal': types.PaddedInt(2), - 'disc': types.PaddedInt(2), - 'disctotal': types.PaddedInt(2), - 'lyrics': types.STRING, - 'comments': types.STRING, - 'bpm': types.INTEGER, - 'comp': types.BOOLEAN, - 'mb_trackid': types.STRING, - 'mb_albumid': types.STRING, - 'mb_artistid': types.STRING, - 'mb_albumartistid': types.STRING, - 'mb_releasetrackid': types.STRING, - 'trackdisambig': types.STRING, - 'albumtype': types.STRING, - 'label': types.STRING, + 'title': types.STRING, + 'artist': types.STRING, + 'artist_sort': types.STRING, + 'artist_credit': types.STRING, + 'album': types.STRING, + 'albumartist': types.STRING, + 'albumartist_sort': types.STRING, + 'albumartist_credit': types.STRING, + 'genre': types.STRING, + 'style': types.STRING, + 'discogs_albumid': types.INTEGER, + 'discogs_artistid': types.INTEGER, + 'discogs_labelid': types.INTEGER, + 'lyricist': types.STRING, + 'composer': types.STRING, + 'composer_sort': types.STRING, + 'work': types.STRING, + 'mb_workid': types.STRING, + 'work_disambig': types.STRING, + 'arranger': types.STRING, + 'grouping': types.STRING, + 'year': types.PaddedInt(4), + 'month': types.PaddedInt(2), + 'day': types.PaddedInt(2), + 'track': types.PaddedInt(2), + 'tracktotal': types.PaddedInt(2), + 'disc': types.PaddedInt(2), + 'disctotal': types.PaddedInt(2), + 'lyrics': types.STRING, + 'comments': types.STRING, + 'bpm': types.INTEGER, + 'comp': types.BOOLEAN, + 'mb_trackid': types.STRING, + 'mb_albumid': types.STRING, + 'mb_artistid': types.STRING, + 'mb_albumartistid': types.STRING, + 'mb_releasetrackid': types.STRING, + 'trackdisambig': types.STRING, + 'albumtype': types.STRING, + 'label': types.STRING, 'acoustid_fingerprint': types.STRING, - 'acoustid_id': types.STRING, - 'mb_releasegroupid': types.STRING, - 'asin': types.STRING, - 'isrc': types.STRING, - 'catalognum': types.STRING, - 'script': types.STRING, - 'language': types.STRING, - 'country': types.STRING, - 'albumstatus': types.STRING, - 'media': types.STRING, - 'albumdisambig': types.STRING, + 'acoustid_id': types.STRING, + 'mb_releasegroupid': types.STRING, + 'asin': types.STRING, + 'isrc': types.STRING, + 'catalognum': types.STRING, + 'script': types.STRING, + 'language': types.STRING, + 'country': types.STRING, + 'albumstatus': types.STRING, + 'media': types.STRING, + 'albumdisambig': types.STRING, 'releasegroupdisambig': types.STRING, - 'disctitle': types.STRING, - 'encoder': types.STRING, - 'rg_track_gain': types.NULL_FLOAT, - 'rg_track_peak': types.NULL_FLOAT, - 'rg_album_gain': types.NULL_FLOAT, - 'rg_album_peak': types.NULL_FLOAT, - 'r128_track_gain': types.NullPaddedInt(6), - 'r128_album_gain': types.NullPaddedInt(6), - 'original_year': types.PaddedInt(4), - 'original_month': types.PaddedInt(2), - 'original_day': types.PaddedInt(2), - 'initial_key': MusicalKey(), + 'disctitle': types.STRING, + 'encoder': types.STRING, + 'rg_track_gain': types.NULL_FLOAT, + 'rg_track_peak': types.NULL_FLOAT, + 'rg_album_gain': types.NULL_FLOAT, + 'rg_album_peak': types.NULL_FLOAT, + 'r128_track_gain': types.NullPaddedInt(6), + 'r128_album_gain': types.NullPaddedInt(6), + 'original_year': types.PaddedInt(4), + 'original_month': types.PaddedInt(2), + 'original_day': types.PaddedInt(2), + 'initial_key': MusicalKey(), - 'length': DurationType(), - 'bitrate': types.ScaledInt(1000, 'kbps'), - 'format': types.STRING, - 'samplerate': types.ScaledInt(1000, 'kHz'), - 'bitdepth': types.INTEGER, - 'channels': types.INTEGER, - 'mtime': DateType(), - 'added': DateType(), + 'length': DurationType(), + 'bitrate': types.ScaledInt(1000, 'kbps'), + 'format': types.STRING, + 'samplerate': types.ScaledInt(1000, 'kHz'), + 'bitdepth': types.INTEGER, + 'channels': types.INTEGER, + 'mtime': DateType(), + 'added': DateType(), } _search_fields = ('artist', 'title', 'comments', @@ -1015,49 +1021,49 @@ class Album(LibModel): _flex_table = 'album_attributes' _always_dirty = True _fields = { - 'id': types.PRIMARY_ID, + 'id': types.PRIMARY_ID, 'artpath': PathType(True), - 'added': DateType(), + 'added': DateType(), - 'albumartist': types.STRING, - 'albumartist_sort': types.STRING, - 'albumartist_credit': types.STRING, - 'album': types.STRING, - 'genre': types.STRING, - 'style': types.STRING, - 'discogs_albumid': types.INTEGER, - 'discogs_artistid': types.INTEGER, - 'discogs_labelid': types.INTEGER, - 'year': types.PaddedInt(4), - 'month': types.PaddedInt(2), - 'day': types.PaddedInt(2), - 'disctotal': types.PaddedInt(2), - 'comp': types.BOOLEAN, - 'mb_albumid': types.STRING, - 'mb_albumartistid': types.STRING, - 'albumtype': types.STRING, - 'label': types.STRING, - 'mb_releasegroupid': types.STRING, - 'asin': types.STRING, - 'catalognum': types.STRING, - 'script': types.STRING, - 'language': types.STRING, - 'country': types.STRING, - 'albumstatus': types.STRING, - 'albumdisambig': types.STRING, + 'albumartist': types.STRING, + 'albumartist_sort': types.STRING, + 'albumartist_credit': types.STRING, + 'album': types.STRING, + 'genre': types.STRING, + 'style': types.STRING, + 'discogs_albumid': types.INTEGER, + 'discogs_artistid': types.INTEGER, + 'discogs_labelid': types.INTEGER, + 'year': types.PaddedInt(4), + 'month': types.PaddedInt(2), + 'day': types.PaddedInt(2), + 'disctotal': types.PaddedInt(2), + 'comp': types.BOOLEAN, + 'mb_albumid': types.STRING, + 'mb_albumartistid': types.STRING, + 'albumtype': types.STRING, + 'label': types.STRING, + 'mb_releasegroupid': types.STRING, + 'asin': types.STRING, + 'catalognum': types.STRING, + 'script': types.STRING, + 'language': types.STRING, + 'country': types.STRING, + 'albumstatus': types.STRING, + 'albumdisambig': types.STRING, 'releasegroupdisambig': types.STRING, - 'rg_album_gain': types.NULL_FLOAT, - 'rg_album_peak': types.NULL_FLOAT, - 'r128_album_gain': types.NullPaddedInt(6), - 'original_year': types.PaddedInt(4), - 'original_month': types.PaddedInt(2), - 'original_day': types.PaddedInt(2), + 'rg_album_gain': types.NULL_FLOAT, + 'rg_album_peak': types.NULL_FLOAT, + 'r128_album_gain': types.NullPaddedInt(6), + 'original_year': types.PaddedInt(4), + 'original_month': types.PaddedInt(2), + 'original_day': types.PaddedInt(2), } _search_fields = ('album', 'albumartist', 'genre') _types = { - 'path': PathType(), + 'path': PathType(), 'data_source': types.STRING, } diff --git a/beets/logging.py b/beets/logging.py index 16cd9b210..4f004f8d2 100644 --- a/beets/logging.py +++ b/beets/logging.py @@ -25,7 +25,6 @@ from copy import copy from logging import * # noqa import subprocess import threading -import six def logsafe(val): @@ -92,6 +91,7 @@ class StrFormatLogger(Logger): class ThreadLocalLevelLogger(Logger): """A version of `Logger` whose level is thread-local instead of shared. """ + def __init__(self, name, level=NOTSET): self._thread_level = threading.local() self.default_level = NOTSET diff --git a/beets/plugins.py b/beets/plugins.py index c3342985f..ed1f82d8f 100644 --- a/beets/plugins.py +++ b/beets/plugins.py @@ -26,7 +26,6 @@ from functools import wraps import beets from beets import logging import mediafile -import six PLUGIN_NAMESPACE = 'beetsplug' @@ -50,6 +49,7 @@ class PluginLogFilter(logging.Filter): """A logging filter that identifies the plugin that emitted a log message. """ + def __init__(self, plugin): self.prefix = f'{plugin.name}: ' @@ -70,6 +70,7 @@ class BeetsPlugin: functionality by defining a subclass of BeetsPlugin and overriding the abstract methods defined here. """ + def __init__(self, name=None): """Perform one-time plugin setup. """ @@ -138,7 +139,7 @@ class BeetsPlugin: self._log.setLevel(log_level) if argspec.varkw is None: kwargs = {k: v for k, v in kwargs.items() - if k in argspec.args} + if k in argspec.args} try: return func(*args, **kwargs) @@ -381,7 +382,7 @@ def candidates(items, artist, album, va_likely, extra_tags=None): """ for plugin in find_plugins(): yield from plugin.candidates(items, artist, album, va_likely, - extra_tags) + extra_tags) def item_candidates(item, artist, title): diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index 662528658..1540a2ee0 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -40,7 +40,6 @@ from beets.autotag import mb from beets.dbcore import query as db_query from beets.dbcore import db import confuse -import six # On Windows platforms, use colorama to support "ANSI" terminal colors. if sys.platform == 'win32': @@ -524,8 +523,8 @@ def colorize(color_name, text): global COLORS if not COLORS: COLORS = {name: - config['ui']['colors'][name].as_str() - for name in COLOR_NAMES} + config['ui']['colors'][name].as_str() + for name in COLOR_NAMES} # In case a 3rd party plugin is still passing the actual color ('red') # instead of the abstract color name ('text_error') color = COLORS.get(color_name) @@ -815,6 +814,7 @@ class CommonOptionsParser(optparse.OptionParser): Each method is fully documented in the related method. """ + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._album_flags = False @@ -933,6 +933,7 @@ class Subcommand: """A subcommand of a root command-line application that may be invoked by a SubcommandOptionParser. """ + def __init__(self, name, parser=None, help='', aliases=(), hide=False): """Creates a new subcommand. name is the primary way to invoke the subcommand; aliases are alternate names. parser is an diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 9fd420aa2..3a3374013 100755 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -37,7 +37,7 @@ from beets.util import syspath, normpath, ancestry, displayable_path, \ from beets import library from beets import config from beets import logging -import six + from . import _store_dict VARIOUS_ARTISTS = 'Various Artists' @@ -110,6 +110,7 @@ def fields_func(lib, opts, args): print_("Album flexible attributes:") _print_keys(tx.query(unique_fields % library.Album._flex_table)) + fields_cmd = ui.Subcommand( 'fields', help='show fields available for queries and format strings' @@ -240,7 +241,7 @@ def show_change(cur_artist, cur_album, match): return f'{medium}-{medium_index}' else: return str(medium_index if medium_index is not None - else index) + else index) else: return str(index) @@ -249,7 +250,7 @@ def show_change(cur_artist, cur_album, match): (cur_album != match.info.album and match.info.album != VARIOUS_ARTISTS): artist_l, artist_r = cur_artist or '', match.info.artist - album_l, album_r = cur_album or '', match.info.album + album_l, album_r = cur_album or '', match.info.album if artist_r == VARIOUS_ARTISTS: # Hide artists for VA releases. artist_l, artist_r = '', '' @@ -301,7 +302,7 @@ def show_change(cur_artist, cur_album, match): media = match.info.media or 'Media' if match.info.mediums > 1 and track_info.disctitle: lhs = '{} {}: {}'.format(media, track_info.medium, - track_info.disctitle) + track_info.disctitle) elif match.info.mediums > 1: lhs = f'{media} {track_info.medium}' elif track_info.disctitle: @@ -380,8 +381,8 @@ def show_change(cur_artist, cur_album, match): match.extra_tracks) for track_info in match.extra_tracks: line = ' ! {0: <{width}} (#{1: >2})'.format(track_info.title, - format_index(track_info), - width=pad_width) + format_index(track_info), + width=pad_width) if track_info.length: line += ' (%s)' % ui.human_seconds_short(track_info.length) print_(ui.colorize('text_warning', line)) @@ -390,8 +391,8 @@ def show_change(cur_artist, cur_album, match): pad_width = max(len(item.title) for item in match.extra_items) for item in match.extra_items: line = ' ! {0: <{width}} (#{1: >2})'.format(item.title, - format_index(item), - width=pad_width) + format_index(item), + width=pad_width) if item.length: line += ' (%s)' % ui.human_seconds_short(item.length) print_(ui.colorize('text_warning', line)) @@ -665,7 +666,7 @@ def manual_id(session, task): Input an ID, either for an album ("release") or a track ("recording"). """ prompt = 'Enter {} ID:'.format('release' if task.is_album - else 'recording') + else 'recording') search_id = input_(prompt).strip() if task.is_album: @@ -686,6 +687,7 @@ def abort_action(session, task): class TerminalImportSession(importer.ImportSession): """An import session that runs in a terminal. """ + def choose_match(self, task): """Given an initial autotagging of items, go through an interactive dance with the user to ask for a choice of metadata. Returns an @@ -1249,20 +1251,20 @@ def remove_items(lib, query, album, delete, force): if not force: # Prepare confirmation with user. album_str = " in {} album{}".format( - len(albums), 's' if len(albums) > 1 else '' - ) if album else "" + len(albums), 's' if len(albums) > 1 else '' + ) if album else "" if delete: fmt = '$path - $title' prompt = 'Really DELETE' prompt_all = 'Really DELETE {} file{}{}'.format( - len(items), 's' if len(items) > 1 else '', album_str + len(items), 's' if len(items) > 1 else '', album_str ) else: fmt = '' prompt = 'Really remove from the library?' prompt_all = 'Really remove {} item{}{} from the library?'.format( - len(items), 's' if len(items) > 1 else '', album_str + len(items), 's' if len(items) > 1 else '', album_str ) # Helpers for printing affected items @@ -1537,8 +1539,12 @@ def move_items(lib, dest, query, copy, album, pretend, confirm=False, num_objs = len(objs) # Filter out files that don't need to be moved. - isitemmoved = lambda item: item.path != item.destination(basedir=dest) - isalbummoved = lambda album: any(isitemmoved(i) for i in album.items()) + def isitemmoved(item): + return item.path != item.destination(basedir=dest) + + def isalbummoved(album): + return any(isitemmoved(i) for i in album.items()) + objs = [o for o in objs if (isalbummoved if album else isitemmoved)(o)] num_unmoved = num_objs - len(objs) # Report unmoved files that match the query. @@ -1724,6 +1730,7 @@ def config_edit(): message += ". Please set the EDITOR environment variable" raise ui.UserError(message) + config_cmd = ui.Subcommand('config', help='show or edit the user configuration') config_cmd.parser.add_option( @@ -1756,6 +1763,7 @@ def print_completion(*args): log.warning('Warning: Unable to find the bash-completion package. ' 'Command line completion might not work.') + BASH_COMPLETION_PATHS = map(syspath, [ '/etc/bash_completion', '/usr/share/bash-completion/bash_completion', diff --git a/beets/util/artresizer.py b/beets/util/artresizer.py index 3c2db08da..40efbc9fd 100644 --- a/beets/util/artresizer.py +++ b/beets/util/artresizer.py @@ -23,7 +23,6 @@ from tempfile import NamedTemporaryFile from six.moves.urllib.parse import urlencode from beets import logging from beets import util -import six # Resizing methods PIL = 1 diff --git a/beets/util/bluelet.py b/beets/util/bluelet.py index bd2b2838b..a40f3b2f7 100644 --- a/beets/util/bluelet.py +++ b/beets/util/bluelet.py @@ -6,7 +6,6 @@ asyncore. Bluelet: easy concurrency without all the messy parallelism. """ -import six import socket import select import sys diff --git a/beets/util/functemplate.py b/beets/util/functemplate.py index 63726d4cc..802076649 100644 --- a/beets/util/functemplate.py +++ b/beets/util/functemplate.py @@ -32,7 +32,6 @@ import ast import dis import types import sys -import six import functools SYMBOL_DELIM = '$' @@ -50,6 +49,7 @@ class Environment: """Contains the values and functions to be substituted into a template. """ + def __init__(self, values, functions): self.values = values self.functions = functions @@ -170,6 +170,7 @@ def compile_func(arg_names, statements, name='_the_func', debug=False): class Symbol: """A variable-substitution symbol in a template.""" + def __init__(self, ident, original): self.ident = ident self.original = original @@ -197,6 +198,7 @@ class Symbol: class Call: """A function call in a template.""" + def __init__(self, ident, args, original): self.ident = ident self.args = args @@ -204,7 +206,7 @@ class Call: def __repr__(self): return 'Call({}, {}, {})'.format(repr(self.ident), repr(self.args), - repr(self.original)) + repr(self.original)) def evaluate(self, env): """Evaluate the function call in the environment, returning a @@ -257,6 +259,7 @@ class Expression: """Top-level template construct: contains a list of text blobs, Symbols, and Calls. """ + def __init__(self, parts): self.parts = parts @@ -312,6 +315,7 @@ class Parser: replaced with a real, accepted parsing technique (PEG, parser generator, etc.). """ + def __init__(self, string, in_argument=False): """ Create a new parser. :param in_arguments: boolean that indicates the parser is to be @@ -568,6 +572,7 @@ def template(fmt): class Template: """A string template, including text, Symbols, and Calls. """ + def __init__(self, template): self.expr = _parse(template) self.original = template diff --git a/beets/util/pipeline.py b/beets/util/pipeline.py index 7e5e2ba2a..fa7e5eb31 100644 --- a/beets/util/pipeline.py +++ b/beets/util/pipeline.py @@ -35,7 +35,6 @@ in place of any single coroutine. from six.moves import queue from threading import Thread, Lock import sys -import six BUBBLE = '__PIPELINE_BUBBLE__' POISON = '__PIPELINE_POISON__' @@ -89,6 +88,7 @@ class CountedQueue(queue.Queue): still feeding into it. The queue is poisoned when all threads are finished with the queue. """ + def __init__(self, maxsize=0): queue.Queue.__init__(self, maxsize) self.nthreads = 0 @@ -137,6 +137,7 @@ class MultiMessage: """A message yielded by a pipeline stage encapsulating multiple values to be sent to the next stage. """ + def __init__(self, messages): self.messages = messages @@ -208,6 +209,7 @@ def _allmsgs(obj): class PipelineThread(Thread): """Abstract base class for pipeline-stage threads.""" + def __init__(self, all_threads): super().__init__() self.abort_lock = Lock() @@ -239,6 +241,7 @@ class FirstPipelineThread(PipelineThread): """The thread running the first stage in a parallel pipeline setup. The coroutine should just be a generator. """ + def __init__(self, coro, out_queue, all_threads): super().__init__(all_threads) self.coro = coro @@ -277,6 +280,7 @@ class MiddlePipelineThread(PipelineThread): """A thread running any stage in the pipeline except the first or last. """ + def __init__(self, coro, in_queue, out_queue, all_threads): super().__init__(all_threads) self.coro = coro @@ -325,6 +329,7 @@ class LastPipelineThread(PipelineThread): """A thread running the last stage in a pipeline. The coroutine should yield nothing. """ + def __init__(self, coro, in_queue, all_threads): super().__init__(all_threads) self.coro = coro @@ -362,6 +367,7 @@ class Pipeline: is a coroutine that receives messages from the previous stage and yields messages to be sent to the next stage. """ + def __init__(self, stages): """Makes a new pipeline from a list of coroutines. There must be at least two stages. @@ -464,6 +470,7 @@ class Pipeline: for msg in msgs: yield msg + # Smoke test. if __name__ == '__main__': import time From 1ec87a3bdd737abe46c6e614051bf9e314db4619 Mon Sep 17 00:00:00 2001 From: Andrew Rogl Date: Thu, 26 Aug 2021 19:12:51 +1000 Subject: [PATCH 08/26] pyupgrade beetsplug and tests All tests working More tidy up to be done --- beetsplug/__init__.py | 2 - beetsplug/absubmit.py | 44 ++- beetsplug/acousticbrainz.py | 48 ++-- beetsplug/aura.py | 21 +- beetsplug/badfiles.py | 36 ++- beetsplug/bareasc.py | 8 +- beetsplug/beatport.py | 82 +++--- beetsplug/bench.py | 2 - beetsplug/bpd/__init__.py | 371 ++++++++++++------------ beetsplug/bpd/gstplayer.py | 8 +- beetsplug/bpm.py | 23 +- beetsplug/bpsync.py | 48 ++-- beetsplug/bucket.py | 19 +- beetsplug/chroma.py | 48 ++-- beetsplug/convert.py | 148 +++++----- beetsplug/deezer.py | 16 +- beetsplug/discogs.py | 60 ++-- beetsplug/duplicates.py | 86 +++--- beetsplug/edit.py | 50 ++-- beetsplug/embedart.py | 44 ++- beetsplug/embyupdate.py | 25 +- beetsplug/export.py | 52 ++-- beetsplug/fetchart.py | 254 +++++++++-------- beetsplug/filefilter.py | 4 +- beetsplug/fish.py | 2 - beetsplug/freedesktop.py | 12 +- beetsplug/fromfilename.py | 6 +- beetsplug/ftintitle.py | 20 +- beetsplug/fuzzy.py | 4 +- beetsplug/gmusic.py | 28 +- beetsplug/hook.py | 16 +- beetsplug/ihate.py | 18 +- beetsplug/importadded.py | 33 +-- beetsplug/importfeeds.py | 10 +- beetsplug/info.py | 32 +-- beetsplug/inline.py | 24 +- beetsplug/ipfs.py | 20 +- beetsplug/keyfinder.py | 22 +- beetsplug/kodiupdate.py | 24 +- beetsplug/lastgenre/__init__.py | 60 ++-- beetsplug/lastimport.py | 52 ++-- beetsplug/loadext.py | 8 +- beetsplug/lyrics.py | 178 ++++++------ beetsplug/mbcollection.py | 28 +- beetsplug/mbsubmit.py | 8 +- beetsplug/mbsync.py | 40 ++- beetsplug/metasync/__init__.py | 16 +- beetsplug/metasync/amarok.py | 6 +- beetsplug/metasync/itunes.py | 18 +- beetsplug/missing.py | 28 +- beetsplug/mpdstats.py | 69 +++-- beetsplug/mpdupdate.py | 24 +- beetsplug/parentwork.py | 14 +- beetsplug/permissions.py | 22 +- beetsplug/play.py | 28 +- beetsplug/playlist.py | 18 +- beetsplug/plexupdate.py | 29 +- beetsplug/random.py | 16 +- beetsplug/replaygain.py | 168 ++++++----- beetsplug/rewrite.py | 10 +- beetsplug/scrub.py | 28 +- beetsplug/smartplaylist.py | 34 ++- beetsplug/sonosupdate.py | 10 +- beetsplug/spotify.py | 66 +++-- beetsplug/subsonicplaylist.py | 14 +- beetsplug/subsonicupdate.py | 22 +- beetsplug/the.py | 26 +- beetsplug/thumbnails.py | 62 ++--- beetsplug/types.py | 4 +- beetsplug/unimported.py | 14 +- beetsplug/web/__init__.py | 24 +- beetsplug/zero.py | 22 +- test/__init__.py | 3 - test/_common.py | 70 +++-- test/helper.py | 44 ++- test/lyrics_download_samples.py | 4 +- test/test_acousticbrainz.py | 2 - test/test_art.py | 106 ++++--- test/test_art_resize.py | 2 - test/test_autotag.py | 286 ++++++++++--------- test/test_bareasc.py | 52 ++-- test/test_beatport.py | 4 +- test/test_bucket.py | 2 - test/test_config_command.py | 10 +- test/test_convert.py | 24 +- test/test_datequery.py | 8 +- test/test_dbcore.py | 46 ++- test/test_discogs.py | 2 - test/test_edit.py | 92 +++--- test/test_embedart.py | 12 +- test/test_embyupdate.py | 16 +- test/test_export.py | 2 - test/test_fetchart.py | 4 +- test/test_filefilter.py | 2 - test/test_files.py | 54 ++-- test/test_ftintitle.py | 82 +++--- test/test_hidden.py | 2 - test/test_hook.py | 20 +- test/test_ihate.py | 23 +- test/test_importadded.py | 26 +- test/test_importer.py | 330 +++++++++++----------- test/test_importfeeds.py | 4 - test/test_info.py | 14 +- test/test_ipfs.py | 6 +- test/test_keyfinder.py | 4 +- test/test_lastgenre.py | 80 +++--- test/test_library.py | 456 +++++++++++++++--------------- test/test_logging.py | 91 +++--- test/test_lyrics.py | 163 ++++++----- test/test_mb.py | 4 +- test/test_mbsubmit.py | 12 +- test/test_mbsync.py | 94 +++---- test/test_metasync.py | 10 +- test/test_mpdstats.py | 22 +- test/test_parentwork.py | 38 ++- test/test_permissions.py | 7 +- test/test_pipeline.py | 9 +- test/test_play.py | 32 +-- test/test_player.py | 36 ++- test/test_playlist.py | 114 ++++---- test/test_plexupdate.py | 8 +- test/test_plugin_mediafield.py | 24 +- test/test_plugins.py | 138 +++++---- test/test_query.py | 480 ++++++++++++++++---------------- test/test_random.py | 2 - test/test_replaygain.py | 30 +- test/test_smartplaylist.py | 112 ++++---- test/test_sort.py | 296 ++++++++++---------- test/test_spotify.py | 45 ++- test/test_subsonicupdate.py | 5 +- test/test_template.py | 196 +++++++------ test/test_the.py | 75 +++-- test/test_thumbnails.py | 30 +- test/test_types_plugin.py | 122 ++++---- test/test_ui.py | 440 +++++++++++++++-------------- test/test_ui_commands.py | 6 +- test/test_ui_importer.py | 26 +- test/test_ui_init.py | 6 +- test/test_util.py | 92 +++--- test/test_vfs.py | 8 +- test/test_web.py | 75 +++-- test/test_zero.py | 53 ++-- test/testall.py | 2 - 143 files changed, 3703 insertions(+), 4030 deletions(-) diff --git a/beetsplug/__init__.py b/beetsplug/__init__.py index febeb66f4..da2484917 100644 --- a/beetsplug/__init__.py +++ b/beetsplug/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -15,7 +14,6 @@ """A namespace package for beets plugins.""" -from __future__ import division, absolute_import, print_function # Make this a namespace package. from pkgutil import extend_path diff --git a/beetsplug/absubmit.py b/beetsplug/absubmit.py index 7419736a3..d1ea692f8 100644 --- a/beetsplug/absubmit.py +++ b/beetsplug/absubmit.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Pieter Mulder. # @@ -16,7 +15,6 @@ """Calculate acoustic information and submit to AcousticBrainz. """ -from __future__ import division, absolute_import, print_function import errno import hashlib @@ -49,17 +47,17 @@ def call(args): return util.command_output(args).stdout except subprocess.CalledProcessError as e: raise ABSubmitError( - u'{0} exited with status {1}'.format(args[0], e.returncode) + '{} exited with status {}'.format(args[0], e.returncode) ) class AcousticBrainzSubmitPlugin(plugins.BeetsPlugin): def __init__(self): - super(AcousticBrainzSubmitPlugin, self).__init__() + super().__init__() self.config.add({ - 'extractor': u'', + 'extractor': '', 'force': False, 'pretend': False }) @@ -70,7 +68,7 @@ class AcousticBrainzSubmitPlugin(plugins.BeetsPlugin): # Expicit path to extractor if not os.path.isfile(self.extractor): raise ui.UserError( - u'Extractor command does not exist: {0}.'. + 'Extractor command does not exist: {0}.'. format(self.extractor) ) else: @@ -80,8 +78,8 @@ class AcousticBrainzSubmitPlugin(plugins.BeetsPlugin): call([self.extractor]) except OSError: raise ui.UserError( - u'No extractor command found: please install the extractor' - u' binary from https://acousticbrainz.org/download' + 'No extractor command found: please install the extractor' + ' binary from https://acousticbrainz.org/download' ) except ABSubmitError: # Extractor found, will exit with an error if not called with @@ -103,17 +101,17 @@ class AcousticBrainzSubmitPlugin(plugins.BeetsPlugin): def commands(self): cmd = ui.Subcommand( 'absubmit', - help=u'calculate and submit AcousticBrainz analysis' + help='calculate and submit AcousticBrainz analysis' ) cmd.parser.add_option( - u'-f', u'--force', dest='force_refetch', + '-f', '--force', dest='force_refetch', action='store_true', default=False, - help=u're-download data when already present' + help='re-download data when already present' ) cmd.parser.add_option( - u'-p', u'--pretend', dest='pretend_fetch', + '-p', '--pretend', dest='pretend_fetch', action='store_true', default=False, - help=u'pretend to perform action, but show \ + help='pretend to perform action, but show \ only files which would be processed' ) cmd.func = self.command @@ -140,12 +138,12 @@ only files which would be processed' # If file has no MBID, skip it. if not mbid: - self._log.info(u'Not analysing {}, missing ' - u'musicbrainz track id.', item) + self._log.info('Not analysing {}, missing ' + 'musicbrainz track id.', item) return None if self.opts.pretend_fetch or self.config['pretend']: - self._log.info(u'pretend action - extract item: {}', item) + self._log.info('pretend action - extract item: {}', item) return None # Temporary file to save extractor output to, extractor only works @@ -160,11 +158,11 @@ only files which would be processed' call([self.extractor, util.syspath(item.path), filename]) except ABSubmitError as e: self._log.warning( - u'Failed to analyse {item} for AcousticBrainz: {error}', + 'Failed to analyse {item} for AcousticBrainz: {error}', item=item, error=e ) return None - with open(filename, 'r') as tmp_file: + with open(filename) as tmp_file: analysis = json.load(tmp_file) # Add the hash to the output. analysis['metadata']['version']['essentia_build_sha'] = \ @@ -188,11 +186,11 @@ only files which would be processed' try: message = response.json()['message'] except (ValueError, KeyError) as e: - message = u'unable to get error message: {}'.format(e) + message = f'unable to get error message: {e}' self._log.error( - u'Failed to submit AcousticBrainz analysis of {item}: ' - u'{message}).', item=item, message=message + 'Failed to submit AcousticBrainz analysis of {item}: ' + '{message}).', item=item, message=message ) else: - self._log.debug(u'Successfully submitted AcousticBrainz analysis ' - u'for {}.', item) + self._log.debug('Successfully submitted AcousticBrainz analysis ' + 'for {}.', item) diff --git a/beetsplug/acousticbrainz.py b/beetsplug/acousticbrainz.py index 725e0d634..4e065eaf9 100644 --- a/beetsplug/acousticbrainz.py +++ b/beetsplug/acousticbrainz.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2015-2016, Ohm Patel. # @@ -15,7 +14,6 @@ """Fetch various AcousticBrainz metadata using MBID. """ -from __future__ import division, absolute_import, print_function from collections import defaultdict @@ -138,7 +136,7 @@ class AcousticPlugin(plugins.BeetsPlugin): } def __init__(self): - super(AcousticPlugin, self).__init__() + super().__init__() self.config.add({ 'auto': True, @@ -152,11 +150,11 @@ class AcousticPlugin(plugins.BeetsPlugin): def commands(self): cmd = ui.Subcommand('acousticbrainz', - help=u"fetch metadata from AcousticBrainz") + help="fetch metadata from AcousticBrainz") cmd.parser.add_option( - u'-f', u'--force', dest='force_refetch', + '-f', '--force', dest='force_refetch', action='store_true', default=False, - help=u're-download data when already present' + help='re-download data when already present' ) def func(lib, opts, args): @@ -175,22 +173,22 @@ class AcousticPlugin(plugins.BeetsPlugin): def _get_data(self, mbid): data = {} for url in _generate_urls(mbid): - self._log.debug(u'fetching URL: {}', url) + self._log.debug('fetching URL: {}', url) try: res = requests.get(url) except requests.RequestException as exc: - self._log.info(u'request error: {}', exc) + self._log.info('request error: {}', exc) return {} if res.status_code == 404: - self._log.info(u'recording ID {} not found', mbid) + self._log.info('recording ID {} not found', mbid) return {} try: data.update(res.json()) except ValueError: - self._log.debug(u'Invalid Response: {}', res.text) + self._log.debug('Invalid Response: {}', res.text) return {} return data @@ -205,28 +203,28 @@ class AcousticPlugin(plugins.BeetsPlugin): # representative field name to check for previously fetched # data. if not force: - mood_str = item.get('mood_acoustic', u'') + mood_str = item.get('mood_acoustic', '') if mood_str: - self._log.info(u'data already present for: {}', item) + self._log.info('data already present for: {}', item) continue # We can only fetch data for tracks with MBIDs. if not item.mb_trackid: continue - self._log.info(u'getting data for: {}', item) + self._log.info('getting data for: {}', item) data = self._get_data(item.mb_trackid) if data: for attr, val in self._map_data_to_scheme(data, ABSCHEME): if not tags or attr in tags: - self._log.debug(u'attribute {} of {} set to {}', + self._log.debug('attribute {} of {} set to {}', attr, item, val) setattr(item, attr, val) else: - self._log.debug(u'skipping attribute {} of {}' - u' (value {}) due to config', + self._log.debug('skipping attribute {} of {}' + ' (value {}) due to config', attr, item, val) @@ -288,10 +286,9 @@ class AcousticPlugin(plugins.BeetsPlugin): # The recursive traversal. composites = defaultdict(list) - for attr, val in self._data_to_scheme_child(data, + yield from self._data_to_scheme_child(data, scheme, - composites): - yield attr, val + composites) # When composites has been populated, yield the composite attributes # by joining their parts. @@ -311,10 +308,9 @@ class AcousticPlugin(plugins.BeetsPlugin): for k, v in subscheme.items(): if k in subdata: if type(v) == dict: - for attr, val in self._data_to_scheme_child(subdata[k], + yield from self._data_to_scheme_child(subdata[k], v, - composites): - yield attr, val + composites) elif type(v) == tuple: composite_attribute, part_number = v attribute_parts = composites[composite_attribute] @@ -325,10 +321,10 @@ class AcousticPlugin(plugins.BeetsPlugin): else: yield v, subdata[k] else: - self._log.warning(u'Acousticbrainz did not provide info' - u'about {}', k) - self._log.debug(u'Data {} could not be mapped to scheme {} ' - u'because key {} was not found', subdata, v, k) + self._log.warning('Acousticbrainz did not provide info' + 'about {}', k) + self._log.debug('Data {} could not be mapped to scheme {} ' + 'because key {} was not found', subdata, v, k) def _generate_urls(mbid): diff --git a/beetsplug/aura.py b/beetsplug/aura.py index df76ca856..3799e0df4 100644 --- a/beetsplug/aura.py +++ b/beetsplug/aura.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - # This file is part of beets. # Copyright 2020, Callum Brown. # @@ -16,7 +14,6 @@ """An AURA server using Flask.""" -from __future__ import division, absolute_import, print_function from mimetypes import guess_type import re @@ -215,7 +212,7 @@ class AURADocument: else: # Increment page token by 1 next_url = request.url.replace( - "page={}".format(page), "page={}".format(page + 1) + f"page={page}", "page={}".format(page + 1) ) # Get only the items in the page range data = [self.resource_object(collection[i]) for i in range(start, end)] @@ -265,7 +262,7 @@ class AURADocument: image_id = identifier["id"] included.append(ImageDocument.resource_object(image_id)) else: - raise ValueError("Invalid resource type: {}".format(res_type)) + raise ValueError(f"Invalid resource type: {res_type}") return included def all_resources(self): @@ -462,7 +459,7 @@ class AlbumDocument(AURADocument): if album.artpath: path = py3_path(album.artpath) filename = path.split("/")[-1] - image_id = "album-{}-{}".format(album.id, filename) + image_id = f"album-{album.id}-{filename}" relationships["images"] = { "data": [{"type": "image", "id": image_id}] } @@ -886,7 +883,7 @@ def create_app(): """An application factory for use by a WSGI server.""" config["aura"].add( { - "host": u"127.0.0.1", + "host": "127.0.0.1", "port": 8337, "cors": [], "cors_supports_credentials": False, @@ -932,7 +929,7 @@ class AURAPlugin(BeetsPlugin): def __init__(self): """Add configuration options for the AURA plugin.""" - super(AURAPlugin, self).__init__() + super().__init__() def commands(self): """Add subcommand used to run the AURA server.""" @@ -954,13 +951,13 @@ class AURAPlugin(BeetsPlugin): threaded=True, ) - run_aura_cmd = Subcommand("aura", help=u"run an AURA server") + run_aura_cmd = Subcommand("aura", help="run an AURA server") run_aura_cmd.parser.add_option( - u"-d", - u"--debug", + "-d", + "--debug", action="store_true", default=False, - help=u"use Flask debug mode", + help="use Flask debug mode", ) run_aura_cmd.func = run_aura return [run_aura_cmd] diff --git a/beetsplug/badfiles.py b/beetsplug/badfiles.py index 5eaecc054..874d533ce 100644 --- a/beetsplug/badfiles.py +++ b/beetsplug/badfiles.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, François-Xavier Thomas. # @@ -16,7 +15,6 @@ """Use command-line tools to check for audio file corruption. """ -from __future__ import division, absolute_import, print_function from subprocess import check_output, CalledProcessError, list2cmdline, STDOUT @@ -52,7 +50,7 @@ class CheckerCommandException(Exception): class BadFiles(BeetsPlugin): def __init__(self): - super(BadFiles, self).__init__() + super().__init__() self.verbose = False self.register_listener('import_task_start', @@ -61,7 +59,7 @@ class BadFiles(BeetsPlugin): self.on_import_task_before_choice) def run_command(self, cmd): - self._log.debug(u"running command: {}", + self._log.debug("running command: {}", displayable_path(list2cmdline(cmd))) try: output = check_output(cmd, stderr=STDOUT) @@ -110,52 +108,52 @@ class BadFiles(BeetsPlugin): # First, check whether the path exists. If not, the user # should probably run `beet update` to cleanup your library. dpath = displayable_path(item.path) - self._log.debug(u"checking path: {}", dpath) + self._log.debug("checking path: {}", dpath) if not os.path.exists(item.path): - ui.print_(u"{}: file does not exist".format( + ui.print_("{}: file does not exist".format( ui.colorize('text_error', dpath))) # Run the checker against the file if one is found ext = os.path.splitext(item.path)[1][1:].decode('utf8', 'ignore') checker = self.get_checker(ext) if not checker: - self._log.error(u"no checker specified in the config for {}", + self._log.error("no checker specified in the config for {}", ext) return [] path = item.path - if not isinstance(path, six.text_type): + if not isinstance(path, str): path = item.path.decode(sys.getfilesystemencoding()) try: status, errors, output = checker(path) except CheckerCommandException as e: if e.errno == errno.ENOENT: self._log.error( - u"command not found: {} when validating file: {}", + "command not found: {} when validating file: {}", e.checker, e.path ) else: - self._log.error(u"error invoking {}: {}", e.checker, e.msg) + self._log.error("error invoking {}: {}", e.checker, e.msg) return [] error_lines = [] if status > 0: error_lines.append( - u"{}: checker exited with status {}" + "{}: checker exited with status {}" .format(ui.colorize('text_error', dpath), status)) for line in output: - error_lines.append(u" {}".format(line)) + error_lines.append(f" {line}") elif errors > 0: error_lines.append( - u"{}: checker found {} errors or warnings" + "{}: checker found {} errors or warnings" .format(ui.colorize('text_warning', dpath), errors)) for line in output: - error_lines.append(u" {}".format(line)) + error_lines.append(f" {line}") elif self.verbose: error_lines.append( - u"{}: ok".format(ui.colorize('text_success', dpath))) + "{}: ok".format(ui.colorize('text_success', dpath))) return error_lines @@ -193,7 +191,7 @@ class BadFiles(BeetsPlugin): elif sel == 'b': raise importer.ImportAbort() else: - raise Exception('Unexpected selection: {}'.format(sel)) + raise Exception(f'Unexpected selection: {sel}') def command(self, lib, opts, args): # Get items from arguments @@ -208,11 +206,11 @@ class BadFiles(BeetsPlugin): def commands(self): bad_command = Subcommand('bad', - help=u'check for corrupt or missing files') + help='check for corrupt or missing files') bad_command.parser.add_option( - u'-v', u'--verbose', + '-v', '--verbose', action='store_true', default=False, dest='verbose', - help=u'view results for both the bad and uncorrupted files' + help='view results for both the bad and uncorrupted files' ) bad_command.func = self.command return [bad_command] diff --git a/beetsplug/bareasc.py b/beetsplug/bareasc.py index 4d574c756..10fd25d46 100644 --- a/beetsplug/bareasc.py +++ b/beetsplug/bareasc.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Philippe Mongeau. # Copyright 2021, Graham R. Cobb. @@ -19,7 +18,6 @@ """Provides a bare-ASCII matching query.""" -from __future__ import division, absolute_import, print_function from beets import ui from beets.ui import print_, decargs @@ -50,7 +48,7 @@ class BareascPlugin(BeetsPlugin): """Plugin to provide bare-ASCII option for beets matching.""" def __init__(self): """Default prefix for selecting bare-ASCII matching is #.""" - super(BareascPlugin, self).__init__() + super().__init__() self.config.add({ 'prefix': '#', }) @@ -64,8 +62,8 @@ class BareascPlugin(BeetsPlugin): """Add bareasc command as unidecode version of 'list'.""" cmd = ui.Subcommand('bareasc', help='unidecode version of beet list command') - cmd.parser.usage += u"\n" \ - u'Example: %prog -f \'$album: $title\' artist:beatles' + cmd.parser.usage += "\n" \ + 'Example: %prog -f \'$album: $title\' artist:beatles' cmd.parser.add_all_common_options() cmd.func = self.unidecode_list return [cmd] diff --git a/beetsplug/beatport.py b/beetsplug/beatport.py index df0abb2fc..e2701bdb3 100644 --- a/beetsplug/beatport.py +++ b/beetsplug/beatport.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -15,7 +14,6 @@ """Adds Beatport release and track search support to the autotagger """ -from __future__ import division, absolute_import, print_function import json import re @@ -34,29 +32,29 @@ import confuse AUTH_ERRORS = (TokenRequestDenied, TokenMissing, VerifierMissing) -USER_AGENT = u'beets/{0} +https://beets.io/'.format(beets.__version__) +USER_AGENT = f'beets/{beets.__version__} +https://beets.io/' class BeatportAPIError(Exception): pass -class BeatportObject(object): +class BeatportObject: def __init__(self, data): self.beatport_id = data['id'] - self.name = six.text_type(data['name']) + self.name = str(data['name']) if 'releaseDate' in data: self.release_date = datetime.strptime(data['releaseDate'], '%Y-%m-%d') if 'artists' in data: - self.artists = [(x['id'], six.text_type(x['name'])) + self.artists = [(x['id'], str(x['name'])) for x in data['artists']] if 'genres' in data: - self.genres = [six.text_type(x['name']) + self.genres = [str(x['name']) for x in data['genres']] -class BeatportClient(object): +class BeatportClient: _api_base = 'https://oauth-api.beatport.com' def __init__(self, c_key, c_secret, auth_key=None, auth_secret=None): @@ -131,7 +129,7 @@ class BeatportClient(object): """ response = self._get('catalog/3/search', query=query, perPage=5, - facets=['fieldType:{0}'.format(release_type)]) + facets=[f'fieldType:{release_type}']) for item in response: if release_type == 'release': if details: @@ -201,21 +199,20 @@ class BeatportClient(object): return response.json()['results'] -@six.python_2_unicode_compatible class BeatportRelease(BeatportObject): def __str__(self): if len(self.artists) < 4: artist_str = ", ".join(x[1] for x in self.artists) else: artist_str = "Various Artists" - return u"".format( + return "".format( artist_str, self.name, self.catalog_number, ) def __repr__(self): - return six.text_type(self).encode('utf-8') + return str(self).encode('utf-8') def __init__(self, data): BeatportObject.__init__(self, data) @@ -226,27 +223,26 @@ class BeatportRelease(BeatportObject): if 'category' in data: self.category = data['category'] if 'slug' in data: - self.url = "https://beatport.com/release/{0}/{1}".format( + self.url = "https://beatport.com/release/{}/{}".format( data['slug'], data['id']) self.genre = data.get('genre') -@six.python_2_unicode_compatible class BeatportTrack(BeatportObject): def __str__(self): artist_str = ", ".join(x[1] for x in self.artists) - return (u"" + return ("" .format(artist_str, self.name, self.mix_name)) def __repr__(self): - return six.text_type(self).encode('utf-8') + return str(self).encode('utf-8') def __init__(self, data): BeatportObject.__init__(self, data) if 'title' in data: - self.title = six.text_type(data['title']) + self.title = str(data['title']) if 'mixName' in data: - self.mix_name = six.text_type(data['mixName']) + self.mix_name = str(data['mixName']) self.length = timedelta(milliseconds=data.get('lengthMs', 0) or 0) if not self.length: try: @@ -255,26 +251,26 @@ class BeatportTrack(BeatportObject): except ValueError: pass if 'slug' in data: - self.url = "https://beatport.com/track/{0}/{1}" \ + self.url = "https://beatport.com/track/{}/{}" \ .format(data['slug'], data['id']) self.track_number = data.get('trackNumber') self.bpm = data.get('bpm') - self.initial_key = six.text_type( + self.initial_key = str( (data.get('key') or {}).get('shortName') ) # Use 'subgenre' and if not present, 'genre' as a fallback. if data.get('subGenres'): - self.genre = six.text_type(data['subGenres'][0].get('name')) + self.genre = str(data['subGenres'][0].get('name')) elif data.get('genres'): - self.genre = six.text_type(data['genres'][0].get('name')) + self.genre = str(data['genres'][0].get('name')) class BeatportPlugin(BeetsPlugin): data_source = 'Beatport' def __init__(self): - super(BeatportPlugin, self).__init__() + super().__init__() self.config.add({ 'apikey': '57713c3906af6f5def151b33601389176b37b429', 'apisecret': 'b3fe08c93c80aefd749fe871a16cd2bb32e2b954', @@ -294,7 +290,7 @@ class BeatportPlugin(BeetsPlugin): try: with open(self._tokenfile()) as f: tokendata = json.load(f) - except IOError: + except OSError: # No token yet. Generate one. token, secret = self.authenticate(c_key, c_secret) else: @@ -309,22 +305,22 @@ class BeatportPlugin(BeetsPlugin): try: url = auth_client.get_authorize_url() except AUTH_ERRORS as e: - self._log.debug(u'authentication error: {0}', e) - raise beets.ui.UserError(u'communication with Beatport failed') + self._log.debug('authentication error: {0}', e) + raise beets.ui.UserError('communication with Beatport failed') - beets.ui.print_(u"To authenticate with Beatport, visit:") + beets.ui.print_("To authenticate with Beatport, visit:") beets.ui.print_(url) # Ask for the verifier data and validate it. - data = beets.ui.input_(u"Enter the string displayed in your browser:") + data = beets.ui.input_("Enter the string displayed in your browser:") try: token, secret = auth_client.get_access_token(data) except AUTH_ERRORS as e: - self._log.debug(u'authentication error: {0}', e) - raise beets.ui.UserError(u'Beatport token request failed') + self._log.debug('authentication error: {0}', e) + raise beets.ui.UserError('Beatport token request failed') # Save the token for later use. - self._log.debug(u'Beatport token {0}, secret {1}', token, secret) + self._log.debug('Beatport token {0}, secret {1}', token, secret) with open(self._tokenfile(), 'w') as f: json.dump({'token': token, 'secret': secret}, f) @@ -362,32 +358,32 @@ class BeatportPlugin(BeetsPlugin): if va_likely: query = release else: - query = '%s %s' % (artist, release) + query = f'{artist} {release}' try: return self._get_releases(query) except BeatportAPIError as e: - self._log.debug(u'API Error: {0} (query: {1})', e, query) + self._log.debug('API Error: {0} (query: {1})', e, query) return [] def item_candidates(self, item, artist, title): """Returns a list of TrackInfo objects for beatport search results matching title and artist. """ - query = '%s %s' % (artist, title) + query = f'{artist} {title}' try: return self._get_tracks(query) except BeatportAPIError as e: - self._log.debug(u'API Error: {0} (query: {1})', e, query) + self._log.debug('API Error: {0} (query: {1})', e, query) return [] def album_for_id(self, release_id): """Fetches a release by its Beatport ID and returns an AlbumInfo object or None if the query is not a valid ID or release is not found. """ - self._log.debug(u'Searching for release {0}', release_id) + self._log.debug('Searching for release {0}', release_id) match = re.search(r'(^|beatport\.com/release/.+/)(\d+)$', release_id) if not match: - self._log.debug(u'Not a valid Beatport release ID.') + self._log.debug('Not a valid Beatport release ID.') return None release = self.client.get_release(match.group(2)) if release: @@ -398,10 +394,10 @@ class BeatportPlugin(BeetsPlugin): """Fetches a track by its Beatport ID and returns a TrackInfo object or None if the track is not a valid Beatport ID or track is not found. """ - self._log.debug(u'Searching for track {0}', track_id) + self._log.debug('Searching for track {0}', track_id) match = re.search(r'(^|beatport\.com/track/.+/)(\d+)$', track_id) if not match: - self._log.debug(u'Not a valid Beatport track ID.') + self._log.debug('Not a valid Beatport track ID.') return None bp_track = self.client.get_track(match.group(2)) if bp_track is not None: @@ -429,7 +425,7 @@ class BeatportPlugin(BeetsPlugin): va = len(release.artists) > 3 artist, artist_id = self._get_artist(release.artists) if va: - artist = u"Various Artists" + artist = "Various Artists" tracks = [self._get_track_info(x) for x in release.tracks] return AlbumInfo(album=release.name, album_id=release.beatport_id, @@ -439,7 +435,7 @@ class BeatportPlugin(BeetsPlugin): month=release.release_date.month, day=release.release_date.day, label=release.label_name, - catalognum=release.catalog_number, media=u'Digital', + catalognum=release.catalog_number, media='Digital', data_source=self.data_source, data_url=release.url, genre=release.genre) @@ -447,8 +443,8 @@ class BeatportPlugin(BeetsPlugin): """Returns a TrackInfo object for a Beatport Track object. """ title = track.name - if track.mix_name != u"Original Mix": - title += u" ({0})".format(track.mix_name) + if track.mix_name != "Original Mix": + title += f" ({track.mix_name})" artist, artist_id = self._get_artist(track.artists) length = track.length.total_seconds() return TrackInfo(title=title, track_id=track.beatport_id, diff --git a/beetsplug/bench.py b/beetsplug/bench.py index 41f575cd2..6dffbdda0 100644 --- a/beetsplug/bench.py +++ b/beetsplug/bench.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -16,7 +15,6 @@ """Some simple performance benchmarks for beets. """ -from __future__ import division, absolute_import, print_function from beets.plugins import BeetsPlugin from beets import ui diff --git a/beetsplug/bpd/__init__.py b/beetsplug/bpd/__init__.py index ef427024a..dc58c81a5 100644 --- a/beetsplug/bpd/__init__.py +++ b/beetsplug/bpd/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -18,7 +17,6 @@ Beets library. Attempts to implement a compatible protocol to allow use of the wide range of MPD clients. """ -from __future__ import division, absolute_import, print_function import re import sys @@ -43,15 +41,15 @@ import six PROTOCOL_VERSION = '0.16.0' BUFSIZE = 1024 -HELLO = u'OK MPD %s' % PROTOCOL_VERSION -CLIST_BEGIN = u'command_list_begin' -CLIST_VERBOSE_BEGIN = u'command_list_ok_begin' -CLIST_END = u'command_list_end' -RESP_OK = u'OK' -RESP_CLIST_VERBOSE = u'list_OK' -RESP_ERR = u'ACK' +HELLO = 'OK MPD %s' % PROTOCOL_VERSION +CLIST_BEGIN = 'command_list_begin' +CLIST_VERBOSE_BEGIN = 'command_list_ok_begin' +CLIST_END = 'command_list_end' +RESP_OK = 'OK' +RESP_CLIST_VERBOSE = 'list_OK' +RESP_ERR = 'ACK' -NEWLINE = u"\n" +NEWLINE = "\n" ERROR_NOT_LIST = 1 ERROR_ARG = 2 @@ -71,15 +69,15 @@ VOLUME_MAX = 100 SAFE_COMMANDS = ( # Commands that are available when unauthenticated. - u'close', u'commands', u'notcommands', u'password', u'ping', + 'close', 'commands', 'notcommands', 'password', 'ping', ) # List of subsystems/events used by the `idle` command. SUBSYSTEMS = [ - u'update', u'player', u'mixer', u'options', u'playlist', u'database', + 'update', 'player', 'mixer', 'options', 'playlist', 'database', # Related to unsupported commands: - u'stored_playlist', u'output', u'subscription', u'sticker', u'message', - u'partition', + 'stored_playlist', 'output', 'subscription', 'sticker', 'message', + 'partition', ] ITEM_KEYS_WRITABLE = set(MediaFile.fields()).intersection(Item._fields.keys()) @@ -102,7 +100,7 @@ class BPDError(Exception): self.cmd_name = cmd_name self.index = index - template = Template(u'$resp [$code@$index] {$cmd_name} $message') + template = Template('$resp [$code@$index] {$cmd_name} $message') def response(self): """Returns a string to be used as the response code for the @@ -131,9 +129,9 @@ def make_bpd_error(s_code, s_message): pass return NewBPDError -ArgumentTypeError = make_bpd_error(ERROR_ARG, u'invalid type for argument') -ArgumentIndexError = make_bpd_error(ERROR_ARG, u'argument out of range') -ArgumentNotFoundError = make_bpd_error(ERROR_NO_EXIST, u'argument not found') +ArgumentTypeError = make_bpd_error(ERROR_ARG, 'invalid type for argument') +ArgumentIndexError = make_bpd_error(ERROR_ARG, 'argument out of range') +ArgumentNotFoundError = make_bpd_error(ERROR_NO_EXIST, 'argument not found') def cast_arg(t, val): @@ -163,14 +161,14 @@ class BPDIdle(Exception): and should be notified when a relevant event happens. """ def __init__(self, subsystems): - super(BPDIdle, self).__init__() + super().__init__() self.subsystems = set(subsystems) # Generic server infrastructure, implementing the basic protocol. -class BaseServer(object): +class BaseServer: """A MPD-compatible music player server. The functions with the `cmd_` prefix are invoked in response to @@ -258,7 +256,7 @@ class BaseServer(object): if not self.ctrl_sock: self.ctrl_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.ctrl_sock.connect((self.ctrl_host, self.ctrl_port)) - self.ctrl_sock.sendall((message + u'\n').encode('utf-8')) + self.ctrl_sock.sendall((message + '\n').encode('utf-8')) def _send_event(self, event): """Notify subscribed connections of an event.""" @@ -330,7 +328,7 @@ class BaseServer(object): for system in subsystems: if system not in SUBSYSTEMS: raise BPDError(ERROR_ARG, - u'Unrecognised idle event: {}'.format(system)) + f'Unrecognised idle event: {system}') raise BPDIdle(subsystems) # put the connection into idle mode def cmd_kill(self, conn): @@ -347,20 +345,20 @@ class BaseServer(object): conn.authenticated = True else: conn.authenticated = False - raise BPDError(ERROR_PASSWORD, u'incorrect password') + raise BPDError(ERROR_PASSWORD, 'incorrect password') def cmd_commands(self, conn): """Lists the commands available to the user.""" if self.password and not conn.authenticated: # Not authenticated. Show limited list of commands. for cmd in SAFE_COMMANDS: - yield u'command: ' + cmd + yield 'command: ' + cmd else: # Authenticated. Show all commands. for func in dir(self): if func.startswith('cmd_'): - yield u'command: ' + func[4:] + yield 'command: ' + func[4:] def cmd_notcommands(self, conn): """Lists all unavailable commands.""" @@ -370,7 +368,7 @@ class BaseServer(object): if func.startswith('cmd_'): cmd = func[4:] if cmd not in SAFE_COMMANDS: - yield u'command: ' + cmd + yield 'command: ' + cmd else: # Authenticated. No commands are unavailable. @@ -384,43 +382,43 @@ class BaseServer(object): playlist, playlistlength, and xfade. """ yield ( - u'repeat: ' + six.text_type(int(self.repeat)), - u'random: ' + six.text_type(int(self.random)), - u'consume: ' + six.text_type(int(self.consume)), - u'single: ' + six.text_type(int(self.single)), - u'playlist: ' + six.text_type(self.playlist_version), - u'playlistlength: ' + six.text_type(len(self.playlist)), - u'mixrampdb: ' + six.text_type(self.mixrampdb), + 'repeat: ' + str(int(self.repeat)), + 'random: ' + str(int(self.random)), + 'consume: ' + str(int(self.consume)), + 'single: ' + str(int(self.single)), + 'playlist: ' + str(self.playlist_version), + 'playlistlength: ' + str(len(self.playlist)), + 'mixrampdb: ' + str(self.mixrampdb), ) if self.volume > 0: - yield u'volume: ' + six.text_type(self.volume) + yield 'volume: ' + str(self.volume) if not math.isnan(self.mixrampdelay): - yield u'mixrampdelay: ' + six.text_type(self.mixrampdelay) + yield 'mixrampdelay: ' + str(self.mixrampdelay) if self.crossfade > 0: - yield u'xfade: ' + six.text_type(self.crossfade) + yield 'xfade: ' + str(self.crossfade) if self.current_index == -1: - state = u'stop' + state = 'stop' elif self.paused: - state = u'pause' + state = 'pause' else: - state = u'play' - yield u'state: ' + state + state = 'play' + yield 'state: ' + state if self.current_index != -1: # i.e., paused or playing current_id = self._item_id(self.playlist[self.current_index]) - yield u'song: ' + six.text_type(self.current_index) - yield u'songid: ' + six.text_type(current_id) + yield 'song: ' + str(self.current_index) + yield 'songid: ' + str(current_id) if len(self.playlist) > self.current_index + 1: # If there's a next song, report its index too. next_id = self._item_id(self.playlist[self.current_index + 1]) - yield u'nextsong: ' + six.text_type(self.current_index + 1) - yield u'nextsongid: ' + six.text_type(next_id) + yield 'nextsong: ' + str(self.current_index + 1) + yield 'nextsongid: ' + str(next_id) if self.error: - yield u'error: ' + self.error + yield 'error: ' + self.error def cmd_clearerror(self, conn): """Removes the persistent error state of the server. This @@ -454,7 +452,7 @@ class BaseServer(object): """Set the player's volume level (0-100).""" vol = cast_arg(int, vol) if vol < VOLUME_MIN or vol > VOLUME_MAX: - raise BPDError(ERROR_ARG, u'volume out of range') + raise BPDError(ERROR_ARG, 'volume out of range') self.volume = vol self._send_event('mixer') @@ -467,8 +465,8 @@ class BaseServer(object): """Set the number of seconds of crossfading.""" crossfade = cast_arg(int, crossfade) if crossfade < 0: - raise BPDError(ERROR_ARG, u'crossfade time must be nonnegative') - self._log.warning(u'crossfade is not implemented in bpd') + raise BPDError(ERROR_ARG, 'crossfade time must be nonnegative') + self._log.warning('crossfade is not implemented in bpd') self.crossfade = crossfade self._send_event('options') @@ -476,7 +474,7 @@ class BaseServer(object): """Set the mixramp normalised max volume in dB.""" db = cast_arg(float, db) if db > 0: - raise BPDError(ERROR_ARG, u'mixrampdb time must be negative') + raise BPDError(ERROR_ARG, 'mixrampdb time must be negative') self._log.warning('mixramp is not implemented in bpd') self.mixrampdb = db self._send_event('options') @@ -485,7 +483,7 @@ class BaseServer(object): """Set the mixramp delay in seconds.""" delay = cast_arg(float, delay) if delay < 0: - raise BPDError(ERROR_ARG, u'mixrampdelay time must be nonnegative') + raise BPDError(ERROR_ARG, 'mixrampdelay time must be nonnegative') self._log.warning('mixramp is not implemented in bpd') self.mixrampdelay = delay self._send_event('options') @@ -493,14 +491,14 @@ class BaseServer(object): def cmd_replay_gain_mode(self, conn, mode): """Set the replay gain mode.""" if mode not in ['off', 'track', 'album', 'auto']: - raise BPDError(ERROR_ARG, u'Unrecognised replay gain mode') + raise BPDError(ERROR_ARG, 'Unrecognised replay gain mode') self._log.warning('replay gain is not implemented in bpd') self.replay_gain_mode = mode self._send_event('options') def cmd_replay_gain_status(self, conn): """Get the replaygain mode.""" - yield u'replay_gain_mode: ' + six.text_type(self.replay_gain_mode) + yield 'replay_gain_mode: ' + str(self.replay_gain_mode) def cmd_clear(self, conn): """Clear the playlist.""" @@ -621,8 +619,8 @@ class BaseServer(object): Also a dummy implementation. """ for idx, track in enumerate(self.playlist): - yield u'cpos: ' + six.text_type(idx) - yield u'Id: ' + six.text_type(track.id) + yield 'cpos: ' + str(idx) + yield 'Id: ' + str(track.id) def cmd_currentsong(self, conn): """Sends information about the currently-playing song. @@ -731,7 +729,7 @@ class BaseServer(object): 'a' + 2 -class Connection(object): +class Connection: """A connection between a client and the server. """ def __init__(self, server, sock): @@ -739,12 +737,12 @@ class Connection(object): """ self.server = server self.sock = sock - self.address = u'{}:{}'.format(*sock.sock.getpeername()) + self.address = '{}:{}'.format(*sock.sock.getpeername()) def debug(self, message, kind=' '): """Log a debug message about this connection. """ - self.server._log.debug(u'{}[{}]: {}', kind, self.address, message) + self.server._log.debug('{}[{}]: {}', kind, self.address, message) def run(self): pass @@ -755,12 +753,12 @@ class Connection(object): added after every string. Returns a Bluelet event that sends the data. """ - if isinstance(lines, six.string_types): + if isinstance(lines, str): lines = [lines] out = NEWLINE.join(lines) + NEWLINE for l in out.split(NEWLINE)[:-1]: self.debug(l, kind='>') - if isinstance(out, six.text_type): + if isinstance(out, str): out = out.encode('utf-8') return self.sock.sendall(out) @@ -779,7 +777,7 @@ class MPDConnection(Connection): def __init__(self, server, sock): """Create a new connection for the accepted socket `client`. """ - super(MPDConnection, self).__init__(server, sock) + super().__init__(server, sock) self.authenticated = False self.notifications = set() self.idle_subscriptions = set() @@ -813,7 +811,7 @@ class MPDConnection(Connection): pending = self.notifications.intersection(self.idle_subscriptions) try: for event in pending: - yield self.send(u'changed: {}'.format(event)) + yield self.send(f'changed: {event}') if pending or force_close_idle: self.idle_subscriptions = set() self.notifications = self.notifications.difference(pending) @@ -837,7 +835,7 @@ class MPDConnection(Connection): break line = line.strip() if not line: - err = BPDError(ERROR_UNKNOWN, u'No command given') + err = BPDError(ERROR_UNKNOWN, 'No command given') yield self.send(err.response()) self.disconnect() # Client sent a blank line. break @@ -847,15 +845,15 @@ class MPDConnection(Connection): if self.idle_subscriptions: # The connection is in idle mode. - if line == u'noidle': + if line == 'noidle': yield bluelet.call(self.send_notifications(True)) else: err = BPDError(ERROR_UNKNOWN, - u'Got command while idle: {}'.format(line)) + f'Got command while idle: {line}') yield self.send(err.response()) break continue - if line == u'noidle': + if line == 'noidle': # When not in idle, this command sends no response. continue @@ -894,10 +892,10 @@ class ControlConnection(Connection): def __init__(self, server, sock): """Create a new connection for the accepted socket `client`. """ - super(ControlConnection, self).__init__(server, sock) + super().__init__(server, sock) def debug(self, message, kind=' '): - self.server._log.debug(u'CTRL {}[{}]: {}', kind, self.address, message) + self.server._log.debug('CTRL {}[{}]: {}', kind, self.address, message) def run(self): """Listen for control commands and delegate to `ctrl_*` methods. @@ -943,10 +941,10 @@ class ControlConnection(Connection): c.address = newlabel break else: - yield self.send(u'ERROR: no such client: {}'.format(oldlabel)) + yield self.send(f'ERROR: no such client: {oldlabel}') -class Command(object): +class Command: """A command issued by the client for processing by the server. """ @@ -966,7 +964,7 @@ class Command(object): if match[0]: # Quoted argument. arg = match[0] - arg = arg.replace(u'\\"', u'"').replace(u'\\\\', u'\\') + arg = arg.replace('\\"', '"').replace('\\\\', '\\') else: # Unquoted argument. arg = match[1] @@ -981,7 +979,7 @@ class Command(object): # Attempt to get correct command function. func_name = prefix + self.name if not hasattr(target, func_name): - raise AttributeError(u'unknown command "{}"'.format(self.name)) + raise AttributeError(f'unknown command "{self.name}"') func = getattr(target, func_name) argspec = inspect.getfullargspec(func) @@ -997,7 +995,7 @@ class Command(object): wrong_num = (len(self.args) > max_args) or (len(self.args) < min_args) # If the command accepts a variable number of arguments skip the check. if wrong_num and not argspec.varargs: - raise TypeError(u'wrong number of arguments for "{}"' + raise TypeError('wrong number of arguments for "{}"' .format(self.name), self.name) return func @@ -1018,7 +1016,7 @@ class Command(object): if conn.server.password and \ not conn.authenticated and \ self.name not in SAFE_COMMANDS: - raise BPDError(ERROR_PERMISSION, u'insufficient privileges') + raise BPDError(ERROR_PERMISSION, 'insufficient privileges') try: args = [conn] + self.args @@ -1044,7 +1042,7 @@ class Command(object): except Exception: # An "unintentional" error. Hide it from the client. conn.server._log.error('{}', traceback.format_exc()) - raise BPDError(ERROR_SYSTEM, u'server error', self.name) + raise BPDError(ERROR_SYSTEM, 'server error', self.name) class CommandList(list): @@ -1096,46 +1094,46 @@ class Server(BaseServer): raise NoGstreamerError() else: raise - log.info(u'Starting server...') - super(Server, self).__init__(host, port, password, ctrl_port, log) + log.info('Starting server...') + super().__init__(host, port, password, ctrl_port, log) self.lib = library self.player = gstplayer.GstPlayer(self.play_finished) self.cmd_update(None) - log.info(u'Server ready and listening on {}:{}'.format( + log.info('Server ready and listening on {}:{}'.format( host, port)) - log.debug(u'Listening for control signals on {}:{}'.format( + log.debug('Listening for control signals on {}:{}'.format( host, ctrl_port)) def run(self): self.player.run() - super(Server, self).run() + super().run() def play_finished(self): """A callback invoked every time our player finishes a track. """ self.cmd_next(None) - self._ctrl_send(u'play_finished') + self._ctrl_send('play_finished') # Metadata helper functions. def _item_info(self, item): info_lines = [ - u'file: ' + item.destination(fragment=True), - u'Time: ' + six.text_type(int(item.length)), - u'duration: ' + u'{:.3f}'.format(item.length), - u'Id: ' + six.text_type(item.id), + 'file: ' + item.destination(fragment=True), + 'Time: ' + str(int(item.length)), + 'duration: ' + f'{item.length:.3f}', + 'Id: ' + str(item.id), ] try: pos = self._id_to_index(item.id) - info_lines.append(u'Pos: ' + six.text_type(pos)) + info_lines.append('Pos: ' + str(pos)) except ArgumentNotFoundError: # Don't include position if not in playlist. pass for tagtype, field in self.tagtype_map.items(): - info_lines.append(u'{}: {}'.format( - tagtype, six.text_type(getattr(item, field)))) + info_lines.append('{}: {}'.format( + tagtype, str(getattr(item, field)))) return info_lines @@ -1149,7 +1147,7 @@ class Server(BaseServer): except ValueError: if accept_single_number: return [cast_arg(int, items)] - raise BPDError(ERROR_ARG, u'bad range syntax') + raise BPDError(ERROR_ARG, 'bad range syntax') start = cast_arg(int, start) stop = cast_arg(int, stop) return range(start, stop) @@ -1159,14 +1157,14 @@ class Server(BaseServer): # Database updating. - def cmd_update(self, conn, path=u'/'): + def cmd_update(self, conn, path='/'): """Updates the catalog to reflect the current database state. """ # Path is ignored. Also, the real MPD does this asynchronously; # this is done inline. - self._log.debug(u'Building directory tree...') + self._log.debug('Building directory tree...') self.tree = vfs.libtree(self.lib) - self._log.debug(u'Finished building directory tree.') + self._log.debug('Finished building directory tree.') self.updated_time = time.time() self._send_event('update') self._send_event('database') @@ -1177,7 +1175,7 @@ class Server(BaseServer): """Returns a VFS node or an item ID located at the path given. If the path does not exist, raises a """ - components = path.split(u'/') + components = path.split('/') node = self.tree for component in components: @@ -1199,25 +1197,25 @@ class Server(BaseServer): def _path_join(self, p1, p2): """Smashes together two BPD paths.""" - out = p1 + u'/' + p2 - return out.replace(u'//', u'/').replace(u'//', u'/') + out = p1 + '/' + p2 + return out.replace('//', '/').replace('//', '/') - def cmd_lsinfo(self, conn, path=u"/"): + def cmd_lsinfo(self, conn, path="/"): """Sends info on all the items in the path.""" node = self._resolve_path(path) if isinstance(node, int): # Trying to list a track. - raise BPDError(ERROR_ARG, u'this is not a directory') + raise BPDError(ERROR_ARG, 'this is not a directory') else: for name, itemid in iter(sorted(node.files.items())): item = self.lib.get_item(itemid) yield self._item_info(item) for name, _ in iter(sorted(node.dirs.items())): dirpath = self._path_join(path, name) - if dirpath.startswith(u"/"): + if dirpath.startswith("/"): # Strip leading slash (libmpc rejects this). dirpath = dirpath[1:] - yield u'directory: %s' % dirpath + yield 'directory: %s' % dirpath def _listall(self, basepath, node, info=False): """Helper function for recursive listing. If info, show @@ -1229,25 +1227,23 @@ class Server(BaseServer): item = self.lib.get_item(node) yield self._item_info(item) else: - yield u'file: ' + basepath + yield 'file: ' + basepath else: # List a directory. Recurse into both directories and files. for name, itemid in sorted(node.files.items()): newpath = self._path_join(basepath, name) # "yield from" - for v in self._listall(newpath, itemid, info): - yield v + yield from self._listall(newpath, itemid, info) for name, subdir in sorted(node.dirs.items()): newpath = self._path_join(basepath, name) - yield u'directory: ' + newpath - for v in self._listall(newpath, subdir, info): - yield v + yield 'directory: ' + newpath + yield from self._listall(newpath, subdir, info) - def cmd_listall(self, conn, path=u"/"): + def cmd_listall(self, conn, path="/"): """Send the paths all items in the directory, recursively.""" return self._listall(path, self._resolve_path(path), False) - def cmd_listallinfo(self, conn, path=u"/"): + def cmd_listallinfo(self, conn, path="/"): """Send info on all the items in the directory, recursively.""" return self._listall(path, self._resolve_path(path), True) @@ -1264,11 +1260,9 @@ class Server(BaseServer): # Recurse into a directory. for name, itemid in sorted(node.files.items()): # "yield from" - for v in self._all_items(itemid): - yield v + yield from self._all_items(itemid) for name, subdir in sorted(node.dirs.items()): - for v in self._all_items(subdir): - yield v + yield from self._all_items(subdir) def _add(self, path, send_id=False): """Adds a track or directory to the playlist, specified by the @@ -1277,7 +1271,7 @@ class Server(BaseServer): for item in self._all_items(self._resolve_path(path)): self.playlist.append(item) if send_id: - yield u'Id: ' + six.text_type(item.id) + yield 'Id: ' + str(item.id) self.playlist_version += 1 self._send_event('playlist') @@ -1294,28 +1288,27 @@ class Server(BaseServer): # Server info. def cmd_status(self, conn): - for line in super(Server, self).cmd_status(conn): - yield line + yield from super().cmd_status(conn) if self.current_index > -1: item = self.playlist[self.current_index] yield ( - u'bitrate: ' + six.text_type(item.bitrate / 1000), - u'audio: {}:{}:{}'.format( - six.text_type(item.samplerate), - six.text_type(item.bitdepth), - six.text_type(item.channels), + 'bitrate: ' + str(item.bitrate / 1000), + 'audio: {}:{}:{}'.format( + str(item.samplerate), + str(item.bitdepth), + str(item.channels), ), ) (pos, total) = self.player.time() yield ( - u'time: {}:{}'.format( - six.text_type(int(pos)), - six.text_type(int(total)), + 'time: {}:{}'.format( + str(int(pos)), + str(int(total)), ), - u'elapsed: ' + u'{:.3f}'.format(pos), - u'duration: ' + u'{:.3f}'.format(total), + 'elapsed: ' + f'{pos:.3f}', + 'duration: ' + f'{total:.3f}', ) # Also missing 'updating_db'. @@ -1331,47 +1324,47 @@ class Server(BaseServer): artists, albums, songs, totaltime = tx.query(statement)[0] yield ( - u'artists: ' + six.text_type(artists), - u'albums: ' + six.text_type(albums), - u'songs: ' + six.text_type(songs), - u'uptime: ' + six.text_type(int(time.time() - self.startup_time)), - u'playtime: ' + u'0', # Missing. - u'db_playtime: ' + six.text_type(int(totaltime)), - u'db_update: ' + six.text_type(int(self.updated_time)), + 'artists: ' + str(artists), + 'albums: ' + str(albums), + 'songs: ' + str(songs), + 'uptime: ' + str(int(time.time() - self.startup_time)), + 'playtime: ' + '0', # Missing. + 'db_playtime: ' + str(int(totaltime)), + 'db_update: ' + str(int(self.updated_time)), ) def cmd_decoders(self, conn): """Send list of supported decoders and formats.""" decoders = self.player.get_decoders() for name, (mimes, exts) in decoders.items(): - yield u'plugin: {}'.format(name) + yield f'plugin: {name}' for ext in exts: - yield u'suffix: {}'.format(ext) + yield f'suffix: {ext}' for mime in mimes: - yield u'mime_type: {}'.format(mime) + yield f'mime_type: {mime}' # Searching. tagtype_map = { - u'Artist': u'artist', - u'ArtistSort': u'artist_sort', - u'Album': u'album', - u'Title': u'title', - u'Track': u'track', - u'AlbumArtist': u'albumartist', - u'AlbumArtistSort': u'albumartist_sort', - u'Label': u'label', - u'Genre': u'genre', - u'Date': u'year', - u'OriginalDate': u'original_year', - u'Composer': u'composer', - u'Disc': u'disc', - u'Comment': u'comments', - u'MUSICBRAINZ_TRACKID': u'mb_trackid', - u'MUSICBRAINZ_ALBUMID': u'mb_albumid', - u'MUSICBRAINZ_ARTISTID': u'mb_artistid', - u'MUSICBRAINZ_ALBUMARTISTID': u'mb_albumartistid', - u'MUSICBRAINZ_RELEASETRACKID': u'mb_releasetrackid', + 'Artist': 'artist', + 'ArtistSort': 'artist_sort', + 'Album': 'album', + 'Title': 'title', + 'Track': 'track', + 'AlbumArtist': 'albumartist', + 'AlbumArtistSort': 'albumartist_sort', + 'Label': 'label', + 'Genre': 'genre', + 'Date': 'year', + 'OriginalDate': 'original_year', + 'Composer': 'composer', + 'Disc': 'disc', + 'Comment': 'comments', + 'MUSICBRAINZ_TRACKID': 'mb_trackid', + 'MUSICBRAINZ_ALBUMID': 'mb_albumid', + 'MUSICBRAINZ_ARTISTID': 'mb_artistid', + 'MUSICBRAINZ_ALBUMARTISTID': 'mb_albumartistid', + 'MUSICBRAINZ_RELEASETRACKID': 'mb_releasetrackid', } def cmd_tagtypes(self, conn): @@ -1379,7 +1372,7 @@ class Server(BaseServer): searching. """ for tag in self.tagtype_map: - yield u'tagtype: ' + tag + yield 'tagtype: ' + tag def _tagtype_lookup(self, tag): """Uses `tagtype_map` to look up the beets column name for an @@ -1391,7 +1384,7 @@ class Server(BaseServer): # Match case-insensitively. if test_tag.lower() == tag.lower(): return test_tag, key - raise BPDError(ERROR_UNKNOWN, u'no such tagtype') + raise BPDError(ERROR_UNKNOWN, 'no such tagtype') def _metadata_query(self, query_type, any_query_type, kv): """Helper function returns a query object that will find items @@ -1404,13 +1397,13 @@ class Server(BaseServer): # Iterate pairwise over the arguments. it = iter(kv) for tag, value in zip(it, it): - if tag.lower() == u'any': + if tag.lower() == 'any': if any_query_type: queries.append(any_query_type(value, ITEM_KEYS_WRITABLE, query_type)) else: - raise BPDError(ERROR_UNKNOWN, u'no such tagtype') + raise BPDError(ERROR_UNKNOWN, 'no such tagtype') else: _, key = self._tagtype_lookup(tag) queries.append(query_type(key, value)) @@ -1447,9 +1440,9 @@ class Server(BaseServer): # rely on this behaviour (e.g. MPDroid, M.A.L.P.). kv = ('Artist', kv[0]) else: - raise BPDError(ERROR_ARG, u'should be "Album" for 3 arguments') + raise BPDError(ERROR_ARG, 'should be "Album" for 3 arguments') elif len(kv) % 2 != 0: - raise BPDError(ERROR_ARG, u'Incorrect number of filter arguments') + raise BPDError(ERROR_ARG, 'Incorrect number of filter arguments') query = self._metadata_query(dbcore.query.MatchQuery, None, kv) clause, subvals = query.clause() @@ -1464,7 +1457,7 @@ class Server(BaseServer): if not row[0]: # Skip any empty values of the field. continue - yield show_tag_canon + u': ' + six.text_type(row[0]) + yield show_tag_canon + ': ' + str(row[0]) def cmd_count(self, conn, tag, value): """Returns the number and total time of songs matching the @@ -1476,44 +1469,44 @@ class Server(BaseServer): for item in self.lib.items(dbcore.query.MatchQuery(key, value)): songs += 1 playtime += item.length - yield u'songs: ' + six.text_type(songs) - yield u'playtime: ' + six.text_type(int(playtime)) + yield 'songs: ' + str(songs) + yield 'playtime: ' + str(int(playtime)) # Persistent playlist manipulation. In MPD this is an optional feature so # these dummy implementations match MPD's behaviour with the feature off. def cmd_listplaylist(self, conn, playlist): - raise BPDError(ERROR_NO_EXIST, u'No such playlist') + raise BPDError(ERROR_NO_EXIST, 'No such playlist') def cmd_listplaylistinfo(self, conn, playlist): - raise BPDError(ERROR_NO_EXIST, u'No such playlist') + raise BPDError(ERROR_NO_EXIST, 'No such playlist') def cmd_listplaylists(self, conn): - raise BPDError(ERROR_UNKNOWN, u'Stored playlists are disabled') + raise BPDError(ERROR_UNKNOWN, 'Stored playlists are disabled') def cmd_load(self, conn, playlist): - raise BPDError(ERROR_NO_EXIST, u'Stored playlists are disabled') + raise BPDError(ERROR_NO_EXIST, 'Stored playlists are disabled') def cmd_playlistadd(self, conn, playlist, uri): - raise BPDError(ERROR_UNKNOWN, u'Stored playlists are disabled') + raise BPDError(ERROR_UNKNOWN, 'Stored playlists are disabled') def cmd_playlistclear(self, conn, playlist): - raise BPDError(ERROR_UNKNOWN, u'Stored playlists are disabled') + raise BPDError(ERROR_UNKNOWN, 'Stored playlists are disabled') def cmd_playlistdelete(self, conn, playlist, index): - raise BPDError(ERROR_UNKNOWN, u'Stored playlists are disabled') + raise BPDError(ERROR_UNKNOWN, 'Stored playlists are disabled') def cmd_playlistmove(self, conn, playlist, from_index, to_index): - raise BPDError(ERROR_UNKNOWN, u'Stored playlists are disabled') + raise BPDError(ERROR_UNKNOWN, 'Stored playlists are disabled') def cmd_rename(self, conn, playlist, new_name): - raise BPDError(ERROR_UNKNOWN, u'Stored playlists are disabled') + raise BPDError(ERROR_UNKNOWN, 'Stored playlists are disabled') def cmd_rm(self, conn, playlist): - raise BPDError(ERROR_UNKNOWN, u'Stored playlists are disabled') + raise BPDError(ERROR_UNKNOWN, 'Stored playlists are disabled') def cmd_save(self, conn, playlist): - raise BPDError(ERROR_UNKNOWN, u'Stored playlists are disabled') + raise BPDError(ERROR_UNKNOWN, 'Stored playlists are disabled') # "Outputs." Just a dummy implementation because we don't control # any outputs. @@ -1521,9 +1514,9 @@ class Server(BaseServer): def cmd_outputs(self, conn): """List the available outputs.""" yield ( - u'outputid: 0', - u'outputname: gstreamer', - u'outputenabled: 1', + 'outputid: 0', + 'outputname: gstreamer', + 'outputenabled: 1', ) def cmd_enableoutput(self, conn, output_id): @@ -1534,7 +1527,7 @@ class Server(BaseServer): def cmd_disableoutput(self, conn, output_id): output_id = cast_arg(int, output_id) if output_id == 0: - raise BPDError(ERROR_ARG, u'cannot disable this output') + raise BPDError(ERROR_ARG, 'cannot disable this output') else: raise ArgumentIndexError() @@ -1545,7 +1538,7 @@ class Server(BaseServer): def cmd_play(self, conn, index=-1): new_index = index != -1 and index != self.current_index was_paused = self.paused - super(Server, self).cmd_play(conn, index) + super().cmd_play(conn, index) if self.current_index > -1: # Not stopped. if was_paused and not new_index: @@ -1555,28 +1548,28 @@ class Server(BaseServer): self.player.play_file(self.playlist[self.current_index].path) def cmd_pause(self, conn, state=None): - super(Server, self).cmd_pause(conn, state) + super().cmd_pause(conn, state) if self.paused: self.player.pause() elif self.player.playing: self.player.play() def cmd_stop(self, conn): - super(Server, self).cmd_stop(conn) + super().cmd_stop(conn) self.player.stop() def cmd_seek(self, conn, index, pos): """Seeks to the specified position in the specified song.""" index = cast_arg(int, index) pos = cast_arg(float, pos) - super(Server, self).cmd_seek(conn, index, pos) + super().cmd_seek(conn, index, pos) self.player.seek(pos) # Volume control. def cmd_setvol(self, conn, vol): vol = cast_arg(int, vol) - super(Server, self).cmd_setvol(conn, vol) + super().cmd_setvol(conn, vol) self.player.volume = float(vol) / 100 @@ -1587,12 +1580,12 @@ class BPDPlugin(BeetsPlugin): server. """ def __init__(self): - super(BPDPlugin, self).__init__() + super().__init__() self.config.add({ - 'host': u'', + 'host': '', 'port': 6600, 'control_port': 6601, - 'password': u'', + 'password': '', 'volume': VOLUME_MAX, }) self.config['password'].redact = True @@ -1604,13 +1597,13 @@ class BPDPlugin(BeetsPlugin): server.cmd_setvol(None, volume) server.run() except NoGstreamerError: - self._log.error(u'Gstreamer Python bindings not found.') - self._log.error(u'Install "gstreamer1.0" and "python-gi"' - u'or similar package to use BPD.') + self._log.error('Gstreamer Python bindings not found.') + self._log.error('Install "gstreamer1.0" and "python-gi"' + 'or similar package to use BPD.') def commands(self): cmd = beets.ui.Subcommand( - 'bpd', help=u'run an MPD-compatible music player server' + 'bpd', help='run an MPD-compatible music player server' ) def func(lib, opts, args): @@ -1622,7 +1615,7 @@ class BPDPlugin(BeetsPlugin): else: ctrl_port = self.config['control_port'].get(int) if args: - raise beets.ui.UserError(u'too many arguments') + raise beets.ui.UserError('too many arguments') password = self.config['password'].as_str() volume = self.config['volume'].get(int) self.start_bpd(lib, host, int(port), password, volume, diff --git a/beetsplug/bpd/gstplayer.py b/beetsplug/bpd/gstplayer.py index 3ba293bf2..8cb6d56cf 100644 --- a/beetsplug/bpd/gstplayer.py +++ b/beetsplug/bpd/gstplayer.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -17,7 +16,6 @@ music player. """ -from __future__ import division, absolute_import, print_function import six import sys @@ -40,7 +38,7 @@ class QueryError(Exception): pass -class GstPlayer(object): +class GstPlayer: """A music player abstracting GStreamer's Playbin element. Create a player object, then call run() to start a thread with a @@ -110,7 +108,7 @@ class GstPlayer(object): # error self.player.set_state(Gst.State.NULL) err, debug = message.parse_error() - print(u"Error: {0}".format(err)) + print(f"Error: {err}") self.playing = False def _set_volume(self, volume): @@ -130,7 +128,7 @@ class GstPlayer(object): path. """ self.player.set_state(Gst.State.NULL) - if isinstance(path, six.text_type): + if isinstance(path, str): path = path.encode('utf-8') uri = 'file://' + urllib.parse.quote(path) self.player.set_property("uri", uri) diff --git a/beetsplug/bpm.py b/beetsplug/bpm.py index 20218bd33..5aa2d95aa 100644 --- a/beetsplug/bpm.py +++ b/beetsplug/bpm.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, aroquen # @@ -15,10 +14,8 @@ """Determine BPM by pressing a key to the rhythm.""" -from __future__ import division, absolute_import, print_function import time -from six.moves import input from beets import ui from beets.plugins import BeetsPlugin @@ -51,16 +48,16 @@ def bpm(max_strokes): class BPMPlugin(BeetsPlugin): def __init__(self): - super(BPMPlugin, self).__init__() + super().__init__() self.config.add({ - u'max_strokes': 3, - u'overwrite': True, + 'max_strokes': 3, + 'overwrite': True, }) def commands(self): cmd = ui.Subcommand('bpm', - help=u'determine bpm of a song by pressing ' - u'a key to the rhythm') + help='determine bpm of a song by pressing ' + 'a key to the rhythm') cmd.func = self.command return [cmd] @@ -72,19 +69,19 @@ class BPMPlugin(BeetsPlugin): def get_bpm(self, items, write=False): overwrite = self.config['overwrite'].get(bool) if len(items) > 1: - raise ValueError(u'Can only get bpm of one song at time') + raise ValueError('Can only get bpm of one song at time') item = items[0] if item['bpm']: - self._log.info(u'Found bpm {0}', item['bpm']) + self._log.info('Found bpm {0}', item['bpm']) if not overwrite: return - self._log.info(u'Press Enter {0} times to the rhythm or Ctrl-D ' - u'to exit', self.config['max_strokes'].get(int)) + self._log.info('Press Enter {0} times to the rhythm or Ctrl-D ' + 'to exit', self.config['max_strokes'].get(int)) new_bpm = bpm(self.config['max_strokes'].get(int)) item['bpm'] = int(new_bpm) if write: item.try_write() item.store() - self._log.info(u'Added new bpm {0}', item['bpm']) + self._log.info('Added new bpm {0}', item['bpm']) diff --git a/beetsplug/bpsync.py b/beetsplug/bpsync.py index aefb1517b..5b28d6d2b 100644 --- a/beetsplug/bpsync.py +++ b/beetsplug/bpsync.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2019, Rahul Ahuja. # @@ -15,7 +14,6 @@ """Update library's tags using Beatport. """ -from __future__ import division, absolute_import, print_function from beets.plugins import BeetsPlugin, apply_item_changes from beets import autotag, library, ui, util @@ -25,39 +23,39 @@ from .beatport import BeatportPlugin class BPSyncPlugin(BeetsPlugin): def __init__(self): - super(BPSyncPlugin, self).__init__() + super().__init__() self.beatport_plugin = BeatportPlugin() self.beatport_plugin.setup() def commands(self): - cmd = ui.Subcommand('bpsync', help=u'update metadata from Beatport') + cmd = ui.Subcommand('bpsync', help='update metadata from Beatport') cmd.parser.add_option( - u'-p', - u'--pretend', + '-p', + '--pretend', action='store_true', - help=u'show all changes but do nothing', + help='show all changes but do nothing', ) cmd.parser.add_option( - u'-m', - u'--move', + '-m', + '--move', action='store_true', dest='move', - help=u"move files in the library directory", + help="move files in the library directory", ) cmd.parser.add_option( - u'-M', - u'--nomove', + '-M', + '--nomove', action='store_false', dest='move', - help=u"don't move files in library", + help="don't move files in library", ) cmd.parser.add_option( - u'-W', - u'--nowrite', + '-W', + '--nowrite', action='store_false', default=None, dest='write', - help=u"don't write updated metadata to files", + help="don't write updated metadata to files", ) cmd.parser.add_format_option() cmd.func = self.func @@ -78,16 +76,16 @@ class BPSyncPlugin(BeetsPlugin): """Retrieve and apply info from the autotagger for items matched by query. """ - for item in lib.items(query + [u'singleton:true']): + for item in lib.items(query + ['singleton:true']): if not item.mb_trackid: self._log.info( - u'Skipping singleton with no mb_trackid: {}', item + 'Skipping singleton with no mb_trackid: {}', item ) continue if not self.is_beatport_track(item): self._log.info( - u'Skipping non-{} singleton: {}', + 'Skipping non-{} singleton: {}', self.beatport_plugin.data_source, item, ) @@ -108,11 +106,11 @@ class BPSyncPlugin(BeetsPlugin): def get_album_tracks(self, album): if not album.mb_albumid: - self._log.info(u'Skipping album with no mb_albumid: {}', album) + self._log.info('Skipping album with no mb_albumid: {}', album) return False if not album.mb_albumid.isnumeric(): self._log.info( - u'Skipping album with invalid {} ID: {}', + 'Skipping album with invalid {} ID: {}', self.beatport_plugin.data_source, album, ) @@ -122,7 +120,7 @@ class BPSyncPlugin(BeetsPlugin): return items if not all(self.is_beatport_track(item) for item in items): self._log.info( - u'Skipping non-{} release: {}', + 'Skipping non-{} release: {}', self.beatport_plugin.data_source, album, ) @@ -144,7 +142,7 @@ class BPSyncPlugin(BeetsPlugin): albuminfo = self.beatport_plugin.album_for_id(album.mb_albumid) if not albuminfo: self._log.info( - u'Release ID {} not found for album {}', + 'Release ID {} not found for album {}', album.mb_albumid, album, ) @@ -161,7 +159,7 @@ class BPSyncPlugin(BeetsPlugin): for track_id, item in library_trackid_to_item.items() } - self._log.info(u'applying changes to {}', album) + self._log.info('applying changes to {}', album) with lib.transaction(): autotag.apply_metadata(albuminfo, item_to_trackinfo) changed = False @@ -184,5 +182,5 @@ class BPSyncPlugin(BeetsPlugin): # Move album art (and any inconsistent items). if move and lib.directory in util.ancestry(items[0].path): - self._log.debug(u'moving album {}', album) + self._log.debug('moving album {}', album) album.move() diff --git a/beetsplug/bucket.py b/beetsplug/bucket.py index db993d612..0837ca8da 100644 --- a/beetsplug/bucket.py +++ b/beetsplug/bucket.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Fabrice Laporte. # @@ -16,12 +15,10 @@ """Provides the %bucket{} function for path formatting. """ -from __future__ import division, absolute_import, print_function from datetime import datetime import re import string -from six.moves import zip from itertools import tee from beets import plugins, ui @@ -49,7 +46,7 @@ def span_from_str(span_str): """Convert string to a 4 digits year """ if yearfrom < 100: - raise BucketError(u"%d must be expressed on 4 digits" % yearfrom) + raise BucketError("%d must be expressed on 4 digits" % yearfrom) # if two digits only, pick closest year that ends by these two # digits starting from yearfrom @@ -62,12 +59,12 @@ def span_from_str(span_str): years = [int(x) for x in re.findall(r'\d+', span_str)] if not years: - raise ui.UserError(u"invalid range defined for year bucket '%s': no " - u"year found" % span_str) + raise ui.UserError("invalid range defined for year bucket '%s': no " + "year found" % span_str) try: years = [normalize_year(x, years[0]) for x in years] except BucketError as exc: - raise ui.UserError(u"invalid range defined for year bucket '%s': %s" % + raise ui.UserError("invalid range defined for year bucket '%s': %s" % (span_str, exc)) res = {'from': years[0], 'str': span_str} @@ -128,7 +125,7 @@ def str2fmt(s): res = {'fromnchars': len(m.group('fromyear')), 'tonchars': len(m.group('toyear'))} - res['fmt'] = "%s%%s%s%s%s" % (m.group('bef'), + res['fmt'] = "{}%s{}{}{}".format(m.group('bef'), m.group('sep'), '%s' if res['tonchars'] else '', m.group('after')) @@ -170,8 +167,8 @@ def build_alpha_spans(alpha_spans_str, alpha_regexs): 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" % + raise ui.UserError("invalid range defined for alpha bucket " + "'%s': no alphanumeric character found" % elem) spans.append( re.compile( @@ -184,7 +181,7 @@ def build_alpha_spans(alpha_spans_str, alpha_regexs): class BucketPlugin(plugins.BeetsPlugin): def __init__(self): - super(BucketPlugin, self).__init__() + super().__init__() self.template_funcs['bucket'] = self._tmpl_bucket self.config.add({ diff --git a/beetsplug/chroma.py b/beetsplug/chroma.py index 91f2fe253..353923aab 100644 --- a/beetsplug/chroma.py +++ b/beetsplug/chroma.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -16,7 +15,6 @@ """Adds Chromaprint/Acoustid acoustic fingerprinting support to the autotagger. Requires the pyacoustid library. """ -from __future__ import division, absolute_import, print_function from beets import plugins from beets import ui @@ -90,7 +88,7 @@ def acoustid_match(log, path): try: duration, fp = acoustid.fingerprint_file(util.syspath(path)) except acoustid.FingerprintGenerationError as exc: - log.error(u'fingerprinting of {0} failed: {1}', + log.error('fingerprinting of {0} failed: {1}', util.displayable_path(repr(path)), exc) return None fp = fp.decode() @@ -99,25 +97,25 @@ def acoustid_match(log, path): res = acoustid.lookup(API_KEY, fp, duration, meta='recordings releases') except acoustid.AcoustidError as exc: - log.debug(u'fingerprint matching {0} failed: {1}', + log.debug('fingerprint matching {0} failed: {1}', util.displayable_path(repr(path)), exc) return None - log.debug(u'chroma: fingerprinted {0}', + log.debug('chroma: fingerprinted {0}', util.displayable_path(repr(path))) # Ensure the response is usable and parse it. if res['status'] != 'ok' or not res.get('results'): - log.debug(u'no match found') + log.debug('no match found') return None result = res['results'][0] # Best match. if result['score'] < SCORE_THRESH: - log.debug(u'no results above threshold') + log.debug('no results above threshold') return None _acoustids[path] = result['id'] # Get recording and releases from the result if not result.get('recordings'): - log.debug(u'no recordings found') + log.debug('no recordings found') return None recording_ids = [] releases = [] @@ -138,7 +136,7 @@ def acoustid_match(log, path): original_year=original_year)) release_ids = [rel['id'] for rel in releases] - log.debug(u'matched recordings {0} on releases {1}', + log.debug('matched recordings {0} on releases {1}', recording_ids, release_ids) _matches[path] = recording_ids, release_ids @@ -167,7 +165,7 @@ def _all_releases(items): class AcoustidPlugin(plugins.BeetsPlugin): def __init__(self): - super(AcoustidPlugin, self).__init__() + super().__init__() self.config.add({ 'auto': True, @@ -198,7 +196,7 @@ class AcoustidPlugin(plugins.BeetsPlugin): if album: albums.append(album) - self._log.debug(u'acoustid album candidates: {0}', len(albums)) + self._log.debug('acoustid album candidates: {0}', len(albums)) return albums def item_candidates(self, item, artist, title): @@ -211,24 +209,24 @@ class AcoustidPlugin(plugins.BeetsPlugin): track = hooks.track_for_mbid(recording_id) if track: tracks.append(track) - self._log.debug(u'acoustid item candidates: {0}', len(tracks)) + self._log.debug('acoustid item candidates: {0}', len(tracks)) return tracks def commands(self): submit_cmd = ui.Subcommand('submit', - help=u'submit Acoustid fingerprints') + help='submit Acoustid fingerprints') def submit_cmd_func(lib, opts, args): try: apikey = config['acoustid']['apikey'].as_str() except confuse.NotFoundError: - raise ui.UserError(u'no Acoustid user API key provided') + raise ui.UserError('no Acoustid user API key provided') submit_items(self._log, apikey, lib.items(ui.decargs(args))) submit_cmd.func = submit_cmd_func fingerprint_cmd = ui.Subcommand( 'fingerprint', - help=u'generate fingerprints for items without them' + help='generate fingerprints for items without them' ) def fingerprint_cmd_func(lib, opts, args): @@ -271,11 +269,11 @@ def submit_items(log, userkey, items, chunksize=64): def submit_chunk(): """Submit the current accumulated fingerprint data.""" - log.info(u'submitting {0} fingerprints', len(data)) + log.info('submitting {0} fingerprints', len(data)) try: acoustid.submit(API_KEY, userkey, data) except acoustid.AcoustidError as exc: - log.warning(u'acoustid submission error: {0}', exc) + log.warning('acoustid submission error: {0}', exc) del data[:] for item in items: @@ -288,7 +286,7 @@ def submit_items(log, userkey, items, chunksize=64): } if item.mb_trackid: item_data['mbid'] = item.mb_trackid - log.debug(u'submitting MBID') + log.debug('submitting MBID') else: item_data.update({ 'track': item.title, @@ -299,7 +297,7 @@ def submit_items(log, userkey, items, chunksize=64): 'trackno': item.track, 'discno': item.disc, }) - log.debug(u'submitting textual metadata') + log.debug('submitting textual metadata') data.append(item_data) # If we have enough data, submit a chunk. @@ -320,28 +318,28 @@ def fingerprint_item(log, item, write=False): """ # Get a fingerprint and length for this track. if not item.length: - log.info(u'{0}: no duration available', + log.info('{0}: no duration available', util.displayable_path(item.path)) elif item.acoustid_fingerprint: if write: - log.info(u'{0}: fingerprint exists, skipping', + log.info('{0}: fingerprint exists, skipping', util.displayable_path(item.path)) else: - log.info(u'{0}: using existing fingerprint', + log.info('{0}: using existing fingerprint', util.displayable_path(item.path)) return item.acoustid_fingerprint else: - log.info(u'{0}: fingerprinting', + log.info('{0}: fingerprinting', util.displayable_path(item.path)) try: _, fp = acoustid.fingerprint_file(util.syspath(item.path)) item.acoustid_fingerprint = fp.decode() if write: - log.info(u'{0}: writing fingerprint', + log.info('{0}: writing fingerprint', util.displayable_path(item.path)) item.try_write() if item._db: item.store() return item.acoustid_fingerprint except acoustid.FingerprintGenerationError as exc: - log.info(u'fingerprint generation failed: {0}', exc) + log.info('fingerprint generation failed: {0}', exc) diff --git a/beetsplug/convert.py b/beetsplug/convert.py index a55b2f7aa..5f07a2e7c 100644 --- a/beetsplug/convert.py +++ b/beetsplug/convert.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Jakob Schnitzer. # @@ -15,7 +14,6 @@ """Converts tracks or albums to external directory """ -from __future__ import division, absolute_import, print_function from beets.util import par_map, decode_commandline_path, arg_encoding import os @@ -38,8 +36,8 @@ _temp_files = [] # Keep track of temporary transcoded files for deletion. # Some convenient alternate names for formats. ALIASES = { - u'wma': u'windows media', - u'vorbis': u'ogg', + 'wma': 'windows media', + 'vorbis': 'ogg', } LOSSLESS_FORMATS = ['ape', 'flac', 'alac', 'wav', 'aiff'] @@ -67,7 +65,7 @@ def get_format(fmt=None): extension = format_info.get('extension', fmt) except KeyError: raise ui.UserError( - u'convert: format {0} needs the "command" field' + 'convert: format {} needs the "command" field' .format(fmt) ) except ConfigTypeError: @@ -80,7 +78,7 @@ def get_format(fmt=None): command = config['convert']['command'].as_str() elif 'opts' in keys: # Undocumented option for backwards compatibility with < 1.3.1. - command = u'ffmpeg -i $source -y {0} $dest'.format( + command = 'ffmpeg -i $source -y {} $dest'.format( config['convert']['opts'].as_str() ) if 'extension' in keys: @@ -109,72 +107,72 @@ def should_transcode(item, fmt): class ConvertPlugin(BeetsPlugin): def __init__(self): - super(ConvertPlugin, self).__init__() + super().__init__() self.config.add({ - u'dest': None, - u'pretend': False, - u'link': False, - u'hardlink': False, - u'threads': util.cpu_count(), - u'format': u'mp3', - u'id3v23': u'inherit', - u'formats': { - u'aac': { - u'command': u'ffmpeg -i $source -y -vn -acodec aac ' - u'-aq 1 $dest', - u'extension': u'm4a', + 'dest': None, + 'pretend': False, + 'link': False, + 'hardlink': False, + 'threads': util.cpu_count(), + 'format': 'mp3', + 'id3v23': 'inherit', + 'formats': { + 'aac': { + 'command': 'ffmpeg -i $source -y -vn -acodec aac ' + '-aq 1 $dest', + 'extension': 'm4a', }, - u'alac': { - u'command': u'ffmpeg -i $source -y -vn -acodec alac $dest', - u'extension': u'm4a', + 'alac': { + 'command': 'ffmpeg -i $source -y -vn -acodec alac $dest', + 'extension': 'm4a', }, - u'flac': u'ffmpeg -i $source -y -vn -acodec flac $dest', - u'mp3': u'ffmpeg -i $source -y -vn -aq 2 $dest', - u'opus': - u'ffmpeg -i $source -y -vn -acodec libopus -ab 96k $dest', - u'ogg': - u'ffmpeg -i $source -y -vn -acodec libvorbis -aq 3 $dest', - u'wma': - u'ffmpeg -i $source -y -vn -acodec wmav2 -vn $dest', + 'flac': 'ffmpeg -i $source -y -vn -acodec flac $dest', + 'mp3': 'ffmpeg -i $source -y -vn -aq 2 $dest', + 'opus': + 'ffmpeg -i $source -y -vn -acodec libopus -ab 96k $dest', + 'ogg': + 'ffmpeg -i $source -y -vn -acodec libvorbis -aq 3 $dest', + 'wma': + 'ffmpeg -i $source -y -vn -acodec wmav2 -vn $dest', }, - u'max_bitrate': 500, - u'auto': False, - u'tmpdir': None, - u'quiet': False, - u'embed': True, - u'paths': {}, - u'no_convert': u'', - u'never_convert_lossy_files': False, - u'copy_album_art': False, - u'album_art_maxwidth': 0, - u'delete_originals': False, + 'max_bitrate': 500, + 'auto': False, + 'tmpdir': None, + 'quiet': False, + 'embed': True, + 'paths': {}, + 'no_convert': '', + 'never_convert_lossy_files': False, + 'copy_album_art': False, + 'album_art_maxwidth': 0, + 'delete_originals': False, }) self.early_import_stages = [self.auto_convert] self.register_listener('import_task_files', self._cleanup) def commands(self): - cmd = ui.Subcommand('convert', help=u'convert to external location') + cmd = ui.Subcommand('convert', help='convert to external location') cmd.parser.add_option('-p', '--pretend', action='store_true', - help=u'show actions but do nothing') + help='show actions but do nothing') cmd.parser.add_option('-t', '--threads', action='store', type='int', - help=u'change the number of threads, \ + help='change the number of threads, \ defaults to maximum available processors') cmd.parser.add_option('-k', '--keep-new', action='store_true', - dest='keep_new', help=u'keep only the converted \ + dest='keep_new', help='keep only the converted \ and move the old files') cmd.parser.add_option('-d', '--dest', action='store', - help=u'set the destination directory') + help='set the destination directory') cmd.parser.add_option('-f', '--format', action='store', dest='format', - help=u'set the target format of the tracks') + help='set the target format of the tracks') cmd.parser.add_option('-y', '--yes', action='store_true', dest='yes', - help=u'do not ask for confirmation') + help='do not ask for confirmation') cmd.parser.add_option('-l', '--link', action='store_true', dest='link', - help=u'symlink files that do not \ + help='symlink files that do not \ need transcoding.') cmd.parser.add_option('-H', '--hardlink', action='store_true', dest='hardlink', - help=u'hardlink files that do not \ + help='hardlink files that do not \ need transcoding. Overrides --link.') cmd.parser.add_album_option() cmd.func = self.convert_func @@ -201,7 +199,7 @@ class ConvertPlugin(BeetsPlugin): quiet = self.config['quiet'].get(bool) if not quiet and not pretend: - self._log.info(u'Encoding {0}', util.displayable_path(source)) + self._log.info('Encoding {0}', util.displayable_path(source)) command = command.decode(arg_encoding(), 'surrogateescape') source = decode_commandline_path(source) @@ -218,16 +216,16 @@ class ConvertPlugin(BeetsPlugin): encode_cmd.append(args[i].encode(util.arg_encoding())) if pretend: - self._log.info(u'{0}', u' '.join(ui.decargs(args))) + self._log.info('{0}', ' '.join(ui.decargs(args))) return try: util.command_output(encode_cmd) except subprocess.CalledProcessError as exc: # Something went wrong (probably Ctrl+C), remove temporary files - self._log.info(u'Encoding {0} failed. Cleaning up...', + self._log.info('Encoding {0} failed. Cleaning up...', util.displayable_path(source)) - self._log.debug(u'Command {0} exited with status {1}: {2}', + self._log.debug('Command {0} exited with status {1}: {2}', args, exc.returncode, exc.output) @@ -236,13 +234,13 @@ class ConvertPlugin(BeetsPlugin): raise except OSError as exc: raise ui.UserError( - u"convert: couldn't invoke '{0}': {1}".format( - u' '.join(ui.decargs(args)), exc + "convert: couldn't invoke '{}': {}".format( + ' '.join(ui.decargs(args)), exc ) ) if not quiet and not pretend: - self._log.info(u'Finished encoding {0}', + self._log.info('Finished encoding {0}', util.displayable_path(source)) def convert_item(self, dest_dir, keep_new, path_formats, fmt, @@ -279,17 +277,17 @@ class ConvertPlugin(BeetsPlugin): util.mkdirall(dest) if os.path.exists(util.syspath(dest)): - self._log.info(u'Skipping {0} (target file exists)', + self._log.info('Skipping {0} (target file exists)', util.displayable_path(item.path)) continue if keep_new: if pretend: - self._log.info(u'mv {0} {1}', + self._log.info('mv {0} {1}', util.displayable_path(item.path), util.displayable_path(original)) else: - self._log.info(u'Moving to {0}', + self._log.info('Moving to {0}', util.displayable_path(original)) util.move(item.path, original) @@ -304,7 +302,7 @@ class ConvertPlugin(BeetsPlugin): if pretend: msg = 'ln' if hardlink else ('ln -s' if link else 'cp') - self._log.info(u'{2} {0} {1}', + self._log.info('{2} {0} {1}', util.displayable_path(original), util.displayable_path(converted), msg) @@ -313,7 +311,7 @@ class ConvertPlugin(BeetsPlugin): msg = 'Hardlinking' if hardlink \ else ('Linking' if link else 'Copying') - self._log.info(u'{1} {0}', + self._log.info('{1} {0}', util.displayable_path(item.path), msg) @@ -344,7 +342,7 @@ class ConvertPlugin(BeetsPlugin): if self.config['embed'] and not linked: album = item._cached_album if album and album.artpath: - self._log.debug(u'embedding album art from {}', + self._log.debug('embedding album art from {}', util.displayable_path(album.artpath)) art.embed_item(self._log, item, album.artpath, itempath=converted, id3v23=id3v23) @@ -385,7 +383,7 @@ class ConvertPlugin(BeetsPlugin): util.mkdirall(dest) if os.path.exists(util.syspath(dest)): - self._log.info(u'Skipping {0} (target file exists)', + self._log.info('Skipping {0} (target file exists)', util.displayable_path(album.artpath)) return @@ -399,12 +397,12 @@ class ConvertPlugin(BeetsPlugin): if size: resize = size[0] > maxwidth else: - self._log.warning(u'Could not get size of image (please see ' - u'documentation for dependencies).') + self._log.warning('Could not get size of image (please see ' + 'documentation for dependencies).') # Either copy or resize (while copying) the image. if resize: - self._log.info(u'Resizing cover art from {0} to {1}', + self._log.info('Resizing cover art from {0} to {1}', util.displayable_path(album.artpath), util.displayable_path(dest)) if not pretend: @@ -413,7 +411,7 @@ class ConvertPlugin(BeetsPlugin): if pretend: msg = 'ln' if hardlink else ('ln -s' if link else 'cp') - self._log.info(u'{2} {0} {1}', + self._log.info('{2} {0} {1}', util.displayable_path(album.artpath), util.displayable_path(dest), msg) @@ -421,7 +419,7 @@ class ConvertPlugin(BeetsPlugin): msg = 'Hardlinking' if hardlink \ else ('Linking' if link else 'Copying') - self._log.info(u'{2} cover art from {0} to {1}', + self._log.info('{2} cover art from {0} to {1}', util.displayable_path(album.artpath), util.displayable_path(dest), msg) @@ -435,7 +433,7 @@ class ConvertPlugin(BeetsPlugin): def convert_func(self, lib, opts, args): dest = opts.dest or self.config['dest'].get() if not dest: - raise ui.UserError(u'no convert destination set') + raise ui.UserError('no convert destination set') dest = util.bytestring_path(dest) threads = opts.threads or self.config['threads'].get(int) @@ -464,17 +462,17 @@ class ConvertPlugin(BeetsPlugin): items = [i for a in albums for i in a.items()] if not pretend: for a in albums: - ui.print_(format(a, u'')) + ui.print_(format(a, '')) else: items = list(lib.items(ui.decargs(args))) if not pretend: for i in items: - ui.print_(format(i, u'')) + ui.print_(format(i, '')) if not items: - self._log.error(u'Empty query result.') + self._log.error('Empty query result.') return - if not (pretend or opts.yes or ui.input_yn(u"Convert? (Y/n)")): + if not (pretend or opts.yes or ui.input_yn("Convert? (Y/n)")): return if opts.album and self.config['copy_album_art']: @@ -525,7 +523,7 @@ class ConvertPlugin(BeetsPlugin): item.store() if self.config['delete_originals']: - self._log.info(u'Removing original file {0}', source_path) + self._log.info('Removing original file {0}', source_path) util.remove(source_path, False) def _cleanup(self, task, session): diff --git a/beetsplug/deezer.py b/beetsplug/deezer.py index 4e3fca33a..201ab6992 100644 --- a/beetsplug/deezer.py +++ b/beetsplug/deezer.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2019, Rahul Ahuja. # @@ -15,7 +14,6 @@ """Adds Deezer release and track search support to the autotagger """ -from __future__ import absolute_import, print_function, division import collections @@ -43,7 +41,7 @@ class DeezerPlugin(MetadataSourcePlugin, BeetsPlugin): } def __init__(self): - super(DeezerPlugin, self).__init__() + super().__init__() def album_for_id(self, album_id): """Fetch an album by its Deezer ID or URL and return an @@ -76,8 +74,8 @@ class DeezerPlugin(MetadataSourcePlugin, BeetsPlugin): day = None else: raise ui.UserError( - u"Invalid `release_date` returned " - u"by {} API: '{}'".format(self.data_source, release_date) + "Invalid `release_date` returned " + "by {} API: '{}'".format(self.data_source, release_date) ) tracks_data = requests.get( @@ -188,10 +186,10 @@ class DeezerPlugin(MetadataSourcePlugin, BeetsPlugin): """ query_components = [ keywords, - ' '.join('{}:"{}"'.format(k, v) for k, v in filters.items()), + ' '.join(f'{k}:"{v}"' for k, v in filters.items()), ] query = ' '.join([q for q in query_components if q]) - if not isinstance(query, six.text_type): + if not isinstance(query, str): query = query.decode('utf8') return unidecode.unidecode(query) @@ -217,7 +215,7 @@ class DeezerPlugin(MetadataSourcePlugin, BeetsPlugin): if not query: return None self._log.debug( - u"Searching {} for '{}'".format(self.data_source, query) + f"Searching {self.data_source} for '{query}'" ) response = requests.get( self.search_url + query_type, params={'q': query} @@ -225,7 +223,7 @@ class DeezerPlugin(MetadataSourcePlugin, BeetsPlugin): response.raise_for_status() response_data = response.json().get('data', []) self._log.debug( - u"Found {} result(s) from {} for '{}'", + "Found {} result(s) from {} for '{}'", len(response_data), self.data_source, query, diff --git a/beetsplug/discogs.py b/beetsplug/discogs.py index d52973b6f..e7c416909 100644 --- a/beetsplug/discogs.py +++ b/beetsplug/discogs.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -16,7 +15,6 @@ """Adds Discogs album search support to the autotagger. Requires the python3-discogs-client library. """ -from __future__ import division, absolute_import, print_function import beets.ui from beets import config @@ -37,7 +35,7 @@ import traceback from string import ascii_lowercase -USER_AGENT = u'beets/{0} +https://beets.io/'.format(beets.__version__) +USER_AGENT = f'beets/{beets.__version__} +https://beets.io/' API_KEY = 'rAzVUQYRaoFjeBjyWuWZ' API_SECRET = 'plxtUTqoCzwxZpqdPysCwGuBSmZNdZVy' @@ -50,14 +48,14 @@ CONNECTION_ERRORS = (ConnectionError, socket.error, http_client.HTTPException, class DiscogsPlugin(BeetsPlugin): def __init__(self): - super(DiscogsPlugin, self).__init__() + super().__init__() self.config.add({ 'apikey': API_KEY, 'apisecret': API_SECRET, 'tokenfile': 'discogs_token.json', 'source_weight': 0.5, 'user_token': '', - 'separator': u', ', + 'separator': ', ', 'index_tracks': False, }) self.config['apikey'].redact = True @@ -87,7 +85,7 @@ class DiscogsPlugin(BeetsPlugin): try: with open(self._tokenfile()) as f: tokendata = json.load(f) - except IOError: + except OSError: # No token yet. Generate one. token, secret = self.authenticate(c_key, c_secret) else: @@ -134,24 +132,24 @@ class DiscogsPlugin(BeetsPlugin): try: _, _, url = auth_client.get_authorize_url() except CONNECTION_ERRORS as e: - self._log.debug(u'connection error: {0}', e) - raise beets.ui.UserError(u'communication with Discogs failed') + self._log.debug('connection error: {0}', e) + raise beets.ui.UserError('communication with Discogs failed') - beets.ui.print_(u"To authenticate with Discogs, visit:") + beets.ui.print_("To authenticate with Discogs, visit:") beets.ui.print_(url) # Ask for the code and validate it. - code = beets.ui.input_(u"Enter the code:") + code = beets.ui.input_("Enter the code:") try: token, secret = auth_client.get_access_token(code) except DiscogsAPIError: - raise beets.ui.UserError(u'Discogs authorization failed') + raise beets.ui.UserError('Discogs authorization failed') except CONNECTION_ERRORS as e: - self._log.debug(u'connection error: {0}', e) - raise beets.ui.UserError(u'Discogs token request failed') + self._log.debug('connection error: {0}', e) + raise beets.ui.UserError('Discogs token request failed') # Save the token for later use. - self._log.debug(u'Discogs token {0}, secret {1}', token, secret) + self._log.debug('Discogs token {0}, secret {1}', token, secret) with open(self._tokenfile(), 'w') as f: json.dump({'token': token, 'secret': secret}, f) @@ -185,18 +183,18 @@ class DiscogsPlugin(BeetsPlugin): if va_likely: query = album else: - query = '%s %s' % (artist, album) + query = f'{artist} {album}' try: return self.get_albums(query) except DiscogsAPIError as e: - self._log.debug(u'API Error: {0} (query: {1})', e, query) + self._log.debug('API Error: {0} (query: {1})', e, query) if e.status_code == 401: self.reset_auth() return self.candidates(items, artist, album, va_likely) else: return [] except CONNECTION_ERRORS: - self._log.debug(u'Connection error in album search', exc_info=True) + self._log.debug('Connection error in album search', exc_info=True) return [] def album_for_id(self, album_id): @@ -206,7 +204,7 @@ class DiscogsPlugin(BeetsPlugin): if not self.discogs_client: return - self._log.debug(u'Searching for release {0}', album_id) + self._log.debug('Searching for release {0}', album_id) # Discogs-IDs are simple integers. We only look for those at the end # of an input string as to avoid confusion with other metadata plugins. # An optional bracket can follow the integer, as this is how discogs @@ -221,14 +219,14 @@ class DiscogsPlugin(BeetsPlugin): getattr(result, 'title') except DiscogsAPIError as e: if e.status_code != 404: - self._log.debug(u'API Error: {0} (query: {1})', e, + self._log.debug('API Error: {0} (query: {1})', e, result.data['resource_url']) if e.status_code == 401: self.reset_auth() return self.album_for_id(album_id) return None except CONNECTION_ERRORS: - self._log.debug(u'Connection error in album lookup', exc_info=True) + self._log.debug('Connection error in album lookup', exc_info=True) return None return self.get_album_info(result) @@ -251,7 +249,7 @@ class DiscogsPlugin(BeetsPlugin): self.request_finished() except CONNECTION_ERRORS: - self._log.debug(u"Communication error while searching for {0!r}", + self._log.debug("Communication error while searching for {0!r}", query, exc_info=True) return [] return [album for album in map(self.get_album_info, releases[:5]) @@ -261,7 +259,7 @@ class DiscogsPlugin(BeetsPlugin): """Fetches a master release given its Discogs ID and returns its year or None if the master release is not found. """ - self._log.debug(u'Searching for master release {0}', master_id) + self._log.debug('Searching for master release {0}', master_id) result = Master(self.discogs_client, {'id': master_id}) self.request_start() @@ -271,14 +269,14 @@ class DiscogsPlugin(BeetsPlugin): return year except DiscogsAPIError as e: if e.status_code != 404: - self._log.debug(u'API Error: {0} (query: {1})', e, + self._log.debug('API Error: {0} (query: {1})', e, result.data['resource_url']) if e.status_code == 401: self.reset_auth() return self.get_master_year(master_id) return None except CONNECTION_ERRORS: - self._log.debug(u'Connection error in master release lookup', + self._log.debug('Connection error in master release lookup', exc_info=True) return None @@ -297,7 +295,7 @@ class DiscogsPlugin(BeetsPlugin): # https://www.discogs.com/help/doc/submission-guidelines-general-rules if not all([result.data.get(k) for k in ['artists', 'title', 'id', 'tracklist']]): - self._log.warning(u"Release does not contain the required fields") + self._log.warning("Release does not contain the required fields") return None artist, artist_id = MetadataSourcePlugin.get_artist( @@ -386,8 +384,8 @@ class DiscogsPlugin(BeetsPlugin): # FIXME: this is an extra precaution for making sure there are no # side effects after #2222. It should be removed after further # testing. - self._log.debug(u'{}', traceback.format_exc()) - self._log.error(u'uncaught exception in coalesce_tracks: {}', exc) + self._log.debug('{}', traceback.format_exc()) + self._log.error('uncaught exception in coalesce_tracks: {}', exc) clean_tracklist = tracklist tracks = [] index_tracks = {} @@ -425,7 +423,7 @@ class DiscogsPlugin(BeetsPlugin): # If a medium has two sides (ie. vinyl or cassette), each pair of # consecutive sides should belong to the same medium. if all([track.medium is not None for track in tracks]): - m = sorted(set([track.medium.lower() for track in tracks])) + m = sorted({track.medium.lower() for track in tracks}) # If all track.medium are single consecutive letters, assume it is # a 2-sided medium. if ''.join(m) in ascii_lowercase: @@ -484,7 +482,7 @@ class DiscogsPlugin(BeetsPlugin): # Calculate position based on first subtrack, without subindex. idx, medium_idx, sub_idx = \ self.get_track_index(subtracks[0]['position']) - position = '%s%s' % (idx or '', medium_idx or '') + position = '{}{}'.format(idx or '', medium_idx or '') if tracklist and not tracklist[-1]['position']: # Assume the previous index track contains the track title. @@ -561,7 +559,7 @@ class DiscogsPlugin(BeetsPlugin): if self.config['index_tracks']: prefix = ', '.join(divisions) if prefix: - title = '{}: {}'.format(prefix, title) + title = f'{prefix}: {title}' track_id = None medium, medium_index, _ = self.get_track_index(track['position']) artist, artist_id = MetadataSourcePlugin.get_artist( @@ -597,7 +595,7 @@ class DiscogsPlugin(BeetsPlugin): if subindex and subindex.startswith('.'): subindex = subindex[1:] else: - self._log.debug(u'Invalid position: {0}', position) + self._log.debug('Invalid position: {0}', position) medium = index = subindex = None return medium or None, index or None, subindex or None diff --git a/beetsplug/duplicates.py b/beetsplug/duplicates.py index d608ddd4b..db1e1ee9e 100644 --- a/beetsplug/duplicates.py +++ b/beetsplug/duplicates.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Pedro Silva. # @@ -15,7 +14,6 @@ """List duplicate tracks or albums. """ -from __future__ import division, absolute_import, print_function import shlex import six @@ -34,7 +32,7 @@ class DuplicatesPlugin(BeetsPlugin): """List duplicate tracks or albums """ def __init__(self): - super(DuplicatesPlugin, self).__init__() + super().__init__() self.config.add({ 'album': False, @@ -57,54 +55,54 @@ class DuplicatesPlugin(BeetsPlugin): help=__doc__, aliases=['dup']) self._command.parser.add_option( - u'-c', u'--count', dest='count', + '-c', '--count', dest='count', action='store_true', - help=u'show duplicate counts', + help='show duplicate counts', ) self._command.parser.add_option( - u'-C', u'--checksum', dest='checksum', + '-C', '--checksum', dest='checksum', action='store', metavar='PROG', - help=u'report duplicates based on arbitrary command', + help='report duplicates based on arbitrary command', ) self._command.parser.add_option( - u'-d', u'--delete', dest='delete', + '-d', '--delete', dest='delete', action='store_true', - help=u'delete items from library and disk', + help='delete items from library and disk', ) self._command.parser.add_option( - u'-F', u'--full', dest='full', + '-F', '--full', dest='full', action='store_true', - help=u'show all versions of duplicate tracks or albums', + help='show all versions of duplicate tracks or albums', ) self._command.parser.add_option( - u'-s', u'--strict', dest='strict', + '-s', '--strict', dest='strict', action='store_true', - help=u'report duplicates only if all attributes are set', + help='report duplicates only if all attributes are set', ) self._command.parser.add_option( - u'-k', u'--key', dest='keys', + '-k', '--key', dest='keys', action='append', metavar='KEY', - help=u'report duplicates based on keys (use multiple times)', + help='report duplicates based on keys (use multiple times)', ) self._command.parser.add_option( - u'-M', u'--merge', dest='merge', + '-M', '--merge', dest='merge', action='store_true', - help=u'merge duplicate items', + help='merge duplicate items', ) self._command.parser.add_option( - u'-m', u'--move', dest='move', + '-m', '--move', dest='move', action='store', metavar='DEST', - help=u'move items to dest', + help='move items to dest', ) self._command.parser.add_option( - u'-o', u'--copy', dest='copy', + '-o', '--copy', dest='copy', action='store', metavar='DEST', - help=u'copy items to dest', + help='copy items to dest', ) self._command.parser.add_option( - u'-t', u'--tag', dest='tag', + '-t', '--tag', dest='tag', action='store', - help=u'tag matched items with \'k=v\' attribute', + help='tag matched items with \'k=v\' attribute', ) self._command.parser.add_all_common_options() @@ -142,15 +140,15 @@ class DuplicatesPlugin(BeetsPlugin): return if path: - fmt = u'$path' + fmt = '$path' # Default format string for count mode. if count and not fmt: if album: - fmt = u'$albumartist - $album' + fmt = '$albumartist - $album' else: - fmt = u'$albumartist - $album - $title' - fmt += u': {0}' + fmt = '$albumartist - $album - $title' + fmt += ': {0}' if checksum: for i in items: @@ -176,7 +174,7 @@ class DuplicatesPlugin(BeetsPlugin): return [self._command] def _process_item(self, item, copy=False, move=False, delete=False, - tag=False, fmt=u''): + tag=False, fmt=''): """Process Item `item`. """ print_(format(item, fmt)) @@ -193,7 +191,7 @@ class DuplicatesPlugin(BeetsPlugin): k, v = tag.split('=') except Exception: raise UserError( - u"{}: can't parse k=v tag: {}".format(PLUGIN, tag) + f"{PLUGIN}: can't parse k=v tag: {tag}" ) setattr(item, k, v) item.store() @@ -208,21 +206,21 @@ class DuplicatesPlugin(BeetsPlugin): key = args[0] checksum = getattr(item, key, False) if not checksum: - self._log.debug(u'key {0} on item {1} not cached:' - u'computing checksum', + self._log.debug('key {0} on item {1} not cached:' + 'computing checksum', key, displayable_path(item.path)) try: checksum = command_output(args).stdout setattr(item, key, checksum) item.store() - self._log.debug(u'computed checksum for {0} using {1}', + self._log.debug('computed checksum for {0} using {1}', item.title, key) except subprocess.CalledProcessError as e: - self._log.debug(u'failed to checksum {0}: {1}', + self._log.debug('failed to checksum {0}: {1}', displayable_path(item.path), e) else: - self._log.debug(u'key {0} on item {1} cached:' - u'not computing checksum', + self._log.debug('key {0} on item {1} cached:' + 'not computing checksum', key, displayable_path(item.path)) return key, checksum @@ -238,12 +236,12 @@ class DuplicatesPlugin(BeetsPlugin): values = [getattr(obj, k, None) for k in keys] values = [v for v in values if v not in (None, '')] if strict and len(values) < len(keys): - self._log.debug(u'some keys {0} on item {1} are null or empty:' - u' skipping', + self._log.debug('some keys {0} on item {1} are null or empty:' + ' skipping', keys, displayable_path(obj.path)) elif (not strict and not len(values)): - self._log.debug(u'all keys {0} on item {1} are null or empty:' - u' skipping', + self._log.debug('all keys {0} on item {1} are null or empty:' + ' skipping', keys, displayable_path(obj.path)) else: key = tuple(values) @@ -271,7 +269,7 @@ class DuplicatesPlugin(BeetsPlugin): # between a bytes object and the empty Unicode # string ''. return v is not None and \ - (v != '' if isinstance(v, six.text_type) else True) + (v != '' if isinstance(v, str) else True) fields = Item.all_keys() key = lambda x: sum(1 for f in fields if truthy(getattr(x, f))) else: @@ -291,8 +289,8 @@ class DuplicatesPlugin(BeetsPlugin): if getattr(objs[0], f, None) in (None, ''): value = getattr(o, f, None) if value: - self._log.debug(u'key {0} on item {1} is null ' - u'or empty: setting from item {2}', + self._log.debug('key {0} on item {1} is null ' + 'or empty: setting from item {2}', f, displayable_path(objs[0].path), displayable_path(o.path)) setattr(objs[0], f, value) @@ -312,8 +310,8 @@ class DuplicatesPlugin(BeetsPlugin): missing = Item.from_path(i.path) missing.album_id = objs[0].id missing.add(i._db) - self._log.debug(u'item {0} missing from album {1}:' - u' merging from {2} into {3}', + self._log.debug('item {0} missing from album {1}:' + ' merging from {2} into {3}', missing, objs[0], displayable_path(o.path), diff --git a/beetsplug/edit.py b/beetsplug/edit.py index 1d923cc36..8372563b8 100644 --- a/beetsplug/edit.py +++ b/beetsplug/edit.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016 # @@ -15,7 +14,6 @@ """Open metadata information in a text editor to let the user edit it. """ -from __future__ import division, absolute_import, print_function from beets import plugins from beets import util @@ -48,11 +46,11 @@ def edit(filename, log): """ cmd = shlex.split(util.editor_command()) cmd.append(filename) - log.debug(u'invoking editor command: {!r}', cmd) + log.debug('invoking editor command: {!r}', cmd) try: subprocess.call(cmd) except OSError as exc: - raise ui.UserError(u'could not run editor command {!r}: {}'.format( + raise ui.UserError('could not run editor command {!r}: {}'.format( cmd[0], exc )) @@ -78,17 +76,17 @@ def load(s): for d in yaml.safe_load_all(s): if not isinstance(d, dict): raise ParseError( - u'each entry must be a dictionary; found {}'.format( + 'each entry must be a dictionary; found {}'.format( type(d).__name__ ) ) # Convert all keys to strings. They started out as strings, # but the user may have inadvertently messed this up. - out.append({six.text_type(k): v for k, v in d.items()}) + out.append({str(k): v for k, v in d.items()}) except yaml.YAMLError as e: - raise ParseError(u'invalid YAML: {}'.format(e)) + raise ParseError(f'invalid YAML: {e}') return out @@ -144,13 +142,13 @@ def apply_(obj, data): else: # Either the field was stringified originally or the user changed # it from a safe type to an unsafe one. Parse it as a string. - obj.set_parse(key, six.text_type(value)) + obj.set_parse(key, str(value)) class EditPlugin(plugins.BeetsPlugin): def __init__(self): - super(EditPlugin, self).__init__() + super().__init__() self.config.add({ # The default fields to edit. @@ -167,18 +165,18 @@ class EditPlugin(plugins.BeetsPlugin): def commands(self): edit_command = ui.Subcommand( 'edit', - help=u'interactively edit metadata' + help='interactively edit metadata' ) edit_command.parser.add_option( - u'-f', u'--field', + '-f', '--field', metavar='FIELD', action='append', - help=u'edit this field also', + help='edit this field also', ) edit_command.parser.add_option( - u'--all', + '--all', action='store_true', dest='all', - help=u'edit all fields', + help='edit all fields', ) edit_command.parser.add_album_option() edit_command.func = self._edit_command @@ -192,7 +190,7 @@ class EditPlugin(plugins.BeetsPlugin): items, albums = _do_query(lib, query, opts.album, False) objs = albums if opts.album else items if not objs: - ui.print_(u'Nothing to edit.') + ui.print_('Nothing to edit.') return # Get the fields to edit. @@ -262,15 +260,15 @@ class EditPlugin(plugins.BeetsPlugin): with codecs.open(new.name, encoding='utf-8') as f: new_str = f.read() if new_str == old_str: - ui.print_(u"No changes; aborting.") + ui.print_("No changes; aborting.") return False # Parse the updated data. try: new_data = load(new_str) except ParseError as e: - ui.print_(u"Could not read data: {}".format(e)) - if ui.input_yn(u"Edit again to fix? (Y/n)", True): + ui.print_(f"Could not read data: {e}") + if ui.input_yn("Edit again to fix? (Y/n)", True): continue else: return False @@ -285,18 +283,18 @@ class EditPlugin(plugins.BeetsPlugin): for obj, obj_old in zip(objs, objs_old): changed |= ui.show_model_changes(obj, obj_old) if not changed: - ui.print_(u'No changes to apply.') + ui.print_('No changes to apply.') return False # Confirm the changes. choice = ui.input_options( - (u'continue Editing', u'apply', u'cancel') + ('continue Editing', 'apply', 'cancel') ) - if choice == u'a': # Apply. + if choice == 'a': # Apply. return True - elif choice == u'c': # Cancel. + elif choice == 'c': # Cancel. return False - elif choice == u'e': # Keep editing. + elif choice == 'e': # Keep editing. # Reset the temporary changes to the objects. I we have a # copy from above, use that, else reload from the database. objs = [(old_obj or obj) @@ -318,7 +316,7 @@ class EditPlugin(plugins.BeetsPlugin): are temporary. """ if len(old_data) != len(new_data): - self._log.warning(u'number of objects changed from {} to {}', + self._log.warning('number of objects changed from {} to {}', len(old_data), len(new_data)) obj_by_id = {o.id: o for o in objs} @@ -329,7 +327,7 @@ class EditPlugin(plugins.BeetsPlugin): forbidden = False for key in ignore_fields: if old_dict.get(key) != new_dict.get(key): - self._log.warning(u'ignoring object whose {} changed', key) + self._log.warning('ignoring object whose {} changed', key) forbidden = True break if forbidden: @@ -344,7 +342,7 @@ class EditPlugin(plugins.BeetsPlugin): # Save to the database and possibly write tags. for ob in objs: if ob._dirty: - self._log.debug(u'saving changes to {}', ob) + self._log.debug('saving changes to {}', ob) ob.try_sync(ui.should_write(), ui.should_move()) # Methods for interactive importer execution. diff --git a/beetsplug/embedart.py b/beetsplug/embedart.py index 61a4d798f..6db46f8c1 100644 --- a/beetsplug/embedart.py +++ b/beetsplug/embedart.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -14,7 +13,6 @@ # included in all copies or substantial portions of the Software. """Allows beets to embed album art into file metadata.""" -from __future__ import division, absolute_import, print_function import os.path @@ -34,11 +32,11 @@ def _confirm(objs, album): `album` is a Boolean indicating whether these are albums (as opposed to items). """ - noun = u'album' if album else u'file' - prompt = u'Modify artwork for {} {}{} (Y/n)?'.format( + noun = 'album' if album else 'file' + prompt = 'Modify artwork for {} {}{} (Y/n)?'.format( len(objs), noun, - u's' if len(objs) > 1 else u'' + 's' if len(objs) > 1 else '' ) # Show all the items or albums. @@ -53,7 +51,7 @@ class EmbedCoverArtPlugin(BeetsPlugin): """Allows albumart to be embedded into the actual files. """ def __init__(self): - super(EmbedCoverArtPlugin, self).__init__() + super().__init__() self.config.add({ 'maxwidth': 0, 'auto': True, @@ -65,26 +63,26 @@ class EmbedCoverArtPlugin(BeetsPlugin): if self.config['maxwidth'].get(int) and not ArtResizer.shared.local: self.config['maxwidth'] = 0 - self._log.warning(u"ImageMagick or PIL not found; " - u"'maxwidth' option ignored") + self._log.warning("ImageMagick or PIL not found; " + "'maxwidth' option ignored") if self.config['compare_threshold'].get(int) and not \ ArtResizer.shared.can_compare: self.config['compare_threshold'] = 0 - self._log.warning(u"ImageMagick 6.8.7 or higher not installed; " - u"'compare_threshold' option ignored") + self._log.warning("ImageMagick 6.8.7 or higher not installed; " + "'compare_threshold' option ignored") self.register_listener('art_set', self.process_album) def commands(self): # Embed command. embed_cmd = ui.Subcommand( - 'embedart', help=u'embed image files into file metadata' + 'embedart', help='embed image files into file metadata' ) embed_cmd.parser.add_option( - u'-f', u'--file', metavar='PATH', help=u'the image file to embed' + '-f', '--file', metavar='PATH', help='the image file to embed' ) embed_cmd.parser.add_option( - u"-y", u"--yes", action="store_true", help=u"skip confirmation" + "-y", "--yes", action="store_true", help="skip confirmation" ) maxwidth = self.config['maxwidth'].get(int) quality = self.config['quality'].get(int) @@ -95,7 +93,7 @@ class EmbedCoverArtPlugin(BeetsPlugin): if opts.file: imagepath = normpath(opts.file) if not os.path.isfile(syspath(imagepath)): - raise ui.UserError(u'image file {0} not found'.format( + raise ui.UserError('image file {} not found'.format( displayable_path(imagepath) )) @@ -127,15 +125,15 @@ class EmbedCoverArtPlugin(BeetsPlugin): # Extract command. extract_cmd = ui.Subcommand( 'extractart', - help=u'extract an image from file metadata', + help='extract an image from file metadata', ) extract_cmd.parser.add_option( - u'-o', dest='outpath', - help=u'image output file', + '-o', dest='outpath', + help='image output file', ) extract_cmd.parser.add_option( - u'-n', dest='filename', - help=u'image filename to create for all matched albums', + '-n', dest='filename', + help='image filename to create for all matched albums', ) extract_cmd.parser.add_option( '-a', dest='associate', action='store_true', @@ -151,7 +149,7 @@ class EmbedCoverArtPlugin(BeetsPlugin): config['art_filename'].get()) if os.path.dirname(filename) != b'': self._log.error( - u"Only specify a name rather than a path for -n") + "Only specify a name rather than a path for -n") return for album in lib.albums(decargs(args)): artpath = normpath(os.path.join(album.path, filename)) @@ -165,10 +163,10 @@ class EmbedCoverArtPlugin(BeetsPlugin): # Clear command. clear_cmd = ui.Subcommand( 'clearart', - help=u'remove images from file metadata', + help='remove images from file metadata', ) clear_cmd.parser.add_option( - u"-y", u"--yes", action="store_true", help=u"skip confirmation" + "-y", "--yes", action="store_true", help="skip confirmation" ) def clear_func(lib, opts, args): @@ -197,7 +195,7 @@ class EmbedCoverArtPlugin(BeetsPlugin): """ if self.config['remove_art_file'] and album.artpath: if os.path.isfile(album.artpath): - self._log.debug(u'Removing album art file for {0}', album) + self._log.debug('Removing album art file for {0}', album) os.remove(album.artpath) album.artpath = None album.store() diff --git a/beetsplug/embyupdate.py b/beetsplug/embyupdate.py index 5c731954b..0f77f17ce 100644 --- a/beetsplug/embyupdate.py +++ b/beetsplug/embyupdate.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - """Updates the Emby Library whenever the beets library is changed. emby: @@ -9,7 +7,6 @@ apikey: apikey password: password """ -from __future__ import division, absolute_import, print_function import hashlib import requests @@ -146,14 +143,14 @@ def get_user(host, port, username): class EmbyUpdate(BeetsPlugin): def __init__(self): - super(EmbyUpdate, self).__init__() + super().__init__() # Adding defaults. config['emby'].add({ - u'host': u'http://localhost', - u'port': 8096, - u'apikey': None, - u'password': None, + 'host': 'http://localhost', + 'port': 8096, + 'apikey': None, + 'password': None, }) self.register_listener('database_change', self.listen_for_db_change) @@ -166,7 +163,7 @@ class EmbyUpdate(BeetsPlugin): def update(self, lib): """When the client exists try to send refresh request to Emby. """ - self._log.info(u'Updating Emby library...') + self._log.info('Updating Emby library...') host = config['emby']['host'].get() port = config['emby']['port'].get() @@ -176,13 +173,13 @@ class EmbyUpdate(BeetsPlugin): # Check if at least a apikey or password is given. if not any([password, token]): - self._log.warning(u'Provide at least Emby password or apikey.') + self._log.warning('Provide at least Emby password or apikey.') return # Get user information from the Emby API. user = get_user(host, port, username) if not user: - self._log.warning(u'User {0} could not be found.'.format(username)) + self._log.warning(f'User {username} could not be found.') return if not token: @@ -194,7 +191,7 @@ class EmbyUpdate(BeetsPlugin): token = get_token(host, port, headers, auth_data) if not token: self._log.warning( - u'Could not get token for user {0}', username + 'Could not get token for user {0}', username ) return @@ -205,6 +202,6 @@ class EmbyUpdate(BeetsPlugin): url = api_url(host, port, '/Library/Refresh') r = requests.post(url, headers=headers) if r.status_code != 204: - self._log.warning(u'Update could not be triggered') + self._log.warning('Update could not be triggered') else: - self._log.info(u'Update triggered.') + self._log.info('Update triggered.') diff --git a/beetsplug/export.py b/beetsplug/export.py index 9aabaea05..3cb8f8c4f 100644 --- a/beetsplug/export.py +++ b/beetsplug/export.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # # Permission is hereby granted, free of charge, to any person obtaining @@ -15,7 +14,6 @@ """Exports data from beets """ -from __future__ import division, absolute_import, print_function import sys import codecs @@ -42,7 +40,7 @@ class ExportEncoder(json.JSONEncoder): class ExportPlugin(BeetsPlugin): def __init__(self): - super(ExportPlugin, self).__init__() + super().__init__() self.config.add({ 'default_format': 'json', @@ -83,28 +81,28 @@ class ExportPlugin(BeetsPlugin): def commands(self): # TODO: Add option to use albums - cmd = ui.Subcommand('export', help=u'export data from beets') + cmd = ui.Subcommand('export', help='export data from beets') cmd.func = self.run cmd.parser.add_option( - u'-l', u'--library', action='store_true', - help=u'show library fields instead of tags', + '-l', '--library', action='store_true', + help='show library fields instead of tags', ) cmd.parser.add_option( - u'--append', action='store_true', default=False, - help=u'if should append data to the file', + '--append', action='store_true', default=False, + help='if should append data to the file', ) cmd.parser.add_option( - u'-i', u'--include-keys', default=[], + '-i', '--include-keys', default=[], action='append', dest='included_keys', - help=u'comma separated list of keys to show', + help='comma separated list of keys to show', ) cmd.parser.add_option( - u'-o', u'--output', - help=u'path for the output file. If not given, will print the data' + '-o', '--output', + help='path for the output file. If not given, will print the data' ) cmd.parser.add_option( - u'-f', u'--format', default='json', - help=u"the output format: json (default), jsonlines, csv, or xml" + '-f', '--format', default='json', + help="the output format: json (default), jsonlines, csv, or xml" ) return [cmd] @@ -133,8 +131,8 @@ class ExportPlugin(BeetsPlugin): for data_emitter in data_collector(lib, ui.decargs(args)): try: data, item = data_emitter(included_keys or '*') - except (mediafile.UnreadableFileError, IOError) as ex: - self._log.error(u'cannot read file: {0}', ex) + except (mediafile.UnreadableFileError, OSError) as ex: + self._log.error('cannot read file: {0}', ex) continue for key, value in data.items(): @@ -152,9 +150,9 @@ class ExportPlugin(BeetsPlugin): export_format.export(items, **format_options) -class ExportFormat(object): +class ExportFormat: """The output format type""" - def __init__(self, file_path, file_mode=u'w', encoding=u'utf-8'): + def __init__(self, file_path, file_mode='w', encoding='utf-8'): self.path = file_path self.mode = file_mode self.encoding = encoding @@ -179,8 +177,8 @@ class ExportFormat(object): class JsonFormat(ExportFormat): """Saves in a json file""" - def __init__(self, file_path, file_mode=u'w', encoding=u'utf-8'): - super(JsonFormat, self).__init__(file_path, file_mode, encoding) + def __init__(self, file_path, file_mode='w', encoding='utf-8'): + super().__init__(file_path, file_mode, encoding) def export(self, data, **kwargs): json.dump(data, self.out_stream, cls=ExportEncoder, **kwargs) @@ -189,8 +187,8 @@ class JsonFormat(ExportFormat): class CSVFormat(ExportFormat): """Saves in a csv file""" - def __init__(self, file_path, file_mode=u'w', encoding=u'utf-8'): - super(CSVFormat, self).__init__(file_path, file_mode, encoding) + def __init__(self, file_path, file_mode='w', encoding='utf-8'): + super().__init__(file_path, file_mode, encoding) def export(self, data, **kwargs): header = list(data[0].keys()) if data else [] @@ -201,16 +199,16 @@ class CSVFormat(ExportFormat): class XMLFormat(ExportFormat): """Saves in a xml file""" - def __init__(self, file_path, file_mode=u'w', encoding=u'utf-8'): - super(XMLFormat, self).__init__(file_path, file_mode, encoding) + def __init__(self, file_path, file_mode='w', encoding='utf-8'): + super().__init__(file_path, file_mode, encoding) def export(self, data, **kwargs): # Creates the XML file structure. - library = ElementTree.Element(u'library') - tracks = ElementTree.SubElement(library, u'tracks') + library = ElementTree.Element('library') + tracks = ElementTree.SubElement(library, 'tracks') if data and isinstance(data[0], dict): for index, item in enumerate(data): - track = ElementTree.SubElement(tracks, u'track') + track = ElementTree.SubElement(tracks, 'track') for key, value in item.items(): track_details = ElementTree.SubElement(track, key) track_details.text = value diff --git a/beetsplug/fetchart.py b/beetsplug/fetchart.py index 0a3254f53..dfc7eb683 100644 --- a/beetsplug/fetchart.py +++ b/beetsplug/fetchart.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -15,7 +14,6 @@ """Fetches album art. """ -from __future__ import division, absolute_import, print_function from contextlib import closing import os @@ -44,7 +42,7 @@ CONTENT_TYPES = { IMAGE_EXTENSIONS = [ext for exts in CONTENT_TYPES.values() for ext in exts] -class Candidate(object): +class Candidate: """Holds information about a matching artwork, deals with validation of dimension restrictions and resizing. """ @@ -56,7 +54,7 @@ class Candidate(object): MATCH_EXACT = 0 MATCH_FALLBACK = 1 - def __init__(self, log, path=None, url=None, source=u'', + def __init__(self, log, path=None, url=None, source='', match=None, size=None): self._log = log self.path = path @@ -86,14 +84,14 @@ class Candidate(object): # get_size returns None if no local imaging backend is available if not self.size: self.size = ArtResizer.shared.get_size(self.path) - self._log.debug(u'image size: {}', self.size) + self._log.debug('image size: {}', self.size) if not self.size: - self._log.warning(u'Could not get size of image (please see ' - u'documentation for dependencies). ' - u'The configuration options `minwidth`, ' - u'`enforce_ratio` and `max_filesize` ' - u'may be violated.') + self._log.warning('Could not get size of image (please see ' + 'documentation for dependencies). ' + 'The configuration options `minwidth`, ' + '`enforce_ratio` and `max_filesize` ' + 'may be violated.') return self.CANDIDATE_EXACT short_edge = min(self.size) @@ -101,7 +99,7 @@ class Candidate(object): # Check minimum dimension. if plugin.minwidth and self.size[0] < plugin.minwidth: - self._log.debug(u'image too small ({} < {})', + self._log.debug('image too small ({} < {})', self.size[0], plugin.minwidth) return self.CANDIDATE_BAD @@ -110,27 +108,27 @@ class Candidate(object): if plugin.enforce_ratio: if plugin.margin_px: if edge_diff > plugin.margin_px: - self._log.debug(u'image is not close enough to being ' - u'square, ({} - {} > {})', + self._log.debug('image is not close enough to being ' + 'square, ({} - {} > {})', long_edge, short_edge, plugin.margin_px) return self.CANDIDATE_BAD elif plugin.margin_percent: margin_px = plugin.margin_percent * long_edge if edge_diff > margin_px: - self._log.debug(u'image is not close enough to being ' - u'square, ({} - {} > {})', + self._log.debug('image is not close enough to being ' + 'square, ({} - {} > {})', long_edge, short_edge, margin_px) return self.CANDIDATE_BAD elif edge_diff: # also reached for margin_px == 0 and margin_percent == 0.0 - self._log.debug(u'image is not square ({} != {})', + self._log.debug('image is not square ({} != {})', self.size[0], self.size[1]) return self.CANDIDATE_BAD # Check maximum dimension. downscale = False if plugin.maxwidth and self.size[0] > plugin.maxwidth: - self._log.debug(u'image needs rescaling ({} > {})', + self._log.debug('image needs rescaling ({} > {})', self.size[0], plugin.maxwidth) downscale = True @@ -139,7 +137,7 @@ class Candidate(object): if plugin.max_filesize: filesize = os.stat(syspath(self.path)).st_size if filesize > plugin.max_filesize: - self._log.debug(u'image needs resizing ({}B > {}B)', + self._log.debug('image needs resizing ({}B > {}B)', filesize, plugin.max_filesize) downsize = True @@ -206,7 +204,7 @@ def _logged_get(log, *args, **kwargs): return s.send(prepped, **send_kwargs) -class RequestMixin(object): +class RequestMixin: """Adds a Requests wrapper to the class that uses the logger, which must be named `self._log`. """ @@ -244,7 +242,7 @@ class ArtSource(RequestMixin): class LocalArtSource(ArtSource): IS_LOCAL = True - LOC_STR = u'local' + LOC_STR = 'local' def fetch_image(self, candidate, plugin): pass @@ -252,7 +250,7 @@ class LocalArtSource(ArtSource): class RemoteArtSource(ArtSource): IS_LOCAL = False - LOC_STR = u'remote' + LOC_STR = 'remote' def fetch_image(self, candidate, plugin): """Downloads an image from a URL and checks whether it seems to @@ -264,7 +262,7 @@ class RemoteArtSource(ArtSource): candidate.url) try: with closing(self.request(candidate.url, stream=True, - message=u'downloading image')) as resp: + message='downloading image')) as resp: ct = resp.headers.get('Content-Type', None) # Download the image to a temporary file. As some servers @@ -292,16 +290,16 @@ class RemoteArtSource(ArtSource): real_ct = ct if real_ct not in CONTENT_TYPES: - self._log.debug(u'not a supported image: {}', - real_ct or u'unknown content type') + self._log.debug('not a supported image: {}', + real_ct or 'unknown content type') return ext = b'.' + CONTENT_TYPES[real_ct][0] if real_ct != ct: - self._log.warning(u'Server specified {}, but returned a ' - u'{} image. Correcting the extension ' - u'to {}', + self._log.warning('Server specified {}, but returned a ' + '{} image. Correcting the extension ' + 'to {}', ct, real_ct, ext) suffix = py3_path(ext) @@ -311,15 +309,15 @@ class RemoteArtSource(ArtSource): # download the remaining part of the image for chunk in data: fh.write(chunk) - self._log.debug(u'downloaded art to: {0}', + self._log.debug('downloaded art to: {0}', util.displayable_path(fh.name)) candidate.path = util.bytestring_path(fh.name) return - except (IOError, requests.RequestException, TypeError) as exc: + except (OSError, requests.RequestException, TypeError) as exc: # Handling TypeError works around a urllib3 bug: # https://github.com/shazow/urllib3/issues/556 - self._log.debug(u'error fetching art: {}', exc) + self._log.debug('error fetching art: {}', exc) return def cleanup(self, candidate): @@ -327,11 +325,11 @@ class RemoteArtSource(ArtSource): try: util.remove(path=candidate.path) except util.FilesystemError as exc: - self._log.debug(u'error cleaning up tmp art: {}', exc) + self._log.debug('error cleaning up tmp art: {}', exc) class CoverArtArchive(RemoteArtSource): - NAME = u"Cover Art Archive" + NAME = "Cover Art Archive" VALID_MATCHING_CRITERIA = ['release', 'releasegroup'] VALID_THUMBNAIL_SIZES = [250, 500, 1200] @@ -351,14 +349,14 @@ class CoverArtArchive(RemoteArtSource): try: response = self.request(url) except requests.RequestException: - self._log.debug(u'{0}: error receiving response' + self._log.debug('{}: error receiving response' .format(self.NAME)) return try: data = response.json() except ValueError: - self._log.debug(u'{0}: error loading response: {1}' + self._log.debug('{}: error loading response: {}' .format(self.NAME, response.text)) return @@ -395,7 +393,7 @@ class CoverArtArchive(RemoteArtSource): class Amazon(RemoteArtSource): - NAME = u"Amazon" + NAME = "Amazon" if util.SNI_SUPPORTED: URL = 'https://images.amazon.com/images/P/%s.%02i.LZZZZZZZ.jpg' else: @@ -412,7 +410,7 @@ class Amazon(RemoteArtSource): class AlbumArtOrg(RemoteArtSource): - NAME = u"AlbumArt.org scraper" + NAME = "AlbumArt.org scraper" if util.SNI_SUPPORTED: URL = 'https://www.albumart.org/index_detail.php' else: @@ -427,9 +425,9 @@ class AlbumArtOrg(RemoteArtSource): # Get the page from albumart.org. try: resp = self.request(self.URL, params={'asin': album.asin}) - self._log.debug(u'scraped art URL: {0}', resp.url) + self._log.debug('scraped art URL: {0}', resp.url) except requests.RequestException: - self._log.debug(u'error scraping art page') + self._log.debug('error scraping art page') return # Search the page for the image URL. @@ -438,15 +436,15 @@ class AlbumArtOrg(RemoteArtSource): image_url = m.group(1) yield self._candidate(url=image_url, match=Candidate.MATCH_EXACT) else: - self._log.debug(u'no image found on page') + self._log.debug('no image found on page') class GoogleImages(RemoteArtSource): - NAME = u"Google Images" - URL = u'https://www.googleapis.com/customsearch/v1' + NAME = "Google Images" + URL = 'https://www.googleapis.com/customsearch/v1' def __init__(self, *args, **kwargs): - super(GoogleImages, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.key = self._config['google_key'].get(), self.cx = self._config['google_engine'].get(), @@ -466,20 +464,20 @@ class GoogleImages(RemoteArtSource): 'searchType': 'image' }) except requests.RequestException: - self._log.debug(u'google: error receiving response') + self._log.debug('google: error receiving response') return # Get results using JSON. try: data = response.json() except ValueError: - self._log.debug(u'google: error loading response: {}' + self._log.debug('google: error loading response: {}' .format(response.text)) return if 'error' in data: reason = data['error']['errors'][0]['reason'] - self._log.debug(u'google fetchart error: {0}', reason) + self._log.debug('google fetchart error: {0}', reason) return if 'items' in data.keys(): @@ -490,13 +488,13 @@ class GoogleImages(RemoteArtSource): class FanartTV(RemoteArtSource): """Art from fanart.tv requested using their API""" - NAME = u"fanart.tv" + NAME = "fanart.tv" API_URL = 'https://webservice.fanart.tv/v3/' API_ALBUMS = API_URL + 'music/albums/' PROJECT_KEY = '61a7d0ab4e67162b7a0c7c35915cd48e' def __init__(self, *args, **kwargs): - super(FanartTV, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.client_key = self._config['fanarttv_key'].get() def get(self, album, plugin, paths): @@ -509,51 +507,51 @@ class FanartTV(RemoteArtSource): headers={'api-key': self.PROJECT_KEY, 'client-key': self.client_key}) except requests.RequestException: - self._log.debug(u'fanart.tv: error receiving response') + self._log.debug('fanart.tv: error receiving response') return try: data = response.json() except ValueError: - self._log.debug(u'fanart.tv: error loading response: {}', + self._log.debug('fanart.tv: error loading response: {}', response.text) return - if u'status' in data and data[u'status'] == u'error': - if u'not found' in data[u'error message'].lower(): - self._log.debug(u'fanart.tv: no image found') - elif u'api key' in data[u'error message'].lower(): - self._log.warning(u'fanart.tv: Invalid API key given, please ' - u'enter a valid one in your config file.') + if 'status' in data and data['status'] == 'error': + if 'not found' in data['error message'].lower(): + self._log.debug('fanart.tv: no image found') + elif 'api key' in data['error message'].lower(): + self._log.warning('fanart.tv: Invalid API key given, please ' + 'enter a valid one in your config file.') else: - self._log.debug(u'fanart.tv: error on request: {}', - data[u'error message']) + self._log.debug('fanart.tv: error on request: {}', + data['error message']) return matches = [] # can there be more than one releasegroupid per response? - for mbid, art in data.get(u'albums', {}).items(): + for mbid, art in data.get('albums', {}).items(): # there might be more art referenced, e.g. cdart, and an albumcover # might not be present, even if the request was successful - if album.mb_releasegroupid == mbid and u'albumcover' in art: - matches.extend(art[u'albumcover']) + if album.mb_releasegroupid == mbid and 'albumcover' in art: + matches.extend(art['albumcover']) # can this actually occur? else: - self._log.debug(u'fanart.tv: unexpected mb_releasegroupid in ' - u'response!') + self._log.debug('fanart.tv: unexpected mb_releasegroupid in ' + 'response!') - matches.sort(key=lambda x: x[u'likes'], reverse=True) + matches.sort(key=lambda x: x['likes'], reverse=True) for item in matches: # fanart.tv has a strict size requirement for album art to be # uploaded - yield self._candidate(url=item[u'url'], + yield self._candidate(url=item['url'], match=Candidate.MATCH_EXACT, size=(1000, 1000)) class ITunesStore(RemoteArtSource): - NAME = u"iTunes Store" - API_URL = u'https://itunes.apple.com/search' + NAME = "iTunes Store" + API_URL = 'https://itunes.apple.com/search' def get(self, album, plugin, paths): """Return art URL from iTunes Store given an album title. @@ -562,31 +560,31 @@ class ITunesStore(RemoteArtSource): return payload = { - 'term': album.albumartist + u' ' + album.album, - 'entity': u'album', - 'media': u'music', + 'term': album.albumartist + ' ' + album.album, + 'entity': 'album', + 'media': 'music', 'limit': 200 } try: r = self.request(self.API_URL, params=payload) r.raise_for_status() except requests.RequestException as e: - self._log.debug(u'iTunes search failed: {0}', e) + self._log.debug('iTunes search failed: {0}', e) return try: candidates = r.json()['results'] except ValueError as e: - self._log.debug(u'Could not decode json response: {0}', e) + self._log.debug('Could not decode json response: {0}', e) return except KeyError as e: - self._log.debug(u'{} not found in json. Fields are {} ', + self._log.debug('{} not found in json. Fields are {} ', e, list(r.json().keys())) return if not candidates: - self._log.debug(u'iTunes search for {!r} got no results', + self._log.debug('iTunes search for {!r} got no results', payload['term']) return @@ -605,7 +603,7 @@ class ITunesStore(RemoteArtSource): yield self._candidate(url=art_url, match=Candidate.MATCH_EXACT) except KeyError as e: - self._log.debug(u'Malformed itunes candidate: {} not found in {}', # NOQA E501 + self._log.debug('Malformed itunes candidate: {} not found in {}', # NOQA E501 e, list(c.keys())) @@ -616,16 +614,16 @@ class ITunesStore(RemoteArtSource): yield self._candidate(url=fallback_art_url, match=Candidate.MATCH_FALLBACK) except KeyError as e: - self._log.debug(u'Malformed itunes candidate: {} not found in {}', + self._log.debug('Malformed itunes candidate: {} not found in {}', e, list(c.keys())) class Wikipedia(RemoteArtSource): - NAME = u"Wikipedia (queried through DBpedia)" + NAME = "Wikipedia (queried through DBpedia)" DBPEDIA_URL = 'https://dbpedia.org/sparql' WIKIPEDIA_URL = 'https://en.wikipedia.org/w/api.php' - SPARQL_QUERY = u'''PREFIX rdf: + SPARQL_QUERY = '''PREFIX rdf: PREFIX dbpprop: PREFIX owl: PREFIX rdfs: @@ -666,7 +664,7 @@ class Wikipedia(RemoteArtSource): headers={'content-type': 'application/json'}, ) except requests.RequestException: - self._log.debug(u'dbpedia: error receiving response') + self._log.debug('dbpedia: error receiving response') return try: @@ -676,9 +674,9 @@ class Wikipedia(RemoteArtSource): cover_filename = 'File:' + results[0]['coverFilename']['value'] page_id = results[0]['pageId']['value'] else: - self._log.debug(u'wikipedia: album not found on dbpedia') + self._log.debug('wikipedia: album not found on dbpedia') except (ValueError, KeyError, IndexError): - self._log.debug(u'wikipedia: error scraping dbpedia response: {}', + self._log.debug('wikipedia: error scraping dbpedia response: {}', dbpedia_response.text) # Ensure we have a filename before attempting to query wikipedia @@ -693,7 +691,7 @@ class Wikipedia(RemoteArtSource): if ' .' in cover_filename and \ '.' not in cover_filename.split(' .')[-1]: self._log.debug( - u'wikipedia: dbpedia provided incomplete cover_filename' + 'wikipedia: dbpedia provided incomplete cover_filename' ) lpart, rpart = cover_filename.rsplit(' .', 1) @@ -711,7 +709,7 @@ class Wikipedia(RemoteArtSource): headers={'content-type': 'application/json'}, ) except requests.RequestException: - self._log.debug(u'wikipedia: error receiving response') + self._log.debug('wikipedia: error receiving response') return # Try to see if one of the images on the pages matches our @@ -726,7 +724,7 @@ class Wikipedia(RemoteArtSource): break except (ValueError, KeyError): self._log.debug( - u'wikipedia: failed to retrieve a cover_filename' + 'wikipedia: failed to retrieve a cover_filename' ) return @@ -745,7 +743,7 @@ class Wikipedia(RemoteArtSource): headers={'content-type': 'application/json'}, ) except requests.RequestException: - self._log.debug(u'wikipedia: error receiving response') + self._log.debug('wikipedia: error receiving response') return try: @@ -756,12 +754,12 @@ class Wikipedia(RemoteArtSource): yield self._candidate(url=image_url, match=Candidate.MATCH_EXACT) except (ValueError, KeyError, IndexError): - self._log.debug(u'wikipedia: error scraping imageinfo') + self._log.debug('wikipedia: error scraping imageinfo') return class FileSystem(LocalArtSource): - NAME = u"Filesystem" + NAME = "Filesystem" @staticmethod def filename_priority(filename, cover_names): @@ -806,7 +804,7 @@ class FileSystem(LocalArtSource): remaining = [] for fn in images: if re.search(cover_pat, os.path.splitext(fn)[0], re.I): - self._log.debug(u'using well-named art file {0}', + self._log.debug('using well-named art file {0}', util.displayable_path(fn)) yield self._candidate(path=os.path.join(path, fn), match=Candidate.MATCH_EXACT) @@ -815,14 +813,14 @@ class FileSystem(LocalArtSource): # Fall back to any image in the folder. if remaining and not plugin.cautious: - self._log.debug(u'using fallback art file {0}', + self._log.debug('using fallback art file {0}', util.displayable_path(remaining[0])) yield self._candidate(path=os.path.join(path, remaining[0]), match=Candidate.MATCH_FALLBACK) class LastFM(RemoteArtSource): - NAME = u"Last.fm" + NAME = "Last.fm" # Sizes in priority order. SIZES = OrderedDict([ @@ -839,7 +837,7 @@ class LastFM(RemoteArtSource): API_URL = 'http://ws.audioscrobbler.com/2.0' def __init__(self, *args, **kwargs): - super(LastFM, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.key = self._config['lastfm_key'].get(), def get(self, album, plugin, paths): @@ -854,7 +852,7 @@ class LastFM(RemoteArtSource): 'format': 'json', }) except requests.RequestException: - self._log.debug(u'lastfm: error receiving response') + self._log.debug('lastfm: error receiving response') return try: @@ -878,26 +876,26 @@ class LastFM(RemoteArtSource): yield self._candidate(url=images[size], size=self.SIZES[size]) except ValueError: - self._log.debug(u'lastfm: error loading response: {}' + self._log.debug('lastfm: error loading response: {}' .format(response.text)) return # Try each source in turn. -SOURCES_ALL = [u'filesystem', - u'coverart', u'itunes', u'amazon', u'albumart', - u'wikipedia', u'google', u'fanarttv', u'lastfm'] +SOURCES_ALL = ['filesystem', + 'coverart', 'itunes', 'amazon', 'albumart', + 'wikipedia', 'google', 'fanarttv', 'lastfm'] ART_SOURCES = { - u'filesystem': FileSystem, - u'coverart': CoverArtArchive, - u'itunes': ITunesStore, - u'albumart': AlbumArtOrg, - u'amazon': Amazon, - u'wikipedia': Wikipedia, - u'google': GoogleImages, - u'fanarttv': FanartTV, - u'lastfm': LastFM, + 'filesystem': FileSystem, + 'coverart': CoverArtArchive, + 'itunes': ITunesStore, + 'albumart': AlbumArtOrg, + 'amazon': Amazon, + 'wikipedia': Wikipedia, + 'google': GoogleImages, + 'fanarttv': FanartTV, + 'lastfm': LastFM, } SOURCE_NAMES = {v: k for k, v in ART_SOURCES.items()} @@ -909,7 +907,7 @@ class FetchArtPlugin(plugins.BeetsPlugin, RequestMixin): PAT_PERCENT = r"(100(\.00?)?|[1-9]?[0-9](\.[0-9]{1,2})?)%" def __init__(self): - super(FetchArtPlugin, self).__init__() + super().__init__() # Holds candidates corresponding to downloaded images between # fetching them and placing them in the filesystem. @@ -927,7 +925,7 @@ class FetchArtPlugin(plugins.BeetsPlugin, RequestMixin): 'sources': ['filesystem', 'coverart', 'itunes', 'amazon', 'albumart'], 'google_key': None, - 'google_engine': u'001442825323518660753:hrh5ch1gjzm', + 'google_engine': '001442825323518660753:hrh5ch1gjzm', 'fanarttv_key': None, 'lastfm_key': None, 'store_source': False, @@ -949,10 +947,10 @@ class FetchArtPlugin(plugins.BeetsPlugin, RequestMixin): confuse.String(pattern=self.PAT_PERCENT)])) self.margin_px = None self.margin_percent = None - if type(self.enforce_ratio) is six.text_type: - if self.enforce_ratio[-1] == u'%': + if type(self.enforce_ratio) is str: + if self.enforce_ratio[-1] == '%': self.margin_percent = float(self.enforce_ratio[:-1]) / 100 - elif self.enforce_ratio[-2:] == u'px': + elif self.enforce_ratio[-2:] == 'px': self.margin_px = int(self.enforce_ratio[:-2]) else: # shouldn't happen @@ -974,11 +972,11 @@ class FetchArtPlugin(plugins.BeetsPlugin, RequestMixin): available_sources = list(SOURCES_ALL) if not self.config['google_key'].get() and \ - u'google' in available_sources: - available_sources.remove(u'google') + 'google' in available_sources: + available_sources.remove('google') if not self.config['lastfm_key'].get() and \ - u'lastfm' in available_sources: - available_sources.remove(u'lastfm') + 'lastfm' in available_sources: + available_sources.remove('lastfm') available_sources = [(s, c) for s in available_sources for c in ART_SOURCES[s].VALID_MATCHING_CRITERIA] @@ -988,9 +986,9 @@ class FetchArtPlugin(plugins.BeetsPlugin, RequestMixin): if 'remote_priority' in self.config: self._log.warning( - u'The `fetch_art.remote_priority` configuration option has ' - u'been deprecated. Instead, place `filesystem` at the end of ' - u'your `sources` list.') + 'The `fetch_art.remote_priority` configuration option has ' + 'been deprecated. Instead, place `filesystem` at the end of ' + 'your `sources` list.') if self.config['remote_priority'].get(bool): fs = [] others = [] @@ -1032,7 +1030,7 @@ class FetchArtPlugin(plugins.BeetsPlugin, RequestMixin): if self.store_source: # store the source of the chosen artwork in a flexible field self._log.debug( - u"Storing art_source for {0.albumartist} - {0.album}", + "Storing art_source for {0.albumartist} - {0.album}", album) album.art_source = SOURCE_NAMES[type(candidate.source)] album.store() @@ -1052,14 +1050,14 @@ class FetchArtPlugin(plugins.BeetsPlugin, RequestMixin): def commands(self): cmd = ui.Subcommand('fetchart', help='download album art') cmd.parser.add_option( - u'-f', u'--force', dest='force', + '-f', '--force', dest='force', action='store_true', default=False, - help=u're-download art when already present' + help='re-download art when already present' ) cmd.parser.add_option( - u'-q', u'--quiet', dest='quiet', + '-q', '--quiet', dest='quiet', action='store_true', default=False, - help=u'quiet mode: do not output albums that already have artwork' + help='quiet mode: do not output albums that already have artwork' ) def func(lib, opts, args): @@ -1083,7 +1081,7 @@ class FetchArtPlugin(plugins.BeetsPlugin, RequestMixin): for source in self.sources: if source.IS_LOCAL or not local_only: self._log.debug( - u'trying source {0} for album {1.albumartist} - {1.album}', + 'trying source {0} for album {1.albumartist} - {1.album}', SOURCE_NAMES[type(source)], album, ) @@ -1094,7 +1092,7 @@ class FetchArtPlugin(plugins.BeetsPlugin, RequestMixin): if candidate.validate(self): out = candidate self._log.debug( - u'using {0.LOC_STR} image {1}'.format( + 'using {0.LOC_STR} image {1}'.format( source, util.displayable_path(out.path))) break # Remove temporary files for invalid candidates. @@ -1115,8 +1113,8 @@ class FetchArtPlugin(plugins.BeetsPlugin, RequestMixin): if album.artpath and not force and os.path.isfile(album.artpath): if not quiet: message = ui.colorize('text_highlight_minor', - u'has album art') - self._log.info(u'{0}: {1}', album, message) + 'has album art') + self._log.info('{0}: {1}', album, message) else: # In ordinary invocations, look for images on the # filesystem. When forcing, however, always go to the Web @@ -1126,7 +1124,7 @@ class FetchArtPlugin(plugins.BeetsPlugin, RequestMixin): candidate = self.art_for_album(album, local_paths) if candidate: self._set_art(album, candidate) - message = ui.colorize('text_success', u'found album art') + message = ui.colorize('text_success', 'found album art') else: - message = ui.colorize('text_error', u'no art found') - self._log.info(u'{0}: {1}', album, message) + message = ui.colorize('text_error', 'no art found') + self._log.info('{0}: {1}', album, message) diff --git a/beetsplug/filefilter.py b/beetsplug/filefilter.py index ea521d5f6..ec8fddb4f 100644 --- a/beetsplug/filefilter.py +++ b/beetsplug/filefilter.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Malte Ried. # @@ -16,7 +15,6 @@ """Filter imported files using a regular expression. """ -from __future__ import division, absolute_import, print_function import re from beets import config @@ -27,7 +25,7 @@ from beets.importer import SingletonImportTask class FileFilterPlugin(BeetsPlugin): def __init__(self): - super(FileFilterPlugin, self).__init__() + super().__init__() self.register_listener('import_task_created', self.import_task_created_event) self.config.add({ diff --git a/beetsplug/fish.py b/beetsplug/fish.py index b4c28c283..21fd67f60 100644 --- a/beetsplug/fish.py +++ b/beetsplug/fish.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2015, winters jean-marie. # Copyright 2020, Justin Mayer @@ -23,7 +22,6 @@ by default but can be added via the `-e` / `--extravalues` flag. For example: `beet fish -e genre -e albumartist` """ -from __future__ import division, absolute_import, print_function from beets.plugins import BeetsPlugin from beets import library, ui diff --git a/beetsplug/freedesktop.py b/beetsplug/freedesktop.py index a768be2d8..ba4d58793 100644 --- a/beetsplug/freedesktop.py +++ b/beetsplug/freedesktop.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Matt Lichtenberg. # @@ -16,7 +15,6 @@ """Creates freedesktop.org-compliant .directory files on an album level. """ -from __future__ import division, absolute_import, print_function from beets.plugins import BeetsPlugin from beets import ui @@ -26,12 +24,12 @@ class FreedesktopPlugin(BeetsPlugin): def commands(self): deprecated = ui.Subcommand( "freedesktop", - help=u"Print a message to redirect to thumbnails --dolphin") + help="Print a message to redirect to thumbnails --dolphin") deprecated.func = self.deprecation_message return [deprecated] def deprecation_message(self, lib, opts, args): - ui.print_(u"This plugin is deprecated. Its functionality is " - u"superseded by the 'thumbnails' plugin") - ui.print_(u"'thumbnails --dolphin' replaces freedesktop. See doc & " - u"changelog for more information") + ui.print_("This plugin is deprecated. Its functionality is " + "superseded by the 'thumbnails' plugin") + ui.print_("'thumbnails --dolphin' replaces freedesktop. See doc & " + "changelog for more information") diff --git a/beetsplug/fromfilename.py b/beetsplug/fromfilename.py index 56b68f756..de9f2b4ee 100644 --- a/beetsplug/fromfilename.py +++ b/beetsplug/fromfilename.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Jan-Erik Dahlin # @@ -16,7 +15,6 @@ """If the title is empty, try to extract track and title from the filename. """ -from __future__ import division, absolute_import, print_function from beets import plugins from beets.util import displayable_path @@ -124,7 +122,7 @@ def apply_matches(d): # Apply the title and track. for item in d: if bad_title(item.title): - item.title = six.text_type(d[item][title_field]) + item.title = str(d[item][title_field]) if 'track' in d[item] and item.track == 0: item.track = int(d[item]['track']) @@ -133,7 +131,7 @@ def apply_matches(d): class FromFilenamePlugin(plugins.BeetsPlugin): def __init__(self): - super(FromFilenamePlugin, self).__init__() + super().__init__() self.register_listener('import_task_start', filename_task) diff --git a/beetsplug/ftintitle.py b/beetsplug/ftintitle.py index 9303f9cfc..57863d2bd 100644 --- a/beetsplug/ftintitle.py +++ b/beetsplug/ftintitle.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Verrus, # @@ -15,7 +14,6 @@ """Moves "featured" artists to the title from the artist field. """ -from __future__ import division, absolute_import, print_function import re @@ -75,22 +73,22 @@ def find_feat_part(artist, albumartist): class FtInTitlePlugin(plugins.BeetsPlugin): def __init__(self): - super(FtInTitlePlugin, self).__init__() + super().__init__() self.config.add({ 'auto': True, 'drop': False, - 'format': u'feat. {0}', + 'format': 'feat. {0}', }) self._command = ui.Subcommand( 'ftintitle', - help=u'move featured artists to the title field') + help='move featured artists to the title field') self._command.parser.add_option( - u'-d', u'--drop', dest='drop', + '-d', '--drop', dest='drop', action='store_true', default=None, - help=u'drop featuring from artists and ignore title update') + help='drop featuring from artists and ignore title update') if self.config['auto']: self.import_stages = [self.imported] @@ -127,7 +125,7 @@ class FtInTitlePlugin(plugins.BeetsPlugin): remove it from the artist field. """ # In all cases, update the artist fields. - self._log.info(u'artist: {0} -> {1}', item.artist, item.albumartist) + self._log.info('artist: {0} -> {1}', item.artist, item.albumartist) item.artist = item.albumartist if item.artist_sort: # Just strip the featured artist from the sort name. @@ -138,8 +136,8 @@ class FtInTitlePlugin(plugins.BeetsPlugin): if not drop_feat and not contains_feat(item.title): feat_format = self.config['format'].as_str() new_format = feat_format.format(feat_part) - new_title = u"{0} {1}".format(item.title, new_format) - self._log.info(u'title: {0} -> {1}', item.title, new_title) + new_title = f"{item.title} {new_format}" + self._log.info('title: {0} -> {1}', item.title, new_title) item.title = new_title def ft_in_title(self, item, drop_feat): @@ -165,4 +163,4 @@ class FtInTitlePlugin(plugins.BeetsPlugin): if feat_part: self.update_metadata(item, feat_part, drop_feat) else: - self._log.info(u'no featuring artists found') + self._log.info('no featuring artists found') diff --git a/beetsplug/fuzzy.py b/beetsplug/fuzzy.py index a7308a525..418296394 100644 --- a/beetsplug/fuzzy.py +++ b/beetsplug/fuzzy.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Philippe Mongeau. # @@ -16,7 +15,6 @@ """Provides a fuzzy matching query. """ -from __future__ import division, absolute_import, print_function from beets.plugins import BeetsPlugin from beets.dbcore.query import StringFieldQuery @@ -37,7 +35,7 @@ class FuzzyQuery(StringFieldQuery): class FuzzyPlugin(BeetsPlugin): def __init__(self): - super(FuzzyPlugin, self).__init__() + super().__init__() self.config.add({ 'prefix': '~', 'threshold': 0.7, diff --git a/beetsplug/gmusic.py b/beetsplug/gmusic.py index f548d1944..1761dbb13 100644 --- a/beetsplug/gmusic.py +++ b/beetsplug/gmusic.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2017, Tigran Kostandyan. # @@ -15,7 +14,6 @@ """Upload files to Google Play Music and list songs in its library.""" -from __future__ import absolute_import, division, print_function import os.path from beets.plugins import BeetsPlugin @@ -29,7 +27,7 @@ import gmusicapi.clients class Gmusic(BeetsPlugin): def __init__(self): - super(Gmusic, self).__init__() + super().__init__() self.m = Musicmanager() # OAUTH_FILEPATH was moved in gmusicapi 12.0.0. @@ -39,22 +37,22 @@ class Gmusic(BeetsPlugin): oauth_file = gmusicapi.clients.OAUTH_FILEPATH self.config.add({ - u'auto': False, - u'uploader_id': '', - u'uploader_name': '', - u'device_id': '', - u'oauth_file': oauth_file, + 'auto': False, + 'uploader_id': '', + 'uploader_name': '', + 'device_id': '', + 'oauth_file': oauth_file, }) if self.config['auto']: self.import_stages = [self.autoupload] def commands(self): gupload = Subcommand('gmusic-upload', - help=u'upload your tracks to Google Play Music') + help='upload your tracks to Google Play Music') gupload.func = self.upload search = Subcommand('gmusic-songs', - help=u'list of songs in Google Play Music library') + help='list of songs in Google Play Music library') search.parser.add_option('-t', '--track', dest='track', action='store_true', help='Search by track name') @@ -83,17 +81,17 @@ class Gmusic(BeetsPlugin): items = lib.items(ui.decargs(args)) files = self.getpaths(items) self.authenticate() - ui.print_(u'Uploading your files...') + ui.print_('Uploading your files...') self.m.upload(filepaths=files) - ui.print_(u'Your files were successfully added to library') + ui.print_('Your files were successfully added to library') def autoupload(self, session, task): items = task.imported_items() files = self.getpaths(items) self.authenticate() - self._log.info(u'Uploading files to Google Play Music...', files) + self._log.info('Uploading files to Google Play Music...', files) self.m.upload(filepaths=files) - self._log.info(u'Your files were successfully added to your ' + self._log.info('Your files were successfully added to your ' + 'Google Play Music library') def getpaths(self, items): @@ -117,7 +115,7 @@ class Gmusic(BeetsPlugin): files = mobile.get_all_songs() except NotLoggedIn: ui.print_( - u'Authentication error. Please check your email and password.' + 'Authentication error. Please check your email and password.' ) return if not args: diff --git a/beetsplug/hook.py b/beetsplug/hook.py index 595531f38..f11898797 100644 --- a/beetsplug/hook.py +++ b/beetsplug/hook.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2015, Adrian Sampson. # @@ -14,7 +13,6 @@ # included in all copies or substantial portions of the Software. """Allows custom commands to be run when an event is emitted by beets""" -from __future__ import division, absolute_import, print_function import string import subprocess @@ -49,7 +47,7 @@ class CodingFormatter(string.Formatter): if isinstance(format_string, bytes): format_string = format_string.decode(self._coding) - return super(CodingFormatter, self).format(format_string, *args, + return super().format(format_string, *args, **kwargs) def convert_field(self, value, conversion): @@ -59,7 +57,7 @@ class CodingFormatter(string.Formatter): See string.Formatter.convert_field. """ - converted = super(CodingFormatter, self).convert_field(value, + converted = super().convert_field(value, conversion) if isinstance(converted, bytes): @@ -71,7 +69,7 @@ class CodingFormatter(string.Formatter): class HookPlugin(BeetsPlugin): """Allows custom commands to be run when an event is emitted by beets""" def __init__(self): - super(HookPlugin, self).__init__() + super().__init__() self.config.add({ 'hooks': [] @@ -102,15 +100,15 @@ class HookPlugin(BeetsPlugin): command_pieces[i] = formatter.format(piece, event=event, **kwargs) - self._log.debug(u'running command "{0}" for event {1}', - u' '.join(command_pieces), event) + self._log.debug('running command "{0}" for event {1}', + ' '.join(command_pieces), event) try: subprocess.check_call(command_pieces) except subprocess.CalledProcessError as exc: - self._log.error(u'hook for {0} exited with status {1}', + self._log.error('hook for {0} exited with status {1}', event, exc.returncode) except OSError as exc: - self._log.error(u'hook for {0} failed: {1}', event, exc) + self._log.error('hook for {0} failed: {1}', event, exc) self.register_listener(event, hook_function) diff --git a/beetsplug/ihate.py b/beetsplug/ihate.py index 6ed250fe4..91850e09b 100644 --- a/beetsplug/ihate.py +++ b/beetsplug/ihate.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Blemjhoo Tezoulbr . # @@ -13,7 +12,6 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -from __future__ import division, absolute_import, print_function """Warns you about things you hate (or even blocks import).""" @@ -33,14 +31,14 @@ def summary(task): object. """ if task.is_album: - return u'{0} - {1}'.format(task.cur_artist, task.cur_album) + return f'{task.cur_artist} - {task.cur_album}' else: - return u'{0} - {1}'.format(task.item.artist, task.item.title) + return f'{task.item.artist} - {task.item.title}' class IHatePlugin(BeetsPlugin): def __init__(self): - super(IHatePlugin, self).__init__() + super().__init__() self.register_listener('import_task_choice', self.import_task_choice_event) self.config.add({ @@ -69,14 +67,14 @@ class IHatePlugin(BeetsPlugin): if task.choice_flag == action.APPLY: if skip_queries or warn_queries: - self._log.debug(u'processing your hate') + self._log.debug('processing your hate') if self.do_i_hate_this(task, skip_queries): task.choice_flag = action.SKIP - self._log.info(u'skipped: {0}', summary(task)) + self._log.info('skipped: {0}', summary(task)) return if self.do_i_hate_this(task, warn_queries): - self._log.info(u'you may hate this: {0}', summary(task)) + self._log.info('you may hate this: {0}', summary(task)) else: - self._log.debug(u'nothing to do') + self._log.debug('nothing to do') else: - self._log.debug(u'user made a decision, nothing to do') + self._log.debug('user made a decision, nothing to do') diff --git a/beetsplug/importadded.py b/beetsplug/importadded.py index 37e881e96..39838b5a2 100644 --- a/beetsplug/importadded.py +++ b/beetsplug/importadded.py @@ -1,11 +1,8 @@ -# -*- coding: utf-8 -*- - """Populate an item's `added` and `mtime` fields by using the file modification time (mtime) of the item's source file before import. Reimported albums and items are skipped. """ -from __future__ import division, absolute_import, print_function import os @@ -16,7 +13,7 @@ from beets.plugins import BeetsPlugin class ImportAddedPlugin(BeetsPlugin): def __init__(self): - super(ImportAddedPlugin, self).__init__() + super().__init__() self.config.add({ 'preserve_mtimes': False, 'preserve_write_mtimes': False, @@ -53,8 +50,8 @@ class ImportAddedPlugin(BeetsPlugin): def record_if_inplace(self, task, session): if not (session.config['copy'] or session.config['move'] or session.config['link'] or session.config['hardlink']): - self._log.debug(u"In place import detected, recording mtimes from " - u"source paths") + self._log.debug("In place import detected, recording mtimes from " + "source paths") items = [task.item] \ if isinstance(task, importer.SingletonImportTask) \ else task.items @@ -62,9 +59,9 @@ class ImportAddedPlugin(BeetsPlugin): self.record_import_mtime(item, item.path, item.path) def record_reimported(self, task, session): - self.reimported_item_ids = set(item.id for item, replaced_items + self.reimported_item_ids = {item.id for item, replaced_items in task.replaced_items.items() - if replaced_items) + if replaced_items} self.replaced_album_paths = set(task.replaced_albums.keys()) def write_file_mtime(self, path, mtime): @@ -86,14 +83,14 @@ class ImportAddedPlugin(BeetsPlugin): """ mtime = os.stat(util.syspath(source)).st_mtime self.item_mtime[destination] = mtime - self._log.debug(u"Recorded mtime {0} for item '{1}' imported from " - u"'{2}'", mtime, util.displayable_path(destination), + self._log.debug("Recorded mtime {0} for item '{1}' imported from " + "'{2}'", mtime, util.displayable_path(destination), util.displayable_path(source)) def update_album_times(self, lib, album): if self.reimported_album(album): - self._log.debug(u"Album '{0}' is reimported, skipping import of " - u"added dates for the album and its items.", + self._log.debug("Album '{0}' is reimported, skipping import of " + "added dates for the album and its items.", util.displayable_path(album.path)) return @@ -106,21 +103,21 @@ class ImportAddedPlugin(BeetsPlugin): self.write_item_mtime(item, mtime) item.store() album.added = min(album_mtimes) - self._log.debug(u"Import of album '{0}', selected album.added={1} " - u"from item file mtimes.", album.album, album.added) + self._log.debug("Import of album '{0}', selected album.added={1} " + "from item file mtimes.", album.album, album.added) album.store() def update_item_times(self, lib, item): if self.reimported_item(item): - self._log.debug(u"Item '{0}' is reimported, skipping import of " - u"added date.", util.displayable_path(item.path)) + self._log.debug("Item '{0}' is reimported, skipping import of " + "added date.", util.displayable_path(item.path)) return mtime = self.item_mtime.pop(item.path, None) if mtime: item.added = mtime if self.config['preserve_mtimes'].get(bool): self.write_item_mtime(item, mtime) - self._log.debug(u"Import of item '{0}', selected item.added={1}", + self._log.debug("Import of item '{0}', selected item.added={1}", util.displayable_path(item.path), item.added) item.store() @@ -131,5 +128,5 @@ class ImportAddedPlugin(BeetsPlugin): if item.added: if self.config['preserve_write_mtimes'].get(bool): self.write_item_mtime(item, item.added) - self._log.debug(u"Write of item '{0}', selected item.added={1}", + self._log.debug("Write of item '{0}', selected item.added={1}", util.displayable_path(item.path), item.added) diff --git a/beetsplug/importfeeds.py b/beetsplug/importfeeds.py index 35ae28835..ad6d84159 100644 --- a/beetsplug/importfeeds.py +++ b/beetsplug/importfeeds.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Fabrice Laporte. # @@ -13,7 +12,6 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -from __future__ import division, absolute_import, print_function """Write paths of imported files in various formats to ease later import in a music player. Also allow printing the new file locations to stdout in case @@ -54,11 +52,11 @@ def _write_m3u(m3u_path, items_paths): class ImportFeedsPlugin(BeetsPlugin): def __init__(self): - super(ImportFeedsPlugin, self).__init__() + super().__init__() self.config.add({ 'formats': [], - 'm3u_name': u'imported.m3u', + 'm3u_name': 'imported.m3u', 'dir': None, 'relative_to': None, 'absolute_path': False, @@ -118,9 +116,9 @@ class ImportFeedsPlugin(BeetsPlugin): link(path, dest) if 'echo' in formats: - self._log.info(u"Location of imported music:") + self._log.info("Location of imported music:") for path in paths: - self._log.info(u" {0}", path) + self._log.info(" {0}", path) def album_imported(self, lib, album): self._record_items(lib, album.album, album.items()) diff --git a/beetsplug/info.py b/beetsplug/info.py index 828b0a81a..1bb29d09b 100644 --- a/beetsplug/info.py +++ b/beetsplug/info.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -16,7 +15,6 @@ """Shows file metadata. """ -from __future__ import division, absolute_import, print_function import os @@ -110,7 +108,7 @@ def print_data(data, item=None, fmt=None): formatted = {} for key, value in data.items(): if isinstance(value, list): - formatted[key] = u'; '.join(value) + formatted[key] = '; '.join(value) if value is not None: formatted[key] = value @@ -118,7 +116,7 @@ def print_data(data, item=None, fmt=None): return maxwidth = max(len(key) for key in formatted) - lineformat = u'{{0:>{0}}}: {{1}}'.format(maxwidth) + lineformat = f'{{0:>{maxwidth}}}: {{1}}' if path: ui.print_(displayable_path(path)) @@ -126,7 +124,7 @@ def print_data(data, item=None, fmt=None): for field in sorted(formatted): value = formatted[field] if isinstance(value, list): - value = u'; '.join(value) + value = '; '.join(value) ui.print_(lineformat.format(field, value)) @@ -141,7 +139,7 @@ def print_data_keys(data, item=None): if len(formatted) == 0: return - line_format = u'{0}{{0}}'.format(u' ' * 4) + line_format = '{0}{{0}}'.format(' ' * 4) if path: ui.print_(displayable_path(path)) @@ -152,24 +150,24 @@ def print_data_keys(data, item=None): class InfoPlugin(BeetsPlugin): def commands(self): - cmd = ui.Subcommand('info', help=u'show file metadata') + cmd = ui.Subcommand('info', help='show file metadata') cmd.func = self.run cmd.parser.add_option( - u'-l', u'--library', action='store_true', - help=u'show library fields instead of tags', + '-l', '--library', action='store_true', + help='show library fields instead of tags', ) cmd.parser.add_option( - u'-s', u'--summarize', action='store_true', - help=u'summarize the tags of all files', + '-s', '--summarize', action='store_true', + help='summarize the tags of all files', ) cmd.parser.add_option( - u'-i', u'--include-keys', default=[], + '-i', '--include-keys', default=[], action='append', dest='included_keys', - help=u'comma separated list of keys to show', + help='comma separated list of keys to show', ) cmd.parser.add_option( - u'-k', u'--keys-only', action='store_true', - help=u'show only the keys', + '-k', '--keys-only', action='store_true', + help='show only the keys', ) cmd.parser.add_format_option(target='item') return [cmd] @@ -204,8 +202,8 @@ class InfoPlugin(BeetsPlugin): for data_emitter in data_collector(lib, ui.decargs(args)): try: data, item = data_emitter(included_keys or '*') - except (mediafile.UnreadableFileError, IOError) as ex: - self._log.error(u'cannot read file: {0}', ex) + except (mediafile.UnreadableFileError, OSError) as ex: + self._log.error('cannot read file: {0}', ex) continue if opts.summarize: diff --git a/beetsplug/inline.py b/beetsplug/inline.py index bf6ff92da..b3a779108 100644 --- a/beetsplug/inline.py +++ b/beetsplug/inline.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -15,7 +14,6 @@ """Allows inline path template customization code in the config file. """ -from __future__ import division, absolute_import, print_function import traceback import itertools @@ -24,16 +22,16 @@ from beets.plugins import BeetsPlugin from beets import config import six -FUNC_NAME = u'__INLINE_FUNC__' +FUNC_NAME = '__INLINE_FUNC__' class InlineError(Exception): """Raised when a runtime error occurs in an inline expression. """ def __init__(self, code, exc): - super(InlineError, self).__init__( - (u"error in inline path field code:\n" - u"%s\n%s: %s") % (code, type(exc).__name__, six.text_type(exc)) + super().__init__( + ("error in inline path field code:\n" + "%s\n%s: %s") % (code, type(exc).__name__, str(exc)) ) @@ -41,7 +39,7 @@ def _compile_func(body): """Given Python code for a function body, return a compiled callable that invokes that code. """ - body = u'def {0}():\n {1}'.format( + body = 'def {}():\n {}'.format( FUNC_NAME, body.replace('\n', '\n ') ) @@ -53,7 +51,7 @@ def _compile_func(body): class InlinePlugin(BeetsPlugin): def __init__(self): - super(InlinePlugin, self).__init__() + super().__init__() config.add({ 'pathfields': {}, # Legacy name. @@ -64,14 +62,14 @@ class InlinePlugin(BeetsPlugin): # Item fields. for key, view in itertools.chain(config['item_fields'].items(), config['pathfields'].items()): - self._log.debug(u'adding item field {0}', key) + self._log.debug('adding item field {0}', key) func = self.compile_inline(view.as_str(), False) if func is not None: self.template_fields[key] = func # Album fields. for key, view in config['album_fields'].items(): - self._log.debug(u'adding album field {0}', key) + self._log.debug('adding album field {0}', key) func = self.compile_inline(view.as_str(), True) if func is not None: self.album_template_fields[key] = func @@ -84,14 +82,14 @@ class InlinePlugin(BeetsPlugin): """ # First, try compiling as a single function. try: - code = compile(u'({0})'.format(python_code), 'inline', 'eval') + code = compile(f'({python_code})', 'inline', 'eval') except SyntaxError: # Fall back to a function body. try: func = _compile_func(python_code) except SyntaxError: - self._log.error(u'syntax error in inline field definition:\n' - u'{0}', traceback.format_exc()) + self._log.error('syntax error in inline field definition:\n' + '{0}', traceback.format_exc()) return else: is_expr = False diff --git a/beetsplug/ipfs.py b/beetsplug/ipfs.py index 40a17d756..3c42e7c86 100644 --- a/beetsplug/ipfs.py +++ b/beetsplug/ipfs.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # # Permission is hereby granted, free of charge, to any person obtaining @@ -15,7 +14,6 @@ """Adds support for ipfs. Requires go-ipfs and a running ipfs daemon """ -from __future__ import division, absolute_import, print_function from beets import ui, util, library, config from beets.plugins import BeetsPlugin @@ -29,7 +27,7 @@ import tempfile class IPFSPlugin(BeetsPlugin): def __init__(self): - super(IPFSPlugin, self).__init__() + super().__init__() self.config.add({ 'auto': True, 'nocopy': False, @@ -125,7 +123,7 @@ class IPFSPlugin(BeetsPlugin): try: output = util.command_output(cmd).stdout.split() except (OSError, subprocess.CalledProcessError) as exc: - self._log.error(u'Failed to add {0}, error: {1}', album_dir, exc) + self._log.error('Failed to add {0}, error: {1}', album_dir, exc) return False length = len(output) @@ -187,7 +185,7 @@ class IPFSPlugin(BeetsPlugin): cmd.append(tmp.name) output = util.command_output(cmd).stdout except (OSError, subprocess.CalledProcessError) as err: - msg = "Failed to publish library. Error: {0}".format(err) + msg = f"Failed to publish library. Error: {err}" self._log.error(msg) return False self._log.info("hash of library: {0}", output) @@ -204,17 +202,17 @@ class IPFSPlugin(BeetsPlugin): try: os.makedirs(remote_libs) except OSError as e: - msg = "Could not create {0}. Error: {1}".format(remote_libs, e) + msg = f"Could not create {remote_libs}. Error: {e}" self._log.error(msg) return False path = os.path.join(remote_libs, lib_name.encode() + b".db") if not os.path.exists(path): - cmd = "ipfs get {0} -o".format(_hash).split() + cmd = f"ipfs get {_hash} -o".split() cmd.append(path) try: util.command_output(cmd) except (OSError, subprocess.CalledProcessError): - self._log.error("Could not import {0}".format(_hash)) + self._log.error(f"Could not import {_hash}") return False # add all albums from remotes into a combined library @@ -241,7 +239,7 @@ class IPFSPlugin(BeetsPlugin): fmt = config['format_album'].get() try: albums = self.query(lib, args) - except IOError: + except OSError: ui.print_("No imported libraries yet.") return @@ -258,7 +256,7 @@ class IPFSPlugin(BeetsPlugin): remote_libs = os.path.join(lib_root, b"remotes") path = os.path.join(remote_libs, b"joined.db") if not os.path.isfile(path): - raise IOError + raise OSError return library.Library(path) def ipfs_added_albums(self, rlib, tmpname): @@ -285,7 +283,7 @@ class IPFSPlugin(BeetsPlugin): util._fsencoding(), 'ignore' ) # Clear current path from item - item.path = '/ipfs/{0}/{1}'.format(album.ipfs, item_path) + item.path = f'/ipfs/{album.ipfs}/{item_path}' item.id = None items.append(item) diff --git a/beetsplug/keyfinder.py b/beetsplug/keyfinder.py index 702003f0f..b695ab54e 100644 --- a/beetsplug/keyfinder.py +++ b/beetsplug/keyfinder.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Thomas Scholtes. # @@ -16,7 +15,6 @@ """Uses the `KeyFinder` program to add the `initial_key` field. """ -from __future__ import division, absolute_import, print_function import os.path import subprocess @@ -29,11 +27,11 @@ from beets.plugins import BeetsPlugin class KeyFinderPlugin(BeetsPlugin): def __init__(self): - super(KeyFinderPlugin, self).__init__() + super().__init__() self.config.add({ - u'bin': u'KeyFinder', - u'auto': True, - u'overwrite': False, + 'bin': 'KeyFinder', + 'auto': True, + 'overwrite': False, }) if self.config['auto'].get(bool): @@ -41,7 +39,7 @@ class KeyFinderPlugin(BeetsPlugin): def commands(self): cmd = ui.Subcommand('keyfinder', - help=u'detect and add initial key from audio') + help='detect and add initial key from audio') cmd.func = self.command return [cmd] @@ -67,12 +65,12 @@ class KeyFinderPlugin(BeetsPlugin): output = util.command_output(command + [util.syspath( item.path)]).stdout except (subprocess.CalledProcessError, OSError) as exc: - self._log.error(u'execution failed: {0}', exc) + self._log.error('execution failed: {0}', exc) continue except UnicodeEncodeError: # Workaround for Python 2 Windows bug. # https://bugs.python.org/issue1759845 - self._log.error(u'execution failed for Unicode path: {0!r}', + self._log.error('execution failed for Unicode path: {0!r}', item.path) continue @@ -81,17 +79,17 @@ class KeyFinderPlugin(BeetsPlugin): except IndexError: # Sometimes keyfinder-cli returns 0 but with no key, usually # when the file is silent or corrupt, so we log and skip. - self._log.error(u'no key returned for path: {0}', item.path) + self._log.error('no key returned for path: {0}', item.path) continue try: key = util.text_string(key_raw) except UnicodeDecodeError: - self._log.error(u'output is invalid UTF-8') + self._log.error('output is invalid UTF-8') continue item['initial_key'] = key - self._log.info(u'added computed initial key {0} for {1}', + self._log.info('added computed initial key {0} for {1}', key, util.displayable_path(item.path)) if write: diff --git a/beetsplug/kodiupdate.py b/beetsplug/kodiupdate.py index ce5cb478c..b24cd9463 100644 --- a/beetsplug/kodiupdate.py +++ b/beetsplug/kodiupdate.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2017, Pauli Kettunen. # @@ -23,7 +22,6 @@ Put something like the following in your config.yaml to configure: user: user pwd: secret """ -from __future__ import division, absolute_import, print_function import requests from beets import config @@ -34,7 +32,7 @@ import six def update_kodi(host, port, user, password): """Sends request to the Kodi api to start a library refresh. """ - url = "http://{0}:{1}/jsonrpc".format(host, port) + url = f"http://{host}:{port}/jsonrpc" """Content-Type: application/json is mandatory according to the kodi jsonrpc documentation""" @@ -54,14 +52,14 @@ def update_kodi(host, port, user, password): class KodiUpdate(BeetsPlugin): def __init__(self): - super(KodiUpdate, self).__init__() + super().__init__() # Adding defaults. config['kodi'].add({ - u'host': u'localhost', - u'port': 8080, - u'user': u'kodi', - u'pwd': u'kodi'}) + 'host': 'localhost', + 'port': 8080, + 'user': 'kodi', + 'pwd': 'kodi'}) config['kodi']['pwd'].redact = True self.register_listener('database_change', self.listen_for_db_change) @@ -73,7 +71,7 @@ class KodiUpdate(BeetsPlugin): def update(self, lib): """When the client exists try to send refresh request to Kodi server. """ - self._log.info(u'Requesting a Kodi library update...') + self._log.info('Requesting a Kodi library update...') # Try to send update request. try: @@ -85,14 +83,14 @@ class KodiUpdate(BeetsPlugin): r.raise_for_status() except requests.exceptions.RequestException as e: - self._log.warning(u'Kodi update failed: {0}', - six.text_type(e)) + self._log.warning('Kodi update failed: {0}', + str(e)) return json = r.json() if json.get('result') != 'OK': - self._log.warning(u'Kodi update failed: JSON response was {0!r}', + self._log.warning('Kodi update failed: JSON response was {0!r}', json) return - self._log.info(u'Kodi update triggered') + self._log.info('Kodi update triggered') diff --git a/beetsplug/lastgenre/__init__.py b/beetsplug/lastgenre/__init__.py index 59beddfa9..3a1e969cf 100644 --- a/beetsplug/lastgenre/__init__.py +++ b/beetsplug/lastgenre/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -13,7 +12,6 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -from __future__ import division, absolute_import, print_function import six @@ -47,7 +45,7 @@ PYLAST_EXCEPTIONS = ( ) REPLACE = { - u'\u2010': '-', + '\u2010': '-', } @@ -74,7 +72,7 @@ def flatten_tree(elem, path, branches): for sub in elem: flatten_tree(sub, path, branches) else: - branches.append(path + [six.text_type(elem)]) + branches.append(path + [str(elem)]) def find_parents(candidate, branches): @@ -98,7 +96,7 @@ C14N_TREE = os.path.join(os.path.dirname(__file__), 'genres-tree.yaml') class LastGenrePlugin(plugins.BeetsPlugin): def __init__(self): - super(LastGenrePlugin, self).__init__() + super().__init__() self.config.add({ 'whitelist': True, @@ -109,7 +107,7 @@ class LastGenrePlugin(plugins.BeetsPlugin): 'source': 'album', 'force': True, 'auto': True, - 'separator': u', ', + 'separator': ', ', 'prefer_specific': False, 'title_case': True, }) @@ -134,7 +132,7 @@ class LastGenrePlugin(plugins.BeetsPlugin): with open(wl_filename, 'rb') as f: for line in f: line = line.decode('utf-8').strip().lower() - if line and not line.startswith(u'#'): + if line and not line.startswith('#'): self.whitelist.add(line) # Read the genres tree for canonicalization if enabled. @@ -267,8 +265,8 @@ class LastGenrePlugin(plugins.BeetsPlugin): if any(not s for s in args): return None - key = u'{0}.{1}'.format(entity, - u'-'.join(six.text_type(a) for a in args)) + key = '{}.{}'.format(entity, + '-'.join(str(a) for a in args)) if key in self._genre_cache: return self._genre_cache[key] else: @@ -286,28 +284,28 @@ class LastGenrePlugin(plugins.BeetsPlugin): """Return the album genre for this Item or Album. """ return self._last_lookup( - u'album', LASTFM.get_album, obj.albumartist, obj.album + 'album', LASTFM.get_album, obj.albumartist, obj.album ) def fetch_album_artist_genre(self, obj): """Return the album artist genre for this Item or Album. """ return self._last_lookup( - u'artist', LASTFM.get_artist, obj.albumartist + 'artist', LASTFM.get_artist, obj.albumartist ) def fetch_artist_genre(self, item): """Returns the track artist genre for this Item. """ return self._last_lookup( - u'artist', LASTFM.get_artist, item.artist + 'artist', LASTFM.get_artist, item.artist ) def fetch_track_genre(self, obj): """Returns the track genre for this Item. """ return self._last_lookup( - u'track', LASTFM.get_track, obj.artist, obj.title + 'track', LASTFM.get_track, obj.artist, obj.title ) def _get_genre(self, obj): @@ -377,22 +375,22 @@ class LastGenrePlugin(plugins.BeetsPlugin): return None, None def commands(self): - lastgenre_cmd = ui.Subcommand('lastgenre', help=u'fetch genres') + lastgenre_cmd = ui.Subcommand('lastgenre', help='fetch genres') lastgenre_cmd.parser.add_option( - u'-f', u'--force', dest='force', + '-f', '--force', dest='force', action='store_true', - help=u're-download genre when already present' + help='re-download genre when already present' ) lastgenre_cmd.parser.add_option( - u'-s', u'--source', dest='source', type='string', - help=u'genre source: artist, album, or track' + '-s', '--source', dest='source', type='string', + help='genre source: artist, album, or track' ) lastgenre_cmd.parser.add_option( - u'-A', u'--items', action='store_false', dest='album', - help=u'match items instead of albums') + '-A', '--items', action='store_false', dest='album', + help='match items instead of albums') lastgenre_cmd.parser.add_option( - u'-a', u'--albums', action='store_true', dest='album', - help=u'match albums instead of items') + '-a', '--albums', action='store_true', dest='album', + help='match albums instead of items') lastgenre_cmd.parser.set_defaults(album=True) def lastgenre_func(lib, opts, args): @@ -403,7 +401,7 @@ class LastGenrePlugin(plugins.BeetsPlugin): # Fetch genres for whole albums for album in lib.albums(ui.decargs(args)): album.genre, src = self._get_genre(album) - self._log.info(u'genre for album {0} ({1}): {0.genre}', + self._log.info('genre for album {0} ({1}): {0.genre}', album, src) album.store() @@ -414,7 +412,7 @@ class LastGenrePlugin(plugins.BeetsPlugin): item.genre, src = self._get_genre(item) item.store() self._log.info( - u'genre for track {0} ({1}): {0.genre}', + 'genre for track {0} ({1}): {0.genre}', item, src) if write: @@ -424,7 +422,7 @@ class LastGenrePlugin(plugins.BeetsPlugin): # an album for item in lib.items(ui.decargs(args)): item.genre, src = self._get_genre(item) - self._log.debug(u'added last.fm item genre ({0}): {1}', + self._log.debug('added last.fm item genre ({0}): {1}', src, item.genre) item.store() @@ -436,21 +434,21 @@ class LastGenrePlugin(plugins.BeetsPlugin): if task.is_album: album = task.album album.genre, src = self._get_genre(album) - self._log.debug(u'added last.fm album genre ({0}): {1}', + self._log.debug('added last.fm album genre ({0}): {1}', src, album.genre) album.store() if 'track' in self.sources: for item in album.items(): item.genre, src = self._get_genre(item) - self._log.debug(u'added last.fm item genre ({0}): {1}', + self._log.debug('added last.fm item genre ({0}): {1}', src, item.genre) item.store() else: item = task.item item.genre, src = self._get_genre(item) - self._log.debug(u'added last.fm item genre ({0}): {1}', + self._log.debug('added last.fm item genre ({0}): {1}', src, item.genre) item.store() @@ -472,12 +470,12 @@ class LastGenrePlugin(plugins.BeetsPlugin): try: res = obj.get_top_tags() except PYLAST_EXCEPTIONS as exc: - self._log.debug(u'last.fm error: {0}', exc) + self._log.debug('last.fm error: {0}', exc) return [] except Exception as exc: # Isolate bugs in pylast. - self._log.debug(u'{}', traceback.format_exc()) - self._log.error(u'error in pylast library: {0}', exc) + self._log.debug('{}', traceback.format_exc()) + self._log.error('error in pylast library: {0}', exc) return [] # Filter by weight (optionally). diff --git a/beetsplug/lastimport.py b/beetsplug/lastimport.py index ca97004cf..16d53302e 100644 --- a/beetsplug/lastimport.py +++ b/beetsplug/lastimport.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Rafael Bodill https://github.com/rafi # @@ -13,7 +12,6 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -from __future__ import division, absolute_import, print_function import pylast from pylast import TopItem, _extract, _number @@ -28,7 +26,7 @@ API_URL = 'https://ws.audioscrobbler.com/2.0/' class LastImportPlugin(plugins.BeetsPlugin): def __init__(self): - super(LastImportPlugin, self).__init__() + super().__init__() config['lastfm'].add({ 'user': '', 'api_key': plugins.LASTFM_KEY, @@ -43,7 +41,7 @@ class LastImportPlugin(plugins.BeetsPlugin): } def commands(self): - cmd = ui.Subcommand('lastimport', help=u'import last.fm play-count') + cmd = ui.Subcommand('lastimport', help='import last.fm play-count') def func(lib, opts, args): import_lastfm(lib, self._log) @@ -59,7 +57,7 @@ class CustomUser(pylast.User): tracks. """ def __init__(self, *args, **kwargs): - super(CustomUser, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def _get_things(self, method, thing, thing_type, params=None, cacheable=True): @@ -114,9 +112,9 @@ def import_lastfm(lib, log): per_page = config['lastimport']['per_page'].get(int) if not user: - raise ui.UserError(u'You must specify a user name for lastimport') + raise ui.UserError('You must specify a user name for lastimport') - log.info(u'Fetching last.fm library for @{0}', user) + log.info('Fetching last.fm library for @{0}', user) page_total = 1 page_current = 0 @@ -125,15 +123,15 @@ def import_lastfm(lib, log): retry_limit = config['lastimport']['retry_limit'].get(int) # Iterate through a yet to be known page total count while page_current < page_total: - log.info(u'Querying page #{0}{1}...', + log.info('Querying page #{0}{1}...', page_current + 1, - '/{}'.format(page_total) if page_total > 1 else '') + f'/{page_total}' if page_total > 1 else '') for retry in range(0, retry_limit): tracks, page_total = fetch_tracks(user, page_current + 1, per_page) if page_total < 1: # It means nothing to us! - raise ui.UserError(u'Last.fm reported no data.') + raise ui.UserError('Last.fm reported no data.') if tracks: found, unknown = process_tracks(lib, tracks, log) @@ -141,22 +139,22 @@ def import_lastfm(lib, log): unknown_total += unknown break else: - log.error(u'ERROR: unable to read page #{0}', + log.error('ERROR: unable to read page #{0}', page_current + 1) if retry < retry_limit: log.info( - u'Retrying page #{0}... ({1}/{2} retry)', + 'Retrying page #{0}... ({1}/{2} retry)', page_current + 1, retry + 1, retry_limit ) else: - log.error(u'FAIL: unable to fetch page #{0}, ', - u'tried {1} times', page_current, retry + 1) + log.error('FAIL: unable to fetch page #{0}, ', + 'tried {1} times', page_current, retry + 1) page_current += 1 - log.info(u'... done!') - log.info(u'finished processing {0} song pages', page_total) - log.info(u'{0} unknown play-counts', unknown_total) - log.info(u'{0} play-counts imported', found_total) + log.info('... done!') + log.info('finished processing {0} song pages', page_total) + log.info('{0} unknown play-counts', unknown_total) + log.info('{0} play-counts imported', found_total) def fetch_tracks(user, page, limit): @@ -190,7 +188,7 @@ def process_tracks(lib, tracks, log): total = len(tracks) total_found = 0 total_fails = 0 - log.info(u'Received {0} tracks in this page, processing...', total) + log.info('Received {0} tracks in this page, processing...', total) for num in range(0, total): song = None @@ -201,7 +199,7 @@ def process_tracks(lib, tracks, log): if 'album' in tracks[num]: album = tracks[num]['album'].get('name', '').strip() - log.debug(u'query: {0} - {1} ({2})', artist, title, album) + log.debug('query: {0} - {1} ({2})', artist, title, album) # First try to query by musicbrainz's trackid if trackid: @@ -211,7 +209,7 @@ def process_tracks(lib, tracks, log): # If not, try just artist/title if song is None: - log.debug(u'no album match, trying by artist/title') + log.debug('no album match, trying by artist/title') query = dbcore.AndQuery([ dbcore.query.SubstringQuery('artist', artist), dbcore.query.SubstringQuery('title', title) @@ -220,8 +218,8 @@ def process_tracks(lib, tracks, log): # Last resort, try just replacing to utf-8 quote if song is None: - title = title.replace("'", u'\u2019') - log.debug(u'no title match, trying utf-8 single quote') + title = title.replace("'", '\u2019') + log.debug('no title match, trying utf-8 single quote') query = dbcore.AndQuery([ dbcore.query.SubstringQuery('artist', artist), dbcore.query.SubstringQuery('title', title) @@ -231,19 +229,19 @@ def process_tracks(lib, tracks, log): if song is not None: count = int(song.get('play_count', 0)) new_count = int(tracks[num]['playcount']) - log.debug(u'match: {0} - {1} ({2}) ' - u'updating: play_count {3} => {4}', + log.debug('match: {0} - {1} ({2}) ' + 'updating: play_count {3} => {4}', song.artist, song.title, song.album, count, new_count) song['play_count'] = new_count song.store() total_found += 1 else: total_fails += 1 - log.info(u' - No match: {0} - {1} ({2})', + log.info(' - No match: {0} - {1} ({2})', artist, title, album) if total_fails > 0: - log.info(u'Acquired {0}/{1} play-counts ({2} unknown)', + log.info('Acquired {0}/{1} play-counts ({2} unknown)', total_found, total, total_fails) return total_found, total_fails diff --git a/beetsplug/loadext.py b/beetsplug/loadext.py index 5ab98bd59..191b97a25 100644 --- a/beetsplug/loadext.py +++ b/beetsplug/loadext.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2019, Jack Wilsdon # @@ -16,7 +15,6 @@ """Load SQLite extensions. """ -from __future__ import division, absolute_import, print_function from beets.dbcore import Database from beets.plugins import BeetsPlugin @@ -25,7 +23,7 @@ import sqlite3 class LoadExtPlugin(BeetsPlugin): def __init__(self): - super(LoadExtPlugin, self).__init__() + super().__init__() if not Database.supports_extensions: self._log.warn('loadext is enabled but the current SQLite ' @@ -38,9 +36,9 @@ class LoadExtPlugin(BeetsPlugin): for v in self.config: ext = v.as_filename() - self._log.debug(u'loading extension {}', ext) + self._log.debug('loading extension {}', ext) try: lib.load_extension(ext) except sqlite3.OperationalError as e: - self._log.error(u'failed to load extension {}: {}', ext, e) + self._log.error('failed to load extension {}: {}', ext, e) diff --git a/beetsplug/lyrics.py b/beetsplug/lyrics.py index 93c31d938..9c3155e93 100644 --- a/beetsplug/lyrics.py +++ b/beetsplug/lyrics.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -16,7 +15,6 @@ """Fetches, embeds, and displays lyrics. """ -from __future__ import absolute_import, division, print_function import difflib import errno @@ -63,23 +61,23 @@ COMMENT_RE = re.compile(r'', re.S) TAG_RE = re.compile(r'<[^>]*>') BREAK_RE = re.compile(r'\n?\s*]*)*>\s*\n?', re.I) URL_CHARACTERS = { - u'\u2018': u"'", - u'\u2019': u"'", - u'\u201c': u'"', - u'\u201d': u'"', - u'\u2010': u'-', - u'\u2011': u'-', - u'\u2012': u'-', - u'\u2013': u'-', - u'\u2014': u'-', - u'\u2015': u'-', - u'\u2016': u'-', - u'\u2026': u'...', + '\u2018': "'", + '\u2019': "'", + '\u201c': '"', + '\u201d': '"', + '\u2010': '-', + '\u2011': '-', + '\u2012': '-', + '\u2013': '-', + '\u2014': '-', + '\u2015': '-', + '\u2016': '-', + '\u2026': '...', } -USER_AGENT = 'beets/{}'.format(beets.__version__) +USER_AGENT = f'beets/{beets.__version__}' # The content for the base index.rst generated in ReST mode. -REST_INDEX_TEMPLATE = u'''Lyrics +REST_INDEX_TEMPLATE = '''Lyrics ====== * :ref:`Song index ` @@ -95,7 +93,7 @@ Artist index: ''' # The content for the base conf.py generated. -REST_CONF_TEMPLATE = u'''# -*- coding: utf-8 -*- +REST_CONF_TEMPLATE = '''# -*- coding: utf-8 -*- master_doc = 'index' project = u'Lyrics' copyright = u'none' @@ -118,7 +116,7 @@ epub_tocdup = False def unichar(i): try: - return six.unichr(i) + return chr(i) except ValueError: return struct.pack('i', i).decode('utf-32') @@ -127,12 +125,12 @@ def unescape(text): """Resolve &#xxx; HTML entities (and some others).""" if isinstance(text, bytes): text = text.decode('utf-8', 'ignore') - out = text.replace(u' ', u' ') + out = text.replace(' ', ' ') def replchar(m): num = m.group(1) return unichar(int(num)) - out = re.sub(u"&#(\\d+);", replchar, out) + out = re.sub("&#(\\d+);", replchar, out) return out @@ -141,7 +139,7 @@ def extract_text_between(html, start_marker, end_marker): _, html = html.split(start_marker, 1) html, _ = html.split(end_marker, 1) except ValueError: - return u'' + return '' return html @@ -174,7 +172,7 @@ def search_pairs(item): patterns = [ # Remove any featuring artists from the artists name - r"(.*?) {0}".format(plugins.feat_tokens())] + fr"(.*?) {plugins.feat_tokens()}"] artists = generate_alternatives(artist, patterns) # Use the artist_sort as fallback only if it differs from artist to avoid # repeated remote requests with the same search terms @@ -186,7 +184,7 @@ def search_pairs(item): # examples include (live), (remix), and (acoustic). r"(.+?)\s+[(].*[)]$", # Remove any featuring artists from the title - r"(.*?) {0}".format(plugins.feat_tokens(for_artist=False)), + r"(.*?) {}".format(plugins.feat_tokens(for_artist=False)), # Remove part of title after colon ':' for songs with subtitles r"(.+?)\s*:.*"] titles = generate_alternatives(title, patterns) @@ -231,7 +229,7 @@ else: return None -class Backend(object): +class Backend: REQUIRES_BS = False def __init__(self, config, log): @@ -240,7 +238,7 @@ class Backend(object): @staticmethod def _encode(s): """Encode the string for inclusion in a URL""" - if isinstance(s, six.text_type): + if isinstance(s, str): for char, repl in URL_CHARACTERS.items(): s = s.replace(char, repl) s = s.encode('utf-8', 'ignore') @@ -265,12 +263,12 @@ class Backend(object): 'User-Agent': USER_AGENT, }) except requests.RequestException as exc: - self._log.debug(u'lyrics request failed: {0}', exc) + self._log.debug('lyrics request failed: {0}', exc) return if r.status_code == requests.codes.ok: return r.text else: - self._log.debug(u'failed to fetch: {0} ({1})', url, r.status_code) + self._log.debug('failed to fetch: {0} ({1})', url, r.status_code) return None def fetch(self, artist, title): @@ -294,7 +292,7 @@ class MusiXmatch(Backend): for old, new in cls.REPLACEMENTS.items(): s = re.sub(old, new, s) - return super(MusiXmatch, cls)._encode(s) + return super()._encode(s) def fetch(self, artist, title): url = self.build_url(artist, title) @@ -303,7 +301,7 @@ class MusiXmatch(Backend): if not html: return None if "We detected that your IP is blocked" in html: - self._log.warning(u'we are blocked at MusixMatch: url %s failed' + self._log.warning('we are blocked at MusixMatch: url %s failed' % url) return None html_parts = html.split('

eats up surrounding '\n'. html = re.sub(r'(?s)<(script).*?', '', html) # Strip script tags. - html = re.sub(u'\u2005', " ", html) # replace unicode with regular space + html = re.sub('\u2005', " ", html) # replace unicode with regular space if plain_text_out: # Strip remaining HTML tags html = COMMENT_RE.sub('', html) @@ -568,7 +566,7 @@ class Google(Backend): REQUIRES_BS = True def __init__(self, config, log): - super(Google, self).__init__(config, log) + super().__init__(config, log) self.api_key = config['google_API_key'].as_str() self.engine_id = config['google_engine_ID'].as_str() @@ -580,7 +578,7 @@ class Google(Backend): bad_triggers_occ = [] nb_lines = text.count('\n') if nb_lines <= 1: - self._log.debug(u"Ignoring too short lyrics '{0}'", text) + self._log.debug("Ignoring too short lyrics '{0}'", text) return False elif nb_lines < 5: bad_triggers_occ.append('too_short') @@ -598,7 +596,7 @@ class Google(Backend): text, re.I)) if bad_triggers_occ: - self._log.debug(u'Bad triggers detected: {0}', bad_triggers_occ) + self._log.debug('Bad triggers detected: {0}', bad_triggers_occ) return len(bad_triggers_occ) < 2 def slugify(self, text): @@ -611,9 +609,9 @@ class Google(Backend): try: text = unicodedata.normalize('NFKD', text).encode('ascii', 'ignore') - text = six.text_type(re.sub(r'[-\s]+', ' ', text.decode('utf-8'))) + text = str(re.sub(r'[-\s]+', ' ', text.decode('utf-8'))) except UnicodeDecodeError: - self._log.exception(u"Failing to normalize '{0}'", text) + self._log.exception("Failing to normalize '{0}'", text) return text BY_TRANS = ['by', 'par', 'de', 'von'] @@ -625,7 +623,7 @@ class Google(Backend): """ title = self.slugify(title.lower()) artist = self.slugify(artist.lower()) - sitename = re.search(u"//([^/]+)/.*", + sitename = re.search("//([^/]+)/.*", self.slugify(url_link.lower())).group(1) url_title = self.slugify(url_title.lower()) @@ -639,7 +637,7 @@ class Google(Backend): [artist, sitename, sitename.replace('www.', '')] + \ self.LYRICS_TRANS tokens = [re.escape(t) for t in tokens] - song_title = re.sub(u'(%s)' % u'|'.join(tokens), u'', url_title) + song_title = re.sub('(%s)' % '|'.join(tokens), '', url_title) song_title = song_title.strip('_|') typo_ratio = .9 @@ -647,28 +645,28 @@ class Google(Backend): return ratio >= typo_ratio def fetch(self, artist, title): - query = u"%s %s" % (artist, title) - url = u'https://www.googleapis.com/customsearch/v1?key=%s&cx=%s&q=%s' \ + query = f"{artist} {title}" + url = 'https://www.googleapis.com/customsearch/v1?key=%s&cx=%s&q=%s' \ % (self.api_key, self.engine_id, urllib.parse.quote(query.encode('utf-8'))) data = self.fetch_url(url) if not data: - self._log.debug(u'google backend returned no data') + self._log.debug('google backend returned no data') return None try: data = json.loads(data) except ValueError as exc: - self._log.debug(u'google backend returned malformed JSON: {}', exc) + self._log.debug('google backend returned malformed JSON: {}', exc) if 'error' in data: reason = data['error']['errors'][0]['reason'] - self._log.debug(u'google backend error: {0}', reason) + self._log.debug('google backend error: {0}', reason) return None if 'items' in data.keys(): for item in data['items']: url_link = item['link'] - url_title = item.get('title', u'') + url_title = item.get('title', '') if not self.is_page_candidate(url_link, url_title, title, artist): continue @@ -680,7 +678,7 @@ class Google(Backend): continue if self.is_lyrics(lyrics, artist): - self._log.debug(u'got lyrics from {0}', + self._log.debug('got lyrics from {0}', item['displayLink']) return lyrics @@ -697,7 +695,7 @@ class LyricsPlugin(plugins.BeetsPlugin): } def __init__(self): - super(LyricsPlugin, self).__init__() + super().__init__() self.import_stages = [self.imported] self.config.add({ 'auto': True, @@ -705,7 +703,7 @@ class LyricsPlugin(plugins.BeetsPlugin): 'bing_lang_from': [], 'bing_lang_to': None, 'google_API_key': None, - 'google_engine_ID': u'009217259823014548361:lndtuqkycfu', + 'google_engine_ID': '009217259823014548361:lndtuqkycfu', 'genius_api_key': "Ryq93pUGm8bM6eUWwD_M3NOFFDAtp2yEE7W" "76V-uFL5jks5dNvcGCdarqFjDhP9c", @@ -721,7 +719,7 @@ class LyricsPlugin(plugins.BeetsPlugin): # State information for the ReST writer. # First, the current artist we're writing. - self.artist = u'Unknown artist' + self.artist = 'Unknown artist' # The current album: False means no album yet. self.album = False # The current rest file content. None means the file is not @@ -741,8 +739,8 @@ class LyricsPlugin(plugins.BeetsPlugin): # configuration includes `google`. This way, the source # is silent by default but can be enabled just by # setting an API key. - self._log.debug(u'Disabling google source: ' - u'no API key configured.') + self._log.debug('Disabling google source: ' + 'no API key configured.') sources.remove('google') self.config['bing_lang_from'] = [ @@ -750,9 +748,9 @@ class LyricsPlugin(plugins.BeetsPlugin): self.bing_auth_token = None if not HAS_LANGDETECT and self.config['bing_client_secret'].get(): - self._log.warning(u'To use bing translations, you need to ' - u'install the langdetect module. See the ' - u'documentation for further details.') + self._log.warning('To use bing translations, you need to ' + 'install the langdetect module. See the ' + 'documentation for further details.') self.backends = [self.SOURCE_BACKENDS[source](self.config, self._log) for source in sources] @@ -761,9 +759,9 @@ class LyricsPlugin(plugins.BeetsPlugin): enabled_sources = [] for source in sources: if source.REQUIRES_BS: - self._log.debug(u'To use the %s lyrics source, you must ' - u'install the beautifulsoup4 module. See ' - u'the documentation for further details.' + self._log.debug('To use the %s lyrics source, you must ' + 'install the beautifulsoup4 module. See ' + 'the documentation for further details.' % source) else: enabled_sources.append(source) @@ -785,30 +783,30 @@ class LyricsPlugin(plugins.BeetsPlugin): if 'access_token' in oauth_token: return "Bearer " + oauth_token['access_token'] else: - self._log.warning(u'Could not get Bing Translate API access token.' - u' Check your "bing_client_secret" password') + self._log.warning('Could not get Bing Translate API access token.' + ' Check your "bing_client_secret" password') def commands(self): cmd = ui.Subcommand('lyrics', help='fetch song lyrics') cmd.parser.add_option( - u'-p', u'--print', dest='printlyr', + '-p', '--print', dest='printlyr', action='store_true', default=False, - help=u'print lyrics to console', + help='print lyrics to console', ) cmd.parser.add_option( - u'-r', u'--write-rest', dest='writerest', + '-r', '--write-rest', dest='writerest', action='store', default=None, metavar='dir', - help=u'write lyrics to given directory as ReST files', + help='write lyrics to given directory as ReST files', ) cmd.parser.add_option( - u'-f', u'--force', dest='force_refetch', + '-f', '--force', dest='force_refetch', action='store_true', default=False, - help=u'always re-download lyrics', + help='always re-download lyrics', ) cmd.parser.add_option( - u'-l', u'--local', dest='local_only', + '-l', '--local', dest='local_only', action='store_true', default=False, - help=u'do not fetch missing lyrics', + help='do not fetch missing lyrics', ) def func(lib, opts, args): @@ -832,13 +830,13 @@ class LyricsPlugin(plugins.BeetsPlugin): if opts.writerest and items: # flush last artist & write to ReST self.writerest(opts.writerest) - ui.print_(u'ReST files generated. to build, use one of:') - ui.print_(u' sphinx-build -b html %s _build/html' + ui.print_('ReST files generated. to build, use one of:') + ui.print_(' sphinx-build -b html %s _build/html' % opts.writerest) - ui.print_(u' sphinx-build -b epub %s _build/epub' + ui.print_(' sphinx-build -b epub %s _build/epub' % opts.writerest) - ui.print_((u' sphinx-build -b latex %s _build/latex ' - u'&& make -C _build/latex all-pdf') + ui.print_((' sphinx-build -b latex %s _build/latex ' + '&& make -C _build/latex all-pdf') % opts.writerest) cmd.func = func return [cmd] @@ -854,19 +852,19 @@ class LyricsPlugin(plugins.BeetsPlugin): # Write current file and start a new one ~ item.albumartist self.writerest(directory) self.artist = item.albumartist.strip() - self.rest = u"%s\n%s\n\n.. contents::\n :local:\n\n" \ + self.rest = "%s\n%s\n\n.. contents::\n :local:\n\n" \ % (self.artist, - u'=' * len(self.artist)) + '=' * len(self.artist)) if self.album != item.album: tmpalbum = self.album = item.album.strip() if self.album == '': - tmpalbum = u'Unknown album' - self.rest += u"%s\n%s\n\n" % (tmpalbum, u'-' * len(tmpalbum)) - title_str = u":index:`%s`" % item.title.strip() - block = u'| ' + item.lyrics.replace(u'\n', u'\n| ') - self.rest += u"%s\n%s\n\n%s\n\n" % (title_str, - u'~' * len(title_str), + tmpalbum = 'Unknown album' + self.rest += "{}\n{}\n\n".format(tmpalbum, '-' * len(tmpalbum)) + title_str = ":index:`%s`" % item.title.strip() + block = '| ' + item.lyrics.replace('\n', '\n| ') + self.rest += "{}\n{}\n\n{}\n\n".format(title_str, + '~' * len(title_str), block) def writerest(self, directory): @@ -874,7 +872,7 @@ class LyricsPlugin(plugins.BeetsPlugin): """ if self.rest is not None and self.artist is not None: path = os.path.join(directory, 'artists', - slug(self.artist) + u'.rst') + slug(self.artist) + '.rst') with open(path, 'wb') as output: output.write(self.rest.encode('utf-8')) @@ -914,7 +912,7 @@ class LyricsPlugin(plugins.BeetsPlugin): """ # Skip if the item already has lyrics. if not force and item.lyrics: - self._log.info(u'lyrics already present: {0}', item) + self._log.info('lyrics already present: {0}', item) return lyrics = None @@ -923,10 +921,10 @@ class LyricsPlugin(plugins.BeetsPlugin): if any(lyrics): break - lyrics = u"\n\n---\n\n".join([l for l in lyrics if l]) + lyrics = "\n\n---\n\n".join([l for l in lyrics if l]) if lyrics: - self._log.info(u'fetched lyrics: {0}', item) + self._log.info('fetched lyrics: {0}', item) if HAS_LANGDETECT and self.config['bing_client_secret'].get(): lang_from = langdetect.detect(lyrics) if self.config['bing_lang_to'].get() != lang_from and ( @@ -936,7 +934,7 @@ class LyricsPlugin(plugins.BeetsPlugin): lyrics = self.append_translation( lyrics, self.config['bing_lang_to']) else: - self._log.info(u'lyrics not found: {0}', item) + self._log.info('lyrics not found: {0}', item) fallback = self.config['fallback'].get() if fallback: lyrics = fallback @@ -954,7 +952,7 @@ class LyricsPlugin(plugins.BeetsPlugin): for backend in self.backends: lyrics = backend.fetch(artist, title) if lyrics: - self._log.debug(u'got lyrics from backend: {0}', + self._log.debug('got lyrics from backend: {0}', backend.__class__.__name__) return _scrape_strip_cruft(lyrics, True) @@ -983,5 +981,5 @@ class LyricsPlugin(plugins.BeetsPlugin): translations = dict(zip(text_lines, lines_translated.split('|'))) result = '' for line in text.split('\n'): - result += '%s / %s\n' % (line, translations[line]) + result += '{} / {}\n'.format(line, translations[line]) return result diff --git a/beetsplug/mbcollection.py b/beetsplug/mbcollection.py index d99c386c9..f4a0d161d 100644 --- a/beetsplug/mbcollection.py +++ b/beetsplug/mbcollection.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright (c) 2011, Jeffrey Aylesworth # @@ -13,7 +12,6 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -from __future__ import division, absolute_import, print_function from beets.plugins import BeetsPlugin from beets.ui import Subcommand @@ -34,11 +32,11 @@ def mb_call(func, *args, **kwargs): try: return func(*args, **kwargs) except musicbrainzngs.AuthenticationError: - raise ui.UserError(u'authentication with MusicBrainz failed') + raise ui.UserError('authentication with MusicBrainz failed') except (musicbrainzngs.ResponseError, musicbrainzngs.NetworkError) as exc: - raise ui.UserError(u'MusicBrainz API error: {0}'.format(exc)) + raise ui.UserError(f'MusicBrainz API error: {exc}') except musicbrainzngs.UsageError: - raise ui.UserError(u'MusicBrainz credentials missing') + raise ui.UserError('MusicBrainz credentials missing') def submit_albums(collection_id, release_ids): @@ -55,7 +53,7 @@ def submit_albums(collection_id, release_ids): class MusicBrainzCollectionPlugin(BeetsPlugin): def __init__(self): - super(MusicBrainzCollectionPlugin, self).__init__() + super().__init__() config['musicbrainz']['pass'].redact = True musicbrainzngs.auth( config['musicbrainz']['user'].as_str(), @@ -63,7 +61,7 @@ class MusicBrainzCollectionPlugin(BeetsPlugin): ) self.config.add({ 'auto': False, - 'collection': u'', + 'collection': '', 'remove': False, }) if self.config['auto']: @@ -72,18 +70,18 @@ class MusicBrainzCollectionPlugin(BeetsPlugin): def _get_collection(self): collections = mb_call(musicbrainzngs.get_collections) if not collections['collection-list']: - raise ui.UserError(u'no collections exist for user') + raise ui.UserError('no collections exist for user') # Get all collection IDs, avoiding event collections collection_ids = [x['id'] for x in collections['collection-list']] if not collection_ids: - raise ui.UserError(u'No collection found.') + raise ui.UserError('No collection found.') # Check that the collection exists so we can present a nice error collection = self.config['collection'].as_str() if collection: if collection not in collection_ids: - raise ui.UserError(u'invalid collection ID: {}' + raise ui.UserError('invalid collection ID: {}' .format(collection)) return collection @@ -110,7 +108,7 @@ class MusicBrainzCollectionPlugin(BeetsPlugin): def commands(self): mbupdate = Subcommand('mbupdate', - help=u'Update MusicBrainz collection') + help='Update MusicBrainz collection') mbupdate.parser.add_option('-r', '--remove', action='store_true', default=None, @@ -120,7 +118,7 @@ class MusicBrainzCollectionPlugin(BeetsPlugin): return [mbupdate] def remove_missing(self, collection_id, lib_albums): - lib_ids = set([x.mb_albumid for x in lib_albums]) + lib_ids = {x.mb_albumid for x in lib_albums} albums_in_collection = self._get_albums_in_collection(collection_id) remove_me = list(set(albums_in_collection) - lib_ids) for i in range(0, len(remove_me), FETCH_CHUNK_SIZE): @@ -154,13 +152,13 @@ class MusicBrainzCollectionPlugin(BeetsPlugin): if re.match(UUID_REGEX, aid): album_ids.append(aid) else: - self._log.info(u'skipping invalid MBID: {0}', aid) + self._log.info('skipping invalid MBID: {0}', aid) # Submit to MusicBrainz. self._log.info( - u'Updating MusicBrainz collection {0}...', collection_id + 'Updating MusicBrainz collection {0}...', collection_id ) submit_albums(collection_id, album_ids) if remove_missing: self.remove_missing(collection_id, lib.albums()) - self._log.info(u'...MusicBrainz collection updated.') + self._log.info('...MusicBrainz collection updated.') diff --git a/beetsplug/mbsubmit.py b/beetsplug/mbsubmit.py index 44a476d15..6d21d9b59 100644 --- a/beetsplug/mbsubmit.py +++ b/beetsplug/mbsubmit.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson and Diego Moreda. # @@ -22,7 +21,6 @@ implemented by MusicBrainz yet. [1] https://wiki.musicbrainz.org/History:How_To_Parse_Track_Listings """ -from __future__ import division, absolute_import, print_function from beets.autotag import Recommendation @@ -33,10 +31,10 @@ from beetsplug.info import print_data class MBSubmitPlugin(BeetsPlugin): def __init__(self): - super(MBSubmitPlugin, self).__init__() + super().__init__() self.config.add({ - 'format': u'$track. $title - $artist ($length)', + 'format': '$track. $title - $artist ($length)', 'threshold': 'medium', }) @@ -53,7 +51,7 @@ class MBSubmitPlugin(BeetsPlugin): def before_choose_candidate_event(self, session, task): if task.rec <= self.threshold: - return [PromptChoice(u'p', u'Print tracks', self.print_tracks)] + return [PromptChoice('p', 'Print tracks', self.print_tracks)] def print_tracks(self, session, task): for i in sorted(task.items, key=lambda i: i.track): diff --git a/beetsplug/mbsync.py b/beetsplug/mbsync.py index ee2c4b5bd..267788307 100644 --- a/beetsplug/mbsync.py +++ b/beetsplug/mbsync.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Jakob Schnitzer. # @@ -15,7 +14,6 @@ """Update library's tags using MusicBrainz. """ -from __future__ import division, absolute_import, print_function from beets.plugins import BeetsPlugin, apply_item_changes from beets import autotag, library, ui, util @@ -29,24 +27,24 @@ MBID_REGEX = r"(\d|\w){8}-(\d|\w){4}-(\d|\w){4}-(\d|\w){4}-(\d|\w){12}" class MBSyncPlugin(BeetsPlugin): def __init__(self): - super(MBSyncPlugin, self).__init__() + super().__init__() def commands(self): cmd = ui.Subcommand('mbsync', - help=u'update metadata from musicbrainz') + help='update metadata from musicbrainz') cmd.parser.add_option( - u'-p', u'--pretend', action='store_true', - help=u'show all changes but do nothing') + '-p', '--pretend', action='store_true', + help='show all changes but do nothing') cmd.parser.add_option( - u'-m', u'--move', action='store_true', dest='move', - help=u"move files in the library directory") + '-m', '--move', action='store_true', dest='move', + help="move files in the library directory") cmd.parser.add_option( - u'-M', u'--nomove', action='store_false', dest='move', - help=u"don't move files in library") + '-M', '--nomove', action='store_false', dest='move', + help="don't move files in library") cmd.parser.add_option( - u'-W', u'--nowrite', action='store_false', + '-W', '--nowrite', action='store_false', default=None, dest='write', - help=u"don't write updated metadata to files") + help="don't write updated metadata to files") cmd.parser.add_format_option() cmd.func = self.func return [cmd] @@ -66,23 +64,23 @@ class MBSyncPlugin(BeetsPlugin): """Retrieve and apply info from the autotagger for items matched by query. """ - for item in lib.items(query + [u'singleton:true']): + for item in lib.items(query + ['singleton:true']): item_formatted = format(item) if not item.mb_trackid: - self._log.info(u'Skipping singleton with no mb_trackid: {0}', + self._log.info('Skipping singleton with no mb_trackid: {0}', item_formatted) continue # Do we have a valid MusicBrainz track ID? if not re.match(MBID_REGEX, item.mb_trackid): - self._log.info(u'Skipping singleton with invalid mb_trackid:' + + self._log.info('Skipping singleton with invalid mb_trackid:' + ' {0}', item_formatted) continue # Get the MusicBrainz recording info. track_info = hooks.track_for_mbid(item.mb_trackid) if not track_info: - self._log.info(u'Recording ID not found: {0} for track {0}', + self._log.info('Recording ID not found: {0} for track {0}', item.mb_trackid, item_formatted) continue @@ -100,7 +98,7 @@ class MBSyncPlugin(BeetsPlugin): for a in lib.albums(query): album_formatted = format(a) if not a.mb_albumid: - self._log.info(u'Skipping album with no mb_albumid: {0}', + self._log.info('Skipping album with no mb_albumid: {0}', album_formatted) continue @@ -108,14 +106,14 @@ class MBSyncPlugin(BeetsPlugin): # Do we have a valid MusicBrainz album ID? if not re.match(MBID_REGEX, a.mb_albumid): - self._log.info(u'Skipping album with invalid mb_albumid: {0}', + self._log.info('Skipping album with invalid mb_albumid: {0}', album_formatted) continue # Get the MusicBrainz album information. album_info = hooks.album_for_mbid(a.mb_albumid) if not album_info: - self._log.info(u'Release ID {0} not found for album {1}', + self._log.info('Release ID {0} not found for album {1}', a.mb_albumid, album_formatted) continue @@ -151,7 +149,7 @@ class MBSyncPlugin(BeetsPlugin): break # Apply. - self._log.debug(u'applying changes to {}', album_formatted) + self._log.debug('applying changes to {}', album_formatted) with lib.transaction(): autotag.apply_metadata(album_info, mapping) changed = False @@ -176,5 +174,5 @@ class MBSyncPlugin(BeetsPlugin): # Move album art (and any inconsistent items). if move and lib.directory in util.ancestry(items[0].path): - self._log.debug(u'moving album {0}', album_formatted) + self._log.debug('moving album {0}', album_formatted) a.move() diff --git a/beetsplug/metasync/__init__.py b/beetsplug/metasync/__init__.py index 943dbac1f..c2f1d50d7 100644 --- a/beetsplug/metasync/__init__.py +++ b/beetsplug/metasync/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Heinz Wiesinger. # @@ -16,7 +15,6 @@ """Synchronize information from music player libraries """ -from __future__ import division, absolute_import, print_function from abc import abstractmethod, ABCMeta from importlib import import_module @@ -36,7 +34,7 @@ SOURCES = { } -class MetaSource(six.with_metaclass(ABCMeta, object)): +class MetaSource(metaclass=ABCMeta): def __init__(self, config, log): self.item_types = {} self.config = config @@ -77,7 +75,7 @@ class MetaSyncPlugin(BeetsPlugin): item_types = load_item_types() def __init__(self): - super(MetaSyncPlugin, self).__init__() + super().__init__() def commands(self): cmd = ui.Subcommand('metasync', @@ -108,7 +106,7 @@ class MetaSyncPlugin(BeetsPlugin): # Avoid needlessly instantiating meta sources (can be expensive) if not items: - self._log.info(u'No items found matching query') + self._log.info('No items found matching query') return # Instantiate the meta sources @@ -116,18 +114,18 @@ class MetaSyncPlugin(BeetsPlugin): try: cls = META_SOURCES[player] except KeyError: - self._log.error(u'Unknown metadata source \'{0}\''.format( + self._log.error('Unknown metadata source \'{}\''.format( player)) try: meta_source_instances[player] = cls(self.config, self._log) except (ImportError, ConfigValueError) as e: - self._log.error(u'Failed to instantiate metadata source ' - u'\'{0}\': {1}'.format(player, e)) + self._log.error('Failed to instantiate metadata source ' + '\'{}\': {}'.format(player, e)) # Avoid needlessly iterating over items if not meta_source_instances: - self._log.error(u'No valid metadata sources found') + self._log.error('No valid metadata sources found') return # Sync the items with all of the meta sources diff --git a/beetsplug/metasync/amarok.py b/beetsplug/metasync/amarok.py index 1d28eb3c9..a49eecc30 100644 --- a/beetsplug/metasync/amarok.py +++ b/beetsplug/metasync/amarok.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Heinz Wiesinger. # @@ -16,7 +15,6 @@ """Synchronize information from amarok's library via dbus """ -from __future__ import division, absolute_import, print_function from os.path import basename from datetime import datetime @@ -49,14 +47,14 @@ class Amarok(MetaSource): 'amarok_lastplayed': DateType(), } - query_xml = u' \ + query_xml = ' \ \ \ \ ' def __init__(self, config, log): - super(Amarok, self).__init__(config, log) + super().__init__(config, log) if not dbus: raise ImportError('failed to import dbus') diff --git a/beetsplug/metasync/itunes.py b/beetsplug/metasync/itunes.py index c36bebc8f..7ed940736 100644 --- a/beetsplug/metasync/itunes.py +++ b/beetsplug/metasync/itunes.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Tom Jaspers. # @@ -16,7 +15,6 @@ """Synchronize information from iTunes's library """ -from __future__ import division, absolute_import, print_function from contextlib import contextmanager import os @@ -72,7 +70,7 @@ class Itunes(MetaSource): } def __init__(self, config, log): - super(Itunes, self).__init__(config, log) + super().__init__(config, log) config.add({'itunes': { 'library': '~/Music/iTunes/iTunes Library.xml' @@ -83,20 +81,20 @@ class Itunes(MetaSource): try: self._log.debug( - u'loading iTunes library from {0}'.format(library_path)) + f'loading iTunes library from {library_path}') with create_temporary_copy(library_path) as library_copy: with open(library_copy, 'rb') as library_copy_f: raw_library = plistlib.load(library_copy_f) - except IOError as e: - raise ConfigValueError(u'invalid iTunes library: ' + e.strerror) + except OSError as e: + raise ConfigValueError('invalid iTunes library: ' + e.strerror) except Exception: # It's likely the user configured their '.itl' library (<> xml) if os.path.splitext(library_path)[1].lower() != '.xml': - hint = u': please ensure that the configured path' \ - u' points to the .XML library' + hint = ': please ensure that the configured path' \ + ' points to the .XML library' else: hint = '' - raise ConfigValueError(u'invalid iTunes library' + hint) + raise ConfigValueError('invalid iTunes library' + hint) # Make the iTunes library queryable using the path self.collection = {_norm_itunes_path(track['Location']): track @@ -107,7 +105,7 @@ class Itunes(MetaSource): result = self.collection.get(util.bytestring_path(item.path).lower()) if not result: - self._log.warning(u'no iTunes match found for {0}'.format(item)) + self._log.warning(f'no iTunes match found for {item}') return item.itunes_rating = result.get('Rating') diff --git a/beetsplug/missing.py b/beetsplug/missing.py index 247ccc999..771978c1b 100644 --- a/beetsplug/missing.py +++ b/beetsplug/missing.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Pedro Silva. # Copyright 2017, Quentin Young. @@ -16,7 +15,6 @@ """List missing tracks. """ -from __future__ import division, absolute_import, print_function import musicbrainzngs @@ -93,7 +91,7 @@ class MissingPlugin(BeetsPlugin): } def __init__(self): - super(MissingPlugin, self).__init__() + super().__init__() self.config.add({ 'count': False, @@ -107,14 +105,14 @@ class MissingPlugin(BeetsPlugin): help=__doc__, aliases=['miss']) self._command.parser.add_option( - u'-c', u'--count', dest='count', action='store_true', - help=u'count missing tracks per album') + '-c', '--count', dest='count', action='store_true', + help='count missing tracks per album') self._command.parser.add_option( - u'-t', u'--total', dest='total', action='store_true', - help=u'count total of missing tracks') + '-t', '--total', dest='total', action='store_true', + help='count total of missing tracks') self._command.parser.add_option( - u'-a', u'--album', dest='album', action='store_true', - help=u'show missing albums for artist instead of tracks') + '-a', '--album', dest='album', action='store_true', + help='show missing albums for artist instead of tracks') self._command.parser.add_format_option() def commands(self): @@ -173,10 +171,10 @@ class MissingPlugin(BeetsPlugin): # build dict mapping artist to list of all albums for artist, albums in albums_by_artist.items(): if artist[1] is None or artist[1] == "": - albs_no_mbid = [u"'" + a['album'] + u"'" for a in albums] + albs_no_mbid = ["'" + a['album'] + "'" for a in albums] self._log.info( - u"No musicbrainz ID for artist '{}' found in album(s) {}; " - "skipping", artist[0], u", ".join(albs_no_mbid) + "No musicbrainz ID for artist '{}' found in album(s) {}; " + "skipping", artist[0], ", ".join(albs_no_mbid) ) continue @@ -185,7 +183,7 @@ class MissingPlugin(BeetsPlugin): release_groups = resp['release-group-list'] except MusicBrainzError as err: self._log.info( - u"Couldn't fetch info for artist '{}' ({}) - '{}'", + "Couldn't fetch info for artist '{}' ({}) - '{}'", artist[0], artist[1], err ) continue @@ -207,7 +205,7 @@ class MissingPlugin(BeetsPlugin): missing_titles = {rg['title'] for rg in missing} for release_title in missing_titles: - print_(u"{} - {}".format(artist[0], release_title)) + print_("{} - {}".format(artist[0], release_title)) if total: print(total_missing) @@ -223,6 +221,6 @@ class MissingPlugin(BeetsPlugin): for track_info in getattr(album_info, 'tracks', []): if track_info.track_id not in item_mbids: item = _item(track_info, album_info, album.id) - self._log.debug(u'track {0} in album {1}', + self._log.debug('track {0} in album {1}', track_info.track_id, album_info.album_id) yield item diff --git a/beetsplug/mpdstats.py b/beetsplug/mpdstats.py index 5c8402329..243562409 100644 --- a/beetsplug/mpdstats.py +++ b/beetsplug/mpdstats.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Peter Schnebel and Johann Klähn. # @@ -13,7 +12,6 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -from __future__ import division, absolute_import, print_function import mpd import socket @@ -46,7 +44,7 @@ def is_url(path): return path.split('://', 1)[0] in ['http', 'https'] -class MPDClientWrapper(object): +class MPDClientWrapper: def __init__(self, log): self._log = log @@ -60,13 +58,8 @@ class MPDClientWrapper(object): self._log.debug('music_directory: {0}', self.music_directory) self._log.debug('strip_path: {0}', self.strip_path) - if sys.version_info < (3, 0): - # On Python 2, use_unicode will enable the utf-8 mode for - # python-mpd2 - self.client = mpd.MPDClient(use_unicode=True) - else: - # On Python 3, python-mpd2 always uses Unicode - self.client = mpd.MPDClient() + # On Python 3, python-mpd2 always uses Unicode + self.client = mpd.MPDClient() def connect(self): """Connect to the MPD. @@ -77,11 +70,11 @@ class MPDClientWrapper(object): if host[0] in ['/', '~']: host = os.path.expanduser(host) - self._log.info(u'connecting to {0}:{1}', host, port) + self._log.info('connecting to {0}:{1}', host, port) try: self.client.connect(host, port) - except socket.error as e: - raise ui.UserError(u'could not connect to MPD: {0}'.format(e)) + except OSError as e: + raise ui.UserError(f'could not connect to MPD: {e}') password = mpd_config['password'].as_str() if password: @@ -89,7 +82,7 @@ class MPDClientWrapper(object): self.client.password(password) except mpd.CommandError as e: raise ui.UserError( - u'could not authenticate to MPD: {0}'.format(e) + f'could not authenticate to MPD: {e}' ) def disconnect(self): @@ -104,12 +97,12 @@ class MPDClientWrapper(object): """ try: return getattr(self.client, command)() - except (select.error, mpd.ConnectionError) as err: - self._log.error(u'{0}', err) + except (OSError, mpd.ConnectionError) as err: + self._log.error('{0}', err) if retries <= 0: # if we exited without breaking, we couldn't reconnect in time :( - raise ui.UserError(u'communication with MPD server failed') + raise ui.UserError('communication with MPD server failed') time.sleep(RETRY_INTERVAL) @@ -154,7 +147,7 @@ class MPDClientWrapper(object): return self.get('idle') -class MPDStats(object): +class MPDStats: def __init__(self, lib, log): self.lib = lib self._log = log @@ -186,7 +179,7 @@ class MPDStats(object): if item: return item else: - self._log.info(u'item not found: {0}', displayable_path(path)) + self._log.info('item not found: {0}', displayable_path(path)) def update_item(self, item, attribute, value=None, increment=None): """Update the beets item. Set attribute to value or increment the value @@ -204,7 +197,7 @@ class MPDStats(object): item[attribute] = value item.store() - self._log.debug(u'updated: {0} = {1} [{2}]', + self._log.debug('updated: {0} = {1} [{2}]', attribute, item[attribute], displayable_path(item.path)) @@ -251,16 +244,16 @@ class MPDStats(object): """Updates the play count of a song. """ self.update_item(song['beets_item'], 'play_count', increment=1) - self._log.info(u'played {0}', displayable_path(song['path'])) + self._log.info('played {0}', displayable_path(song['path'])) def handle_skipped(self, song): """Updates the skip count of a song. """ self.update_item(song['beets_item'], 'skip_count', increment=1) - self._log.info(u'skipped {0}', displayable_path(song['path'])) + self._log.info('skipped {0}', displayable_path(song['path'])) def on_stop(self, status): - self._log.info(u'stop') + self._log.info('stop') # if the current song stays the same it means that we stopped on the # current track and should not record a skip. @@ -270,7 +263,7 @@ class MPDStats(object): self.now_playing = None def on_pause(self, status): - self._log.info(u'pause') + self._log.info('pause') self.now_playing = None def on_play(self, status): @@ -300,11 +293,11 @@ class MPDStats(object): self.handle_song_change(self.now_playing) if is_url(path): - self._log.info(u'playing stream {0}', displayable_path(path)) + self._log.info('playing stream {0}', displayable_path(path)) self.now_playing = None return - self._log.info(u'playing {0}', displayable_path(path)) + self._log.info('playing {0}', displayable_path(path)) self.now_playing = { 'started': time.time(), @@ -330,7 +323,7 @@ class MPDStats(object): if handler: handler(status) else: - self._log.debug(u'unhandled status "{0}"', status) + self._log.debug('unhandled status "{0}"', status) events = self.mpd.events() @@ -345,31 +338,31 @@ class MPDStatsPlugin(plugins.BeetsPlugin): } def __init__(self): - super(MPDStatsPlugin, self).__init__() + super().__init__() mpd_config.add({ 'music_directory': config['directory'].as_filename(), - 'strip_path': u'', + 'strip_path': '', 'rating': True, 'rating_mix': 0.75, - 'host': os.environ.get('MPD_HOST', u'localhost'), + 'host': os.environ.get('MPD_HOST', 'localhost'), 'port': int(os.environ.get('MPD_PORT', 6600)), - 'password': u'', + 'password': '', }) mpd_config['password'].redact = True def commands(self): cmd = ui.Subcommand( 'mpdstats', - help=u'run a MPD client to gather play statistics') + help='run a MPD client to gather play statistics') cmd.parser.add_option( - u'--host', dest='host', type='string', - help=u'set the hostname of the server to connect to') + '--host', dest='host', type='string', + help='set the hostname of the server to connect to') cmd.parser.add_option( - u'--port', dest='port', type='int', - help=u'set the port of the MPD server to connect to') + '--port', dest='port', type='int', + help='set the port of the MPD server to connect to') cmd.parser.add_option( - u'--password', dest='password', type='string', - help=u'set the password of the MPD server to connect to') + '--password', dest='password', type='string', + help='set the password of the MPD server to connect to') def func(lib, opts, args): mpd_config.set_args(opts) diff --git a/beetsplug/mpdupdate.py b/beetsplug/mpdupdate.py index 72a98af0e..141f85b08 100644 --- a/beetsplug/mpdupdate.py +++ b/beetsplug/mpdupdate.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -21,7 +20,6 @@ Put something like the following in your config.yaml to configure: port: 6600 password: seekrit """ -from __future__ import division, absolute_import, print_function from beets.plugins import BeetsPlugin import os @@ -33,7 +31,7 @@ import six # No need to introduce a dependency on an MPD library for such a # simple use case. Here's a simple socket abstraction to make things # easier. -class BufferedSocket(object): +class BufferedSocket: """Socket abstraction that allows reading by line.""" def __init__(self, host, port, sep=b'\n'): if host[0] in ['/', '~']: @@ -66,11 +64,11 @@ class BufferedSocket(object): class MPDUpdatePlugin(BeetsPlugin): def __init__(self): - super(MPDUpdatePlugin, self).__init__() + super().__init__() config['mpd'].add({ - 'host': os.environ.get('MPD_HOST', u'localhost'), + 'host': os.environ.get('MPD_HOST', 'localhost'), 'port': int(os.environ.get('MPD_PORT', 6600)), - 'password': u'', + 'password': '', }) config['mpd']['password'].redact = True @@ -100,21 +98,21 @@ class MPDUpdatePlugin(BeetsPlugin): try: s = BufferedSocket(host, port) - except socket.error as e: - self._log.warning(u'MPD connection failed: {0}', - six.text_type(e.strerror)) + except OSError as e: + self._log.warning('MPD connection failed: {0}', + str(e.strerror)) return resp = s.readline() if b'OK MPD' not in resp: - self._log.warning(u'MPD connection failed: {0!r}', resp) + self._log.warning('MPD connection failed: {0!r}', resp) return if password: s.send(b'password "%s"\n' % password.encode('utf8')) resp = s.readline() if b'OK' not in resp: - self._log.warning(u'Authentication failed: {0!r}', resp) + self._log.warning('Authentication failed: {0!r}', resp) s.send(b'close\n') s.close() return @@ -122,8 +120,8 @@ class MPDUpdatePlugin(BeetsPlugin): s.send(b'update\n') resp = s.readline() if b'updating_db' not in resp: - self._log.warning(u'Update failed: {0!r}', resp) + self._log.warning('Update failed: {0!r}', resp) s.send(b'close\n') s.close() - self._log.info(u'Database updated.') + self._log.info('Database updated.') diff --git a/beetsplug/parentwork.py b/beetsplug/parentwork.py index a5e042344..75307b8ff 100644 --- a/beetsplug/parentwork.py +++ b/beetsplug/parentwork.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2017, Dorian Soergel. # @@ -17,7 +16,6 @@ and work composition date """ -from __future__ import division, absolute_import, print_function from beets import ui from beets.plugins import BeetsPlugin @@ -71,7 +69,7 @@ def find_parentwork_info(mb_workid): class ParentWorkPlugin(BeetsPlugin): def __init__(self): - super(ParentWorkPlugin, self).__init__() + super().__init__() self.config.add({ 'auto': False, @@ -96,12 +94,12 @@ class ParentWorkPlugin(BeetsPlugin): item.try_write() command = ui.Subcommand( 'parentwork', - help=u'fetch parent works, composers and dates') + help='fetch parent works, composers and dates') command.parser.add_option( - u'-f', u'--force', dest='force', + '-f', '--force', dest='force', action='store_true', default=None, - help=u're-fetch when parent work is already present') + help='re-fetch when parent work is already present') command.func = func return [command] @@ -135,8 +133,8 @@ class ParentWorkPlugin(BeetsPlugin): if 'end' in artist.keys(): parentwork_info["parentwork_date"] = artist['end'] - parentwork_info['parent_composer'] = u', '.join(parent_composer) - parentwork_info['parent_composer_sort'] = u', '.join( + parentwork_info['parent_composer'] = ', '.join(parent_composer) + parentwork_info['parent_composer_sort'] = ', '.join( parent_composer_sort) if not composer_exists: diff --git a/beetsplug/permissions.py b/beetsplug/permissions.py index dd9e09843..18b554ccc 100644 --- a/beetsplug/permissions.py +++ b/beetsplug/permissions.py @@ -1,7 +1,3 @@ -# -*- coding: utf-8 -*- - -from __future__ import division, absolute_import, print_function - """Fixes file permissions after the file gets written on import. Put something like the following in your config.yaml to configure: @@ -21,8 +17,8 @@ def convert_perm(perm): Or, if `perm` is an integer, reinterpret it as an octal number that has been "misinterpreted" as decimal. """ - if isinstance(perm, six.integer_types): - perm = six.text_type(perm) + if isinstance(perm, int): + perm = str(perm) return int(perm, 8) @@ -40,11 +36,11 @@ def assert_permissions(path, permission, log): """ if not check_permissions(util.syspath(path), permission): log.warning( - u'could not set permissions on {}', + 'could not set permissions on {}', util.displayable_path(path), ) log.debug( - u'set permissions to {}, but permissions are now {}', + 'set permissions to {}, but permissions are now {}', permission, os.stat(util.syspath(path)).st_mode & 0o777, ) @@ -60,12 +56,12 @@ def dirs_in_library(library, item): class Permissions(BeetsPlugin): def __init__(self): - super(Permissions, self).__init__() + super().__init__() # Adding defaults. self.config.add({ - u'file': '644', - u'dir': '755', + 'file': '644', + 'dir': '755', }) self.register_listener('item_imported', self.fix) @@ -97,7 +93,7 @@ class Permissions(BeetsPlugin): for path in file_chmod_queue: # Changing permissions on the destination file. self._log.debug( - u'setting file permissions on {}', + 'setting file permissions on {}', util.displayable_path(path), ) os.chmod(util.syspath(path), file_perm) @@ -114,7 +110,7 @@ class Permissions(BeetsPlugin): for path in dir_chmod_queue: # Chaning permissions on the destination directory. self._log.debug( - u'setting directory permissions on {}', + 'setting directory permissions on {}', util.displayable_path(path), ) os.chmod(util.syspath(path), dir_perm) diff --git a/beetsplug/play.py b/beetsplug/play.py index d41346042..f4233490f 100644 --- a/beetsplug/play.py +++ b/beetsplug/play.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, David Hamp-Gonsalves # @@ -15,7 +14,6 @@ """Send the results of a query to the configured music player as a playlist. """ -from __future__ import division, absolute_import, print_function from beets.plugins import BeetsPlugin from beets.ui import Subcommand @@ -40,8 +38,8 @@ def play(command_str, selection, paths, open_args, log, item_type='track', """ # Print number of tracks or albums to be played, log command to be run. item_type += 's' if len(selection) > 1 else '' - ui.print_(u'Playing {0} {1}.'.format(len(selection), item_type)) - log.debug(u'executing command: {} {!r}', command_str, open_args) + ui.print_('Playing {} {}.'.format(len(selection), item_type)) + log.debug('executing command: {} {!r}', command_str, open_args) try: if keep_open: @@ -52,13 +50,13 @@ def play(command_str, selection, paths, open_args, log, item_type='track', util.interactive_open(open_args, command_str) except OSError as exc: raise ui.UserError( - "Could not play the query: {0}".format(exc)) + f"Could not play the query: {exc}") class PlayPlugin(BeetsPlugin): def __init__(self): - super(PlayPlugin, self).__init__() + super().__init__() config['play'].add({ 'command': None, @@ -75,18 +73,18 @@ class PlayPlugin(BeetsPlugin): def commands(self): play_command = Subcommand( 'play', - help=u'send music to a player as a playlist' + help='send music to a player as a playlist' ) play_command.parser.add_album_option() play_command.parser.add_option( - u'-A', u'--args', + '-A', '--args', action='store', - help=u'add additional arguments to the command', + help='add additional arguments to the command', ) play_command.parser.add_option( - u'-y', u'--yes', + '-y', '--yes', action="store_true", - help=u'skip the warning threshold', + help='skip the warning threshold', ) play_command.func = self._play_command return [play_command] @@ -125,7 +123,7 @@ class PlayPlugin(BeetsPlugin): if not selection: ui.print_(ui.colorize('text_warning', - u'No {0} to play.'.format(item_type))) + f'No {item_type} to play.')) return open_args = self._playlist_or_paths(paths) @@ -149,7 +147,7 @@ class PlayPlugin(BeetsPlugin): if ARGS_MARKER in command_str: return command_str.replace(ARGS_MARKER, args) else: - return u"{} {}".format(command_str, args) + return f"{command_str} {args}" else: # Don't include the marker in the command. return command_str.replace(" " + ARGS_MARKER, "") @@ -176,10 +174,10 @@ class PlayPlugin(BeetsPlugin): ui.print_(ui.colorize( 'text_warning', - u'You are about to queue {0} {1}.'.format( + 'You are about to queue {} {}.'.format( len(selection), item_type))) - if ui.input_options((u'Continue', u'Abort')) == 'a': + if ui.input_options(('Continue', 'Abort')) == 'a': return True return False diff --git a/beetsplug/playlist.py b/beetsplug/playlist.py index 3f8ba2b0e..265b8bad2 100644 --- a/beetsplug/playlist.py +++ b/beetsplug/playlist.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # # Permission is hereby granted, free of charge, to any person obtaining @@ -12,7 +11,6 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -from __future__ import division, absolute_import, print_function import os import fnmatch @@ -33,7 +31,7 @@ class PlaylistQuery(beets.dbcore.Query): pattern, os.path.abspath(os.path.join( config['playlist_dir'].as_filename(), - '{0}.m3u'.format(pattern), + f'{pattern}.m3u', )), ) @@ -45,7 +43,7 @@ class PlaylistQuery(beets.dbcore.Query): try: f = open(beets.util.syspath(playlist_path), mode='rb') - except (OSError, IOError): + except OSError: continue if config['relative_to'].get() == 'library': @@ -71,7 +69,7 @@ class PlaylistQuery(beets.dbcore.Query): if not self.paths: # Playlist is empty return '0', () - clause = 'path IN ({0})'.format(', '.join('?' for path in self.paths)) + clause = 'path IN ({})'.format(', '.join('?' for path in self.paths)) return clause, (beets.library.BLOB_TYPE(p) for p in self.paths) def match(self, item): @@ -82,7 +80,7 @@ class PlaylistPlugin(beets.plugins.BeetsPlugin): item_queries = {'playlist': PlaylistQuery} def __init__(self): - super(PlaylistPlugin, self).__init__() + super().__init__() self.config.add({ 'auto': False, 'playlist_dir': '.', @@ -116,7 +114,7 @@ class PlaylistPlugin(beets.plugins.BeetsPlugin): def cli_exit(self, lib): for playlist in self.find_playlists(): - self._log.info('Updating playlist: {0}'.format(playlist)) + self._log.info(f'Updating playlist: {playlist}') base_dir = beets.util.bytestring_path( self.relative_to if self.relative_to else os.path.dirname(playlist) @@ -125,7 +123,7 @@ class PlaylistPlugin(beets.plugins.BeetsPlugin): try: self.update_playlist(playlist, base_dir) except beets.util.FilesystemError: - self._log.error('Failed to update playlist: {0}'.format( + self._log.error('Failed to update playlist: {}'.format( beets.util.displayable_path(playlist))) def find_playlists(self): @@ -133,7 +131,7 @@ class PlaylistPlugin(beets.plugins.BeetsPlugin): try: dir_contents = os.listdir(beets.util.syspath(self.playlist_dir)) except OSError: - self._log.warning('Unable to open playlist directory {0}'.format( + self._log.warning('Unable to open playlist directory {}'.format( beets.util.displayable_path(self.playlist_dir))) return @@ -181,7 +179,7 @@ class PlaylistPlugin(beets.plugins.BeetsPlugin): if changes or deletions: self._log.info( - 'Updated playlist {0} ({1} changes, {2} deletions)'.format( + 'Updated playlist {} ({} changes, {} deletions)'.format( filename, changes, deletions)) beets.util.copy(new_playlist, filename, replace=True) beets.util.remove(new_playlist) diff --git a/beetsplug/plexupdate.py b/beetsplug/plexupdate.py index 860757cfb..42620c0cd 100644 --- a/beetsplug/plexupdate.py +++ b/beetsplug/plexupdate.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - """Updates an Plex library whenever the beets library is changed. Plex Home users enter the Plex Token to enable updating. @@ -9,7 +7,6 @@ Put something like the following in your config.yaml to configure: port: 32400 token: token """ -from __future__ import division, absolute_import, print_function import requests from xml.etree import ElementTree @@ -23,7 +20,7 @@ def get_music_section(host, port, token, library_name, secure, """Getting the section key for the music library in Plex. """ api_endpoint = append_token('library/sections', token) - url = urljoin('{0}://{1}:{2}'.format(get_protocol(secure), host, + url = urljoin('{}://{}:{}'.format(get_protocol(secure), host, port), api_endpoint) # Sends request. @@ -48,9 +45,9 @@ def update_plex(host, port, token, library_name, secure, # Getting section key and build url. section_key = get_music_section(host, port, token, library_name, secure, ignore_cert_errors) - api_endpoint = 'library/sections/{0}/refresh'.format(section_key) + api_endpoint = f'library/sections/{section_key}/refresh' api_endpoint = append_token(api_endpoint, token) - url = urljoin('{0}://{1}:{2}'.format(get_protocol(secure), host, + url = urljoin('{}://{}:{}'.format(get_protocol(secure), host, port), api_endpoint) # Sends request and returns requests object. @@ -75,16 +72,16 @@ def get_protocol(secure): class PlexUpdate(BeetsPlugin): def __init__(self): - super(PlexUpdate, self).__init__() + super().__init__() # Adding defaults. config['plex'].add({ - u'host': u'localhost', - u'port': 32400, - u'token': u'', - u'library_name': u'Music', - u'secure': False, - u'ignore_cert_errors': False}) + 'host': 'localhost', + 'port': 32400, + 'token': '', + 'library_name': 'Music', + 'secure': False, + 'ignore_cert_errors': False}) config['plex']['token'].redact = True self.register_listener('database_change', self.listen_for_db_change) @@ -96,7 +93,7 @@ class PlexUpdate(BeetsPlugin): def update(self, lib): """When the client exists try to send refresh request to Plex server. """ - self._log.info(u'Updating Plex library...') + self._log.info('Updating Plex library...') # Try to send update request. try: @@ -107,7 +104,7 @@ class PlexUpdate(BeetsPlugin): config['plex']['library_name'].get(), config['plex']['secure'].get(bool), config['plex']['ignore_cert_errors'].get(bool)) - self._log.info(u'... started.') + self._log.info('... started.') except requests.exceptions.RequestException: - self._log.warning(u'Update failed.') + self._log.warning('Update failed.') diff --git a/beetsplug/random.py b/beetsplug/random.py index a8e29313a..ea9b7b98f 100644 --- a/beetsplug/random.py +++ b/beetsplug/random.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Philippe Mongeau. # @@ -15,7 +14,6 @@ """Get a random song or album from the library. """ -from __future__ import division, absolute_import, print_function from beets.plugins import BeetsPlugin from beets.ui import Subcommand, decargs, print_ @@ -40,16 +38,16 @@ def random_func(lib, opts, args): random_cmd = Subcommand('random', - help=u'choose a random track or album') + help='choose a random track or album') random_cmd.parser.add_option( - u'-n', u'--number', action='store', type="int", - help=u'number of objects to choose', default=1) + '-n', '--number', action='store', type="int", + help='number of objects to choose', default=1) random_cmd.parser.add_option( - u'-e', u'--equal-chance', action='store_true', - help=u'each artist has the same chance') + '-e', '--equal-chance', action='store_true', + help='each artist has the same chance') random_cmd.parser.add_option( - u'-t', u'--time', action='store', type="float", - help=u'total length in minutes of objects to choose') + '-t', '--time', action='store', type="float", + help='total length in minutes of objects to choose') random_cmd.parser.add_all_common_options() random_cmd.func = random_func diff --git a/beetsplug/replaygain.py b/beetsplug/replaygain.py index 70b99671d..6592da2c6 100644 --- a/beetsplug/replaygain.py +++ b/beetsplug/replaygain.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Fabrice Laporte, Yevgeny Bezman, and Adrian Sampson. # @@ -13,7 +12,6 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -from __future__ import division, absolute_import, print_function import collections import enum @@ -25,7 +23,7 @@ import subprocess import sys import warnings from multiprocessing.pool import ThreadPool, RUN -from six.moves import zip, queue +from six.moves import queue from threading import Thread, Event from beets import ui @@ -60,13 +58,13 @@ def call(args, **kwargs): return command_output(args, **kwargs) except subprocess.CalledProcessError as e: raise ReplayGainError( - u"{0} exited with status {1}".format(args[0], e.returncode) + "{} exited with status {}".format(args[0], e.returncode) ) except UnicodeEncodeError: # Due to a bug in Python 2's subprocess on Windows, Unicode # filenames can fail to encode on that platform. See: # https://github.com/google-code-export/beets/issues/499 - raise ReplayGainError(u"argument encoding failed") + raise ReplayGainError("argument encoding failed") def after_version(version_a, version_b): @@ -108,7 +106,7 @@ class Peak(enum.Enum): sample = 2 -class Backend(object): +class Backend: """An abstract class representing engine for calculating RG values. """ @@ -141,7 +139,7 @@ class FfmpegBackend(Backend): do_parallel = True def __init__(self, config, log): - super(FfmpegBackend, self).__init__(config, log) + super().__init__(config, log) self._ffmpeg_path = "ffmpeg" # check that ffmpeg is installed @@ -149,7 +147,7 @@ class FfmpegBackend(Backend): ffmpeg_version_out = call([self._ffmpeg_path, "-version"]) except OSError: raise FatalReplayGainError( - u"could not find ffmpeg at {0}".format(self._ffmpeg_path) + f"could not find ffmpeg at {self._ffmpeg_path}" ) incompatible_ffmpeg = True for line in ffmpeg_version_out.stdout.splitlines(): @@ -163,9 +161,9 @@ class FfmpegBackend(Backend): incompatible_ffmpeg = False if incompatible_ffmpeg: raise FatalReplayGainError( - u"Installed FFmpeg version does not support ReplayGain." - u"calculation. Either libavfilter version 6.67.100 or above or" - u"the --enable-libebur128 configuration option is required." + "Installed FFmpeg version does not support ReplayGain." + "calculation. Either libavfilter version 6.67.100 or above or" + "the --enable-libebur128 configuration option is required." ) def compute_track_gain(self, items, target_level, peak): @@ -236,7 +234,7 @@ class FfmpegBackend(Backend): album_gain = target_level_lufs - album_gain self._log.debug( - u"{0}: gain {1} LU, peak {2}" + "{}: gain {} LU, peak {}" .format(items, album_gain, album_peak) ) @@ -253,7 +251,7 @@ class FfmpegBackend(Backend): "-map", "a:0", "-filter", - "ebur128=peak={0}".format(peak_method), + f"ebur128=peak={peak_method}", "-f", "null", "-", @@ -270,10 +268,10 @@ class FfmpegBackend(Backend): peak_method = peak.name # call ffmpeg - self._log.debug(u"analyzing {0}".format(item)) + self._log.debug(f"analyzing {item}") cmd = self._construct_cmd(item, peak_method) self._log.debug( - u'executing {0}', u' '.join(map(displayable_path, cmd)) + 'executing {0}', ' '.join(map(displayable_path, cmd)) ) output = call(cmd).stderr.splitlines() @@ -284,7 +282,7 @@ class FfmpegBackend(Backend): else: line_peak = self._find_line( output, - " {0} peak:".format(peak_method.capitalize()).encode(), + f" {peak_method.capitalize()} peak:".encode(), start_line=len(output) - 1, step_size=-1, ) peak = self._parse_float( @@ -329,12 +327,12 @@ class FfmpegBackend(Backend): if self._parse_float(b"M: " + line[1]) >= gating_threshold: n_blocks += 1 self._log.debug( - u"{0}: {1} blocks over {2} LUFS" + "{}: {} blocks over {} LUFS" .format(item, n_blocks, gating_threshold) ) self._log.debug( - u"{0}: gain {1} LU, peak {2}" + "{}: gain {} LU, peak {}" .format(item, gain, peak) ) @@ -350,7 +348,7 @@ class FfmpegBackend(Backend): if output[i].startswith(search): return i raise ReplayGainError( - u"ffmpeg output: missing {0} after line {1}" + "ffmpeg output: missing {} after line {}" .format(repr(search), start_line) ) @@ -364,7 +362,7 @@ class FfmpegBackend(Backend): value = line.split(b":", 1) if len(value) < 2: raise ReplayGainError( - u"ffmpeg output: expected key value pair, found {0}" + "ffmpeg output: expected key value pair, found {}" .format(line) ) value = value[1].lstrip() @@ -375,7 +373,7 @@ class FfmpegBackend(Backend): return float(value) except ValueError: raise ReplayGainError( - u"ffmpeg output: expected float value, found {0}" + "ffmpeg output: expected float value, found {}" .format(value) ) @@ -385,9 +383,9 @@ class CommandBackend(Backend): do_parallel = True def __init__(self, config, log): - super(CommandBackend, self).__init__(config, log) + super().__init__(config, log) config.add({ - 'command': u"", + 'command': "", 'noclip': True, }) @@ -397,7 +395,7 @@ class CommandBackend(Backend): # Explicit executable path. if not os.path.isfile(self.command): raise FatalReplayGainError( - u'replaygain command does not exist: {0}'.format( + 'replaygain command does not exist: {}'.format( self.command) ) else: @@ -410,7 +408,7 @@ class CommandBackend(Backend): pass if not self.command: raise FatalReplayGainError( - u'no replaygain command found: install mp3gain or aacgain' + 'no replaygain command found: install mp3gain or aacgain' ) self.noclip = config['noclip'].get(bool) @@ -432,7 +430,7 @@ class CommandBackend(Backend): supported_items = list(filter(self.format_supported, items)) if len(supported_items) != len(items): - self._log.debug(u'tracks are of unsupported format') + self._log.debug('tracks are of unsupported format') return AlbumGain(None, []) output = self.compute_gain(supported_items, target_level, True) @@ -455,7 +453,7 @@ class CommandBackend(Backend): the album gain """ if len(items) == 0: - self._log.debug(u'no supported tracks to analyze') + self._log.debug('no supported tracks to analyze') return [] """Compute ReplayGain values and return a list of results @@ -477,10 +475,10 @@ class CommandBackend(Backend): cmd = cmd + ['-d', str(int(target_level - 89))] cmd = cmd + [syspath(i.path) for i in items] - self._log.debug(u'analyzing {0} files', len(items)) - self._log.debug(u"executing {0}", " ".join(map(displayable_path, cmd))) + self._log.debug('analyzing {0} files', len(items)) + self._log.debug("executing {0}", " ".join(map(displayable_path, cmd))) output = call(cmd).stdout - self._log.debug(u'analysis finished') + self._log.debug('analysis finished') return self.parse_tool_output(output, len(items) + (1 if is_album else 0)) @@ -493,8 +491,8 @@ class CommandBackend(Backend): for line in text.split(b'\n')[1:num_lines + 1]: parts = line.split(b'\t') if len(parts) != 6 or parts[0] == b'File': - self._log.debug(u'bad tool output: {0}', text) - raise ReplayGainError(u'mp3gain failed') + self._log.debug('bad tool output: {0}', text) + raise ReplayGainError('mp3gain failed') d = { 'file': parts[0], 'mp3gain': int(parts[1]), @@ -512,7 +510,7 @@ class CommandBackend(Backend): class GStreamerBackend(Backend): def __init__(self, config, log): - super(GStreamerBackend, self).__init__(config, log) + super().__init__(config, log) self._import_gst() # Initialized a GStreamer pipeline of the form filesrc -> @@ -529,7 +527,7 @@ class GStreamerBackend(Backend): if self._src is None or self._decbin is None or self._conv is None \ or self._res is None or self._rg is None: raise FatalGstreamerPluginReplayGainError( - u"Failed to load required GStreamer plugins" + "Failed to load required GStreamer plugins" ) # We check which files need gain ourselves, so all files given @@ -574,14 +572,14 @@ class GStreamerBackend(Backend): import gi except ImportError: raise FatalReplayGainError( - u"Failed to load GStreamer: python-gi not found" + "Failed to load GStreamer: python-gi not found" ) try: gi.require_version('Gst', '1.0') except ValueError as e: raise FatalReplayGainError( - u"Failed to load GStreamer 1.0: {0}".format(e) + f"Failed to load GStreamer 1.0: {e}" ) from gi.repository import GObject, Gst, GLib @@ -618,7 +616,7 @@ class GStreamerBackend(Backend): def compute_track_gain(self, items, target_level, peak): self.compute(items, target_level, False) if len(self._file_tags) != len(items): - raise ReplayGainError(u"Some tracks did not receive tags") + raise ReplayGainError("Some tracks did not receive tags") ret = [] for item in items: @@ -631,7 +629,7 @@ class GStreamerBackend(Backend): items = list(items) self.compute(items, target_level, True) if len(self._file_tags) != len(items): - raise ReplayGainError(u"Some items in album did not receive tags") + raise ReplayGainError("Some items in album did not receive tags") # Collect track gains. track_gains = [] @@ -640,7 +638,7 @@ class GStreamerBackend(Backend): gain = self._file_tags[item]["TRACK_GAIN"] peak = self._file_tags[item]["TRACK_PEAK"] except KeyError: - raise ReplayGainError(u"results missing for track") + raise ReplayGainError("results missing for track") track_gains.append(Gain(gain, peak)) # Get album gain information from the last track. @@ -649,7 +647,7 @@ class GStreamerBackend(Backend): gain = last_tags["ALBUM_GAIN"] peak = last_tags["ALBUM_PEAK"] except KeyError: - raise ReplayGainError(u"results missing for album") + raise ReplayGainError("results missing for album") return AlbumGain(Gain(gain, peak), track_gains) @@ -671,7 +669,7 @@ class GStreamerBackend(Backend): f = self._src.get_property("location") # A GStreamer error, either an unsupported format or a bug. self._error = ReplayGainError( - u"Error {0!r} - {1!r} on file {2!r}".format(err, debug, f) + f"Error {err!r} - {debug!r} on file {f!r}" ) def _on_tag(self, bus, message): @@ -784,7 +782,7 @@ class AudioToolsBackend(Backend): """ def __init__(self, config, log): - super(AudioToolsBackend, self).__init__(config, log) + super().__init__(config, log) self._import_audiotools() def _import_audiotools(self): @@ -798,7 +796,7 @@ class AudioToolsBackend(Backend): import audiotools.replaygain except ImportError: raise FatalReplayGainError( - u"Failed to load audiotools: audiotools not found" + "Failed to load audiotools: audiotools not found" ) self._mod_audiotools = audiotools self._mod_replaygain = audiotools.replaygain @@ -814,13 +812,13 @@ class AudioToolsBackend(Backend): """ try: audiofile = self._mod_audiotools.open(py3_path(syspath(item.path))) - except IOError: + except OSError: raise ReplayGainError( - u"File {} was not found".format(item.path) + f"File {item.path} was not found" ) except self._mod_audiotools.UnsupportedFile: raise ReplayGainError( - u"Unsupported file type {}".format(item.format) + f"Unsupported file type {item.format}" ) return audiofile @@ -839,7 +837,7 @@ class AudioToolsBackend(Backend): rg = self._mod_replaygain.ReplayGain(audiofile.sample_rate()) except ValueError: raise ReplayGainError( - u"Unsupported sample rate {}".format(item.samplerate)) + f"Unsupported sample rate {item.samplerate}") return return rg @@ -871,8 +869,8 @@ class AudioToolsBackend(Backend): except ValueError as exc: # `audiotools.replaygain` can raise a `ValueError` if the sample # rate is incorrect. - self._log.debug(u'error in rg.title_gain() call: {}', exc) - raise ReplayGainError(u'audiotools audio data error') + self._log.debug('error in rg.title_gain() call: {}', exc) + raise ReplayGainError('audiotools audio data error') return self._with_target_level(gain, target_level), peak def _compute_track_gain(self, item, target_level): @@ -889,7 +887,7 @@ class AudioToolsBackend(Backend): rg, audiofile, target_level ) - self._log.debug(u'ReplayGain for track {0} - {1}: {2:.2f}, {3:.2f}', + self._log.debug('ReplayGain for track {0} - {1}: {2:.2f}, {3:.2f}', item.artist, item.title, rg_track_gain, rg_track_peak) return Gain(gain=rg_track_gain, peak=rg_track_peak) @@ -914,14 +912,14 @@ class AudioToolsBackend(Backend): track_gains.append( Gain(gain=rg_track_gain, peak=rg_track_peak) ) - self._log.debug(u'ReplayGain for track {0}: {1:.2f}, {2:.2f}', + self._log.debug('ReplayGain for track {0}: {1:.2f}, {2:.2f}', item, rg_track_gain, rg_track_peak) # After getting the values for all tracks, it's possible to get the # album values. rg_album_gain, rg_album_peak = rg.album_gain() rg_album_gain = self._with_target_level(rg_album_gain, target_level) - self._log.debug(u'ReplayGain for album {0}: {1:.2f}, {2:.2f}', + self._log.debug('ReplayGain for album {0}: {1:.2f}, {2:.2f}', items[0].album, rg_album_gain, rg_album_peak) return AlbumGain( @@ -946,7 +944,7 @@ class ExceptionWatcher(Thread): try: exc = self._queue.get_nowait() self._callback() - six.reraise(exc[0], exc[1], exc[2]) + raise exc[1].with_traceback(exc[2]) except queue.Empty: # No exceptions yet, loop back to check # whether `_stopevent` is set @@ -976,13 +974,13 @@ class ReplayGainPlugin(BeetsPlugin): } def __init__(self): - super(ReplayGainPlugin, self).__init__() + super().__init__() # default backend is 'command' for backward-compatibility. self.config.add({ 'overwrite': False, 'auto': True, - 'backend': u'command', + 'backend': 'command', 'threads': cpu_count(), 'parallel_on_import': False, 'per_disc': False, @@ -1000,19 +998,19 @@ class ReplayGainPlugin(BeetsPlugin): if self.backend_name not in self.backends: raise ui.UserError( - u"Selected ReplayGain backend {0} is not supported. " - u"Please select one of: {1}".format( + "Selected ReplayGain backend {} is not supported. " + "Please select one of: {}".format( self.backend_name, - u', '.join(self.backends.keys()) + ', '.join(self.backends.keys()) ) ) peak_method = self.config["peak"].as_str() if peak_method not in self.peak_methods: raise ui.UserError( - u"Selected ReplayGain peak method {0} is not supported. " - u"Please select one of: {1}".format( + "Selected ReplayGain peak method {} is not supported. " + "Please select one of: {}".format( peak_method, - u', '.join(self.peak_methods.keys()) + ', '.join(self.peak_methods.keys()) ) ) self._peak_method = self.peak_methods[peak_method] @@ -1032,7 +1030,7 @@ class ReplayGainPlugin(BeetsPlugin): ) except (ReplayGainError, FatalReplayGainError) as e: raise ui.UserError( - u'replaygain initialization failed: {0}'.format(e)) + f'replaygain initialization failed: {e}') def should_use_r128(self, item): """Checks the plugin setting to decide whether the calculation @@ -1063,27 +1061,27 @@ class ReplayGainPlugin(BeetsPlugin): item.rg_track_gain = track_gain.gain item.rg_track_peak = track_gain.peak item.store() - self._log.debug(u'applied track gain {0} LU, peak {1} of FS', + self._log.debug('applied track gain {0} LU, peak {1} of FS', item.rg_track_gain, item.rg_track_peak) def store_album_gain(self, item, album_gain): item.rg_album_gain = album_gain.gain item.rg_album_peak = album_gain.peak item.store() - self._log.debug(u'applied album gain {0} LU, peak {1} of FS', + self._log.debug('applied album gain {0} LU, peak {1} of FS', item.rg_album_gain, item.rg_album_peak) def store_track_r128_gain(self, item, track_gain): item.r128_track_gain = track_gain.gain item.store() - self._log.debug(u'applied r128 track gain {0} LU', + self._log.debug('applied r128 track gain {0} LU', item.r128_track_gain) def store_album_r128_gain(self, item, album_gain): item.r128_album_gain = album_gain.gain item.store() - self._log.debug(u'applied r128 album gain {0} LU', + self._log.debug('applied r128 album gain {0} LU', item.r128_album_gain) def tag_specific_values(self, items): @@ -1114,17 +1112,17 @@ class ReplayGainPlugin(BeetsPlugin): items, nothing is done. """ if not force and not self.album_requires_gain(album): - self._log.info(u'Skipping album {0}', album) + self._log.info('Skipping album {0}', album) return if (any([self.should_use_r128(item) for item in album.items()]) and not - all(([self.should_use_r128(item) for item in album.items()]))): + all([self.should_use_r128(item) for item in album.items()])): self._log.error( - u"Cannot calculate gain for album {0} (incompatible formats)", + "Cannot calculate gain for album {0} (incompatible formats)", album) return - self._log.info(u'analyzing {0}', album) + self._log.info('analyzing {0}', album) tag_vals = self.tag_specific_values(album.items()) store_track_gain, store_album_gain, target_level, peak = tag_vals @@ -1146,8 +1144,8 @@ class ReplayGainPlugin(BeetsPlugin): # `album_gain` without throwing FatalReplayGainError # => raise non-fatal exception & continue raise ReplayGainError( - u"ReplayGain backend `{}` failed " - u"for some tracks in album {}" + "ReplayGain backend `{}` failed " + "for some tracks in album {}" .format(self.backend_name, album) ) for item, track_gain in zip(items, @@ -1156,7 +1154,7 @@ class ReplayGainPlugin(BeetsPlugin): store_album_gain(item, album_gain.album_gain) if write: item.try_write() - self._log.debug(u'done analyzing {0}', item) + self._log.debug('done analyzing {0}', item) try: self._apply( @@ -1169,10 +1167,10 @@ class ReplayGainPlugin(BeetsPlugin): callback=_store_album ) except ReplayGainError as e: - self._log.info(u"ReplayGain error: {0}", e) + self._log.info("ReplayGain error: {0}", e) except FatalReplayGainError as e: raise ui.UserError( - u"Fatal replay gain error: {0}".format(e)) + f"Fatal replay gain error: {e}") def handle_track(self, item, write, force=False): """Compute track replay gain and store it in the item. @@ -1182,7 +1180,7 @@ class ReplayGainPlugin(BeetsPlugin): in the item, nothing is done. """ if not force and not self.track_requires_gain(item): - self._log.info(u'Skipping track {0}', item) + self._log.info('Skipping track {0}', item) return tag_vals = self.tag_specific_values([item]) @@ -1194,14 +1192,14 @@ class ReplayGainPlugin(BeetsPlugin): # `track_gains` without throwing FatalReplayGainError # => raise non-fatal exception & continue raise ReplayGainError( - u"ReplayGain backend `{}` failed for track {}" + "ReplayGain backend `{}` failed for track {}" .format(self.backend_name, item) ) store_track_gain(item, track_gains[0]) if write: item.try_write() - self._log.debug(u'done analyzing {0}', item) + self._log.debug('done analyzing {0}', item) try: self._apply( @@ -1214,9 +1212,9 @@ class ReplayGainPlugin(BeetsPlugin): callback=_store_track ) except ReplayGainError as e: - self._log.info(u"ReplayGain error: {0}", e) + self._log.info("ReplayGain error: {0}", e) except FatalReplayGainError as e: - raise ui.UserError(u"Fatal replay gain error: {0}".format(e)) + raise ui.UserError(f"Fatal replay gain error: {e}") def _has_pool(self): """Check whether a `ThreadPool` is running instance in `self.pool` @@ -1350,22 +1348,22 @@ class ReplayGainPlugin(BeetsPlugin): def commands(self): """Return the "replaygain" ui subcommand. """ - cmd = ui.Subcommand('replaygain', help=u'analyze for ReplayGain') + cmd = ui.Subcommand('replaygain', help='analyze for ReplayGain') cmd.parser.add_album_option() cmd.parser.add_option( "-t", "--threads", dest="threads", type=int, - help=u'change the number of threads, \ + help='change the number of threads, \ defaults to maximum available processors' ) cmd.parser.add_option( "-f", "--force", dest="force", action="store_true", default=False, - help=u"analyze all files, including those that " + help="analyze all files, including those that " "already have ReplayGain metadata") cmd.parser.add_option( "-w", "--write", default=None, action="store_true", - help=u"write new metadata to files' tags") + help="write new metadata to files' tags") cmd.parser.add_option( "-W", "--nowrite", dest="write", action="store_false", - help=u"don't write metadata (opposite of -w)") + help="don't write metadata (opposite of -w)") cmd.func = self.command_func return [cmd] diff --git a/beetsplug/rewrite.py b/beetsplug/rewrite.py index eadb14252..e02e4080d 100644 --- a/beetsplug/rewrite.py +++ b/beetsplug/rewrite.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -16,7 +15,6 @@ """Uses user-specified rewriting rules to canonicalize names for path formats. """ -from __future__ import division, absolute_import, print_function import re from collections import defaultdict @@ -44,7 +42,7 @@ def rewriter(field, rules): class RewritePlugin(BeetsPlugin): def __init__(self): - super(RewritePlugin, self).__init__() + super().__init__() self.config.add({}) @@ -55,11 +53,11 @@ class RewritePlugin(BeetsPlugin): try: fieldname, pattern = key.split(None, 1) except ValueError: - raise ui.UserError(u"invalid rewrite specification") + raise ui.UserError("invalid rewrite specification") if fieldname not in library.Item._fields: - raise ui.UserError(u"invalid field name (%s) in rewriter" % + raise ui.UserError("invalid field name (%s) in rewriter" % fieldname) - self._log.debug(u'adding template field {0}', key) + self._log.debug('adding template field {0}', key) pattern = re.compile(pattern.lower()) rules[fieldname].append((pattern, value)) if fieldname == 'artist': diff --git a/beetsplug/scrub.py b/beetsplug/scrub.py index a905899da..d80446686 100644 --- a/beetsplug/scrub.py +++ b/beetsplug/scrub.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -17,7 +16,6 @@ automatically whenever tags are written. """ -from __future__ import division, absolute_import, print_function from beets.plugins import BeetsPlugin from beets import ui @@ -48,7 +46,7 @@ _MUTAGEN_FORMATS = { class ScrubPlugin(BeetsPlugin): """Removes extraneous metadata from files' tags.""" def __init__(self): - super(ScrubPlugin, self).__init__() + super().__init__() self.config.add({ 'auto': True, }) @@ -60,15 +58,15 @@ class ScrubPlugin(BeetsPlugin): def scrub_func(lib, opts, args): # Walk through matching files and remove tags. for item in lib.items(ui.decargs(args)): - self._log.info(u'scrubbing: {0}', + self._log.info('scrubbing: {0}', util.displayable_path(item.path)) self._scrub_item(item, opts.write) - scrub_cmd = ui.Subcommand('scrub', help=u'clean audio tags') + scrub_cmd = ui.Subcommand('scrub', help='clean audio tags') scrub_cmd.parser.add_option( - u'-W', u'--nowrite', dest='write', + '-W', '--nowrite', dest='write', action='store_false', default=True, - help=u'leave tags empty') + help='leave tags empty') scrub_cmd.func = scrub_func return [scrub_cmd] @@ -79,7 +77,7 @@ class ScrubPlugin(BeetsPlugin): """ classes = [] for modname, clsname in _MUTAGEN_FORMATS.items(): - mod = __import__('mutagen.{0}'.format(modname), + mod = __import__(f'mutagen.{modname}', fromlist=[clsname]) classes.append(getattr(mod, clsname)) return classes @@ -107,8 +105,8 @@ class ScrubPlugin(BeetsPlugin): for tag in f.keys(): del f[tag] f.save() - except (IOError, mutagen.MutagenError) as exc: - self._log.error(u'could not scrub {0}: {1}', + except (OSError, mutagen.MutagenError) as exc: + self._log.error('could not scrub {0}: {1}', util.displayable_path(path), exc) def _scrub_item(self, item, restore=True): @@ -121,7 +119,7 @@ class ScrubPlugin(BeetsPlugin): mf = mediafile.MediaFile(util.syspath(item.path), config['id3v23'].get(bool)) except mediafile.UnreadableFileError as exc: - self._log.error(u'could not open file to scrub: {0}', + self._log.error('could not open file to scrub: {0}', exc) return images = mf.images @@ -131,21 +129,21 @@ class ScrubPlugin(BeetsPlugin): # Restore tags, if enabled. if restore: - self._log.debug(u'writing new tags after scrub') + self._log.debug('writing new tags after scrub') item.try_write() if images: - self._log.debug(u'restoring art') + self._log.debug('restoring art') try: mf = mediafile.MediaFile(util.syspath(item.path), config['id3v23'].get(bool)) mf.images = images mf.save() except mediafile.UnreadableFileError as exc: - self._log.error(u'could not write tags: {0}', exc) + self._log.error('could not write tags: {0}', exc) def import_task_files(self, session, task): """Automatically scrub imported files.""" for item in task.imported_items(): - self._log.debug(u'auto-scrubbing {0}', + self._log.debug('auto-scrubbing {0}', util.displayable_path(item.path)) self._scrub_item(item) diff --git a/beetsplug/smartplaylist.py b/beetsplug/smartplaylist.py index 923703f57..d2043f0c9 100644 --- a/beetsplug/smartplaylist.py +++ b/beetsplug/smartplaylist.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Dang Mai . # @@ -16,7 +15,6 @@ """Generates smart playlists based on beets queries. """ -from __future__ import division, absolute_import, print_function from beets.plugins import BeetsPlugin from beets import ui @@ -38,14 +36,14 @@ except ImportError: class SmartPlaylistPlugin(BeetsPlugin): def __init__(self): - super(SmartPlaylistPlugin, self).__init__() + super().__init__() self.config.add({ 'relative_to': None, - 'playlist_dir': u'.', + 'playlist_dir': '.', 'auto': True, 'playlists': [], 'forward_slash': False, - 'prefix': u'', + 'prefix': '', 'urlencode': False, }) @@ -59,8 +57,8 @@ class SmartPlaylistPlugin(BeetsPlugin): def commands(self): spl_update = ui.Subcommand( 'splupdate', - help=u'update the smart playlists. Playlist names may be ' - u'passed as arguments.' + help='update the smart playlists. Playlist names may be ' + 'passed as arguments.' ) spl_update.func = self.update_cmd return [spl_update] @@ -71,14 +69,14 @@ class SmartPlaylistPlugin(BeetsPlugin): args = set(ui.decargs(args)) for a in list(args): if not a.endswith(".m3u"): - args.add("{0}.m3u".format(a)) + args.add(f"{a}.m3u") - playlists = set((name, q, a_q) + playlists = {(name, q, a_q) for name, q, a_q in self._unmatched_playlists - if name in args) + if name in args} if not playlists: raise ui.UserError( - u'No playlist matching any of {0} found'.format( + 'No playlist matching any of {} found'.format( [name for name, _, _ in self._unmatched_playlists]) ) @@ -109,7 +107,7 @@ class SmartPlaylistPlugin(BeetsPlugin): for playlist in self.config['playlists'].get(list): if 'name' not in playlist: - self._log.warning(u"playlist configuration is missing name") + self._log.warning("playlist configuration is missing name") continue playlist_data = (playlist['name'],) @@ -119,7 +117,7 @@ class SmartPlaylistPlugin(BeetsPlugin): qs = playlist.get(key) if qs is None: query_and_sort = None, None - elif isinstance(qs, six.string_types): + elif isinstance(qs, str): query_and_sort = parse_query_string(qs, model_cls) elif len(qs) == 1: query_and_sort = parse_query_string(qs[0], model_cls) @@ -146,7 +144,7 @@ class SmartPlaylistPlugin(BeetsPlugin): playlist_data += (query_and_sort,) except ParsingError as exc: - self._log.warning(u"invalid query in playlist {}: {}", + self._log.warning("invalid query in playlist {}: {}", playlist['name'], exc) continue @@ -167,14 +165,14 @@ class SmartPlaylistPlugin(BeetsPlugin): n, (q, _), (a_q, _) = playlist if self.matches(model, q, a_q): self._log.debug( - u"{0} will be updated because of {1}", n, model) + "{0} will be updated because of {1}", n, model) self._matched_playlists.add(playlist) self.register_listener('cli_exit', self.update_playlists) self._unmatched_playlists -= self._matched_playlists def update_playlists(self, lib): - self._log.info(u"Updating {0} smart playlists...", + self._log.info("Updating {0} smart playlists...", len(self._matched_playlists)) playlist_dir = self.config['playlist_dir'].as_filename() @@ -188,7 +186,7 @@ class SmartPlaylistPlugin(BeetsPlugin): for playlist in self._matched_playlists: name, (query, q_sort), (album_query, a_q_sort) = playlist - self._log.debug(u"Creating playlist {0}", name) + self._log.debug("Creating playlist {0}", name) items = [] if query: @@ -224,4 +222,4 @@ class SmartPlaylistPlugin(BeetsPlugin): path = bytestring_path(pathname2url(path)) f.write(prefix + path + b'\n') - self._log.info(u"{0} playlists updated", len(self._matched_playlists)) + self._log.info("{0} playlists updated", len(self._matched_playlists)) diff --git a/beetsplug/sonosupdate.py b/beetsplug/sonosupdate.py index 56a315a15..aeb211d80 100644 --- a/beetsplug/sonosupdate.py +++ b/beetsplug/sonosupdate.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2018, Tobias Sauerwein. # @@ -16,7 +15,6 @@ """Updates a Sonos library whenever the beets library is changed. This is based on the Kodi Update plugin. """ -from __future__ import division, absolute_import, print_function from beets.plugins import BeetsPlugin import soco @@ -24,7 +22,7 @@ import soco class SonosUpdate(BeetsPlugin): def __init__(self): - super(SonosUpdate, self).__init__() + super().__init__() self.register_listener('database_change', self.listen_for_db_change) def listen_for_db_change(self, lib, model): @@ -35,14 +33,14 @@ class SonosUpdate(BeetsPlugin): """When the client exists try to send refresh request to a Sonos controler. """ - self._log.info(u'Requesting a Sonos library update...') + self._log.info('Requesting a Sonos library update...') device = soco.discovery.any_soco() if device: device.music_library.start_library_update() else: - self._log.warning(u'Could not find a Sonos device.') + self._log.warning('Could not find a Sonos device.') return - self._log.info(u'Sonos update triggered') + self._log.info('Sonos update triggered') diff --git a/beetsplug/spotify.py b/beetsplug/spotify.py index 8fe0d394c..69d4f1c0d 100644 --- a/beetsplug/spotify.py +++ b/beetsplug/spotify.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2019, Rahul Ahuja. # @@ -16,7 +15,6 @@ """Adds Spotify release and track search support to the autotagger, along with Spotify playlist construction. """ -from __future__ import division, absolute_import, print_function import re import json @@ -53,7 +51,7 @@ class SpotifyPlugin(MetadataSourcePlugin, BeetsPlugin): } def __init__(self): - super(SpotifyPlugin, self).__init__() + super().__init__() self.config.add( { 'mode': 'list', @@ -81,7 +79,7 @@ class SpotifyPlugin(MetadataSourcePlugin, BeetsPlugin): try: with open(self.tokenfile) as f: token_data = json.load(f) - except IOError: + except OSError: self._authenticate() else: self.access_token = token_data['access_token'] @@ -109,7 +107,7 @@ class SpotifyPlugin(MetadataSourcePlugin, BeetsPlugin): response.raise_for_status() except requests.exceptions.HTTPError as e: raise ui.UserError( - u'Spotify authorization failed: {}\n{}'.format( + 'Spotify authorization failed: {}\n{}'.format( e, response.text ) ) @@ -117,7 +115,7 @@ class SpotifyPlugin(MetadataSourcePlugin, BeetsPlugin): # Save the token for later use. self._log.debug( - u'{} access token: {}', self.data_source, self.access_token + '{} access token: {}', self.data_source, self.access_token ) with open(self.tokenfile, 'w') as f: json.dump({'access_token': self.access_token}, f) @@ -138,11 +136,11 @@ class SpotifyPlugin(MetadataSourcePlugin, BeetsPlugin): """ response = request_type( url, - headers={'Authorization': 'Bearer {}'.format(self.access_token)}, + headers={'Authorization': f'Bearer {self.access_token}'}, params=params, ) if response.status_code != 200: - if u'token expired' in response.text: + if 'token expired' in response.text: self._log.debug( '{} access token has expired. Reauthenticating.', self.data_source, @@ -151,7 +149,7 @@ class SpotifyPlugin(MetadataSourcePlugin, BeetsPlugin): return self._handle_response(request_type, url, params=params) else: raise ui.UserError( - u'{} API error:\n{}\nURL:\n{}\nparams:\n{}'.format( + '{} API error:\n{}\nURL:\n{}\nparams:\n{}'.format( self.data_source, response.text, url, params ) ) @@ -191,8 +189,8 @@ class SpotifyPlugin(MetadataSourcePlugin, BeetsPlugin): day = None else: raise ui.UserError( - u"Invalid `release_date_precision` returned " - u"by {} API: '{}'".format( + "Invalid `release_date_precision` returned " + "by {} API: '{}'".format( self.data_source, release_date_precision ) ) @@ -303,7 +301,7 @@ class SpotifyPlugin(MetadataSourcePlugin, BeetsPlugin): ' '.join(':'.join((k, v)) for k, v in filters.items()), ] query = ' '.join([q for q in query_components if q]) - if not isinstance(query, six.text_type): + if not isinstance(query, str): query = query.decode('utf8') return unidecode.unidecode(query) @@ -328,7 +326,7 @@ class SpotifyPlugin(MetadataSourcePlugin, BeetsPlugin): if not query: return None self._log.debug( - u"Searching {} for '{}'".format(self.data_source, query) + f"Searching {self.data_source} for '{query}'" ) response_data = ( self._handle_response( @@ -340,7 +338,7 @@ class SpotifyPlugin(MetadataSourcePlugin, BeetsPlugin): .get('items', []) ) self._log.debug( - u"Found {} result(s) from {} for '{}'", + "Found {} result(s) from {} for '{}'", len(response_data), self.data_source, query, @@ -355,21 +353,21 @@ class SpotifyPlugin(MetadataSourcePlugin, BeetsPlugin): self._output_match_results(results) spotify_cmd = ui.Subcommand( - 'spotify', help=u'build a {} playlist'.format(self.data_source) + 'spotify', help=f'build a {self.data_source} playlist' ) spotify_cmd.parser.add_option( - u'-m', - u'--mode', + '-m', + '--mode', action='store', - help=u'"open" to open {} with playlist, ' - u'"list" to print (default)'.format(self.data_source), + help='"open" to open {} with playlist, ' + '"list" to print (default)'.format(self.data_source), ) spotify_cmd.parser.add_option( - u'-f', - u'--show-failures', + '-f', + '--show-failures', action='store_true', dest='show_failures', - help=u'list tracks that did not match a {} ID'.format( + help='list tracks that did not match a {} ID'.format( self.data_source ), ) @@ -385,7 +383,7 @@ class SpotifyPlugin(MetadataSourcePlugin, BeetsPlugin): if self.config['mode'].get() not in ['list', 'open']: self._log.warning( - u'{0} is not a valid mode', self.config['mode'].get() + '{0} is not a valid mode', self.config['mode'].get() ) return False @@ -411,12 +409,12 @@ class SpotifyPlugin(MetadataSourcePlugin, BeetsPlugin): if not items: self._log.debug( - u'Your beets query returned no items, skipping {}.', + 'Your beets query returned no items, skipping {}.', self.data_source, ) return - self._log.info(u'Processing {} tracks...', len(items)) + self._log.info('Processing {} tracks...', len(items)) for item in items: # Apply regex transformations if provided @@ -464,7 +462,7 @@ class SpotifyPlugin(MetadataSourcePlugin, BeetsPlugin): or self.config['tiebreak'].get() == 'first' ): self._log.debug( - u'{} track(s) found, count: {}', + '{} track(s) found, count: {}', self.data_source, len(response_data_tracks), ) @@ -472,7 +470,7 @@ class SpotifyPlugin(MetadataSourcePlugin, BeetsPlugin): else: # Use the popularity filter self._log.debug( - u'Most popular track chosen, count: {}', + 'Most popular track chosen, count: {}', len(response_data_tracks), ) chosen_result = max( @@ -484,17 +482,17 @@ class SpotifyPlugin(MetadataSourcePlugin, BeetsPlugin): if failure_count > 0: if self.config['show_failures'].get(): self._log.info( - u'{} track(s) did not match a {} ID:', + '{} track(s) did not match a {} ID:', failure_count, self.data_source, ) for track in failures: - self._log.info(u'track: {}', track) - self._log.info(u'') + self._log.info('track: {}', track) + self._log.info('') else: self._log.warning( - u'{} track(s) did not match a {} ID:\n' - u'use --show-failures to display', + '{} track(s) did not match a {} ID:\n' + 'use --show-failures to display', failure_count, self.data_source, ) @@ -513,7 +511,7 @@ class SpotifyPlugin(MetadataSourcePlugin, BeetsPlugin): spotify_ids = [track_data['id'] for track_data in results] if self.config['mode'].get() == 'open': self._log.info( - u'Attempting to open {} with playlist'.format( + 'Attempting to open {} with playlist'.format( self.data_source ) ) @@ -526,5 +524,5 @@ class SpotifyPlugin(MetadataSourcePlugin, BeetsPlugin): print(self.open_track_url + spotify_id) else: self._log.warning( - u'No {} tracks found from beets query'.format(self.data_source) + f'No {self.data_source} tracks found from beets query' ) diff --git a/beetsplug/subsonicplaylist.py b/beetsplug/subsonicplaylist.py index 5d64708ad..42b486f44 100644 --- a/beetsplug/subsonicplaylist.py +++ b/beetsplug/subsonicplaylist.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2019, Joris Jensen # @@ -13,7 +12,6 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -from __future__ import absolute_import, division, print_function import random import string @@ -56,7 +54,7 @@ def filter_to_be_removed(items, keys): class SubsonicPlaylistPlugin(BeetsPlugin): def __init__(self): - super(SubsonicPlaylistPlugin, self).__init__() + super().__init__() self.config.add( { 'delete': False, @@ -76,7 +74,7 @@ class SubsonicPlaylistPlugin(BeetsPlugin): MatchQuery("title", query[2])]) items = lib.items(query) if not items: - self._log.warn(u"{} | track not found ({})", playlist_tag, + self._log.warn("{} | track not found ({})", playlist_tag, query) continue for item in items: @@ -130,13 +128,13 @@ class SubsonicPlaylistPlugin(BeetsPlugin): self.update_tags(playlist_dict, lib) subsonicplaylist_cmds = Subcommand( - 'subsonicplaylist', help=u'import a subsonic playlist' + 'subsonicplaylist', help='import a subsonic playlist' ) subsonicplaylist_cmds.parser.add_option( - u'-d', - u'--delete', + '-d', + '--delete', action='store_true', - help=u'delete tag from items not in any playlist anymore', + help='delete tag from items not in any playlist anymore', ) subsonicplaylist_cmds.func = build_playlist return [subsonicplaylist_cmds] diff --git a/beetsplug/subsonicupdate.py b/beetsplug/subsonicupdate.py index 89b59429f..9480bcb41 100644 --- a/beetsplug/subsonicupdate.py +++ b/beetsplug/subsonicupdate.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -29,7 +28,6 @@ is not supported, use password instead: pass: password auth: pass """ -from __future__ import division, absolute_import, print_function import hashlib import random @@ -46,7 +44,7 @@ __author__ = 'https://github.com/maffo999' class SubsonicUpdate(BeetsPlugin): def __init__(self): - super(SubsonicUpdate, self).__init__() + super().__init__() # Set default configuration values config['subsonic'].add({ 'user': 'admin', @@ -94,16 +92,16 @@ class SubsonicUpdate(BeetsPlugin): context_path = config['subsonic']['contextpath'].as_str() if context_path == '/': context_path = '' - url = "http://{}:{}{}".format(host, port, context_path) + url = f"http://{host}:{port}{context_path}" - return url + '/rest/{}'.format(endpoint) + return url + f'/rest/{endpoint}' def start_scan(self): user = config['subsonic']['user'].as_str() auth = config['subsonic']['auth'].as_str() url = self.__format_url("startScan") - self._log.debug(u'URL is {0}', url) - self._log.debug(u'auth type is {0}', config['subsonic']['auth']) + self._log.debug('URL is {0}', url) + self._log.debug('auth type is {0}', config['subsonic']['auth']) if auth == "token": salt, token = self.__create_token() @@ -120,7 +118,7 @@ class SubsonicUpdate(BeetsPlugin): encpass = hexlify(password.encode()).decode() payload = { 'u': user, - 'p': 'enc:{}'.format(encpass), + 'p': f'enc:{encpass}', 'v': '1.12.0', 'c': 'beets', 'f': 'json' @@ -135,12 +133,12 @@ class SubsonicUpdate(BeetsPlugin): json['subsonic-response']['status'] == "ok": count = json['subsonic-response']['scanStatus']['count'] self._log.info( - u'Updating Subsonic; scanning {0} tracks'.format(count)) + f'Updating Subsonic; scanning {count} tracks') elif response.status_code == 200 and \ json['subsonic-response']['status'] == "failed": error_message = json['subsonic-response']['error']['message'] - self._log.error(u'Error: {0}'.format(error_message)) + self._log.error(f'Error: {error_message}') else: - self._log.error(u'Error: {0}', json) + self._log.error('Error: {0}', json) except Exception as error: - self._log.error(u'Error: {0}'.format(error)) + self._log.error(f'Error: {error}') diff --git a/beetsplug/the.py b/beetsplug/the.py index dfc58817d..e6626d2b2 100644 --- a/beetsplug/the.py +++ b/beetsplug/the.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Blemjhoo Tezoulbr . # @@ -15,7 +14,6 @@ """Moves patterns in path formats (suitable for moving articles).""" -from __future__ import division, absolute_import, print_function import re from beets.plugins import BeetsPlugin @@ -23,9 +21,9 @@ from beets.plugins import BeetsPlugin __author__ = 'baobab@heresiarch.info' __version__ = '1.1' -PATTERN_THE = u'^the\\s' -PATTERN_A = u'^[a][n]?\\s' -FORMAT = u'{0}, {1}' +PATTERN_THE = '^the\\s' +PATTERN_A = '^[a][n]?\\s' +FORMAT = '{0}, {1}' class ThePlugin(BeetsPlugin): @@ -33,14 +31,14 @@ class ThePlugin(BeetsPlugin): patterns = [] def __init__(self): - super(ThePlugin, self).__init__() + super().__init__() self.template_funcs['the'] = self.the_template_func self.config.add({ 'the': True, 'a': True, - 'format': u'{0}, {1}', + 'format': '{0}, {1}', 'strip': False, 'patterns': [], }) @@ -51,17 +49,17 @@ class ThePlugin(BeetsPlugin): try: re.compile(p) except re.error: - self._log.error(u'invalid pattern: {0}', p) + self._log.error('invalid pattern: {0}', p) else: if not (p.startswith('^') or p.endswith('$')): - self._log.warning(u'warning: \"{0}\" will not ' - u'match string start/end', p) + self._log.warning('warning: \"{0}\" will not ' + 'match string start/end', p) if self.config['a']: self.patterns = [PATTERN_A] + self.patterns if self.config['the']: self.patterns = [PATTERN_THE] + self.patterns if not self.patterns: - self._log.warning(u'no patterns defined!') + self._log.warning('no patterns defined!') def unthe(self, text, pattern): """Moves pattern in the path format string or strips it @@ -84,7 +82,7 @@ class ThePlugin(BeetsPlugin): fmt = self.config['format'].as_str() return fmt.format(r, t.strip()).strip() else: - return u'' + return '' def the_template_func(self, text): if not self.patterns: @@ -93,8 +91,8 @@ class ThePlugin(BeetsPlugin): for p in self.patterns: r = self.unthe(text, p) if r != text: - self._log.debug(u'\"{0}\" -> \"{1}\"', text, r) + self._log.debug('\"{0}\" -> \"{1}\"', text, r) break return r else: - return u'' + return '' diff --git a/beetsplug/thumbnails.py b/beetsplug/thumbnails.py index 1b262eca5..c4ad024dc 100644 --- a/beetsplug/thumbnails.py +++ b/beetsplug/thumbnails.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Bruno Cauet # @@ -19,7 +18,6 @@ This plugin is POSIX-only. Spec: standards.freedesktop.org/thumbnail-spec/latest/index.html """ -from __future__ import division, absolute_import, print_function from hashlib import md5 import os @@ -45,7 +43,7 @@ LARGE_DIR = util.bytestring_path(os.path.join(BASE_DIR, "large")) class ThumbnailsPlugin(BeetsPlugin): def __init__(self): - super(ThumbnailsPlugin, self).__init__() + super().__init__() self.config.add({ 'auto': True, 'force': False, @@ -58,15 +56,15 @@ class ThumbnailsPlugin(BeetsPlugin): def commands(self): thumbnails_command = Subcommand("thumbnails", - help=u"Create album thumbnails") + help="Create album thumbnails") thumbnails_command.parser.add_option( - u'-f', u'--force', + '-f', '--force', dest='force', action='store_true', default=False, - help=u'force regeneration of thumbnails deemed fine (existing & ' - u'recent enough)') + help='force regeneration of thumbnails deemed fine (existing & ' + 'recent enough)') thumbnails_command.parser.add_option( - u'--dolphin', dest='dolphin', action='store_true', default=False, - help=u"create Dolphin-compatible thumbnail information (for KDE)") + '--dolphin', dest='dolphin', action='store_true', default=False, + help="create Dolphin-compatible thumbnail information (for KDE)") thumbnails_command.func = self.process_query return [thumbnails_command] @@ -85,8 +83,8 @@ class ThumbnailsPlugin(BeetsPlugin): - detect whether we'll use GIO or Python to get URIs """ if not ArtResizer.shared.local: - self._log.warning(u"No local image resizing capabilities, " - u"cannot generate thumbnails") + self._log.warning("No local image resizing capabilities, " + "cannot generate thumbnails") return False for dir in (NORMAL_DIR, LARGE_DIR): @@ -100,12 +98,12 @@ class ThumbnailsPlugin(BeetsPlugin): 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) + self._log.debug("using {0} to write metadata", tool) uri_getter = GioURI() if not uri_getter.available: uri_getter = PathlibURI() - self._log.debug(u"using {0.name} to compute URIs", uri_getter) + self._log.debug("using {0.name} to compute URIs", uri_getter) self.get_uri = uri_getter.uri return True @@ -113,9 +111,9 @@ class ThumbnailsPlugin(BeetsPlugin): def process_album(self, album): """Produce thumbnails for the album folder. """ - self._log.debug(u'generating thumbnail for {0}', album) + self._log.debug('generating thumbnail for {0}', album) if not album.artpath: - self._log.info(u'album {0} has no art', album) + self._log.info('album {0} has no art', album) return if self.config['dolphin']: @@ -123,7 +121,7 @@ class ThumbnailsPlugin(BeetsPlugin): size = ArtResizer.shared.get_size(album.artpath) if not size: - self._log.warning(u'problem getting the picture size for {0}', + self._log.warning('problem getting the picture size for {0}', album.artpath) return @@ -133,9 +131,9 @@ class ThumbnailsPlugin(BeetsPlugin): wrote &= self.make_cover_thumbnail(album, 128, NORMAL_DIR) if wrote: - self._log.info(u'wrote thumbnail for {0}', album) + self._log.info('wrote thumbnail for {0}', album) else: - self._log.info(u'nothing to do for {0}', album) + self._log.info('nothing to do for {0}', album) def make_cover_thumbnail(self, album, size, target_dir): """Make a thumbnail of given size for `album` and put it in @@ -146,11 +144,11 @@ class ThumbnailsPlugin(BeetsPlugin): if os.path.exists(target) and \ os.stat(target).st_mtime > os.stat(album.artpath).st_mtime: if self.config['force']: - self._log.debug(u"found a suitable {1}x{1} thumbnail for {0}, " - u"forcing regeneration", album, size) + self._log.debug("found a suitable {1}x{1} thumbnail for {0}, " + "forcing regeneration", album, size) else: - self._log.debug(u"{1}x{1} thumbnail for {0} exists and is " - u"recent enough", album, size) + self._log.debug("{1}x{1} thumbnail for {0} exists and is " + "recent enough", album, size) return False resized = ArtResizer.shared.resize(size, album.artpath, util.syspath(target)) @@ -164,7 +162,7 @@ class ThumbnailsPlugin(BeetsPlugin): """ uri = self.get_uri(path) hash = md5(uri.encode('utf-8')).hexdigest() - return util.bytestring_path("{0}.png".format(hash)) + return util.bytestring_path(f"{hash}.png") def add_tags(self, album, image_path): """Write required metadata to the thumbnail @@ -172,11 +170,11 @@ class ThumbnailsPlugin(BeetsPlugin): """ mtime = os.stat(album.artpath).st_mtime metadata = {"Thumb::URI": self.get_uri(album.artpath), - "Thumb::MTime": six.text_type(mtime)} + "Thumb::MTime": str(mtime)} try: self.write_metadata(image_path, metadata) except Exception: - self._log.exception(u"could not write metadata to {0}", + self._log.exception("could not write metadata to {0}", util.displayable_path(image_path)) def make_dolphin_cover_thumbnail(self, album): @@ -186,9 +184,9 @@ class ThumbnailsPlugin(BeetsPlugin): artfile = os.path.split(album.artpath)[1] with open(outfilename, 'w') as f: f.write('[Desktop Entry]\n') - f.write('Icon=./{0}'.format(artfile.decode('utf-8'))) + f.write('Icon=./{}'.format(artfile.decode('utf-8'))) f.close() - self._log.debug(u"Wrote file {0}", util.displayable_path(outfilename)) + self._log.debug("Wrote file {0}", util.displayable_path(outfilename)) def write_metadata_im(file, metadata): @@ -211,7 +209,7 @@ def write_metadata_pil(file, metadata): return True -class URIGetter(object): +class URIGetter: available = False name = "Abstract base" @@ -269,7 +267,7 @@ class GioURI(URIGetter): def uri(self, path): g_file_ptr = self.libgio.g_file_new_for_path(path) if not g_file_ptr: - raise RuntimeError(u"No gfile pointer received for {0}".format( + raise RuntimeError("No gfile pointer received for {}".format( util.displayable_path(path))) try: @@ -278,8 +276,8 @@ class GioURI(URIGetter): self.libgio.g_object_unref(g_file_ptr) if not uri_ptr: self.libgio.g_free(uri_ptr) - raise RuntimeError(u"No URI received from the gfile pointer for " - u"{0}".format(util.displayable_path(path))) + raise RuntimeError("No URI received from the gfile pointer for " + "{}".format(util.displayable_path(path))) try: uri = copy_c_string(uri_ptr) @@ -290,5 +288,5 @@ class GioURI(URIGetter): return uri.decode(util._fsencoding()) except UnicodeDecodeError: raise RuntimeError( - "Could not decode filename from GIO: {!r}".format(uri) + f"Could not decode filename from GIO: {uri!r}" ) diff --git a/beetsplug/types.py b/beetsplug/types.py index 4a39f05b1..930d5e869 100644 --- a/beetsplug/types.py +++ b/beetsplug/types.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Thomas Scholtes. # @@ -13,7 +12,6 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -from __future__ import division, absolute_import, print_function from beets.plugins import BeetsPlugin from beets.dbcore import types @@ -47,6 +45,6 @@ class TypesPlugin(BeetsPlugin): mytypes[key] = library.DateType() else: raise ConfigValueError( - u"unknown type '{0}' for the '{1}' field" + "unknown type '{}' for the '{}' field" .format(value, key)) return mytypes diff --git a/beetsplug/unimported.py b/beetsplug/unimported.py index 544e9de46..7c92def91 100644 --- a/beetsplug/unimported.py +++ b/beetsplug/unimported.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2019, Joris Jensen # @@ -18,7 +17,6 @@ List all files in the library folder which are not listed in the beets library database, including art files """ -from __future__ import absolute_import, division, print_function import os from beets import util @@ -31,7 +29,7 @@ __author__ = 'https://github.com/MrNuggelz' class Unimported(BeetsPlugin): def __init__(self): - super(Unimported, self).__init__() + super().__init__() self.config.add( { 'ignore_extensions': [] @@ -42,13 +40,13 @@ class Unimported(BeetsPlugin): def print_unimported(lib, opts, args): ignore_exts = [('.' + x).encode() for x in self.config['ignore_extensions'].as_str_seq()] - in_folder = set( - (os.path.join(r, file) for r, d, f in os.walk(lib.directory) + in_folder = { + os.path.join(r, file) for r, d, f in os.walk(lib.directory) for file in f if not any( [file.endswith(extension) for extension in - ignore_exts]))) - in_library = set(x.path for x in lib.items()) - art_files = set(x.artpath for x in lib.albums()) + ignore_exts])} + in_library = {x.path for x in lib.items()} + art_files = {x.artpath for x in lib.albums()} for f in in_folder - in_library - art_files: print_(util.displayable_path(f)) diff --git a/beetsplug/web/__init__.py b/beetsplug/web/__init__.py index c74cd0748..240126e95 100644 --- a/beetsplug/web/__init__.py +++ b/beetsplug/web/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -14,7 +13,6 @@ # included in all copies or substantial portions of the Software. """A Web interface to beets.""" -from __future__ import division, absolute_import, print_function from beets.plugins import BeetsPlugin from beets import ui @@ -155,7 +153,7 @@ def resource(name, patchable=False): else: return flask.abort(405) - responder.__name__ = 'get_{0}'.format(name) + responder.__name__ = f'get_{name}' return responder return make_responder @@ -203,7 +201,7 @@ def resource_query(name, patchable=False): else: return flask.abort(405) - responder.__name__ = 'query_{0}'.format(name) + responder.__name__ = f'query_{name}' return responder @@ -220,7 +218,7 @@ def resource_list(name): json_generator(list_all(), root=name, expand=is_expand()), mimetype='application/json' ) - responder.__name__ = 'all_{0}'.format(name) + responder.__name__ = f'all_{name}' return responder return make_responder @@ -230,7 +228,7 @@ def _get_unique_table_field_values(model, field, sort_field): if field not in model.all_keys() or sort_field not in model.all_keys(): raise KeyError with g.lib.transaction() as tx: - rows = tx.query('SELECT DISTINCT "{0}" FROM "{1}" ORDER BY "{2}"' + rows = tx.query('SELECT DISTINCT "{}" FROM "{}" ORDER BY "{}"' .format(field, model._table, sort_field)) return [row[0] for row in rows] @@ -434,9 +432,9 @@ def home(): class WebPlugin(BeetsPlugin): def __init__(self): - super(WebPlugin, self).__init__() + super().__init__() self.config.add({ - 'host': u'127.0.0.1', + 'host': '127.0.0.1', 'port': 8337, 'cors': '', 'cors_supports_credentials': False, @@ -446,9 +444,9 @@ class WebPlugin(BeetsPlugin): }) def commands(self): - cmd = ui.Subcommand('web', help=u'start a Web interface') - cmd.parser.add_option(u'-d', u'--debug', action='store_true', - default=False, help=u'debug mode') + cmd = ui.Subcommand('web', help='start a Web interface') + cmd.parser.add_option('-d', '--debug', action='store_true', + default=False, help='debug mode') def func(lib, opts, args): args = ui.decargs(args) @@ -466,7 +464,7 @@ class WebPlugin(BeetsPlugin): # Enable CORS if required. if self.config['cors']: - self._log.info(u'Enabling CORS with origin: {0}', + self._log.info('Enabling CORS with origin: {0}', self.config['cors']) from flask_cors import CORS app.config['CORS_ALLOW_HEADERS'] = "Content-Type" @@ -492,7 +490,7 @@ class WebPlugin(BeetsPlugin): return [cmd] -class ReverseProxied(object): +class ReverseProxied: '''Wrap the application in this middleware and configure the front-end server to add these headers, to let you quietly bind this to a URL other than / and to an HTTP scheme that is diff --git a/beetsplug/zero.py b/beetsplug/zero.py index 0cca199c5..8d90e952a 100644 --- a/beetsplug/zero.py +++ b/beetsplug/zero.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Blemjhoo Tezoulbr . # @@ -15,7 +14,6 @@ """ Clears tag fields in media files.""" -from __future__ import division, absolute_import, print_function import six import re @@ -31,7 +29,7 @@ __author__ = 'baobab@heresiarch.info' class ZeroPlugin(BeetsPlugin): def __init__(self): - super(ZeroPlugin, self).__init__() + super().__init__() self.register_listener('write', self.write_event) self.register_listener('import_task_choice', @@ -56,7 +54,7 @@ class ZeroPlugin(BeetsPlugin): """ if self.config['fields'] and self.config['keep_fields']: self._log.warning( - u'cannot blacklist and whitelist at the same time' + 'cannot blacklist and whitelist at the same time' ) # Blacklist mode. elif self.config['fields']: @@ -75,7 +73,7 @@ class ZeroPlugin(BeetsPlugin): def zero_fields(lib, opts, args): if not decargs(args) and not input_yn( - u"Remove fields for all items? (Y/n)", + "Remove fields for all items? (Y/n)", True): return for item in lib.items(decargs(args)): @@ -89,10 +87,10 @@ class ZeroPlugin(BeetsPlugin): Do some sanity checks then compile the regexes. """ if field not in MediaFile.fields(): - self._log.error(u'invalid field: {0}', field) + self._log.error('invalid field: {0}', field) elif field in ('id', 'path', 'album_id'): - self._log.warning(u'field \'{0}\' ignored, zeroing ' - u'it would be dangerous', field) + self._log.warning('field \'{0}\' ignored, zeroing ' + 'it would be dangerous', field) else: try: for pattern in self.config[field].as_str_seq(): @@ -104,7 +102,7 @@ class ZeroPlugin(BeetsPlugin): def import_task_choice_event(self, session, task): if task.choice_flag == action.ASIS and not self.warned: - self._log.warning(u'cannot zero in \"as-is\" mode') + self._log.warning('cannot zero in \"as-is\" mode') self.warned = True # TODO request write in as-is mode @@ -122,7 +120,7 @@ class ZeroPlugin(BeetsPlugin): fields_set = False if not self.fields_to_progs: - self._log.warning(u'no fields, nothing to do') + self._log.warning('no fields, nothing to do') return False for field, progs in self.fields_to_progs.items(): @@ -135,7 +133,7 @@ class ZeroPlugin(BeetsPlugin): if match: fields_set = True - self._log.debug(u'{0}: {1} -> None', field, value) + self._log.debug('{0}: {1} -> None', field, value) tags[field] = None if self.config['update_database']: item[field] = None @@ -158,6 +156,6 @@ def _match_progs(value, progs): if not progs: return True for prog in progs: - if prog.search(six.text_type(value)): + if prog.search(str(value)): return True return False diff --git a/test/__init__.py b/test/__init__.py index 8214c3b8e..f34357b7c 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -1,5 +1,2 @@ -# -*- coding: utf-8 -*- - # Make python -m testall.py work. -from __future__ import division, absolute_import, print_function diff --git a/test/_common.py b/test/_common.py index 2f2cffcf7..8dfb50867 100644 --- a/test/_common.py +++ b/test/_common.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -14,7 +13,6 @@ # included in all copies or substantial portions of the Software. """Some common functionality for beets' test cases.""" -from __future__ import division, absolute_import, print_function import time import sys @@ -63,18 +61,18 @@ def item(lib=None): global _item_ident _item_ident += 1 i = beets.library.Item( - title=u'the title', - artist=u'the artist', - albumartist=u'the album artist', - album=u'the album', - genre=u'the genre', - lyricist=u'the lyricist', - composer=u'the composer', - arranger=u'the arranger', - grouping=u'the grouping', - work=u'the work title', - mb_workid=u'the work musicbrainz id', - work_disambig=u'the work disambiguation', + title='the title', + artist='the artist', + albumartist='the album artist', + album='the album', + genre='the genre', + lyricist='the lyricist', + composer='the composer', + arranger='the arranger', + grouping='the grouping', + work='the work title', + mb_workid='the work musicbrainz id', + work_disambig='the work disambiguation', year=1, month=2, day=3, @@ -82,11 +80,11 @@ def item(lib=None): tracktotal=5, disc=6, disctotal=7, - lyrics=u'the lyrics', - comments=u'the comments', + lyrics='the lyrics', + comments='the comments', bpm=8, comp=True, - path='somepath{0}'.format(_item_ident), + path=f'somepath{_item_ident}', length=60.0, bitrate=128000, format='FLAC', @@ -110,11 +108,11 @@ def album(lib=None): _item_ident += 1 i = beets.library.Album( artpath=None, - albumartist=u'some album artist', - albumartist_sort=u'some sort album artist', - albumartist_credit=u'some album artist credit', - album=u'the album', - genre=u'the genre', + albumartist='some album artist', + albumartist_sort='some sort album artist', + albumartist_credit='some album artist credit', + album='the album', + genre='the genre', year=2014, month=2, day=5, @@ -135,21 +133,21 @@ def import_session(lib=None, loghandler=None, paths=[], query=[], cli=False): return cls(lib, loghandler, paths, query) -class Assertions(object): +class Assertions: """A mixin with additional unit test assertions.""" def assertExists(self, path): # noqa self.assertTrue(os.path.exists(util.syspath(path)), - u'file does not exist: {!r}'.format(path)) + f'file does not exist: {path!r}') def assertNotExists(self, path): # noqa self.assertFalse(os.path.exists(util.syspath(path)), - u'file exists: {!r}'.format((path))) + f'file exists: {path!r}') def assert_equal_path(self, a, b): """Check that two paths are equal.""" self.assertEqual(util.normpath(a), util.normpath(b), - u'paths are not equal: {!r} and {!r}'.format(a, b)) + f'paths are not equal: {a!r} and {b!r}') # A test harness for all beets tests. @@ -202,18 +200,18 @@ class LibTestCase(TestCase): an item added to the library (`i`). """ def setUp(self): - super(LibTestCase, self).setUp() + super().setUp() self.lib = beets.library.Library(':memory:') self.i = item(self.lib) def tearDown(self): self.lib._connection().close() - super(LibTestCase, self).tearDown() + super().tearDown() # Mock timing. -class Timecop(object): +class Timecop: """Mocks the timing system (namely time() and sleep()) for testing. Inspired by the Ruby timecop library. """ @@ -248,11 +246,11 @@ class InputException(Exception): def __str__(self): msg = "Attempt to read with no input provided." if self.output is not None: - msg += " Output: {!r}".format(self.output) + msg += f" Output: {self.output!r}" return msg -class DummyOut(object): +class DummyOut: encoding = 'utf-8' def __init__(self): @@ -271,7 +269,7 @@ class DummyOut(object): self.buf = [] -class DummyIn(object): +class DummyIn: encoding = 'utf-8' def __init__(self, out=None): @@ -295,7 +293,7 @@ class DummyIn(object): return self.buf.pop(0) -class DummyIO(object): +class DummyIO: """Mocks input and output streams for testing UI code.""" def __init__(self): self.stdout = DummyOut() @@ -327,7 +325,7 @@ def touch(path): open(path, 'a').close() -class Bag(object): +class Bag: """An object that exposes a set of fields given as keyword arguments. Any field not found in the dictionary appears to be None. Used for mocking Album objects and the like. @@ -342,7 +340,7 @@ class Bag(object): # Convenience methods for setting up a temporary sandbox directory for tests # that need to interact with the filesystem. -class TempDirMixin(object): +class TempDirMixin: """Text mixin for creating and deleting a temporary directory. """ @@ -401,5 +399,5 @@ def slow_test(unused=None): def _id(obj): return obj if 'SKIP_SLOW_TESTS' in os.environ: - return unittest.skip(u'test is slow') + return unittest.skip('test is slow') return _id diff --git a/test/helper.py b/test/helper.py index 69717830b..fcd997648 100644 --- a/test/helper.py +++ b/test/helper.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Thomas Scholtes. # @@ -31,7 +30,6 @@ information or mock the environment. """ -from __future__ import division, absolute_import, print_function import sys import os @@ -66,7 +64,7 @@ class LogCapture(logging.Handler): self.messages = [] def emit(self, record): - self.messages.append(six.text_type(record.msg)) + self.messages.append(str(record.msg)) @contextmanager @@ -142,7 +140,7 @@ def has_program(cmd, args=['--version']): return True -class TestHelper(object): +class TestHelper: """Helper mixin for high-level cli and plugin tests. This mixin provides methods to isolate beets' global state provide @@ -251,7 +249,7 @@ class TestHelper(object): album_no = 0 while album_count: - album = util.bytestring_path(u'album {0}'.format(album_no)) + album = util.bytestring_path(f'album {album_no}') album_dir = os.path.join(import_dir, album) if os.path.exists(album_dir): album_no += 1 @@ -262,9 +260,9 @@ class TestHelper(object): track_no = 0 album_item_count = item_count while album_item_count: - title = u'track {0}'.format(track_no) + title = f'track {track_no}' src = os.path.join(_common.RSRC, b'full.mp3') - title_file = util.bytestring_path('{0}.mp3'.format(title)) + title_file = util.bytestring_path(f'{title}.mp3') dest = os.path.join(album_dir, title_file) if os.path.exists(dest): track_no += 1 @@ -305,9 +303,9 @@ class TestHelper(object): """ item_count = self._get_item_count() values_ = { - 'title': u't\u00eftle {0}', - 'artist': u'the \u00e4rtist', - 'album': u'the \u00e4lbum', + 'title': 't\u00eftle {0}', + 'artist': 'the \u00e4rtist', + 'album': 'the \u00e4lbum', 'track': item_count, 'format': 'MP3', } @@ -367,8 +365,8 @@ class TestHelper(object): path = os.path.join(_common.RSRC, util.bytestring_path('full.' + ext)) for i in range(count): item = Item.from_path(path) - item.album = u'\u00e4lbum {0}'.format(i) # Check unicode paths - item.title = u't\u00eftle {0}'.format(i) + item.album = f'\u00e4lbum {i}' # Check unicode paths + item.title = f't\u00eftle {i}' # mtime needs to be set last since other assignments reset it. item.mtime = 12345 item.add(self.lib) @@ -384,8 +382,8 @@ class TestHelper(object): path = os.path.join(_common.RSRC, util.bytestring_path('full.' + ext)) for i in range(track_count): item = Item.from_path(path) - item.album = u'\u00e4lbum' # Check unicode paths - item.title = u't\u00eftle {0}'.format(i) + item.album = '\u00e4lbum' # Check unicode paths + item.title = f't\u00eftle {i}' # mtime needs to be set last since other assignments reset it. item.mtime = 12345 item.add(self.lib) @@ -414,7 +412,7 @@ class TestHelper(object): mediafile = MediaFile(path) imgs = [] for img_ext in images: - file = util.bytestring_path('image-2x3.{0}'.format(img_ext)) + file = util.bytestring_path(f'image-2x3.{img_ext}') img_path = os.path.join(_common.RSRC, file) with open(img_path, 'rb') as f: imgs.append(Image(f.read())) @@ -509,7 +507,7 @@ class ImportSessionFixture(importer.ImportSession): """ def __init__(self, *args, **kwargs): - super(ImportSessionFixture, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self._choices = [] self._resolutions = [] @@ -569,14 +567,14 @@ def generate_album_info(album_id, track_values): """ tracks = [generate_track_info(id, values) for id, values in track_values] album = AlbumInfo( - album_id=u'album info', - album=u'album info', - artist=u'album info', - artist_id=u'album info', + album_id='album info', + album='album info', + artist='album info', + artist_id='album info', tracks=tracks, ) for field in ALBUM_INFO_FIELDS: - setattr(album, field, u'album info') + setattr(album, field, 'album info') return album @@ -595,11 +593,11 @@ def generate_track_info(track_id='track info', values={}): string fields are set to "track info". """ track = TrackInfo( - title=u'track info', + title='track info', track_id=track_id, ) for field in TRACK_INFO_FIELDS: - setattr(track, field, u'track info') + setattr(track, field, 'track info') for field, value in values.items(): setattr(track, field, value) return track diff --git a/test/lyrics_download_samples.py b/test/lyrics_download_samples.py index 9e7b69731..f33eafa5d 100644 --- a/test/lyrics_download_samples.py +++ b/test/lyrics_download_samples.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Fabrice Laporte # @@ -13,7 +12,6 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -from __future__ import division, absolute_import, print_function import os import sys @@ -44,7 +42,7 @@ def main(argv=None): """ if argv is None: argv = sys.argv - print(u'Fetching samples from:') + print('Fetching samples from:') for s in test_lyrics.GOOGLE_SOURCES + test_lyrics.DEFAULT_SOURCES: print(s['url']) url = s['url'] + s['path'] diff --git a/test/test_acousticbrainz.py b/test/test_acousticbrainz.py index 4c0b0137b..880f55438 100644 --- a/test/test_acousticbrainz.py +++ b/test/test_acousticbrainz.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Nathan Dwek. # @@ -16,7 +15,6 @@ """Tests for the 'acousticbrainz' plugin. """ -from __future__ import division, absolute_import, print_function import json import os.path diff --git a/test/test_art.py b/test/test_art.py index d84ca4a91..0377da8d8 100644 --- a/test/test_art.py +++ b/test/test_art.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -15,14 +14,13 @@ """Tests for the album art fetchers.""" -from __future__ import division, absolute_import, print_function import os import shutil import unittest import responses -from mock import patch +from unittest.mock import patch from test import _common from test.helper import capture_log @@ -51,7 +49,7 @@ class Settings(): class UseThePlugin(_common.TestCase): def setUp(self): - super(UseThePlugin, self).setUp() + super().setUp() self.plugin = fetchart.FetchArtPlugin() @@ -61,7 +59,7 @@ class FetchImageHelper(_common.TestCase): """ @responses.activate def run(self, *args, **kwargs): - super(FetchImageHelper, self).run(*args, **kwargs) + super().run(*args, **kwargs) IMAGEHEADER = {'image/jpeg': b'\x00' * 6 + b'JFIF', 'image/png': b'\211PNG\r\n\032\n', } @@ -81,9 +79,9 @@ class CAAHelper(): MBID_RELASE = 'rid' MBID_GROUP = 'rgid' - RELEASE_URL = 'coverartarchive.org/release/{0}' \ + RELEASE_URL = 'coverartarchive.org/release/{}' \ .format(MBID_RELASE) - GROUP_URL = 'coverartarchive.org/release-group/{0}' \ + GROUP_URL = 'coverartarchive.org/release-group/{}' \ .format(MBID_GROUP) if util.SNI_SUPPORTED: @@ -170,7 +168,7 @@ class FetchImageTest(FetchImageHelper, UseThePlugin): URL = 'http://example.com/test.jpg' def setUp(self): - super(FetchImageTest, self).setUp() + super().setUp() self.dpath = os.path.join(self.temp_dir, b'arttest') self.source = fetchart.RemoteArtSource(logger, self.plugin.config) self.settings = Settings(maxwidth=0) @@ -201,7 +199,7 @@ class FetchImageTest(FetchImageHelper, UseThePlugin): class FSArtTest(UseThePlugin): def setUp(self): - super(FSArtTest, self).setUp() + super().setUp() self.dpath = os.path.join(self.temp_dir, b'arttest') os.mkdir(self.dpath) @@ -249,13 +247,13 @@ class FSArtTest(UseThePlugin): class CombinedTest(FetchImageHelper, UseThePlugin, CAAHelper): ASIN = 'xxxx' MBID = 'releaseid' - AMAZON_URL = 'https://images.amazon.com/images/P/{0}.01.LZZZZZZZ.jpg' \ + AMAZON_URL = 'https://images.amazon.com/images/P/{}.01.LZZZZZZZ.jpg' \ .format(ASIN) - AAO_URL = 'https://www.albumart.org/index_detail.php?asin={0}' \ + AAO_URL = 'https://www.albumart.org/index_detail.php?asin={}' \ .format(ASIN) def setUp(self): - super(CombinedTest, self).setUp() + super().setUp() self.dpath = os.path.join(self.temp_dir, b'arttest') os.mkdir(self.dpath) @@ -330,16 +328,16 @@ class CombinedTest(FetchImageHelper, UseThePlugin, CAAHelper): class AAOTest(UseThePlugin): ASIN = 'xxxx' - AAO_URL = 'https://www.albumart.org/index_detail.php?asin={0}'.format(ASIN) + AAO_URL = f'https://www.albumart.org/index_detail.php?asin={ASIN}' def setUp(self): - super(AAOTest, self).setUp() + super().setUp() self.source = fetchart.AlbumArtOrg(logger, self.plugin.config) self.settings = Settings() @responses.activate def run(self, *args, **kwargs): - super(AAOTest, self).run(*args, **kwargs) + super().run(*args, **kwargs) def mock_response(self, url, body): responses.add(responses.GET, url, body=body, content_type='text/html', @@ -367,14 +365,14 @@ class AAOTest(UseThePlugin): class ITunesStoreTest(UseThePlugin): def setUp(self): - super(ITunesStoreTest, self).setUp() + super().setUp() self.source = fetchart.ITunesStore(logger, self.plugin.config) self.settings = Settings() self.album = _common.Bag(albumartist="some artist", album="some album") @responses.activate def run(self, *args, **kwargs): - super(ITunesStoreTest, self).run(*args, **kwargs) + super().run(*args, **kwargs) def mock_response(self, url, json): responses.add(responses.GET, url, body=json, @@ -399,7 +397,7 @@ class ITunesStoreTest(UseThePlugin): def test_itunesstore_no_result(self): json = '{"results": []}' self.mock_response(fetchart.ITunesStore.API_URL, json) - expected = u"got no results" + expected = "got no results" with capture_log('beets.test_art') as logs: with self.assertRaises(StopIteration): @@ -409,7 +407,7 @@ class ITunesStoreTest(UseThePlugin): def test_itunesstore_requestexception(self): responses.add(responses.GET, fetchart.ITunesStore.API_URL, json={'error': 'not found'}, status=404) - expected = u'iTunes search failed: 404 Client Error' + expected = 'iTunes search failed: 404 Client Error' with capture_log('beets.test_art') as logs: with self.assertRaises(StopIteration): @@ -442,7 +440,7 @@ class ITunesStoreTest(UseThePlugin): ] }""" self.mock_response(fetchart.ITunesStore.API_URL, json) - expected = u'Malformed itunes candidate' + expected = 'Malformed itunes candidate' with capture_log('beets.test_art') as logs: with self.assertRaises(StopIteration): @@ -452,7 +450,7 @@ class ITunesStoreTest(UseThePlugin): def test_itunesstore_returns_no_result_when_error_received(self): json = '{"error": {"errors": [{"reason": "some reason"}]}}' self.mock_response(fetchart.ITunesStore.API_URL, json) - expected = u"not found in json. Fields are" + expected = "not found in json. Fields are" with capture_log('beets.test_art') as logs: with self.assertRaises(StopIteration): @@ -462,7 +460,7 @@ class ITunesStoreTest(UseThePlugin): def test_itunesstore_returns_no_result_with_malformed_response(self): json = """bla blup""" self.mock_response(fetchart.ITunesStore.API_URL, json) - expected = u"Could not decode json response:" + expected = "Could not decode json response:" with capture_log('beets.test_art') as logs: with self.assertRaises(StopIteration): @@ -472,13 +470,13 @@ class ITunesStoreTest(UseThePlugin): class GoogleImageTest(UseThePlugin): def setUp(self): - super(GoogleImageTest, self).setUp() + super().setUp() self.source = fetchart.GoogleImages(logger, self.plugin.config) self.settings = Settings() @responses.activate def run(self, *args, **kwargs): - super(GoogleImageTest, self).run(*args, **kwargs) + super().run(*args, **kwargs) def mock_response(self, url, json): responses.add(responses.GET, url, body=json, @@ -509,13 +507,13 @@ class GoogleImageTest(UseThePlugin): class CoverArtArchiveTest(UseThePlugin, CAAHelper): def setUp(self): - super(CoverArtArchiveTest, self).setUp() + super().setUp() self.source = fetchart.CoverArtArchive(logger, self.plugin.config) self.settings = Settings(maxwidth=0) @responses.activate def run(self, *args, **kwargs): - super(CoverArtArchiveTest, self).run(*args, **kwargs) + super().run(*args, **kwargs) def test_caa_finds_image(self): album = _common.Bag(mb_albumid=self.MBID_RELASE, @@ -529,7 +527,7 @@ class CoverArtArchiveTest(UseThePlugin, CAAHelper): class FanartTVTest(UseThePlugin): - RESPONSE_MULTIPLE = u"""{ + RESPONSE_MULTIPLE = """{ "name": "artistname", "mbid_id": "artistid", "albums": { @@ -563,7 +561,7 @@ class FanartTVTest(UseThePlugin): } } }""" - RESPONSE_NO_ART = u"""{ + RESPONSE_NO_ART = """{ "name": "artistname", "mbid_id": "artistid", "albums": { @@ -580,50 +578,50 @@ class FanartTVTest(UseThePlugin): } } }""" - RESPONSE_ERROR = u"""{ + RESPONSE_ERROR = """{ "status": "error", "error message": "the error message" }""" - RESPONSE_MALFORMED = u"bla blup" + RESPONSE_MALFORMED = "bla blup" def setUp(self): - super(FanartTVTest, self).setUp() + super().setUp() self.source = fetchart.FanartTV(logger, self.plugin.config) self.settings = Settings() @responses.activate def run(self, *args, **kwargs): - super(FanartTVTest, self).run(*args, **kwargs) + super().run(*args, **kwargs) def mock_response(self, url, json): responses.add(responses.GET, url, body=json, content_type='application/json') def test_fanarttv_finds_image(self): - album = _common.Bag(mb_releasegroupid=u'thereleasegroupid') - self.mock_response(fetchart.FanartTV.API_ALBUMS + u'thereleasegroupid', + album = _common.Bag(mb_releasegroupid='thereleasegroupid') + self.mock_response(fetchart.FanartTV.API_ALBUMS + 'thereleasegroupid', self.RESPONSE_MULTIPLE) candidate = next(self.source.get(album, self.settings, [])) self.assertEqual(candidate.url, 'http://example.com/1.jpg') def test_fanarttv_returns_no_result_when_error_received(self): - album = _common.Bag(mb_releasegroupid=u'thereleasegroupid') - self.mock_response(fetchart.FanartTV.API_ALBUMS + u'thereleasegroupid', + album = _common.Bag(mb_releasegroupid='thereleasegroupid') + self.mock_response(fetchart.FanartTV.API_ALBUMS + 'thereleasegroupid', self.RESPONSE_ERROR) with self.assertRaises(StopIteration): next(self.source.get(album, self.settings, [])) def test_fanarttv_returns_no_result_with_malformed_response(self): - album = _common.Bag(mb_releasegroupid=u'thereleasegroupid') - self.mock_response(fetchart.FanartTV.API_ALBUMS + u'thereleasegroupid', + album = _common.Bag(mb_releasegroupid='thereleasegroupid') + self.mock_response(fetchart.FanartTV.API_ALBUMS + 'thereleasegroupid', self.RESPONSE_MALFORMED) with self.assertRaises(StopIteration): next(self.source.get(album, self.settings, [])) 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', + album = _common.Bag(mb_releasegroupid='thereleasegroupid') + self.mock_response(fetchart.FanartTV.API_ALBUMS + 'thereleasegroupid', self.RESPONSE_NO_ART) with self.assertRaises(StopIteration): next(self.source.get(album, self.settings, [])) @@ -632,7 +630,7 @@ class FanartTVTest(UseThePlugin): @_common.slow_test() class ArtImporterTest(UseThePlugin): def setUp(self): - super(ArtImporterTest, self).setUp() + super().setUp() # Mock the album art fetcher to always return our test file. self.art_file = os.path.join(self.temp_dir, b'tmpcover.jpg') @@ -666,17 +664,17 @@ class ArtImporterTest(UseThePlugin): self.task.is_album = True self.task.album = self.album info = AlbumInfo( - album=u'some album', - album_id=u'albumid', - artist=u'some artist', - artist_id=u'artistid', + album='some album', + album_id='albumid', + artist='some artist', + artist_id='artistid', tracks=[], ) self.task.set_choice(AlbumMatch(0, info, {}, set(), set())) def tearDown(self): self.lib._connection().close() - super(ArtImporterTest, self).tearDown() + super().tearDown() self.plugin.art_for_album = self.old_afa def _fetch_art(self, should_exist): @@ -754,7 +752,7 @@ class ArtForAlbumTest(UseThePlugin): IMG_348x348_SIZE = os.stat(util.syspath(IMG_348x348)).st_size def setUp(self): - super(ArtForAlbumTest, self).setUp() + super().setUp() self.old_fs_source_get = fetchart.FileSystem.get @@ -768,7 +766,7 @@ class ArtForAlbumTest(UseThePlugin): def tearDown(self): fetchart.FileSystem.get = self.old_fs_source_get - super(ArtForAlbumTest, self).tearDown() + super().tearDown() def _assertImageIsValidArt(self, image_file, should_exist): # noqa self.assertExists(image_file) @@ -794,7 +792,7 @@ class ArtForAlbumTest(UseThePlugin): PIL (so comparisons and measurements are unavailable). """ if ArtResizer.shared.method[0] == WEBPROXY: - self.skipTest(u"ArtResizer has no local imaging backend available") + self.skipTest("ArtResizer has no local imaging backend available") def test_respect_minwidth(self): self._require_backend() @@ -876,7 +874,7 @@ class DeprecatedConfigTest(_common.TestCase): # overwritten by _common.TestCase or be set after constructing the # plugin object def setUp(self): - super(DeprecatedConfigTest, self).setUp() + super().setUp() config['fetchart']['remote_priority'] = True self.plugin = fetchart.FetchArtPlugin() @@ -899,12 +897,12 @@ class EnforceRatioConfigTest(_common.TestCase): fetchart.FetchArtPlugin() def test_px(self): - self._load_with_config(u'0px 4px 12px 123px'.split(), False) - self._load_with_config(u'00px stuff5px'.split(), True) + self._load_with_config('0px 4px 12px 123px'.split(), False) + self._load_with_config('00px stuff5px'.split(), True) def test_percent(self): - self._load_with_config(u'0% 0.00% 5.1% 5% 100%'.split(), False) - self._load_with_config(u'00% 1.234% foo5% 100.1%'.split(), True) + self._load_with_config('0% 0.00% 5.1% 5% 100%'.split(), False) + self._load_with_config('00% 1.234% foo5% 100.1%'.split(), True) def suite(): diff --git a/test/test_art_resize.py b/test/test_art_resize.py index f7090d5e7..fc5af07f4 100644 --- a/test/test_art_resize.py +++ b/test/test_art_resize.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2020, David Swarbrick. # @@ -15,7 +14,6 @@ """Tests for image resizing based on filesize.""" -from __future__ import division, absolute_import, print_function import unittest diff --git a/test/test_autotag.py b/test/test_autotag.py index febd1641d..2314b42e0 100644 --- a/test/test_autotag.py +++ b/test/test_autotag.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -15,7 +14,6 @@ """Tests for autotagging functionality. """ -from __future__ import division, absolute_import, print_function import re import unittest @@ -86,7 +84,7 @@ class PluralityTest(_common.TestCase): fields = ['artist', 'album', 'albumartist', 'year', 'disctotal', 'mb_albumid', 'label', 'catalognum', 'country', 'media', 'albumdisambig'] - items = [Item(**dict((f, '%s_%s' % (f, i or 1)) for f in fields)) + items = [Item(**{f: '{}_{}'.format(f, i or 1) for f in fields}) for i in range(5)] likelies, _ = match.current_metadata(items) for f in fields: @@ -96,20 +94,20 @@ class PluralityTest(_common.TestCase): self.assertEqual(likelies[f], '%s_1' % f) -def _make_item(title, track, artist=u'some artist'): +def _make_item(title, track, artist='some artist'): return Item(title=title, track=track, - artist=artist, album=u'some album', + artist=artist, album='some album', length=1, mb_trackid='', mb_albumid='', mb_artistid='') def _make_trackinfo(): return [ - TrackInfo(title=u'one', track_id=None, artist=u'some artist', + TrackInfo(title='one', track_id=None, artist='some artist', length=1, index=1), - TrackInfo(title=u'two', track_id=None, artist=u'some artist', + TrackInfo(title='two', track_id=None, artist='some artist', length=1, index=2), - TrackInfo(title=u'three', track_id=None, artist=u'some artist', + TrackInfo(title='three', track_id=None, artist='some artist', length=1, index=3), ] @@ -123,7 +121,7 @@ def _clear_weights(): class DistanceTest(_common.TestCase): def tearDown(self): - super(DistanceTest, self).tearDown() + super().tearDown() _clear_weights() def test_add(self): @@ -199,8 +197,8 @@ class DistanceTest(_common.TestCase): def test_add_string(self): dist = Distance() - sdist = string_dist(u'abc', u'bcd') - dist.add_string('string', u'abc', u'bcd') + sdist = string_dist('abc', 'bcd') + dist.add_string('string', 'abc', 'bcd') self.assertEqual(dist._penalties['string'], [sdist]) self.assertNotEqual(dist._penalties['string'], [0]) @@ -305,27 +303,27 @@ class DistanceTest(_common.TestCase): class TrackDistanceTest(_common.TestCase): def test_identical_tracks(self): - item = _make_item(u'one', 1) + item = _make_item('one', 1) info = _make_trackinfo()[0] dist = match.track_distance(item, info, incl_artist=True) self.assertEqual(dist, 0.0) def test_different_title(self): - item = _make_item(u'foo', 1) + item = _make_item('foo', 1) info = _make_trackinfo()[0] dist = match.track_distance(item, info, incl_artist=True) self.assertNotEqual(dist, 0.0) def test_different_artist(self): - item = _make_item(u'one', 1) - item.artist = u'foo' + item = _make_item('one', 1) + item.artist = 'foo' info = _make_trackinfo()[0] dist = match.track_distance(item, info, incl_artist=True) self.assertNotEqual(dist, 0.0) def test_various_artists_tolerated(self): - item = _make_item(u'one', 1) - item.artist = u'Various Artists' + item = _make_item('one', 1) + item.artist = 'Various Artists' info = _make_trackinfo()[0] dist = match.track_distance(item, info, incl_artist=True) self.assertEqual(dist, 0.0) @@ -343,12 +341,12 @@ class AlbumDistanceTest(_common.TestCase): def test_identical_albums(self): items = [] - items.append(_make_item(u'one', 1)) - items.append(_make_item(u'two', 2)) - items.append(_make_item(u'three', 3)) + items.append(_make_item('one', 1)) + items.append(_make_item('two', 2)) + items.append(_make_item('three', 3)) info = AlbumInfo( - artist=u'some artist', - album=u'some album', + artist='some artist', + album='some album', tracks=_make_trackinfo(), va=False ) @@ -356,11 +354,11 @@ class AlbumDistanceTest(_common.TestCase): def test_incomplete_album(self): items = [] - items.append(_make_item(u'one', 1)) - items.append(_make_item(u'three', 3)) + items.append(_make_item('one', 1)) + items.append(_make_item('three', 3)) info = AlbumInfo( - artist=u'some artist', - album=u'some album', + artist='some artist', + album='some album', tracks=_make_trackinfo(), va=False ) @@ -371,12 +369,12 @@ class AlbumDistanceTest(_common.TestCase): def test_global_artists_differ(self): items = [] - items.append(_make_item(u'one', 1)) - items.append(_make_item(u'two', 2)) - items.append(_make_item(u'three', 3)) + items.append(_make_item('one', 1)) + items.append(_make_item('two', 2)) + items.append(_make_item('three', 3)) info = AlbumInfo( - artist=u'someone else', - album=u'some album', + artist='someone else', + album='some album', tracks=_make_trackinfo(), va=False ) @@ -384,12 +382,12 @@ class AlbumDistanceTest(_common.TestCase): def test_comp_track_artists_match(self): items = [] - items.append(_make_item(u'one', 1)) - items.append(_make_item(u'two', 2)) - items.append(_make_item(u'three', 3)) + items.append(_make_item('one', 1)) + items.append(_make_item('two', 2)) + items.append(_make_item('three', 3)) info = AlbumInfo( - artist=u'should be ignored', - album=u'some album', + artist='should be ignored', + album='some album', tracks=_make_trackinfo(), va=True ) @@ -398,12 +396,12 @@ class AlbumDistanceTest(_common.TestCase): def test_comp_no_track_artists(self): # Some VA releases don't have track artists (incomplete metadata). items = [] - items.append(_make_item(u'one', 1)) - items.append(_make_item(u'two', 2)) - items.append(_make_item(u'three', 3)) + items.append(_make_item('one', 1)) + items.append(_make_item('two', 2)) + items.append(_make_item('three', 3)) info = AlbumInfo( - artist=u'should be ignored', - album=u'some album', + artist='should be ignored', + album='some album', tracks=_make_trackinfo(), va=True ) @@ -414,12 +412,12 @@ class AlbumDistanceTest(_common.TestCase): def test_comp_track_artists_do_not_match(self): items = [] - items.append(_make_item(u'one', 1)) - items.append(_make_item(u'two', 2, u'someone else')) - items.append(_make_item(u'three', 3)) + items.append(_make_item('one', 1)) + items.append(_make_item('two', 2, 'someone else')) + items.append(_make_item('three', 3)) info = AlbumInfo( - artist=u'some artist', - album=u'some album', + artist='some artist', + album='some album', tracks=_make_trackinfo(), va=True ) @@ -427,12 +425,12 @@ class AlbumDistanceTest(_common.TestCase): def test_tracks_out_of_order(self): items = [] - items.append(_make_item(u'one', 1)) - items.append(_make_item(u'three', 2)) - items.append(_make_item(u'two', 3)) + items.append(_make_item('one', 1)) + items.append(_make_item('three', 2)) + items.append(_make_item('two', 3)) info = AlbumInfo( - artist=u'some artist', - album=u'some album', + artist='some artist', + album='some album', tracks=_make_trackinfo(), va=False ) @@ -441,12 +439,12 @@ class AlbumDistanceTest(_common.TestCase): def test_two_medium_release(self): items = [] - items.append(_make_item(u'one', 1)) - items.append(_make_item(u'two', 2)) - items.append(_make_item(u'three', 3)) + items.append(_make_item('one', 1)) + items.append(_make_item('two', 2)) + items.append(_make_item('three', 3)) info = AlbumInfo( - artist=u'some artist', - album=u'some album', + artist='some artist', + album='some album', tracks=_make_trackinfo(), va=False ) @@ -458,12 +456,12 @@ class AlbumDistanceTest(_common.TestCase): def test_per_medium_track_numbers(self): items = [] - items.append(_make_item(u'one', 1)) - items.append(_make_item(u'two', 2)) - items.append(_make_item(u'three', 1)) + items.append(_make_item('one', 1)) + items.append(_make_item('two', 2)) + items.append(_make_item('three', 1)) info = AlbumInfo( - artist=u'some artist', - album=u'some album', + artist='some artist', + album='some album', tracks=_make_trackinfo(), va=False ) @@ -483,13 +481,13 @@ class AssignmentTest(unittest.TestCase): def test_reorder_when_track_numbers_incorrect(self): items = [] - items.append(self.item(u'one', 1)) - items.append(self.item(u'three', 2)) - items.append(self.item(u'two', 3)) + items.append(self.item('one', 1)) + items.append(self.item('three', 2)) + items.append(self.item('two', 3)) trackinfo = [] - trackinfo.append(TrackInfo(title=u'one')) - trackinfo.append(TrackInfo(title=u'two')) - trackinfo.append(TrackInfo(title=u'three')) + trackinfo.append(TrackInfo(title='one')) + trackinfo.append(TrackInfo(title='two')) + trackinfo.append(TrackInfo(title='three')) mapping, extra_items, extra_tracks = \ match.assign_items(items, trackinfo) self.assertEqual(extra_items, []) @@ -502,13 +500,13 @@ class AssignmentTest(unittest.TestCase): def test_order_works_with_invalid_track_numbers(self): items = [] - items.append(self.item(u'one', 1)) - items.append(self.item(u'three', 1)) - items.append(self.item(u'two', 1)) + items.append(self.item('one', 1)) + items.append(self.item('three', 1)) + items.append(self.item('two', 1)) trackinfo = [] - trackinfo.append(TrackInfo(title=u'one')) - trackinfo.append(TrackInfo(title=u'two')) - trackinfo.append(TrackInfo(title=u'three')) + trackinfo.append(TrackInfo(title='one')) + trackinfo.append(TrackInfo(title='two')) + trackinfo.append(TrackInfo(title='three')) mapping, extra_items, extra_tracks = \ match.assign_items(items, trackinfo) self.assertEqual(extra_items, []) @@ -521,12 +519,12 @@ class AssignmentTest(unittest.TestCase): def test_order_works_with_missing_tracks(self): items = [] - items.append(self.item(u'one', 1)) - items.append(self.item(u'three', 3)) + items.append(self.item('one', 1)) + items.append(self.item('three', 3)) trackinfo = [] - trackinfo.append(TrackInfo(title=u'one')) - trackinfo.append(TrackInfo(title=u'two')) - trackinfo.append(TrackInfo(title=u'three')) + trackinfo.append(TrackInfo(title='one')) + trackinfo.append(TrackInfo(title='two')) + trackinfo.append(TrackInfo(title='three')) mapping, extra_items, extra_tracks = \ match.assign_items(items, trackinfo) self.assertEqual(extra_items, []) @@ -538,12 +536,12 @@ class AssignmentTest(unittest.TestCase): def test_order_works_with_extra_tracks(self): items = [] - items.append(self.item(u'one', 1)) - items.append(self.item(u'two', 2)) - items.append(self.item(u'three', 3)) + items.append(self.item('one', 1)) + items.append(self.item('two', 2)) + items.append(self.item('three', 3)) trackinfo = [] - trackinfo.append(TrackInfo(title=u'one')) - trackinfo.append(TrackInfo(title=u'three')) + trackinfo.append(TrackInfo(title='one')) + trackinfo.append(TrackInfo(title='three')) mapping, extra_items, extra_tracks = \ match.assign_items(items, trackinfo) self.assertEqual(extra_items, [items[1]]) @@ -557,9 +555,9 @@ class AssignmentTest(unittest.TestCase): # A real-world test case contributed by a user. def item(i, length): return Item( - artist=u'ben harper', - album=u'burn to shine', - title=u'ben harper - Burn to Shine {0}'.format(i), + artist='ben harper', + album='burn to shine', + title=f'ben harper - Burn to Shine {i}', track=i, length=length, mb_trackid='', mb_albumid='', mb_artistid='', @@ -582,18 +580,18 @@ class AssignmentTest(unittest.TestCase): return TrackInfo(title=title, length=length, index=index) trackinfo = [] - trackinfo.append(info(1, u'Alone', 238.893)) - trackinfo.append(info(2, u'The Woman in You', 341.44)) - trackinfo.append(info(3, u'Less', 245.59999999999999)) - trackinfo.append(info(4, u'Two Hands of a Prayer', 470.49299999999999)) - trackinfo.append(info(5, u'Please Bleed', 277.86599999999999)) - trackinfo.append(info(6, u'Suzie Blue', 269.30599999999998)) - trackinfo.append(info(7, u'Steal My Kisses', 245.36000000000001)) - trackinfo.append(info(8, u'Burn to Shine', 214.90600000000001)) - trackinfo.append(info(9, u'Show Me a Little Shame', 224.0929999999999)) - trackinfo.append(info(10, u'Forgiven', 317.19999999999999)) - trackinfo.append(info(11, u'Beloved One', 243.733)) - trackinfo.append(info(12, u'In the Lord\'s Arms', 186.13300000000001)) + trackinfo.append(info(1, 'Alone', 238.893)) + trackinfo.append(info(2, 'The Woman in You', 341.44)) + trackinfo.append(info(3, 'Less', 245.59999999999999)) + trackinfo.append(info(4, 'Two Hands of a Prayer', 470.49299999999999)) + trackinfo.append(info(5, 'Please Bleed', 277.86599999999999)) + trackinfo.append(info(6, 'Suzie Blue', 269.30599999999998)) + trackinfo.append(info(7, 'Steal My Kisses', 245.36000000000001)) + trackinfo.append(info(8, 'Burn to Shine', 214.90600000000001)) + trackinfo.append(info(9, 'Show Me a Little Shame', 224.0929999999999)) + trackinfo.append(info(10, 'Forgiven', 317.19999999999999)) + trackinfo.append(info(11, 'Beloved One', 243.733)) + trackinfo.append(info(12, 'In the Lord\'s Arms', 186.13300000000001)) mapping, extra_items, extra_tracks = \ match.assign_items(items, trackinfo) @@ -603,7 +601,7 @@ class AssignmentTest(unittest.TestCase): self.assertEqual(items.index(item), trackinfo.index(info)) -class ApplyTestUtil(object): +class ApplyTestUtil: def _apply(self, info=None, per_disc_numbering=False, artist_credit=False): info = info or self.info mapping = {} @@ -616,15 +614,15 @@ class ApplyTestUtil(object): class ApplyTest(_common.TestCase, ApplyTestUtil): def setUp(self): - super(ApplyTest, self).setUp() + super().setUp() self.items = [] self.items.append(Item({})) self.items.append(Item({})) trackinfo = [] trackinfo.append(TrackInfo( - title=u'oneNew', - track_id=u'dfa939ec-118c-4d0f-84a0-60f3d1e6522c', + title='oneNew', + track_id='dfa939ec-118c-4d0f-84a0-60f3d1e6522c', medium=1, medium_index=1, medium_total=1, @@ -633,8 +631,8 @@ class ApplyTest(_common.TestCase, ApplyTestUtil): artist_sort='trackArtistSort', )) trackinfo.append(TrackInfo( - title=u'twoNew', - track_id=u'40130ed1-a27c-42fd-a328-1ebefb6caef4', + title='twoNew', + track_id='40130ed1-a27c-42fd-a328-1ebefb6caef4', medium=2, medium_index=1, index=2, @@ -642,13 +640,13 @@ class ApplyTest(_common.TestCase, ApplyTestUtil): )) self.info = AlbumInfo( tracks=trackinfo, - artist=u'artistNew', - album=u'albumNew', + artist='artistNew', + album='albumNew', album_id='7edb51cb-77d6-4416-a23c-3a8c2994a2c7', artist_id='a6623d39-2d8e-4f70-8242-0a9553b91e50', - artist_credit=u'albumArtistCredit', - artist_sort=u'albumArtistSort', - albumtype=u'album', + artist_credit='albumArtistCredit', + artist_sort='albumArtistSort', + albumtype='album', va=False, mediums=2, ) @@ -806,33 +804,33 @@ class ApplyTest(_common.TestCase, ApplyTestUtil): class ApplyCompilationTest(_common.TestCase, ApplyTestUtil): def setUp(self): - super(ApplyCompilationTest, self).setUp() + super().setUp() self.items = [] self.items.append(Item({})) self.items.append(Item({})) trackinfo = [] trackinfo.append(TrackInfo( - title=u'oneNew', - track_id=u'dfa939ec-118c-4d0f-84a0-60f3d1e6522c', - artist=u'artistOneNew', - artist_id=u'a05686fc-9db2-4c23-b99e-77f5db3e5282', + title='oneNew', + track_id='dfa939ec-118c-4d0f-84a0-60f3d1e6522c', + artist='artistOneNew', + artist_id='a05686fc-9db2-4c23-b99e-77f5db3e5282', index=1, )) trackinfo.append(TrackInfo( - title=u'twoNew', - track_id=u'40130ed1-a27c-42fd-a328-1ebefb6caef4', - artist=u'artistTwoNew', - artist_id=u'80b3cf5e-18fe-4c59-98c7-e5bb87210710', + title='twoNew', + track_id='40130ed1-a27c-42fd-a328-1ebefb6caef4', + artist='artistTwoNew', + artist_id='80b3cf5e-18fe-4c59-98c7-e5bb87210710', index=2, )) self.info = AlbumInfo( tracks=trackinfo, - artist=u'variousNew', - album=u'albumNew', + artist='variousNew', + album='albumNew', album_id='3b69ea40-39b8-487f-8818-04b6eff8c21a', artist_id='89ad4ac3-39f7-470e-963a-56509c546377', - albumtype=u'compilation', + albumtype='compilation', ) def test_album_and_track_artists_separate(self): @@ -868,77 +866,77 @@ class ApplyCompilationTest(_common.TestCase, ApplyTestUtil): class StringDistanceTest(unittest.TestCase): def test_equal_strings(self): - dist = string_dist(u'Some String', u'Some String') + dist = string_dist('Some String', 'Some String') self.assertEqual(dist, 0.0) def test_different_strings(self): - dist = string_dist(u'Some String', u'Totally Different') + dist = string_dist('Some String', 'Totally Different') self.assertNotEqual(dist, 0.0) def test_punctuation_ignored(self): - dist = string_dist(u'Some String', u'Some.String!') + dist = string_dist('Some String', 'Some.String!') self.assertEqual(dist, 0.0) def test_case_ignored(self): - dist = string_dist(u'Some String', u'sOME sTring') + dist = string_dist('Some String', 'sOME sTring') self.assertEqual(dist, 0.0) def test_leading_the_has_lower_weight(self): - dist1 = string_dist(u'XXX Band Name', u'Band Name') - dist2 = string_dist(u'The Band Name', u'Band Name') + dist1 = string_dist('XXX Band Name', 'Band Name') + dist2 = string_dist('The Band Name', 'Band Name') self.assertTrue(dist2 < dist1) def test_parens_have_lower_weight(self): - dist1 = string_dist(u'One .Two.', u'One') - dist2 = string_dist(u'One (Two)', u'One') + dist1 = string_dist('One .Two.', 'One') + dist2 = string_dist('One (Two)', 'One') self.assertTrue(dist2 < dist1) def test_brackets_have_lower_weight(self): - dist1 = string_dist(u'One .Two.', u'One') - dist2 = string_dist(u'One [Two]', u'One') + dist1 = string_dist('One .Two.', 'One') + dist2 = string_dist('One [Two]', 'One') self.assertTrue(dist2 < dist1) def test_ep_label_has_zero_weight(self): - dist = string_dist(u'My Song (EP)', u'My Song') + dist = string_dist('My Song (EP)', 'My Song') self.assertEqual(dist, 0.0) def test_featured_has_lower_weight(self): - dist1 = string_dist(u'My Song blah Someone', u'My Song') - dist2 = string_dist(u'My Song feat Someone', u'My Song') + dist1 = string_dist('My Song blah Someone', 'My Song') + dist2 = string_dist('My Song feat Someone', 'My Song') self.assertTrue(dist2 < dist1) def test_postfix_the(self): - dist = string_dist(u'The Song Title', u'Song Title, The') + dist = string_dist('The Song Title', 'Song Title, The') self.assertEqual(dist, 0.0) def test_postfix_a(self): - dist = string_dist(u'A Song Title', u'Song Title, A') + dist = string_dist('A Song Title', 'Song Title, A') self.assertEqual(dist, 0.0) def test_postfix_an(self): - dist = string_dist(u'An Album Title', u'Album Title, An') + dist = string_dist('An Album Title', 'Album Title, An') self.assertEqual(dist, 0.0) def test_empty_strings(self): - dist = string_dist(u'', u'') + dist = string_dist('', '') self.assertEqual(dist, 0.0) def test_solo_pattern(self): # Just make sure these don't crash. - string_dist(u'The ', u'') - string_dist(u'(EP)', u'(EP)') - string_dist(u', An', u'') + string_dist('The ', '') + string_dist('(EP)', '(EP)') + string_dist(', An', '') def test_heuristic_does_not_harm_distance(self): - dist = string_dist(u'Untitled', u'[Untitled]') + dist = string_dist('Untitled', '[Untitled]') self.assertEqual(dist, 0.0) def test_ampersand_expansion(self): - dist = string_dist(u'And', u'&') + dist = string_dist('And', '&') self.assertEqual(dist, 0.0) def test_accented_characters(self): - dist = string_dist(u'\xe9\xe1\xf1', u'ean') + dist = string_dist('\xe9\xe1\xf1', 'ean') self.assertEqual(dist, 0.0) diff --git a/test/test_bareasc.py b/test/test_bareasc.py index 1ce4e6176..f8f24c8b6 100644 --- a/test/test_bareasc.py +++ b/test/test_bareasc.py @@ -1,10 +1,8 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2021, Graham R. Cobb. """Tests for the 'bareasc' plugin.""" -from __future__ import division, absolute_import, print_function import unittest @@ -20,55 +18,55 @@ class BareascPluginTest(unittest.TestCase, TestHelper): """Set up test environment for bare ASCII query matching.""" self.setup_beets() self.log = logging.getLogger('beets.web') - self.config['bareasc']['prefix'] = u'#' + self.config['bareasc']['prefix'] = '#' self.load_plugins('bareasc') # Add library elements. Note that self.lib.add overrides any "id=" # and assigns the next free id number. - self.add_item(title=u'with accents', + self.add_item(title='with accents', album_id=2, - artist=u'Antonín Dvořák') - self.add_item(title=u'without accents', - artist=u'Antonín Dvorak') - self.add_item(title=u'with umlaut', + artist='Antonín Dvořák') + self.add_item(title='without accents', + artist='Antonín Dvorak') + self.add_item(title='with umlaut', album_id=2, - artist=u'Brüggen') - self.add_item(title=u'without umlaut or e', - artist=u'Bruggen') - self.add_item(title=u'without umlaut with e', - artist=u'Brueggen') + artist='Brüggen') + self.add_item(title='without umlaut or e', + artist='Bruggen') + self.add_item(title='without umlaut with e', + artist='Brueggen') def test_search_normal_noaccent(self): """Normal search, no accents, not using bare-ASCII match. Finds just the unaccented entry. """ - items = self.lib.items(u'dvorak') + items = self.lib.items('dvorak') self.assertEqual(len(items), 1) - self.assertEqual([items[0].title], [u'without accents']) + self.assertEqual([items[0].title], ['without accents']) def test_search_normal_accent(self): """Normal search, with accents, not using bare-ASCII match. Finds just the accented entry. """ - items = self.lib.items(u'dvořák') + items = self.lib.items('dvořák') self.assertEqual(len(items), 1) - self.assertEqual([items[0].title], [u'with accents']) + self.assertEqual([items[0].title], ['with accents']) def test_search_bareasc_noaccent(self): """Bare-ASCII search, no accents. Finds both entries. """ - items = self.lib.items(u'#dvorak') + items = self.lib.items('#dvorak') self.assertEqual(len(items), 2) self.assertEqual( {items[0].title, items[1].title}, - {u'without accents', u'with accents'} + {'without accents', 'with accents'} ) def test_search_bareasc_accent(self): @@ -76,12 +74,12 @@ class BareascPluginTest(unittest.TestCase, TestHelper): Finds both entries. """ - items = self.lib.items(u'#dvořák') + items = self.lib.items('#dvořák') self.assertEqual(len(items), 2) self.assertEqual( {items[0].title, items[1].title}, - {u'without accents', u'with accents'} + {'without accents', 'with accents'} ) def test_search_bareasc_wrong_accent(self): @@ -89,12 +87,12 @@ class BareascPluginTest(unittest.TestCase, TestHelper): Finds both entries. """ - items = self.lib.items(u'#dvořäk') + items = self.lib.items('#dvořäk') self.assertEqual(len(items), 2) self.assertEqual( {items[0].title, items[1].title}, - {u'without accents', u'with accents'} + {'without accents', 'with accents'} ) def test_search_bareasc_noumlaut(self): @@ -105,12 +103,12 @@ class BareascPluginTest(unittest.TestCase, TestHelper): This is expected behaviour for this simple plugin. """ - items = self.lib.items(u'#Bruggen') + items = self.lib.items('#Bruggen') self.assertEqual(len(items), 2) self.assertEqual( {items[0].title, items[1].title}, - {u'without umlaut or e', u'with umlaut'} + {'without umlaut or e', 'with umlaut'} ) def test_search_bareasc_umlaut(self): @@ -121,12 +119,12 @@ class BareascPluginTest(unittest.TestCase, TestHelper): This is expected behaviour for this simple plugin. """ - items = self.lib.items(u'#Brüggen') + items = self.lib.items('#Brüggen') self.assertEqual(len(items), 2) self.assertEqual( {items[0].title, items[1].title}, - {u'without umlaut or e', u'with umlaut'} + {'without umlaut or e', 'with umlaut'} ) def test_bareasc_list_output(self): diff --git a/test/test_beatport.py b/test/test_beatport.py index fb39627f8..53330fc26 100644 --- a/test/test_beatport.py +++ b/test/test_beatport.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -15,7 +14,6 @@ """Tests for the 'beatport' plugin. """ -from __future__ import division, absolute_import, print_function import unittest from test import _common @@ -543,7 +541,7 @@ class BeatportTest(_common.TestCase, TestHelper): for track, test_track, id in zip(self.tracks, self.test_tracks, ids): self.assertEqual( track.url, 'https://beatport.com/track/' + - test_track.url + '/' + six.text_type(id)) + test_track.url + '/' + str(id)) def test_bpm_applied(self): for track, test_track in zip(self.tracks, self.test_tracks): diff --git a/test/test_bucket.py b/test/test_bucket.py index 61f6cfe24..46091e242 100644 --- a/test/test_bucket.py +++ b/test/test_bucket.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Fabrice Laporte. # @@ -15,7 +14,6 @@ """Tests for the 'bucket' plugin.""" -from __future__ import division, absolute_import, print_function import unittest from beetsplug import bucket diff --git a/test/test_config_command.py b/test/test_config_command.py index 0d16dbf19..973f43b61 100644 --- a/test/test_config_command.py +++ b/test/test_config_command.py @@ -1,10 +1,6 @@ -# -*- coding: utf-8 -*- - -from __future__ import division, absolute_import, print_function - import os import yaml -from mock import patch +from unittest.mock import patch from tempfile import mkdtemp from shutil import rmtree import unittest @@ -116,8 +112,8 @@ class ConfigCommandTest(unittest.TestCase, TestHelper): execlp.side_effect = OSError('here is problem') self.run_command('config', '-e') self.assertIn('Could not edit configuration', - six.text_type(user_error.exception)) - self.assertIn('here is problem', six.text_type(user_error.exception)) + str(user_error.exception)) + self.assertIn('here is problem', str(user_error.exception)) def test_edit_invalid_config_file(self): with open(self.config_path, 'w') as file: diff --git a/test/test_convert.py b/test/test_convert.py index 6ef090ba9..ba7015179 100644 --- a/test/test_convert.py +++ b/test/test_convert.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Thomas Scholtes. # @@ -13,7 +12,6 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -from __future__ import division, absolute_import, print_function import fnmatch import sys @@ -45,12 +43,12 @@ class TestHelper(helper.TestHelper): `tag` to the copy. """ if re.search('[^a-zA-Z0-9]', tag): - raise ValueError(u"tag '{0}' must only contain letters and digits" + raise ValueError("tag '{}' must only contain letters and digits" .format(tag)) # A Python script that copies the file and appends a tag. stub = os.path.join(_common.RSRC, b'convert_stub.py').decode('utf-8') - return u"{} {} $source $dest {}".format(shell_quote(sys.executable), + return "{} {} $source $dest {}".format(shell_quote(sys.executable), shell_quote(stub), tag) def assertFileTag(self, path, tag): # noqa @@ -59,12 +57,12 @@ class TestHelper(helper.TestHelper): display_tag = tag tag = tag.encode('utf-8') self.assertTrue(os.path.isfile(path), - u'{0} is not a file'.format( + '{} is not a file'.format( util.displayable_path(path))) with open(path, 'rb') as f: f.seek(-len(display_tag), os.SEEK_END) self.assertEqual(f.read(), tag, - u'{0} is not tagged with {1}' + '{} is not tagged with {}' .format( util.displayable_path(path), display_tag)) @@ -76,12 +74,12 @@ class TestHelper(helper.TestHelper): display_tag = tag tag = tag.encode('utf-8') self.assertTrue(os.path.isfile(path), - u'{0} is not a file'.format( + '{} is not a file'.format( util.displayable_path(path))) with open(path, 'rb') as f: f.seek(-len(tag), os.SEEK_END) self.assertNotEqual(f.read(), tag, - u'{0} is unexpectedly tagged with {1}' + '{} is unexpectedly tagged with {}' .format( util.displayable_path(path), display_tag)) @@ -116,7 +114,7 @@ class ImportConvertTest(unittest.TestCase, TestHelper): @unittest.skipIf(sys.platform, 'win32') # FIXME: fails on windows def test_import_original_on_convert_error(self): # `false` exits with non-zero code - self.config['convert']['command'] = u'false' + self.config['convert']['command'] = 'false' self.importer.run() item = self.lib.items().get() @@ -129,11 +127,11 @@ class ImportConvertTest(unittest.TestCase, TestHelper): for path in self.importer.paths: for root, dirnames, filenames in os.walk(path): self.assertTrue(len(fnmatch.filter(filenames, '*.mp3')) == 0, - u'Non-empty import directory {0}' + 'Non-empty import directory {}' .format(util.displayable_path(path))) -class ConvertCommand(object): +class ConvertCommand: """A mixin providing a utility method to run the `convert`command in tests. """ @@ -230,7 +228,7 @@ class ConvertCliTest(unittest.TestCase, TestHelper, ConvertCommand): converted = os.path.join(self.convert_dest, b'converted.mp3') self.touch(converted, content='XXX') self.run_convert('--yes') - with open(converted, 'r') as f: + with open(converted) as f: self.assertEqual(f.read(), 'XXX') def test_pretend(self): @@ -241,7 +239,7 @@ class ConvertCliTest(unittest.TestCase, TestHelper, ConvertCommand): def test_empty_query(self): with capture_log('beets.convert') as logs: self.run_convert('An impossible query') - self.assertEqual(logs[0], u'convert: Empty query result.') + self.assertEqual(logs[0], 'convert: Empty query result.') @_common.slow_test() diff --git a/test/test_datequery.py b/test/test_datequery.py index b8348ca53..56664e212 100644 --- a/test/test_datequery.py +++ b/test/test_datequery.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -15,7 +14,6 @@ """Test for dbcore's date-based queries. """ -from __future__ import division, absolute_import, print_function from test import _common from datetime import datetime, timedelta @@ -135,7 +133,7 @@ def _parsetime(s): class DateQueryTest(_common.LibTestCase): def setUp(self): - super(DateQueryTest, self).setUp() + super().setUp() self.i.added = _parsetime('2013-03-30 22:21') self.i.store() @@ -170,7 +168,7 @@ class DateQueryTest(_common.LibTestCase): class DateQueryTestRelative(_common.LibTestCase): def setUp(self): - super(DateQueryTestRelative, self).setUp() + super().setUp() # We pick a date near a month changeover, which can reveal some time # zone bugs. @@ -213,7 +211,7 @@ class DateQueryTestRelative(_common.LibTestCase): class DateQueryTestRelativeMore(_common.LibTestCase): def setUp(self): - super(DateQueryTestRelativeMore, self).setUp() + super().setUp() self.i.added = _parsetime(datetime.now().strftime('%Y-%m-%d %H:%M')) self.i.store() diff --git a/test/test_dbcore.py b/test/test_dbcore.py index 1dd2284c6..0485756fa 100644 --- a/test/test_dbcore.py +++ b/test/test_dbcore.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -15,7 +14,6 @@ """Tests for the DBCore database abstraction. """ -from __future__ import division, absolute_import, print_function import os import shutil @@ -236,7 +234,7 @@ class TransactionTest(unittest.TestCase): old_rev = self.db.revision with self.db.transaction() as tx: tx.mutate( - 'INSERT INTO {0} ' + 'INSERT INTO {} ' '(field_one) ' 'VALUES (?);'.format(ModelFixture1._table), (111,), @@ -385,9 +383,9 @@ class ModelTest(unittest.TestCase): self.assertNotIn('flex_field', model2) def test_check_db_fails(self): - with assertRaisesRegex(self, ValueError, 'no database'): + with self.assertRaisesRegex(ValueError, 'no database'): dbcore.Model()._check_db() - with assertRaisesRegex(self, ValueError, 'no id'): + with self.assertRaisesRegex(ValueError, 'no id'): ModelFixture1(self.db)._check_db() dbcore.Model(self.db)._check_db(need_id=False) @@ -399,7 +397,7 @@ class ModelTest(unittest.TestCase): def test_computed_field(self): model = ModelFixtureWithGetters() self.assertEqual(model.aComputedField, 'thing') - with assertRaisesRegex(self, KeyError, u'computed field .+ deleted'): + with self.assertRaisesRegex(KeyError, 'computed field .+ deleted'): del model.aComputedField def test_items(self): @@ -415,7 +413,7 @@ class ModelTest(unittest.TestCase): model._db def test_parse_nonstring(self): - with assertRaisesRegex(self, TypeError, u"must be a string"): + with self.assertRaisesRegex(TypeError, "must be a string"): dbcore.Model._parse(None, 42) @@ -424,7 +422,7 @@ class FormatTest(unittest.TestCase): model = ModelFixture1() model.field_one = 155 value = model.formatted().get('field_one') - self.assertEqual(value, u'155') + self.assertEqual(value, '155') def test_format_fixed_field_integer_normalized(self): """The normalize method of the Integer class rounds floats @@ -432,41 +430,41 @@ class FormatTest(unittest.TestCase): model = ModelFixture1() model.field_one = 142.432 value = model.formatted().get('field_one') - self.assertEqual(value, u'142') + self.assertEqual(value, '142') model.field_one = 142.863 value = model.formatted().get('field_one') - self.assertEqual(value, u'143') + self.assertEqual(value, '143') def test_format_fixed_field_string(self): model = ModelFixture1() - model.field_two = u'caf\xe9' + model.field_two = 'caf\xe9' value = model.formatted().get('field_two') - self.assertEqual(value, u'caf\xe9') + self.assertEqual(value, 'caf\xe9') def test_format_flex_field(self): model = ModelFixture1() - model.other_field = u'caf\xe9' + model.other_field = 'caf\xe9' value = model.formatted().get('other_field') - self.assertEqual(value, u'caf\xe9') + self.assertEqual(value, 'caf\xe9') def test_format_flex_field_bytes(self): model = ModelFixture1() - model.other_field = u'caf\xe9'.encode('utf-8') + model.other_field = 'caf\xe9'.encode() value = model.formatted().get('other_field') - self.assertTrue(isinstance(value, six.text_type)) - self.assertEqual(value, u'caf\xe9') + self.assertTrue(isinstance(value, str)) + self.assertEqual(value, 'caf\xe9') def test_format_unset_field(self): model = ModelFixture1() value = model.formatted().get('other_field') - self.assertEqual(value, u'') + self.assertEqual(value, '') def test_format_typed_flex_field(self): model = ModelFixture1() model.some_float_field = 3.14159265358979 value = model.formatted().get('some_float_field') - self.assertEqual(value, u'3.1') + self.assertEqual(value, '3.1') class FormattedMappingTest(unittest.TestCase): @@ -484,7 +482,7 @@ class FormattedMappingTest(unittest.TestCase): def test_get_method_with_default(self): model = ModelFixture1() formatted = model.formatted() - self.assertEqual(formatted.get('other_field'), u'') + self.assertEqual(formatted.get('other_field'), '') def test_get_method_with_specified_default(self): model = ModelFixture1() @@ -494,18 +492,18 @@ class FormattedMappingTest(unittest.TestCase): class ParseTest(unittest.TestCase): def test_parse_fixed_field(self): - value = ModelFixture1._parse('field_one', u'2') + value = ModelFixture1._parse('field_one', '2') self.assertIsInstance(value, int) self.assertEqual(value, 2) def test_parse_flex_field(self): - value = ModelFixture1._parse('some_float_field', u'2') + value = ModelFixture1._parse('some_float_field', '2') self.assertIsInstance(value, float) self.assertEqual(value, 2.0) def test_parse_untyped_field(self): - value = ModelFixture1._parse('field_nine', u'2') - self.assertEqual(value, u'2') + value = ModelFixture1._parse('field_nine', '2') + self.assertEqual(value, '2') class QueryParseTest(unittest.TestCase): diff --git a/test/test_discogs.py b/test/test_discogs.py index 61d9d5aa1..8f1b07721 100644 --- a/test/test_discogs.py +++ b/test/test_discogs.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -15,7 +14,6 @@ """Tests for discogs plugin. """ -from __future__ import division, absolute_import, print_function import unittest from test import _common diff --git a/test/test_edit.py b/test/test_edit.py index 1b627939c..3926b2ebc 100644 --- a/test/test_edit.py +++ b/test/test_edit.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson and Diego Moreda. # @@ -13,11 +12,10 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -from __future__ import division, absolute_import, print_function import codecs import unittest -from mock import patch +from unittest.mock import patch from test import _common from test.helper import TestHelper, control_stdin from test.test_ui_importer import TerminalImportSessionSetup @@ -27,7 +25,7 @@ from beets.library import Item from beetsplug.edit import EditPlugin -class ModifyFileMocker(object): +class ModifyFileMocker: """Helper for modifying a file, replacing or editing its contents. Used for mocking the calls to the external editor during testing. """ @@ -71,7 +69,7 @@ class ModifyFileMocker(object): f.write(contents) -class EditMixin(object): +class EditMixin: """Helper containing some common functionality used for the Edit tests.""" def assertItemFieldsModified(self, library_items, items, fields=[], # noqa allowed=['path']): @@ -143,33 +141,33 @@ class EditCommandTest(unittest.TestCase, TestHelper, EditMixin): def test_title_edit_discard(self, mock_write): """Edit title for all items in the library, then discard changes.""" # Edit track titles. - self.run_mocked_command({'replacements': {u't\u00eftle': - u'modified t\u00eftle'}}, + self.run_mocked_command({'replacements': {'t\u00eftle': + 'modified t\u00eftle'}}, # Cancel. ['c']) self.assertCounts(mock_write, write_call_count=0, - title_starts_with=u't\u00eftle') + title_starts_with='t\u00eftle') self.assertItemFieldsModified(self.album.items(), self.items_orig, []) def test_title_edit_apply(self, mock_write): """Edit title for all items in the library, then apply changes.""" # Edit track titles. - self.run_mocked_command({'replacements': {u't\u00eftle': - u'modified t\u00eftle'}}, + self.run_mocked_command({'replacements': {'t\u00eftle': + 'modified t\u00eftle'}}, # Apply changes. ['a']) self.assertCounts(mock_write, write_call_count=self.TRACK_COUNT, - title_starts_with=u'modified t\u00eftle') + title_starts_with='modified t\u00eftle') self.assertItemFieldsModified(self.album.items(), self.items_orig, ['title', 'mtime']) def test_single_title_edit_apply(self, mock_write): """Edit title for one item in the library, then apply changes.""" # Edit one track title. - self.run_mocked_command({'replacements': {u't\u00eftle 9': - u'modified t\u00eftle 9'}}, + self.run_mocked_command({'replacements': {'t\u00eftle 9': + 'modified t\u00eftle 9'}}, # Apply changes. ['a']) @@ -178,7 +176,7 @@ class EditCommandTest(unittest.TestCase, TestHelper, EditMixin): self.assertItemFieldsModified(list(self.album.items())[:-1], self.items_orig[:-1], []) self.assertEqual(list(self.album.items())[-1].title, - u'modified t\u00eftle 9') + 'modified t\u00eftle 9') def test_noedit(self, mock_write): """Do not edit anything.""" @@ -188,7 +186,7 @@ class EditCommandTest(unittest.TestCase, TestHelper, EditMixin): []) self.assertCounts(mock_write, write_call_count=0, - title_starts_with=u't\u00eftle') + title_starts_with='t\u00eftle') self.assertItemFieldsModified(self.album.items(), self.items_orig, []) def test_album_edit_apply(self, mock_write): @@ -196,8 +194,8 @@ class EditCommandTest(unittest.TestCase, TestHelper, EditMixin): By design, the album should not be updated."" """ # Edit album. - self.run_mocked_command({'replacements': {u'\u00e4lbum': - u'modified \u00e4lbum'}}, + self.run_mocked_command({'replacements': {'\u00e4lbum': + 'modified \u00e4lbum'}}, # Apply changes. ['a']) @@ -206,50 +204,50 @@ class EditCommandTest(unittest.TestCase, TestHelper, EditMixin): ['album', 'mtime']) # Ensure album is *not* modified. self.album.load() - self.assertEqual(self.album.album, u'\u00e4lbum') + self.assertEqual(self.album.album, '\u00e4lbum') def test_single_edit_add_field(self, mock_write): """Edit the yaml file appending an extra field to the first item, then apply changes.""" # Append "foo: bar" to item with id == 2. ("id: 1" would match both # "id: 1" and "id: 10") - self.run_mocked_command({'replacements': {u"id: 2": - u"id: 2\nfoo: bar"}}, + self.run_mocked_command({'replacements': {"id: 2": + "id: 2\nfoo: bar"}}, # Apply changes. ['a']) - self.assertEqual(self.lib.items(u'id:2')[0].foo, 'bar') + self.assertEqual(self.lib.items('id:2')[0].foo, 'bar') # Even though a flexible attribute was written (which is not directly # written to the tags), write should still be called since templates # might use it. self.assertCounts(mock_write, write_call_count=1, - title_starts_with=u't\u00eftle') + title_starts_with='t\u00eftle') def test_a_album_edit_apply(self, mock_write): """Album query (-a), edit album field, apply changes.""" - self.run_mocked_command({'replacements': {u'\u00e4lbum': - u'modified \u00e4lbum'}}, + self.run_mocked_command({'replacements': {'\u00e4lbum': + 'modified \u00e4lbum'}}, # Apply changes. ['a'], args=['-a']) self.album.load() self.assertCounts(mock_write, write_call_count=self.TRACK_COUNT) - self.assertEqual(self.album.album, u'modified \u00e4lbum') + self.assertEqual(self.album.album, 'modified \u00e4lbum') self.assertItemFieldsModified(self.album.items(), self.items_orig, ['album', 'mtime']) def test_a_albumartist_edit_apply(self, mock_write): """Album query (-a), edit albumartist field, apply changes.""" - self.run_mocked_command({'replacements': {u'album artist': - u'modified album artist'}}, + self.run_mocked_command({'replacements': {'album artist': + 'modified album artist'}}, # Apply changes. ['a'], args=['-a']) self.album.load() self.assertCounts(mock_write, write_call_count=self.TRACK_COUNT) - self.assertEqual(self.album.albumartist, u'the modified album artist') + self.assertEqual(self.album.albumartist, 'the modified album artist') self.assertItemFieldsModified(self.album.items(), self.items_orig, ['albumartist', 'mtime']) @@ -262,18 +260,18 @@ class EditCommandTest(unittest.TestCase, TestHelper, EditMixin): ['n']) self.assertCounts(mock_write, write_call_count=0, - title_starts_with=u't\u00eftle') + title_starts_with='t\u00eftle') def test_invalid_yaml(self, mock_write): """Edit the yaml file incorrectly (resulting in a well-formed but invalid yaml document).""" # Edit the yaml file to an invalid but parseable file. - self.run_mocked_command({'contents': u'wellformed: yes, but invalid'}, + self.run_mocked_command({'contents': 'wellformed: yes, but invalid'}, # No stdin. []) self.assertCounts(mock_write, write_call_count=0, - title_starts_with=u't\u00eftle') + title_starts_with='t\u00eftle') @_common.slow_test() @@ -305,8 +303,8 @@ class EditDuringImporterTest(TerminalImportSessionSetup, unittest.TestCase, """ self._setup_import_session() # Edit track titles. - self.run_mocked_interpreter({'replacements': {u'Tag Title': - u'Edited Title'}}, + self.run_mocked_interpreter({'replacements': {'Tag Title': + 'Edited Title'}}, # eDit, Apply changes. ['d', 'a']) @@ -319,7 +317,7 @@ class EditDuringImporterTest(TerminalImportSessionSetup, unittest.TestCase, for i in self.lib.items())) # Ensure album is *not* fetched from a candidate. - self.assertEqual(self.lib.albums()[0].mb_albumid, u'') + self.assertEqual(self.lib.albums()[0].mb_albumid, '') def test_edit_discard_asis(self): """Edit the album field for all items in the library, discard changes, @@ -327,8 +325,8 @@ class EditDuringImporterTest(TerminalImportSessionSetup, unittest.TestCase, """ self._setup_import_session() # Edit track titles. - self.run_mocked_interpreter({'replacements': {u'Tag Title': - u'Edited Title'}}, + self.run_mocked_interpreter({'replacements': {'Tag Title': + 'Edited Title'}}, # eDit, Cancel, Use as-is. ['d', 'c', 'u']) @@ -341,7 +339,7 @@ class EditDuringImporterTest(TerminalImportSessionSetup, unittest.TestCase, for i in self.lib.items())) # Ensure album is *not* fetched from a candidate. - self.assertEqual(self.lib.albums()[0].mb_albumid, u'') + self.assertEqual(self.lib.albums()[0].mb_albumid, '') def test_edit_apply_candidate(self): """Edit the album field for all items in the library, apply changes, @@ -349,8 +347,8 @@ class EditDuringImporterTest(TerminalImportSessionSetup, unittest.TestCase, """ self._setup_import_session() # Edit track titles. - self.run_mocked_interpreter({'replacements': {u'Applied Title': - u'Edited Title'}}, + self.run_mocked_interpreter({'replacements': {'Applied Title': + 'Edited Title'}}, # edit Candidates, 1, Apply changes. ['c', '1', 'a']) @@ -377,8 +375,8 @@ class EditDuringImporterTest(TerminalImportSessionSetup, unittest.TestCase, # ids but not the db connections. self.importer.paths = [] self.importer.query = TrueQuery() - self.run_mocked_interpreter({'replacements': {u'Applied Title': - u'Edited Title'}}, + self.run_mocked_interpreter({'replacements': {'Applied Title': + 'Edited Title'}}, # eDit, Apply changes. ['d', 'a']) @@ -398,8 +396,8 @@ class EditDuringImporterTest(TerminalImportSessionSetup, unittest.TestCase, """ self._setup_import_session() # Edit track titles. - self.run_mocked_interpreter({'replacements': {u'Applied Title': - u'Edited Title'}}, + self.run_mocked_interpreter({'replacements': {'Applied Title': + 'Edited Title'}}, # edit Candidates, 1, Apply changes. ['c', '1', 'a']) @@ -419,8 +417,8 @@ class EditDuringImporterTest(TerminalImportSessionSetup, unittest.TestCase, """ self._setup_import_session(singletons=True) # Edit track titles. - self.run_mocked_interpreter({'replacements': {u'Tag Title': - u'Edited Title'}}, + self.run_mocked_interpreter({'replacements': {'Tag Title': + 'Edited Title'}}, # eDit, Apply changes, aBort. ['d', 'a', 'b']) @@ -438,8 +436,8 @@ class EditDuringImporterTest(TerminalImportSessionSetup, unittest.TestCase, """ self._setup_import_session() # Edit track titles. - self.run_mocked_interpreter({'replacements': {u'Applied Title': - u'Edited Title'}}, + self.run_mocked_interpreter({'replacements': {'Applied Title': + 'Edited Title'}}, # edit Candidates, 1, Apply changes, aBort. ['c', '1', 'a', 'b']) diff --git a/test/test_embedart.py b/test/test_embedart.py index c465a5a96..6b6d61614 100644 --- a/test/test_embedart.py +++ b/test/test_embedart.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Thomas Scholtes. # @@ -13,11 +12,10 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -from __future__ import division, absolute_import, print_function import os.path import shutil -from mock import patch, MagicMock +from unittest.mock import patch, MagicMock import tempfile import unittest @@ -51,7 +49,7 @@ class EmbedartCliTest(_common.TestCase, TestHelper): abbey_differentpath = os.path.join(_common.RSRC, b'abbey-different.jpg') def setUp(self): - super(EmbedartCliTest, self).setUp() + super().setUp() self.io.install() self.setup_beets() # Converter is threaded self.load_plugins('embedart') @@ -121,7 +119,7 @@ class EmbedartCliTest(_common.TestCase, TestHelper): if os.path.isfile(tmp_path): os.remove(tmp_path) - self.fail(u'Artwork file {0} was not deleted'.format(tmp_path)) + self.fail(f'Artwork file {tmp_path} was not deleted') def test_art_file_missing(self): self.add_album_fixture() @@ -156,7 +154,7 @@ class EmbedartCliTest(_common.TestCase, TestHelper): mediafile = MediaFile(syspath(item.path)) self.assertEqual(mediafile.images[0].data, self.image_data, - u'Image written is not {0}'.format( + 'Image written is not {}'.format( displayable_path(self.abbey_artpath))) @require_artresizer_compare @@ -170,7 +168,7 @@ class EmbedartCliTest(_common.TestCase, TestHelper): mediafile = MediaFile(syspath(item.path)) self.assertEqual(mediafile.images[0].data, self.image_data, - u'Image written is not {0}'.format( + 'Image written is not {}'.format( displayable_path(self.abbey_similarpath))) def test_non_ascii_album_path(self): diff --git a/test/test_embyupdate.py b/test/test_embyupdate.py index 2b83a4896..f45b82de6 100644 --- a/test/test_embyupdate.py +++ b/test/test_embyupdate.py @@ -1,7 +1,3 @@ -# -*- coding: utf-8 -*- - -from __future__ import division, absolute_import, print_function - from test.helper import TestHelper from beetsplug import embyupdate import unittest @@ -14,10 +10,10 @@ class EmbyUpdateTest(unittest.TestCase, TestHelper): self.load_plugins('embyupdate') self.config['emby'] = { - u'host': u'localhost', - u'port': 8096, - u'username': u'username', - u'password': u'password' + 'host': 'localhost', + 'port': 8096, + 'username': 'username', + 'password': 'password' } def tearDown(self): @@ -34,7 +30,7 @@ class EmbyUpdateTest(unittest.TestCase, TestHelper): def test_api_url_http(self): self.assertEqual( - embyupdate.api_url(u'http://localhost', + embyupdate.api_url('http://localhost', self.config['emby']['port'].get(), '/Library/Refresh'), 'http://localhost:8096/Library/Refresh?format=json' @@ -42,7 +38,7 @@ class EmbyUpdateTest(unittest.TestCase, TestHelper): def test_api_url_https(self): self.assertEqual( - embyupdate.api_url(u'https://localhost', + embyupdate.api_url('https://localhost', self.config['emby']['port'].get(), '/Library/Refresh'), 'https://localhost:8096/Library/Refresh?format=json' diff --git a/test/test_export.py b/test/test_export.py index f0a8eb0f7..27ad86998 100644 --- a/test/test_export.py +++ b/test/test_export.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2019, Carl Suster # @@ -16,7 +15,6 @@ """Test the beets.export utilities associated with the export plugin. """ -from __future__ import division, absolute_import, print_function import unittest from test.helper import TestHelper diff --git a/test/test_fetchart.py b/test/test_fetchart.py index 8288e8f71..25d7b6eba 100644 --- a/test/test_fetchart.py +++ b/test/test_fetchart.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Thomas Scholtes. # @@ -13,7 +12,6 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -from __future__ import division, absolute_import, print_function import ctypes import os @@ -39,7 +37,7 @@ class FetchartCliTest(unittest.TestCase, TestHelper): def check_cover_is_stored(self): self.assertEqual(self.album['artpath'], self.cover_path) - with open(util.syspath(self.cover_path), 'r') as f: + with open(util.syspath(self.cover_path)) as f: self.assertEqual(f.read(), 'IMAGE') def hide_file_windows(self): diff --git a/test/test_filefilter.py b/test/test_filefilter.py index 0b0da0d3f..7e5e91f03 100644 --- a/test/test_filefilter.py +++ b/test/test_filefilter.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Malte Ried. # @@ -16,7 +15,6 @@ """Tests for the `filefilter` plugin. """ -from __future__ import division, absolute_import, print_function import os import shutil diff --git a/test/test_files.py b/test/test_files.py index ab82c192e..c625f7a12 100644 --- a/test/test_files.py +++ b/test/test_files.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -15,7 +14,6 @@ """Test file manipulation functionality of Item. """ -from __future__ import division, absolute_import, print_function import shutil import os @@ -32,7 +30,7 @@ from beets.util import MoveOperation class MoveTest(_common.TestCase): def setUp(self): - super(MoveTest, self).setUp() + super().setUp() # make a temporary file self.path = join(self.temp_dir, b'temp.mp3') @@ -73,7 +71,7 @@ class MoveTest(_common.TestCase): old_path = self.i.path self.assertExists(old_path) - self.i.artist = u'newArtist' + self.i.artist = 'newArtist' self.i.move() self.assertNotExists(old_path) self.assertNotExists(os.path.dirname(old_path)) @@ -121,20 +119,20 @@ class MoveTest(_common.TestCase): self.assertEqual(self.i.path, old_path) def test_move_file_with_colon(self): - self.i.artist = u'C:DOS' + self.i.artist = 'C:DOS' self.i.move() self.assertIn('C_DOS', self.i.path.decode()) def test_move_file_with_multiple_colons(self): print(beets.config['replace']) - self.i.artist = u'COM:DOS' + self.i.artist = 'COM:DOS' self.i.move() self.assertIn('COM_DOS', self.i.path.decode()) def test_move_file_with_colon_alt_separator(self): old = beets.config['drive_sep_replace'] beets.config["drive_sep_replace"] = '0' - self.i.artist = u'C:DOS' + self.i.artist = 'C:DOS' self.i.move() self.assertIn('C0DOS', self.i.path.decode()) beets.config["drive_sep_replace"] = old @@ -240,7 +238,7 @@ class HelperTest(_common.TestCase): class AlbumFileTest(_common.TestCase): def setUp(self): - super(AlbumFileTest, self).setUp() + super().setUp() # Make library and item. self.lib = beets.library.Library(':memory:') @@ -259,7 +257,7 @@ class AlbumFileTest(_common.TestCase): self.otherdir = os.path.join(self.temp_dir, b'testotherdir') def test_albuminfo_move_changes_paths(self): - self.ai.album = u'newAlbumName' + self.ai.album = 'newAlbumName' self.ai.move() self.ai.store() self.i.load() @@ -268,7 +266,7 @@ class AlbumFileTest(_common.TestCase): def test_albuminfo_move_moves_file(self): oldpath = self.i.path - self.ai.album = u'newAlbumName' + self.ai.album = 'newAlbumName' self.ai.move() self.ai.store() self.i.load() @@ -278,7 +276,7 @@ class AlbumFileTest(_common.TestCase): def test_albuminfo_move_copies_file(self): oldpath = self.i.path - self.ai.album = u'newAlbumName' + self.ai.album = 'newAlbumName' self.ai.move(operation=MoveOperation.COPY) self.ai.store() self.i.load() @@ -289,7 +287,7 @@ class AlbumFileTest(_common.TestCase): @unittest.skipUnless(_common.HAVE_REFLINK, "need reflink") def test_albuminfo_move_reflinks_file(self): oldpath = self.i.path - self.ai.album = u'newAlbumName' + self.ai.album = 'newAlbumName' self.ai.move(operation=MoveOperation.REFLINK) self.ai.store() self.i.load() @@ -306,7 +304,7 @@ class AlbumFileTest(_common.TestCase): class ArtFileTest(_common.TestCase): def setUp(self): - super(ArtFileTest, self).setUp() + super().setUp() # Make library and item. self.lib = beets.library.Library(':memory:') @@ -335,7 +333,7 @@ class ArtFileTest(_common.TestCase): def test_art_moves_with_album(self): self.assertTrue(os.path.exists(self.art)) oldpath = self.i.path - self.ai.album = u'newAlbum' + self.ai.album = 'newAlbum' self.ai.move() self.i.load() @@ -363,7 +361,7 @@ class ArtFileTest(_common.TestCase): touch(newart) i2 = item() i2.path = self.i.path - i2.artist = u'someArtist' + i2.artist = 'someArtist' ai = self.lib.add_album((i2,)) i2.move(operation=MoveOperation.COPY) @@ -379,7 +377,7 @@ class ArtFileTest(_common.TestCase): touch(newart) i2 = item() i2.path = self.i.path - i2.artist = u'someArtist' + i2.artist = 'someArtist' ai = self.lib.add_album((i2,)) i2.move(operation=MoveOperation.COPY) ai.set_art(newart) @@ -393,7 +391,7 @@ class ArtFileTest(_common.TestCase): touch(newart) i2 = item() i2.path = self.i.path - i2.artist = u'someArtist' + i2.artist = 'someArtist' ai = self.lib.add_album((i2,)) i2.move(operation=MoveOperation.COPY) @@ -410,7 +408,7 @@ class ArtFileTest(_common.TestCase): touch(newart) i2 = item() i2.path = self.i.path - i2.artist = u'someArtist' + i2.artist = 'someArtist' ai = self.lib.add_album((i2,)) i2.move(operation=MoveOperation.COPY) @@ -434,7 +432,7 @@ class ArtFileTest(_common.TestCase): try: i2 = item() i2.path = self.i.path - i2.artist = u'someArtist' + i2.artist = 'someArtist' ai = self.lib.add_album((i2,)) i2.move(operation=MoveOperation.COPY) ai.set_art(newart) @@ -452,7 +450,7 @@ class ArtFileTest(_common.TestCase): oldartpath = self.lib.albums()[0].artpath self.assertExists(oldartpath) - self.ai.album = u'different_album' + self.ai.album = 'different_album' self.ai.store() self.ai.items()[0].move() @@ -469,7 +467,7 @@ class ArtFileTest(_common.TestCase): oldartpath = self.lib.albums()[0].artpath self.assertExists(oldartpath) - self.i.album = u'different_album' + self.i.album = 'different_album' self.i.album_id = None # detach from album self.i.move() @@ -481,7 +479,7 @@ class ArtFileTest(_common.TestCase): class RemoveTest(_common.TestCase): def setUp(self): - super(RemoveTest, self).setUp() + super().setUp() # Make library and item. self.lib = beets.library.Library(':memory:') @@ -542,7 +540,7 @@ class RemoveTest(_common.TestCase): # Tests that we can "delete" nonexistent files. class SoftRemoveTest(_common.TestCase): def setUp(self): - super(SoftRemoveTest, self).setUp() + super().setUp() self.path = os.path.join(self.temp_dir, b'testfile') touch(self.path) @@ -555,12 +553,12 @@ class SoftRemoveTest(_common.TestCase): try: util.remove(self.path + b'XXX', True) except OSError: - self.fail(u'OSError when removing path') + self.fail('OSError when removing path') class SafeMoveCopyTest(_common.TestCase): def setUp(self): - super(SafeMoveCopyTest, self).setUp() + super().setUp() self.path = os.path.join(self.temp_dir, b'testfile') touch(self.path) @@ -608,7 +606,7 @@ class SafeMoveCopyTest(_common.TestCase): class PruneTest(_common.TestCase): def setUp(self): - super(PruneTest, self).setUp() + super().setUp() self.base = os.path.join(self.temp_dir, b'testdir') os.mkdir(self.base) @@ -628,7 +626,7 @@ class PruneTest(_common.TestCase): class WalkTest(_common.TestCase): def setUp(self): - super(WalkTest, self).setUp() + super().setUp() self.base = os.path.join(self.temp_dir, b'testdir') os.mkdir(self.base) @@ -668,7 +666,7 @@ class WalkTest(_common.TestCase): class UniquePathTest(_common.TestCase): def setUp(self): - super(UniquePathTest, self).setUp() + super().setUp() self.base = os.path.join(self.temp_dir, b'testdir') os.mkdir(self.base) diff --git a/test/test_ftintitle.py b/test/test_ftintitle.py index d698263be..692f2ef57 100644 --- a/test/test_ftintitle.py +++ b/test/test_ftintitle.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Fabrice Laporte. # @@ -15,7 +14,6 @@ """Tests for the 'ftintitle' plugin.""" -from __future__ import division, absolute_import, print_function import unittest from test.helper import TestHelper @@ -45,41 +43,41 @@ class FtInTitlePluginFunctional(unittest.TestCase, TestHelper): self.config['ftintitle']['auto'] = auto def test_functional_drop(self): - item = self._ft_add_item('/', u'Alice ft Bob', u'Song 1', u'Alice') + item = self._ft_add_item('/', 'Alice ft Bob', 'Song 1', 'Alice') self.run_command('ftintitle', '-d') item.load() - self.assertEqual(item['artist'], u'Alice') - self.assertEqual(item['title'], u'Song 1') + self.assertEqual(item['artist'], 'Alice') + self.assertEqual(item['title'], 'Song 1') def test_functional_not_found(self): - item = self._ft_add_item('/', u'Alice ft Bob', u'Song 1', u'George') + item = self._ft_add_item('/', 'Alice ft Bob', 'Song 1', 'George') self.run_command('ftintitle', '-d') item.load() # item should be unchanged - self.assertEqual(item['artist'], u'Alice ft Bob') - self.assertEqual(item['title'], u'Song 1') + self.assertEqual(item['artist'], 'Alice ft Bob') + self.assertEqual(item['title'], 'Song 1') def test_functional_custom_format(self): self._ft_set_config('feat. {0}') - item = self._ft_add_item('/', u'Alice ft Bob', u'Song 1', u'Alice') + item = self._ft_add_item('/', 'Alice ft Bob', 'Song 1', 'Alice') self.run_command('ftintitle') item.load() - self.assertEqual(item['artist'], u'Alice') - self.assertEqual(item['title'], u'Song 1 feat. Bob') + self.assertEqual(item['artist'], 'Alice') + self.assertEqual(item['title'], 'Song 1 feat. Bob') self._ft_set_config('featuring {0}') - item = self._ft_add_item('/', u'Alice feat. Bob', u'Song 1', u'Alice') + item = self._ft_add_item('/', 'Alice feat. Bob', 'Song 1', 'Alice') self.run_command('ftintitle') item.load() - self.assertEqual(item['artist'], u'Alice') - self.assertEqual(item['title'], u'Song 1 featuring Bob') + self.assertEqual(item['artist'], 'Alice') + self.assertEqual(item['title'], 'Song 1 featuring Bob') self._ft_set_config('with {0}') - item = self._ft_add_item('/', u'Alice feat Bob', u'Song 1', u'Alice') + item = self._ft_add_item('/', 'Alice feat Bob', 'Song 1', 'Alice') self.run_command('ftintitle') item.load() - self.assertEqual(item['artist'], u'Alice') - self.assertEqual(item['title'], u'Song 1 with Bob') + self.assertEqual(item['artist'], 'Alice') + self.assertEqual(item['title'], 'Song 1 with Bob') class FtInTitlePluginTest(unittest.TestCase): @@ -149,33 +147,33 @@ class FtInTitlePluginTest(unittest.TestCase): self.assertEqual(feat_part, test_case['feat_part']) def test_split_on_feat(self): - parts = ftintitle.split_on_feat(u'Alice ft. Bob') - self.assertEqual(parts, (u'Alice', u'Bob')) - parts = ftintitle.split_on_feat(u'Alice feat Bob') - self.assertEqual(parts, (u'Alice', u'Bob')) - parts = ftintitle.split_on_feat(u'Alice feat. Bob') - self.assertEqual(parts, (u'Alice', u'Bob')) - parts = ftintitle.split_on_feat(u'Alice featuring Bob') - self.assertEqual(parts, (u'Alice', u'Bob')) - parts = ftintitle.split_on_feat(u'Alice & Bob') - self.assertEqual(parts, (u'Alice', u'Bob')) - parts = ftintitle.split_on_feat(u'Alice and Bob') - self.assertEqual(parts, (u'Alice', u'Bob')) - parts = ftintitle.split_on_feat(u'Alice With Bob') - self.assertEqual(parts, (u'Alice', u'Bob')) - parts = ftintitle.split_on_feat(u'Alice defeat Bob') - self.assertEqual(parts, (u'Alice defeat Bob', None)) + parts = ftintitle.split_on_feat('Alice ft. Bob') + self.assertEqual(parts, ('Alice', 'Bob')) + parts = ftintitle.split_on_feat('Alice feat Bob') + self.assertEqual(parts, ('Alice', 'Bob')) + parts = ftintitle.split_on_feat('Alice feat. Bob') + self.assertEqual(parts, ('Alice', 'Bob')) + parts = ftintitle.split_on_feat('Alice featuring Bob') + self.assertEqual(parts, ('Alice', 'Bob')) + parts = ftintitle.split_on_feat('Alice & Bob') + self.assertEqual(parts, ('Alice', 'Bob')) + parts = ftintitle.split_on_feat('Alice and Bob') + self.assertEqual(parts, ('Alice', 'Bob')) + parts = ftintitle.split_on_feat('Alice With Bob') + self.assertEqual(parts, ('Alice', 'Bob')) + parts = ftintitle.split_on_feat('Alice defeat Bob') + self.assertEqual(parts, ('Alice defeat Bob', None)) def test_contains_feat(self): - self.assertTrue(ftintitle.contains_feat(u'Alice ft. Bob')) - self.assertTrue(ftintitle.contains_feat(u'Alice feat. Bob')) - self.assertTrue(ftintitle.contains_feat(u'Alice feat Bob')) - self.assertTrue(ftintitle.contains_feat(u'Alice featuring Bob')) - self.assertTrue(ftintitle.contains_feat(u'Alice & Bob')) - self.assertTrue(ftintitle.contains_feat(u'Alice and Bob')) - self.assertTrue(ftintitle.contains_feat(u'Alice With Bob')) - self.assertFalse(ftintitle.contains_feat(u'Alice defeat Bob')) - self.assertFalse(ftintitle.contains_feat(u'Aliceft.Bob')) + self.assertTrue(ftintitle.contains_feat('Alice ft. Bob')) + self.assertTrue(ftintitle.contains_feat('Alice feat. Bob')) + self.assertTrue(ftintitle.contains_feat('Alice feat Bob')) + self.assertTrue(ftintitle.contains_feat('Alice featuring Bob')) + self.assertTrue(ftintitle.contains_feat('Alice & Bob')) + self.assertTrue(ftintitle.contains_feat('Alice and Bob')) + self.assertTrue(ftintitle.contains_feat('Alice With Bob')) + self.assertFalse(ftintitle.contains_feat('Alice defeat Bob')) + self.assertFalse(ftintitle.contains_feat('Aliceft.Bob')) def suite(): diff --git a/test/test_hidden.py b/test/test_hidden.py index e2dd1afb3..9bce048c1 100644 --- a/test/test_hidden.py +++ b/test/test_hidden.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Fabrice Laporte. # @@ -15,7 +14,6 @@ """Tests for the 'hidden' utility.""" -from __future__ import division, absolute_import, print_function import unittest import sys diff --git a/test/test_hook.py b/test/test_hook.py index 5ce3abd00..6ade06349 100644 --- a/test/test_hook.py +++ b/test/test_hook.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2015, Thomas Scholtes. # @@ -13,7 +12,6 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -from __future__ import division, absolute_import, print_function import os.path import sys @@ -95,13 +93,13 @@ class HookTest(_common.TestCase, TestHelper): ] for index, path in enumerate(temporary_paths): - self._add_hook('test_no_argument_event_{0}'.format(index), - 'touch "{0}"'.format(path)) + self._add_hook(f'test_no_argument_event_{index}', + f'touch "{path}"') self.load_plugins('hook') for index in range(len(temporary_paths)): - plugins.send('test_no_argument_event_{0}'.format(index)) + plugins.send(f'test_no_argument_event_{index}') for path in temporary_paths: self.assertTrue(os.path.isfile(path)) @@ -110,12 +108,12 @@ class HookTest(_common.TestCase, TestHelper): @unittest.skipIf(sys.platform, 'win32') # FIXME: fails on windows def test_hook_event_substitution(self): temporary_directory = tempfile._get_default_tempdir() - event_names = ['test_event_event_{0}'.format(i) for i in + event_names = [f'test_event_event_{i}' for i in range(self.TEST_HOOK_COUNT)] for event in event_names: self._add_hook(event, - 'touch "{0}/{{event}}"'.format(temporary_directory)) + f'touch "{temporary_directory}/{{event}}"') self.load_plugins('hook') @@ -135,13 +133,13 @@ class HookTest(_common.TestCase, TestHelper): ] for index, path in enumerate(temporary_paths): - self._add_hook('test_argument_event_{0}'.format(index), + self._add_hook(f'test_argument_event_{index}', 'touch "{path}"') self.load_plugins('hook') for index, path in enumerate(temporary_paths): - plugins.send('test_argument_event_{0}'.format(index), path=path) + plugins.send(f'test_argument_event_{index}', path=path) for path in temporary_paths: self.assertTrue(os.path.isfile(path)) @@ -155,13 +153,13 @@ class HookTest(_common.TestCase, TestHelper): ] for index, path in enumerate(temporary_paths): - self._add_hook('test_bytes_event_{0}'.format(index), + self._add_hook(f'test_bytes_event_{index}', 'touch "{path}"') self.load_plugins('hook') for index, path in enumerate(temporary_paths): - plugins.send('test_bytes_event_{0}'.format(index), path=path) + plugins.send(f'test_bytes_event_{index}', path=path) for path in temporary_paths: self.assertTrue(os.path.isfile(path)) diff --git a/test/test_ihate.py b/test/test_ihate.py index f52617481..3ba4e67de 100644 --- a/test/test_ihate.py +++ b/test/test_ihate.py @@ -1,8 +1,5 @@ -# -*- coding: utf-8 -*- - """Tests for the 'ihate' plugin""" -from __future__ import division, absolute_import, print_function import unittest from beets import importer @@ -16,34 +13,34 @@ class IHatePluginTest(unittest.TestCase): match_pattern = {} test_item = Item( - genre=u'TestGenre', - album=u'TestAlbum', - artist=u'TestArtist') + genre='TestGenre', + album='TestAlbum', + artist='TestArtist') task = importer.SingletonImportTask(None, test_item) # Empty query should let it pass. self.assertFalse(IHatePlugin.do_i_hate_this(task, match_pattern)) # 1 query match. - match_pattern = [u"artist:bad_artist", u"artist:TestArtist"] + match_pattern = ["artist:bad_artist", "artist:TestArtist"] self.assertTrue(IHatePlugin.do_i_hate_this(task, match_pattern)) # 2 query matches, either should trigger. - match_pattern = [u"album:test", u"artist:testartist"] + match_pattern = ["album:test", "artist:testartist"] self.assertTrue(IHatePlugin.do_i_hate_this(task, match_pattern)) # Query is blocked by AND clause. - match_pattern = [u"album:notthis genre:testgenre"] + match_pattern = ["album:notthis genre:testgenre"] self.assertFalse(IHatePlugin.do_i_hate_this(task, match_pattern)) # Both queries are blocked by AND clause with unmatched condition. - match_pattern = [u"album:notthis genre:testgenre", - u"artist:testartist album:notthis"] + match_pattern = ["album:notthis genre:testgenre", + "artist:testartist album:notthis"] self.assertFalse(IHatePlugin.do_i_hate_this(task, match_pattern)) # Only one query should fire. - match_pattern = [u"album:testalbum genre:testgenre", - u"artist:testartist album:notthis"] + match_pattern = ["album:testalbum genre:testgenre", + "artist:testartist album:notthis"] self.assertTrue(IHatePlugin.do_i_hate_this(task, match_pattern)) diff --git a/test/test_importadded.py b/test/test_importadded.py index dd933c3c8..263ab34d0 100644 --- a/test/test_importadded.py +++ b/test/test_importadded.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Stig Inge Lea Bjornsen. # @@ -13,7 +12,6 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -from __future__ import division, absolute_import, print_function """Tests for the `importadded` plugin.""" @@ -54,7 +52,7 @@ class ImportAddedTest(unittest.TestCase, ImportHelper): self._create_import_dir(2) # Different mtimes on the files to be imported in order to test the # plugin - modify_mtimes((mfile.path for mfile in self.media_files)) + modify_mtimes(mfile.path for mfile in self.media_files) self.min_mtime = min(os.path.getmtime(mfile.path) for mfile in self.media_files) self.matcher = AutotagStub().install() @@ -72,7 +70,7 @@ class ImportAddedTest(unittest.TestCase, ImportHelper): for m in self.media_files: if m.title.replace('Tag', 'Applied') == item.title: return m - raise AssertionError(u"No MediaFile found for Item " + + raise AssertionError("No MediaFile found for Item " + util.displayable_path(item.path)) def assertEqualTimes(self, first, second, msg=None): # noqa @@ -113,8 +111,8 @@ class ImportAddedTest(unittest.TestCase, ImportHelper): self.importer.run() album = self.lib.albums().get() album_added_before = album.added - items_added_before = dict((item.path, item.added) - for item in album.items()) + items_added_before = {item.path: item.added + for item in album.items()} # Newer Item path mtimes as if Beets had modified them modify_mtimes(items_added_before.keys(), offset=10000) # Reimport @@ -123,11 +121,11 @@ class ImportAddedTest(unittest.TestCase, ImportHelper): # Verify the reimported items album = self.lib.albums().get() self.assertEqualTimes(album.added, album_added_before) - items_added_after = dict((item.path, item.added) - for item in album.items()) + items_added_after = {item.path: item.added + for item in album.items()} for item_path, added_after in items_added_after.items(): self.assertEqualTimes(items_added_before[item_path], added_after, - u"reimport modified Item.added for " + + "reimport modified Item.added for " + util.displayable_path(item_path)) def test_import_singletons_with_added_dates(self): @@ -152,8 +150,8 @@ class ImportAddedTest(unittest.TestCase, ImportHelper): self.config['import']['singletons'] = True # Import and record the original added dates self.importer.run() - items_added_before = dict((item.path, item.added) - for item in self.lib.items()) + items_added_before = {item.path: item.added + for item in self.lib.items()} # Newer Item path mtimes as if Beets had modified them modify_mtimes(items_added_before.keys(), offset=10000) # Reimport @@ -161,11 +159,11 @@ class ImportAddedTest(unittest.TestCase, ImportHelper): self._setup_import_session(import_dir=import_dir, singletons=True) self.importer.run() # Verify the reimported items - items_added_after = dict((item.path, item.added) - for item in self.lib.items()) + items_added_after = {item.path: item.added + for item in self.lib.items()} for item_path, added_after in items_added_after.items(): self.assertEqualTimes(items_added_before[item_path], added_after, - u"reimport modified Item.added for " + + "reimport modified Item.added for " + util.displayable_path(item_path)) diff --git a/test/test_importer.py b/test/test_importer.py index 16881a152..306491af2 100644 --- a/test/test_importer.py +++ b/test/test_importer.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -13,7 +12,6 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -from __future__ import division, absolute_import, print_function """Tests for the general importer functionality. """ @@ -27,7 +25,7 @@ from six import StringIO from tempfile import mkstemp from zipfile import ZipFile from tarfile import TarFile -from mock import patch, Mock +from unittest.mock import patch, Mock import unittest from test import _common @@ -44,7 +42,7 @@ from beets import logging from beets import util -class AutotagStub(object): +class AutotagStub: """Stub out MusicBrainz album and track matcher and control what the autotagger returns. """ @@ -97,9 +95,9 @@ class AutotagStub(object): def match_track(self, artist, title): yield TrackInfo( title=title.replace('Tag', 'Applied'), - track_id=u'trackid', + track_id='trackid', artist=artist.replace('Tag', 'Applied'), - artist_id=u'artistid', + artist_id='artistid', length=1, index=0, ) @@ -112,8 +110,8 @@ class AutotagStub(object): def _make_track_match(self, artist, album, number): return TrackInfo( - title=u'Applied Title %d' % number, - track_id=u'match %d' % number, + title='Applied Title %d' % number, + track_id='match %d' % number, artist=artist, length=1, index=0, @@ -125,7 +123,7 @@ class AutotagStub(object): else: id = '' if artist is None: - artist = u"Various Artists" + artist = "Various Artists" else: artist = artist.replace('Tag', 'Applied') + id album = album.replace('Tag', 'Applied') + id @@ -139,9 +137,9 @@ class AutotagStub(object): album=album, tracks=track_infos, va=False, - album_id=u'albumid' + id, - artist_id=u'artistid' + id, - albumtype=u'soundtrack' + album_id='albumid' + id, + artist_id='artistid' + id, + albumtype='soundtrack' ) @@ -152,11 +150,11 @@ class ImportHelper(TestHelper): """ def setup_beets(self, disk=False): - super(ImportHelper, self).setup_beets(disk) + super().setup_beets(disk) self.lib.path_formats = [ - (u'default', os.path.join('$artist', '$album', '$title')), - (u'singleton:true', os.path.join('singletons', '$title')), - (u'comp:true', os.path.join('compilations', '$album', '$title')), + ('default', os.path.join('$artist', '$album', '$title')), + ('singleton:true', os.path.join('singletons', '$title')), + ('comp:true', os.path.join('compilations', '$album', '$title')), ] def _create_import_dir(self, count=3): @@ -183,8 +181,8 @@ class ImportHelper(TestHelper): resource_path = os.path.join(_common.RSRC, b'full.mp3') metadata = { - 'artist': u'Tag Artist', - 'album': u'Tag Album', + 'artist': 'Tag Artist', + 'album': 'Tag Album', 'albumartist': None, 'mb_trackid': None, 'mb_albumid': None, @@ -202,7 +200,7 @@ class ImportHelper(TestHelper): # Set metadata metadata['track'] = i + 1 - metadata['title'] = u'Tag Title %d' % (i + 1) + metadata['title'] = 'Tag Title %d' % (i + 1) for attr in metadata: setattr(medium, attr, metadata[attr]) medium.save() @@ -259,14 +257,14 @@ class NonAutotaggedImportTest(_common.TestCase, ImportHelper): self.importer.run() albums = self.lib.albums() self.assertEqual(len(albums), 1) - self.assertEqual(albums[0].albumartist, u'Tag Artist') + self.assertEqual(albums[0].albumartist, 'Tag Artist') def test_import_copy_arrives(self): self.importer.run() for mediafile in self.import_media: self.assert_file_in_lib( b'Tag Artist', b'Tag Album', - util.bytestring_path('{0}.mp3'.format(mediafile.title))) + util.bytestring_path(f'{mediafile.title}.mp3')) def test_threaded_import_copy_arrives(self): config['threaded'] = True @@ -275,7 +273,7 @@ class NonAutotaggedImportTest(_common.TestCase, ImportHelper): for mediafile in self.import_media: self.assert_file_in_lib( b'Tag Artist', b'Tag Album', - util.bytestring_path('{0}.mp3'.format(mediafile.title))) + util.bytestring_path(f'{mediafile.title}.mp3')) def test_import_with_move_deletes_import_files(self): config['import']['move'] = True @@ -311,7 +309,7 @@ class NonAutotaggedImportTest(_common.TestCase, ImportHelper): for mediafile in self.import_media: self.assert_file_in_lib( b'Tag Artist', b'Tag Album', - util.bytestring_path('{0}.mp3'.format(mediafile.title))) + util.bytestring_path(f'{mediafile.title}.mp3')) def test_threaded_import_move_deletes_import(self): config['import']['move'] = True @@ -348,7 +346,7 @@ class NonAutotaggedImportTest(_common.TestCase, ImportHelper): filename = os.path.join( self.libdir, b'Tag Artist', b'Tag Album', - util.bytestring_path('{0}.mp3'.format(mediafile.title)) + util.bytestring_path(f'{mediafile.title}.mp3') ) self.assertExists(filename) self.assertTrue(os.path.islink(filename)) @@ -365,7 +363,7 @@ class NonAutotaggedImportTest(_common.TestCase, ImportHelper): filename = os.path.join( self.libdir, b'Tag Artist', b'Tag Album', - util.bytestring_path('{0}.mp3'.format(mediafile.title)) + util.bytestring_path(f'{mediafile.title}.mp3') ) self.assertExists(filename) s1 = os.stat(mediafile.path) @@ -443,7 +441,7 @@ class ImportTarTest(ImportZipTest): return path -@unittest.skipIf(not has_program('unrar'), u'unrar program not found') +@unittest.skipIf(not has_program('unrar'), 'unrar program not found') class ImportRarTest(ImportZipTest): def create_archive(self): @@ -484,7 +482,7 @@ class ImportSingletonTest(_common.TestCase, ImportHelper): self.importer.add_choice(importer.action.ASIS) self.importer.run() - self.assertEqual(self.lib.items().get().title, u'Tag Title 1') + self.assertEqual(self.lib.items().get().title, 'Tag Title 1') def test_apply_asis_does_not_add_album(self): self.assertEqual(self.lib.albums().get(), None) @@ -505,7 +503,7 @@ class ImportSingletonTest(_common.TestCase, ImportHelper): self.importer.add_choice(importer.action.APPLY) self.importer.run() - self.assertEqual(self.lib.items().get().title, u'Applied Title 1') + self.assertEqual(self.lib.items().get().title, 'Applied Title 1') def test_apply_candidate_does_not_add_album(self): self.importer.add_choice(importer.action.APPLY) @@ -551,12 +549,12 @@ class ImportSingletonTest(_common.TestCase, ImportHelper): self.assertEqual(len(self.lib.albums()), 2) def test_set_fields(self): - genre = u"\U0001F3B7 Jazz" - collection = u"To Listen" + genre = "\U0001F3B7 Jazz" + collection = "To Listen" config['import']['set_fields'] = { - u'collection': collection, - u'genre': genre + 'collection': collection, + 'genre': genre } # As-is item import. @@ -602,13 +600,13 @@ class ImportTest(_common.TestCase, ImportHelper): self.importer.add_choice(importer.action.ASIS) self.importer.run() - self.assertEqual(self.lib.albums().get().album, u'Tag Album') + self.assertEqual(self.lib.albums().get().album, 'Tag Album') def test_apply_asis_adds_tracks(self): self.assertEqual(self.lib.items().get(), None) self.importer.add_choice(importer.action.ASIS) self.importer.run() - self.assertEqual(self.lib.items().get().title, u'Tag Title 1') + self.assertEqual(self.lib.items().get().title, 'Tag Title 1') def test_apply_asis_adds_album_path(self): self.assert_lib_dir_empty() @@ -623,14 +621,14 @@ class ImportTest(_common.TestCase, ImportHelper): self.importer.add_choice(importer.action.APPLY) self.importer.run() - self.assertEqual(self.lib.albums().get().album, u'Applied Album') + self.assertEqual(self.lib.albums().get().album, 'Applied Album') def test_apply_candidate_adds_tracks(self): self.assertEqual(self.lib.items().get(), None) self.importer.add_choice(importer.action.APPLY) self.importer.run() - self.assertEqual(self.lib.items().get().title, u'Applied Title 1') + self.assertEqual(self.lib.items().get().title, 'Applied Title 1') def test_apply_candidate_adds_album_path(self): self.assert_lib_dir_empty() @@ -644,19 +642,19 @@ class ImportTest(_common.TestCase, ImportHelper): config['import']['from_scratch'] = True for mediafile in self.import_media: - mediafile.genre = u'Tag Genre' + mediafile.genre = 'Tag Genre' mediafile.save() self.importer.add_choice(importer.action.APPLY) self.importer.run() - self.assertEqual(self.lib.items().get().genre, u'') + self.assertEqual(self.lib.items().get().genre, '') def test_apply_from_scratch_keeps_format(self): config['import']['from_scratch'] = True self.importer.add_choice(importer.action.APPLY) self.importer.run() - self.assertEqual(self.lib.items().get().format, u'MP3') + self.assertEqual(self.lib.items().get().format, 'MP3') def test_apply_from_scratch_keeps_bitrate(self): config['import']['from_scratch'] = True @@ -716,7 +714,7 @@ class ImportTest(_common.TestCase, ImportHelper): self.importer.run() import_dir = displayable_path(import_dir) - self.assertIn(u'No files imported from {0}'.format(import_dir), logs) + self.assertIn(f'No files imported from {import_dir}', logs) def test_empty_directory_singleton_warning(self): import_dir = os.path.join(self.temp_dir, b'empty') @@ -726,7 +724,7 @@ class ImportTest(_common.TestCase, ImportHelper): self.importer.run() import_dir = displayable_path(import_dir) - self.assertIn(u'No files imported from {0}'.format(import_dir), logs) + self.assertIn(f'No files imported from {import_dir}', logs) def test_asis_no_data_source(self): self.assertEqual(self.lib.items().get(), None) @@ -738,14 +736,14 @@ class ImportTest(_common.TestCase, ImportHelper): self.lib.items().get().data_source def test_set_fields(self): - genre = u"\U0001F3B7 Jazz" - collection = u"To Listen" - comments = u"managed by beets" + genre = "\U0001F3B7 Jazz" + collection = "To Listen" + comments = "managed by beets" config['import']['set_fields'] = { - u'genre': genre, - u'collection': collection, - u'comments': comments + 'genre': genre, + 'collection': collection, + 'comments': comments } # As-is album import. @@ -813,7 +811,7 @@ class ImportTracksTest(_common.TestCase, ImportHelper): self.importer.add_choice(importer.action.APPLY) self.importer.add_choice(importer.action.APPLY) self.importer.run() - self.assertEqual(self.lib.items().get().title, u'Applied Title 1') + self.assertEqual(self.lib.items().get().title, 'Applied Title 1') self.assertEqual(self.lib.albums().get(), None) def test_apply_tracks_adds_singleton_path(self): @@ -842,27 +840,27 @@ class ImportCompilationTest(_common.TestCase, ImportHelper): def test_asis_homogenous_sets_albumartist(self): self.importer.add_choice(importer.action.ASIS) self.importer.run() - self.assertEqual(self.lib.albums().get().albumartist, u'Tag Artist') + self.assertEqual(self.lib.albums().get().albumartist, 'Tag Artist') for item in self.lib.items(): - self.assertEqual(item.albumartist, u'Tag Artist') + self.assertEqual(item.albumartist, 'Tag Artist') def test_asis_heterogenous_sets_various_albumartist(self): - self.import_media[0].artist = u'Other Artist' + self.import_media[0].artist = 'Other Artist' self.import_media[0].save() - self.import_media[1].artist = u'Another Artist' + self.import_media[1].artist = 'Another Artist' self.import_media[1].save() self.importer.add_choice(importer.action.ASIS) self.importer.run() self.assertEqual(self.lib.albums().get().albumartist, - u'Various Artists') + 'Various Artists') for item in self.lib.items(): - self.assertEqual(item.albumartist, u'Various Artists') + self.assertEqual(item.albumartist, 'Various Artists') def test_asis_heterogenous_sets_sompilation(self): - self.import_media[0].artist = u'Other Artist' + self.import_media[0].artist = 'Other Artist' self.import_media[0].save() - self.import_media[1].artist = u'Another Artist' + self.import_media[1].artist = 'Another Artist' self.import_media[1].save() self.importer.add_choice(importer.action.ASIS) @@ -871,33 +869,33 @@ class ImportCompilationTest(_common.TestCase, ImportHelper): self.assertTrue(item.comp) def test_asis_sets_majority_albumartist(self): - self.import_media[0].artist = u'Other Artist' + self.import_media[0].artist = 'Other Artist' self.import_media[0].save() - self.import_media[1].artist = u'Other Artist' + self.import_media[1].artist = 'Other Artist' self.import_media[1].save() self.importer.add_choice(importer.action.ASIS) self.importer.run() - self.assertEqual(self.lib.albums().get().albumartist, u'Other Artist') + self.assertEqual(self.lib.albums().get().albumartist, 'Other Artist') for item in self.lib.items(): - self.assertEqual(item.albumartist, u'Other Artist') + self.assertEqual(item.albumartist, 'Other Artist') def test_asis_albumartist_tag_sets_albumartist(self): - self.import_media[0].artist = u'Other Artist' - self.import_media[1].artist = u'Another Artist' + self.import_media[0].artist = 'Other Artist' + self.import_media[1].artist = 'Another Artist' for mediafile in self.import_media: - mediafile.albumartist = u'Album Artist' - mediafile.mb_albumartistid = u'Album Artist ID' + mediafile.albumartist = 'Album Artist' + mediafile.mb_albumartistid = 'Album Artist ID' mediafile.save() self.importer.add_choice(importer.action.ASIS) self.importer.run() - self.assertEqual(self.lib.albums().get().albumartist, u'Album Artist') + self.assertEqual(self.lib.albums().get().albumartist, 'Album Artist') self.assertEqual(self.lib.albums().get().mb_albumartistid, - u'Album Artist ID') + 'Album Artist ID') for item in self.lib.items(): - self.assertEqual(item.albumartist, u'Album Artist') - self.assertEqual(item.mb_albumartistid, u'Album Artist ID') + self.assertEqual(item.albumartist, 'Album Artist') + self.assertEqual(item.mb_albumartistid, 'Album Artist ID') class ImportExistingTest(_common.TestCase, ImportHelper): @@ -920,45 +918,45 @@ class ImportExistingTest(_common.TestCase, ImportHelper): def test_does_not_duplicate_item(self): self.setup_importer.run() - self.assertEqual(len((self.lib.items())), 1) + self.assertEqual(len(self.lib.items()), 1) self.importer.add_choice(importer.action.APPLY) self.importer.run() - self.assertEqual(len((self.lib.items())), 1) + self.assertEqual(len(self.lib.items()), 1) def test_does_not_duplicate_album(self): self.setup_importer.run() - self.assertEqual(len((self.lib.albums())), 1) + self.assertEqual(len(self.lib.albums()), 1) self.importer.add_choice(importer.action.APPLY) self.importer.run() - self.assertEqual(len((self.lib.albums())), 1) + self.assertEqual(len(self.lib.albums()), 1) def test_does_not_duplicate_singleton_track(self): self.setup_importer.add_choice(importer.action.TRACKS) self.setup_importer.add_choice(importer.action.APPLY) self.setup_importer.run() - self.assertEqual(len((self.lib.items())), 1) + self.assertEqual(len(self.lib.items()), 1) self.importer.add_choice(importer.action.TRACKS) self.importer.add_choice(importer.action.APPLY) self.importer.run() - self.assertEqual(len((self.lib.items())), 1) + self.assertEqual(len(self.lib.items()), 1) def test_asis_updates_metadata(self): self.setup_importer.run() medium = MediaFile(self.lib.items().get().path) - medium.title = u'New Title' + medium.title = 'New Title' medium.save() self.importer.add_choice(importer.action.ASIS) self.importer.run() - self.assertEqual(self.lib.items().get().title, u'New Title') + self.assertEqual(self.lib.items().get().title, 'New Title') def test_asis_updated_moves_file(self): self.setup_importer.run() medium = MediaFile(self.lib.items().get().path) - medium.title = u'New Title' + medium.title = 'New Title' medium.save() old_path = os.path.join(b'Applied Artist', b'Applied Album', @@ -974,7 +972,7 @@ class ImportExistingTest(_common.TestCase, ImportHelper): def test_asis_updated_without_copy_does_not_move_file(self): self.setup_importer.run() medium = MediaFile(self.lib.items().get().path) - medium.title = u'New Title' + medium.title = 'New Title' medium.save() old_path = os.path.join(b'Applied Artist', b'Applied Album', @@ -1035,56 +1033,56 @@ class GroupAlbumsImportTest(_common.TestCase, ImportHelper): self.matcher.restore() def test_add_album_for_different_artist_and_different_album(self): - self.import_media[0].artist = u"Artist B" - self.import_media[0].album = u"Album B" + self.import_media[0].artist = "Artist B" + self.import_media[0].album = "Album B" self.import_media[0].save() self.importer.run() - albums = set([album.album for album in self.lib.albums()]) - self.assertEqual(albums, set(['Album B', 'Tag Album'])) + albums = {album.album for album in self.lib.albums()} + self.assertEqual(albums, {'Album B', 'Tag Album'}) def test_add_album_for_different_artist_and_same_albumartist(self): - self.import_media[0].artist = u"Artist B" - self.import_media[0].albumartist = u"Album Artist" + self.import_media[0].artist = "Artist B" + self.import_media[0].albumartist = "Album Artist" self.import_media[0].save() - self.import_media[1].artist = u"Artist C" - self.import_media[1].albumartist = u"Album Artist" + self.import_media[1].artist = "Artist C" + self.import_media[1].albumartist = "Album Artist" self.import_media[1].save() self.importer.run() - artists = set([album.albumartist for album in self.lib.albums()]) - self.assertEqual(artists, set(['Album Artist', 'Tag Artist'])) + artists = {album.albumartist for album in self.lib.albums()} + self.assertEqual(artists, {'Album Artist', 'Tag Artist'}) def test_add_album_for_same_artist_and_different_album(self): - self.import_media[0].album = u"Album B" + self.import_media[0].album = "Album B" self.import_media[0].save() self.importer.run() - albums = set([album.album for album in self.lib.albums()]) - self.assertEqual(albums, set(['Album B', 'Tag Album'])) + albums = {album.album for album in self.lib.albums()} + self.assertEqual(albums, {'Album B', 'Tag Album'}) def test_add_album_for_same_album_and_different_artist(self): - self.import_media[0].artist = u"Artist B" + self.import_media[0].artist = "Artist B" self.import_media[0].save() self.importer.run() - artists = set([album.albumartist for album in self.lib.albums()]) - self.assertEqual(artists, set(['Artist B', 'Tag Artist'])) + artists = {album.albumartist for album in self.lib.albums()} + self.assertEqual(artists, {'Artist B', 'Tag Artist'}) def test_incremental(self): config['import']['incremental'] = True - self.import_media[0].album = u"Album B" + self.import_media[0].album = "Album B" self.import_media[0].save() self.importer.run() - albums = set([album.album for album in self.lib.albums()]) - self.assertEqual(albums, set(['Album B', 'Tag Album'])) + albums = {album.album for album in self.lib.albums()} + self.assertEqual(albums, {'Album B', 'Tag Album'}) class GlobalGroupAlbumsImportTest(GroupAlbumsImportTest): def setUp(self): - super(GlobalGroupAlbumsImportTest, self).setUp() + super().setUp() self.importer.clear_choices() self.importer.default_choice = importer.action.ASIS config['import']['group_albums'] = True @@ -1105,24 +1103,24 @@ class ChooseCandidateTest(_common.TestCase, ImportHelper): def test_choose_first_candidate(self): self.importer.add_choice(1) self.importer.run() - self.assertEqual(self.lib.albums().get().album, u'Applied Album M') + self.assertEqual(self.lib.albums().get().album, 'Applied Album M') def test_choose_second_candidate(self): self.importer.add_choice(2) self.importer.run() - self.assertEqual(self.lib.albums().get().album, u'Applied Album MM') + self.assertEqual(self.lib.albums().get().album, 'Applied Album MM') class InferAlbumDataTest(_common.TestCase): def setUp(self): - super(InferAlbumDataTest, self).setUp() + super().setUp() i1 = _common.item() i2 = _common.item() i3 = _common.item() - i1.title = u'first item' - i2.title = u'second item' - i3.title = u'third item' + i1.title = 'first item' + i2.title = 'second item' + i3.title = 'third item' i1.comp = i2.comp = i3.comp = False i1.albumartist = i2.albumartist = i3.albumartist = '' i1.mb_albumartistid = i2.mb_albumartistid = i3.mb_albumartistid = '' @@ -1138,28 +1136,28 @@ class InferAlbumDataTest(_common.TestCase): self.assertEqual(self.items[0].albumartist, self.items[2].artist) def test_asis_heterogenous_va(self): - self.items[0].artist = u'another artist' - self.items[1].artist = u'some other artist' + self.items[0].artist = 'another artist' + self.items[1].artist = 'some other artist' self.task.set_choice(importer.action.ASIS) self.task.align_album_level_fields() self.assertTrue(self.items[0].comp) - self.assertEqual(self.items[0].albumartist, u'Various Artists') + self.assertEqual(self.items[0].albumartist, 'Various Artists') def test_asis_comp_applied_to_all_items(self): - self.items[0].artist = u'another artist' - self.items[1].artist = u'some other artist' + self.items[0].artist = 'another artist' + self.items[1].artist = 'some other artist' self.task.set_choice(importer.action.ASIS) self.task.align_album_level_fields() for item in self.items: self.assertTrue(item.comp) - self.assertEqual(item.albumartist, u'Various Artists') + self.assertEqual(item.albumartist, 'Various Artists') def test_asis_majority_artist_single_artist(self): - self.items[0].artist = u'another artist' + self.items[0].artist = 'another artist' self.task.set_choice(importer.action.ASIS) self.task.align_album_level_fields() @@ -1168,19 +1166,19 @@ class InferAlbumDataTest(_common.TestCase): self.assertEqual(self.items[0].albumartist, self.items[2].artist) def test_asis_track_albumartist_override(self): - self.items[0].artist = u'another artist' - self.items[1].artist = u'some other artist' + self.items[0].artist = 'another artist' + self.items[1].artist = 'some other artist' for item in self.items: - item.albumartist = u'some album artist' - item.mb_albumartistid = u'some album artist id' + item.albumartist = 'some album artist' + item.mb_albumartistid = 'some album artist id' self.task.set_choice(importer.action.ASIS) self.task.align_album_level_fields() self.assertEqual(self.items[0].albumartist, - u'some album artist') + 'some album artist') self.assertEqual(self.items[0].mb_albumartistid, - u'some album artist id') + 'some album artist id') def test_apply_gets_artist_and_id(self): self.task.set_choice(AlbumMatch(0, None, {}, set(), set())) # APPLY @@ -1193,16 +1191,16 @@ class InferAlbumDataTest(_common.TestCase): def test_apply_lets_album_values_override(self): for item in self.items: - item.albumartist = u'some album artist' - item.mb_albumartistid = u'some album artist id' + item.albumartist = 'some album artist' + item.mb_albumartistid = 'some album artist id' self.task.set_choice(AlbumMatch(0, None, {}, set(), set())) # APPLY self.task.align_album_level_fields() self.assertEqual(self.items[0].albumartist, - u'some album artist') + 'some album artist') self.assertEqual(self.items[0].mb_albumartistid, - u'some album artist id') + 'some album artist id') def test_small_single_artist_album(self): self.items = [self.items[0]] @@ -1216,16 +1214,16 @@ def test_album_info(*args, **kwargs): """Create an AlbumInfo object for testing. """ track_info = TrackInfo( - title=u'new title', - track_id=u'trackid', + title='new title', + track_id='trackid', index=0, ) album_info = AlbumInfo( - artist=u'artist', - album=u'album', + artist='artist', + album='album', tracks=[track_info], - album_id=u'albumid', - artist_id=u'artistid', + album_id='albumid', + artist_id='artistid', ) return iter([album_info]) @@ -1238,7 +1236,7 @@ class ImportDuplicateAlbumTest(unittest.TestCase, TestHelper, self.setup_beets() # Original album - self.add_album_fixture(albumartist=u'artist', album=u'album') + self.add_album_fixture(albumartist='artist', album='album') # Create import session self.importer = self.create_importer() @@ -1249,7 +1247,7 @@ class ImportDuplicateAlbumTest(unittest.TestCase, TestHelper, def test_remove_duplicate_album(self): item = self.lib.items().get() - self.assertEqual(item.title, u't\xeftle 0') + self.assertEqual(item.title, 't\xeftle 0') self.assertExists(item.path) self.importer.default_resolution = self.importer.Resolution.REMOVE @@ -1259,12 +1257,12 @@ class ImportDuplicateAlbumTest(unittest.TestCase, TestHelper, self.assertEqual(len(self.lib.albums()), 1) self.assertEqual(len(self.lib.items()), 1) item = self.lib.items().get() - self.assertEqual(item.title, u'new title') + self.assertEqual(item.title, 'new title') def test_no_autotag_keeps_duplicate_album(self): config['import']['autotag'] = False item = self.lib.items().get() - self.assertEqual(item.title, u't\xeftle 0') + self.assertEqual(item.title, 't\xeftle 0') self.assertExists(item.path) # Imported item has the same artist and album as the one in the @@ -1293,7 +1291,7 @@ class ImportDuplicateAlbumTest(unittest.TestCase, TestHelper, def test_skip_duplicate_album(self): item = self.lib.items().get() - self.assertEqual(item.title, u't\xeftle 0') + self.assertEqual(item.title, 't\xeftle 0') self.importer.default_resolution = self.importer.Resolution.SKIP self.importer.run() @@ -1301,7 +1299,7 @@ class ImportDuplicateAlbumTest(unittest.TestCase, TestHelper, self.assertEqual(len(self.lib.albums()), 1) self.assertEqual(len(self.lib.items()), 1) item = self.lib.items().get() - self.assertEqual(item.title, u't\xeftle 0') + self.assertEqual(item.title, 't\xeftle 0') def test_merge_duplicate_album(self): self.importer.default_resolution = self.importer.Resolution.MERGE @@ -1314,7 +1312,7 @@ class ImportDuplicateAlbumTest(unittest.TestCase, TestHelper, def add_album_fixture(self, **kwargs): # TODO move this into upstream - album = super(ImportDuplicateAlbumTest, self).add_album_fixture() + album = super().add_album_fixture() album.update(kwargs) album.store() return album @@ -1322,8 +1320,8 @@ class ImportDuplicateAlbumTest(unittest.TestCase, TestHelper, def test_track_info(*args, **kwargs): return iter([TrackInfo( - artist=u'artist', title=u'title', - track_id=u'new trackid', index=0,)]) + artist='artist', title='title', + track_id='new trackid', index=0,)]) @patch('beets.autotag.mb.match_track', Mock(side_effect=test_track_info)) @@ -1334,7 +1332,7 @@ class ImportDuplicateSingletonTest(unittest.TestCase, TestHelper, self.setup_beets() # Original file in library - self.add_item_fixture(artist=u'artist', title=u'title', + self.add_item_fixture(artist='artist', title='title', mb_trackid='old trackid') # Import session @@ -1347,7 +1345,7 @@ class ImportDuplicateSingletonTest(unittest.TestCase, TestHelper, def test_remove_duplicate(self): item = self.lib.items().get() - self.assertEqual(item.mb_trackid, u'old trackid') + self.assertEqual(item.mb_trackid, 'old trackid') self.assertExists(item.path) self.importer.default_resolution = self.importer.Resolution.REMOVE @@ -1356,7 +1354,7 @@ class ImportDuplicateSingletonTest(unittest.TestCase, TestHelper, self.assertNotExists(item.path) self.assertEqual(len(self.lib.items()), 1) item = self.lib.items().get() - self.assertEqual(item.mb_trackid, u'new trackid') + self.assertEqual(item.mb_trackid, 'new trackid') def test_keep_duplicate(self): self.assertEqual(len(self.lib.items()), 1) @@ -1368,14 +1366,14 @@ class ImportDuplicateSingletonTest(unittest.TestCase, TestHelper, def test_skip_duplicate(self): item = self.lib.items().get() - self.assertEqual(item.mb_trackid, u'old trackid') + self.assertEqual(item.mb_trackid, 'old trackid') self.importer.default_resolution = self.importer.Resolution.SKIP self.importer.run() self.assertEqual(len(self.lib.items()), 1) item = self.lib.items().get() - self.assertEqual(item.mb_trackid, u'old trackid') + self.assertEqual(item.mb_trackid, 'old trackid') def test_twice_in_import_dir(self): self.skipTest('write me') @@ -1400,8 +1398,8 @@ class TagLogTest(_common.TestCase): sio = StringIO() handler = logging.StreamHandler(sio) session = _common.import_session(loghandler=handler) - session.tag_log('status', u'caf\xe9') # send unicode - self.assertIn(u'status caf\xe9', sio.getvalue()) + session.tag_log('status', 'caf\xe9') # send unicode + self.assertIn('status caf\xe9', sio.getvalue()) class ResumeImportTest(unittest.TestCase, TestHelper): @@ -1426,11 +1424,11 @@ class ResumeImportTest(unittest.TestCase, TestHelper): self.importer.run() self.assertEqual(len(self.lib.albums()), 1) - self.assertIsNotNone(self.lib.albums(u'album:album 0').get()) + self.assertIsNotNone(self.lib.albums('album:album 0').get()) self.importer.run() self.assertEqual(len(self.lib.albums()), 2) - self.assertIsNotNone(self.lib.albums(u'album:album 1').get()) + self.assertIsNotNone(self.lib.albums('album:album 1').get()) @patch('beets.plugins.send') def test_resume_singleton(self, plugins_send): @@ -1447,11 +1445,11 @@ class ResumeImportTest(unittest.TestCase, TestHelper): self.importer.run() self.assertEqual(len(self.lib.items()), 1) - self.assertIsNotNone(self.lib.items(u'title:track 0').get()) + self.assertIsNotNone(self.lib.items('title:track 0').get()) self.importer.run() self.assertEqual(len(self.lib.items()), 2) - self.assertIsNotNone(self.lib.items(u'title:track 1').get()) + self.assertIsNotNone(self.lib.items('title:track 1').get()) class IncrementalImportTest(unittest.TestCase, TestHelper): @@ -1506,7 +1504,7 @@ def _mkmp3(path): class AlbumsInDirTest(_common.TestCase): def setUp(self): - super(AlbumsInDirTest, self).setUp() + super().setUp() # create a directory structure for testing self.base = os.path.abspath(os.path.join(self.temp_dir, b'tempdir')) @@ -1557,8 +1555,8 @@ class MultiDiscAlbumsInDirTest(_common.TestCase): self.base = os.path.abspath(os.path.join(self.temp_dir, b'tempdir')) os.mkdir(self.base) - name = b'CAT' if ascii else util.bytestring_path(u'C\xc1T') - name_alt_case = b'CAt' if ascii else util.bytestring_path(u'C\xc1t') + name = b'CAT' if ascii else util.bytestring_path('C\xc1T') + name_alt_case = b'CAt' if ascii else util.bytestring_path('C\xc1t') self.dirs = [ # Nested album, multiple subdirs. @@ -1681,10 +1679,10 @@ class ReimportTest(unittest.TestCase, ImportHelper, _common.Assertions): # The existing album. album = self.add_album_fixture() album.added = 4242.0 - album.foo = u'bar' # Some flexible attribute. + album.foo = 'bar' # Some flexible attribute. album.store() item = album.items().get() - item.baz = u'qux' + item.baz = 'qux' item.added = 4747.0 item.store() @@ -1708,14 +1706,14 @@ class ReimportTest(unittest.TestCase, ImportHelper, _common.Assertions): def test_reimported_album_gets_new_metadata(self): self._setup_session() - self.assertEqual(self._album().album, u'\xe4lbum') + self.assertEqual(self._album().album, '\xe4lbum') self.importer.run() - self.assertEqual(self._album().album, u'the album') + self.assertEqual(self._album().album, 'the album') def test_reimported_album_preserves_flexattr(self): self._setup_session() self.importer.run() - self.assertEqual(self._album().foo, u'bar') + self.assertEqual(self._album().foo, 'bar') def test_reimported_album_preserves_added(self): self._setup_session() @@ -1725,7 +1723,7 @@ class ReimportTest(unittest.TestCase, ImportHelper, _common.Assertions): def test_reimported_album_preserves_item_flexattr(self): self._setup_session() self.importer.run() - self.assertEqual(self._item().baz, u'qux') + self.assertEqual(self._item().baz, 'qux') def test_reimported_album_preserves_item_added(self): self._setup_session() @@ -1734,14 +1732,14 @@ class ReimportTest(unittest.TestCase, ImportHelper, _common.Assertions): def test_reimported_item_gets_new_metadata(self): self._setup_session(True) - self.assertEqual(self._item().title, u't\xeftle 0') + self.assertEqual(self._item().title, 't\xeftle 0') self.importer.run() - self.assertEqual(self._item().title, u'full') + self.assertEqual(self._item().title, 'full') def test_reimported_item_preserves_flexattr(self): self._setup_session(True) self.importer.run() - self.assertEqual(self._item().baz, u'qux') + self.assertEqual(self._item().baz, 'qux') def test_reimported_item_preserves_added(self): self._setup_session(True) @@ -1769,11 +1767,11 @@ class ImportPretendTest(_common.TestCase, ImportHelper): """ def __init__(self, method_name='runTest'): - super(ImportPretendTest, self).__init__(method_name) + super().__init__(method_name) self.matcher = None def setUp(self): - super(ImportPretendTest, self).setUp() + super().setUp() self.setup_beets() self.__create_import_dir() self.__create_empty_import_dir() @@ -1839,7 +1837,7 @@ class ImportPretendTest(_common.TestCase, ImportHelper): def test_import_pretend_empty(self): logs = self.__run([self.empty_path]) - self.assertEqual(logs, [u'No files imported from {0}' + self.assertEqual(logs, ['No files imported from {}' .format(displayable_path(self.empty_path))]) # Helpers for ImportMusicBrainzIdTest. @@ -1985,8 +1983,8 @@ class ImportMusicBrainzIdTest(_common.TestCase, ImportHelper): 'an invalid and discarded id'] task.lookup_candidates() - self.assertEqual(set(['VALID_RELEASE_0', 'VALID_RELEASE_1']), - set([c.info.album for c in task.candidates])) + self.assertEqual({'VALID_RELEASE_0', 'VALID_RELEASE_1'}, + {c.info.album for c in task.candidates}) def test_candidates_singleton(self): """Test directly SingletonImportTask.lookup_candidates().""" @@ -1997,8 +1995,8 @@ class ImportMusicBrainzIdTest(_common.TestCase, ImportHelper): 'an invalid and discarded id'] task.lookup_candidates() - self.assertEqual(set(['VALID_RECORDING_0', 'VALID_RECORDING_1']), - set([c.info.title for c in task.candidates])) + self.assertEqual({'VALID_RECORDING_0', 'VALID_RECORDING_1'}, + {c.info.title for c in task.candidates}) def suite(): diff --git a/test/test_importfeeds.py b/test/test_importfeeds.py index 064f5eee5..828960527 100644 --- a/test/test_importfeeds.py +++ b/test/test_importfeeds.py @@ -1,7 +1,3 @@ -# -*- coding: utf-8 -*- - -from __future__ import division, absolute_import, print_function - import os import os.path import tempfile diff --git a/test/test_info.py b/test/test_info.py index 0f29621d6..ed42458de 100644 --- a/test/test_info.py +++ b/test/test_info.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Thomas Scholtes. # @@ -13,7 +12,6 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -from __future__ import division, absolute_import, print_function import unittest from test.helper import TestHelper @@ -59,7 +57,7 @@ class InfoTest(unittest.TestCase, TestHelper): out = self.run_with_output('info', 'album:yyyy') self.assertIn(displayable_path(item1.path), out) - self.assertIn(u'album: xxxx', out) + self.assertIn('album: xxxx', out) self.assertNotIn(displayable_path(item2.path), out) @@ -70,7 +68,7 @@ class InfoTest(unittest.TestCase, TestHelper): out = self.run_with_output('info', '--library', 'album:xxxx') self.assertIn(displayable_path(item.path), out) - self.assertIn(u'album: xxxx', out) + self.assertIn('album: xxxx', out) def test_collect_item_and_path(self): path = self.create_mediafile_fixture() @@ -87,16 +85,16 @@ class InfoTest(unittest.TestCase, TestHelper): mediafile.save() out = self.run_with_output('info', '--summarize', 'album:AAA', path) - self.assertIn(u'album: AAA', out) - self.assertIn(u'tracktotal: 5', out) - self.assertIn(u'title: [various]', out) + self.assertIn('album: AAA', out) + self.assertIn('tracktotal: 5', out) + self.assertIn('title: [various]', out) self.remove_mediafile_fixtures() def test_custom_format(self): self.add_item_fixtures() out = self.run_with_output('info', '--library', '--format', '$track. $title - $artist ($length)') - self.assertEqual(u'02. tïtle 0 - the artist (0:01)\n', out) + self.assertEqual('02. tïtle 0 - the artist (0:01)\n', out) def suite(): diff --git a/test/test_ipfs.py b/test/test_ipfs.py index 2fe89e7e5..c0b0b6854 100644 --- a/test/test_ipfs.py +++ b/test/test_ipfs.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # # Permission is hereby granted, free of charge, to any person obtaining @@ -12,9 +11,8 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -from __future__ import division, absolute_import, print_function -from mock import patch, Mock +from unittest.mock import patch, Mock from beets import library from beets.util import bytestring_path, _fsencoding @@ -53,7 +51,7 @@ class IPFSPluginTest(unittest.TestCase, TestHelper): ipfs_item = os.path.basename(want_item.path).decode( _fsencoding(), ) - want_path = '/ipfs/{0}/{1}'.format(test_album.ipfs, + want_path = '/ipfs/{}/{}'.format(test_album.ipfs, ipfs_item) want_path = bytestring_path(want_path) self.assertEqual(check_item.path, want_path) diff --git a/test/test_keyfinder.py b/test/test_keyfinder.py index c8735e47f..4fb77e3de 100644 --- a/test/test_keyfinder.py +++ b/test/test_keyfinder.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Thomas Scholtes. # @@ -13,9 +12,8 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -from __future__ import division, absolute_import, print_function -from mock import patch +from unittest.mock import patch import unittest from test.helper import TestHelper diff --git a/test/test_lastgenre.py b/test/test_lastgenre.py index a8e6318b7..07b12a73e 100644 --- a/test/test_lastgenre.py +++ b/test/test_lastgenre.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Fabrice Laporte. # @@ -15,10 +14,9 @@ """Tests for the 'lastgenre' plugin.""" -from __future__ import division, absolute_import, print_function import unittest -from mock import Mock +from unittest.mock import Mock from test import _common from beetsplug import lastgenre @@ -41,11 +39,11 @@ class LastGenrePluginTest(unittest.TestCase, TestHelper): config['lastgenre']['canonical'] = canonical config['lastgenre']['count'] = count config['lastgenre']['prefer_specific'] = prefer_specific - if isinstance(whitelist, (bool, six.string_types)): + if isinstance(whitelist, (bool, (str,))): # Filename, default, or disabled. config['lastgenre']['whitelist'] = whitelist self.plugin.setup() - if not isinstance(whitelist, (bool, six.string_types)): + if not isinstance(whitelist, (bool, (str,))): # Explicit list of genres. self.plugin.whitelist = whitelist @@ -54,7 +52,7 @@ class LastGenrePluginTest(unittest.TestCase, TestHelper): """ self._setup_config() self.assertEqual(self.plugin._resolve_genres(['delta blues']), - u'Delta Blues') + 'Delta Blues') def test_c14n_only(self): """Default c14n tree funnels up to most common genre except for *wrong* @@ -62,16 +60,16 @@ class LastGenrePluginTest(unittest.TestCase, TestHelper): """ self._setup_config(canonical=True, count=99) self.assertEqual(self.plugin._resolve_genres(['delta blues']), - u'Blues') + 'Blues') self.assertEqual(self.plugin._resolve_genres(['iota blues']), - u'Iota Blues') + 'Iota Blues') def test_whitelist_only(self): """Default whitelist rejects *wrong* (non existing) genres. """ self._setup_config(whitelist=True) self.assertEqual(self.plugin._resolve_genres(['iota blues']), - u'') + '') def test_whitelist_c14n(self): """Default whitelist and c14n both activated result in all parents @@ -79,48 +77,48 @@ class LastGenrePluginTest(unittest.TestCase, TestHelper): """ self._setup_config(canonical=True, whitelist=True, count=99) self.assertEqual(self.plugin._resolve_genres(['delta blues']), - u'Delta Blues, Blues') + 'Delta Blues, Blues') def test_whitelist_custom(self): """Keep only genres that are in the whitelist. """ - self._setup_config(whitelist=set(['blues', 'rock', 'jazz']), + self._setup_config(whitelist={'blues', 'rock', 'jazz'}, count=2) self.assertEqual(self.plugin._resolve_genres(['pop', 'blues']), - u'Blues') + 'Blues') - self._setup_config(canonical='', whitelist=set(['rock'])) + self._setup_config(canonical='', whitelist={'rock'}) self.assertEqual(self.plugin._resolve_genres(['delta blues']), - u'') + '') def test_count(self): """Keep the n first genres, as we expect them to be sorted from more to less popular. """ - self._setup_config(whitelist=set(['blues', 'rock', 'jazz']), + self._setup_config(whitelist={'blues', 'rock', 'jazz'}, count=2) self.assertEqual(self.plugin._resolve_genres( ['jazz', 'pop', 'rock', 'blues']), - u'Jazz, Rock') + 'Jazz, Rock') def test_count_c14n(self): """Keep the n first genres, after having applied c14n when necessary """ - self._setup_config(whitelist=set(['blues', 'rock', 'jazz']), + self._setup_config(whitelist={'blues', 'rock', 'jazz'}, canonical=True, count=2) # thanks to c14n, 'blues' superseeds 'country blues' and takes the # second slot self.assertEqual(self.plugin._resolve_genres( ['jazz', 'pop', 'country blues', 'rock']), - u'Jazz, Blues') + 'Jazz, Blues') def test_c14n_whitelist(self): """Genres first pass through c14n and are then filtered """ - self._setup_config(canonical=True, whitelist=set(['rock'])) + self._setup_config(canonical=True, whitelist={'rock'}) self.assertEqual(self.plugin._resolve_genres(['delta blues']), - u'') + '') def test_empty_string_enables_canonical(self): """For backwards compatibility, setting the `canonical` option @@ -128,7 +126,7 @@ class LastGenrePluginTest(unittest.TestCase, TestHelper): """ self._setup_config(canonical='', count=99) self.assertEqual(self.plugin._resolve_genres(['delta blues']), - u'Blues') + 'Blues') def test_empty_string_enables_whitelist(self): """Again for backwards compatibility, setting the `whitelist` @@ -136,7 +134,7 @@ class LastGenrePluginTest(unittest.TestCase, TestHelper): """ self._setup_config(whitelist='') self.assertEqual(self.plugin._resolve_genres(['iota blues']), - u'') + '') def test_prefer_specific_loads_tree(self): """When prefer_specific is enabled but canonical is not the @@ -151,41 +149,41 @@ class LastGenrePluginTest(unittest.TestCase, TestHelper): self._setup_config(prefer_specific=True, canonical=False, count=4) self.assertEqual(self.plugin._resolve_genres( ['math rock', 'post-rock']), - u'Post-Rock, Math Rock') + 'Post-Rock, Math Rock') def test_no_duplicate(self): """Remove duplicated genres. """ self._setup_config(count=99) self.assertEqual(self.plugin._resolve_genres(['blues', 'blues']), - u'Blues') + 'Blues') def test_tags_for(self): - class MockPylastElem(object): + class MockPylastElem: def __init__(self, name): self.name = name def get_name(self): return self.name - class MockPylastObj(object): + class MockPylastObj: def get_top_tags(self): tag1 = Mock() tag1.weight = 90 - tag1.item = MockPylastElem(u'Pop') + tag1.item = MockPylastElem('Pop') tag2 = Mock() tag2.weight = 40 - tag2.item = MockPylastElem(u'Rap') + tag2.item = MockPylastElem('Rap') return [tag1, tag2] plugin = lastgenre.LastGenrePlugin() res = plugin._tags_for(MockPylastObj()) - self.assertEqual(res, [u'pop', u'rap']) + self.assertEqual(res, ['pop', 'rap']) res = plugin._tags_for(MockPylastObj(), min_weight=50) - self.assertEqual(res, [u'pop']) + self.assertEqual(res, ['pop']) def test_get_genre(self): - mock_genres = {'track': u'1', 'album': u'2', 'artist': u'3'} + mock_genres = {'track': '1', 'album': '2', 'artist': '3'} def mock_fetch_track_genre(self, obj=None): return mock_genres['track'] @@ -206,29 +204,29 @@ class LastGenrePluginTest(unittest.TestCase, TestHelper): config['lastgenre'] = {'force': False} res = self.plugin._get_genre(item) - self.assertEqual(res, (item.genre, u'keep')) + self.assertEqual(res, (item.genre, 'keep')) - config['lastgenre'] = {'force': True, 'source': u'track'} + config['lastgenre'] = {'force': True, 'source': 'track'} res = self.plugin._get_genre(item) - self.assertEqual(res, (mock_genres['track'], u'track')) + self.assertEqual(res, (mock_genres['track'], 'track')) - config['lastgenre'] = {'source': u'album'} + config['lastgenre'] = {'source': 'album'} res = self.plugin._get_genre(item) - self.assertEqual(res, (mock_genres['album'], u'album')) + self.assertEqual(res, (mock_genres['album'], 'album')) - config['lastgenre'] = {'source': u'artist'} + config['lastgenre'] = {'source': 'artist'} res = self.plugin._get_genre(item) - self.assertEqual(res, (mock_genres['artist'], u'artist')) + self.assertEqual(res, (mock_genres['artist'], 'artist')) mock_genres['artist'] = None res = self.plugin._get_genre(item) - self.assertEqual(res, (item.genre, u'original')) + self.assertEqual(res, (item.genre, 'original')) - config['lastgenre'] = {'fallback': u'rap'} + config['lastgenre'] = {'fallback': 'rap'} item.genre = None res = self.plugin._get_genre(item) self.assertEqual(res, (config['lastgenre']['fallback'].get(), - u'fallback')) + 'fallback')) def test_sort_by_depth(self): self._setup_config(canonical=True) diff --git a/test/test_library.py b/test/test_library.py index 51171b1f8..6ce913602 100644 --- a/test/test_library.py +++ b/test/test_library.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -15,7 +14,6 @@ """Tests for non-query database functions of Item. """ -from __future__ import division, absolute_import, print_function import os import os.path @@ -46,12 +44,12 @@ np = util.normpath class LoadTest(_common.LibTestCase): def test_load_restores_data_from_db(self): original_title = self.i.title - self.i.title = u'something' + self.i.title = 'something' self.i.load() self.assertEqual(original_title, self.i.title) def test_load_clears_dirty_flags(self): - self.i.artist = u'something' + self.i.artist = 'something' self.assertTrue('artist' in self.i._dirty) self.i.load() self.assertTrue('artist' not in self.i._dirty) @@ -68,7 +66,7 @@ class StoreTest(_common.LibTestCase): def test_store_only_writes_dirty_fields(self): original_genre = self.i.genre - self.i._values_fixed['genre'] = u'beatboxing' # change w/o dirtying + self.i._values_fixed['genre'] = 'beatboxing' # change w/o dirtying self.i.store() new_genre = self.lib._connection().execute( 'select genre from items where ' @@ -76,14 +74,14 @@ class StoreTest(_common.LibTestCase): self.assertEqual(new_genre, original_genre) def test_store_clears_dirty_flags(self): - self.i.composer = u'tvp' + self.i.composer = 'tvp' self.i.store() self.assertTrue('composer' not in self.i._dirty) class AddTest(_common.TestCase): def setUp(self): - super(AddTest, self).setUp() + super().setUp() self.lib = beets.library.Library(':memory:') self.i = item() @@ -114,7 +112,7 @@ class RemoveTest(_common.LibTestCase): class GetSetTest(_common.TestCase): def setUp(self): - super(GetSetTest, self).setUp() + super().setUp() self.i = item() def test_set_changes_value(self): @@ -130,32 +128,32 @@ class GetSetTest(_common.TestCase): self.assertTrue('title' not in self.i._dirty) def test_invalid_field_raises_attributeerror(self): - self.assertRaises(AttributeError, getattr, self.i, u'xyzzy') + self.assertRaises(AttributeError, getattr, self.i, 'xyzzy') def test_album_fallback(self): # integration test of item-album fallback lib = beets.library.Library(':memory:') i = item(lib) album = lib.add_album([i]) - album['flex'] = u'foo' + album['flex'] = 'foo' album.store() self.assertTrue('flex' in i) self.assertFalse('flex' in i.keys(with_album=False)) - self.assertEqual(i['flex'], u'foo') - self.assertEqual(i.get('flex'), u'foo') + self.assertEqual(i['flex'], 'foo') + self.assertEqual(i.get('flex'), 'foo') self.assertEqual(i.get('flex', with_album=False), None) self.assertEqual(i.get('flexx'), None) class DestinationTest(_common.TestCase): def setUp(self): - super(DestinationTest, self).setUp() + super().setUp() self.lib = beets.library.Library(':memory:') self.i = item(self.lib) def tearDown(self): - super(DestinationTest, self).tearDown() + super().tearDown() self.lib._connection().close() # Reset config if it was changed in test cases @@ -164,17 +162,17 @@ class DestinationTest(_common.TestCase): def test_directory_works_with_trailing_slash(self): self.lib.directory = b'one/' - self.lib.path_formats = [(u'default', u'two')] + self.lib.path_formats = [('default', 'two')] self.assertEqual(self.i.destination(), np('one/two')) def test_directory_works_without_trailing_slash(self): self.lib.directory = b'one' - self.lib.path_formats = [(u'default', u'two')] + self.lib.path_formats = [('default', 'two')] self.assertEqual(self.i.destination(), np('one/two')) def test_destination_substitutes_metadata_values(self): self.lib.directory = b'base' - self.lib.path_formats = [(u'default', u'$album/$artist $title')] + self.lib.path_formats = [('default', '$album/$artist $title')] self.i.title = 'three' self.i.artist = 'two' self.i.album = 'one' @@ -183,22 +181,22 @@ class DestinationTest(_common.TestCase): def test_destination_preserves_extension(self): self.lib.directory = b'base' - self.lib.path_formats = [(u'default', u'$title')] + self.lib.path_formats = [('default', '$title')] self.i.path = 'hey.audioformat' self.assertEqual(self.i.destination(), np('base/the title.audioformat')) def test_lower_case_extension(self): self.lib.directory = b'base' - self.lib.path_formats = [(u'default', u'$title')] + self.lib.path_formats = [('default', '$title')] self.i.path = 'hey.MP3' self.assertEqual(self.i.destination(), np('base/the title.mp3')) def test_destination_pads_some_indices(self): self.lib.directory = b'base' - self.lib.path_formats = [(u'default', - u'$track $tracktotal $disc $disctotal $bpm')] + self.lib.path_formats = [('default', + '$track $tracktotal $disc $disctotal $bpm')] self.i.track = 1 self.i.tracktotal = 2 self.i.disc = 3 @@ -209,7 +207,7 @@ class DestinationTest(_common.TestCase): def test_destination_pads_date_values(self): self.lib.directory = b'base' - self.lib.path_formats = [(u'default', u'$year-$month-$day')] + self.lib.path_formats = [('default', '$year-$month-$day')] self.i.year = 1 self.i.month = 2 self.i.day = 3 @@ -236,13 +234,13 @@ class DestinationTest(_common.TestCase): self.assertTrue(os.path.join(b'one', b'two') in dest) def test_destination_long_names_truncated(self): - self.i.title = u'X' * 300 - self.i.artist = u'Y' * 300 + self.i.title = 'X' * 300 + self.i.artist = 'Y' * 300 for c in self.i.destination().split(util.PATH_SEP): self.assertTrue(len(c) <= 255) def test_destination_long_names_keep_extension(self): - self.i.title = u'X' * 300 + self.i.title = 'X' * 300 self.i.path = b'something.extn' dest = self.i.destination() self.assertEqual(dest[-5:], b'.extn') @@ -257,7 +255,7 @@ class DestinationTest(_common.TestCase): self.assertFalse(b'two / three' in p) def test_path_with_format(self): - self.lib.path_formats = [(u'default', u'$artist/$album ($format)')] + self.lib.path_formats = [('default', '$artist/$album ($format)')] p = self.i.destination() self.assertTrue(b'(FLAC)' in p) @@ -265,7 +263,7 @@ class DestinationTest(_common.TestCase): i1, i2 = item(), item() self.lib.add_album([i1, i2]) i1.year, i2.year = 2009, 2010 - self.lib.path_formats = [(u'default', u'$album ($year)/$track $title')] + self.lib.path_formats = [('default', '$album ($year)/$track $title')] dest1, dest2 = i1.destination(), i2.destination() self.assertEqual(os.path.dirname(dest1), os.path.dirname(dest2)) @@ -273,17 +271,17 @@ class DestinationTest(_common.TestCase): self.i.comp = False self.lib.add_album([self.i]) self.lib.directory = b'one' - self.lib.path_formats = [(u'default', u'two'), - (u'comp:true', u'three')] + self.lib.path_formats = [('default', 'two'), + ('comp:true', 'three')] self.assertEqual(self.i.destination(), np('one/two')) def test_singleton_path(self): i = item(self.lib) self.lib.directory = b'one' self.lib.path_formats = [ - (u'default', u'two'), - (u'singleton:true', u'four'), - (u'comp:true', u'three'), + ('default', 'two'), + ('singleton:true', 'four'), + ('comp:true', 'three'), ] self.assertEqual(i.destination(), np('one/four')) @@ -292,9 +290,9 @@ class DestinationTest(_common.TestCase): i.comp = True self.lib.directory = b'one' self.lib.path_formats = [ - (u'default', u'two'), - (u'comp:true', u'three'), - (u'singleton:true', u'four'), + ('default', 'two'), + ('comp:true', 'three'), + ('singleton:true', 'four'), ] self.assertEqual(i.destination(), np('one/three')) @@ -303,32 +301,32 @@ class DestinationTest(_common.TestCase): self.lib.add_album([self.i]) self.lib.directory = b'one' self.lib.path_formats = [ - (u'default', u'two'), - (u'comp:true', u'three'), + ('default', 'two'), + ('comp:true', 'three'), ] self.assertEqual(self.i.destination(), np('one/three')) def test_albumtype_query_path(self): self.i.comp = True self.lib.add_album([self.i]) - self.i.albumtype = u'sometype' + self.i.albumtype = 'sometype' self.lib.directory = b'one' self.lib.path_formats = [ - (u'default', u'two'), - (u'albumtype:sometype', u'four'), - (u'comp:true', u'three'), + ('default', 'two'), + ('albumtype:sometype', 'four'), + ('comp:true', 'three'), ] self.assertEqual(self.i.destination(), np('one/four')) def test_albumtype_path_fallback_to_comp(self): self.i.comp = True self.lib.add_album([self.i]) - self.i.albumtype = u'sometype' + self.i.albumtype = 'sometype' self.lib.directory = b'one' self.lib.path_formats = [ - (u'default', u'two'), - (u'albumtype:anothertype', u'four'), - (u'comp:true', u'three'), + ('default', 'two'), + ('albumtype:anothertype', 'four'), + ('comp:true', 'three'), ] self.assertEqual(self.i.destination(), np('one/three')) @@ -349,13 +347,13 @@ class DestinationTest(_common.TestCase): with _common.platform_posix(): self.i.bitrate = 12345 val = self.i.formatted().get('bitrate') - self.assertEqual(val, u'12kbps') + self.assertEqual(val, '12kbps') def test_get_formatted_uses_khz_samplerate(self): with _common.platform_posix(): self.i.samplerate = 12345 val = self.i.formatted().get('samplerate') - self.assertEqual(val, u'12kHz') + self.assertEqual(val, '12kHz') def test_get_formatted_datetime(self): with _common.platform_posix(): @@ -367,45 +365,45 @@ class DestinationTest(_common.TestCase): with _common.platform_posix(): self.i.some_other_field = None val = self.i.formatted().get('some_other_field') - self.assertEqual(val, u'') + self.assertEqual(val, '') def test_artist_falls_back_to_albumartist(self): - self.i.artist = u'' - self.i.albumartist = u'something' - self.lib.path_formats = [(u'default', u'$artist')] + self.i.artist = '' + self.i.albumartist = 'something' + self.lib.path_formats = [('default', '$artist')] p = self.i.destination() self.assertEqual(p.rsplit(util.PATH_SEP, 1)[1], b'something') def test_albumartist_falls_back_to_artist(self): - self.i.artist = u'trackartist' - self.i.albumartist = u'' - self.lib.path_formats = [(u'default', u'$albumartist')] + self.i.artist = 'trackartist' + self.i.albumartist = '' + self.lib.path_formats = [('default', '$albumartist')] p = self.i.destination() self.assertEqual(p.rsplit(util.PATH_SEP, 1)[1], b'trackartist') def test_artist_overrides_albumartist(self): - self.i.artist = u'theartist' - self.i.albumartist = u'something' - self.lib.path_formats = [(u'default', u'$artist')] + self.i.artist = 'theartist' + self.i.albumartist = 'something' + self.lib.path_formats = [('default', '$artist')] p = self.i.destination() self.assertEqual(p.rsplit(util.PATH_SEP, 1)[1], b'theartist') def test_albumartist_overrides_artist(self): - self.i.artist = u'theartist' - self.i.albumartist = u'something' - self.lib.path_formats = [(u'default', u'$albumartist')] + self.i.artist = 'theartist' + self.i.albumartist = 'something' + self.lib.path_formats = [('default', '$albumartist')] p = self.i.destination() self.assertEqual(p.rsplit(util.PATH_SEP, 1)[1], b'something') def test_unicode_normalized_nfd_on_mac(self): - instr = unicodedata.normalize('NFC', u'caf\xe9') - self.lib.path_formats = [(u'default', instr)] + instr = unicodedata.normalize('NFC', 'caf\xe9') + self.lib.path_formats = [('default', instr)] dest = self.i.destination(platform='darwin', fragment=True) self.assertEqual(dest, unicodedata.normalize('NFD', instr)) def test_unicode_normalized_nfc_on_linux(self): - instr = unicodedata.normalize('NFD', u'caf\xe9') - self.lib.path_formats = [(u'default', instr)] + instr = unicodedata.normalize('NFD', 'caf\xe9') + self.lib.path_formats = [('default', instr)] dest = self.i.destination(platform='linux', fragment=True) self.assertEqual(dest, unicodedata.normalize('NFC', instr)) @@ -413,64 +411,64 @@ class DestinationTest(_common.TestCase): oldfunc = sys.getfilesystemencoding sys.getfilesystemencoding = lambda: 'mbcs' try: - self.i.title = u'h\u0259d' - self.lib.path_formats = [(u'default', u'$title')] + self.i.title = 'h\u0259d' + self.lib.path_formats = [('default', '$title')] p = self.i.destination() self.assertFalse(b'?' in p) # We use UTF-8 to encode Windows paths now. - self.assertTrue(u'h\u0259d'.encode('utf-8') in p) + self.assertTrue('h\u0259d'.encode() in p) finally: sys.getfilesystemencoding = oldfunc def test_unicode_extension_in_fragment(self): - self.lib.path_formats = [(u'default', u'foo')] - self.i.path = util.bytestring_path(u'bar.caf\xe9') + self.lib.path_formats = [('default', 'foo')] + self.i.path = util.bytestring_path('bar.caf\xe9') dest = self.i.destination(platform='linux', fragment=True) - self.assertEqual(dest, u'foo.caf\xe9') + self.assertEqual(dest, 'foo.caf\xe9') def test_asciify_and_replace(self): config['asciify_paths'] = True - self.lib.replacements = [(re.compile(u'"'), u'q')] + self.lib.replacements = [(re.compile('"'), 'q')] self.lib.directory = b'lib' - self.lib.path_formats = [(u'default', u'$title')] - self.i.title = u'\u201c\u00f6\u2014\u00cf\u201d' + self.lib.path_formats = [('default', '$title')] + self.i.title = '\u201c\u00f6\u2014\u00cf\u201d' self.assertEqual(self.i.destination(), np('lib/qo--Iq')) def test_asciify_character_expanding_to_slash(self): config['asciify_paths'] = True self.lib.directory = b'lib' - self.lib.path_formats = [(u'default', u'$title')] - self.i.title = u'ab\xa2\xbdd' + self.lib.path_formats = [('default', '$title')] + self.i.title = 'ab\xa2\xbdd' self.assertEqual(self.i.destination(), np('lib/abC_ 1_2 d')) def test_destination_with_replacements(self): self.lib.directory = b'base' - self.lib.replacements = [(re.compile(r'a'), u'e')] - self.lib.path_formats = [(u'default', u'$album/$title')] - self.i.title = u'foo' - self.i.album = u'bar' + self.lib.replacements = [(re.compile(r'a'), 'e')] + self.lib.path_formats = [('default', '$album/$title')] + self.i.title = 'foo' + self.i.album = 'bar' self.assertEqual(self.i.destination(), np('base/ber/foo')) @unittest.skip('unimplemented: #359') def test_destination_with_empty_component(self): self.lib.directory = b'base' - self.lib.replacements = [(re.compile(r'^$'), u'_')] - self.lib.path_formats = [(u'default', u'$album/$artist/$title')] - self.i.title = u'three' - self.i.artist = u'' - self.i.albumartist = u'' - self.i.album = u'one' + self.lib.replacements = [(re.compile(r'^$'), '_')] + self.lib.path_formats = [('default', '$album/$artist/$title')] + self.i.title = 'three' + self.i.artist = '' + self.i.albumartist = '' + self.i.album = 'one' self.assertEqual(self.i.destination(), np('base/one/_/three')) @unittest.skip('unimplemented: #359') def test_destination_with_empty_final_component(self): self.lib.directory = b'base' - self.lib.replacements = [(re.compile(r'^$'), u'_')] - self.lib.path_formats = [(u'default', u'$album/$title')] - self.i.title = u'' - self.i.album = u'one' + self.lib.replacements = [(re.compile(r'^$'), '_')] + self.lib.path_formats = [('default', '$album/$title')] + self.i.title = '' + self.i.album = 'one' self.i.path = 'foo.mp3' self.assertEqual(self.i.destination(), np('base/one/_.mp3')) @@ -479,12 +477,12 @@ class DestinationTest(_common.TestCase): # Use a replacement that should always replace the last X in any # path component with a Z. self.lib.replacements = [ - (re.compile(r'X$'), u'Z'), + (re.compile(r'X$'), 'Z'), ] # Construct an item whose untruncated path ends with a Y but whose # truncated version ends with an X. - self.i.title = u'X' * 300 + u'Y' + self.i.title = 'X' * 300 + 'Y' # The final path should reflect the replacement. dest = self.i.destination() @@ -494,12 +492,12 @@ class DestinationTest(_common.TestCase): # Use a replacement that should always replace the last X in any # path component with four Zs. self.lib.replacements = [ - (re.compile(r'X$'), u'ZZZZ'), + (re.compile(r'X$'), 'ZZZZ'), ] # Construct an item whose untruncated path ends with a Y but whose # truncated version ends with an X. - self.i.title = u'X' * 300 + u'Y' + self.i.title = 'X' * 300 + 'Y' # The final path should ignore the user replacement and create a path # of the correct length, containing Xs. @@ -508,19 +506,19 @@ class DestinationTest(_common.TestCase): def test_album_field_query(self): self.lib.directory = b'one' - self.lib.path_formats = [(u'default', u'two'), - (u'flex:foo', u'three')] + self.lib.path_formats = [('default', 'two'), + ('flex:foo', 'three')] album = self.lib.add_album([self.i]) self.assertEqual(self.i.destination(), np('one/two')) - album['flex'] = u'foo' + album['flex'] = 'foo' album.store() self.assertEqual(self.i.destination(), np('one/three')) def test_album_field_in_template(self): self.lib.directory = b'one' - self.lib.path_formats = [(u'default', u'$flex/two')] + self.lib.path_formats = [('default', '$flex/two')] album = self.lib.add_album([self.i]) - album['flex'] = u'foo' + album['flex'] = 'foo' album.store() self.assertEqual(self.i.destination(), np('one/foo/two')) @@ -528,7 +526,7 @@ class DestinationTest(_common.TestCase): class ItemFormattedMappingTest(_common.LibTestCase): def test_formatted_item_value(self): formatted = self.i.formatted() - self.assertEqual(formatted['artist'], u'the artist') + self.assertEqual(formatted['artist'], 'the artist') def test_get_unset_field(self): formatted = self.i.formatted() @@ -537,57 +535,57 @@ class ItemFormattedMappingTest(_common.LibTestCase): def test_get_method_with_default(self): formatted = self.i.formatted() - self.assertEqual(formatted.get('other_field'), u'') + self.assertEqual(formatted.get('other_field'), '') def test_get_method_with_specified_default(self): formatted = self.i.formatted() - self.assertEqual(formatted.get('other_field', u'default'), u'default') + self.assertEqual(formatted.get('other_field', 'default'), 'default') def test_item_precedence(self): album = self.lib.add_album([self.i]) - album['artist'] = u'foo' + album['artist'] = 'foo' album.store() - self.assertNotEqual(u'foo', self.i.formatted().get('artist')) + self.assertNotEqual('foo', self.i.formatted().get('artist')) def test_album_flex_field(self): album = self.lib.add_album([self.i]) - album['flex'] = u'foo' + album['flex'] = 'foo' album.store() - self.assertEqual(u'foo', self.i.formatted().get('flex')) + self.assertEqual('foo', self.i.formatted().get('flex')) def test_album_field_overrides_item_field_for_path(self): # Make the album inconsistent with the item. album = self.lib.add_album([self.i]) - album.album = u'foo' + album.album = 'foo' album.store() - self.i.album = u'bar' + self.i.album = 'bar' self.i.store() # Ensure the album takes precedence. formatted = self.i.formatted(for_path=True) - self.assertEqual(formatted['album'], u'foo') + self.assertEqual(formatted['album'], 'foo') def test_artist_falls_back_to_albumartist(self): - self.i.artist = u'' + self.i.artist = '' formatted = self.i.formatted() - self.assertEqual(formatted['artist'], u'the album artist') + self.assertEqual(formatted['artist'], 'the album artist') def test_albumartist_falls_back_to_artist(self): - self.i.albumartist = u'' + self.i.albumartist = '' formatted = self.i.formatted() - self.assertEqual(formatted['albumartist'], u'the artist') + self.assertEqual(formatted['albumartist'], 'the artist') def test_both_artist_and_albumartist_empty(self): - self.i.artist = u'' - self.i.albumartist = u'' + self.i.artist = '' + self.i.albumartist = '' formatted = self.i.formatted() - self.assertEqual(formatted['albumartist'], u'') + self.assertEqual(formatted['albumartist'], '') -class PathFormattingMixin(object): +class PathFormattingMixin: """Utilities for testing path formatting.""" def _setf(self, fmt): - self.lib.path_formats.insert(0, (u'default', fmt)) + self.lib.path_formats.insert(0, ('default', fmt)) def _assert_dest(self, dest, i=None): if i is None: @@ -599,119 +597,119 @@ class PathFormattingMixin(object): class DestinationFunctionTest(_common.TestCase, PathFormattingMixin): def setUp(self): - super(DestinationFunctionTest, self).setUp() + super().setUp() self.lib = beets.library.Library(':memory:') self.lib.directory = b'/base' - self.lib.path_formats = [(u'default', u'path')] + self.lib.path_formats = [('default', 'path')] self.i = item(self.lib) def tearDown(self): - super(DestinationFunctionTest, self).tearDown() + super().tearDown() self.lib._connection().close() def test_upper_case_literal(self): - self._setf(u'%upper{foo}') + self._setf('%upper{foo}') self._assert_dest(b'/base/FOO') def test_upper_case_variable(self): - self._setf(u'%upper{$title}') + self._setf('%upper{$title}') self._assert_dest(b'/base/THE TITLE') def test_title_case_variable(self): - self._setf(u'%title{$title}') + self._setf('%title{$title}') self._assert_dest(b'/base/The Title') def test_title_case_variable_aphostrophe(self): - self._setf(u'%title{I can\'t}') + self._setf('%title{I can\'t}') self._assert_dest(b'/base/I Can\'t') def test_asciify_variable(self): - self._setf(u'%asciify{ab\xa2\xbdd}') + self._setf('%asciify{ab\xa2\xbdd}') self._assert_dest(b'/base/abC_ 1_2 d') def test_left_variable(self): - self._setf(u'%left{$title, 3}') + self._setf('%left{$title, 3}') self._assert_dest(b'/base/the') def test_right_variable(self): - self._setf(u'%right{$title,3}') + self._setf('%right{$title,3}') self._assert_dest(b'/base/tle') def test_if_false(self): - self._setf(u'x%if{,foo}') + self._setf('x%if{,foo}') self._assert_dest(b'/base/x') def test_if_false_value(self): - self._setf(u'x%if{false,foo}') + self._setf('x%if{false,foo}') self._assert_dest(b'/base/x') def test_if_true(self): - self._setf(u'%if{bar,foo}') + self._setf('%if{bar,foo}') self._assert_dest(b'/base/foo') def test_if_else_false(self): - self._setf(u'%if{,foo,baz}') + self._setf('%if{,foo,baz}') self._assert_dest(b'/base/baz') def test_if_else_false_value(self): - self._setf(u'%if{false,foo,baz}') + self._setf('%if{false,foo,baz}') self._assert_dest(b'/base/baz') def test_if_int_value(self): - self._setf(u'%if{0,foo,baz}') + self._setf('%if{0,foo,baz}') self._assert_dest(b'/base/baz') def test_nonexistent_function(self): - self._setf(u'%foo{bar}') + self._setf('%foo{bar}') self._assert_dest(b'/base/%foo{bar}') def test_if_def_field_return_self(self): self.i.bar = 3 - self._setf(u'%ifdef{bar}') + self._setf('%ifdef{bar}') self._assert_dest(b'/base/3') def test_if_def_field_not_defined(self): - self._setf(u' %ifdef{bar}/$artist') + self._setf(' %ifdef{bar}/$artist') self._assert_dest(b'/base/the artist') def test_if_def_field_not_defined_2(self): - self._setf(u'$artist/%ifdef{bar}') + self._setf('$artist/%ifdef{bar}') self._assert_dest(b'/base/the artist') def test_if_def_true(self): - self._setf(u'%ifdef{artist,cool}') + self._setf('%ifdef{artist,cool}') self._assert_dest(b'/base/cool') def test_if_def_true_complete(self): self.i.series = "Now" - self._setf(u'%ifdef{series,$series Series,Albums}/$album') + self._setf('%ifdef{series,$series Series,Albums}/$album') self._assert_dest(b'/base/Now Series/the album') def test_if_def_false_complete(self): - self._setf(u'%ifdef{plays,$plays,not_played}') + self._setf('%ifdef{plays,$plays,not_played}') self._assert_dest(b'/base/not_played') def test_first(self): self.i.genres = "Pop; Rock; Classical Crossover" - self._setf(u'%first{$genres}') + self._setf('%first{$genres}') self._assert_dest(b'/base/Pop') def test_first_skip(self): self.i.genres = "Pop; Rock; Classical Crossover" - self._setf(u'%first{$genres,1,2}') + self._setf('%first{$genres,1,2}') self._assert_dest(b'/base/Classical Crossover') def test_first_different_sep(self): - self._setf(u'%first{Alice / Bob / Eve,2,0, / , & }') + self._setf('%first{Alice / Bob / Eve,2,0, / , & }') self._assert_dest(b'/base/Alice & Bob') class DisambiguationTest(_common.TestCase, PathFormattingMixin): def setUp(self): - super(DisambiguationTest, self).setUp() + super().setUp() self.lib = beets.library.Library(':memory:') self.lib.directory = b'/base' - self.lib.path_formats = [(u'default', u'path')] + self.lib.path_formats = [('default', 'path')] self.i1 = item() self.i1.year = 2001 @@ -721,10 +719,10 @@ class DisambiguationTest(_common.TestCase, PathFormattingMixin): self.lib.add_album([self.i2]) self.lib._connection().commit() - self._setf(u'foo%aunique{albumartist album,year}/$title') + self._setf('foo%aunique{albumartist album,year}/$title') def tearDown(self): - super(DisambiguationTest, self).tearDown() + super().tearDown() self.lib._connection().close() def test_unique_expands_to_disambiguating_year(self): @@ -732,14 +730,14 @@ class DisambiguationTest(_common.TestCase, PathFormattingMixin): def test_unique_with_default_arguments_uses_albumtype(self): album2 = self.lib.get_album(self.i1) - album2.albumtype = u'bar' + album2.albumtype = 'bar' album2.store() - self._setf(u'foo%aunique{}/$title') + self._setf('foo%aunique{}/$title') self._assert_dest(b'/base/foo [bar]/the title', self.i1) def test_unique_expands_to_nothing_for_distinct_albums(self): album2 = self.lib.get_album(self.i2) - album2.album = u'different album' + album2.album = 'different album' album2.store() self._assert_dest(b'/base/foo/the title', self.i1) @@ -753,41 +751,41 @@ class DisambiguationTest(_common.TestCase, PathFormattingMixin): self._assert_dest(b'/base/foo [2]/the title', self.i2) def test_unique_falls_back_to_second_distinguishing_field(self): - self._setf(u'foo%aunique{albumartist album,month year}/$title') + self._setf('foo%aunique{albumartist album,month year}/$title') self._assert_dest(b'/base/foo [2001]/the title', self.i1) def test_unique_sanitized(self): album2 = self.lib.get_album(self.i2) album2.year = 2001 album1 = self.lib.get_album(self.i1) - album1.albumtype = u'foo/bar' + album1.albumtype = 'foo/bar' album2.store() album1.store() - self._setf(u'foo%aunique{albumartist album,albumtype}/$title') + self._setf('foo%aunique{albumartist album,albumtype}/$title') self._assert_dest(b'/base/foo [foo_bar]/the title', self.i1) def test_drop_empty_disambig_string(self): album1 = self.lib.get_album(self.i1) album1.albumdisambig = None album2 = self.lib.get_album(self.i2) - album2.albumdisambig = u'foo' + album2.albumdisambig = 'foo' album1.store() album2.store() - self._setf(u'foo%aunique{albumartist album,albumdisambig}/$title') + self._setf('foo%aunique{albumartist album,albumdisambig}/$title') self._assert_dest(b'/base/foo/the title', self.i1) def test_change_brackets(self): - self._setf(u'foo%aunique{albumartist album,year,()}/$title') + self._setf('foo%aunique{albumartist album,year,()}/$title') self._assert_dest(b'/base/foo (2001)/the title', self.i1) def test_remove_brackets(self): - self._setf(u'foo%aunique{albumartist album,year,}/$title') + self._setf('foo%aunique{albumartist album,year,}/$title') self._assert_dest(b'/base/foo 2001/the title', self.i1) class PluginDestinationTest(_common.TestCase): def setUp(self): - super(PluginDestinationTest, self).setUp() + super().setUp() # Mock beets.plugins.item_field_getters. self._tv_map = {} @@ -803,11 +801,11 @@ class PluginDestinationTest(_common.TestCase): self.lib = beets.library.Library(':memory:') self.lib.directory = b'/base' - self.lib.path_formats = [(u'default', u'$artist $foo')] + self.lib.path_formats = [('default', '$artist $foo')] self.i = item(self.lib) def tearDown(self): - super(PluginDestinationTest, self).tearDown() + super().tearDown() plugins.item_field_getters = self.old_field_getters def _assert_dest(self, dest): @@ -839,7 +837,7 @@ class PluginDestinationTest(_common.TestCase): class AlbumInfoTest(_common.TestCase): def setUp(self): - super(AlbumInfoTest, self).setUp() + super().setUp() self.lib = beets.library.Library(':memory:') self.i = item() self.lib.add_album((self.i,)) @@ -871,7 +869,7 @@ class AlbumInfoTest(_common.TestCase): def test_individual_tracks_have_no_albuminfo(self): i2 = item() - i2.album = u'aTotallyDifferentAlbum' + i2.album = 'aTotallyDifferentAlbum' self.lib.add(i2) ai = self.lib.get_album(i2) self.assertEqual(ai, None) @@ -887,29 +885,29 @@ class AlbumInfoTest(_common.TestCase): if i.id == self.i.id: break else: - self.fail(u"item not found") + self.fail("item not found") def test_albuminfo_changes_affect_items(self): ai = self.lib.get_album(self.i) - ai.album = u'myNewAlbum' + ai.album = 'myNewAlbum' ai.store() i = self.lib.items()[0] - self.assertEqual(i.album, u'myNewAlbum') + self.assertEqual(i.album, 'myNewAlbum') def test_albuminfo_change_albumartist_changes_items(self): ai = self.lib.get_album(self.i) - ai.albumartist = u'myNewArtist' + ai.albumartist = 'myNewArtist' ai.store() i = self.lib.items()[0] - self.assertEqual(i.albumartist, u'myNewArtist') - self.assertNotEqual(i.artist, u'myNewArtist') + self.assertEqual(i.albumartist, 'myNewArtist') + self.assertNotEqual(i.artist, 'myNewArtist') def test_albuminfo_change_artist_does_not_change_items(self): ai = self.lib.get_album(self.i) - ai.artist = u'myNewArtist' + ai.artist = 'myNewArtist' ai.store() i = self.lib.items()[0] - self.assertNotEqual(i.artist, u'myNewArtist') + self.assertNotEqual(i.artist, 'myNewArtist') def test_albuminfo_remove_removes_items(self): item_id = self.i.id @@ -926,7 +924,7 @@ class AlbumInfoTest(_common.TestCase): def test_noop_albuminfo_changes_affect_items(self): i = self.lib.items()[0] - i.album = u'foobar' + i.album = 'foobar' i.store() ai = self.lib.get_album(self.i) ai.album = ai.album @@ -937,11 +935,11 @@ class AlbumInfoTest(_common.TestCase): class ArtDestinationTest(_common.TestCase): def setUp(self): - super(ArtDestinationTest, self).setUp() - config['art_filename'] = u'artimage' - config['replace'] = {u'X': u'Y'} + super().setUp() + config['art_filename'] = 'artimage' + config['replace'] = {'X': 'Y'} self.lib = beets.library.Library( - ':memory:', replacements=[(re.compile(u'X'), u'Y')] + ':memory:', replacements=[(re.compile('X'), 'Y')] ) self.i = item(self.lib) self.i.path = self.i.destination() @@ -958,14 +956,14 @@ class ArtDestinationTest(_common.TestCase): self.assertEqual(os.path.dirname(art), os.path.dirname(track)) def test_art_path_sanitized(self): - config['art_filename'] = u'artXimage' + config['art_filename'] = 'artXimage' art = self.ai.art_destination('something.jpg') self.assertTrue(b'artYimage' in art) class PathStringTest(_common.TestCase): def setUp(self): - super(PathStringTest, self).setUp() + super().setUp() self.lib = beets.library.Library(':memory:') self.i = item(self.lib) @@ -977,18 +975,18 @@ class PathStringTest(_common.TestCase): self.assertTrue(isinstance(i.path, bytes)) def test_unicode_path_becomes_bytestring(self): - self.i.path = u'unicodepath' + self.i.path = 'unicodepath' self.assertTrue(isinstance(self.i.path, bytes)) def test_unicode_in_database_becomes_bytestring(self): self.lib._connection().execute(""" update items set path=? where id=? - """, (self.i.id, u'somepath')) + """, (self.i.id, 'somepath')) i = list(self.lib.items())[0] self.assertTrue(isinstance(i.path, bytes)) def test_special_chars_preserved_in_database(self): - path = u'b\xe1r'.encode('utf-8') + path = 'b\xe1r'.encode() self.i.path = path self.i.store() i = list(self.lib.items())[0] @@ -996,7 +994,7 @@ class PathStringTest(_common.TestCase): def test_special_char_path_added_to_database(self): self.i.remove() - path = u'b\xe1r'.encode('utf-8') + path = 'b\xe1r'.encode() i = item() i.path = path self.lib.add(i) @@ -1004,14 +1002,14 @@ class PathStringTest(_common.TestCase): self.assertEqual(i.path, path) def test_destination_returns_bytestring(self): - self.i.artist = u'b\xe1r' + self.i.artist = 'b\xe1r' dest = self.i.destination() self.assertTrue(isinstance(dest, bytes)) def test_art_destination_returns_bytestring(self): - self.i.artist = u'b\xe1r' + self.i.artist = 'b\xe1r' alb = self.lib.add_album([self.i]) - dest = alb.art_destination(u'image.jpg') + dest = alb.art_destination('image.jpg') self.assertTrue(isinstance(dest, bytes)) def test_artpath_stores_special_chars(self): @@ -1023,25 +1021,25 @@ class PathStringTest(_common.TestCase): self.assertEqual(path, alb.artpath) def test_sanitize_path_with_special_chars(self): - path = u'b\xe1r?' + path = 'b\xe1r?' new_path = util.sanitize_path(path) - self.assertTrue(new_path.startswith(u'b\xe1r')) + self.assertTrue(new_path.startswith('b\xe1r')) def test_sanitize_path_returns_unicode(self): - path = u'b\xe1r?' + path = 'b\xe1r?' new_path = util.sanitize_path(path) - self.assertTrue(isinstance(new_path, six.text_type)) + self.assertTrue(isinstance(new_path, str)) def test_unicode_artpath_becomes_bytestring(self): alb = self.lib.add_album([self.i]) - alb.artpath = u'somep\xe1th' + alb.artpath = 'somep\xe1th' self.assertTrue(isinstance(alb.artpath, bytes)) def test_unicode_artpath_in_database_decoded(self): alb = self.lib.add_album([self.i]) self.lib._connection().execute( "update albums set artpath=? where id=?", - (u'somep\xe1th', alb.id) + ('somep\xe1th', alb.id) ) alb = self.lib.get_album(alb.id) self.assertTrue(isinstance(alb.artpath, bytes)) @@ -1049,7 +1047,7 @@ class PathStringTest(_common.TestCase): class MtimeTest(_common.TestCase): def setUp(self): - super(MtimeTest, self).setUp() + super().setUp() self.ipath = os.path.join(self.temp_dir, b'testfile.mp3') shutil.copy(os.path.join(_common.RSRC, b'full.mp3'), self.ipath) self.i = beets.library.Item.from_path(self.ipath) @@ -1057,7 +1055,7 @@ class MtimeTest(_common.TestCase): self.lib.add(self.i) def tearDown(self): - super(MtimeTest, self).tearDown() + super().tearDown() if os.path.exists(self.ipath): os.remove(self.ipath) @@ -1068,23 +1066,23 @@ class MtimeTest(_common.TestCase): self.assertGreaterEqual(self.i.mtime, self._mtime()) def test_mtime_reset_on_db_modify(self): - self.i.title = u'something else' + self.i.title = 'something else' self.assertLess(self.i.mtime, self._mtime()) def test_mtime_up_to_date_after_write(self): - self.i.title = u'something else' + self.i.title = 'something else' self.i.write() self.assertGreaterEqual(self.i.mtime, self._mtime()) def test_mtime_up_to_date_after_read(self): - self.i.title = u'something else' + self.i.title = 'something else' self.i.read() self.assertGreaterEqual(self.i.mtime, self._mtime()) class ImportTimeTest(_common.TestCase): def setUp(self): - super(ImportTimeTest, self).setUp() + super().setUp() self.lib = beets.library.Library(':memory:') def added(self): @@ -1102,36 +1100,36 @@ class TemplateTest(_common.LibTestCase): def test_year_formatted_in_template(self): self.i.year = 123 self.i.store() - self.assertEqual(self.i.evaluate_template('$year'), u'0123') + self.assertEqual(self.i.evaluate_template('$year'), '0123') def test_album_flexattr_appears_in_item_template(self): self.album = self.lib.add_album([self.i]) - self.album.foo = u'baz' + self.album.foo = 'baz' self.album.store() - self.assertEqual(self.i.evaluate_template('$foo'), u'baz') + self.assertEqual(self.i.evaluate_template('$foo'), 'baz') def test_album_and_item_format(self): - config['format_album'] = u'foö $foo' + config['format_album'] = 'foö $foo' album = beets.library.Album() - album.foo = u'bar' - album.tagada = u'togodo' - self.assertEqual(u"{0}".format(album), u"foö bar") - self.assertEqual(u"{0:$tagada}".format(album), u"togodo") - self.assertEqual(six.text_type(album), u"foö bar") + album.foo = 'bar' + album.tagada = 'togodo' + self.assertEqual(f"{album}", "foö bar") + self.assertEqual(f"{album:$tagada}", "togodo") + self.assertEqual(str(album), "foö bar") self.assertEqual(bytes(album), b"fo\xc3\xb6 bar") - config['format_item'] = u'bar $foo' + config['format_item'] = 'bar $foo' item = beets.library.Item() - item.foo = u'bar' - item.tagada = u'togodo' - self.assertEqual(u"{0}".format(item), u"bar bar") - self.assertEqual(u"{0:$tagada}".format(item), u"togodo") + item.foo = 'bar' + item.tagada = 'togodo' + self.assertEqual(f"{item}", "bar bar") + self.assertEqual(f"{item:$tagada}", "togodo") class UnicodePathTest(_common.LibTestCase): def test_unicode_path(self): self.i.path = os.path.join(_common.RSRC, - u'unicode\u2019d.mp3'.encode('utf-8')) + 'unicode\u2019d.mp3'.encode()) # If there are any problems with unicode paths, we will raise # here and fail. self.i.read() @@ -1188,7 +1186,7 @@ class WriteTest(unittest.TestCase, TestHelper): # Since `date` is not a MediaField, this should do nothing. item = self.add_item_fixture() clean_year = item.year - item.date = u'foo' + item.date = 'foo' item.write() self.assertEqual(MediaFile(syspath(item.path)).year, clean_year) @@ -1228,7 +1226,7 @@ class FilesizeTest(unittest.TestCase, TestHelper): class ParseQueryTest(unittest.TestCase): def test_parse_invalid_query_string(self): with self.assertRaises(beets.dbcore.InvalidQueryError) as raised: - beets.library.parse_query_string(u'foo"', None) + beets.library.parse_query_string('foo"', None) self.assertIsInstance(raised.exception, beets.dbcore.query.ParsingError) @@ -1249,42 +1247,42 @@ class LibraryFieldTypesTest(unittest.TestCase): self.assertEqual(time_local, t.format(123456789)) # parse self.assertEqual(123456789.0, t.parse(time_local)) - self.assertEqual(123456789.0, t.parse(u'123456789.0')) - self.assertEqual(t.null, t.parse(u'not123456789.0')) - self.assertEqual(t.null, t.parse(u'1973-11-29')) + self.assertEqual(123456789.0, t.parse('123456789.0')) + self.assertEqual(t.null, t.parse('not123456789.0')) + self.assertEqual(t.null, t.parse('1973-11-29')) def test_pathtype(self): t = beets.library.PathType() # format self.assertEqual('/tmp', t.format('/tmp')) - self.assertEqual(u'/tmp/\xe4lbum', t.format(u'/tmp/\u00e4lbum')) + self.assertEqual('/tmp/\xe4lbum', t.format('/tmp/\u00e4lbum')) # parse self.assertEqual(np(b'/tmp'), t.parse('/tmp')) self.assertEqual(np(b'/tmp/\xc3\xa4lbum'), - t.parse(u'/tmp/\u00e4lbum/')) + t.parse('/tmp/\u00e4lbum/')) def test_musicalkey(self): t = beets.library.MusicalKey() # parse - self.assertEqual(u'C#m', t.parse(u'c#m')) - self.assertEqual(u'Gm', t.parse(u'g minor')) - self.assertEqual(u'Not c#m', t.parse(u'not C#m')) + self.assertEqual('C#m', t.parse('c#m')) + self.assertEqual('Gm', t.parse('g minor')) + self.assertEqual('Not c#m', t.parse('not C#m')) def test_durationtype(self): t = beets.library.DurationType() # format - self.assertEqual(u'1:01', t.format(61.23)) - self.assertEqual(u'60:01', t.format(3601.23)) - self.assertEqual(u'0:00', t.format(None)) + self.assertEqual('1:01', t.format(61.23)) + self.assertEqual('60:01', t.format(3601.23)) + self.assertEqual('0:00', t.format(None)) # parse - self.assertEqual(61.0, t.parse(u'1:01')) - self.assertEqual(61.23, t.parse(u'61.23')) - self.assertEqual(3601.0, t.parse(u'60:01')) - self.assertEqual(t.null, t.parse(u'1:00:01')) - self.assertEqual(t.null, t.parse(u'not61.23')) + self.assertEqual(61.0, t.parse('1:01')) + self.assertEqual(61.23, t.parse('61.23')) + self.assertEqual(3601.0, t.parse('60:01')) + self.assertEqual(t.null, t.parse('1:00:01')) + self.assertEqual(t.null, t.parse('not61.23')) # config format_raw_length beets.config['format_raw_length'] = True self.assertEqual(61.23, t.format(61.23)) diff --git a/test/test_logging.py b/test/test_logging.py index 826b2447b..a5f4ffc84 100644 --- a/test/test_logging.py +++ b/test/test_logging.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- - """Stupid tests that ensure logging works as expected""" -from __future__ import division, absolute_import, print_function import sys import threading @@ -46,13 +43,13 @@ class LoggingTest(TestCase): l.addHandler(handler) l.propagate = False - l.warning(u"foo {0} {bar}", "oof", bar=u"baz") + l.warning("foo {0} {bar}", "oof", bar="baz") handler.flush() - self.assertTrue(stream.getvalue(), u"foo oof baz") + self.assertTrue(stream.getvalue(), "foo oof baz") class LoggingLevelTest(unittest.TestCase, helper.TestHelper): - class DummyModule(object): + class DummyModule: class DummyPlugin(plugins.BeetsPlugin): def __init__(self): plugins.BeetsPlugin.__init__(self, 'dummy') @@ -60,9 +57,9 @@ class LoggingLevelTest(unittest.TestCase, helper.TestHelper): self.register_listener('dummy_event', self.listener) def log_all(self, name): - self._log.debug(u'debug ' + name) - self._log.info(u'info ' + name) - self._log.warning(u'warning ' + name) + self._log.debug('debug ' + name) + self._log.info('info ' + name) + self._log.warning('warning ' + name) def commands(self): cmd = ui.Subcommand('dummy') @@ -93,76 +90,76 @@ class LoggingLevelTest(unittest.TestCase, helper.TestHelper): self.config['verbose'] = 0 with helper.capture_log() as logs: self.run_command('dummy') - self.assertIn(u'dummy: warning cmd', logs) - self.assertIn(u'dummy: info cmd', logs) - self.assertNotIn(u'dummy: debug cmd', logs) + self.assertIn('dummy: warning cmd', logs) + self.assertIn('dummy: info cmd', logs) + self.assertNotIn('dummy: debug cmd', logs) def test_command_level1(self): self.config['verbose'] = 1 with helper.capture_log() as logs: self.run_command('dummy') - self.assertIn(u'dummy: warning cmd', logs) - self.assertIn(u'dummy: info cmd', logs) - self.assertIn(u'dummy: debug cmd', logs) + self.assertIn('dummy: warning cmd', logs) + self.assertIn('dummy: info cmd', logs) + self.assertIn('dummy: debug cmd', logs) def test_command_level2(self): self.config['verbose'] = 2 with helper.capture_log() as logs: self.run_command('dummy') - self.assertIn(u'dummy: warning cmd', logs) - self.assertIn(u'dummy: info cmd', logs) - self.assertIn(u'dummy: debug cmd', logs) + self.assertIn('dummy: warning cmd', logs) + self.assertIn('dummy: info cmd', logs) + self.assertIn('dummy: debug cmd', logs) def test_listener_level0(self): self.config['verbose'] = 0 with helper.capture_log() as logs: plugins.send('dummy_event') - self.assertIn(u'dummy: warning listener', logs) - self.assertNotIn(u'dummy: info listener', logs) - self.assertNotIn(u'dummy: debug listener', logs) + self.assertIn('dummy: warning listener', logs) + self.assertNotIn('dummy: info listener', logs) + self.assertNotIn('dummy: debug listener', logs) def test_listener_level1(self): self.config['verbose'] = 1 with helper.capture_log() as logs: plugins.send('dummy_event') - self.assertIn(u'dummy: warning listener', logs) - self.assertIn(u'dummy: info listener', logs) - self.assertNotIn(u'dummy: debug listener', logs) + self.assertIn('dummy: warning listener', logs) + self.assertIn('dummy: info listener', logs) + self.assertNotIn('dummy: debug listener', logs) def test_listener_level2(self): self.config['verbose'] = 2 with helper.capture_log() as logs: plugins.send('dummy_event') - self.assertIn(u'dummy: warning listener', logs) - self.assertIn(u'dummy: info listener', logs) - self.assertIn(u'dummy: debug listener', logs) + self.assertIn('dummy: warning listener', logs) + self.assertIn('dummy: info listener', logs) + self.assertIn('dummy: debug listener', logs) def test_import_stage_level0(self): self.config['verbose'] = 0 with helper.capture_log() as logs: importer = self.create_importer() importer.run() - self.assertIn(u'dummy: warning import_stage', logs) - self.assertNotIn(u'dummy: info import_stage', logs) - self.assertNotIn(u'dummy: debug import_stage', logs) + self.assertIn('dummy: warning import_stage', logs) + self.assertNotIn('dummy: info import_stage', logs) + self.assertNotIn('dummy: debug import_stage', logs) def test_import_stage_level1(self): self.config['verbose'] = 1 with helper.capture_log() as logs: importer = self.create_importer() importer.run() - self.assertIn(u'dummy: warning import_stage', logs) - self.assertIn(u'dummy: info import_stage', logs) - self.assertNotIn(u'dummy: debug import_stage', logs) + self.assertIn('dummy: warning import_stage', logs) + self.assertIn('dummy: info import_stage', logs) + self.assertNotIn('dummy: debug import_stage', logs) def test_import_stage_level2(self): self.config['verbose'] = 2 with helper.capture_log() as logs: importer = self.create_importer() importer.run() - self.assertIn(u'dummy: warning import_stage', logs) - self.assertIn(u'dummy: info import_stage', logs) - self.assertIn(u'dummy: debug import_stage', logs) + self.assertIn('dummy: warning import_stage', logs) + self.assertIn('dummy: info import_stage', logs) + self.assertIn('dummy: debug import_stage', logs) @_common.slow_test() @@ -183,9 +180,9 @@ class ConcurrentEventsTest(TestCase, helper.TestHelper): self.t1_step = self.t2_step = 0 def log_all(self, name): - self._log.debug(u'debug ' + name) - self._log.info(u'info ' + name) - self._log.warning(u'warning ' + name) + self._log.debug('debug ' + name) + self._log.info('info ' + name) + self._log.warning('warning ' + name) def listener1(self): try: @@ -220,7 +217,7 @@ class ConcurrentEventsTest(TestCase, helper.TestHelper): def check_dp_exc(): if dp.exc_info: - six.reraise(dp.exc_info[1], None, dp.exc_info[2]) + raise None.with_traceback(dp.exc_info[2]) try: dp.lock1.acquire() @@ -258,14 +255,14 @@ class ConcurrentEventsTest(TestCase, helper.TestHelper): self.assertFalse(t2.is_alive()) except Exception: - print(u"Alive threads:", threading.enumerate()) + print("Alive threads:", threading.enumerate()) if dp.lock1.locked(): - print(u"Releasing lock1 after exception in test") + print("Releasing lock1 after exception in test") dp.lock1.release() if dp.lock2.locked(): - print(u"Releasing lock2 after exception in test") + print("Releasing lock2 after exception in test") dp.lock2.release() - print(u"Alive threads:", threading.enumerate()) + print("Alive threads:", threading.enumerate()) raise def test_root_logger_levels(self): @@ -284,14 +281,14 @@ class ConcurrentEventsTest(TestCase, helper.TestHelper): importer = self.create_importer() importer.run() for l in logs: - self.assertIn(u"import", l) - self.assertIn(u"album", l) + self.assertIn("import", l) + self.assertIn("album", l) blog.getLogger('beets').set_global_level(blog.DEBUG) with helper.capture_log() as logs: importer = self.create_importer() importer.run() - self.assertIn(u"Sending event: database_change", logs) + self.assertIn("Sending event: database_change", logs) def suite(): diff --git a/test/test_lyrics.py b/test/test_lyrics.py index 652b7a567..0f09af82e 100644 --- a/test/test_lyrics.py +++ b/test/test_lyrics.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Fabrice Laporte. # @@ -15,10 +14,8 @@ """Tests for the 'lyrics' plugin.""" -from __future__ import absolute_import, division, print_function import itertools -from io import open import os import re import six @@ -26,7 +23,7 @@ import sys import unittest import confuse -from mock import MagicMock, patch +from unittest.mock import MagicMock, patch from beets import logging from beets.library import Item @@ -48,72 +45,72 @@ class LyricsPluginTest(unittest.TestCase): lyrics.LyricsPlugin() def test_search_artist(self): - item = Item(artist=u'Alice ft. Bob', title=u'song') - self.assertIn((u'Alice ft. Bob', [u'song']), + item = Item(artist='Alice ft. Bob', title='song') + self.assertIn(('Alice ft. Bob', ['song']), lyrics.search_pairs(item)) - self.assertIn((u'Alice', [u'song']), + self.assertIn(('Alice', ['song']), lyrics.search_pairs(item)) - item = Item(artist=u'Alice feat Bob', title=u'song') - self.assertIn((u'Alice feat Bob', [u'song']), + item = Item(artist='Alice feat Bob', title='song') + self.assertIn(('Alice feat Bob', ['song']), lyrics.search_pairs(item)) - self.assertIn((u'Alice', [u'song']), + self.assertIn(('Alice', ['song']), lyrics.search_pairs(item)) - item = Item(artist=u'Alice feat. Bob', title=u'song') - self.assertIn((u'Alice feat. Bob', [u'song']), + item = Item(artist='Alice feat. Bob', title='song') + self.assertIn(('Alice feat. Bob', ['song']), lyrics.search_pairs(item)) - self.assertIn((u'Alice', [u'song']), + self.assertIn(('Alice', ['song']), lyrics.search_pairs(item)) - item = Item(artist=u'Alice feats Bob', title=u'song') - self.assertIn((u'Alice feats Bob', [u'song']), + item = Item(artist='Alice feats Bob', title='song') + self.assertIn(('Alice feats Bob', ['song']), lyrics.search_pairs(item)) - self.assertNotIn((u'Alice', [u'song']), + self.assertNotIn(('Alice', ['song']), lyrics.search_pairs(item)) - item = Item(artist=u'Alice featuring Bob', title=u'song') - self.assertIn((u'Alice featuring Bob', [u'song']), + item = Item(artist='Alice featuring Bob', title='song') + self.assertIn(('Alice featuring Bob', ['song']), lyrics.search_pairs(item)) - self.assertIn((u'Alice', [u'song']), + self.assertIn(('Alice', ['song']), lyrics.search_pairs(item)) - item = Item(artist=u'Alice & Bob', title=u'song') - self.assertIn((u'Alice & Bob', [u'song']), + item = Item(artist='Alice & Bob', title='song') + self.assertIn(('Alice & Bob', ['song']), lyrics.search_pairs(item)) - self.assertIn((u'Alice', [u'song']), + self.assertIn(('Alice', ['song']), lyrics.search_pairs(item)) - item = Item(artist=u'Alice and Bob', title=u'song') - self.assertIn((u'Alice and Bob', [u'song']), + item = Item(artist='Alice and Bob', title='song') + self.assertIn(('Alice and Bob', ['song']), lyrics.search_pairs(item)) - self.assertIn((u'Alice', [u'song']), + self.assertIn(('Alice', ['song']), lyrics.search_pairs(item)) - item = Item(artist=u'Alice and Bob', title=u'song') - self.assertEqual((u'Alice and Bob', [u'song']), + item = Item(artist='Alice and Bob', title='song') + self.assertEqual(('Alice and Bob', ['song']), list(lyrics.search_pairs(item))[0]) def test_search_artist_sort(self): - item = Item(artist=u'CHVRCHΞS', title=u'song', artist_sort=u'CHVRCHES') - self.assertIn((u'CHVRCHΞS', [u'song']), + item = Item(artist='CHVRCHΞS', title='song', artist_sort='CHVRCHES') + self.assertIn(('CHVRCHΞS', ['song']), lyrics.search_pairs(item)) - self.assertIn((u'CHVRCHES', [u'song']), + self.assertIn(('CHVRCHES', ['song']), lyrics.search_pairs(item)) # Make sure that the original artist name is still the first entry - self.assertEqual((u'CHVRCHΞS', [u'song']), + self.assertEqual(('CHVRCHΞS', ['song']), list(lyrics.search_pairs(item))[0]) - item = Item(artist=u'横山克', title=u'song', - artist_sort=u'Masaru Yokoyama') - self.assertIn((u'横山克', [u'song']), + item = Item(artist='横山克', title='song', + artist_sort='Masaru Yokoyama') + self.assertIn(('横山克', ['song']), lyrics.search_pairs(item)) - self.assertIn((u'Masaru Yokoyama', [u'song']), + self.assertIn(('Masaru Yokoyama', ['song']), lyrics.search_pairs(item)) # Make sure that the original artist name is still the first entry - self.assertEqual((u'横山克', [u'song']), + self.assertEqual(('横山克', ['song']), list(lyrics.search_pairs(item))[0]) def test_search_pairs_multi_titles(self): @@ -179,12 +176,12 @@ class LyricsPluginTest(unittest.TestCase): self.assertFalse(google.is_lyrics(t)) def test_slugify(self): - text = u"http://site.com/\xe7afe-au_lait(boisson)" + text = "http://site.com/\xe7afe-au_lait(boisson)" self.assertEqual(google.slugify(text), 'http://site.com/cafe_au_lait') def test_scrape_strip_cruft(self): - text = u""" + text = """  one
two ! @@ -194,17 +191,17 @@ class LyricsPluginTest(unittest.TestCase): "one\ntwo !\n\nfour") def test_scrape_strip_scripts(self): - text = u"""foobaz""" + text = """foobaz""" self.assertEqual(lyrics._scrape_strip_cruft(text, True), "foobaz") def test_scrape_strip_tag_in_comment(self): - text = u"""fooqux""" + text = """fooqux""" self.assertEqual(lyrics._scrape_strip_cruft(text, True), "fooqux") def test_scrape_merge_paragraphs(self): - text = u"one

two

three" + text = "one

two

three" self.assertEqual(lyrics._scrape_merge_paragraphs(text), "one\ntwo\nthree") @@ -222,7 +219,7 @@ def url_to_filename(url): return fn -class MockFetchUrl(object): +class MockFetchUrl: def __init__(self, pathval='fetched_path'): self.pathval = pathval @@ -231,7 +228,7 @@ class MockFetchUrl(object): def __call__(self, url, filename=None): self.fetched = url fn = url_to_filename(url) - with open(fn, 'r', encoding="utf8") as f: + with open(fn, encoding="utf8") as f: content = f.read() return content @@ -241,7 +238,7 @@ def is_lyrics_content_ok(title, text): if not text: return keywords = set(LYRICS_TEXTS[google.slugify(title)].split()) - words = set(x.strip(".?, ") for x in text.lower().split()) + words = {x.strip(".?, ") for x in text.lower().split()} return keywords <= words LYRICS_ROOT_DIR = os.path.join(_common.RSRC, b'lyrics') @@ -266,7 +263,7 @@ class LyricsPluginSourcesTest(LyricsGoogleBaseTest): scraped. """ - DEFAULT_SONG = dict(artist=u'The Beatles', title=u'Lady Madonna') + DEFAULT_SONG = dict(artist='The Beatles', title='Lady Madonna') DEFAULT_SOURCES = [ # dict(artist=u'Santana', title=u'Black magic woman', @@ -274,53 +271,53 @@ class LyricsPluginSourcesTest(LyricsGoogleBaseTest): dict(DEFAULT_SONG, backend=lyrics.Genius, # GitHub actions is on some form of Cloudflare blacklist. skip=os.environ.get('GITHUB_ACTIONS') == 'true'), - dict(artist=u'Boy In Space', title=u'u n eye', + dict(artist='Boy In Space', title='u n eye', backend=lyrics.Tekstowo), ] GOOGLE_SOURCES = [ dict(DEFAULT_SONG, - url=u'http://www.absolutelyrics.com', - path=u'/lyrics/view/the_beatles/lady_madonna'), + url='http://www.absolutelyrics.com', + path='/lyrics/view/the_beatles/lady_madonna'), dict(DEFAULT_SONG, - url=u'http://www.azlyrics.com', - path=u'/lyrics/beatles/ladymadonna.html', + url='http://www.azlyrics.com', + path='/lyrics/beatles/ladymadonna.html', # AZLyrics returns a 403 on GitHub actions. skip=os.environ.get('GITHUB_ACTIONS') == 'true'), dict(DEFAULT_SONG, - url=u'http://www.chartlyrics.com', - path=u'/_LsLsZ7P4EK-F-LD4dJgDQ/Lady+Madonna.aspx'), + url='http://www.chartlyrics.com', + path='/_LsLsZ7P4EK-F-LD4dJgDQ/Lady+Madonna.aspx'), # dict(DEFAULT_SONG, # url=u'http://www.elyricsworld.com', # path=u'/lady_madonna_lyrics_beatles.html'), - dict(url=u'http://www.lacoccinelle.net', - artist=u'Jacques Brel', title=u"Amsterdam", - path=u'/paroles-officielles/275679.html'), + dict(url='http://www.lacoccinelle.net', + artist='Jacques Brel', title="Amsterdam", + path='/paroles-officielles/275679.html'), dict(DEFAULT_SONG, - url=u'http://letras.mus.br/', path=u'the-beatles/275/'), + url='http://letras.mus.br/', path='the-beatles/275/'), dict(DEFAULT_SONG, url='http://www.lyricsmania.com/', path='lady_madonna_lyrics_the_beatles.html'), dict(DEFAULT_SONG, - url=u'http://www.lyricsmode.com', - path=u'/lyrics/b/beatles/lady_madonna.html'), - dict(url=u'http://www.lyricsontop.com', - artist=u'Amy Winehouse', title=u"Jazz'n'blues", - path=u'/amy-winehouse-songs/jazz-n-blues-lyrics.html'), + url='http://www.lyricsmode.com', + path='/lyrics/b/beatles/lady_madonna.html'), + dict(url='http://www.lyricsontop.com', + artist='Amy Winehouse', title="Jazz'n'blues", + path='/amy-winehouse-songs/jazz-n-blues-lyrics.html'), # dict(DEFAULT_SONG, # url='http://www.metrolyrics.com/', # path='lady-madonna-lyrics-beatles.html'), # dict(url='http://www.musica.com/', path='letras.asp?letra=2738', # artist=u'Santana', title=u'Black magic woman'), - dict(url=u'http://www.paroles.net/', - artist=u'Lilly Wood & the prick', title=u"Hey it's ok", - path=u'lilly-wood-the-prick/paroles-hey-it-s-ok'), + dict(url='http://www.paroles.net/', + artist='Lilly Wood & the prick', title="Hey it's ok", + path='lilly-wood-the-prick/paroles-hey-it-s-ok'), dict(DEFAULT_SONG, url='http://www.songlyrics.com', - path=u'/the-beatles/lady-madonna-lyrics'), + path='/the-beatles/lady-madonna-lyrics'), dict(DEFAULT_SONG, - url=u'http://www.sweetslyrics.com', - path=u'/761696.The%20Beatles%20-%20Lady%20Madonna.html') + url='http://www.sweetslyrics.com', + path='/761696.The%20Beatles%20-%20Lady%20Madonna.html') ] def setUp(self): @@ -364,8 +361,8 @@ class LyricsGooglePluginMachineryTest(LyricsGoogleBaseTest): """Test scraping heuristics on a fake html page. """ - source = dict(url=u'http://www.example.com', artist=u'John Doe', - title=u'Beets song', path=u'/lyrics/beetssong') + source = dict(url='http://www.example.com', artist='John Doe', + title='Beets song', path='/lyrics/beetssong') def setUp(self): """Set up configuration""" @@ -388,7 +385,7 @@ class LyricsGooglePluginMachineryTest(LyricsGoogleBaseTest): """ from bs4 import SoupStrainer, BeautifulSoup s = self.source - url = six.text_type(s['url'] + s['path']) + url = str(s['url'] + s['path']) html = raw_backend.fetch_url(url) soup = BeautifulSoup(html, "html.parser", parse_only=SoupStrainer('title')) @@ -402,13 +399,13 @@ class LyricsGooglePluginMachineryTest(LyricsGoogleBaseTest): """ s = self.source url = s['url'] + s['path'] - url_title = u'example.com | Beats song by John doe' + url_title = '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, url_title, s['title'], s['artist']), True, url) # reject different title - url_title = u'example.com | seets bong lyrics by John doe' + url_title = 'example.com | seets bong lyrics by John doe' self.assertEqual(google.is_page_candidate(url, url_title, s['title'], s['artist']), False, url) @@ -419,9 +416,9 @@ class LyricsGooglePluginMachineryTest(LyricsGoogleBaseTest): # https://github.com/beetbox/beets/issues/1673 s = self.source url = s['url'] + s['path'] - url_title = u'foo' + url_title = 'foo' - google.is_page_candidate(url, url_title, s['title'], u'Sunn O)))') + google.is_page_candidate(url, url_title, s['title'], 'Sunn O)))') # test Genius backend @@ -483,7 +480,7 @@ class GeniusFetchTest(GeniusBaseTest): { "result": { "primary_artist": { - "name": u"\u200Bblackbear", + "name": "\u200Bblackbear", }, "url": "blackbear_url" } @@ -491,7 +488,7 @@ class GeniusFetchTest(GeniusBaseTest): { "result": { "primary_artist": { - "name": u"El\u002Dp" + "name": "El\u002Dp" }, "url": "El-p_url" } @@ -527,28 +524,28 @@ class SlugTests(unittest.TestCase): def test_slug(self): # plain ascii passthrough - text = u"test" + text = "test" self.assertEqual(lyrics.slug(text), 'test') # german unicode and capitals - text = u"Mørdag" + text = "Mørdag" self.assertEqual(lyrics.slug(text), 'mordag') # more accents and quotes - text = u"l'été c'est fait pour jouer" + text = "l'été c'est fait pour jouer" self.assertEqual(lyrics.slug(text), 'l-ete-c-est-fait-pour-jouer') # accents, parens and spaces - text = u"\xe7afe au lait (boisson)" + text = "\xe7afe au lait (boisson)" self.assertEqual(lyrics.slug(text), 'cafe-au-lait-boisson') - text = u"Multiple spaces -- and symbols! -- merged" + text = "Multiple spaces -- and symbols! -- merged" self.assertEqual(lyrics.slug(text), 'multiple-spaces-and-symbols-merged') - text = u"\u200Bno-width-space" + text = "\u200Bno-width-space" self.assertEqual(lyrics.slug(text), 'no-width-space') # variations of dashes should get standardized - dashes = [u'\u200D', u'\u2010'] + dashes = ['\u200D', '\u2010'] for dash1, dash2 in itertools.combinations(dashes, 2): self.assertEqual(lyrics.slug(dash1), lyrics.slug(dash2)) diff --git a/test/test_mb.py b/test/test_mb.py index 9eca57c80..0b39d6ce4 100644 --- a/test/test_mb.py +++ b/test/test_mb.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -15,14 +14,13 @@ """Tests for MusicBrainz API wrapper. """ -from __future__ import division, absolute_import, print_function from test import _common from beets.autotag import mb from beets import config import unittest -import mock +from unittest import mock class MBAlbumInfoTest(_common.TestCase): diff --git a/test/test_mbsubmit.py b/test/test_mbsubmit.py index 3402c8464..d8654982d 100644 --- a/test/test_mbsubmit.py +++ b/test/test_mbsubmit.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson and Diego Moreda. # @@ -13,7 +12,6 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -from __future__ import division, absolute_import, print_function import unittest from test.helper import capture_stdout, control_stdin, TestHelper @@ -45,9 +43,9 @@ class MBSubmitPluginTest(TerminalImportSessionSetup, unittest.TestCase, self.importer.run() # Manually build the string for comparing the output. - tracklist = (u'Print tracks? ' - u'01. Tag Title 1 - Tag Artist (0:01)\n' - u'02. Tag Title 2 - Tag Artist (0:01)') + tracklist = ('Print tracks? ' + '01. Tag Title 1 - Tag Artist (0:01)\n' + '02. Tag Title 2 - Tag Artist (0:01)') self.assertIn(tracklist, output.getvalue()) def test_print_tracks_output_as_tracks(self): @@ -60,8 +58,8 @@ class MBSubmitPluginTest(TerminalImportSessionSetup, unittest.TestCase, self.importer.run() # Manually build the string for comparing the output. - tracklist = (u'Print tracks? ' - u'02. Tag Title 2 - Tag Artist (0:01)') + tracklist = ('Print tracks? ' + '02. Tag Title 2 - Tag Artist (0:01)') self.assertIn(tracklist, output.getvalue()) diff --git a/test/test_mbsync.py b/test/test_mbsync.py index 35caa3046..d1c5e48b0 100644 --- a/test/test_mbsync.py +++ b/test/test_mbsync.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Thomas Scholtes. # @@ -13,10 +12,9 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -from __future__ import division, absolute_import, print_function import unittest -from mock import patch +from unittest.mock import patch from test.helper import TestHelper,\ generate_album_info, \ @@ -43,24 +41,24 @@ class MbsyncCliTest(unittest.TestCase, TestHelper): album_for_id.return_value = \ generate_album_info( 'album id', - [('track id', {'release_track_id': u'release track id'})] + [('track id', {'release_track_id': 'release track id'})] ) track_for_id.return_value = \ - generate_track_info(u'singleton track id', - {'title': u'singleton info'}) + generate_track_info('singleton track id', + {'title': 'singleton info'}) album_item = Item( - album=u'old title', - mb_albumid=u'81ae60d4-5b75-38df-903a-db2cfa51c2c6', - mb_trackid=u'old track id', - mb_releasetrackid=u'release track id', + album='old title', + mb_albumid='81ae60d4-5b75-38df-903a-db2cfa51c2c6', + mb_trackid='old track id', + mb_releasetrackid='release track id', path='' ) album = self.lib.add_album([album_item]) item = Item( - title=u'old title', - mb_trackid=u'b8c2cf90-83f9-3b5f-8ccd-31fb866fcf37', + title='old title', + mb_trackid='b8c2cf90-83f9-3b5f-8ccd-31fb866fcf37', path='', ) self.lib.add(item) @@ -72,25 +70,25 @@ class MbsyncCliTest(unittest.TestCase, TestHelper): self.assertIn('Sending event: trackinfo_received', logs) item.load() - self.assertEqual(item.title, u'singleton info') + self.assertEqual(item.title, 'singleton info') album_item.load() - self.assertEqual(album_item.title, u'track info') - self.assertEqual(album_item.mb_trackid, u'track id') + self.assertEqual(album_item.title, 'track info') + self.assertEqual(album_item.mb_trackid, 'track id') album.load() - self.assertEqual(album.album, u'album info') + self.assertEqual(album.album, 'album info') def test_message_when_skipping(self): - config['format_item'] = u'$artist - $album - $title' - config['format_album'] = u'$albumartist - $album' + config['format_item'] = '$artist - $album - $title' + config['format_album'] = '$albumartist - $album' # Test album with no mb_albumid. # The default format for an album include $albumartist so # set that here, too. album_invalid = Item( - albumartist=u'album info', - album=u'album info', + albumartist='album info', + album='album info', path='' ) self.lib.add_album([album_invalid]) @@ -98,27 +96,27 @@ class MbsyncCliTest(unittest.TestCase, TestHelper): # default format with capture_log('beets.mbsync') as logs: self.run_command('mbsync') - e = u'mbsync: Skipping album with no mb_albumid: ' + \ - u'album info - album info' + e = 'mbsync: Skipping album with no mb_albumid: ' + \ + 'album info - album info' self.assertEqual(e, logs[0]) # custom format with capture_log('beets.mbsync') as logs: self.run_command('mbsync', '-f', "'$album'") - e = u"mbsync: Skipping album with no mb_albumid: 'album info'" + e = "mbsync: Skipping album with no mb_albumid: 'album info'" self.assertEqual(e, logs[0]) # restore the config - config['format_item'] = u'$artist - $album - $title' - config['format_album'] = u'$albumartist - $album' + config['format_item'] = '$artist - $album - $title' + config['format_album'] = '$albumartist - $album' # Test singleton with no mb_trackid. # The default singleton format includes $artist and $album # so we need to stub them here item_invalid = Item( - artist=u'album info', - album=u'album info', - title=u'old title', + artist='album info', + album='album info', + title='old title', path='', ) self.lib.add(item_invalid) @@ -126,27 +124,27 @@ class MbsyncCliTest(unittest.TestCase, TestHelper): # default format with capture_log('beets.mbsync') as logs: self.run_command('mbsync') - e = u'mbsync: Skipping singleton with no mb_trackid: ' + \ - u'album info - album info - old title' + e = 'mbsync: Skipping singleton with no mb_trackid: ' + \ + 'album info - album info - old title' self.assertEqual(e, logs[0]) # custom format with capture_log('beets.mbsync') as logs: self.run_command('mbsync', '-f', "'$title'") - e = u"mbsync: Skipping singleton with no mb_trackid: 'old title'" + e = "mbsync: Skipping singleton with no mb_trackid: 'old title'" self.assertEqual(e, logs[0]) def test_message_when_invalid(self): - config['format_item'] = u'$artist - $album - $title' - config['format_album'] = u'$albumartist - $album' + config['format_item'] = '$artist - $album - $title' + config['format_album'] = '$albumartist - $album' # Test album with invalid mb_albumid. # The default format for an album include $albumartist so # set that here, too. album_invalid = Item( - albumartist=u'album info', - album=u'album info', - mb_albumid=u'a1b2c3d4', + albumartist='album info', + album='album info', + mb_albumid='a1b2c3d4', path='' ) self.lib.add_album([album_invalid]) @@ -154,28 +152,28 @@ class MbsyncCliTest(unittest.TestCase, TestHelper): # default format with capture_log('beets.mbsync') as logs: self.run_command('mbsync') - e = u'mbsync: Skipping album with invalid mb_albumid: ' + \ - u'album info - album info' + e = 'mbsync: Skipping album with invalid mb_albumid: ' + \ + 'album info - album info' self.assertEqual(e, logs[0]) # custom format with capture_log('beets.mbsync') as logs: self.run_command('mbsync', '-f', "'$album'") - e = u"mbsync: Skipping album with invalid mb_albumid: 'album info'" + e = "mbsync: Skipping album with invalid mb_albumid: 'album info'" self.assertEqual(e, logs[0]) # restore the config - config['format_item'] = u'$artist - $album - $title' - config['format_album'] = u'$albumartist - $album' + config['format_item'] = '$artist - $album - $title' + config['format_album'] = '$albumartist - $album' # Test singleton with invalid mb_trackid. # The default singleton format includes $artist and $album # so we need to stub them here item_invalid = Item( - artist=u'album info', - album=u'album info', - title=u'old title', - mb_trackid=u'a1b2c3d4', + artist='album info', + album='album info', + title='old title', + mb_trackid='a1b2c3d4', path='', ) self.lib.add(item_invalid) @@ -183,14 +181,14 @@ class MbsyncCliTest(unittest.TestCase, TestHelper): # default format with capture_log('beets.mbsync') as logs: self.run_command('mbsync') - e = u'mbsync: Skipping singleton with invalid mb_trackid: ' + \ - u'album info - album info - old title' + e = 'mbsync: Skipping singleton with invalid mb_trackid: ' + \ + 'album info - album info - old title' self.assertEqual(e, logs[0]) # custom format with capture_log('beets.mbsync') as logs: self.run_command('mbsync', '-f', "'$title'") - e = u"mbsync: Skipping singleton with invalid mb_trackid: 'old title'" + e = "mbsync: Skipping singleton with invalid mb_trackid: 'old title'" self.assertEqual(e, logs[0]) diff --git a/test/test_metasync.py b/test/test_metasync.py index 2fccfe3b8..99c2f5a70 100644 --- a/test/test_metasync.py +++ b/test/test_metasync.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Tom Jaspers. # @@ -13,7 +12,6 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -from __future__ import division, absolute_import, print_function import os import platform @@ -72,12 +70,12 @@ class MetaSyncTest(_common.TestCase, TestHelper): if _is_windows(): items[0].path = \ - u'G:\\Music\\Alt-J\\An Awesome Wave\\03 Tessellate.mp3' + 'G:\\Music\\Alt-J\\An Awesome Wave\\03 Tessellate.mp3' items[1].path = \ - u'G:\\Music\\Alt-J\\An Awesome Wave\\04 Breezeblocks.mp3' + 'G:\\Music\\Alt-J\\An Awesome Wave\\04 Breezeblocks.mp3' else: - items[0].path = u'/Music/Alt-J/An Awesome Wave/03 Tessellate.mp3' - items[1].path = u'/Music/Alt-J/An Awesome Wave/04 Breezeblocks.mp3' + items[0].path = '/Music/Alt-J/An Awesome Wave/03 Tessellate.mp3' + items[1].path = '/Music/Alt-J/An Awesome Wave/04 Breezeblocks.mp3' for item in items: self.lib.add(item) diff --git a/test/test_mpdstats.py b/test/test_mpdstats.py index 20226927f..03ef8c7b5 100644 --- a/test/test_mpdstats.py +++ b/test/test_mpdstats.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016 # @@ -13,10 +12,9 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -from __future__ import division, absolute_import, print_function import unittest -from mock import Mock, patch, call, ANY +from unittest.mock import Mock, patch, call, ANY from test.helper import TestHelper from beets.library import Item @@ -34,7 +32,7 @@ class MPDStatsTest(unittest.TestCase, TestHelper): self.unload_plugins() def test_update_rating(self): - item = Item(title=u'title', path='', id=1) + item = Item(title='title', path='', id=1) item.add(self.lib) log = Mock() @@ -45,7 +43,7 @@ class MPDStatsTest(unittest.TestCase, TestHelper): def test_get_item(self): item_path = util.normpath('/foo/bar.flac') - item = Item(title=u'title', path=item_path, id=1) + item = Item(title='title', path=item_path, id=1) item.add(self.lib) log = Mock() @@ -53,13 +51,13 @@ class MPDStatsTest(unittest.TestCase, TestHelper): 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]) + self.assertIn('item not found:', log.info.call_args[0][0]) FAKE_UNKNOWN_STATE = 'some-unknown-one' STATUSES = [{'state': FAKE_UNKNOWN_STATE}, - {'state': u'pause'}, - {'state': u'play', 'songid': 1, 'time': u'0:1'}, - {'state': u'stop'}] + {'state': 'pause'}, + {'state': 'play', 'songid': 1, 'time': '0:1'}, + {'state': 'stop'}] EVENTS = [["player"]] * (len(STATUSES) - 1) + [KeyboardInterrupt] item_path = util.normpath('/foo/bar.flac') songid = 1 @@ -68,7 +66,7 @@ class MPDStatsTest(unittest.TestCase, TestHelper): "events.side_effect": EVENTS, "status.side_effect": STATUSES, "currentsong.return_value": (item_path, songid)})) def test_run_mpdstats(self, mpd_mock): - item = Item(title=u'title', path=self.item_path, id=1) + item = Item(title='title', path=self.item_path, id=1) item.add(self.lib) log = Mock() @@ -78,9 +76,9 @@ class MPDStatsTest(unittest.TestCase, TestHelper): pass log.debug.assert_has_calls( - [call(u'unhandled status "{0}"', ANY)]) + [call('unhandled status "{0}"', ANY)]) log.info.assert_has_calls( - [call(u'pause'), call(u'playing {0}', ANY), call(u'stop')]) + [call('pause'), call('playing {0}', ANY), call('stop')]) def suite(): diff --git a/test/test_parentwork.py b/test/test_parentwork.py index b124785bb..dbaa5976c 100644 --- a/test/test_parentwork.py +++ b/test/test_parentwork.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2017, Dorian Soergel # @@ -15,12 +14,11 @@ """Tests for the 'parentwork' plugin.""" -from __future__ import division, absolute_import, print_function import os import unittest from test.helper import TestHelper -from mock import patch +from unittest.mock import patch from beets.library import Item from beetsplug import parentwork @@ -81,8 +79,8 @@ class ParentWorkIntegrationTest(unittest.TestCase, TestHelper): 'integration testing not enabled') def test_normal_case_real(self): item = Item(path='/file', - mb_workid=u'e27bda6e-531e-36d3-9cd7-b8ebc18e8c53', - parentwork_workid_current=u'e27bda6e-531e-36d3-9cd7-\ + mb_workid='e27bda6e-531e-36d3-9cd7-b8ebc18e8c53', + parentwork_workid_current='e27bda6e-531e-36d3-9cd7-\ b8ebc18e8c53') item.add(self.lib) @@ -90,7 +88,7 @@ class ParentWorkIntegrationTest(unittest.TestCase, TestHelper): item.load() self.assertEqual(item['mb_parentworkid'], - u'32c8943f-1b27-3a23-8660-4567f4847c94') + '32c8943f-1b27-3a23-8660-4567f4847c94') @unittest.skipUnless( os.environ.get('INTEGRATION_TEST', '0') == '1', @@ -98,9 +96,9 @@ class ParentWorkIntegrationTest(unittest.TestCase, TestHelper): def test_force_real(self): self.config['parentwork']['force'] = True item = Item(path='/file', - mb_workid=u'e27bda6e-531e-36d3-9cd7-b8ebc18e8c53', - mb_parentworkid=u'XXX', - parentwork_workid_current=u'e27bda6e-531e-36d3-9cd7-\ + mb_workid='e27bda6e-531e-36d3-9cd7-b8ebc18e8c53', + mb_parentworkid='XXX', + parentwork_workid_current='e27bda6e-531e-36d3-9cd7-\ b8ebc18e8c53', parentwork='whatever') item.add(self.lib) @@ -108,23 +106,23 @@ class ParentWorkIntegrationTest(unittest.TestCase, TestHelper): item.load() self.assertEqual(item['mb_parentworkid'], - u'32c8943f-1b27-3a23-8660-4567f4847c94') + '32c8943f-1b27-3a23-8660-4567f4847c94') @unittest.skipUnless( os.environ.get('INTEGRATION_TEST', '0') == '1', 'integration testing not enabled') def test_no_force_real(self): self.config['parentwork']['force'] = False - item = Item(path='/file', mb_workid=u'e27bda6e-531e-36d3-9cd7-\ - b8ebc18e8c53', mb_parentworkid=u'XXX', - parentwork_workid_current=u'e27bda6e-531e-36d3-9cd7-\ + item = Item(path='/file', mb_workid='e27bda6e-531e-36d3-9cd7-\ + b8ebc18e8c53', mb_parentworkid='XXX', + parentwork_workid_current='e27bda6e-531e-36d3-9cd7-\ b8ebc18e8c53', parentwork='whatever') item.add(self.lib) self.run_command('parentwork') item.load() - self.assertEqual(item['mb_parentworkid'], u'XXX') + self.assertEqual(item['mb_parentworkid'], 'XXX') # test different cases, still with Matthew Passion Ouverture or Mozart # requiem @@ -133,10 +131,10 @@ class ParentWorkIntegrationTest(unittest.TestCase, TestHelper): os.environ.get('INTEGRATION_TEST', '0') == '1', 'integration testing not enabled') def test_direct_parent_work_real(self): - mb_workid = u'2e4a3668-458d-3b2a-8be2-0b08e0d8243a' - self.assertEqual(u'f04b42df-7251-4d86-a5ee-67cfa49580d1', + mb_workid = '2e4a3668-458d-3b2a-8be2-0b08e0d8243a' + self.assertEqual('f04b42df-7251-4d86-a5ee-67cfa49580d1', parentwork.direct_parent_id(mb_workid)[0]) - self.assertEqual(u'45afb3b2-18ac-4187-bc72-beb1b1c194ba', + self.assertEqual('45afb3b2-18ac-4187-bc72-beb1b1c194ba', parentwork.work_parent_id(mb_workid)[0]) @@ -165,7 +163,7 @@ class ParentWorkTest(unittest.TestCase, TestHelper): def test_force(self): self.config['parentwork']['force'] = True - item = Item(path='/file', mb_workid='1', mb_parentworkid=u'XXX', + item = Item(path='/file', mb_workid='1', mb_parentworkid='XXX', parentwork_workid_current='1', parentwork='parentwork') item.add(self.lib) @@ -176,14 +174,14 @@ class ParentWorkTest(unittest.TestCase, TestHelper): def test_no_force(self): self.config['parentwork']['force'] = False - item = Item(path='/file', mb_workid='1', mb_parentworkid=u'XXX', + item = Item(path='/file', mb_workid='1', mb_parentworkid='XXX', parentwork_workid_current='1', parentwork='parentwork') item.add(self.lib) self.run_command('parentwork') item.load() - self.assertEqual(item['mb_parentworkid'], u'XXX') + self.assertEqual(item['mb_parentworkid'], 'XXX') def test_direct_parent_work(self): self.assertEqual('2', parentwork.direct_parent_id('1')[0]) diff --git a/test/test_permissions.py b/test/test_permissions.py index ed84798f1..a67c219c9 100644 --- a/test/test_permissions.py +++ b/test/test_permissions.py @@ -1,13 +1,10 @@ -# -*- coding: utf-8 -*- - """Tests for the 'permissions' plugin. """ -from __future__ import division, absolute_import, print_function import os import platform import unittest -from mock import patch, Mock +from unittest.mock import patch, Mock from test.helper import TestHelper from beets.util import displayable_path @@ -68,7 +65,7 @@ class PermissionsPluginTest(unittest.TestCase, TestHelper): 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], '==')]: - msg = u'{} : {} {} {}'.format( + msg = '{} : {} {} {}'.format( displayable_path(path), oct(os.stat(path).st_mode), x[2], diff --git a/test/test_pipeline.py b/test/test_pipeline.py index 5f44a63bf..cc93d2442 100644 --- a/test/test_pipeline.py +++ b/test/test_pipeline.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -15,7 +14,6 @@ """Test the "pipeline.py" restricted parallel programming library. """ -from __future__ import division, absolute_import, print_function import unittest @@ -24,8 +22,7 @@ from beets.util import pipeline # Some simple pipeline stages for testing. def _produce(num=5): - for i in range(num): - yield i + yield from range(num) def _work(): @@ -111,7 +108,7 @@ class ParallelStageTest(unittest.TestCase): def test_run_parallel(self): self.pl.run_parallel() # Order possibly not preserved; use set equality. - self.assertEqual(set(self.l), set([0, 2, 4, 6, 8])) + self.assertEqual(set(self.l), {0, 2, 4, 6, 8}) def test_pull(self): pl = pipeline.Pipeline((_produce(), (_work(), _work()))) @@ -170,7 +167,7 @@ class ConstrainedThreadedPipelineTest(unittest.TestCase): _produce(1000), (_work(), _work()), _consume(l) )) pl.run_parallel(1) - self.assertEqual(set(l), set(i * 2 for i in range(1000))) + self.assertEqual(set(l), {i * 2 for i in range(1000)}) class BubbleTest(unittest.TestCase): diff --git a/test/test_play.py b/test/test_play.py index 917fb7961..2007686c7 100644 --- a/test/test_play.py +++ b/test/test_play.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Jesse Weinstein # @@ -15,13 +14,12 @@ """Tests for the play plugin""" -from __future__ import division, absolute_import, print_function import os import sys import unittest -from mock import patch, ANY +from unittest.mock import patch, ANY from test.helper import TestHelper, control_stdin @@ -34,7 +32,7 @@ class PlayPluginTest(unittest.TestCase, TestHelper): def setUp(self): self.setup_beets() self.load_plugins('play') - self.item = self.add_item(album=u'a nice älbum', title=u'aNiceTitle') + self.item = self.add_item(album='a nice älbum', title='aNiceTitle') self.lib.add_album([self.item]) self.config['play']['command'] = 'echo' @@ -48,7 +46,7 @@ class PlayPluginTest(unittest.TestCase, TestHelper): open_mock.assert_called_once_with(ANY, expected_cmd) expected_playlist = expected_playlist or self.item.path.decode('utf-8') - exp_playlist = expected_playlist + u'\n' + exp_playlist = expected_playlist + '\n' with open(open_mock.call_args[0][0][0], 'rb') as playlist: self.assertEqual(exp_playlist, playlist.read().decode('utf-8')) @@ -56,23 +54,23 @@ class PlayPluginTest(unittest.TestCase, TestHelper): self.run_and_assert(open_mock) def test_album_option(self, open_mock): - self.run_and_assert(open_mock, [u'-a', u'nice']) + self.run_and_assert(open_mock, ['-a', 'nice']) def test_args_option(self, open_mock): self.run_and_assert( - open_mock, [u'-A', u'foo', u'title:aNiceTitle'], u'echo foo') + open_mock, ['-A', 'foo', 'title:aNiceTitle'], 'echo foo') def test_args_option_in_middle(self, open_mock): self.config['play']['command'] = 'echo $args other' self.run_and_assert( - open_mock, [u'-A', u'foo', u'title:aNiceTitle'], u'echo foo other') + open_mock, ['-A', 'foo', 'title:aNiceTitle'], 'echo foo other') def test_unset_args_option_in_middle(self, open_mock): self.config['play']['command'] = 'echo $args other' self.run_and_assert( - open_mock, [u'title:aNiceTitle'], u'echo other') + open_mock, ['title:aNiceTitle'], 'echo other') @unittest.skipIf(sys.platform, 'win32') # FIXME: fails on windows def test_relative_to(self, open_mock): @@ -92,19 +90,19 @@ class PlayPluginTest(unittest.TestCase, TestHelper): open_mock.assert_called_once_with(ANY, open_anything()) with open(open_mock.call_args[0][0][0], 'rb') as f: playlist = f.read().decode('utf-8') - self.assertEqual(u'{}\n'.format( + self.assertEqual('{}\n'.format( os.path.dirname(self.item.path.decode('utf-8'))), playlist) def test_raw(self, open_mock): self.config['play']['raw'] = True - self.run_command(u'play', u'nice') + self.run_command('play', 'nice') open_mock.assert_called_once_with([self.item.path], 'echo') def test_not_found(self, open_mock): - self.run_command(u'play', u'not found') + self.run_command('play', 'not found') open_mock.assert_not_called() @@ -113,7 +111,7 @@ class PlayPluginTest(unittest.TestCase, TestHelper): self.add_item(title='another NiceTitle') with control_stdin("a"): - self.run_command(u'play', u'nice') + self.run_command('play', 'nice') open_mock.assert_not_called() @@ -121,21 +119,21 @@ class PlayPluginTest(unittest.TestCase, TestHelper): self.config['play']['warning_threshold'] = 1 self.other_item = self.add_item(title='another NiceTitle') - expected_playlist = u'{0}\n{1}'.format( + expected_playlist = '{}\n{}'.format( self.item.path.decode('utf-8'), self.other_item.path.decode('utf-8')) with control_stdin("a"): self.run_and_assert( open_mock, - [u'-y', u'NiceTitle'], + ['-y', 'NiceTitle'], expected_playlist=expected_playlist) def test_command_failed(self, open_mock): - open_mock.side_effect = OSError(u"some reason") + open_mock.side_effect = OSError("some reason") with self.assertRaises(UserError): - self.run_command(u'play', u'title:aNiceTitle') + self.run_command('play', 'title:aNiceTitle') def suite(): diff --git a/test/test_player.py b/test/test_player.py index dd47ac62b..9179fb2e1 100644 --- a/test/test_player.py +++ b/test/test_player.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -15,7 +14,6 @@ """Tests for BPD's implementation of the MPD protocol. """ -from __future__ import division, absolute_import, print_function import unittest from test.helper import TestHelper @@ -36,7 +34,7 @@ import confuse # Mock GstPlayer so that the forked process doesn't attempt to import gi: -import mock +from unittest import mock import imp gstplayer = imp.new_module("beetsplug.bpd.gstplayer") def _gstplayer_play(*_): # noqa: 42 @@ -63,45 +61,45 @@ class CommandParseTest(unittest.TestCase): def test_no_args(self): s = r'command' c = bpd.Command(s) - self.assertEqual(c.name, u'command') + self.assertEqual(c.name, 'command') self.assertEqual(c.args, []) def test_one_unquoted_arg(self): s = r'command hello' c = bpd.Command(s) - self.assertEqual(c.name, u'command') - self.assertEqual(c.args, [u'hello']) + self.assertEqual(c.name, 'command') + self.assertEqual(c.args, ['hello']) def test_two_unquoted_args(self): s = r'command hello there' c = bpd.Command(s) - self.assertEqual(c.name, u'command') - self.assertEqual(c.args, [u'hello', u'there']) + self.assertEqual(c.name, 'command') + self.assertEqual(c.args, ['hello', 'there']) def test_one_quoted_arg(self): s = r'command "hello there"' c = bpd.Command(s) - self.assertEqual(c.name, u'command') - self.assertEqual(c.args, [u'hello there']) + self.assertEqual(c.name, 'command') + self.assertEqual(c.args, ['hello there']) def test_heterogenous_args(self): s = r'command "hello there" sir' c = bpd.Command(s) - self.assertEqual(c.name, u'command') - self.assertEqual(c.args, [u'hello there', u'sir']) + self.assertEqual(c.name, 'command') + self.assertEqual(c.args, ['hello there', 'sir']) def test_quote_in_arg(self): s = r'command "hello \" there"' c = bpd.Command(s) - self.assertEqual(c.args, [u'hello " there']) + self.assertEqual(c.args, ['hello " there']) def test_backslash_in_arg(self): s = r'command "hello \\ there"' c = bpd.Command(s) - self.assertEqual(c.args, [u'hello \\ there']) + self.assertEqual(c.args, ['hello \\ there']) -class MPCResponse(object): +class MPCResponse: def __init__(self, raw_response): body = b'\n'.join(raw_response.split(b'\n')[:-2]).decode('utf-8') self.data = self._parse_body(body) @@ -119,7 +117,7 @@ class MPCResponse(object): cmd, rest = rest[2:].split('}') return False, (int(code), int(pos), cmd, rest[1:]) else: - raise RuntimeError('Unexpected status: {!r}'.format(status)) + raise RuntimeError(f'Unexpected status: {status!r}') def _parse_body(self, body): """ Messages are generally in the format "header: content". @@ -132,7 +130,7 @@ class MPCResponse(object): if not line: continue if ':' not in line: - raise RuntimeError('Unexpected line: {!r}'.format(line)) + raise RuntimeError(f'Unexpected line: {line!r}') header, content = line.split(':', 1) content = content.lstrip() if header in repeated_headers: @@ -145,7 +143,7 @@ class MPCResponse(object): return data -class MPCClient(object): +class MPCClient: def __init__(self, sock, do_hello=True): self.sock = sock self.buf = b'' @@ -178,7 +176,7 @@ class MPCClient(object): responses.append(MPCResponse(response)) response = b'' elif not line: - raise RuntimeError('Unexpected response: {!r}'.format(line)) + raise RuntimeError(f'Unexpected response: {line!r}') def serialise_command(self, command, *args): cmd = [command.encode('utf-8')] diff --git a/test/test_playlist.py b/test/test_playlist.py index 88abb733f..3b5ac2660 100644 --- a/test/test_playlist.py +++ b/test/test_playlist.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Thomas Scholtes. # @@ -13,7 +12,6 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -from __future__ import division, absolute_import, print_function from six.moves import shlex_quote import os @@ -39,8 +37,8 @@ class PlaylistTestHelper(helper.TestHelper): self.music_dir, 'a', 'b', 'c.mp3', )) - i1.title = u'some item' - i1.album = u'some album' + i1.title = 'some item' + i1.album = 'some album' self.lib.add(i1) self.lib.add_album([i1]) @@ -82,50 +80,50 @@ class PlaylistTestHelper(helper.TestHelper): class PlaylistQueryTestHelper(PlaylistTestHelper): def test_name_query_with_absolute_paths_in_playlist(self): - q = u'playlist:absolute' + q = 'playlist:absolute' results = self.lib.items(q) - self.assertEqual(set([i.title for i in results]), set([ - u'some item', - u'another item', - ])) + self.assertEqual({i.title for i in results}, { + 'some item', + 'another item', + }) def test_path_query_with_absolute_paths_in_playlist(self): - q = u'playlist:{0}'.format(shlex_quote(os.path.join( + q = 'playlist:{}'.format(shlex_quote(os.path.join( self.playlist_dir, 'absolute.m3u', ))) results = self.lib.items(q) - self.assertEqual(set([i.title for i in results]), set([ - u'some item', - u'another item', - ])) + self.assertEqual({i.title for i in results}, { + 'some item', + 'another item', + }) def test_name_query_with_relative_paths_in_playlist(self): - q = u'playlist:relative' + q = 'playlist:relative' results = self.lib.items(q) - self.assertEqual(set([i.title for i in results]), set([ - u'some item', - u'another item', - ])) + self.assertEqual({i.title for i in results}, { + 'some item', + 'another item', + }) def test_path_query_with_relative_paths_in_playlist(self): - q = u'playlist:{0}'.format(shlex_quote(os.path.join( + q = 'playlist:{}'.format(shlex_quote(os.path.join( self.playlist_dir, 'relative.m3u', ))) results = self.lib.items(q) - self.assertEqual(set([i.title for i in results]), set([ - u'some item', - u'another item', - ])) + self.assertEqual({i.title for i in results}, { + 'some item', + 'another item', + }) def test_name_query_with_nonexisting_playlist(self): - q = u'playlist:nonexisting' + q = 'playlist:nonexisting' results = self.lib.items(q) self.assertEqual(set(results), set()) def test_path_query_with_nonexisting_playlist(self): - q = u'playlist:{0}'.format(shlex_quote(os.path.join( + q = 'playlist:{}'.format(shlex_quote(os.path.join( self.playlist_dir, self.playlist_dir, 'nonexisting.m3u', @@ -137,17 +135,17 @@ class PlaylistQueryTestHelper(PlaylistTestHelper): class PlaylistTestRelativeToLib(PlaylistQueryTestHelper, unittest.TestCase): def setup_test(self): with open(os.path.join(self.playlist_dir, 'absolute.m3u'), 'w') as f: - f.write('{0}\n'.format(os.path.join( + f.write('{}\n'.format(os.path.join( self.music_dir, 'a', 'b', 'c.mp3'))) - f.write('{0}\n'.format(os.path.join( + f.write('{}\n'.format(os.path.join( self.music_dir, 'd', 'e', 'f.mp3'))) - f.write('{0}\n'.format(os.path.join( + f.write('{}\n'.format(os.path.join( self.music_dir, 'nonexisting.mp3'))) with open(os.path.join(self.playlist_dir, 'relative.m3u'), 'w') as f: - f.write('{0}\n'.format(os.path.join('a', 'b', 'c.mp3'))) - f.write('{0}\n'.format(os.path.join('d', 'e', 'f.mp3'))) - f.write('{0}\n'.format('nonexisting.mp3')) + f.write('{}\n'.format(os.path.join('a', 'b', 'c.mp3'))) + f.write('{}\n'.format(os.path.join('d', 'e', 'f.mp3'))) + f.write('{}\n'.format('nonexisting.mp3')) self.config['playlist']['relative_to'] = 'library' @@ -155,17 +153,17 @@ class PlaylistTestRelativeToLib(PlaylistQueryTestHelper, unittest.TestCase): class PlaylistTestRelativeToDir(PlaylistQueryTestHelper, unittest.TestCase): def setup_test(self): with open(os.path.join(self.playlist_dir, 'absolute.m3u'), 'w') as f: - f.write('{0}\n'.format(os.path.join( + f.write('{}\n'.format(os.path.join( self.music_dir, 'a', 'b', 'c.mp3'))) - f.write('{0}\n'.format(os.path.join( + f.write('{}\n'.format(os.path.join( self.music_dir, 'd', 'e', 'f.mp3'))) - f.write('{0}\n'.format(os.path.join( + f.write('{}\n'.format(os.path.join( self.music_dir, 'nonexisting.mp3'))) with open(os.path.join(self.playlist_dir, 'relative.m3u'), 'w') as f: - f.write('{0}\n'.format(os.path.join('a', 'b', 'c.mp3'))) - f.write('{0}\n'.format(os.path.join('d', 'e', 'f.mp3'))) - f.write('{0}\n'.format('nonexisting.mp3')) + f.write('{}\n'.format(os.path.join('a', 'b', 'c.mp3'))) + f.write('{}\n'.format(os.path.join('d', 'e', 'f.mp3'))) + f.write('{}\n'.format('nonexisting.mp3')) self.config['playlist']['relative_to'] = self.music_dir @@ -173,23 +171,23 @@ class PlaylistTestRelativeToDir(PlaylistQueryTestHelper, unittest.TestCase): class PlaylistTestRelativeToPls(PlaylistQueryTestHelper, unittest.TestCase): def setup_test(self): with open(os.path.join(self.playlist_dir, 'absolute.m3u'), 'w') as f: - f.write('{0}\n'.format(os.path.join( + f.write('{}\n'.format(os.path.join( self.music_dir, 'a', 'b', 'c.mp3'))) - f.write('{0}\n'.format(os.path.join( + f.write('{}\n'.format(os.path.join( self.music_dir, 'd', 'e', 'f.mp3'))) - f.write('{0}\n'.format(os.path.join( + f.write('{}\n'.format(os.path.join( self.music_dir, 'nonexisting.mp3'))) with open(os.path.join(self.playlist_dir, 'relative.m3u'), 'w') as f: - f.write('{0}\n'.format(os.path.relpath( + f.write('{}\n'.format(os.path.relpath( os.path.join(self.music_dir, 'a', 'b', 'c.mp3'), start=self.playlist_dir, ))) - f.write('{0}\n'.format(os.path.relpath( + f.write('{}\n'.format(os.path.relpath( os.path.join(self.music_dir, 'd', 'e', 'f.mp3'), start=self.playlist_dir, ))) - f.write('{0}\n'.format(os.path.relpath( + f.write('{}\n'.format(os.path.relpath( os.path.join(self.music_dir, 'nonexisting.mp3'), start=self.playlist_dir, ))) @@ -201,17 +199,17 @@ class PlaylistTestRelativeToPls(PlaylistQueryTestHelper, unittest.TestCase): class PlaylistUpdateTestHelper(PlaylistTestHelper): def setup_test(self): with open(os.path.join(self.playlist_dir, 'absolute.m3u'), 'w') as f: - f.write('{0}\n'.format(os.path.join( + f.write('{}\n'.format(os.path.join( self.music_dir, 'a', 'b', 'c.mp3'))) - f.write('{0}\n'.format(os.path.join( + f.write('{}\n'.format(os.path.join( self.music_dir, 'd', 'e', 'f.mp3'))) - f.write('{0}\n'.format(os.path.join( + f.write('{}\n'.format(os.path.join( self.music_dir, 'nonexisting.mp3'))) with open(os.path.join(self.playlist_dir, 'relative.m3u'), 'w') as f: - f.write('{0}\n'.format(os.path.join('a', 'b', 'c.mp3'))) - f.write('{0}\n'.format(os.path.join('d', 'e', 'f.mp3'))) - f.write('{0}\n'.format('nonexisting.mp3')) + f.write('{}\n'.format(os.path.join('a', 'b', 'c.mp3'))) + f.write('{}\n'.format(os.path.join('d', 'e', 'f.mp3'))) + f.write('{}\n'.format('nonexisting.mp3')) self.config['playlist']['auto'] = True self.config['playlist']['relative_to'] = 'library' @@ -220,7 +218,7 @@ class PlaylistUpdateTestHelper(PlaylistTestHelper): class PlaylistTestItemMoved(PlaylistUpdateTestHelper, unittest.TestCase): def test_item_moved(self): # Emit item_moved event for an item that is in a playlist - results = self.lib.items(u'path:{0}'.format(shlex_quote( + results = self.lib.items('path:{}'.format(shlex_quote( os.path.join(self.music_dir, 'd', 'e', 'f.mp3')))) item = results[0] beets.plugins.send( @@ -229,7 +227,7 @@ class PlaylistTestItemMoved(PlaylistUpdateTestHelper, unittest.TestCase): os.path.join(self.music_dir, 'g', 'h', 'i.mp3'))) # Emit item_moved event for an item that is not in a playlist - results = self.lib.items(u'path:{0}'.format(shlex_quote( + results = self.lib.items('path:{}'.format(shlex_quote( os.path.join(self.music_dir, 'x', 'y', 'z.mp3')))) item = results[0] beets.plugins.send( @@ -242,7 +240,7 @@ class PlaylistTestItemMoved(PlaylistUpdateTestHelper, unittest.TestCase): # Check playlist with absolute paths playlist_path = os.path.join(self.playlist_dir, 'absolute.m3u') - with open(playlist_path, 'r') as f: + with open(playlist_path) as f: lines = [line.strip() for line in f.readlines()] self.assertEqual(lines, [ @@ -253,7 +251,7 @@ class PlaylistTestItemMoved(PlaylistUpdateTestHelper, unittest.TestCase): # Check playlist with relative paths playlist_path = os.path.join(self.playlist_dir, 'relative.m3u') - with open(playlist_path, 'r') as f: + with open(playlist_path) as f: lines = [line.strip() for line in f.readlines()] self.assertEqual(lines, [ @@ -266,13 +264,13 @@ class PlaylistTestItemMoved(PlaylistUpdateTestHelper, unittest.TestCase): class PlaylistTestItemRemoved(PlaylistUpdateTestHelper, unittest.TestCase): def test_item_removed(self): # Emit item_removed event for an item that is in a playlist - results = self.lib.items(u'path:{0}'.format(shlex_quote( + results = self.lib.items('path:{}'.format(shlex_quote( os.path.join(self.music_dir, 'd', 'e', 'f.mp3')))) item = results[0] beets.plugins.send('item_removed', item=item) # Emit item_removed event for an item that is not in a playlist - results = self.lib.items(u'path:{0}'.format(shlex_quote( + results = self.lib.items('path:{}'.format(shlex_quote( os.path.join(self.music_dir, 'x', 'y', 'z.mp3')))) item = results[0] beets.plugins.send('item_removed', item=item) @@ -282,7 +280,7 @@ class PlaylistTestItemRemoved(PlaylistUpdateTestHelper, unittest.TestCase): # Check playlist with absolute paths playlist_path = os.path.join(self.playlist_dir, 'absolute.m3u') - with open(playlist_path, 'r') as f: + with open(playlist_path) as f: lines = [line.strip() for line in f.readlines()] self.assertEqual(lines, [ @@ -292,7 +290,7 @@ class PlaylistTestItemRemoved(PlaylistUpdateTestHelper, unittest.TestCase): # Check playlist with relative paths playlist_path = os.path.join(self.playlist_dir, 'relative.m3u') - with open(playlist_path, 'r') as f: + with open(playlist_path) as f: lines = [line.strip() for line in f.readlines()] self.assertEqual(lines, [ diff --git a/test/test_plexupdate.py b/test/test_plexupdate.py index ef89892a4..2d725a4f6 100644 --- a/test/test_plexupdate.py +++ b/test/test_plexupdate.py @@ -1,7 +1,3 @@ -# -*- coding: utf-8 -*- - -from __future__ import division, absolute_import, print_function - from test.helper import TestHelper from beetsplug.plexupdate import get_music_section, update_plex import unittest @@ -76,8 +72,8 @@ class PlexUpdateTest(unittest.TestCase, TestHelper): self.load_plugins('plexupdate') self.config['plex'] = { - u'host': u'localhost', - u'port': 32400} + 'host': 'localhost', + 'port': 32400} def tearDown(self): self.teardown_beets() diff --git a/test/test_plugin_mediafield.py b/test/test_plugin_mediafield.py index a08db4542..e5d336b9a 100644 --- a/test/test_plugin_mediafield.py +++ b/test/test_plugin_mediafield.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -15,7 +14,6 @@ """Tests the facility that lets plugins add custom field to MediaFile. """ -from __future__ import division, absolute_import, print_function import os import six @@ -30,7 +28,7 @@ from beets.util import bytestring_path field_extension = mediafile.MediaField( - mediafile.MP3DescStorageStyle(u'customtag'), + mediafile.MP3DescStorageStyle('customtag'), mediafile.MP4StorageStyle('----:com.apple.iTunes:customtag'), mediafile.StorageStyle('customtag'), mediafile.ASFStorageStyle('customtag'), @@ -52,11 +50,11 @@ class ExtendedFieldTestMixin(_common.TestCase): try: mf = self._mediafile_fixture('empty') - mf.customtag = u'F#' + mf.customtag = 'F#' mf.save() mf = mediafile.MediaFile(mf.path) - self.assertEqual(mf.customtag, u'F#') + self.assertEqual(mf.customtag, 'F#') finally: delattr(mediafile.MediaFile, 'customtag') @@ -70,10 +68,10 @@ class ExtendedFieldTestMixin(_common.TestCase): mf = self._mediafile_fixture('empty') self.assertIsNone(mf.customtag) - item = Item(path=mf.path, customtag=u'Gb') + item = Item(path=mf.path, customtag='Gb') item.write() mf = mediafile.MediaFile(mf.path) - self.assertEqual(mf.customtag, u'Gb') + self.assertEqual(mf.customtag, 'Gb') finally: delattr(mediafile.MediaFile, 'customtag') @@ -85,11 +83,11 @@ class ExtendedFieldTestMixin(_common.TestCase): try: mf = self._mediafile_fixture('empty') - mf.update({'customtag': u'F#'}) + mf.update({'customtag': 'F#'}) mf.save() item = Item.from_path(mf.path) - self.assertEqual(item['customtag'], u'F#') + self.assertEqual(item['customtag'], 'F#') finally: delattr(mediafile.MediaFile, 'customtag') @@ -98,14 +96,14 @@ class ExtendedFieldTestMixin(_common.TestCase): def test_invalid_descriptor(self): with self.assertRaises(ValueError) as cm: mediafile.MediaFile.add_field('somekey', True) - self.assertIn(u'must be an instance of MediaField', - six.text_type(cm.exception)) + self.assertIn('must be an instance of MediaField', + str(cm.exception)) def test_overwrite_property(self): with self.assertRaises(ValueError) as cm: mediafile.MediaFile.add_field('artist', mediafile.MediaField()) - self.assertIn(u'property "artist" already exists', - six.text_type(cm.exception)) + self.assertIn('property "artist" already exists', + str(cm.exception)) def suite(): diff --git a/test/test_plugins.py b/test/test_plugins.py index 884aa7875..2e5b24380 100644 --- a/test/test_plugins.py +++ b/test/test_plugins.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Thomas Scholtes. # @@ -13,10 +12,9 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -from __future__ import division, absolute_import, print_function import os -from mock import patch, Mock, ANY +from unittest.mock import patch, Mock, ANY import shutil import itertools import unittest @@ -74,22 +72,22 @@ class ItemTypesTest(unittest.TestCase, TestHelper): self.register_plugin(RatingPlugin) self.config['plugins'] = 'rating' - item = Item(path=u'apath', artist=u'aaa') + item = Item(path='apath', artist='aaa') item.add(self.lib) # Do not match unset values - out = self.run_with_output(u'ls', u'rating:1..3') - self.assertNotIn(u'aaa', out) + out = self.run_with_output('ls', 'rating:1..3') + self.assertNotIn('aaa', out) - self.run_command(u'modify', u'rating=2', u'--yes') + self.run_command('modify', 'rating=2', '--yes') # Match in range - out = self.run_with_output(u'ls', u'rating:1..3') - self.assertIn(u'aaa', out) + out = self.run_with_output('ls', 'rating:1..3') + self.assertIn('aaa', out) # Don't match out of range - out = self.run_with_output(u'ls', u'rating:3..5') - self.assertNotIn(u'aaa', out) + out = self.run_with_output('ls', 'rating:3..5') + self.assertNotIn('aaa', out) class ItemWriteTest(unittest.TestCase, TestHelper): @@ -110,16 +108,16 @@ class ItemWriteTest(unittest.TestCase, TestHelper): def test_change_tags(self): def on_write(item=None, path=None, tags=None): - if tags['artist'] == u'XXX': - tags['artist'] = u'YYY' + if tags['artist'] == 'XXX': + tags['artist'] = 'YYY' self.register_listener('write', on_write) - item = self.add_item_fixture(artist=u'XXX') + item = self.add_item_fixture(artist='XXX') item.write() mediafile = MediaFile(syspath(item.path)) - self.assertEqual(mediafile.artist, u'YYY') + self.assertEqual(mediafile.artist, 'YYY') def register_listener(self, event, func): self.event_listener_plugin.register_listener(event, func) @@ -195,8 +193,8 @@ class EventsTest(unittest.TestCase, ImportHelper, TestHelper): os.makedirs(self.album_path) metadata = { - 'artist': u'Tag Artist', - 'album': u'Tag Album', + 'artist': 'Tag Artist', + 'album': 'Tag Album', 'albumartist': None, 'mb_trackid': None, 'mb_albumid': None, @@ -205,7 +203,7 @@ class EventsTest(unittest.TestCase, ImportHelper, TestHelper): self.file_paths = [] for i in range(count): metadata['track'] = i + 1 - metadata['title'] = u'Tag Title Album %d' % (i + 1) + metadata['title'] = 'Tag Title Album %d' % (i + 1) track_file = bytestring_path('%02d - track.mp3' % (i + 1)) dest_path = os.path.join(self.album_path, track_file) self.__copy_file(dest_path, metadata) @@ -222,21 +220,21 @@ class EventsTest(unittest.TestCase, ImportHelper, TestHelper): # Exactly one event should have been imported (for the album). # Sentinels do not get emitted. - self.assertEqual(logs.count(u'Sending event: import_task_created'), 1) + self.assertEqual(logs.count('Sending event: import_task_created'), 1) logs = [line for line in logs if not line.startswith( - u'Sending event:')] + 'Sending event:')] self.assertEqual(logs, [ - u'Album: {0}'.format(displayable_path( + 'Album: {}'.format(displayable_path( os.path.join(self.import_dir, b'album'))), - u' {0}'.format(displayable_path(self.file_paths[0])), - u' {0}'.format(displayable_path(self.file_paths[1])), + ' {}'.format(displayable_path(self.file_paths[0])), + ' {}'.format(displayable_path(self.file_paths[1])), ]) def test_import_task_created_with_plugin(self): class ToSingletonPlugin(plugins.BeetsPlugin): def __init__(self): - super(ToSingletonPlugin, self).__init__() + super().__init__() self.register_listener('import_task_created', self.import_task_created_event) @@ -266,13 +264,13 @@ class EventsTest(unittest.TestCase, ImportHelper, TestHelper): # Exactly one event should have been imported (for the album). # Sentinels do not get emitted. - self.assertEqual(logs.count(u'Sending event: import_task_created'), 1) + self.assertEqual(logs.count('Sending event: import_task_created'), 1) logs = [line for line in logs if not line.startswith( - u'Sending event:')] + 'Sending event:')] self.assertEqual(logs, [ - u'Singleton: {0}'.format(displayable_path(self.file_paths[0])), - u'Singleton: {0}'.format(displayable_path(self.file_paths[1])), + 'Singleton: {}'.format(displayable_path(self.file_paths[0])), + 'Singleton: {}'.format(displayable_path(self.file_paths[1])), ]) @@ -280,13 +278,13 @@ class HelpersTest(unittest.TestCase): def test_sanitize_choices(self): self.assertEqual( - plugins.sanitize_choices([u'A', u'Z'], (u'A', u'B')), [u'A']) + plugins.sanitize_choices(['A', 'Z'], ('A', 'B')), ['A']) self.assertEqual( - plugins.sanitize_choices([u'A', u'A'], (u'A')), [u'A']) + plugins.sanitize_choices(['A', 'A'], ('A')), ['A']) self.assertEqual( - plugins.sanitize_choices([u'D', u'*', u'A'], - (u'A', u'B', u'C', u'D')), - [u'D', u'B', u'C', u'A']) + plugins.sanitize_choices(['D', '*', 'A'], + ('A', 'B', 'C', 'D')), + ['D', 'B', 'C', 'A']) class ListenersTest(unittest.TestCase, TestHelper): @@ -301,7 +299,7 @@ class ListenersTest(unittest.TestCase, TestHelper): class DummyPlugin(plugins.BeetsPlugin): def __init__(self): - super(DummyPlugin, self).__init__() + super().__init__() self.register_listener('cli_exit', self.dummy) self.register_listener('cli_exit', self.dummy) @@ -326,7 +324,7 @@ class ListenersTest(unittest.TestCase, TestHelper): class DummyPlugin(plugins.BeetsPlugin): def __init__(self): - super(DummyPlugin, self).__init__() + super().__init__() self.foo = Mock(__name__='foo') self.register_listener('event_foo', self.foo) self.bar = Mock(__name__='bar') @@ -339,8 +337,8 @@ class ListenersTest(unittest.TestCase, TestHelper): d.foo.assert_has_calls([]) d.bar.assert_has_calls([]) - plugins.send('event_foo', var=u"tagada") - d.foo.assert_called_once_with(var=u"tagada") + plugins.send('event_foo', var="tagada") + d.foo.assert_called_once_with(var="tagada") d.bar.assert_has_calls([]) @patch('beets.plugins.find_plugins') @@ -349,13 +347,13 @@ class ListenersTest(unittest.TestCase, TestHelper): class DummyPlugin(plugins.BeetsPlugin): def __init__(self): - super(DummyPlugin, self).__init__() + super().__init__() for i in itertools.count(1): try: - meth = getattr(self, 'dummy{0}'.format(i)) + meth = getattr(self, f'dummy{i}') except AttributeError: break - self.register_listener('event{0}'.format(i), meth) + self.register_listener(f'event{i}', meth) def dummy1(self, foo): test.assertEqual(foo, 5) @@ -433,19 +431,19 @@ class PromptChoicesTest(TerminalImportSessionSetup, unittest.TestCase, """Test the presence of plugin choices on the prompt (album).""" class DummyPlugin(plugins.BeetsPlugin): def __init__(self): - super(DummyPlugin, self).__init__() + super().__init__() self.register_listener('before_choose_candidate', self.return_choices) def return_choices(self, session, task): - return [ui.commands.PromptChoice('f', u'Foo', None), - ui.commands.PromptChoice('r', u'baR', None)] + return [ui.commands.PromptChoice('f', 'Foo', None), + ui.commands.PromptChoice('r', 'baR', None)] self.register_plugin(DummyPlugin) # Default options + extra choices by the plugin ('Foo', 'Bar') - opts = (u'Apply', u'More candidates', u'Skip', u'Use as-is', - u'as Tracks', u'Group albums', u'Enter search', - u'enter Id', u'aBort') + (u'Foo', u'baR') + opts = ('Apply', 'More candidates', 'Skip', 'Use as-is', + 'as Tracks', 'Group albums', 'Enter search', + 'enter Id', 'aBort') + ('Foo', 'baR') self.importer.add_choice(action.SKIP) self.importer.run() @@ -456,19 +454,19 @@ class PromptChoicesTest(TerminalImportSessionSetup, unittest.TestCase, """Test the presence of plugin choices on the prompt (singleton).""" class DummyPlugin(plugins.BeetsPlugin): def __init__(self): - super(DummyPlugin, self).__init__() + super().__init__() self.register_listener('before_choose_candidate', self.return_choices) def return_choices(self, session, task): - return [ui.commands.PromptChoice('f', u'Foo', None), - ui.commands.PromptChoice('r', u'baR', None)] + return [ui.commands.PromptChoice('f', 'Foo', None), + ui.commands.PromptChoice('r', 'baR', None)] self.register_plugin(DummyPlugin) # Default options + extra choices by the plugin ('Foo', 'Bar') - opts = (u'Apply', u'More candidates', u'Skip', u'Use as-is', - u'Enter search', - u'enter Id', u'aBort') + (u'Foo', u'baR') + opts = ('Apply', 'More candidates', 'Skip', 'Use as-is', + 'Enter search', + 'enter Id', 'aBort') + ('Foo', 'baR') config['import']['singletons'] = True self.importer.add_choice(action.SKIP) @@ -480,21 +478,21 @@ class PromptChoicesTest(TerminalImportSessionSetup, unittest.TestCase, """Test the short letter conflict solving.""" class DummyPlugin(plugins.BeetsPlugin): def __init__(self): - super(DummyPlugin, self).__init__() + super().__init__() self.register_listener('before_choose_candidate', self.return_choices) def return_choices(self, session, task): - return [ui.commands.PromptChoice('a', u'A foo', None), # dupe - ui.commands.PromptChoice('z', u'baZ', None), # ok - ui.commands.PromptChoice('z', u'Zupe', None), # dupe - ui.commands.PromptChoice('z', u'Zoo', None)] # dupe + return [ui.commands.PromptChoice('a', 'A foo', None), # dupe + ui.commands.PromptChoice('z', 'baZ', None), # ok + ui.commands.PromptChoice('z', 'Zupe', None), # dupe + ui.commands.PromptChoice('z', 'Zoo', None)] # dupe self.register_plugin(DummyPlugin) # Default options + not dupe extra choices by the plugin ('baZ') - opts = (u'Apply', u'More candidates', u'Skip', u'Use as-is', - u'as Tracks', u'Group albums', u'Enter search', - u'enter Id', u'aBort') + (u'baZ',) + opts = ('Apply', 'More candidates', 'Skip', 'Use as-is', + 'as Tracks', 'Group albums', 'Enter search', + 'enter Id', 'aBort') + ('baZ',) self.importer.add_choice(action.SKIP) self.importer.run() self.mock_input_options.assert_called_once_with(opts, default='a', @@ -504,21 +502,21 @@ class PromptChoicesTest(TerminalImportSessionSetup, unittest.TestCase, """Test that plugin callbacks are being called upon user choice.""" class DummyPlugin(plugins.BeetsPlugin): def __init__(self): - super(DummyPlugin, self).__init__() + super().__init__() self.register_listener('before_choose_candidate', self.return_choices) def return_choices(self, session, task): - return [ui.commands.PromptChoice('f', u'Foo', self.foo)] + return [ui.commands.PromptChoice('f', 'Foo', self.foo)] def foo(self, session, task): pass self.register_plugin(DummyPlugin) # Default options + extra choices by the plugin ('Foo', 'Bar') - opts = (u'Apply', u'More candidates', u'Skip', u'Use as-is', - u'as Tracks', u'Group albums', u'Enter search', - u'enter Id', u'aBort') + (u'Foo',) + opts = ('Apply', 'More candidates', 'Skip', 'Use as-is', + 'as Tracks', 'Group albums', 'Enter search', + 'enter Id', 'aBort') + ('Foo',) # DummyPlugin.foo() should be called once with patch.object(DummyPlugin, 'foo', autospec=True) as mock_foo: @@ -535,21 +533,21 @@ class PromptChoicesTest(TerminalImportSessionSetup, unittest.TestCase, """Test that plugin callbacks that return a value exit the loop.""" class DummyPlugin(plugins.BeetsPlugin): def __init__(self): - super(DummyPlugin, self).__init__() + super().__init__() self.register_listener('before_choose_candidate', self.return_choices) def return_choices(self, session, task): - return [ui.commands.PromptChoice('f', u'Foo', self.foo)] + return [ui.commands.PromptChoice('f', 'Foo', self.foo)] def foo(self, session, task): return action.SKIP self.register_plugin(DummyPlugin) # Default options + extra choices by the plugin ('Foo', 'Bar') - opts = (u'Apply', u'More candidates', u'Skip', u'Use as-is', - u'as Tracks', u'Group albums', u'Enter search', - u'enter Id', u'aBort') + (u'Foo',) + opts = ('Apply', 'More candidates', 'Skip', 'Use as-is', + 'as Tracks', 'Group albums', 'Enter search', + 'enter Id', 'aBort') + ('Foo',) # DummyPlugin.foo() should be called once with helper.control_stdin('f\n'): diff --git a/test/test_query.py b/test/test_query.py index a53a91f43..68fd40cc1 100644 --- a/test/test_query.py +++ b/test/test_query.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -15,10 +14,9 @@ """Various tests for querying the library database. """ -from __future__ import division, absolute_import, print_function from functools import partial -from mock import patch +from unittest.mock import patch import os import sys import unittest @@ -57,19 +55,19 @@ class AnyFieldQueryTest(_common.LibTestCase): self.assertEqual(self.lib.items(q).get().title, 'the title') def test_restriction_completeness(self): - q = dbcore.query.AnyFieldQuery('title', [u'title'], + q = dbcore.query.AnyFieldQuery('title', ['title'], dbcore.query.SubstringQuery) - self.assertEqual(self.lib.items(q).get().title, u'the title') + self.assertEqual(self.lib.items(q).get().title, 'the title') def test_restriction_soundness(self): - q = dbcore.query.AnyFieldQuery('title', [u'artist'], + q = dbcore.query.AnyFieldQuery('title', ['artist'], dbcore.query.SubstringQuery) self.assertEqual(self.lib.items(q).get(), None) def test_eq(self): - q1 = dbcore.query.AnyFieldQuery('foo', [u'bar'], + q1 = dbcore.query.AnyFieldQuery('foo', ['bar'], dbcore.query.SubstringQuery) - q2 = dbcore.query.AnyFieldQuery('foo', [u'bar'], + q2 = dbcore.query.AnyFieldQuery('foo', ['bar'], dbcore.query.SubstringQuery) self.assertEqual(q1, q2) @@ -77,34 +75,34 @@ class AnyFieldQueryTest(_common.LibTestCase): self.assertNotEqual(q1, q2) -class AssertsMixin(object): +class AssertsMixin: def assert_items_matched(self, results, titles): - self.assertEqual(set([i.title for i in results]), set(titles)) + self.assertEqual({i.title for i in results}, set(titles)) def assert_albums_matched(self, results, albums): - self.assertEqual(set([a.album for a in results]), set(albums)) + self.assertEqual({a.album for a in results}, set(albums)) # A test case class providing a library with some dummy data and some # assertions involving that data. class DummyDataTestCase(_common.TestCase, AssertsMixin): def setUp(self): - super(DummyDataTestCase, self).setUp() + super().setUp() self.lib = beets.library.Library(':memory:') items = [_common.item() for _ in range(3)] - items[0].title = u'foo bar' - items[0].artist = u'one' - items[0].album = u'baz' + items[0].title = 'foo bar' + items[0].artist = 'one' + items[0].album = 'baz' items[0].year = 2001 items[0].comp = True - items[1].title = u'baz qux' - items[1].artist = u'two' - items[1].album = u'baz' + items[1].title = 'baz qux' + items[1].artist = 'two' + items[1].album = 'baz' items[1].year = 2002 items[1].comp = True - items[2].title = u'beets 4 eva' - items[2].artist = u'three' - items[2].album = u'foo' + items[2].title = 'beets 4 eva' + items[2].artist = 'three' + items[2].album = 'foo' items[2].year = 2003 items[2].comp = False for item in items: @@ -113,15 +111,15 @@ class DummyDataTestCase(_common.TestCase, AssertsMixin): def assert_items_matched_all(self, results): self.assert_items_matched(results, [ - u'foo bar', - u'baz qux', - u'beets 4 eva', + 'foo bar', + 'baz qux', + 'beets 4 eva', ]) class GetTest(DummyDataTestCase): def test_get_empty(self): - q = u'' + q = '' results = self.lib.items(q) self.assert_items_matched_all(results) @@ -131,255 +129,255 @@ class GetTest(DummyDataTestCase): self.assert_items_matched_all(results) def test_get_one_keyed_term(self): - q = u'title:qux' + q = 'title:qux' results = self.lib.items(q) - self.assert_items_matched(results, [u'baz qux']) + self.assert_items_matched(results, ['baz qux']) def test_get_one_keyed_regexp(self): - q = u'artist::t.+r' + q = 'artist::t.+r' results = self.lib.items(q) - self.assert_items_matched(results, [u'beets 4 eva']) + self.assert_items_matched(results, ['beets 4 eva']) def test_get_one_unkeyed_term(self): - q = u'three' + q = 'three' results = self.lib.items(q) - self.assert_items_matched(results, [u'beets 4 eva']) + self.assert_items_matched(results, ['beets 4 eva']) def test_get_one_unkeyed_regexp(self): - q = u':x$' + q = ':x$' results = self.lib.items(q) - self.assert_items_matched(results, [u'baz qux']) + self.assert_items_matched(results, ['baz qux']) def test_get_no_matches(self): - q = u'popebear' + q = 'popebear' results = self.lib.items(q) self.assert_items_matched(results, []) def test_invalid_key(self): - q = u'pope:bear' + q = 'pope:bear' results = self.lib.items(q) # Matches nothing since the flexattr is not present on the # objects. self.assert_items_matched(results, []) def test_term_case_insensitive(self): - q = u'oNE' + q = 'oNE' results = self.lib.items(q) - self.assert_items_matched(results, [u'foo bar']) + self.assert_items_matched(results, ['foo bar']) def test_regexp_case_sensitive(self): - q = u':oNE' + q = ':oNE' results = self.lib.items(q) self.assert_items_matched(results, []) - q = u':one' + q = ':one' results = self.lib.items(q) - self.assert_items_matched(results, [u'foo bar']) + self.assert_items_matched(results, ['foo bar']) def test_term_case_insensitive_with_key(self): - q = u'artist:thrEE' + q = 'artist:thrEE' results = self.lib.items(q) - self.assert_items_matched(results, [u'beets 4 eva']) + self.assert_items_matched(results, ['beets 4 eva']) def test_key_case_insensitive(self): - q = u'ArTiST:three' + q = 'ArTiST:three' results = self.lib.items(q) - self.assert_items_matched(results, [u'beets 4 eva']) + self.assert_items_matched(results, ['beets 4 eva']) def test_unkeyed_term_matches_multiple_columns(self): - q = u'baz' + q = 'baz' results = self.lib.items(q) self.assert_items_matched(results, [ - u'foo bar', - u'baz qux', + 'foo bar', + 'baz qux', ]) def test_unkeyed_regexp_matches_multiple_columns(self): - q = u':z$' + q = ':z$' results = self.lib.items(q) self.assert_items_matched(results, [ - u'foo bar', - u'baz qux', + 'foo bar', + 'baz qux', ]) def test_keyed_term_matches_only_one_column(self): - q = u'title:baz' + q = 'title:baz' results = self.lib.items(q) - self.assert_items_matched(results, [u'baz qux']) + self.assert_items_matched(results, ['baz qux']) def test_keyed_regexp_matches_only_one_column(self): - q = u'title::baz' + q = 'title::baz' results = self.lib.items(q) self.assert_items_matched(results, [ - u'baz qux', + 'baz qux', ]) def test_multiple_terms_narrow_search(self): - q = u'qux baz' + q = 'qux baz' results = self.lib.items(q) self.assert_items_matched(results, [ - u'baz qux', + 'baz qux', ]) def test_multiple_regexps_narrow_search(self): - q = u':baz :qux' + q = ':baz :qux' results = self.lib.items(q) - self.assert_items_matched(results, [u'baz qux']) + self.assert_items_matched(results, ['baz qux']) def test_mixed_terms_regexps_narrow_search(self): - q = u':baz qux' + q = ':baz qux' results = self.lib.items(q) - self.assert_items_matched(results, [u'baz qux']) + self.assert_items_matched(results, ['baz qux']) def test_single_year(self): - q = u'year:2001' + q = 'year:2001' results = self.lib.items(q) - self.assert_items_matched(results, [u'foo bar']) + self.assert_items_matched(results, ['foo bar']) def test_year_range(self): - q = u'year:2000..2002' + q = 'year:2000..2002' results = self.lib.items(q) self.assert_items_matched(results, [ - u'foo bar', - u'baz qux', + 'foo bar', + 'baz qux', ]) def test_singleton_true(self): - q = u'singleton:true' + q = 'singleton:true' results = self.lib.items(q) - self.assert_items_matched(results, [u'beets 4 eva']) + self.assert_items_matched(results, ['beets 4 eva']) def test_singleton_false(self): - q = u'singleton:false' + q = 'singleton:false' results = self.lib.items(q) - self.assert_items_matched(results, [u'foo bar', u'baz qux']) + self.assert_items_matched(results, ['foo bar', 'baz qux']) def test_compilation_true(self): - q = u'comp:true' + q = 'comp:true' results = self.lib.items(q) - self.assert_items_matched(results, [u'foo bar', u'baz qux']) + self.assert_items_matched(results, ['foo bar', 'baz qux']) def test_compilation_false(self): - q = u'comp:false' + q = 'comp:false' results = self.lib.items(q) - self.assert_items_matched(results, [u'beets 4 eva']) + self.assert_items_matched(results, ['beets 4 eva']) def test_unknown_field_name_no_results(self): - q = u'xyzzy:nonsense' + q = 'xyzzy:nonsense' results = self.lib.items(q) titles = [i.title for i in results] self.assertEqual(titles, []) def test_unknown_field_name_no_results_in_album_query(self): - q = u'xyzzy:nonsense' + q = 'xyzzy:nonsense' results = self.lib.albums(q) names = [a.album for a in results] self.assertEqual(names, []) def test_item_field_name_matches_nothing_in_album_query(self): - q = u'format:nonsense' + q = 'format:nonsense' results = self.lib.albums(q) names = [a.album for a in results] self.assertEqual(names, []) def test_unicode_query(self): item = self.lib.items().get() - item.title = u'caf\xe9' + item.title = 'caf\xe9' item.store() - q = u'title:caf\xe9' + q = 'title:caf\xe9' results = self.lib.items(q) - self.assert_items_matched(results, [u'caf\xe9']) + self.assert_items_matched(results, ['caf\xe9']) def test_numeric_search_positive(self): - q = dbcore.query.NumericQuery('year', u'2001') + q = dbcore.query.NumericQuery('year', '2001') results = self.lib.items(q) self.assertTrue(results) def test_numeric_search_negative(self): - q = dbcore.query.NumericQuery('year', u'1999') + q = dbcore.query.NumericQuery('year', '1999') results = self.lib.items(q) self.assertFalse(results) def test_album_field_fallback(self): - self.album['albumflex'] = u'foo' + self.album['albumflex'] = 'foo' self.album.store() - q = u'albumflex:foo' + q = 'albumflex:foo' results = self.lib.items(q) self.assert_items_matched(results, [ - u'foo bar', - u'baz qux', + 'foo bar', + 'baz qux', ]) def test_invalid_query(self): with self.assertRaises(InvalidQueryArgumentValueError) as raised: - dbcore.query.NumericQuery('year', u'199a') - self.assertIn(u'not an int', six.text_type(raised.exception)) + dbcore.query.NumericQuery('year', '199a') + self.assertIn('not an int', str(raised.exception)) with self.assertRaises(InvalidQueryArgumentValueError) as raised: - dbcore.query.RegexpQuery('year', u'199(') - exception_text = six.text_type(raised.exception) - self.assertIn(u'not a regular expression', exception_text) + dbcore.query.RegexpQuery('year', '199(') + exception_text = str(raised.exception) + self.assertIn('not a regular expression', exception_text) if sys.version_info >= (3, 5): - self.assertIn(u'unterminated subpattern', exception_text) + self.assertIn('unterminated subpattern', exception_text) else: - self.assertIn(u'unbalanced parenthesis', exception_text) + self.assertIn('unbalanced parenthesis', exception_text) self.assertIsInstance(raised.exception, ParsingError) class MatchTest(_common.TestCase): def setUp(self): - super(MatchTest, self).setUp() + super().setUp() self.item = _common.item() def test_regex_match_positive(self): - q = dbcore.query.RegexpQuery('album', u'^the album$') + q = dbcore.query.RegexpQuery('album', '^the album$') self.assertTrue(q.match(self.item)) def test_regex_match_negative(self): - q = dbcore.query.RegexpQuery('album', u'^album$') + q = dbcore.query.RegexpQuery('album', '^album$') self.assertFalse(q.match(self.item)) def test_regex_match_non_string_value(self): - q = dbcore.query.RegexpQuery('disc', u'^6$') + q = dbcore.query.RegexpQuery('disc', '^6$') self.assertTrue(q.match(self.item)) def test_substring_match_positive(self): - q = dbcore.query.SubstringQuery('album', u'album') + q = dbcore.query.SubstringQuery('album', 'album') self.assertTrue(q.match(self.item)) def test_substring_match_negative(self): - q = dbcore.query.SubstringQuery('album', u'ablum') + q = dbcore.query.SubstringQuery('album', 'ablum') self.assertFalse(q.match(self.item)) def test_substring_match_non_string_value(self): - q = dbcore.query.SubstringQuery('disc', u'6') + q = dbcore.query.SubstringQuery('disc', '6') self.assertTrue(q.match(self.item)) def test_year_match_positive(self): - q = dbcore.query.NumericQuery('year', u'1') + q = dbcore.query.NumericQuery('year', '1') self.assertTrue(q.match(self.item)) def test_year_match_negative(self): - q = dbcore.query.NumericQuery('year', u'10') + q = dbcore.query.NumericQuery('year', '10') self.assertFalse(q.match(self.item)) def test_bitrate_range_positive(self): - q = dbcore.query.NumericQuery('bitrate', u'100000..200000') + q = dbcore.query.NumericQuery('bitrate', '100000..200000') self.assertTrue(q.match(self.item)) def test_bitrate_range_negative(self): - q = dbcore.query.NumericQuery('bitrate', u'200000..300000') + q = dbcore.query.NumericQuery('bitrate', '200000..300000') self.assertFalse(q.match(self.item)) def test_open_range(self): - dbcore.query.NumericQuery('bitrate', u'100000..') + dbcore.query.NumericQuery('bitrate', '100000..') def test_eq(self): - q1 = dbcore.query.MatchQuery('foo', u'bar') - q2 = dbcore.query.MatchQuery('foo', u'bar') - q3 = dbcore.query.MatchQuery('foo', u'baz') - q4 = dbcore.query.StringFieldQuery('foo', u'bar') + q1 = dbcore.query.MatchQuery('foo', 'bar') + q2 = dbcore.query.MatchQuery('foo', 'bar') + q3 = dbcore.query.MatchQuery('foo', 'baz') + q4 = dbcore.query.StringFieldQuery('foo', 'bar') self.assertEqual(q1, q2) self.assertNotEqual(q1, q3) self.assertNotEqual(q1, q4) @@ -388,12 +386,12 @@ class MatchTest(_common.TestCase): class PathQueryTest(_common.LibTestCase, TestHelper, AssertsMixin): def setUp(self): - super(PathQueryTest, self).setUp() + super().setUp() # This is the item we'll try to match. self.i.path = util.normpath('/a/b/c.mp3') - self.i.title = u'path item' - self.i.album = u'path album' + self.i.title = 'path item' + self.i.album = 'path album' self.i.store() self.lib.add_album([self.i]) @@ -418,39 +416,39 @@ class PathQueryTest(_common.LibTestCase, TestHelper, AssertsMixin): self.patcher_samefile.start().return_value = True def tearDown(self): - super(PathQueryTest, self).tearDown() + super().tearDown() self.patcher_samefile.stop() self.patcher_exists.stop() def test_path_exact_match(self): - q = u'path:/a/b/c.mp3' + q = 'path:/a/b/c.mp3' results = self.lib.items(q) - self.assert_items_matched(results, [u'path item']) + self.assert_items_matched(results, ['path item']) results = self.lib.albums(q) self.assert_albums_matched(results, []) @unittest.skipIf(sys.platform, 'win32') # FIXME: fails on windows def test_parent_directory_no_slash(self): - q = u'path:/a' + q = 'path:/a' results = self.lib.items(q) - self.assert_items_matched(results, [u'path item']) + self.assert_items_matched(results, ['path item']) results = self.lib.albums(q) - self.assert_albums_matched(results, [u'path album']) + self.assert_albums_matched(results, ['path album']) @unittest.skipIf(sys.platform, 'win32') # FIXME: fails on windows def test_parent_directory_with_slash(self): - q = u'path:/a/' + q = 'path:/a/' results = self.lib.items(q) - self.assert_items_matched(results, [u'path item']) + self.assert_items_matched(results, ['path item']) results = self.lib.albums(q) - self.assert_albums_matched(results, [u'path album']) + self.assert_albums_matched(results, ['path album']) def test_no_match(self): - q = u'path:/xyzzy/' + q = 'path:/xyzzy/' results = self.lib.items(q) self.assert_items_matched(results, []) @@ -458,7 +456,7 @@ class PathQueryTest(_common.LibTestCase, TestHelper, AssertsMixin): self.assert_albums_matched(results, []) def test_fragment_no_match(self): - q = u'path:/b/' + q = 'path:/b/' results = self.lib.items(q) self.assert_items_matched(results, []) @@ -466,20 +464,20 @@ class PathQueryTest(_common.LibTestCase, TestHelper, AssertsMixin): self.assert_albums_matched(results, []) def test_nonnorm_path(self): - q = u'path:/x/../a/b' + q = 'path:/x/../a/b' results = self.lib.items(q) - self.assert_items_matched(results, [u'path item']) + self.assert_items_matched(results, ['path item']) results = self.lib.albums(q) - self.assert_albums_matched(results, [u'path album']) + self.assert_albums_matched(results, ['path album']) def test_slashed_query_matches_path(self): - q = u'/a/b' + q = '/a/b' results = self.lib.items(q) - self.assert_items_matched(results, [u'path item']) + self.assert_items_matched(results, ['path item']) results = self.lib.albums(q) - self.assert_albums_matched(results, [u'path album']) + self.assert_albums_matched(results, ['path album']) @unittest.skip('unfixed (#1865)') def test_path_query_in_or_query(self): @@ -488,7 +486,7 @@ class PathQueryTest(_common.LibTestCase, TestHelper, AssertsMixin): self.assert_items_matched(results, ['path item']) def test_non_slashed_does_not_match_path(self): - q = u'c.mp3' + q = 'c.mp3' results = self.lib.items(q) self.assert_items_matched(results, []) @@ -496,60 +494,60 @@ class PathQueryTest(_common.LibTestCase, TestHelper, AssertsMixin): self.assert_albums_matched(results, []) def test_slashes_in_explicit_field_does_not_match_path(self): - q = u'title:/a/b' + q = 'title:/a/b' results = self.lib.items(q) self.assert_items_matched(results, []) def test_path_item_regex(self): - q = u'path::c\\.mp3$' + q = 'path::c\\.mp3$' results = self.lib.items(q) - self.assert_items_matched(results, [u'path item']) + self.assert_items_matched(results, ['path item']) def test_path_album_regex(self): - q = u'path::b' + q = 'path::b' results = self.lib.albums(q) - self.assert_albums_matched(results, [u'path album']) + self.assert_albums_matched(results, ['path album']) def test_escape_underscore(self): - self.add_album(path=b'/a/_/title.mp3', title=u'with underscore', - album=u'album with underscore') - q = u'path:/a/_' + self.add_album(path=b'/a/_/title.mp3', title='with underscore', + album='album with underscore') + q = 'path:/a/_' results = self.lib.items(q) - self.assert_items_matched(results, [u'with underscore']) + self.assert_items_matched(results, ['with underscore']) results = self.lib.albums(q) - self.assert_albums_matched(results, [u'album with underscore']) + self.assert_albums_matched(results, ['album with underscore']) def test_escape_percent(self): - self.add_album(path=b'/a/%/title.mp3', title=u'with percent', - album=u'album with percent') - q = u'path:/a/%' + self.add_album(path=b'/a/%/title.mp3', title='with percent', + album='album with percent') + q = 'path:/a/%' results = self.lib.items(q) - self.assert_items_matched(results, [u'with percent']) + self.assert_items_matched(results, ['with percent']) results = self.lib.albums(q) - self.assert_albums_matched(results, [u'album with percent']) + self.assert_albums_matched(results, ['album with percent']) def test_escape_backslash(self): - self.add_album(path=br'/a/\x/title.mp3', title=u'with backslash', - album=u'album with backslash') - q = u'path:/a/\\\\x' + self.add_album(path=br'/a/\x/title.mp3', title='with backslash', + album='album with backslash') + q = 'path:/a/\\\\x' results = self.lib.items(q) - self.assert_items_matched(results, [u'with backslash']) + self.assert_items_matched(results, ['with backslash']) results = self.lib.albums(q) - self.assert_albums_matched(results, [u'album with backslash']) + self.assert_albums_matched(results, ['album with backslash']) def test_case_sensitivity(self): - self.add_album(path=b'/A/B/C2.mp3', title=u'caps path') + self.add_album(path=b'/A/B/C2.mp3', title='caps path') - makeq = partial(beets.library.PathQuery, u'path', '/A/B') + makeq = partial(beets.library.PathQuery, 'path', '/A/B') results = self.lib.items(makeq(case_sensitive=True)) - self.assert_items_matched(results, [u'caps path']) + self.assert_items_matched(results, ['caps path']) results = self.lib.items(makeq(case_sensitive=False)) - self.assert_items_matched(results, [u'path item', u'caps path']) + self.assert_items_matched(results, ['path item', 'caps path']) # Check for correct case sensitivity selection (this check # only works on non-Windows OSes). @@ -624,7 +622,7 @@ class PathQueryTest(_common.LibTestCase, TestHelper, AssertsMixin): self.assertTrue(is_path(parent)) # Some non-existent path. - self.assertFalse(is_path(path + u'baz')) + self.assertFalse(is_path(path + 'baz')) finally: # Restart the `os.path.exists` patch. @@ -641,10 +639,10 @@ class PathQueryTest(_common.LibTestCase, TestHelper, AssertsMixin): cur_dir = os.getcwd() try: os.chdir(self.temp_dir) - self.assertTrue(is_path(u'foo/')) - self.assertTrue(is_path(u'foo/bar')) - self.assertTrue(is_path(u'foo/bar:tagada')) - self.assertFalse(is_path(u'bar')) + self.assertTrue(is_path('foo/')) + self.assertTrue(is_path('foo/bar')) + self.assertTrue(is_path('foo/bar:tagada')) + self.assertFalse(is_path('bar')) finally: os.chdir(cur_dir) @@ -662,32 +660,32 @@ class IntQueryTest(unittest.TestCase, TestHelper): def test_exact_value_match(self): item = self.add_item(bpm=120) - matched = self.lib.items(u'bpm:120').get() + matched = self.lib.items('bpm:120').get() self.assertEqual(item.id, matched.id) def test_range_match(self): item = self.add_item(bpm=120) self.add_item(bpm=130) - matched = self.lib.items(u'bpm:110..125') + matched = self.lib.items('bpm:110..125') self.assertEqual(1, len(matched)) self.assertEqual(item.id, matched.get().id) def test_flex_range_match(self): Item._types = {'myint': types.Integer()} item = self.add_item(myint=2) - matched = self.lib.items(u'myint:2').get() + matched = self.lib.items('myint:2').get() self.assertEqual(item.id, matched.id) def test_flex_dont_match_missing(self): Item._types = {'myint': types.Integer()} self.add_item() - matched = self.lib.items(u'myint:2').get() + matched = self.lib.items('myint:2').get() self.assertIsNone(matched) def test_no_substring_match(self): self.add_item(bpm=120) - matched = self.lib.items(u'bpm:12').get() + matched = self.lib.items('bpm:12').get() self.assertIsNone(matched) @@ -703,35 +701,35 @@ class BoolQueryTest(unittest.TestCase, TestHelper): def test_parse_true(self): item_true = self.add_item(comp=True) item_false = self.add_item(comp=False) - matched = self.lib.items(u'comp:true') + matched = self.lib.items('comp:true') self.assertInResult(item_true, matched) self.assertNotInResult(item_false, matched) def test_flex_parse_true(self): item_true = self.add_item(flexbool=True) item_false = self.add_item(flexbool=False) - matched = self.lib.items(u'flexbool:true') + matched = self.lib.items('flexbool:true') self.assertInResult(item_true, matched) self.assertNotInResult(item_false, matched) def test_flex_parse_false(self): item_true = self.add_item(flexbool=True) item_false = self.add_item(flexbool=False) - matched = self.lib.items(u'flexbool:false') + matched = self.lib.items('flexbool:false') self.assertInResult(item_false, matched) self.assertNotInResult(item_true, matched) def test_flex_parse_1(self): item_true = self.add_item(flexbool=True) item_false = self.add_item(flexbool=False) - matched = self.lib.items(u'flexbool:1') + matched = self.lib.items('flexbool:1') self.assertInResult(item_true, matched) self.assertNotInResult(item_false, matched) def test_flex_parse_0(self): item_true = self.add_item(flexbool=True) item_false = self.add_item(flexbool=False) - matched = self.lib.items(u'flexbool:0') + matched = self.lib.items('flexbool:0') self.assertInResult(item_false, matched) self.assertNotInResult(item_true, matched) @@ -739,26 +737,26 @@ class BoolQueryTest(unittest.TestCase, TestHelper): # TODO this should be the other way around item_true = self.add_item(flexbool=True) item_false = self.add_item(flexbool=False) - matched = self.lib.items(u'flexbool:something') + matched = self.lib.items('flexbool:something') self.assertInResult(item_false, matched) self.assertNotInResult(item_true, matched) class DefaultSearchFieldsTest(DummyDataTestCase): def test_albums_matches_album(self): - albums = list(self.lib.albums(u'baz')) + albums = list(self.lib.albums('baz')) self.assertEqual(len(albums), 1) def test_albums_matches_albumartist(self): - albums = list(self.lib.albums([u'album artist'])) + albums = list(self.lib.albums(['album artist'])) self.assertEqual(len(albums), 1) def test_items_matches_title(self): - items = self.lib.items(u'beets') - self.assert_items_matched(items, [u'beets 4 eva']) + items = self.lib.items('beets') + self.assert_items_matched(items, ['beets 4 eva']) def test_items_does_not_match_year(self): - items = self.lib.items(u'2001') + items = self.lib.items('2001') self.assert_items_matched(items, []) @@ -771,33 +769,33 @@ class NoneQueryTest(unittest.TestCase, TestHelper): singleton = self.add_item() album_item = self.add_album().items().get() - matched = self.lib.items(NoneQuery(u'album_id')) + matched = self.lib.items(NoneQuery('album_id')) self.assertInResult(singleton, matched) self.assertNotInResult(album_item, matched) def test_match_after_set_none(self): item = self.add_item(rg_track_gain=0) - matched = self.lib.items(NoneQuery(u'rg_track_gain')) + matched = self.lib.items(NoneQuery('rg_track_gain')) self.assertNotInResult(item, matched) item['rg_track_gain'] = None item.store() - matched = self.lib.items(NoneQuery(u'rg_track_gain')) + matched = self.lib.items(NoneQuery('rg_track_gain')) self.assertInResult(item, matched) def test_match_slow(self): item = self.add_item() - matched = self.lib.items(NoneQuery(u'rg_track_peak', fast=False)) + matched = self.lib.items(NoneQuery('rg_track_peak', fast=False)) self.assertInResult(item, matched) def test_match_slow_after_set_none(self): item = self.add_item(rg_track_gain=0) - matched = self.lib.items(NoneQuery(u'rg_track_gain', fast=False)) + matched = self.lib.items(NoneQuery('rg_track_gain', fast=False)) self.assertNotInResult(item, matched) item['rg_track_gain'] = None item.store() - matched = self.lib.items(NoneQuery(u'rg_track_gain', fast=False)) + matched = self.lib.items(NoneQuery('rg_track_gain', fast=False)) self.assertInResult(item, matched) @@ -807,61 +805,61 @@ class NotQueryMatchTest(_common.TestCase): queries (ie. assertTrue(q) -> assertFalse(NotQuery(q))). """ def setUp(self): - super(NotQueryMatchTest, self).setUp() + super().setUp() self.item = _common.item() def test_regex_match_positive(self): - q = dbcore.query.RegexpQuery(u'album', u'^the album$') + q = dbcore.query.RegexpQuery('album', '^the album$') self.assertTrue(q.match(self.item)) self.assertFalse(dbcore.query.NotQuery(q).match(self.item)) def test_regex_match_negative(self): - q = dbcore.query.RegexpQuery(u'album', u'^album$') + q = dbcore.query.RegexpQuery('album', '^album$') self.assertFalse(q.match(self.item)) self.assertTrue(dbcore.query.NotQuery(q).match(self.item)) def test_regex_match_non_string_value(self): - q = dbcore.query.RegexpQuery(u'disc', u'^6$') + q = dbcore.query.RegexpQuery('disc', '^6$') self.assertTrue(q.match(self.item)) self.assertFalse(dbcore.query.NotQuery(q).match(self.item)) def test_substring_match_positive(self): - q = dbcore.query.SubstringQuery(u'album', u'album') + q = dbcore.query.SubstringQuery('album', 'album') self.assertTrue(q.match(self.item)) self.assertFalse(dbcore.query.NotQuery(q).match(self.item)) def test_substring_match_negative(self): - q = dbcore.query.SubstringQuery(u'album', u'ablum') + q = dbcore.query.SubstringQuery('album', 'ablum') self.assertFalse(q.match(self.item)) self.assertTrue(dbcore.query.NotQuery(q).match(self.item)) def test_substring_match_non_string_value(self): - q = dbcore.query.SubstringQuery(u'disc', u'6') + q = dbcore.query.SubstringQuery('disc', '6') self.assertTrue(q.match(self.item)) self.assertFalse(dbcore.query.NotQuery(q).match(self.item)) def test_year_match_positive(self): - q = dbcore.query.NumericQuery(u'year', u'1') + q = dbcore.query.NumericQuery('year', '1') self.assertTrue(q.match(self.item)) self.assertFalse(dbcore.query.NotQuery(q).match(self.item)) def test_year_match_negative(self): - q = dbcore.query.NumericQuery(u'year', u'10') + q = dbcore.query.NumericQuery('year', '10') self.assertFalse(q.match(self.item)) self.assertTrue(dbcore.query.NotQuery(q).match(self.item)) def test_bitrate_range_positive(self): - q = dbcore.query.NumericQuery(u'bitrate', u'100000..200000') + q = dbcore.query.NumericQuery('bitrate', '100000..200000') self.assertTrue(q.match(self.item)) self.assertFalse(dbcore.query.NotQuery(q).match(self.item)) def test_bitrate_range_negative(self): - q = dbcore.query.NumericQuery(u'bitrate', u'200000..300000') + q = dbcore.query.NumericQuery('bitrate', '200000..300000') self.assertFalse(q.match(self.item)) self.assertTrue(dbcore.query.NotQuery(q).match(self.item)) def test_open_range(self): - q = dbcore.query.NumericQuery(u'bitrate', u'100000..') + q = dbcore.query.NumericQuery('bitrate', '100000..') dbcore.query.NotQuery(q) @@ -884,47 +882,47 @@ class NotQueryTest(DummyDataTestCase): self.assert_items_matched(self.lib.items(q_and), []) # assert manually checking the item titles - all_titles = set([i.title for i in self.lib.items()]) - q_results = set([i.title for i in self.lib.items(q)]) - not_q_results = set([i.title for i in self.lib.items(not_q)]) + all_titles = {i.title for i in self.lib.items()} + q_results = {i.title for i in self.lib.items(q)} + not_q_results = {i.title for i in self.lib.items(not_q)} self.assertEqual(q_results.union(not_q_results), all_titles) self.assertEqual(q_results.intersection(not_q_results), set()) # round trip not_not_q = dbcore.query.NotQuery(not_q) - self.assertEqual(set([i.title for i in self.lib.items(q)]), - set([i.title for i in self.lib.items(not_not_q)])) + self.assertEqual({i.title for i in self.lib.items(q)}, + {i.title for i in self.lib.items(not_not_q)}) def test_type_and(self): # not(a and b) <-> not(a) or not(b) q = dbcore.query.AndQuery([ - dbcore.query.BooleanQuery(u'comp', True), - dbcore.query.NumericQuery(u'year', u'2002')], + dbcore.query.BooleanQuery('comp', True), + dbcore.query.NumericQuery('year', '2002')], ) not_results = self.lib.items(dbcore.query.NotQuery(q)) - self.assert_items_matched(not_results, [u'foo bar', u'beets 4 eva']) + self.assert_items_matched(not_results, ['foo bar', 'beets 4 eva']) self.assertNegationProperties(q) def test_type_anyfield(self): - q = dbcore.query.AnyFieldQuery(u'foo', [u'title', u'artist', u'album'], + q = dbcore.query.AnyFieldQuery('foo', ['title', 'artist', 'album'], dbcore.query.SubstringQuery) not_results = self.lib.items(dbcore.query.NotQuery(q)) - self.assert_items_matched(not_results, [u'baz qux']) + self.assert_items_matched(not_results, ['baz qux']) self.assertNegationProperties(q) def test_type_boolean(self): - q = dbcore.query.BooleanQuery(u'comp', True) + q = dbcore.query.BooleanQuery('comp', True) not_results = self.lib.items(dbcore.query.NotQuery(q)) - self.assert_items_matched(not_results, [u'beets 4 eva']) + self.assert_items_matched(not_results, ['beets 4 eva']) self.assertNegationProperties(q) def test_type_date(self): - q = dbcore.query.DateQuery(u'added', u'2000-01-01') + q = dbcore.query.DateQuery('added', '2000-01-01') not_results = self.lib.items(dbcore.query.NotQuery(q)) # query date is in the past, thus the 'not' results should contain all # items - self.assert_items_matched(not_results, [u'foo bar', u'baz qux', - u'beets 4 eva']) + self.assert_items_matched(not_results, ['foo bar', 'baz qux', + 'beets 4 eva']) self.assertNegationProperties(q) def test_type_false(self): @@ -934,41 +932,41 @@ class NotQueryTest(DummyDataTestCase): self.assertNegationProperties(q) def test_type_match(self): - q = dbcore.query.MatchQuery(u'year', u'2003') + q = dbcore.query.MatchQuery('year', '2003') not_results = self.lib.items(dbcore.query.NotQuery(q)) - self.assert_items_matched(not_results, [u'foo bar', u'baz qux']) + self.assert_items_matched(not_results, ['foo bar', 'baz qux']) self.assertNegationProperties(q) def test_type_none(self): - q = dbcore.query.NoneQuery(u'rg_track_gain') + q = dbcore.query.NoneQuery('rg_track_gain') not_results = self.lib.items(dbcore.query.NotQuery(q)) self.assert_items_matched(not_results, []) self.assertNegationProperties(q) def test_type_numeric(self): - q = dbcore.query.NumericQuery(u'year', u'2001..2002') + q = dbcore.query.NumericQuery('year', '2001..2002') not_results = self.lib.items(dbcore.query.NotQuery(q)) - self.assert_items_matched(not_results, [u'beets 4 eva']) + self.assert_items_matched(not_results, ['beets 4 eva']) self.assertNegationProperties(q) def test_type_or(self): # not(a or b) <-> not(a) and not(b) - q = dbcore.query.OrQuery([dbcore.query.BooleanQuery(u'comp', True), - dbcore.query.NumericQuery(u'year', u'2002')]) + q = dbcore.query.OrQuery([dbcore.query.BooleanQuery('comp', True), + dbcore.query.NumericQuery('year', '2002')]) not_results = self.lib.items(dbcore.query.NotQuery(q)) - self.assert_items_matched(not_results, [u'beets 4 eva']) + self.assert_items_matched(not_results, ['beets 4 eva']) self.assertNegationProperties(q) def test_type_regexp(self): - q = dbcore.query.RegexpQuery(u'artist', u'^t') + q = dbcore.query.RegexpQuery('artist', '^t') not_results = self.lib.items(dbcore.query.NotQuery(q)) - self.assert_items_matched(not_results, [u'foo bar']) + self.assert_items_matched(not_results, ['foo bar']) self.assertNegationProperties(q) def test_type_substring(self): - q = dbcore.query.SubstringQuery(u'album', u'ba') + q = dbcore.query.SubstringQuery('album', 'ba') not_results = self.lib.items(dbcore.query.NotQuery(q)) - self.assert_items_matched(not_results, [u'beets 4 eva']) + self.assert_items_matched(not_results, ['beets 4 eva']) self.assertNegationProperties(q) def test_type_true(self): @@ -979,41 +977,41 @@ class NotQueryTest(DummyDataTestCase): def test_get_prefixes_keyed(self): """Test both negation prefixes on a keyed query.""" - q0 = u'-title:qux' - q1 = u'^title:qux' + q0 = '-title:qux' + q1 = '^title:qux' results0 = self.lib.items(q0) results1 = self.lib.items(q1) - self.assert_items_matched(results0, [u'foo bar', u'beets 4 eva']) - self.assert_items_matched(results1, [u'foo bar', u'beets 4 eva']) + self.assert_items_matched(results0, ['foo bar', 'beets 4 eva']) + self.assert_items_matched(results1, ['foo bar', 'beets 4 eva']) def test_get_prefixes_unkeyed(self): """Test both negation prefixes on an unkeyed query.""" - q0 = u'-qux' - q1 = u'^qux' + q0 = '-qux' + q1 = '^qux' results0 = self.lib.items(q0) results1 = self.lib.items(q1) - self.assert_items_matched(results0, [u'foo bar', u'beets 4 eva']) - self.assert_items_matched(results1, [u'foo bar', u'beets 4 eva']) + self.assert_items_matched(results0, ['foo bar', 'beets 4 eva']) + self.assert_items_matched(results1, ['foo bar', 'beets 4 eva']) def test_get_one_keyed_regexp(self): - q = u'-artist::t.+r' + q = '-artist::t.+r' results = self.lib.items(q) - self.assert_items_matched(results, [u'foo bar', u'baz qux']) + self.assert_items_matched(results, ['foo bar', 'baz qux']) def test_get_one_unkeyed_regexp(self): - q = u'-:x$' + q = '-:x$' results = self.lib.items(q) - self.assert_items_matched(results, [u'foo bar', u'beets 4 eva']) + self.assert_items_matched(results, ['foo bar', 'beets 4 eva']) def test_get_multiple_terms(self): - q = u'baz -bar' + q = 'baz -bar' results = self.lib.items(q) - self.assert_items_matched(results, [u'baz qux']) + self.assert_items_matched(results, ['baz qux']) def test_get_mixed_terms(self): - q = u'baz -title:bar' + q = 'baz -title:bar' results = self.lib.items(q) - self.assert_items_matched(results, [u'baz qux']) + self.assert_items_matched(results, ['baz qux']) def test_fast_vs_slow(self): """Test that the results are the same regardless of the `fast` flag @@ -1023,13 +1021,13 @@ class NotQueryTest(DummyDataTestCase): AttributeError: type object 'NoneQuery' has no attribute 'field' at NoneQuery.match() (due to being @classmethod, and no self?) """ - classes = [(dbcore.query.DateQuery, [u'added', u'2001-01-01']), - (dbcore.query.MatchQuery, [u'artist', u'one']), + classes = [(dbcore.query.DateQuery, ['added', '2001-01-01']), + (dbcore.query.MatchQuery, ['artist', 'one']), # (dbcore.query.NoneQuery, ['rg_track_gain']), - (dbcore.query.NumericQuery, [u'year', u'2002']), - (dbcore.query.StringFieldQuery, [u'year', u'2001']), - (dbcore.query.RegexpQuery, [u'album', u'^.a']), - (dbcore.query.SubstringQuery, [u'title', u'x'])] + (dbcore.query.NumericQuery, ['year', '2002']), + (dbcore.query.StringFieldQuery, ['year', '2001']), + (dbcore.query.RegexpQuery, ['album', '^.a']), + (dbcore.query.SubstringQuery, ['title', 'x'])] for klass, args in classes: q_fast = dbcore.query.NotQuery(klass(*(args + [True]))) diff --git a/test/test_random.py b/test/test_random.py index 4f243efd7..4c63f4587 100644 --- a/test/test_random.py +++ b/test/test_random.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2019, Carl Suster # @@ -16,7 +15,6 @@ """Test the beets.random utilities associated with the random plugin. """ -from __future__ import division, absolute_import, print_function import unittest from test.helper import TestHelper diff --git a/test/test_replaygain.py b/test/test_replaygain.py index 53daafbb9..a2fdf5dbd 100644 --- a/test/test_replaygain.py +++ b/test/test_replaygain.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Thomas Scholtes # @@ -14,7 +13,6 @@ # included in all copies or substantial portions of the Software. -from __future__ import division, absolute_import, print_function import six import unittest @@ -70,7 +68,7 @@ class ReplayGainCliTestBase(TestHelper): # teardown operations may fail. In particular # {Item,Album} # may not have the _original_types attribute in unload_plugins pass - six.reraise(exc_info[1], None, exc_info[2]) + raise None.with_traceback(exc_info[2]) album = self.add_album_fixture(2) for item in album.items(): @@ -104,7 +102,7 @@ class ReplayGainCliTestBase(TestHelper): # that it could only happen if the decoder plugins are missing. if all(i.rg_track_peak is None and i.rg_track_gain is None for i in self.lib.items()): - self.skipTest(u'decoder plugins could not be loaded.') + self.skipTest('decoder plugins could not be loaded.') for item in self.lib.items(): self.assertIsNotNone(item.rg_track_peak) @@ -116,11 +114,11 @@ class ReplayGainCliTestBase(TestHelper): mediafile.rg_track_gain, item.rg_track_gain, places=2) def test_cli_skips_calculated_tracks(self): - self.run_command(u'replaygain') + self.run_command('replaygain') item = self.lib.items()[0] peak = item.rg_track_peak item.rg_track_gain = 0.0 - self.run_command(u'replaygain') + self.run_command('replaygain') self.assertEqual(item.rg_track_gain, 0.0) self.assertEqual(item.rg_track_peak, peak) @@ -130,7 +128,7 @@ class ReplayGainCliTestBase(TestHelper): self.assertIsNone(mediafile.rg_album_peak) self.assertIsNone(mediafile.rg_album_gain) - self.run_command(u'replaygain', u'-a') + self.run_command('replaygain', '-a') peaks = [] gains = [] @@ -155,7 +153,7 @@ class ReplayGainCliTestBase(TestHelper): for item in album.items(): self._reset_replaygain(item) - self.run_command(u'replaygain', u'-a') + self.run_command('replaygain', '-a') for item in album.items(): mediafile = MediaFile(item.path) @@ -172,7 +170,7 @@ class ReplayGainCliTestBase(TestHelper): def analyse(target_level): self.config['replaygain']['targetlevel'] = target_level self._reset_replaygain(item) - self.run_command(u'replaygain', '-f') + self.run_command('replaygain', '-f') mediafile = MediaFile(item.path) return mediafile.rg_track_gain @@ -186,9 +184,9 @@ class ReplayGainCliTestBase(TestHelper): self.assertNotEqual(gain_relative_to_84, gain_relative_to_89) -@unittest.skipIf(not GST_AVAILABLE, u'gstreamer cannot be found') +@unittest.skipIf(not GST_AVAILABLE, 'gstreamer cannot be found') class ReplayGainGstCliTest(ReplayGainCliTestBase, unittest.TestCase): - backend = u'gstreamer' + backend = 'gstreamer' def setUp(self): try: @@ -200,17 +198,17 @@ class ReplayGainGstCliTest(ReplayGainCliTestBase, unittest.TestCase): # Skip the test if plugins could not be loaded. self.skipTest(str(e)) - super(ReplayGainGstCliTest, self).setUp() + super().setUp() -@unittest.skipIf(not GAIN_PROG_AVAILABLE, u'no *gain command found') +@unittest.skipIf(not GAIN_PROG_AVAILABLE, 'no *gain command found') class ReplayGainCmdCliTest(ReplayGainCliTestBase, unittest.TestCase): - backend = u'command' + backend = 'command' -@unittest.skipIf(not FFMPEG_AVAILABLE, u'ffmpeg cannot be found') +@unittest.skipIf(not FFMPEG_AVAILABLE, 'ffmpeg cannot be found') class ReplayGainFfmpegTest(ReplayGainCliTestBase, unittest.TestCase): - backend = u'ffmpeg' + backend = 'ffmpeg' def suite(): diff --git a/test/test_smartplaylist.py b/test/test_smartplaylist.py index af60501a4..b1fc11108 100644 --- a/test/test_smartplaylist.py +++ b/test/test_smartplaylist.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Bruno Cauet. # @@ -13,14 +12,13 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -from __future__ import division, absolute_import, print_function from os import path, remove from tempfile import mkdtemp from shutil import rmtree import unittest -from mock import Mock, MagicMock +from unittest.mock import Mock, MagicMock from beetsplug.smartplaylist import SmartPlaylistPlugin from beets.library import Item, Album, parse_query_string @@ -45,58 +43,58 @@ class SmartPlaylistTest(unittest.TestCase): self.assertEqual(spl._unmatched_playlists, set()) config['smartplaylist']['playlists'].set([ - {'name': u'foo', - 'query': u'FOO foo'}, - {'name': u'bar', - 'album_query': [u'BAR bar1', u'BAR bar2']}, - {'name': u'baz', - 'query': u'BAZ baz', - 'album_query': u'BAZ baz'} + {'name': 'foo', + 'query': 'FOO foo'}, + {'name': 'bar', + 'album_query': ['BAR bar1', 'BAR bar2']}, + {'name': 'baz', + 'query': 'BAZ baz', + 'album_query': 'BAZ baz'} ]) spl.build_queries() self.assertEqual(spl._matched_playlists, set()) - foo_foo = parse_query_string(u'FOO foo', Item) - baz_baz = parse_query_string(u'BAZ baz', Item) - baz_baz2 = parse_query_string(u'BAZ baz', Album) - bar_bar = OrQuery((parse_query_string(u'BAR bar1', Album)[0], - parse_query_string(u'BAR bar2', Album)[0])) - self.assertEqual(spl._unmatched_playlists, set([ - (u'foo', foo_foo, (None, None)), - (u'baz', baz_baz, baz_baz2), - (u'bar', (None, None), (bar_bar, None)), - ])) + foo_foo = parse_query_string('FOO foo', Item) + baz_baz = parse_query_string('BAZ baz', Item) + baz_baz2 = parse_query_string('BAZ baz', Album) + bar_bar = OrQuery((parse_query_string('BAR bar1', Album)[0], + parse_query_string('BAR bar2', Album)[0])) + self.assertEqual(spl._unmatched_playlists, { + ('foo', foo_foo, (None, None)), + ('baz', baz_baz, baz_baz2), + ('bar', (None, None), (bar_bar, None)), + }) def test_build_queries_with_sorts(self): spl = SmartPlaylistPlugin() config['smartplaylist']['playlists'].set([ - {'name': u'no_sort', - 'query': u'foo'}, - {'name': u'one_sort', - 'query': u'foo year+'}, - {'name': u'only_empty_sorts', - 'query': [u'foo', u'bar']}, - {'name': u'one_non_empty_sort', - 'query': [u'foo year+', u'bar']}, - {'name': u'multiple_sorts', - 'query': [u'foo year+', u'bar genre-']}, - {'name': u'mixed', - 'query': [u'foo year+', u'bar', u'baz genre+ id-']} + {'name': 'no_sort', + 'query': 'foo'}, + {'name': 'one_sort', + 'query': 'foo year+'}, + {'name': 'only_empty_sorts', + 'query': ['foo', 'bar']}, + {'name': 'one_non_empty_sort', + 'query': ['foo year+', 'bar']}, + {'name': 'multiple_sorts', + 'query': ['foo year+', 'bar genre-']}, + {'name': 'mixed', + 'query': ['foo year+', 'bar', 'baz genre+ id-']} ]) spl.build_queries() - sorts = dict((name, sort) - for name, (_, sort), _ in spl._unmatched_playlists) + sorts = {name: sort + for name, (_, sort), _ in spl._unmatched_playlists} asseq = self.assertEqual # less cluttered code sort = FixedFieldSort # short cut since we're only dealing with this asseq(sorts["no_sort"], NullSort()) - asseq(sorts["one_sort"], sort(u'year')) + asseq(sorts["one_sort"], sort('year')) asseq(sorts["only_empty_sorts"], None) - asseq(sorts["one_non_empty_sort"], sort(u'year')) + asseq(sorts["one_non_empty_sort"], sort('year')) asseq(sorts["multiple_sorts"], - MultipleSort([sort('year'), sort(u'genre', False)])) + MultipleSort([sort('year'), sort('genre', False)])) asseq(sorts["mixed"], - MultipleSort([sort('year'), sort(u'genre'), sort(u'id', False)])) + MultipleSort([sort('year'), sort('genre'), sort('id', False)])) def test_matches(self): spl = SmartPlaylistPlugin() @@ -124,27 +122,27 @@ class SmartPlaylistTest(unittest.TestCase): spl = SmartPlaylistPlugin() nones = None, None - pl1 = '1', (u'q1', None), nones - pl2 = '2', (u'q2', None), nones - pl3 = '3', (u'q3', None), nones + pl1 = '1', ('q1', None), nones + pl2 = '2', ('q2', None), nones + pl3 = '3', ('q3', None), nones - spl._unmatched_playlists = set([pl1, pl2, pl3]) + spl._unmatched_playlists = {pl1, pl2, pl3} spl._matched_playlists = set() spl.matches = Mock(return_value=False) - spl.db_change(None, u"nothing") - self.assertEqual(spl._unmatched_playlists, set([pl1, pl2, pl3])) + spl.db_change(None, "nothing") + self.assertEqual(spl._unmatched_playlists, {pl1, pl2, pl3}) self.assertEqual(spl._matched_playlists, set()) - spl.matches.side_effect = lambda _, q, __: q == u'q3' - spl.db_change(None, u"matches 3") - self.assertEqual(spl._unmatched_playlists, set([pl1, pl2])) - self.assertEqual(spl._matched_playlists, set([pl3])) + spl.matches.side_effect = lambda _, q, __: q == 'q3' + spl.db_change(None, "matches 3") + self.assertEqual(spl._unmatched_playlists, {pl1, pl2}) + self.assertEqual(spl._matched_playlists, {pl3}) - spl.matches.side_effect = lambda _, q, __: q == u'q1' - spl.db_change(None, u"matches 3") - self.assertEqual(spl._matched_playlists, set([pl1, pl3])) - self.assertEqual(spl._unmatched_playlists, set([pl2])) + spl.matches.side_effect = lambda _, q, __: q == 'q1' + spl.db_change(None, "matches 3") + self.assertEqual(spl._matched_playlists, {pl1, pl3}) + self.assertEqual(spl._unmatched_playlists, {pl2}) def test_playlist_update(self): spl = SmartPlaylistPlugin() @@ -193,7 +191,7 @@ class SmartPlaylistCLITest(unittest.TestCase, TestHelper): {'name': 'my_playlist.m3u', 'query': self.item.title}, {'name': 'all.m3u', - 'query': u''} + 'query': ''} ]) config['smartplaylist']['playlist_dir'].set(py3_path(self.temp_dir)) self.load_plugins('smartplaylist') @@ -204,21 +202,21 @@ class SmartPlaylistCLITest(unittest.TestCase, TestHelper): def test_splupdate(self): with self.assertRaises(UserError): - self.run_with_output(u'splupdate', u'tagada') + self.run_with_output('splupdate', 'tagada') - self.run_with_output(u'splupdate', u'my_playlist') + self.run_with_output('splupdate', 'my_playlist') m3u_path = path.join(self.temp_dir, b'my_playlist.m3u') self.assertTrue(path.exists(m3u_path)) with open(m3u_path, 'rb') as f: self.assertEqual(f.read(), self.item.path + b"\n") remove(m3u_path) - self.run_with_output(u'splupdate', u'my_playlist.m3u') + self.run_with_output('splupdate', 'my_playlist.m3u') with open(m3u_path, 'rb') as f: self.assertEqual(f.read(), self.item.path + b"\n") remove(m3u_path) - self.run_with_output(u'splupdate') + self.run_with_output('splupdate') for name in (b'my_playlist.m3u', b'all.m3u'): with open(path.join(self.temp_dir, name), 'rb') as f: self.assertEqual(f.read(), self.item.path + b"\n") diff --git a/test/test_sort.py b/test/test_sort.py index d611ff2d6..70b1bc93e 100644 --- a/test/test_sort.py +++ b/test/test_sort.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -15,7 +14,6 @@ """Various tests for querying the library database. """ -from __future__ import division, absolute_import, print_function import unittest from test import _common @@ -28,75 +26,75 @@ from beets import config # assertions involving that data. class DummyDataTestCase(_common.TestCase): def setUp(self): - super(DummyDataTestCase, self).setUp() + super().setUp() self.lib = beets.library.Library(':memory:') albums = [_common.album() for _ in range(3)] - albums[0].album = u"Album A" - albums[0].genre = u"Rock" + albums[0].album = "Album A" + albums[0].genre = "Rock" albums[0].year = 2001 - albums[0].flex1 = u"Flex1-1" - albums[0].flex2 = u"Flex2-A" - albums[0].albumartist = u"Foo" + albums[0].flex1 = "Flex1-1" + albums[0].flex2 = "Flex2-A" + albums[0].albumartist = "Foo" albums[0].albumartist_sort = None - albums[1].album = u"Album B" - albums[1].genre = u"Rock" + albums[1].album = "Album B" + albums[1].genre = "Rock" albums[1].year = 2001 - albums[1].flex1 = u"Flex1-2" - albums[1].flex2 = u"Flex2-A" - albums[1].albumartist = u"Bar" + albums[1].flex1 = "Flex1-2" + albums[1].flex2 = "Flex2-A" + albums[1].albumartist = "Bar" albums[1].albumartist_sort = None - albums[2].album = u"Album C" - albums[2].genre = u"Jazz" + albums[2].album = "Album C" + albums[2].genre = "Jazz" albums[2].year = 2005 - albums[2].flex1 = u"Flex1-1" - albums[2].flex2 = u"Flex2-B" - albums[2].albumartist = u"Baz" + albums[2].flex1 = "Flex1-1" + albums[2].flex2 = "Flex2-B" + albums[2].albumartist = "Baz" albums[2].albumartist_sort = None for album in albums: self.lib.add(album) items = [_common.item() for _ in range(4)] - items[0].title = u'Foo bar' - items[0].artist = u'One' - items[0].album = u'Baz' + items[0].title = 'Foo bar' + items[0].artist = 'One' + items[0].album = 'Baz' items[0].year = 2001 items[0].comp = True - items[0].flex1 = u"Flex1-0" - items[0].flex2 = u"Flex2-A" + items[0].flex1 = "Flex1-0" + items[0].flex2 = "Flex2-A" items[0].album_id = albums[0].id items[0].artist_sort = None items[0].path = "/path0.mp3" items[0].track = 1 - items[1].title = u'Baz qux' - items[1].artist = u'Two' - items[1].album = u'Baz' + items[1].title = 'Baz qux' + items[1].artist = 'Two' + items[1].album = 'Baz' items[1].year = 2002 items[1].comp = True - items[1].flex1 = u"Flex1-1" - items[1].flex2 = u"Flex2-A" + items[1].flex1 = "Flex1-1" + items[1].flex2 = "Flex2-A" items[1].album_id = albums[0].id items[1].artist_sort = None items[1].path = "/patH1.mp3" items[1].track = 2 - items[2].title = u'Beets 4 eva' - items[2].artist = u'Three' - items[2].album = u'Foo' + items[2].title = 'Beets 4 eva' + items[2].artist = 'Three' + items[2].album = 'Foo' items[2].year = 2003 items[2].comp = False - items[2].flex1 = u"Flex1-2" - items[2].flex2 = u"Flex1-B" + items[2].flex1 = "Flex1-2" + items[2].flex2 = "Flex1-B" items[2].album_id = albums[1].id items[2].artist_sort = None items[2].path = "/paTH2.mp3" items[2].track = 3 - items[3].title = u'Beets 4 eva' - items[3].artist = u'Three' - items[3].album = u'Foo2' + items[3].title = 'Beets 4 eva' + items[3].artist = 'Three' + items[3].album = 'Foo2' items[3].year = 2004 items[3].comp = False - items[3].flex1 = u"Flex1-2" - items[3].flex2 = u"Flex1-C" + items[3].flex1 = "Flex1-2" + items[3].flex2 = "Flex1-C" items[3].album_id = albums[2].id items[3].artist_sort = None items[3].path = "/PATH3.mp3" @@ -107,50 +105,50 @@ class DummyDataTestCase(_common.TestCase): class SortFixedFieldTest(DummyDataTestCase): def test_sort_asc(self): - q = u'' - sort = dbcore.query.FixedFieldSort(u"year", True) + q = '' + sort = dbcore.query.FixedFieldSort("year", True) results = self.lib.items(q, sort) self.assertLessEqual(results[0]['year'], results[1]['year']) self.assertEqual(results[0]['year'], 2001) # same thing with query string - q = u'year+' + q = 'year+' results2 = self.lib.items(q) for r1, r2 in zip(results, results2): self.assertEqual(r1.id, r2.id) def test_sort_desc(self): - q = u'' - sort = dbcore.query.FixedFieldSort(u"year", False) + q = '' + sort = dbcore.query.FixedFieldSort("year", False) results = self.lib.items(q, sort) self.assertGreaterEqual(results[0]['year'], results[1]['year']) self.assertEqual(results[0]['year'], 2004) # same thing with query string - q = u'year-' + q = 'year-' results2 = self.lib.items(q) for r1, r2 in zip(results, results2): self.assertEqual(r1.id, r2.id) def test_sort_two_field_asc(self): - q = u'' - s1 = dbcore.query.FixedFieldSort(u"album", True) - s2 = dbcore.query.FixedFieldSort(u"year", True) + q = '' + s1 = dbcore.query.FixedFieldSort("album", True) + s2 = dbcore.query.FixedFieldSort("year", True) sort = dbcore.query.MultipleSort() sort.add_sort(s1) sort.add_sort(s2) results = self.lib.items(q, sort) self.assertLessEqual(results[0]['album'], results[1]['album']) self.assertLessEqual(results[1]['album'], results[2]['album']) - self.assertEqual(results[0]['album'], u'Baz') - self.assertEqual(results[1]['album'], u'Baz') + self.assertEqual(results[0]['album'], 'Baz') + self.assertEqual(results[1]['album'], 'Baz') self.assertLessEqual(results[0]['year'], results[1]['year']) # same thing with query string - q = u'album+ year+' + q = 'album+ year+' results2 = self.lib.items(q) for r1, r2 in zip(results, results2): self.assertEqual(r1.id, r2.id) def test_sort_path_field(self): - q = u'' + q = '' sort = dbcore.query.FixedFieldSort('path', True) results = self.lib.items(q, sort) self.assertEqual(results[0]['path'], b'/path0.mp3') @@ -161,46 +159,46 @@ class SortFixedFieldTest(DummyDataTestCase): class SortFlexFieldTest(DummyDataTestCase): def test_sort_asc(self): - q = u'' - sort = dbcore.query.SlowFieldSort(u"flex1", True) + q = '' + sort = dbcore.query.SlowFieldSort("flex1", True) results = self.lib.items(q, sort) self.assertLessEqual(results[0]['flex1'], results[1]['flex1']) - self.assertEqual(results[0]['flex1'], u'Flex1-0') + self.assertEqual(results[0]['flex1'], 'Flex1-0') # same thing with query string - q = u'flex1+' + q = 'flex1+' results2 = self.lib.items(q) for r1, r2 in zip(results, results2): self.assertEqual(r1.id, r2.id) def test_sort_desc(self): - q = u'' - sort = dbcore.query.SlowFieldSort(u"flex1", False) + q = '' + sort = dbcore.query.SlowFieldSort("flex1", False) results = self.lib.items(q, sort) self.assertGreaterEqual(results[0]['flex1'], results[1]['flex1']) self.assertGreaterEqual(results[1]['flex1'], results[2]['flex1']) self.assertGreaterEqual(results[2]['flex1'], results[3]['flex1']) - self.assertEqual(results[0]['flex1'], u'Flex1-2') + self.assertEqual(results[0]['flex1'], 'Flex1-2') # same thing with query string - q = u'flex1-' + q = 'flex1-' results2 = self.lib.items(q) for r1, r2 in zip(results, results2): self.assertEqual(r1.id, r2.id) def test_sort_two_field(self): - q = u'' - s1 = dbcore.query.SlowFieldSort(u"flex2", False) - s2 = dbcore.query.SlowFieldSort(u"flex1", True) + q = '' + s1 = dbcore.query.SlowFieldSort("flex2", False) + s2 = dbcore.query.SlowFieldSort("flex1", True) sort = dbcore.query.MultipleSort() sort.add_sort(s1) sort.add_sort(s2) results = self.lib.items(q, sort) self.assertGreaterEqual(results[0]['flex2'], results[1]['flex2']) self.assertGreaterEqual(results[1]['flex2'], results[2]['flex2']) - self.assertEqual(results[0]['flex2'], u'Flex2-A') - self.assertEqual(results[1]['flex2'], u'Flex2-A') + self.assertEqual(results[0]['flex2'], 'Flex2-A') + self.assertEqual(results[1]['flex2'], 'Flex2-A') self.assertLessEqual(results[0]['flex1'], results[1]['flex1']) # same thing with query string - q = u'flex2- flex1+' + q = 'flex2- flex1+' results2 = self.lib.items(q) for r1, r2 in zip(results, results2): self.assertEqual(r1.id, r2.id) @@ -208,44 +206,44 @@ class SortFlexFieldTest(DummyDataTestCase): class SortAlbumFixedFieldTest(DummyDataTestCase): def test_sort_asc(self): - q = u'' - sort = dbcore.query.FixedFieldSort(u"year", True) + q = '' + sort = dbcore.query.FixedFieldSort("year", True) results = self.lib.albums(q, sort) self.assertLessEqual(results[0]['year'], results[1]['year']) self.assertEqual(results[0]['year'], 2001) # same thing with query string - q = u'year+' + q = 'year+' results2 = self.lib.albums(q) for r1, r2 in zip(results, results2): self.assertEqual(r1.id, r2.id) def test_sort_desc(self): - q = u'' - sort = dbcore.query.FixedFieldSort(u"year", False) + q = '' + sort = dbcore.query.FixedFieldSort("year", False) results = self.lib.albums(q, sort) self.assertGreaterEqual(results[0]['year'], results[1]['year']) self.assertEqual(results[0]['year'], 2005) # same thing with query string - q = u'year-' + q = 'year-' results2 = self.lib.albums(q) for r1, r2 in zip(results, results2): self.assertEqual(r1.id, r2.id) def test_sort_two_field_asc(self): - q = u'' - s1 = dbcore.query.FixedFieldSort(u"genre", True) - s2 = dbcore.query.FixedFieldSort(u"album", True) + q = '' + s1 = dbcore.query.FixedFieldSort("genre", True) + s2 = dbcore.query.FixedFieldSort("album", True) sort = dbcore.query.MultipleSort() sort.add_sort(s1) sort.add_sort(s2) results = self.lib.albums(q, sort) self.assertLessEqual(results[0]['genre'], results[1]['genre']) self.assertLessEqual(results[1]['genre'], results[2]['genre']) - self.assertEqual(results[1]['genre'], u'Rock') - self.assertEqual(results[2]['genre'], u'Rock') + self.assertEqual(results[1]['genre'], 'Rock') + self.assertEqual(results[2]['genre'], 'Rock') self.assertLessEqual(results[1]['album'], results[2]['album']) # same thing with query string - q = u'genre+ album+' + q = 'genre+ album+' results2 = self.lib.albums(q) for r1, r2 in zip(results, results2): self.assertEqual(r1.id, r2.id) @@ -253,44 +251,44 @@ class SortAlbumFixedFieldTest(DummyDataTestCase): class SortAlbumFlexFieldTest(DummyDataTestCase): def test_sort_asc(self): - q = u'' - sort = dbcore.query.SlowFieldSort(u"flex1", True) + q = '' + sort = dbcore.query.SlowFieldSort("flex1", True) results = self.lib.albums(q, sort) self.assertLessEqual(results[0]['flex1'], results[1]['flex1']) self.assertLessEqual(results[1]['flex1'], results[2]['flex1']) # same thing with query string - q = u'flex1+' + q = 'flex1+' results2 = self.lib.albums(q) for r1, r2 in zip(results, results2): self.assertEqual(r1.id, r2.id) def test_sort_desc(self): - q = u'' - sort = dbcore.query.SlowFieldSort(u"flex1", False) + q = '' + sort = dbcore.query.SlowFieldSort("flex1", False) results = self.lib.albums(q, sort) self.assertGreaterEqual(results[0]['flex1'], results[1]['flex1']) self.assertGreaterEqual(results[1]['flex1'], results[2]['flex1']) # same thing with query string - q = u'flex1-' + q = 'flex1-' results2 = self.lib.albums(q) for r1, r2 in zip(results, results2): self.assertEqual(r1.id, r2.id) def test_sort_two_field_asc(self): - q = u'' - s1 = dbcore.query.SlowFieldSort(u"flex2", True) - s2 = dbcore.query.SlowFieldSort(u"flex1", True) + q = '' + s1 = dbcore.query.SlowFieldSort("flex2", True) + s2 = dbcore.query.SlowFieldSort("flex1", True) sort = dbcore.query.MultipleSort() sort.add_sort(s1) sort.add_sort(s2) results = self.lib.albums(q, sort) self.assertLessEqual(results[0]['flex2'], results[1]['flex2']) self.assertLessEqual(results[1]['flex2'], results[2]['flex2']) - self.assertEqual(results[0]['flex2'], u'Flex2-A') - self.assertEqual(results[1]['flex2'], u'Flex2-A') + self.assertEqual(results[0]['flex2'], 'Flex2-A') + self.assertEqual(results[1]['flex2'], 'Flex2-A') self.assertLessEqual(results[0]['flex1'], results[1]['flex1']) # same thing with query string - q = u'flex2+ flex1+' + q = 'flex2+ flex1+' results2 = self.lib.albums(q) for r1, r2 in zip(results, results2): self.assertEqual(r1.id, r2.id) @@ -298,25 +296,25 @@ class SortAlbumFlexFieldTest(DummyDataTestCase): class SortAlbumComputedFieldTest(DummyDataTestCase): def test_sort_asc(self): - q = u'' - sort = dbcore.query.SlowFieldSort(u"path", True) + q = '' + sort = dbcore.query.SlowFieldSort("path", True) results = self.lib.albums(q, sort) self.assertLessEqual(results[0]['path'], results[1]['path']) self.assertLessEqual(results[1]['path'], results[2]['path']) # same thing with query string - q = u'path+' + q = 'path+' results2 = self.lib.albums(q) for r1, r2 in zip(results, results2): self.assertEqual(r1.id, r2.id) def test_sort_desc(self): - q = u'' - sort = dbcore.query.SlowFieldSort(u"path", False) + q = '' + sort = dbcore.query.SlowFieldSort("path", False) results = self.lib.albums(q, sort) self.assertGreaterEqual(results[0]['path'], results[1]['path']) self.assertGreaterEqual(results[1]['path'], results[2]['path']) # same thing with query string - q = u'path-' + q = 'path-' results2 = self.lib.albums(q) for r1, r2 in zip(results, results2): self.assertEqual(r1.id, r2.id) @@ -324,24 +322,24 @@ class SortAlbumComputedFieldTest(DummyDataTestCase): class SortCombinedFieldTest(DummyDataTestCase): def test_computed_first(self): - q = u'' - s1 = dbcore.query.SlowFieldSort(u"path", True) - s2 = dbcore.query.FixedFieldSort(u"year", True) + q = '' + s1 = dbcore.query.SlowFieldSort("path", True) + s2 = dbcore.query.FixedFieldSort("year", True) sort = dbcore.query.MultipleSort() sort.add_sort(s1) sort.add_sort(s2) results = self.lib.albums(q, sort) self.assertLessEqual(results[0]['path'], results[1]['path']) self.assertLessEqual(results[1]['path'], results[2]['path']) - q = u'path+ year+' + q = 'path+ year+' results2 = self.lib.albums(q) for r1, r2 in zip(results, results2): self.assertEqual(r1.id, r2.id) def test_computed_second(self): - q = u'' - s1 = dbcore.query.FixedFieldSort(u"year", True) - s2 = dbcore.query.SlowFieldSort(u"path", True) + q = '' + s1 = dbcore.query.FixedFieldSort("year", True) + s2 = dbcore.query.SlowFieldSort("path", True) sort = dbcore.query.MultipleSort() sort.add_sort(s1) sort.add_sort(s2) @@ -349,7 +347,7 @@ class SortCombinedFieldTest(DummyDataTestCase): self.assertLessEqual(results[0]['year'], results[1]['year']) self.assertLessEqual(results[1]['year'], results[2]['year']) self.assertLessEqual(results[0]['path'], results[1]['path']) - q = u'year+ path+' + q = 'year+ path+' results2 = self.lib.albums(q) for r1, r2 in zip(results, results2): self.assertEqual(r1.id, r2.id) @@ -381,26 +379,26 @@ class CaseSensitivityTest(DummyDataTestCase, _common.TestCase): """ def setUp(self): - super(CaseSensitivityTest, self).setUp() + super().setUp() album = _common.album() - album.album = u"album" - album.genre = u"alternative" - album.year = u"2001" - album.flex1 = u"flex1" - album.flex2 = u"flex2-A" - album.albumartist = u"bar" + album.album = "album" + album.genre = "alternative" + album.year = "2001" + album.flex1 = "flex1" + album.flex2 = "flex2-A" + album.albumartist = "bar" album.albumartist_sort = None self.lib.add(album) item = _common.item() - item.title = u'another' - item.artist = u'lowercase' - item.album = u'album' + item.title = 'another' + item.artist = 'lowercase' + item.album = 'album' item.year = 2001 item.comp = True - item.flex1 = u"flex1" - item.flex2 = u"flex2-A" + item.flex1 = "flex1" + item.flex2 = "flex2-A" item.album_id = album.id item.artist_sort = None item.track = 10 @@ -412,53 +410,53 @@ class CaseSensitivityTest(DummyDataTestCase, _common.TestCase): def tearDown(self): self.new_item.remove(delete=True) self.new_album.remove(delete=True) - super(CaseSensitivityTest, self).tearDown() + super().tearDown() def test_smart_artist_case_insensitive(self): config['sort_case_insensitive'] = True - q = u'artist+' + q = 'artist+' results = list(self.lib.items(q)) - self.assertEqual(results[0].artist, u'lowercase') - self.assertEqual(results[1].artist, u'One') + self.assertEqual(results[0].artist, 'lowercase') + self.assertEqual(results[1].artist, 'One') def test_smart_artist_case_sensitive(self): config['sort_case_insensitive'] = False - q = u'artist+' + q = 'artist+' results = list(self.lib.items(q)) - self.assertEqual(results[0].artist, u'One') - self.assertEqual(results[-1].artist, u'lowercase') + self.assertEqual(results[0].artist, 'One') + self.assertEqual(results[-1].artist, 'lowercase') def test_fixed_field_case_insensitive(self): config['sort_case_insensitive'] = True - q = u'album+' + q = 'album+' results = list(self.lib.albums(q)) - self.assertEqual(results[0].album, u'album') - self.assertEqual(results[1].album, u'Album A') + self.assertEqual(results[0].album, 'album') + self.assertEqual(results[1].album, 'Album A') def test_fixed_field_case_sensitive(self): config['sort_case_insensitive'] = False - q = u'album+' + q = 'album+' results = list(self.lib.albums(q)) - self.assertEqual(results[0].album, u'Album A') - self.assertEqual(results[-1].album, u'album') + self.assertEqual(results[0].album, 'Album A') + self.assertEqual(results[-1].album, 'album') def test_flex_field_case_insensitive(self): config['sort_case_insensitive'] = True - q = u'flex1+' + q = 'flex1+' results = list(self.lib.items(q)) - self.assertEqual(results[0].flex1, u'flex1') - self.assertEqual(results[1].flex1, u'Flex1-0') + self.assertEqual(results[0].flex1, 'flex1') + self.assertEqual(results[1].flex1, 'Flex1-0') def test_flex_field_case_sensitive(self): config['sort_case_insensitive'] = False - q = u'flex1+' + q = 'flex1+' results = list(self.lib.items(q)) - self.assertEqual(results[0].flex1, u'Flex1-0') - self.assertEqual(results[-1].flex1, u'flex1') + self.assertEqual(results[0].flex1, 'Flex1-0') + self.assertEqual(results[-1].flex1, 'flex1') def test_case_sensitive_only_affects_text(self): config['sort_case_insensitive'] = True - q = u'track+' + q = 'track+' results = list(self.lib.items(q)) # If the numerical values were sorted as strings, # then ['1', '10', '2'] would be valid. @@ -472,10 +470,10 @@ class NonExistingFieldTest(DummyDataTestCase): """Test sorting by non-existing fields""" def test_non_existing_fields_not_fail(self): - qs = [u'foo+', u'foo-', u'--', u'-+', u'+-', - u'++', u'-foo-', u'-foo+', u'---'] + qs = ['foo+', 'foo-', '--', '-+', '+-', + '++', '-foo-', '-foo+', '---'] - q0 = u'foo+' + q0 = 'foo+' results0 = list(self.lib.items(q0)) for q1 in qs: results1 = list(self.lib.items(q1)) @@ -483,16 +481,16 @@ class NonExistingFieldTest(DummyDataTestCase): self.assertEqual(r1.id, r2.id) def test_combined_non_existing_field_asc(self): - all_results = list(self.lib.items(u'id+')) - q = u'foo+ id+' + all_results = list(self.lib.items('id+')) + q = 'foo+ id+' results = list(self.lib.items(q)) self.assertEqual(len(all_results), len(results)) for r1, r2 in zip(all_results, results): self.assertEqual(r1.id, r2.id) def test_combined_non_existing_field_desc(self): - all_results = list(self.lib.items(u'id+')) - q = u'foo- id+' + all_results = list(self.lib.items('id+')) + q = 'foo- id+' results = list(self.lib.items(q)) self.assertEqual(len(all_results), len(results)) for r1, r2 in zip(all_results, results): @@ -501,18 +499,18 @@ class NonExistingFieldTest(DummyDataTestCase): def test_field_present_in_some_items(self): """Test ordering by a field not present on all items.""" # append 'foo' to two to items (1,2) - items = self.lib.items(u'id+') + items = self.lib.items('id+') ids = [i.id for i in items] - items[1].foo = u'bar1' - items[2].foo = u'bar2' + items[1].foo = 'bar1' + items[2].foo = 'bar2' items[1].store() items[2].store() - results_asc = list(self.lib.items(u'foo+ id+')) + results_asc = list(self.lib.items('foo+ id+')) self.assertEqual([i.id for i in results_asc], # items without field first [ids[0], ids[3], ids[1], ids[2]]) - results_desc = list(self.lib.items(u'foo- id+')) + results_desc = list(self.lib.items('foo- id+')) self.assertEqual([i.id for i in results_desc], # items without field last [ids[2], ids[1], ids[0], ids[3]]) @@ -523,13 +521,13 @@ class NonExistingFieldTest(DummyDataTestCase): If a string ends with a sorting suffix, it takes precedence over the NotQuery parsing. """ - query, sort = beets.library.parse_query_string(u'-bar+', + query, sort = beets.library.parse_query_string('-bar+', beets.library.Item) self.assertEqual(len(query.subqueries), 1) self.assertTrue(isinstance(query.subqueries[0], dbcore.query.TrueQuery)) self.assertTrue(isinstance(sort, dbcore.query.SlowFieldSort)) - self.assertEqual(sort.field, u'-bar') + self.assertEqual(sort.field, '-bar') def suite(): diff --git a/test/test_spotify.py b/test/test_spotify.py index ea54a13db..41217a9fd 100644 --- a/test/test_spotify.py +++ b/test/test_spotify.py @@ -1,8 +1,5 @@ -# -*- coding: utf-8 -*- - """Tests for the 'spotify' plugin""" -from __future__ import division, absolute_import, print_function import os import responses @@ -16,7 +13,7 @@ from test.helper import TestHelper from six.moves.urllib.parse import parse_qs, urlparse -class ArgumentsMock(object): +class ArgumentsMock: def __init__(self, mode, show_failures): self.mode = mode self.show_failures = show_failures @@ -60,7 +57,7 @@ class SpotifyPluginTest(_common.TestCase, TestHelper): def test_empty_query(self): self.assertEqual( - None, self.spotify._match_library_tracks(self.lib, u"1=2") + None, self.spotify._match_library_tracks(self.lib, "1=2") ) @responses.activate @@ -79,21 +76,21 @@ class SpotifyPluginTest(_common.TestCase, TestHelper): content_type='application/json', ) item = Item( - mb_trackid=u'01234', - album=u'lkajsdflakjsd', - albumartist=u'ujydfsuihse', - title=u'duifhjslkef', + mb_trackid='01234', + album='lkajsdflakjsd', + albumartist='ujydfsuihse', + title='duifhjslkef', length=10, ) item.add(self.lib) - self.assertEqual([], self.spotify._match_library_tracks(self.lib, u"")) + self.assertEqual([], self.spotify._match_library_tracks(self.lib, "")) params = _params(responses.calls[0].request.url) query = params['q'][0] - self.assertIn(u'duifhjslkef', query) - self.assertIn(u'artist:ujydfsuihse', query) - self.assertIn(u'album:lkajsdflakjsd', query) - self.assertEqual(params['type'], [u'track']) + self.assertIn('duifhjslkef', query) + self.assertIn('artist:ujydfsuihse', query) + self.assertIn('album:lkajsdflakjsd', query) + self.assertEqual(params['type'], ['track']) @responses.activate def test_track_request(self): @@ -111,24 +108,24 @@ class SpotifyPluginTest(_common.TestCase, TestHelper): content_type='application/json', ) item = Item( - mb_trackid=u'01234', - album=u'Despicable Me 2', - albumartist=u'Pharrell Williams', - title=u'Happy', + mb_trackid='01234', + album='Despicable Me 2', + albumartist='Pharrell Williams', + title='Happy', length=10, ) item.add(self.lib) - results = self.spotify._match_library_tracks(self.lib, u"Happy") + results = self.spotify._match_library_tracks(self.lib, "Happy") self.assertEqual(1, len(results)) - self.assertEqual(u"6NPVjNh8Jhru9xOmyQigds", results[0]['id']) + self.assertEqual("6NPVjNh8Jhru9xOmyQigds", results[0]['id']) self.spotify._output_match_results(results) params = _params(responses.calls[0].request.url) query = params['q'][0] - self.assertIn(u'Happy', query) - self.assertIn(u'artist:Pharrell Williams', query) - self.assertIn(u'album:Despicable Me 2', query) - self.assertEqual(params['type'], [u'track']) + self.assertIn('Happy', query) + self.assertIn('artist:Pharrell Williams', query) + self.assertIn('album:Despicable Me 2', query) + self.assertEqual(params['type'], ['track']) def suite(): diff --git a/test/test_subsonicupdate.py b/test/test_subsonicupdate.py index dd254d593..70a37d09d 100644 --- a/test/test_subsonicupdate.py +++ b/test/test_subsonicupdate.py @@ -1,8 +1,5 @@ -# -*- coding: utf-8 -*- - """Tests for the 'subsonic' plugin.""" -from __future__ import division, absolute_import, print_function import responses import unittest @@ -14,7 +11,7 @@ from test.helper import TestHelper from six.moves.urllib.parse import parse_qs, urlparse -class ArgumentsMock(object): +class ArgumentsMock: """Argument mocks for tests.""" def __init__(self, mode, show_failures): """Constructs ArgumentsMock.""" diff --git a/test/test_template.py b/test/test_template.py index 288bc2314..efc6da1d8 100644 --- a/test/test_template.py +++ b/test/test_template.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -15,7 +14,6 @@ """Tests for template engine. """ -from __future__ import division, absolute_import, print_function import unittest import six @@ -29,17 +27,17 @@ def _normexpr(expr): """ textbuf = [] for part in expr.parts: - if isinstance(part, six.string_types): + if isinstance(part, str): textbuf.append(part) else: if textbuf: - text = u''.join(textbuf) + text = ''.join(textbuf) if text: yield text textbuf = [] yield part if textbuf: - text = u''.join(textbuf) + text = ''.join(textbuf) if text: yield text @@ -51,15 +49,15 @@ def _normparse(text): class ParseTest(unittest.TestCase): def test_empty_string(self): - self.assertEqual(list(_normparse(u'')), []) + self.assertEqual(list(_normparse('')), []) def _assert_symbol(self, obj, ident): """Assert that an object is a Symbol with the given identifier. """ self.assertTrue(isinstance(obj, functemplate.Symbol), - u"not a Symbol: %s" % repr(obj)) + "not a Symbol: %s" % repr(obj)) self.assertEqual(obj.ident, ident, - u"wrong identifier: %s vs. %s" % + "wrong identifier: %s vs. %s" % (repr(obj.ident), repr(ident))) def _assert_call(self, obj, ident, numargs): @@ -67,223 +65,223 @@ class ParseTest(unittest.TestCase): argument count. """ self.assertTrue(isinstance(obj, functemplate.Call), - u"not a Call: %s" % repr(obj)) + "not a Call: %s" % repr(obj)) self.assertEqual(obj.ident, ident, - u"wrong identifier: %s vs. %s" % + "wrong identifier: %s vs. %s" % (repr(obj.ident), repr(ident))) self.assertEqual(len(obj.args), numargs, - u"wrong argument count in %s: %i vs. %i" % + "wrong argument count in %s: %i vs. %i" % (repr(obj.ident), len(obj.args), numargs)) def test_plain_text(self): - self.assertEqual(list(_normparse(u'hello world')), [u'hello world']) + self.assertEqual(list(_normparse('hello world')), ['hello world']) def test_escaped_character_only(self): - self.assertEqual(list(_normparse(u'$$')), [u'$']) + self.assertEqual(list(_normparse('$$')), ['$']) def test_escaped_character_in_text(self): - self.assertEqual(list(_normparse(u'a $$ b')), [u'a $ b']) + self.assertEqual(list(_normparse('a $$ b')), ['a $ b']) def test_escaped_character_at_start(self): - self.assertEqual(list(_normparse(u'$$ hello')), [u'$ hello']) + self.assertEqual(list(_normparse('$$ hello')), ['$ hello']) def test_escaped_character_at_end(self): - self.assertEqual(list(_normparse(u'hello $$')), [u'hello $']) + self.assertEqual(list(_normparse('hello $$')), ['hello $']) def test_escaped_function_delim(self): - self.assertEqual(list(_normparse(u'a $% b')), [u'a % b']) + self.assertEqual(list(_normparse('a $% b')), ['a % b']) def test_escaped_sep(self): - self.assertEqual(list(_normparse(u'a $, b')), [u'a , b']) + self.assertEqual(list(_normparse('a $, b')), ['a , b']) def test_escaped_close_brace(self): - self.assertEqual(list(_normparse(u'a $} b')), [u'a } b']) + self.assertEqual(list(_normparse('a $} b')), ['a } b']) def test_bare_value_delim_kept_intact(self): - self.assertEqual(list(_normparse(u'a $ b')), [u'a $ b']) + self.assertEqual(list(_normparse('a $ b')), ['a $ b']) def test_bare_function_delim_kept_intact(self): - self.assertEqual(list(_normparse(u'a % b')), [u'a % b']) + self.assertEqual(list(_normparse('a % b')), ['a % b']) def test_bare_opener_kept_intact(self): - self.assertEqual(list(_normparse(u'a { b')), [u'a { b']) + self.assertEqual(list(_normparse('a { b')), ['a { b']) def test_bare_closer_kept_intact(self): - self.assertEqual(list(_normparse(u'a } b')), [u'a } b']) + self.assertEqual(list(_normparse('a } b')), ['a } b']) def test_bare_sep_kept_intact(self): - self.assertEqual(list(_normparse(u'a , b')), [u'a , b']) + self.assertEqual(list(_normparse('a , b')), ['a , b']) def test_symbol_alone(self): - parts = list(_normparse(u'$foo')) + parts = list(_normparse('$foo')) self.assertEqual(len(parts), 1) - self._assert_symbol(parts[0], u"foo") + self._assert_symbol(parts[0], "foo") def test_symbol_in_text(self): - parts = list(_normparse(u'hello $foo world')) + parts = list(_normparse('hello $foo world')) self.assertEqual(len(parts), 3) - self.assertEqual(parts[0], u'hello ') - self._assert_symbol(parts[1], u"foo") - self.assertEqual(parts[2], u' world') + self.assertEqual(parts[0], 'hello ') + self._assert_symbol(parts[1], "foo") + self.assertEqual(parts[2], ' world') def test_symbol_with_braces(self): - parts = list(_normparse(u'hello${foo}world')) + parts = list(_normparse('hello${foo}world')) self.assertEqual(len(parts), 3) - self.assertEqual(parts[0], u'hello') - self._assert_symbol(parts[1], u"foo") - self.assertEqual(parts[2], u'world') + self.assertEqual(parts[0], 'hello') + self._assert_symbol(parts[1], "foo") + self.assertEqual(parts[2], 'world') def test_unclosed_braces_symbol(self): - self.assertEqual(list(_normparse(u'a ${ b')), [u'a ${ b']) + self.assertEqual(list(_normparse('a ${ b')), ['a ${ b']) def test_empty_braces_symbol(self): - self.assertEqual(list(_normparse(u'a ${} b')), [u'a ${} b']) + self.assertEqual(list(_normparse('a ${} b')), ['a ${} b']) def test_call_without_args_at_end(self): - self.assertEqual(list(_normparse(u'foo %bar')), [u'foo %bar']) + self.assertEqual(list(_normparse('foo %bar')), ['foo %bar']) def test_call_without_args(self): - self.assertEqual(list(_normparse(u'foo %bar baz')), [u'foo %bar baz']) + self.assertEqual(list(_normparse('foo %bar baz')), ['foo %bar baz']) def test_call_with_unclosed_args(self): - self.assertEqual(list(_normparse(u'foo %bar{ baz')), - [u'foo %bar{ baz']) + self.assertEqual(list(_normparse('foo %bar{ baz')), + ['foo %bar{ baz']) def test_call_with_unclosed_multiple_args(self): - self.assertEqual(list(_normparse(u'foo %bar{bar,bar baz')), - [u'foo %bar{bar,bar baz']) + self.assertEqual(list(_normparse('foo %bar{bar,bar baz')), + ['foo %bar{bar,bar baz']) def test_call_empty_arg(self): - parts = list(_normparse(u'%foo{}')) + parts = list(_normparse('%foo{}')) self.assertEqual(len(parts), 1) - self._assert_call(parts[0], u"foo", 1) + self._assert_call(parts[0], "foo", 1) self.assertEqual(list(_normexpr(parts[0].args[0])), []) def test_call_single_arg(self): - parts = list(_normparse(u'%foo{bar}')) + parts = list(_normparse('%foo{bar}')) self.assertEqual(len(parts), 1) - self._assert_call(parts[0], u"foo", 1) - self.assertEqual(list(_normexpr(parts[0].args[0])), [u'bar']) + self._assert_call(parts[0], "foo", 1) + self.assertEqual(list(_normexpr(parts[0].args[0])), ['bar']) def test_call_two_args(self): - parts = list(_normparse(u'%foo{bar,baz}')) + parts = list(_normparse('%foo{bar,baz}')) self.assertEqual(len(parts), 1) - self._assert_call(parts[0], u"foo", 2) - self.assertEqual(list(_normexpr(parts[0].args[0])), [u'bar']) - self.assertEqual(list(_normexpr(parts[0].args[1])), [u'baz']) + self._assert_call(parts[0], "foo", 2) + self.assertEqual(list(_normexpr(parts[0].args[0])), ['bar']) + self.assertEqual(list(_normexpr(parts[0].args[1])), ['baz']) def test_call_with_escaped_sep(self): - parts = list(_normparse(u'%foo{bar$,baz}')) + parts = list(_normparse('%foo{bar$,baz}')) self.assertEqual(len(parts), 1) - self._assert_call(parts[0], u"foo", 1) - self.assertEqual(list(_normexpr(parts[0].args[0])), [u'bar,baz']) + self._assert_call(parts[0], "foo", 1) + self.assertEqual(list(_normexpr(parts[0].args[0])), ['bar,baz']) def test_call_with_escaped_close(self): - parts = list(_normparse(u'%foo{bar$}baz}')) + parts = list(_normparse('%foo{bar$}baz}')) self.assertEqual(len(parts), 1) - self._assert_call(parts[0], u"foo", 1) - self.assertEqual(list(_normexpr(parts[0].args[0])), [u'bar}baz']) + self._assert_call(parts[0], "foo", 1) + self.assertEqual(list(_normexpr(parts[0].args[0])), ['bar}baz']) def test_call_with_symbol_argument(self): - parts = list(_normparse(u'%foo{$bar,baz}')) + parts = list(_normparse('%foo{$bar,baz}')) self.assertEqual(len(parts), 1) - self._assert_call(parts[0], u"foo", 2) + self._assert_call(parts[0], "foo", 2) arg_parts = list(_normexpr(parts[0].args[0])) self.assertEqual(len(arg_parts), 1) - self._assert_symbol(arg_parts[0], u"bar") - self.assertEqual(list(_normexpr(parts[0].args[1])), [u"baz"]) + self._assert_symbol(arg_parts[0], "bar") + self.assertEqual(list(_normexpr(parts[0].args[1])), ["baz"]) def test_call_with_nested_call_argument(self): - parts = list(_normparse(u'%foo{%bar{},baz}')) + parts = list(_normparse('%foo{%bar{},baz}')) self.assertEqual(len(parts), 1) - self._assert_call(parts[0], u"foo", 2) + self._assert_call(parts[0], "foo", 2) arg_parts = list(_normexpr(parts[0].args[0])) self.assertEqual(len(arg_parts), 1) - self._assert_call(arg_parts[0], u"bar", 1) - self.assertEqual(list(_normexpr(parts[0].args[1])), [u"baz"]) + self._assert_call(arg_parts[0], "bar", 1) + self.assertEqual(list(_normexpr(parts[0].args[1])), ["baz"]) def test_nested_call_with_argument(self): - parts = list(_normparse(u'%foo{%bar{baz}}')) + parts = list(_normparse('%foo{%bar{baz}}')) self.assertEqual(len(parts), 1) - self._assert_call(parts[0], u"foo", 1) + self._assert_call(parts[0], "foo", 1) arg_parts = list(_normexpr(parts[0].args[0])) self.assertEqual(len(arg_parts), 1) - self._assert_call(arg_parts[0], u"bar", 1) - self.assertEqual(list(_normexpr(arg_parts[0].args[0])), [u'baz']) + self._assert_call(arg_parts[0], "bar", 1) + self.assertEqual(list(_normexpr(arg_parts[0].args[0])), ['baz']) def test_sep_before_call_two_args(self): - parts = list(_normparse(u'hello, %foo{bar,baz}')) + parts = list(_normparse('hello, %foo{bar,baz}')) self.assertEqual(len(parts), 2) - self.assertEqual(parts[0], u'hello, ') - self._assert_call(parts[1], u"foo", 2) - self.assertEqual(list(_normexpr(parts[1].args[0])), [u'bar']) - self.assertEqual(list(_normexpr(parts[1].args[1])), [u'baz']) + self.assertEqual(parts[0], 'hello, ') + self._assert_call(parts[1], "foo", 2) + self.assertEqual(list(_normexpr(parts[1].args[0])), ['bar']) + self.assertEqual(list(_normexpr(parts[1].args[1])), ['baz']) def test_sep_with_symbols(self): - parts = list(_normparse(u'hello,$foo,$bar')) + parts = list(_normparse('hello,$foo,$bar')) self.assertEqual(len(parts), 4) - self.assertEqual(parts[0], u'hello,') - self._assert_symbol(parts[1], u"foo") - self.assertEqual(parts[2], u',') - self._assert_symbol(parts[3], u"bar") + self.assertEqual(parts[0], 'hello,') + self._assert_symbol(parts[1], "foo") + self.assertEqual(parts[2], ',') + self._assert_symbol(parts[3], "bar") def test_newline_at_end(self): - parts = list(_normparse(u'foo\n')) + parts = list(_normparse('foo\n')) self.assertEqual(len(parts), 1) - self.assertEqual(parts[0], u'foo\n') + self.assertEqual(parts[0], 'foo\n') class EvalTest(unittest.TestCase): def _eval(self, template): values = { - u'foo': u'bar', - u'baz': u'BaR', + 'foo': 'bar', + 'baz': 'BaR', } functions = { - u'lower': six.text_type.lower, - u'len': len, + 'lower': str.lower, + 'len': len, } return functemplate.Template(template).substitute(values, functions) def test_plain_text(self): - self.assertEqual(self._eval(u"foo"), u"foo") + self.assertEqual(self._eval("foo"), "foo") def test_subtitute_value(self): - self.assertEqual(self._eval(u"$foo"), u"bar") + self.assertEqual(self._eval("$foo"), "bar") def test_subtitute_value_in_text(self): - self.assertEqual(self._eval(u"hello $foo world"), u"hello bar world") + self.assertEqual(self._eval("hello $foo world"), "hello bar world") def test_not_subtitute_undefined_value(self): - self.assertEqual(self._eval(u"$bar"), u"$bar") + self.assertEqual(self._eval("$bar"), "$bar") def test_function_call(self): - self.assertEqual(self._eval(u"%lower{FOO}"), u"foo") + self.assertEqual(self._eval("%lower{FOO}"), "foo") def test_function_call_with_text(self): - self.assertEqual(self._eval(u"A %lower{FOO} B"), u"A foo B") + self.assertEqual(self._eval("A %lower{FOO} B"), "A foo B") def test_nested_function_call(self): - self.assertEqual(self._eval(u"%lower{%lower{FOO}}"), u"foo") + self.assertEqual(self._eval("%lower{%lower{FOO}}"), "foo") def test_symbol_in_argument(self): - self.assertEqual(self._eval(u"%lower{$baz}"), u"bar") + self.assertEqual(self._eval("%lower{$baz}"), "bar") def test_function_call_exception(self): - res = self._eval(u"%lower{a,b,c,d,e}") - self.assertTrue(isinstance(res, six.string_types)) + res = self._eval("%lower{a,b,c,d,e}") + self.assertTrue(isinstance(res, str)) def test_function_returning_integer(self): - self.assertEqual(self._eval(u"%len{foo}"), u"3") + self.assertEqual(self._eval("%len{foo}"), "3") def test_not_subtitute_undefined_func(self): - self.assertEqual(self._eval(u"%bar{}"), u"%bar{}") + self.assertEqual(self._eval("%bar{}"), "%bar{}") def test_not_subtitute_func_with_no_args(self): - self.assertEqual(self._eval(u"%lower"), u"%lower") + self.assertEqual(self._eval("%lower"), "%lower") def test_function_call_with_empty_arg(self): - self.assertEqual(self._eval(u"%len{}"), u"0") + self.assertEqual(self._eval("%len{}"), "0") def suite(): diff --git a/test/test_the.py b/test/test_the.py index 1fc488953..28feea373 100644 --- a/test/test_the.py +++ b/test/test_the.py @@ -1,8 +1,5 @@ -# -*- coding: utf-8 -*- - """Tests for the 'the' plugin""" -from __future__ import division, absolute_import, print_function import unittest from test import _common @@ -13,54 +10,54 @@ from beetsplug.the import ThePlugin, PATTERN_A, PATTERN_THE, FORMAT class ThePluginTest(_common.TestCase): def test_unthe_with_default_patterns(self): - self.assertEqual(ThePlugin().unthe(u'', PATTERN_THE), '') - self.assertEqual(ThePlugin().unthe(u'The Something', PATTERN_THE), - u'Something, The') - self.assertEqual(ThePlugin().unthe(u'The The', PATTERN_THE), - u'The, The') - self.assertEqual(ThePlugin().unthe(u'The The', PATTERN_THE), - u'The, The') - self.assertEqual(ThePlugin().unthe(u'The The X', PATTERN_THE), - u'The X, The') - self.assertEqual(ThePlugin().unthe(u'the The', PATTERN_THE), - u'The, the') - self.assertEqual(ThePlugin().unthe(u'Protected The', PATTERN_THE), - u'Protected The') - self.assertEqual(ThePlugin().unthe(u'A Boy', PATTERN_A), - u'Boy, A') - self.assertEqual(ThePlugin().unthe(u'a girl', PATTERN_A), - u'girl, a') - self.assertEqual(ThePlugin().unthe(u'An Apple', PATTERN_A), - u'Apple, An') - self.assertEqual(ThePlugin().unthe(u'An A Thing', PATTERN_A), - u'A Thing, An') - self.assertEqual(ThePlugin().unthe(u'the An Arse', PATTERN_A), - u'the An Arse') - self.assertEqual(ThePlugin().unthe(u'TET - Travailleur', PATTERN_THE), - u'TET - Travailleur') + self.assertEqual(ThePlugin().unthe('', PATTERN_THE), '') + self.assertEqual(ThePlugin().unthe('The Something', PATTERN_THE), + 'Something, The') + self.assertEqual(ThePlugin().unthe('The The', PATTERN_THE), + 'The, The') + self.assertEqual(ThePlugin().unthe('The The', PATTERN_THE), + 'The, The') + self.assertEqual(ThePlugin().unthe('The The X', PATTERN_THE), + 'The X, The') + self.assertEqual(ThePlugin().unthe('the The', PATTERN_THE), + 'The, the') + self.assertEqual(ThePlugin().unthe('Protected The', PATTERN_THE), + 'Protected The') + self.assertEqual(ThePlugin().unthe('A Boy', PATTERN_A), + 'Boy, A') + self.assertEqual(ThePlugin().unthe('a girl', PATTERN_A), + 'girl, a') + self.assertEqual(ThePlugin().unthe('An Apple', PATTERN_A), + 'Apple, An') + self.assertEqual(ThePlugin().unthe('An A Thing', PATTERN_A), + 'A Thing, An') + self.assertEqual(ThePlugin().unthe('the An Arse', PATTERN_A), + 'the An Arse') + self.assertEqual(ThePlugin().unthe('TET - Travailleur', PATTERN_THE), + 'TET - Travailleur') def test_unthe_with_strip(self): config['the']['strip'] = True - self.assertEqual(ThePlugin().unthe(u'The Something', PATTERN_THE), - u'Something') - self.assertEqual(ThePlugin().unthe(u'An A', PATTERN_A), u'A') + self.assertEqual(ThePlugin().unthe('The Something', PATTERN_THE), + 'Something') + self.assertEqual(ThePlugin().unthe('An A', PATTERN_A), 'A') def test_template_function_with_defaults(self): ThePlugin().patterns = [PATTERN_THE, PATTERN_A] - self.assertEqual(ThePlugin().the_template_func(u'The The'), - u'The, The') - self.assertEqual(ThePlugin().the_template_func(u'An A'), u'A, An') + self.assertEqual(ThePlugin().the_template_func('The The'), + 'The, The') + self.assertEqual(ThePlugin().the_template_func('An A'), 'A, An') def test_custom_pattern(self): - config['the']['patterns'] = [u'^test\\s'] + config['the']['patterns'] = ['^test\\s'] config['the']['format'] = FORMAT - self.assertEqual(ThePlugin().the_template_func(u'test passed'), - u'passed, test') + self.assertEqual(ThePlugin().the_template_func('test passed'), + 'passed, test') def test_custom_format(self): config['the']['patterns'] = [PATTERN_THE, PATTERN_A] - config['the']['format'] = u'{1} ({0})' - self.assertEqual(ThePlugin().the_template_func(u'The A'), u'The (A)') + config['the']['format'] = '{1} ({0})' + self.assertEqual(ThePlugin().the_template_func('The A'), 'The (A)') def suite(): diff --git a/test/test_thumbnails.py b/test/test_thumbnails.py index dbbc032f7..e8ab21d72 100644 --- a/test/test_thumbnails.py +++ b/test/test_thumbnails.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Bruno Cauet # @@ -13,10 +12,9 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -from __future__ import division, absolute_import, print_function import os.path -from mock import Mock, patch, call +from unittest.mock import Mock, patch, call from tempfile import mkdtemp from shutil import rmtree import unittest @@ -38,13 +36,13 @@ class ThumbnailsTest(unittest.TestCase, TestHelper): @patch('beetsplug.thumbnails.util') def test_write_metadata_im(self, mock_util): - metadata = {"a": u"A", "b": u"B"} + metadata = {"a": "A", "b": "B"} write_metadata_im("foo", metadata) try: - command = u"convert foo -set a A -set b B foo".split(' ') + command = "convert foo -set a A -set b B foo".split(' ') mock_util.command_output.assert_called_once_with(command) except AssertionError: - command = u"convert foo -set b B -set a A foo".split(' ') + command = "convert foo -set b B -set a A foo".split(' ') mock_util.command_output.assert_called_once_with(command) @patch('beetsplug.thumbnails.ThumbnailsPlugin._check_local_ok') @@ -60,7 +58,7 @@ class ThumbnailsTest(unittest.TestCase, TestHelper): plugin.add_tags(album, b"/path/to/thumbnail") metadata = {"Thumb::URI": "COVER_URI", - "Thumb::MTime": u"12345"} + "Thumb::MTime": "12345"} plugin.write_metadata.assert_called_once_with(b"/path/to/thumbnail", metadata) mock_stat.assert_called_once_with(album.artpath) @@ -85,7 +83,7 @@ class ThumbnailsTest(unittest.TestCase, TestHelper): return False if path == LARGE_DIR: return True - raise ValueError(u"unexpected path {0!r}".format(path)) + raise ValueError(f"unexpected path {path!r}") mock_os.path.exists = exists plugin = ThumbnailsPlugin() mock_os.makedirs.assert_called_once_with(NORMAL_DIR) @@ -144,7 +142,7 @@ class ThumbnailsTest(unittest.TestCase, TestHelper): elif target == path_to_art: return Mock(st_mtime=2) else: - raise ValueError(u"invalid target {0}".format(target)) + raise ValueError(f"invalid target {target}") mock_os.stat.side_effect = os_stat plugin.make_cover_thumbnail(album, 12345, thumbnail_dir) @@ -170,7 +168,7 @@ class ThumbnailsTest(unittest.TestCase, TestHelper): elif target == path_to_art: return Mock(st_mtime=2) else: - raise ValueError(u"invalid target {0}".format(target)) + raise ValueError(f"invalid target {target}") mock_os.stat.side_effect = os_stat plugin.make_cover_thumbnail(album, 12345, thumbnail_dir) @@ -267,21 +265,21 @@ class ThumbnailsTest(unittest.TestCase, TestHelper): @patch('beetsplug.thumbnails.BaseDirectory') def test_thumbnail_file_name(self, mock_basedir): plug = ThumbnailsPlugin() - plug.get_uri = Mock(return_value=u"file:///my/uri") + plug.get_uri = Mock(return_value="file:///my/uri") self.assertEqual(plug.thumbnail_file_name(b'idontcare'), b"9488f5797fbe12ffb316d607dfd93d04.png") def test_uri(self): gio = GioURI() if not gio.available: - self.skipTest(u"GIO library not found") + self.skipTest("GIO library not found") - self.assertEqual(gio.uri(u"/foo"), u"file:///") # silent fail - self.assertEqual(gio.uri(b"/foo"), u"file:///foo") - self.assertEqual(gio.uri(b"/foo!"), u"file:///foo!") + self.assertEqual(gio.uri("/foo"), "file:///") # silent fail + self.assertEqual(gio.uri(b"/foo"), "file:///foo") + self.assertEqual(gio.uri(b"/foo!"), "file:///foo!") self.assertEqual( gio.uri(b'/music/\xec\x8b\xb8\xec\x9d\xb4'), - u'file:///music/%EC%8B%B8%EC%9D%B4') + 'file:///music/%EC%8B%B8%EC%9D%B4') class TestPathlibURI(): diff --git a/test/test_types_plugin.py b/test/test_types_plugin.py index 65ad7bee4..5a3bdcc49 100644 --- a/test/test_types_plugin.py +++ b/test/test_types_plugin.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Thomas Scholtes. # @@ -13,7 +12,6 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -from __future__ import division, absolute_import, print_function import time from datetime import datetime @@ -36,77 +34,77 @@ class TypesPluginTest(unittest.TestCase, TestHelper): def test_integer_modify_and_query(self): self.config['types'] = {'myint': 'int'} - item = self.add_item(artist=u'aaa') + item = self.add_item(artist='aaa') # Do not match unset values - out = self.list(u'myint:1..3') - self.assertEqual(u'', out) + out = self.list('myint:1..3') + self.assertEqual('', out) - self.modify(u'myint=2') + self.modify('myint=2') item.load() self.assertEqual(item['myint'], 2) # Match in range - out = self.list(u'myint:1..3') + out = self.list('myint:1..3') self.assertIn('aaa', out) def test_album_integer_modify_and_query(self): - self.config['types'] = {'myint': u'int'} - album = self.add_album(albumartist=u'aaa') + self.config['types'] = {'myint': 'int'} + album = self.add_album(albumartist='aaa') # Do not match unset values - out = self.list_album(u'myint:1..3') - self.assertEqual(u'', out) + out = self.list_album('myint:1..3') + self.assertEqual('', out) - self.modify(u'-a', u'myint=2') + self.modify('-a', 'myint=2') album.load() self.assertEqual(album['myint'], 2) # Match in range - out = self.list_album(u'myint:1..3') + out = self.list_album('myint:1..3') self.assertIn('aaa', out) def test_float_modify_and_query(self): - self.config['types'] = {'myfloat': u'float'} - item = self.add_item(artist=u'aaa') + self.config['types'] = {'myfloat': 'float'} + item = self.add_item(artist='aaa') # Do not match unset values - out = self.list(u'myfloat:10..0') - self.assertEqual(u'', out) + out = self.list('myfloat:10..0') + self.assertEqual('', out) - self.modify(u'myfloat=-9.1') + self.modify('myfloat=-9.1') item.load() self.assertEqual(item['myfloat'], -9.1) # Match in range - out = self.list(u'myfloat:-10..0') + out = self.list('myfloat:-10..0') self.assertIn('aaa', out) def test_bool_modify_and_query(self): - self.config['types'] = {'mybool': u'bool'} - true = self.add_item(artist=u'true') - false = self.add_item(artist=u'false') - self.add_item(artist=u'unset') + self.config['types'] = {'mybool': 'bool'} + true = self.add_item(artist='true') + false = self.add_item(artist='false') + self.add_item(artist='unset') # Do not match unset values - out = self.list(u'mybool:true, mybool:false') - self.assertEqual(u'', out) + out = self.list('mybool:true, mybool:false') + self.assertEqual('', out) # Set true - self.modify(u'mybool=1', u'artist:true') + self.modify('mybool=1', 'artist:true') true.load() self.assertEqual(true['mybool'], True) # Set false - self.modify(u'mybool=false', u'artist:false') + self.modify('mybool=false', 'artist:false') false.load() self.assertEqual(false['mybool'], False) # Query bools - out = self.list(u'mybool:true', u'$artist $mybool') - self.assertEqual(u'true True', out) + out = self.list('mybool:true', '$artist $mybool') + self.assertEqual('true True', out) - out = self.list(u'mybool:false', u'$artist $mybool') + out = self.list('mybool:false', '$artist $mybool') # Dealing with unset fields? # self.assertEqual('false False', out) @@ -114,27 +112,27 @@ class TypesPluginTest(unittest.TestCase, TestHelper): # self.assertIn('unset $mybool', out) def test_date_modify_and_query(self): - self.config['types'] = {'mydate': u'date'} + self.config['types'] = {'mydate': 'date'} # FIXME parsing should also work with default time format self.config['time_format'] = '%Y-%m-%d' - old = self.add_item(artist=u'prince') - new = self.add_item(artist=u'britney') + old = self.add_item(artist='prince') + new = self.add_item(artist='britney') # Do not match unset values - out = self.list(u'mydate:..2000') - self.assertEqual(u'', out) + out = self.list('mydate:..2000') + self.assertEqual('', out) - self.modify(u'mydate=1999-01-01', u'artist:prince') + self.modify('mydate=1999-01-01', 'artist:prince') old.load() self.assertEqual(old['mydate'], mktime(1999, 1, 1)) - self.modify(u'mydate=1999-12-30', u'artist:britney') + self.modify('mydate=1999-12-30', 'artist:britney') new.load() self.assertEqual(new['mydate'], mktime(1999, 12, 30)) # Match in range - out = self.list(u'mydate:..1999-07', u'$artist $mydate') - self.assertEqual(u'prince 1999-01-01', out) + out = self.list('mydate:..1999-07', '$artist $mydate') + self.assertEqual('prince 1999-01-01', out) # FIXME some sort of timezone issue here # out = self.list('mydate:1999-12-30', '$artist $mydate') @@ -143,50 +141,50 @@ class TypesPluginTest(unittest.TestCase, TestHelper): def test_unknown_type_error(self): self.config['types'] = {'flex': 'unkown type'} with self.assertRaises(ConfigValueError): - self.run_command(u'ls') + self.run_command('ls') def test_template_if_def(self): # Tests for a subtle bug when using %ifdef in templates along with # types that have truthy default values (e.g. '0', '0.0', 'False') # https://github.com/beetbox/beets/issues/3852 - self.config['types'] = {'playcount': u'int', 'rating': u'float', - 'starred': u'bool'} + self.config['types'] = {'playcount': 'int', 'rating': 'float', + 'starred': 'bool'} - with_fields = self.add_item(artist=u'prince') - self.modify(u'playcount=10', u'artist=prince') - self.modify(u'rating=5.0', u'artist=prince') - self.modify(u'starred=yes', u'artist=prince') + with_fields = self.add_item(artist='prince') + self.modify('playcount=10', 'artist=prince') + self.modify('rating=5.0', 'artist=prince') + self.modify('starred=yes', 'artist=prince') with_fields.load() - without_fields = self.add_item(artist=u'britney') + without_fields = self.add_item(artist='britney') - int_template = u'%ifdef{playcount,Play count: $playcount,Not played}' + int_template = '%ifdef{playcount,Play count: $playcount,Not played}' self.assertEqual(with_fields.evaluate_template(int_template), - u'Play count: 10') + 'Play count: 10') self.assertEqual(without_fields.evaluate_template(int_template), - u'Not played') + 'Not played') - float_template = u'%ifdef{rating,Rating: $rating,Not rated}' + float_template = '%ifdef{rating,Rating: $rating,Not rated}' self.assertEqual(with_fields.evaluate_template(float_template), - u'Rating: 5.0') + 'Rating: 5.0') self.assertEqual(without_fields.evaluate_template(float_template), - u'Not rated') + 'Not rated') - bool_template = u'%ifdef{starred,Starred: $starred,Not starred}' + bool_template = '%ifdef{starred,Starred: $starred,Not starred}' self.assertIn(with_fields.evaluate_template(bool_template).lower(), - (u'starred: true', u'starred: yes', u'starred: y')) + ('starred: true', 'starred: yes', 'starred: y')) self.assertEqual(without_fields.evaluate_template(bool_template), - u'Not starred') + 'Not starred') def modify(self, *args): - return self.run_with_output(u'modify', u'--yes', u'--nowrite', - u'--nomove', *args) + return self.run_with_output('modify', '--yes', '--nowrite', + '--nomove', *args) - def list(self, query, fmt=u'$artist - $album - $title'): - return self.run_with_output(u'ls', u'-f', fmt, query).strip() + def list(self, query, fmt='$artist - $album - $title'): + return self.run_with_output('ls', '-f', fmt, query).strip() - def list_album(self, query, fmt=u'$albumartist - $album - $title'): - return self.run_with_output(u'ls', u'-a', u'-f', fmt, query).strip() + def list_album(self, query, fmt='$albumartist - $album - $title'): + return self.run_with_output('ls', '-a', '-f', fmt, query).strip() def mktime(*args): diff --git a/test/test_ui.py b/test/test_ui.py index 25dd68ed7..9f3fbdaa5 100644 --- a/test/test_ui.py +++ b/test/test_ui.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -15,7 +14,6 @@ """Tests for the command-line interface. """ -from __future__ import division, absolute_import, print_function import os import shutil @@ -25,7 +23,7 @@ import platform import sys import unittest -from mock import patch, Mock +from unittest.mock import patch, Mock from test import _common from test.helper import capture_stdout, has_program, TestHelper, control_stdin @@ -50,68 +48,68 @@ class ListTest(unittest.TestCase): self.lib.add(self.item) self.lib.add_album([self.item]) - def _run_list(self, query=u'', album=False, path=False, fmt=u''): + def _run_list(self, query='', album=False, path=False, fmt=''): with capture_stdout() as stdout: commands.list_items(self.lib, query, album, fmt) return stdout def test_list_outputs_item(self): stdout = self._run_list() - self.assertIn(u'the title', stdout.getvalue()) + self.assertIn('the title', stdout.getvalue()) def test_list_unicode_query(self): - self.item.title = u'na\xefve' + self.item.title = 'na\xefve' self.item.store() self.lib._connection().commit() - stdout = self._run_list([u'na\xefve']) + stdout = self._run_list(['na\xefve']) out = stdout.getvalue() - self.assertTrue(u'na\xefve' in out) + self.assertTrue('na\xefve' in out) def test_list_item_path(self): - stdout = self._run_list(fmt=u'$path') - self.assertEqual(stdout.getvalue().strip(), u'xxx/yyy') + stdout = self._run_list(fmt='$path') + self.assertEqual(stdout.getvalue().strip(), 'xxx/yyy') def test_list_album_outputs_something(self): stdout = self._run_list(album=True) self.assertGreater(len(stdout.getvalue()), 0) def test_list_album_path(self): - stdout = self._run_list(album=True, fmt=u'$path') - self.assertEqual(stdout.getvalue().strip(), u'xxx') + stdout = self._run_list(album=True, fmt='$path') + self.assertEqual(stdout.getvalue().strip(), 'xxx') def test_list_album_omits_title(self): stdout = self._run_list(album=True) - self.assertNotIn(u'the title', stdout.getvalue()) + self.assertNotIn('the title', stdout.getvalue()) def test_list_uses_track_artist(self): stdout = self._run_list() - self.assertIn(u'the artist', stdout.getvalue()) - self.assertNotIn(u'the album artist', stdout.getvalue()) + self.assertIn('the artist', stdout.getvalue()) + self.assertNotIn('the album artist', stdout.getvalue()) def test_list_album_uses_album_artist(self): stdout = self._run_list(album=True) - self.assertNotIn(u'the artist', stdout.getvalue()) - self.assertIn(u'the album artist', stdout.getvalue()) + self.assertNotIn('the artist', stdout.getvalue()) + self.assertIn('the album artist', stdout.getvalue()) def test_list_item_format_artist(self): - stdout = self._run_list(fmt=u'$artist') - self.assertIn(u'the artist', stdout.getvalue()) + stdout = self._run_list(fmt='$artist') + self.assertIn('the artist', stdout.getvalue()) def test_list_item_format_multiple(self): - stdout = self._run_list(fmt=u'$artist - $album - $year') - self.assertEqual(u'the artist - the album - 0001', + stdout = self._run_list(fmt='$artist - $album - $year') + self.assertEqual('the artist - the album - 0001', stdout.getvalue().strip()) def test_list_album_format(self): - stdout = self._run_list(album=True, fmt=u'$genre') - self.assertIn(u'the genre', stdout.getvalue()) - self.assertNotIn(u'the album', stdout.getvalue()) + stdout = self._run_list(album=True, fmt='$genre') + self.assertIn('the genre', stdout.getvalue()) + self.assertNotIn('the album', stdout.getvalue()) class RemoveTest(_common.TestCase, TestHelper): def setUp(self): - super(RemoveTest, self).setUp() + super().setUp() self.io.install() @@ -127,26 +125,26 @@ class RemoveTest(_common.TestCase, TestHelper): def test_remove_items_no_delete(self): self.io.addinput('y') - commands.remove_items(self.lib, u'', False, False, False) + commands.remove_items(self.lib, '', False, False, False) items = self.lib.items() self.assertEqual(len(list(items)), 0) self.assertTrue(os.path.exists(self.i.path)) def test_remove_items_with_delete(self): self.io.addinput('y') - commands.remove_items(self.lib, u'', False, True, False) + commands.remove_items(self.lib, '', False, True, False) items = self.lib.items() self.assertEqual(len(list(items)), 0) self.assertFalse(os.path.exists(self.i.path)) def test_remove_items_with_force_no_delete(self): - commands.remove_items(self.lib, u'', False, False, True) + commands.remove_items(self.lib, '', False, False, True) items = self.lib.items() self.assertEqual(len(list(items)), 0) self.assertTrue(os.path.exists(self.i.path)) def test_remove_items_with_force_delete(self): - commands.remove_items(self.lib, u'', False, True, True) + commands.remove_items(self.lib, '', False, True, True) items = self.lib.items() self.assertEqual(len(list(items)), 0) self.assertFalse(os.path.exists(self.i.path)) @@ -158,7 +156,7 @@ class RemoveTest(_common.TestCase, TestHelper): for s in ('s', 'y', 'n'): self.io.addinput(s) - commands.remove_items(self.lib, u'', False, True, False) + commands.remove_items(self.lib, '', False, True, False) items = self.lib.items() self.assertEqual(len(list(items)), 1) # There is probably no guarantee that the items are queried in any @@ -180,7 +178,7 @@ class RemoveTest(_common.TestCase, TestHelper): for s in ('s', 'y', 'n'): self.io.addinput(s) - commands.remove_items(self.lib, u'', True, True, False) + commands.remove_items(self.lib, '', True, True, False) items = self.lib.items() self.assertEqual(len(list(items)), 2) # incl. the item from setUp() # See test_remove_items_select_with_delete() @@ -210,58 +208,58 @@ class ModifyTest(unittest.TestCase, TestHelper): # Item tests def test_modify_item(self): - self.modify(u"title=newTitle") + self.modify("title=newTitle") item = self.lib.items().get() - self.assertEqual(item.title, u'newTitle') + self.assertEqual(item.title, 'newTitle') def test_modify_item_abort(self): item = self.lib.items().get() title = item.title - self.modify_inp('n', u"title=newTitle") + self.modify_inp('n', "title=newTitle") item = self.lib.items().get() self.assertEqual(item.title, title) def test_modify_item_no_change(self): - title = u"Tracktitle" + title = "Tracktitle" item = self.add_item_fixture(title=title) - self.modify_inp('y', u"title", u"title={0}".format(title)) + self.modify_inp('y', "title", f"title={title}") item = self.lib.items(title).get() self.assertEqual(item.title, title) def test_modify_write_tags(self): - self.modify(u"title=newTitle") + self.modify("title=newTitle") item = self.lib.items().get() item.read() - self.assertEqual(item.title, u'newTitle') + self.assertEqual(item.title, 'newTitle') def test_modify_dont_write_tags(self): - self.modify(u"--nowrite", u"title=newTitle") + self.modify("--nowrite", "title=newTitle") item = self.lib.items().get() item.read() self.assertNotEqual(item.title, 'newTitle') def test_move(self): - self.modify(u"title=newTitle") + self.modify("title=newTitle") item = self.lib.items().get() self.assertIn(b'newTitle', item.path) def test_not_move(self): - self.modify(u"--nomove", u"title=newTitle") + self.modify("--nomove", "title=newTitle") item = self.lib.items().get() self.assertNotIn(b'newTitle', item.path) def test_no_write_no_move(self): - self.modify(u"--nomove", u"--nowrite", u"title=newTitle") + self.modify("--nomove", "--nowrite", "title=newTitle") item = self.lib.items().get() item.read() self.assertNotIn(b'newTitle', item.path) - self.assertNotEqual(item.title, u'newTitle') + self.assertNotEqual(item.title, 'newTitle') def test_update_mtime(self): item = self.item old_mtime = item.mtime - self.modify(u"title=newTitle") + self.modify("title=newTitle") item.load() self.assertNotEqual(old_mtime, item.mtime) self.assertEqual(item.current_mtime(), item.mtime) @@ -269,53 +267,53 @@ class ModifyTest(unittest.TestCase, TestHelper): def test_reset_mtime_with_no_write(self): item = self.item - self.modify(u"--nowrite", u"title=newTitle") + self.modify("--nowrite", "title=newTitle") item.load() self.assertEqual(0, item.mtime) def test_selective_modify(self): - title = u"Tracktitle" - album = u"album" - original_artist = u"composer" - new_artist = u"coverArtist" + title = "Tracktitle" + album = "album" + original_artist = "composer" + new_artist = "coverArtist" for i in range(0, 10): - self.add_item_fixture(title=u"{0}{1}".format(title, i), + self.add_item_fixture(title=f"{title}{i}", artist=original_artist, album=album) self.modify_inp('s\ny\ny\ny\nn\nn\ny\ny\ny\ny\nn', - 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)) + title, f"artist={new_artist}") + original_items = self.lib.items(f"artist:{original_artist}") + new_items = self.lib.items(f"artist:{new_artist}") self.assertEqual(len(list(original_items)), 3) self.assertEqual(len(list(new_items)), 7) # Album Tests def test_modify_album(self): - self.modify(u"--album", u"album=newAlbum") + self.modify("--album", "album=newAlbum") album = self.lib.albums().get() - self.assertEqual(album.album, u'newAlbum') + self.assertEqual(album.album, 'newAlbum') def test_modify_album_write_tags(self): - self.modify(u"--album", u"album=newAlbum") + self.modify("--album", "album=newAlbum") item = self.lib.items().get() item.read() - self.assertEqual(item.album, u'newAlbum') + self.assertEqual(item.album, 'newAlbum') def test_modify_album_dont_write_tags(self): - self.modify(u"--album", u"--nowrite", u"album=newAlbum") + self.modify("--album", "--nowrite", "album=newAlbum") item = self.lib.items().get() item.read() - self.assertEqual(item.album, u'the album') + self.assertEqual(item.album, 'the album') def test_album_move(self): - self.modify(u"--album", u"album=newAlbum") + self.modify("--album", "album=newAlbum") item = self.lib.items().get() item.read() self.assertIn(b'newAlbum', item.path) def test_album_not_move(self): - self.modify(u"--nomove", u"--album", u"album=newAlbum") + self.modify("--nomove", "--album", "album=newAlbum") item = self.lib.items().get() item.read() self.assertNotIn(b'newAlbum', item.path) @@ -323,62 +321,62 @@ class ModifyTest(unittest.TestCase, TestHelper): # Misc def test_write_initial_key_tag(self): - self.modify(u"initial_key=C#m") + self.modify("initial_key=C#m") item = self.lib.items().get() mediafile = MediaFile(syspath(item.path)) - self.assertEqual(mediafile.initial_key, u'C#m') + self.assertEqual(mediafile.initial_key, 'C#m') def test_set_flexattr(self): - self.modify(u"flexattr=testAttr") + self.modify("flexattr=testAttr") item = self.lib.items().get() - self.assertEqual(item.flexattr, u'testAttr') + self.assertEqual(item.flexattr, 'testAttr') def test_remove_flexattr(self): item = self.lib.items().get() - item.flexattr = u'testAttr' + item.flexattr = 'testAttr' item.store() - self.modify(u"flexattr!") + self.modify("flexattr!") item = self.lib.items().get() - self.assertNotIn(u"flexattr", item) + self.assertNotIn("flexattr", item) - @unittest.skip(u'not yet implemented') + @unittest.skip('not yet implemented') def test_delete_initial_key_tag(self): item = self.lib.items().get() - item.initial_key = u'C#m' + item.initial_key = 'C#m' item.write() item.store() mediafile = MediaFile(syspath(item.path)) - self.assertEqual(mediafile.initial_key, u'C#m') + self.assertEqual(mediafile.initial_key, 'C#m') - self.modify(u"initial_key!") + self.modify("initial_key!") mediafile = MediaFile(syspath(item.path)) self.assertIsNone(mediafile.initial_key) def test_arg_parsing_colon_query(self): - (query, mods, dels) = commands.modify_parse_args([u"title:oldTitle", - u"title=newTitle"]) - self.assertEqual(query, [u"title:oldTitle"]) - self.assertEqual(mods, {"title": u"newTitle"}) + (query, mods, dels) = commands.modify_parse_args(["title:oldTitle", + "title=newTitle"]) + self.assertEqual(query, ["title:oldTitle"]) + self.assertEqual(mods, {"title": "newTitle"}) def test_arg_parsing_delete(self): - (query, mods, dels) = commands.modify_parse_args([u"title:oldTitle", - u"title!"]) - self.assertEqual(query, [u"title:oldTitle"]) + (query, mods, dels) = commands.modify_parse_args(["title:oldTitle", + "title!"]) + self.assertEqual(query, ["title:oldTitle"]) self.assertEqual(dels, ["title"]) def test_arg_parsing_query_with_exclaimation(self): - (query, mods, dels) = commands.modify_parse_args([u"title:oldTitle!", - u"title=newTitle!"]) - self.assertEqual(query, [u"title:oldTitle!"]) - self.assertEqual(mods, {"title": u"newTitle!"}) + (query, mods, dels) = commands.modify_parse_args(["title:oldTitle!", + "title=newTitle!"]) + self.assertEqual(query, ["title:oldTitle!"]) + self.assertEqual(mods, {"title": "newTitle!"}) def test_arg_parsing_equals_in_value(self): - (query, mods, dels) = commands.modify_parse_args([u"title:foo=bar", - u"title=newTitle"]) - self.assertEqual(query, [u"title:foo=bar"]) - self.assertEqual(mods, {"title": u"newTitle"}) + (query, mods, dels) = commands.modify_parse_args(["title:foo=bar", + "title=newTitle"]) + self.assertEqual(query, ["title:foo=bar"]) + self.assertEqual(mods, {"title": "newTitle"}) class WriteTest(unittest.TestCase, TestHelper): @@ -394,7 +392,7 @@ class WriteTest(unittest.TestCase, TestHelper): def test_update_mtime(self): item = self.add_item_fixture() - item['title'] = u'a new title' + item['title'] = 'a new title' item.store() item = self.lib.items().get() @@ -425,18 +423,18 @@ class WriteTest(unittest.TestCase, TestHelper): item.read() old_title = item.title - item.title = u'new title' + item.title = 'new title' item.store() output = self.write_cmd() - self.assertTrue(u'{0} -> new title'.format(old_title) + self.assertTrue(f'{old_title} -> new title' in output) class MoveTest(_common.TestCase): def setUp(self): - super(MoveTest, self).setUp() + super().setUp() self.io.install() @@ -533,7 +531,7 @@ class MoveTest(_common.TestCase): class UpdateTest(_common.TestCase): def setUp(self): - super(UpdateTest, self).setUp() + super().setUp() self.io.install() @@ -591,15 +589,15 @@ class UpdateTest(_common.TestCase): def test_modified_metadata_detected(self): mf = MediaFile(syspath(self.i.path)) - mf.title = u'differentTitle' + mf.title = 'differentTitle' mf.save() self._update() item = self.lib.items().get() - self.assertEqual(item.title, u'differentTitle') + self.assertEqual(item.title, 'differentTitle') def test_modified_metadata_moved(self): mf = MediaFile(syspath(self.i.path)) - mf.title = u'differentTitle' + mf.title = 'differentTitle' mf.save() self._update(move=True) item = self.lib.items().get() @@ -607,7 +605,7 @@ class UpdateTest(_common.TestCase): def test_modified_metadata_not_moved(self): mf = MediaFile(syspath(self.i.path)) - mf.title = u'differentTitle' + mf.title = 'differentTitle' mf.save() self._update(move=False) item = self.lib.items().get() @@ -615,27 +613,27 @@ class UpdateTest(_common.TestCase): def test_selective_modified_metadata_moved(self): mf = MediaFile(syspath(self.i.path)) - mf.title = u'differentTitle' - mf.genre = u'differentGenre' + mf.title = 'differentTitle' + mf.genre = 'differentGenre' mf.save() self._update(move=True, fields=['title']) item = self.lib.items().get() self.assertTrue(b'differentTitle' in item.path) - self.assertNotEqual(item.genre, u'differentGenre') + self.assertNotEqual(item.genre, 'differentGenre') def test_selective_modified_metadata_not_moved(self): mf = MediaFile(syspath(self.i.path)) - mf.title = u'differentTitle' - mf.genre = u'differentGenre' + mf.title = 'differentTitle' + mf.genre = 'differentGenre' mf.save() self._update(move=False, fields=['title']) item = self.lib.items().get() self.assertTrue(b'differentTitle' not in item.path) - self.assertNotEqual(item.genre, u'differentGenre') + self.assertNotEqual(item.genre, 'differentGenre') def test_modified_album_metadata_moved(self): mf = MediaFile(syspath(self.i.path)) - mf.album = u'differentAlbum' + mf.album = 'differentAlbum' mf.save() self._update(move=True) item = self.lib.items().get() @@ -644,7 +642,7 @@ class UpdateTest(_common.TestCase): def test_modified_album_metadata_art_moved(self): artpath = self.album.artpath mf = MediaFile(syspath(self.i.path)) - mf.album = u'differentAlbum' + mf.album = 'differentAlbum' mf.save() self._update(move=True) album = self.lib.albums()[0] @@ -653,27 +651,27 @@ class UpdateTest(_common.TestCase): def test_selective_modified_album_metadata_moved(self): mf = MediaFile(syspath(self.i.path)) - mf.album = u'differentAlbum' - mf.genre = u'differentGenre' + mf.album = 'differentAlbum' + mf.genre = 'differentGenre' mf.save() self._update(move=True, fields=['album']) item = self.lib.items().get() self.assertTrue(b'differentAlbum' in item.path) - self.assertNotEqual(item.genre, u'differentGenre') + self.assertNotEqual(item.genre, 'differentGenre') def test_selective_modified_album_metadata_not_moved(self): mf = MediaFile(syspath(self.i.path)) - mf.album = u'differentAlbum' - mf.genre = u'differentGenre' + mf.album = 'differentAlbum' + mf.genre = 'differentGenre' mf.save() self._update(move=True, fields=['genre']) item = self.lib.items().get() self.assertTrue(b'differentAlbum' not in item.path) - self.assertEqual(item.genre, u'differentGenre') + self.assertEqual(item.genre, 'differentGenre') def test_mtime_match_skips_update(self): mf = MediaFile(syspath(self.i.path)) - mf.title = u'differentTitle' + mf.title = 'differentTitle' mf.save() # Make in-memory mtime match on-disk mtime. @@ -682,12 +680,12 @@ class UpdateTest(_common.TestCase): self._update(reset_mtime=False) item = self.lib.items().get() - self.assertEqual(item.title, u'full') + self.assertEqual(item.title, 'full') class PrintTest(_common.TestCase): def setUp(self): - super(PrintTest, self).setUp() + super().setUp() self.io.install() def test_print_without_locale(self): @@ -696,9 +694,9 @@ class PrintTest(_common.TestCase): del os.environ['LANG'] try: - ui.print_(u'something') + ui.print_('something') except TypeError: - self.fail(u'TypeError during print') + self.fail('TypeError during print') finally: if lang: os.environ['LANG'] = lang @@ -710,9 +708,9 @@ class PrintTest(_common.TestCase): os.environ['LC_CTYPE'] = 'UTF-8' try: - ui.print_(u'something') + ui.print_('something') except ValueError: - self.fail(u'ValueError during print') + self.fail('ValueError during print') finally: if old_lang: os.environ['LANG'] = old_lang @@ -783,7 +781,7 @@ class ConfigTest(unittest.TestCase, TestHelper, _common.Assertions): self.teardown_beets() def _make_test_cmd(self): - test_cmd = ui.Subcommand('test', help=u'test') + test_cmd = ui.Subcommand('test', help='test') def run(lib, options, args): test_cmd.lib = lib @@ -844,7 +842,7 @@ class ConfigTest(unittest.TestCase, TestHelper, _common.Assertions): self.run_command('test', lib=None) replacements = self.test_cmd.lib.replacements repls = [(p.pattern, s) for p, s in replacements] # Compare patterns. - self.assertEqual(repls, [(u'[xy]', 'z')]) + self.assertEqual(repls, [('[xy]', 'z')]) def test_multiple_replacements_parsed(self): with self.write_config_file() as config: @@ -853,8 +851,8 @@ class ConfigTest(unittest.TestCase, TestHelper, _common.Assertions): replacements = self.test_cmd.lib.replacements repls = [(p.pattern, s) for p, s in replacements] self.assertEqual(repls, [ - (u'[xy]', u'z'), - (u'foo', u'bar'), + ('[xy]', 'z'), + ('foo', 'bar'), ]) def test_cli_config_option(self): @@ -1028,7 +1026,7 @@ class ConfigTest(unittest.TestCase, TestHelper, _common.Assertions): class ShowModelChangeTest(_common.TestCase): def setUp(self): - super(ShowModelChangeTest, self).setUp() + super().setUp() self.io.install() self.a = _common.item() self.b = _common.item() @@ -1048,54 +1046,54 @@ class ShowModelChangeTest(_common.TestCase): self.b.title = 'x' change, out = self._show() self.assertTrue(change) - self.assertTrue(u'title' in out) + self.assertTrue('title' in out) def test_int_fixed_field_change(self): self.b.track = 9 change, out = self._show() self.assertTrue(change) - self.assertTrue(u'track' in out) + self.assertTrue('track' in out) def test_floats_close_to_identical(self): self.a.length = 1.00001 self.b.length = 1.00005 change, out = self._show() self.assertFalse(change) - self.assertEqual(out, u'') + self.assertEqual(out, '') def test_floats_different(self): self.a.length = 1.00001 self.b.length = 2.00001 change, out = self._show() self.assertTrue(change) - self.assertTrue(u'length' in out) + self.assertTrue('length' in out) def test_both_values_shown(self): - self.a.title = u'foo' - self.b.title = u'bar' + self.a.title = 'foo' + self.b.title = 'bar' change, out = self._show() - self.assertTrue(u'foo' in out) - self.assertTrue(u'bar' in out) + self.assertTrue('foo' in out) + self.assertTrue('bar' in out) class ShowChangeTest(_common.TestCase): def setUp(self): - super(ShowChangeTest, self).setUp() + super().setUp() self.io.install() self.items = [_common.item()] self.items[0].track = 1 self.items[0].path = b'/path/to/file.mp3' self.info = autotag.AlbumInfo( - album=u'the album', album_id=u'album id', artist=u'the artist', - artist_id=u'artist id', tracks=[ - autotag.TrackInfo(title=u'the title', track_id=u'track id', + album='the album', album_id='album id', artist='the artist', + artist_id='artist id', tracks=[ + autotag.TrackInfo(title='the title', track_id='track id', index=1) ] ) def _show_change(self, items=None, info=None, - cur_artist=u'the artist', cur_album=u'the album', + cur_artist='the artist', cur_album='the album', dist=0.1): """Return an unicode string representing the changes""" items = items or self.items @@ -1123,37 +1121,37 @@ class ShowChangeTest(_common.TestCase): self.assertTrue('correcting tags from:' in msg) def test_item_data_change(self): - self.items[0].title = u'different' + self.items[0].title = 'different' msg = self._show_change() self.assertTrue('different -> the title' in msg) def test_item_data_change_with_unicode(self): - self.items[0].title = u'caf\xe9' + self.items[0].title = 'caf\xe9' msg = self._show_change() - self.assertTrue(u'caf\xe9 -> the title' in msg) + self.assertTrue('caf\xe9 -> the title' in msg) def test_album_data_change_with_unicode(self): - msg = self._show_change(cur_artist=u'caf\xe9', - cur_album=u'another album') - self.assertTrue(u'correcting tags from:' in msg) + msg = self._show_change(cur_artist='caf\xe9', + cur_album='another album') + self.assertTrue('correcting tags from:' in msg) def test_item_data_change_title_missing(self): - self.items[0].title = u'' + self.items[0].title = '' msg = re.sub(r' +', ' ', self._show_change()) - self.assertTrue(u'file.mp3 -> the title' in msg) + self.assertTrue('file.mp3 -> the title' in msg) def test_item_data_change_title_missing_with_unicode_filename(self): - self.items[0].title = u'' - self.items[0].path = u'/path/to/caf\xe9.mp3'.encode('utf-8') + self.items[0].title = '' + self.items[0].path = '/path/to/caf\xe9.mp3'.encode() msg = re.sub(r' +', ' ', self._show_change()) - self.assertTrue(u'caf\xe9.mp3 -> the title' in msg or - u'caf.mp3 ->' in msg) + self.assertTrue('caf\xe9.mp3 -> the title' in msg or + 'caf.mp3 ->' in msg) @patch('beets.library.Item.try_filesize', Mock(return_value=987)) class SummarizeItemsTest(_common.TestCase): def setUp(self): - super(SummarizeItemsTest, self).setUp() + super().setUp() item = library.Item() item.bitrate = 4321 item.length = 10 * 60 + 54 @@ -1162,41 +1160,41 @@ class SummarizeItemsTest(_common.TestCase): def test_summarize_item(self): summary = commands.summarize_items([], True) - self.assertEqual(summary, u"") + self.assertEqual(summary, "") summary = commands.summarize_items([self.item], True) - self.assertEqual(summary, u"F, 4kbps, 10:54, 987.0 B") + self.assertEqual(summary, "F, 4kbps, 10:54, 987.0 B") def test_summarize_items(self): summary = commands.summarize_items([], False) - self.assertEqual(summary, u"0 items") + self.assertEqual(summary, "0 items") summary = commands.summarize_items([self.item], False) - self.assertEqual(summary, u"1 items, F, 4kbps, 10:54, 987.0 B") + self.assertEqual(summary, "1 items, F, 4kbps, 10:54, 987.0 B") # make a copy of self.item i2 = self.item.copy() summary = commands.summarize_items([self.item, i2], False) - self.assertEqual(summary, u"2 items, F, 4kbps, 21:48, 1.9 KiB") + self.assertEqual(summary, "2 items, F, 4kbps, 21:48, 1.9 KiB") i2.format = "G" summary = commands.summarize_items([self.item, i2], False) - self.assertEqual(summary, u"2 items, F 1, G 1, 4kbps, 21:48, 1.9 KiB") + self.assertEqual(summary, "2 items, F 1, G 1, 4kbps, 21:48, 1.9 KiB") summary = commands.summarize_items([self.item, i2, i2], False) - self.assertEqual(summary, u"3 items, G 2, F 1, 4kbps, 32:42, 2.9 KiB") + self.assertEqual(summary, "3 items, G 2, F 1, 4kbps, 32:42, 2.9 KiB") class PathFormatTest(_common.TestCase): def test_custom_paths_prepend(self): default_formats = ui.get_path_formats() - config['paths'] = {u'foo': u'bar'} + config['paths'] = {'foo': 'bar'} pf = ui.get_path_formats() key, tmpl = pf[0] - self.assertEqual(key, u'foo') - self.assertEqual(tmpl.original, u'bar') + self.assertEqual(key, 'foo') + self.assertEqual(tmpl.original, 'bar') self.assertEqual(pf[1:], default_formats) @@ -1224,7 +1222,7 @@ class CompletionTest(_common.TestCase, TestHelper): # commands via stdin. cmd = os.environ.get('BEETS_TEST_SHELL', '/bin/bash --norc').split() if not has_program(cmd[0]): - self.skipTest(u'bash not available') + self.skipTest('bash not available') tester = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, env=env) @@ -1234,12 +1232,12 @@ class CompletionTest(_common.TestCase, TestHelper): bash_completion = path break else: - self.skipTest(u'bash-completion script not found') + self.skipTest('bash-completion script not found') try: with open(util.syspath(bash_completion), 'rb') as f: tester.stdin.writelines(f) - except IOError: - self.skipTest(u'could not read bash-completion script') + except OSError: + self.skipTest('could not read bash-completion script') # Load completion script. self.io.install() @@ -1255,7 +1253,7 @@ class CompletionTest(_common.TestCase, TestHelper): out, err = tester.communicate() if tester.returncode != 0 or out != b'completion tests passed\n': print(out.decode('utf-8')) - self.fail(u'test/test_completion.sh did not execute properly') + self.fail('test/test_completion.sh did not execute properly') class CommonOptionsParserCliTest(unittest.TestCase, TestHelper): @@ -1275,63 +1273,63 @@ class CommonOptionsParserCliTest(unittest.TestCase, TestHelper): self.teardown_beets() def test_base(self): - l = self.run_with_output(u'ls') - self.assertEqual(l, u'the artist - the album - the title\n') + l = self.run_with_output('ls') + self.assertEqual(l, 'the artist - the album - the title\n') - l = self.run_with_output(u'ls', u'-a') - self.assertEqual(l, u'the album artist - the album\n') + l = self.run_with_output('ls', '-a') + self.assertEqual(l, 'the album artist - the album\n') def test_path_option(self): - l = self.run_with_output(u'ls', u'-p') - self.assertEqual(l, u'xxx/yyy\n') + l = self.run_with_output('ls', '-p') + self.assertEqual(l, 'xxx/yyy\n') - l = self.run_with_output(u'ls', u'-a', u'-p') - self.assertEqual(l, u'xxx\n') + l = self.run_with_output('ls', '-a', '-p') + self.assertEqual(l, 'xxx\n') def test_format_option(self): - l = self.run_with_output(u'ls', u'-f', u'$artist') - self.assertEqual(l, u'the artist\n') + l = self.run_with_output('ls', '-f', '$artist') + self.assertEqual(l, 'the artist\n') - l = self.run_with_output(u'ls', u'-a', u'-f', u'$albumartist') - self.assertEqual(l, u'the album artist\n') + l = self.run_with_output('ls', '-a', '-f', '$albumartist') + self.assertEqual(l, 'the album artist\n') def test_format_option_unicode(self): l = self.run_with_output(b'ls', b'-f', - u'caf\xe9'.encode(util.arg_encoding())) - self.assertEqual(l, u'caf\xe9\n') + 'caf\xe9'.encode(util.arg_encoding())) + self.assertEqual(l, 'caf\xe9\n') def test_root_format_option(self): - l = self.run_with_output(u'--format-item', u'$artist', - u'--format-album', u'foo', u'ls') - self.assertEqual(l, u'the artist\n') + l = self.run_with_output('--format-item', '$artist', + '--format-album', 'foo', 'ls') + self.assertEqual(l, 'the artist\n') - l = self.run_with_output(u'--format-item', u'foo', - u'--format-album', u'$albumartist', - u'ls', u'-a') - self.assertEqual(l, u'the album artist\n') + l = self.run_with_output('--format-item', 'foo', + '--format-album', '$albumartist', + 'ls', '-a') + self.assertEqual(l, 'the album artist\n') def test_help(self): - l = self.run_with_output(u'help') - self.assertIn(u'Usage:', l) + l = self.run_with_output('help') + self.assertIn('Usage:', l) - l = self.run_with_output(u'help', u'list') - self.assertIn(u'Usage:', l) + l = self.run_with_output('help', 'list') + self.assertIn('Usage:', l) with self.assertRaises(ui.UserError): - self.run_command(u'help', u'this.is.not.a.real.command') + self.run_command('help', 'this.is.not.a.real.command') def test_stats(self): - l = self.run_with_output(u'stats') - self.assertIn(u'Approximate total size:', l) + l = self.run_with_output('stats') + self.assertIn('Approximate total size:', l) # # Need to have more realistic library setup for this to work # l = self.run_with_output('stats', '-e') # self.assertIn('Total size:', l) def test_version(self): - l = self.run_with_output(u'version') - self.assertIn(u'Python version', l) - self.assertIn(u'no plugins loaded', l) + l = self.run_with_output('version') + self.assertIn('Python version', l) + self.assertIn('no plugins loaded', l) # # Need to have plugin loaded # l = self.run_with_output('version') @@ -1352,8 +1350,8 @@ class CommonOptionsParserTest(unittest.TestCase, TestHelper): self.assertTrue(bool(parser._album_flags)) self.assertEqual(parser.parse_args([]), ({'album': None}, [])) - self.assertEqual(parser.parse_args([u'-a']), ({'album': True}, [])) - self.assertEqual(parser.parse_args([u'--album']), + self.assertEqual(parser.parse_args(['-a']), ({'album': True}, [])) + self.assertEqual(parser.parse_args(['--album']), ({'album': True}, [])) def test_path_option(self): @@ -1363,15 +1361,15 @@ class CommonOptionsParserTest(unittest.TestCase, TestHelper): config['format_item'].set('$foo') self.assertEqual(parser.parse_args([]), ({'path': None}, [])) - self.assertEqual(config['format_item'].as_str(), u'$foo') + self.assertEqual(config['format_item'].as_str(), '$foo') - self.assertEqual(parser.parse_args([u'-p']), - ({'path': True, 'format': u'$path'}, [])) + self.assertEqual(parser.parse_args(['-p']), + ({'path': True, 'format': '$path'}, [])) self.assertEqual(parser.parse_args(['--path']), - ({'path': True, 'format': u'$path'}, [])) + ({'path': True, 'format': '$path'}, [])) - self.assertEqual(config['format_item'].as_str(), u'$path') - self.assertEqual(config['format_album'].as_str(), u'$path') + self.assertEqual(config['format_item'].as_str(), '$path') + self.assertEqual(config['format_album'].as_str(), '$path') def test_format_option(self): parser = ui.CommonOptionsParser() @@ -1380,15 +1378,15 @@ class CommonOptionsParserTest(unittest.TestCase, TestHelper): config['format_item'].set('$foo') self.assertEqual(parser.parse_args([]), ({'format': None}, [])) - self.assertEqual(config['format_item'].as_str(), u'$foo') + self.assertEqual(config['format_item'].as_str(), '$foo') - self.assertEqual(parser.parse_args([u'-f', u'$bar']), - ({'format': u'$bar'}, [])) - self.assertEqual(parser.parse_args([u'--format', u'$baz']), - ({'format': u'$baz'}, [])) + self.assertEqual(parser.parse_args(['-f', '$bar']), + ({'format': '$bar'}, [])) + self.assertEqual(parser.parse_args(['--format', '$baz']), + ({'format': '$baz'}, [])) - self.assertEqual(config['format_item'].as_str(), u'$baz') - self.assertEqual(config['format_album'].as_str(), u'$baz') + self.assertEqual(config['format_item'].as_str(), '$baz') + self.assertEqual(config['format_album'].as_str(), '$baz') def test_format_option_with_target(self): with self.assertRaises(KeyError): @@ -1400,11 +1398,11 @@ class CommonOptionsParserTest(unittest.TestCase, TestHelper): config['format_item'].set('$item') config['format_album'].set('$album') - self.assertEqual(parser.parse_args([u'-f', u'$bar']), - ({'format': u'$bar'}, [])) + self.assertEqual(parser.parse_args(['-f', '$bar']), + ({'format': '$bar'}, [])) - self.assertEqual(config['format_item'].as_str(), u'$bar') - self.assertEqual(config['format_album'].as_str(), u'$album') + self.assertEqual(config['format_item'].as_str(), '$bar') + self.assertEqual(config['format_album'].as_str(), '$album') def test_format_option_with_album(self): parser = ui.CommonOptionsParser() @@ -1414,16 +1412,16 @@ class CommonOptionsParserTest(unittest.TestCase, TestHelper): config['format_item'].set('$item') config['format_album'].set('$album') - parser.parse_args([u'-f', u'$bar']) - self.assertEqual(config['format_item'].as_str(), u'$bar') - self.assertEqual(config['format_album'].as_str(), u'$album') + parser.parse_args(['-f', '$bar']) + self.assertEqual(config['format_item'].as_str(), '$bar') + self.assertEqual(config['format_album'].as_str(), '$album') - parser.parse_args([u'-a', u'-f', u'$foo']) - self.assertEqual(config['format_item'].as_str(), u'$bar') - self.assertEqual(config['format_album'].as_str(), u'$foo') + parser.parse_args(['-a', '-f', '$foo']) + self.assertEqual(config['format_item'].as_str(), '$bar') + self.assertEqual(config['format_album'].as_str(), '$foo') - parser.parse_args([u'-f', u'$foo2', u'-a']) - self.assertEqual(config['format_album'].as_str(), u'$foo2') + parser.parse_args(['-f', '$foo2', '-a']) + self.assertEqual(config['format_album'].as_str(), '$foo2') def test_add_all_common_options(self): parser = ui.CommonOptionsParser() diff --git a/test/test_ui_commands.py b/test/test_ui_commands.py index 764d1b009..7ef8aa534 100644 --- a/test/test_ui_commands.py +++ b/test/test_ui_commands.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -16,7 +15,6 @@ """Test module for file ui/commands.py """ -from __future__ import division, absolute_import, print_function import os import shutil @@ -31,7 +29,7 @@ from beets.ui import commands class QueryTest(_common.TestCase): def setUp(self): - super(QueryTest, self).setUp() + super().setUp() self.libdir = os.path.join(self.temp_dir, b'testlibdir') os.mkdir(self.libdir) @@ -89,7 +87,7 @@ class QueryTest(_common.TestCase): class FieldsTest(_common.LibTestCase): def setUp(self): - super(FieldsTest, self).setUp() + super().setUp() self.io.install() diff --git a/test/test_ui_importer.py b/test/test_ui_importer.py index 229dac5d2..43b833669 100644 --- a/test/test_ui_importer.py +++ b/test/test_ui_importer.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -19,7 +18,6 @@ test_importer module. But here the test importer inherits from ``TerminalImportSession``. So we test this class, too. """ -from __future__ import division, absolute_import, print_function import unittest from test._common import DummyIO @@ -34,7 +32,7 @@ class TerminalImportSessionFixture(TerminalImportSession): def __init__(self, *args, **kwargs): self.io = kwargs.pop('io') - super(TerminalImportSessionFixture, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self._choices = [] default_choice = importer.action.APPLY @@ -47,11 +45,11 @@ class TerminalImportSessionFixture(TerminalImportSession): def choose_match(self, task): self._add_choice_input() - return super(TerminalImportSessionFixture, self).choose_match(task) + return super().choose_match(task) def choose_item(self, task): self._add_choice_input() - return super(TerminalImportSessionFixture, self).choose_item(task) + return super().choose_item(task) def _add_choice_input(self): try: @@ -60,24 +58,24 @@ class TerminalImportSessionFixture(TerminalImportSession): choice = self.default_choice if choice == importer.action.APPLY: - self.io.addinput(u'A') + self.io.addinput('A') elif choice == importer.action.ASIS: - self.io.addinput(u'U') + self.io.addinput('U') elif choice == importer.action.ALBUMS: - self.io.addinput(u'G') + self.io.addinput('G') elif choice == importer.action.TRACKS: - self.io.addinput(u'T') + self.io.addinput('T') elif choice == importer.action.SKIP: - self.io.addinput(u'S') + self.io.addinput('S') elif isinstance(choice, int): - self.io.addinput(u'M') - self.io.addinput(six.text_type(choice)) + self.io.addinput('M') + self.io.addinput(str(choice)) self._add_choice_input() else: - raise Exception(u'Unknown choice %s' % choice) + raise Exception('Unknown choice %s' % choice) -class TerminalImportSessionSetup(object): +class TerminalImportSessionSetup: """Overwrites test_importer.ImportHelper to provide a terminal importer """ diff --git a/test/test_ui_init.py b/test/test_ui_init.py index 41f167193..bb9a922a5 100644 --- a/test/test_ui_init.py +++ b/test/test_ui_init.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -16,7 +15,6 @@ """Test module for file ui/__init__.py """ -from __future__ import division, absolute_import, print_function import unittest from test import _common @@ -26,7 +24,7 @@ from beets import ui class InputMethodsTest(_common.TestCase): def setUp(self): - super(InputMethodsTest, self).setUp() + super().setUp() self.io.install() def _print_helper(self, s): @@ -86,7 +84,7 @@ class InputMethodsTest(_common.TestCase): class InitTest(_common.LibTestCase): def setUp(self): - super(InitTest, self).setUp() + super().setUp() def test_human_bytes(self): tests = [ diff --git a/test/test_util.py b/test/test_util.py index b2452d597..230d28bd6 100644 --- a/test/test_util.py +++ b/test/test_util.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -14,7 +13,6 @@ # included in all copies or substantial portions of the Software. """Tests for base utils from the beets.util package. """ -from __future__ import division, absolute_import, print_function import sys import re @@ -22,7 +20,7 @@ import os import subprocess import unittest -from mock import patch, Mock +from unittest.mock import patch, Mock from test import _common from beets import util @@ -43,66 +41,66 @@ class UtilTest(unittest.TestCase): @patch('os.execlp') @patch('beets.util.open_anything') def test_interactive_open(self, mock_open, mock_execlp): - mock_open.return_value = u'tagada' + mock_open.return_value = 'tagada' util.interactive_open(['foo'], util.open_anything()) - mock_execlp.assert_called_once_with(u'tagada', u'tagada', u'foo') + mock_execlp.assert_called_once_with('tagada', 'tagada', 'foo') mock_execlp.reset_mock() - util.interactive_open(['foo'], u'bar') - mock_execlp.assert_called_once_with(u'bar', u'bar', u'foo') + util.interactive_open(['foo'], 'bar') + mock_execlp.assert_called_once_with('bar', 'bar', 'foo') def test_sanitize_unix_replaces_leading_dot(self): with _common.platform_posix(): - p = util.sanitize_path(u'one/.two/three') - self.assertFalse(u'.' in p) + p = util.sanitize_path('one/.two/three') + self.assertFalse('.' in p) def test_sanitize_windows_replaces_trailing_dot(self): with _common.platform_windows(): - p = util.sanitize_path(u'one/two./three') - self.assertFalse(u'.' in p) + p = util.sanitize_path('one/two./three') + self.assertFalse('.' in p) def test_sanitize_windows_replaces_illegal_chars(self): with _common.platform_windows(): - p = util.sanitize_path(u':*?"<>|') - self.assertFalse(u':' in p) - self.assertFalse(u'*' in p) - self.assertFalse(u'?' in p) - self.assertFalse(u'"' in p) - self.assertFalse(u'<' in p) - self.assertFalse(u'>' in p) - self.assertFalse(u'|' in p) + p = util.sanitize_path(':*?"<>|') + self.assertFalse(':' in p) + self.assertFalse('*' in p) + self.assertFalse('?' in p) + self.assertFalse('"' in p) + self.assertFalse('<' in p) + self.assertFalse('>' in p) + self.assertFalse('|' in p) def test_sanitize_windows_replaces_trailing_space(self): with _common.platform_windows(): - p = util.sanitize_path(u'one/two /three') - self.assertFalse(u' ' in p) + p = util.sanitize_path('one/two /three') + self.assertFalse(' ' in p) def test_sanitize_path_works_on_empty_string(self): with _common.platform_posix(): - p = util.sanitize_path(u'') - self.assertEqual(p, u'') + p = util.sanitize_path('') + self.assertEqual(p, '') def test_sanitize_with_custom_replace_overrides_built_in_sub(self): with _common.platform_posix(): - p = util.sanitize_path(u'a/.?/b', [ - (re.compile(r'foo'), u'bar'), + p = util.sanitize_path('a/.?/b', [ + (re.compile(r'foo'), 'bar'), ]) - self.assertEqual(p, u'a/.?/b') + self.assertEqual(p, 'a/.?/b') def test_sanitize_with_custom_replace_adds_replacements(self): with _common.platform_posix(): - p = util.sanitize_path(u'foo/bar', [ - (re.compile(r'foo'), u'bar'), + p = util.sanitize_path('foo/bar', [ + (re.compile(r'foo'), 'bar'), ]) - self.assertEqual(p, u'bar/bar') + self.assertEqual(p, 'bar/bar') - @unittest.skip(u'unimplemented: #359') + @unittest.skip('unimplemented: #359') def test_sanitize_empty_component(self): with _common.platform_posix(): - p = util.sanitize_path(u'foo//bar', [ - (re.compile(r'^$'), u'_'), + p = util.sanitize_path('foo//bar', [ + (re.compile(r'^$'), '_'), ]) - self.assertEqual(p, u'foo/_/bar') + self.assertEqual(p, 'foo/_/bar') def test_convert_command_args_keeps_undecodeable_bytes(self): arg = b'\x82' # non-ascii bytes @@ -115,7 +113,7 @@ class UtilTest(unittest.TestCase): def test_command_output(self, mock_popen): def popen_fail(*args, **kwargs): m = Mock(returncode=1) - m.communicate.return_value = u'foo', u'bar' + m.communicate.return_value = 'foo', 'bar' return m mock_popen.side_effect = popen_fail @@ -128,10 +126,10 @@ class UtilTest(unittest.TestCase): class PathConversionTest(_common.TestCase): def test_syspath_windows_format(self): with _common.platform_windows(): - path = os.path.join(u'a', u'b', u'c') + path = os.path.join('a', 'b', 'c') outpath = util.syspath(path) - self.assertTrue(isinstance(outpath, six.text_type)) - self.assertTrue(outpath.startswith(u'\\\\?\\')) + self.assertTrue(isinstance(outpath, str)) + self.assertTrue(outpath.startswith('\\\\?\\')) def test_syspath_windows_format_unc_path(self): # The \\?\ prefix on Windows behaves differently with UNC @@ -139,12 +137,12 @@ class PathConversionTest(_common.TestCase): path = '\\\\server\\share\\file.mp3' with _common.platform_windows(): outpath = util.syspath(path) - self.assertTrue(isinstance(outpath, six.text_type)) - self.assertEqual(outpath, u'\\\\?\\UNC\\server\\share\\file.mp3') + self.assertTrue(isinstance(outpath, str)) + self.assertEqual(outpath, '\\\\?\\UNC\\server\\share\\file.mp3') def test_syspath_posix_unchanged(self): with _common.platform_posix(): - path = os.path.join(u'a', u'b', u'c') + path = os.path.join('a', 'b', 'c') outpath = util.syspath(path) self.assertEqual(path, outpath) @@ -158,14 +156,14 @@ class PathConversionTest(_common.TestCase): sys.getfilesystemencoding = old_gfse def test_bytestring_path_windows_encodes_utf8(self): - path = u'caf\xe9' + path = 'caf\xe9' outpath = self._windows_bytestring_path(path) self.assertEqual(path, outpath.decode('utf-8')) def test_bytesting_path_windows_removes_magic_prefix(self): - path = u'\\\\?\\C:\\caf\xe9' + path = '\\\\?\\C:\\caf\xe9' outpath = self._windows_bytestring_path(path) - self.assertEqual(outpath, u'C:\\caf\xe9'.encode('utf-8')) + self.assertEqual(outpath, 'C:\\caf\xe9'.encode()) class PathTruncationTest(_common.TestCase): @@ -176,13 +174,13 @@ class PathTruncationTest(_common.TestCase): def test_truncate_unicode(self): with _common.platform_posix(): - p = util.truncate_path(u'abcde/fgh', 4) - self.assertEqual(p, u'abcd/fgh') + p = util.truncate_path('abcde/fgh', 4) + self.assertEqual(p, 'abcd/fgh') def test_truncate_preserves_extension(self): with _common.platform_posix(): - p = util.truncate_path(u'abcde/fgh.ext', 5) - self.assertEqual(p, u'abcde/f.ext') + p = util.truncate_path('abcde/fgh.ext', 5) + self.assertEqual(p, 'abcde/f.ext') def suite(): diff --git a/test/test_vfs.py b/test/test_vfs.py index b94acfd09..4cd04e213 100644 --- a/test/test_vfs.py +++ b/test/test_vfs.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. # @@ -14,7 +13,6 @@ # included in all copies or substantial portions of the Software. """Tests for the virtual filesystem builder..""" -from __future__ import division, absolute_import, print_function import unittest from test import _common @@ -24,10 +22,10 @@ from beets import vfs class VFSTest(_common.TestCase): def setUp(self): - super(VFSTest, self).setUp() + super().setUp() self.lib = library.Library(':memory:', path_formats=[ - (u'default', u'albums/$album/$title'), - (u'singleton:true', u'tracks/$artist/$title'), + ('default', 'albums/$album/$title'), + ('singleton:true', 'tracks/$artist/$title'), ]) self.lib.add(_common.item()) self.lib.add_album([_common.item()]) diff --git a/test/test_web.py b/test/test_web.py index 570a6447c..c9e8ce678 100644 --- a/test/test_web.py +++ b/test/test_web.py @@ -1,8 +1,5 @@ -# -*- coding: utf-8 -*- - """Tests for the 'web' plugin""" -from __future__ import division, absolute_import, print_function import json import unittest @@ -23,13 +20,13 @@ class WebPluginTest(_common.LibTestCase): def setUp(self): - super(WebPluginTest, self).setUp() + super().setUp() self.log = logging.getLogger('beets.web') if platform.system() == 'Windows': - self.path_prefix = u'C:' + self.path_prefix = 'C:' else: - self.path_prefix = u'' + self.path_prefix = '' # Add fixtures for track in self.lib.items(): @@ -40,27 +37,27 @@ class WebPluginTest(_common.LibTestCase): # The following adds will create items #1, #2 and #3 path1 = self.path_prefix + os.sep + \ os.path.join(b'path_1').decode('utf-8') - self.lib.add(Item(title=u'title', + self.lib.add(Item(title='title', path=path1, album_id=2, artist='AAA Singers')) path2 = self.path_prefix + os.sep + \ os.path.join(b'somewhere', b'a').decode('utf-8') - self.lib.add(Item(title=u'another title', + self.lib.add(Item(title='another title', path=path2, artist='AAA Singers')) path3 = self.path_prefix + os.sep + \ os.path.join(b'somewhere', b'abc').decode('utf-8') - self.lib.add(Item(title=u'and a third', + self.lib.add(Item(title='and a third', testattr='ABC', path=path3, album_id=2)) # The following adds will create albums #1 and #2 - self.lib.add(Album(album=u'album', + self.lib.add(Album(album='album', albumtest='xyz')) path4 = self.path_prefix + os.sep + \ os.path.join(b'somewhere2', b'art_path_2').decode('utf-8') - self.lib.add(Album(album=u'other album', + self.lib.add(Album(album='other album', artpath=path4)) web.app.config['TESTING'] = True @@ -122,7 +119,7 @@ class WebPluginTest(_common.LibTestCase): self.assertEqual(response.status_code, 200) self.assertEqual(res_json['id'], 1) - self.assertEqual(res_json['title'], u'title') + self.assertEqual(res_json['title'], 'title') def test_get_multiple_items_by_id(self): response = self.client.get('/item/1,2') @@ -131,7 +128,7 @@ class WebPluginTest(_common.LibTestCase): self.assertEqual(response.status_code, 200) self.assertEqual(len(res_json['items']), 2) response_titles = {item['title'] for item in res_json['items']} - self.assertEqual(response_titles, {u'title', u'another title'}) + self.assertEqual(response_titles, {'title', 'another title'}) def test_get_single_item_not_found(self): response = self.client.get('/item/4') @@ -144,7 +141,7 @@ class WebPluginTest(_common.LibTestCase): res_json = json.loads(response.data.decode('utf-8')) self.assertEqual(response.status_code, 200) - self.assertEqual(res_json['title'], u'full') + self.assertEqual(res_json['title'], 'full') def test_get_single_item_by_path_not_found_if_not_in_library(self): data_path = os.path.join(_common.RSRC, b'full.mp3') @@ -170,7 +167,7 @@ class WebPluginTest(_common.LibTestCase): self.assertEqual(response.status_code, 200) self.assertEqual(len(res_json['results']), 1) self.assertEqual(res_json['results'][0]['title'], - u'another title') + 'another title') def test_query_item_string(self): """ testing item query: testattr:ABC """ @@ -180,7 +177,7 @@ class WebPluginTest(_common.LibTestCase): self.assertEqual(response.status_code, 200) self.assertEqual(len(res_json['results']), 1) self.assertEqual(res_json['results'][0]['title'], - u'and a third') + 'and a third') def test_query_item_regex(self): """ testing item query: testattr::[A-C]+ """ @@ -190,7 +187,7 @@ class WebPluginTest(_common.LibTestCase): self.assertEqual(response.status_code, 200) self.assertEqual(len(res_json['results']), 1) self.assertEqual(res_json['results'][0]['title'], - u'and a third') + 'and a third') def test_query_item_regex_backslash(self): # """ testing item query: testattr::\w+ """ @@ -200,7 +197,7 @@ class WebPluginTest(_common.LibTestCase): self.assertEqual(response.status_code, 200) self.assertEqual(len(res_json['results']), 1) self.assertEqual(res_json['results'][0]['title'], - u'and a third') + 'and a third') def test_query_item_path(self): # """ testing item query: path:\somewhere\a """ @@ -216,7 +213,7 @@ class WebPluginTest(_common.LibTestCase): self.assertEqual(response.status_code, 200) self.assertEqual(len(res_json['results']), 1) self.assertEqual(res_json['results'][0]['title'], - u'another title') + 'another title') def test_get_all_albums(self): response = self.client.get('/album/') @@ -224,7 +221,7 @@ class WebPluginTest(_common.LibTestCase): self.assertEqual(response.status_code, 200) response_albums = [album['album'] for album in res_json['albums']] - assertCountEqual(self, response_albums, [u'album', u'other album']) + self.assertCountEqual(response_albums, ['album', 'other album']) def test_get_single_album_by_id(self): response = self.client.get('/album/2') @@ -232,7 +229,7 @@ class WebPluginTest(_common.LibTestCase): self.assertEqual(response.status_code, 200) self.assertEqual(res_json['id'], 2) - self.assertEqual(res_json['album'], u'other album') + self.assertEqual(res_json['album'], 'other album') def test_get_multiple_albums_by_id(self): response = self.client.get('/album/1,2') @@ -240,7 +237,7 @@ class WebPluginTest(_common.LibTestCase): self.assertEqual(response.status_code, 200) response_albums = [album['album'] for album in res_json['albums']] - assertCountEqual(self, response_albums, [u'album', u'other album']) + self.assertCountEqual(response_albums, ['album', 'other album']) def test_get_album_empty_query(self): response = self.client.get('/album/query/') @@ -256,7 +253,7 @@ class WebPluginTest(_common.LibTestCase): self.assertEqual(response.status_code, 200) self.assertEqual(len(res_json['results']), 1) self.assertEqual(res_json['results'][0]['album'], - u'other album') + 'other album') self.assertEqual(res_json['results'][0]['id'], 2) def test_get_album_details(self): @@ -266,11 +263,11 @@ class WebPluginTest(_common.LibTestCase): self.assertEqual(response.status_code, 200) self.assertEqual(len(res_json['items']), 2) self.assertEqual(res_json['items'][0]['album'], - u'other album') + 'other album') self.assertEqual(res_json['items'][1]['album'], - u'other album') + 'other album') response_track_titles = {item['title'] for item in res_json['items']} - self.assertEqual(response_track_titles, {u'title', u'and a third'}) + self.assertEqual(response_track_titles, {'title', 'and a third'}) def test_query_album_string(self): """ testing query: albumtest:xy """ @@ -280,7 +277,7 @@ class WebPluginTest(_common.LibTestCase): self.assertEqual(response.status_code, 200) self.assertEqual(len(res_json['results']), 1) self.assertEqual(res_json['results'][0]['album'], - u'album') + 'album') def test_query_album_artpath_regex(self): """ testing query: artpath::art_ """ @@ -290,7 +287,7 @@ class WebPluginTest(_common.LibTestCase): self.assertEqual(response.status_code, 200) self.assertEqual(len(res_json['results']), 1) self.assertEqual(res_json['results'][0]['album'], - u'other album') + 'other album') def test_query_album_regex_backslash(self): # """ testing query: albumtest::\w+ """ @@ -300,7 +297,7 @@ class WebPluginTest(_common.LibTestCase): self.assertEqual(response.status_code, 200) self.assertEqual(len(res_json['results']), 1) self.assertEqual(res_json['results'][0]['album'], - u'album') + 'album') def test_get_stats(self): response = self.client.get('/stats') @@ -315,7 +312,7 @@ class WebPluginTest(_common.LibTestCase): web.app.config['READONLY'] = False # Create a temporary item - item_id = self.lib.add(Item(title=u'test_delete_item_id', + item_id = self.lib.add(Item(title='test_delete_item_id', test_delete_item_id=1)) # Check we can find the temporary item we just created @@ -397,7 +394,7 @@ class WebPluginTest(_common.LibTestCase): web.app.config['READONLY'] = False # Create a temporary item - self.lib.add(Item(title=u'test_delete_item_query', + self.lib.add(Item(title='test_delete_item_query', test_delete_item_query=1)) # Check we can find the temporary item we just created @@ -434,7 +431,7 @@ class WebPluginTest(_common.LibTestCase): web.app.config['READONLY'] = True # Create a temporary item - item_id = self.lib.add(Item(title=u'test_delete_item_id_ro', + item_id = self.lib.add(Item(title='test_delete_item_id_ro', test_delete_item_id_ro=1)) # Check we can find the temporary item we just created @@ -461,7 +458,7 @@ class WebPluginTest(_common.LibTestCase): web.app.config['READONLY'] = True # Create a temporary item - item_id = self.lib.add(Item(title=u'test_delete_item_q_ro', + item_id = self.lib.add(Item(title='test_delete_item_q_ro', test_delete_item_q_ro=1)) # Check we can find the temporary item we just created @@ -488,7 +485,7 @@ class WebPluginTest(_common.LibTestCase): web.app.config['READONLY'] = False # Create a temporary album - album_id = self.lib.add(Album(album=u'test_delete_album_id', + album_id = self.lib.add(Album(album='test_delete_album_id', test_delete_album_id=1)) # Check we can find the temporary album we just created @@ -513,7 +510,7 @@ class WebPluginTest(_common.LibTestCase): web.app.config['READONLY'] = False # Create a temporary album - self.lib.add(Album(album=u'test_delete_album_query', + self.lib.add(Album(album='test_delete_album_query', test_delete_album_query=1)) # Check we can find the temporary album we just created @@ -550,7 +547,7 @@ class WebPluginTest(_common.LibTestCase): web.app.config['READONLY'] = True # Create a temporary album - album_id = self.lib.add(Album(album=u'test_delete_album_id_ro', + album_id = self.lib.add(Album(album='test_delete_album_id_ro', test_delete_album_id_ro=1)) # Check we can find the temporary album we just created @@ -577,7 +574,7 @@ class WebPluginTest(_common.LibTestCase): web.app.config['READONLY'] = True # Create a temporary album - album_id = self.lib.add(Album(album=u'test_delete_album_query_ro', + album_id = self.lib.add(Album(album='test_delete_album_query_ro', test_delete_album_query_ro=1)) # Check we can find the temporary album we just created @@ -607,7 +604,7 @@ class WebPluginTest(_common.LibTestCase): web.app.config['READONLY'] = False # Create a temporary item - item_id = self.lib.add(Item(title=u'test_patch_item_id', + item_id = self.lib.add(Item(title='test_patch_item_id', test_patch_f1=1, test_patch_f2="Old")) @@ -649,7 +646,7 @@ class WebPluginTest(_common.LibTestCase): web.app.config['READONLY'] = True # Create a temporary item - item_id = self.lib.add(Item(title=u'test_patch_item_id_ro', + item_id = self.lib.add(Item(title='test_patch_item_id_ro', test_patch_f1=2, test_patch_f2="Old")) diff --git a/test/test_zero.py b/test/test_zero.py index 1b7cc92d7..c4c176960 100644 --- a/test/test_zero.py +++ b/test/test_zero.py @@ -1,8 +1,5 @@ -# -*- coding: utf-8 -*- - """Tests for the 'zero' plugin""" -from __future__ import division, absolute_import, print_function import unittest from test.helper import TestHelper, control_stdin @@ -31,8 +28,8 @@ class ZeroPluginTest(unittest.TestCase, TestHelper): self.config['zero']['fields'] = ['comments', 'month'] item = self.add_item_fixture( - comments=u'test comment', - title=u'Title', + comments='test comment', + title='Title', month=1, year=2000, ) @@ -44,14 +41,14 @@ class ZeroPluginTest(unittest.TestCase, TestHelper): mf = MediaFile(syspath(item.path)) self.assertIsNone(mf.comments) self.assertIsNone(mf.month) - self.assertEqual(mf.title, u'Title') + self.assertEqual(mf.title, 'Title') self.assertEqual(mf.year, 2000) def test_pattern_match(self): self.config['zero']['fields'] = ['comments'] - self.config['zero']['comments'] = [u'encoded by'] + self.config['zero']['comments'] = ['encoded by'] - item = self.add_item_fixture(comments=u'encoded by encoder') + item = self.add_item_fixture(comments='encoded by encoder') item.write() self.load_plugins('zero') @@ -62,16 +59,16 @@ class ZeroPluginTest(unittest.TestCase, TestHelper): def test_pattern_nomatch(self): self.config['zero']['fields'] = ['comments'] - self.config['zero']['comments'] = [u'encoded by'] + self.config['zero']['comments'] = ['encoded by'] - item = self.add_item_fixture(comments=u'recorded at place') + item = self.add_item_fixture(comments='recorded at place') item.write() self.load_plugins('zero') item.write() mf = MediaFile(syspath(item.path)) - self.assertEqual(mf.comments, u'recorded at place') + self.assertEqual(mf.comments, 'recorded at place') def test_do_not_change_database(self): self.config['zero']['fields'] = ['year'] @@ -126,7 +123,7 @@ class ZeroPluginTest(unittest.TestCase, TestHelper): year=2016, day=13, month=3, - comments=u'test comment' + comments='test comment' ) item.write() item_id = item.id @@ -144,14 +141,14 @@ class ZeroPluginTest(unittest.TestCase, TestHelper): self.assertEqual(item['year'], 2016) self.assertEqual(mf.year, 2016) self.assertEqual(mf.comments, None) - self.assertEqual(item['comments'], u'') + self.assertEqual(item['comments'], '') def test_subcommand_update_database_false(self): item = self.add_item_fixture( year=2016, day=13, month=3, - comments=u'test comment' + comments='test comment' ) item.write() item_id = item.id @@ -169,7 +166,7 @@ class ZeroPluginTest(unittest.TestCase, TestHelper): self.assertEqual(item['year'], 2016) self.assertEqual(mf.year, 2016) - self.assertEqual(item['comments'], u'test comment') + self.assertEqual(item['comments'], 'test comment') self.assertEqual(mf.comments, None) def test_subcommand_query_include(self): @@ -177,7 +174,7 @@ class ZeroPluginTest(unittest.TestCase, TestHelper): year=2016, day=13, month=3, - comments=u'test comment' + comments='test comment' ) item.write() @@ -199,7 +196,7 @@ class ZeroPluginTest(unittest.TestCase, TestHelper): year=2016, day=13, month=3, - comments=u'test comment' + comments='test comment' ) item.write() @@ -214,7 +211,7 @@ class ZeroPluginTest(unittest.TestCase, TestHelper): mf = MediaFile(syspath(item.path)) self.assertEqual(mf.year, 2016) - self.assertEqual(mf.comments, u'test comment') + self.assertEqual(mf.comments, 'test comment') def test_no_fields(self): item = self.add_item_fixture(year=2016) @@ -240,8 +237,8 @@ class ZeroPluginTest(unittest.TestCase, TestHelper): self.assertEqual(mf.year, 2016) item_id = item.id - self.config['zero']['fields'] = [u'year'] - self.config['zero']['keep_fields'] = [u'comments'] + self.config['zero']['fields'] = ['year'] + self.config['zero']['keep_fields'] = ['comments'] self.load_plugins('zero') with control_stdin('y'): @@ -253,13 +250,13 @@ class ZeroPluginTest(unittest.TestCase, TestHelper): self.assertEqual(mf.year, 2016) def test_keep_fields(self): - item = self.add_item_fixture(year=2016, comments=u'test comment') - self.config['zero']['keep_fields'] = [u'year'] + item = self.add_item_fixture(year=2016, comments='test comment') + self.config['zero']['keep_fields'] = ['year'] self.config['zero']['fields'] = None self.config['zero']['update_database'] = True tags = { - 'comments': u'test comment', + 'comments': 'test comment', 'year': 2016, } self.load_plugins('zero') @@ -270,7 +267,7 @@ class ZeroPluginTest(unittest.TestCase, TestHelper): self.assertEqual(tags['year'], 2016) def test_keep_fields_removes_preserved_tags(self): - self.config['zero']['keep_fields'] = [u'year'] + self.config['zero']['keep_fields'] = ['year'] self.config['zero']['fields'] = None self.config['zero']['update_database'] = True @@ -279,7 +276,7 @@ class ZeroPluginTest(unittest.TestCase, TestHelper): self.assertNotIn('id', z.fields_to_progs) def test_fields_removes_preserved_tags(self): - self.config['zero']['fields'] = [u'year id'] + self.config['zero']['fields'] = ['year id'] self.config['zero']['update_database'] = True z = ZeroPlugin() @@ -291,7 +288,7 @@ class ZeroPluginTest(unittest.TestCase, TestHelper): year=2016, day=13, month=3, - comments=u'test comment' + comments='test comment' ) item.write() item_id = item.id @@ -308,8 +305,8 @@ class ZeroPluginTest(unittest.TestCase, TestHelper): self.assertEqual(item['year'], 2016) self.assertEqual(mf.year, 2016) - self.assertEqual(mf.comments, u'test comment') - self.assertEqual(item['comments'], u'test comment') + self.assertEqual(mf.comments, 'test comment') + self.assertEqual(item['comments'], 'test comment') def suite(): diff --git a/test/testall.py b/test/testall.py index 418b4a3ca..74236f5a0 100755 --- a/test/testall.py +++ b/test/testall.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2016, Adrian Sampson. @@ -15,7 +14,6 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -from __future__ import division, absolute_import, print_function import os import re From 0f48ccde780c7fe0768c3329aaa015599f9ff777 Mon Sep 17 00:00:00 2001 From: Andrew Rogl Date: Thu, 26 Aug 2021 20:13:15 +1000 Subject: [PATCH 09/26] Final pyupgrade --- docs/conf.py | 16 ++++++---------- extra/release.py | 15 +++++++-------- test/rsrc/beetsplug/test.py | 8 ++------ test/rsrc/convert_stub.py | 2 -- 4 files changed, 15 insertions(+), 26 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 5ae600c1a..ee067fd29 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,8 +1,4 @@ -# -*- coding: utf-8 -*- - -from __future__ import division, absolute_import, print_function - -AUTHOR = u'Adrian Sampson' +AUTHOR = 'Adrian Sampson' # General configuration @@ -12,8 +8,8 @@ exclude_patterns = ['_build'] source_suffix = '.rst' master_doc = 'index' -project = u'beets' -copyright = u'2016, Adrian Sampson' +project = 'beets' +copyright = '2016, Adrian Sampson' version = '1.5' release = '1.5.1' @@ -41,14 +37,14 @@ htmlhelp_basename = 'beetsdoc' # Options for LaTeX output latex_documents = [ - ('index', 'beets.tex', u'beets Documentation', + ('index', 'beets.tex', 'beets Documentation', AUTHOR, 'manual'), ] # Options for manual page output man_pages = [ - ('reference/cli', 'beet', u'music tagger and library organizer', + ('reference/cli', 'beet', 'music tagger and library organizer', [AUTHOR], 1), - ('reference/config', 'beetsconfig', u'beets configuration file', + ('reference/config', 'beetsconfig', 'beets configuration file', [AUTHOR], 5), ] diff --git a/extra/release.py b/extra/release.py index 65153a9bc..0309f0bb0 100755 --- a/extra/release.py +++ b/extra/release.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- """A utility script for automating the beets release process. """ @@ -110,14 +109,14 @@ def bump_version(version): out_lines.append(line) if not found: - print("No pattern found in {}".format(filename)) + print(f"No pattern found in {filename}") # Write the file back. with open(filename, 'w') as f: f.write(''.join(out_lines)) # Generate bits to insert into changelog. - header_line = '{} (in development)'.format(version) + header_line = f'{version} (in development)' header = '\n\n' + header_line + '\n' + '-' * len(header_line) + '\n\n' header += 'Changelog goes here!\n' @@ -277,7 +276,7 @@ def prep(): cur_version = get_version() # Tag. - subprocess.check_output(['git', 'tag', 'v{}'.format(cur_version)]) + subprocess.check_output(['git', 'tag', f'v{cur_version}']) # Build. with chdir(BASE): @@ -292,7 +291,7 @@ def prep(): # FIXME It should be possible to specify this as an argument. version_parts = [int(n) for n in cur_version.split('.')] version_parts[-1] += 1 - next_version = u'.'.join(map(str, version_parts)) + next_version = '.'.join(map(str, version_parts)) bump_version(next_version) @@ -311,7 +310,7 @@ def publish(): subprocess.check_call(['git', 'push', '--tags']) # Upload to PyPI. - path = os.path.join(BASE, 'dist', 'beets-{}.tar.gz'.format(version)) + path = os.path.join(BASE, 'dist', f'beets-{version}.tar.gz') subprocess.check_call(['twine', 'upload', path]) @@ -335,12 +334,12 @@ def ghrelease(): 'github-release', 'release', '-u', GITHUB_USER, '-r', GITHUB_REPO, '--tag', tag, - '--name', '{} {}'.format(GITHUB_REPO, version), + '--name', f'{GITHUB_REPO} {version}', '--description', cl_md, ]) # Attach the release tarball. - tarball = os.path.join(BASE, 'dist', 'beets-{}.tar.gz'.format(version)) + tarball = os.path.join(BASE, 'dist', f'beets-{version}.tar.gz') subprocess.check_call([ 'github-release', 'upload', '-u', GITHUB_USER, '-r', GITHUB_REPO, diff --git a/test/rsrc/beetsplug/test.py b/test/rsrc/beetsplug/test.py index 6dedd0e33..c57d3f517 100644 --- a/test/rsrc/beetsplug/test.py +++ b/test/rsrc/beetsplug/test.py @@ -1,14 +1,10 @@ -# -*- coding: utf-8 -*- - -from __future__ import division, absolute_import, print_function - from beets.plugins import BeetsPlugin from beets import ui class TestPlugin(BeetsPlugin): def __init__(self): - super(TestPlugin, self).__init__() + super().__init__() self.is_test_plugin = True def commands(self): @@ -16,7 +12,7 @@ class TestPlugin(BeetsPlugin): test.func = lambda *args: None # Used in CompletionTest - test.parser.add_option(u'-o', u'--option', dest='my_opt') + test.parser.add_option('-o', '--option', dest='my_opt') plugin = ui.Subcommand('plugin') plugin.func = lambda *args: None diff --git a/test/rsrc/convert_stub.py b/test/rsrc/convert_stub.py index cb42692d7..28dbfffc5 100755 --- a/test/rsrc/convert_stub.py +++ b/test/rsrc/convert_stub.py @@ -1,11 +1,9 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- """A tiny tool used to test the `convert` plugin. It copies a file and appends a specified text tag. """ -from __future__ import division, absolute_import, print_function import sys import platform import locale From ee4268dabbb728a9c3c4403a6b91a4df5155b809 Mon Sep 17 00:00:00 2001 From: Andrew Rogl Date: Thu, 26 Aug 2021 20:59:48 +1000 Subject: [PATCH 10/26] Remove unused imports Fix imports Fix formatting --- beets/autotag/mb.py | 2 +- beets/util/__init__.py | 7 +++---- beets/util/artresizer.py | 2 +- beets/util/pipeline.py | 2 +- beetsplug/acousticbrainz.py | 8 ++++---- beetsplug/badfiles.py | 1 - beetsplug/bareasc.py | 9 ++++----- beetsplug/beatport.py | 1 - beetsplug/bpd/__init__.py | 1 - beetsplug/bpd/gstplayer.py | 5 ++--- beetsplug/bucket.py | 6 +++--- beetsplug/convert.py | 2 +- beetsplug/deezer.py | 1 - beetsplug/discogs.py | 6 +++--- beetsplug/duplicates.py | 1 - beetsplug/edit.py | 1 - beetsplug/embyupdate.py | 4 +--- beetsplug/fetchart.py | 1 - beetsplug/fromfilename.py | 1 - beetsplug/hook.py | 5 +++-- beetsplug/importadded.py | 4 ++-- beetsplug/inline.py | 1 - beetsplug/kodiupdate.py | 1 - beetsplug/lastgenre/__init__.py | 4 +--- beetsplug/lyrics.py | 9 ++++----- beetsplug/mbsubmit.py | 1 - beetsplug/metasync/__init__.py | 1 - beetsplug/metasync/itunes.py | 12 ++++++------ beetsplug/mpdstats.py | 29 +++++++++++++---------------- beetsplug/mpdupdate.py | 1 - beetsplug/permissions.py | 1 - beetsplug/plexupdate.py | 2 +- beetsplug/replaygain.py | 1 - beetsplug/smartplaylist.py | 5 ++--- beetsplug/spotify.py | 1 - beetsplug/subsonicplaylist.py | 4 ++-- beetsplug/thumbnails.py | 1 - beetsplug/unimported.py | 2 +- beetsplug/zero.py | 1 - setup.py | 1 - test/__init__.py | 1 - test/helper.py | 4 ++-- test/test_art_resize.py | 1 - test/test_beatport.py | 1 - test/test_config_command.py | 1 - test/test_convert.py | 3 ++- test/test_dbcore.py | 2 -- test/test_importadded.py | 9 +++++---- test/test_ipfs.py | 3 ++- test/test_lastgenre.py | 1 - test/test_library.py | 1 - test/test_logging.py | 1 - test/test_lyrics.py | 1 - test/test_playlist.py | 17 +++++++++-------- test/test_plugin_mediafield.py | 1 - test/test_query.py | 1 - test/test_replaygain.py | 3 +-- test/test_smartplaylist.py | 2 +- test/test_subsonicupdate.py | 3 ++- test/test_template.py | 1 - test/test_ui_importer.py | 1 - test/test_util.py | 1 - test/test_web.py | 2 +- 63 files changed, 84 insertions(+), 125 deletions(-) diff --git a/beets/autotag/mb.py b/beets/autotag/mb.py index cb74d5b30..f659f15cb 100644 --- a/beets/autotag/mb.py +++ b/beets/autotag/mb.py @@ -18,7 +18,6 @@ import musicbrainzngs import re import traceback -from six.moves.urllib.parse import urljoin from beets import logging from beets import plugins @@ -26,6 +25,7 @@ import beets.autotag.hooks import beets from beets import util from beets import config +from urllib.parse import urljoin VARIOUS_ARTISTS_ID = '89ad4ac3-39f7-470e-963a-56509c546377' diff --git a/beets/util/__init__.py b/beets/util/__init__.py index 7a47ad48e..6ee8d68a3 100644 --- a/beets/util/__init__.py +++ b/beets/util/__init__.py @@ -29,7 +29,6 @@ import subprocess import platform import shlex from beets.util import hidden -import six from unidecode import unidecode from enum import Enum @@ -100,6 +99,7 @@ class FilesystemError(HumanReadableException): via a function in this module. The `paths` field is a sequence of pathnames involved in the operation. """ + def __init__(self, reason, verb, paths, tb=None): self.paths = paths super().__init__(reason, verb, tb) @@ -599,6 +599,7 @@ def unique_path(path): if not os.path.exists(new_path): return new_path + # Note: The Windows "reserved characters" are, of course, allowed on # Unix. They are forbidden here because they cause problems on Samba # shares, which are sufficiently common as to cause frequent problems. @@ -736,8 +737,6 @@ def py3_path(path): if isinstance(path, str): return path assert isinstance(path, bytes) - if six.PY2: - return path return os.fsdecode(path) @@ -801,7 +800,7 @@ def cpu_count(): '/usr/sbin/sysctl', '-n', 'hw.ncpu', - ]).stdout) + ]).stdout) except (ValueError, OSError, subprocess.CalledProcessError): num = 0 else: diff --git a/beets/util/artresizer.py b/beets/util/artresizer.py index 40efbc9fd..725712ed4 100644 --- a/beets/util/artresizer.py +++ b/beets/util/artresizer.py @@ -20,7 +20,7 @@ import subprocess import os import re from tempfile import NamedTemporaryFile -from six.moves.urllib.parse import urlencode +from urllib.parse import urlencode from beets import logging from beets import util diff --git a/beets/util/pipeline.py b/beets/util/pipeline.py index fa7e5eb31..d338cb51b 100644 --- a/beets/util/pipeline.py +++ b/beets/util/pipeline.py @@ -32,7 +32,7 @@ in place of any single coroutine. """ -from six.moves import queue +import queue from threading import Thread, Lock import sys diff --git a/beetsplug/acousticbrainz.py b/beetsplug/acousticbrainz.py index 4e065eaf9..eabc5849f 100644 --- a/beetsplug/acousticbrainz.py +++ b/beetsplug/acousticbrainz.py @@ -287,8 +287,8 @@ class AcousticPlugin(plugins.BeetsPlugin): # The recursive traversal. composites = defaultdict(list) yield from self._data_to_scheme_child(data, - scheme, - composites) + scheme, + composites) # When composites has been populated, yield the composite attributes # by joining their parts. @@ -309,8 +309,8 @@ class AcousticPlugin(plugins.BeetsPlugin): if k in subdata: if type(v) == dict: yield from self._data_to_scheme_child(subdata[k], - v, - composites) + v, + composites) elif type(v) == tuple: composite_attribute, part_number = v attribute_parts = composites[composite_attribute] diff --git a/beetsplug/badfiles.py b/beetsplug/badfiles.py index 874d533ce..ec465895b 100644 --- a/beetsplug/badfiles.py +++ b/beetsplug/badfiles.py @@ -22,7 +22,6 @@ import shlex import os import errno import sys -import six import confuse from beets.plugins import BeetsPlugin from beets.ui import Subcommand diff --git a/beetsplug/bareasc.py b/beetsplug/bareasc.py index 10fd25d46..218369364 100644 --- a/beetsplug/bareasc.py +++ b/beetsplug/bareasc.py @@ -24,7 +24,6 @@ from beets.ui import print_, decargs from beets.plugins import BeetsPlugin from beets.dbcore.query import StringFieldQuery from unidecode import unidecode -import six class BareascQuery(StringFieldQuery): @@ -75,9 +74,9 @@ class BareascPlugin(BeetsPlugin): # Copied from commands.py - list_items if album: for album in lib.albums(query): - bare = unidecode(six.ensure_text(str(album))) - print_(six.ensure_text(bare)) + bare = unidecode(str(album)) + print_(bare) else: for item in lib.items(query): - bare = unidecode(six.ensure_text(str(item))) - print_(six.ensure_text(bare)) + bare = unidecode(str(item)) + print_(bare) diff --git a/beetsplug/beatport.py b/beetsplug/beatport.py index e2701bdb3..133441d7e 100644 --- a/beetsplug/beatport.py +++ b/beetsplug/beatport.py @@ -17,7 +17,6 @@ import json import re -import six from datetime import datetime, timedelta from requests_oauthlib import OAuth1Session diff --git a/beetsplug/bpd/__init__.py b/beetsplug/bpd/__init__.py index dc58c81a5..07198b1b4 100644 --- a/beetsplug/bpd/__init__.py +++ b/beetsplug/bpd/__init__.py @@ -36,7 +36,6 @@ from beets.util import bluelet from beets.library import Item from beets import dbcore from mediafile import MediaFile -import six PROTOCOL_VERSION = '0.16.0' BUFSIZE = 1024 diff --git a/beetsplug/bpd/gstplayer.py b/beetsplug/bpd/gstplayer.py index 8cb6d56cf..64954b1c1 100644 --- a/beetsplug/bpd/gstplayer.py +++ b/beetsplug/bpd/gstplayer.py @@ -17,13 +17,12 @@ music player. """ -import six import sys import time -from six.moves import _thread +import _thread import os import copy -from six.moves import urllib +import urllib from beets import ui import gi diff --git a/beetsplug/bucket.py b/beetsplug/bucket.py index 0837ca8da..9ed50b45c 100644 --- a/beetsplug/bucket.py +++ b/beetsplug/bucket.py @@ -126,9 +126,9 @@ def str2fmt(s): res = {'fromnchars': len(m.group('fromyear')), 'tonchars': len(m.group('toyear'))} res['fmt'] = "{}%s{}{}{}".format(m.group('bef'), - m.group('sep'), - '%s' if res['tonchars'] else '', - m.group('after')) + m.group('sep'), + '%s' if res['tonchars'] else '', + m.group('after')) return res diff --git a/beetsplug/convert.py b/beetsplug/convert.py index 5f07a2e7c..6bc07c287 100644 --- a/beetsplug/convert.py +++ b/beetsplug/convert.py @@ -119,7 +119,7 @@ class ConvertPlugin(BeetsPlugin): 'formats': { 'aac': { 'command': 'ffmpeg -i $source -y -vn -acodec aac ' - '-aq 1 $dest', + '-aq 1 $dest', 'extension': 'm4a', }, 'alac': { diff --git a/beetsplug/deezer.py b/beetsplug/deezer.py index 201ab6992..5f158f936 100644 --- a/beetsplug/deezer.py +++ b/beetsplug/deezer.py @@ -17,7 +17,6 @@ import collections -import six import unidecode import requests diff --git a/beetsplug/discogs.py b/beetsplug/discogs.py index e7c416909..76036f1be 100644 --- a/beetsplug/discogs.py +++ b/beetsplug/discogs.py @@ -24,7 +24,7 @@ import confuse from discogs_client import Release, Master, Client from discogs_client.exceptions import DiscogsAPIError from requests.exceptions import ConnectionError -from six.moves import http_client +import http.client import beets import re import time @@ -40,7 +40,7 @@ API_KEY = 'rAzVUQYRaoFjeBjyWuWZ' API_SECRET = 'plxtUTqoCzwxZpqdPysCwGuBSmZNdZVy' # Exceptions that discogs_client should really handle but does not. -CONNECTION_ERRORS = (ConnectionError, socket.error, http_client.HTTPException, +CONNECTION_ERRORS = (ConnectionError, socket.error, http.client.HTTPException, ValueError, # JSON decoding raises a ValueError. DiscogsAPIError) @@ -505,7 +505,7 @@ class DiscogsPlugin(BeetsPlugin): if self.config['index_tracks']: for subtrack in subtracks: subtrack['title'] = '{}: {}'.format( - index_track['title'], subtrack['title']) + index_track['title'], subtrack['title']) tracklist.extend(subtracks) else: # Merge the subtracks, pick a title, and append the new track. diff --git a/beetsplug/duplicates.py b/beetsplug/duplicates.py index db1e1ee9e..fdd5c1750 100644 --- a/beetsplug/duplicates.py +++ b/beetsplug/duplicates.py @@ -16,7 +16,6 @@ """ import shlex -import six from beets.plugins import BeetsPlugin from beets.ui import decargs, print_, Subcommand, UserError diff --git a/beetsplug/edit.py b/beetsplug/edit.py index 8372563b8..6f03fa4d8 100644 --- a/beetsplug/edit.py +++ b/beetsplug/edit.py @@ -26,7 +26,6 @@ import subprocess import yaml from tempfile import NamedTemporaryFile import os -import six import shlex diff --git a/beetsplug/embyupdate.py b/beetsplug/embyupdate.py index 0f77f17ce..c17fabad2 100644 --- a/beetsplug/embyupdate.py +++ b/beetsplug/embyupdate.py @@ -11,9 +11,7 @@ import hashlib import requests -from six.moves.urllib.parse import urlencode -from six.moves.urllib.parse import urljoin, parse_qs, urlsplit, urlunsplit - +from urllib.parse import urlencode, urljoin, parse_qs, urlsplit, urlunsplit from beets import config from beets.plugins import BeetsPlugin diff --git a/beetsplug/fetchart.py b/beetsplug/fetchart.py index dfc7eb683..2076d5986 100644 --- a/beetsplug/fetchart.py +++ b/beetsplug/fetchart.py @@ -33,7 +33,6 @@ from beets.util.artresizer import ArtResizer from beets.util import sorted_walk from beets.util import syspath, bytestring_path, py3_path import confuse -import six CONTENT_TYPES = { 'image/jpeg': [b'jpg', b'jpeg'], diff --git a/beetsplug/fromfilename.py b/beetsplug/fromfilename.py index de9f2b4ee..55684a274 100644 --- a/beetsplug/fromfilename.py +++ b/beetsplug/fromfilename.py @@ -20,7 +20,6 @@ from beets import plugins from beets.util import displayable_path import os import re -import six # Filename field extraction patterns. diff --git a/beetsplug/hook.py b/beetsplug/hook.py index f11898797..0fe3bffc6 100644 --- a/beetsplug/hook.py +++ b/beetsplug/hook.py @@ -48,7 +48,7 @@ class CodingFormatter(string.Formatter): format_string = format_string.decode(self._coding) return super().format(format_string, *args, - **kwargs) + **kwargs) def convert_field(self, value, conversion): """Converts the provided value given a conversion type. @@ -58,7 +58,7 @@ class CodingFormatter(string.Formatter): See string.Formatter.convert_field. """ converted = super().convert_field(value, - conversion) + conversion) if isinstance(converted, bytes): return converted.decode(self._coding) @@ -68,6 +68,7 @@ class CodingFormatter(string.Formatter): class HookPlugin(BeetsPlugin): """Allows custom commands to be run when an event is emitted by beets""" + def __init__(self): super().__init__() diff --git a/beetsplug/importadded.py b/beetsplug/importadded.py index 39838b5a2..e6665e0ff 100644 --- a/beetsplug/importadded.py +++ b/beetsplug/importadded.py @@ -60,8 +60,8 @@ class ImportAddedPlugin(BeetsPlugin): def record_reimported(self, task, session): self.reimported_item_ids = {item.id for item, replaced_items - in task.replaced_items.items() - if replaced_items} + in task.replaced_items.items() + if replaced_items} self.replaced_album_paths = set(task.replaced_albums.keys()) def write_file_mtime(self, path, mtime): diff --git a/beetsplug/inline.py b/beetsplug/inline.py index b3a779108..e19eaa9d7 100644 --- a/beetsplug/inline.py +++ b/beetsplug/inline.py @@ -20,7 +20,6 @@ import itertools from beets.plugins import BeetsPlugin from beets import config -import six FUNC_NAME = '__INLINE_FUNC__' diff --git a/beetsplug/kodiupdate.py b/beetsplug/kodiupdate.py index b24cd9463..2a885d2c2 100644 --- a/beetsplug/kodiupdate.py +++ b/beetsplug/kodiupdate.py @@ -26,7 +26,6 @@ Put something like the following in your config.yaml to configure: import requests from beets import config from beets.plugins import BeetsPlugin -import six def update_kodi(host, port, user, password): diff --git a/beetsplug/lastgenre/__init__.py b/beetsplug/lastgenre/__init__.py index 3a1e969cf..05412308d 100644 --- a/beetsplug/lastgenre/__init__.py +++ b/beetsplug/lastgenre/__init__.py @@ -13,8 +13,6 @@ # included in all copies or substantial portions of the Software. -import six - """Gets genres for imported music based on Last.fm tags. Uses a provided whitelist file to determine which tags are valid genres. @@ -266,7 +264,7 @@ class LastGenrePlugin(plugins.BeetsPlugin): return None key = '{}.{}'.format(entity, - '-'.join(str(a) for a in args)) + '-'.join(str(a) for a in args)) if key in self._genre_cache: return self._genre_cache[key] else: diff --git a/beetsplug/lyrics.py b/beetsplug/lyrics.py index 9c3155e93..8a9896bc3 100644 --- a/beetsplug/lyrics.py +++ b/beetsplug/lyrics.py @@ -27,8 +27,7 @@ import requests import unicodedata from unidecode import unidecode import warnings -import six -from six.moves import urllib +import urllib try: import bs4 @@ -47,7 +46,7 @@ try: # PY3: HTMLParseError was removed in 3.5 as strict mode # was deprecated in 3.3. # https://docs.python.org/3.3/library/html.parser.html - from six.moves.html_parser import HTMLParseError + from html.parser import HTMLParseError except ImportError: class HTMLParseError(Exception): pass @@ -864,8 +863,8 @@ class LyricsPlugin(plugins.BeetsPlugin): title_str = ":index:`%s`" % item.title.strip() block = '| ' + item.lyrics.replace('\n', '\n| ') self.rest += "{}\n{}\n\n{}\n\n".format(title_str, - '~' * len(title_str), - block) + '~' * len(title_str), + block) def writerest(self, directory): """Write self.rest to a ReST file diff --git a/beetsplug/mbsubmit.py b/beetsplug/mbsubmit.py index 6d21d9b59..3ede0125b 100644 --- a/beetsplug/mbsubmit.py +++ b/beetsplug/mbsubmit.py @@ -22,7 +22,6 @@ implemented by MusicBrainz yet. """ - from beets.autotag import Recommendation from beets.plugins import BeetsPlugin from beets.ui.commands import PromptChoice diff --git a/beetsplug/metasync/__init__.py b/beetsplug/metasync/__init__.py index c2f1d50d7..361071fb5 100644 --- a/beetsplug/metasync/__init__.py +++ b/beetsplug/metasync/__init__.py @@ -22,7 +22,6 @@ from importlib import import_module from confuse import ConfigValueError from beets import ui from beets.plugins import BeetsPlugin -import six METASYNC_MODULE = 'beetsplug.metasync' diff --git a/beetsplug/metasync/itunes.py b/beetsplug/metasync/itunes.py index 7ed940736..e50a57138 100644 --- a/beetsplug/metasync/itunes.py +++ b/beetsplug/metasync/itunes.py @@ -22,7 +22,7 @@ import shutil import tempfile import plistlib -from six.moves.urllib.parse import urlparse, unquote +from urllib.parse import urlparse, unquote from time import mktime from beets import util @@ -61,12 +61,12 @@ def _norm_itunes_path(path): class Itunes(MetaSource): item_types = { - 'itunes_rating': types.INTEGER, # 0..100 scale - 'itunes_playcount': types.INTEGER, - 'itunes_skipcount': types.INTEGER, - 'itunes_lastplayed': DateType(), + 'itunes_rating': types.INTEGER, # 0..100 scale + 'itunes_playcount': types.INTEGER, + 'itunes_skipcount': types.INTEGER, + 'itunes_lastplayed': DateType(), 'itunes_lastskipped': DateType(), - 'itunes_dateadded': DateType(), + 'itunes_dateadded': DateType(), } def __init__(self, config, log): diff --git a/beetsplug/mpdstats.py b/beetsplug/mpdstats.py index 243562409..dd91228e5 100644 --- a/beetsplug/mpdstats.py +++ b/beetsplug/mpdstats.py @@ -14,9 +14,6 @@ import mpd -import socket -import select -import sys import time import os @@ -300,10 +297,10 @@ class MPDStats: self._log.info('playing {0}', displayable_path(path)) self.now_playing = { - 'started': time.time(), - 'remaining': remaining, - 'path': path, - 'id': songid, + 'started': time.time(), + 'remaining': remaining, + 'path': path, + 'id': songid, 'beets_item': self.get_item(path), } @@ -331,22 +328,22 @@ class MPDStats: class MPDStatsPlugin(plugins.BeetsPlugin): item_types = { - 'play_count': types.INTEGER, - 'skip_count': types.INTEGER, + 'play_count': types.INTEGER, + 'skip_count': types.INTEGER, 'last_played': library.DateType(), - 'rating': types.FLOAT, + 'rating': types.FLOAT, } def __init__(self): super().__init__() mpd_config.add({ 'music_directory': config['directory'].as_filename(), - 'strip_path': '', - 'rating': True, - 'rating_mix': 0.75, - 'host': os.environ.get('MPD_HOST', 'localhost'), - 'port': int(os.environ.get('MPD_PORT', 6600)), - 'password': '', + 'strip_path': '', + 'rating': True, + 'rating_mix': 0.75, + 'host': os.environ.get('MPD_HOST', 'localhost'), + 'port': int(os.environ.get('MPD_PORT', 6600)), + 'password': '', }) mpd_config['password'].redact = True diff --git a/beetsplug/mpdupdate.py b/beetsplug/mpdupdate.py index 141f85b08..e5264e182 100644 --- a/beetsplug/mpdupdate.py +++ b/beetsplug/mpdupdate.py @@ -25,7 +25,6 @@ from beets.plugins import BeetsPlugin import os import socket from beets import config -import six # No need to introduce a dependency on an MPD library for such a diff --git a/beetsplug/permissions.py b/beetsplug/permissions.py index 18b554ccc..cd4577e4f 100644 --- a/beetsplug/permissions.py +++ b/beetsplug/permissions.py @@ -9,7 +9,6 @@ import os from beets import config, util from beets.plugins import BeetsPlugin from beets.util import ancestry -import six def convert_perm(perm): diff --git a/beetsplug/plexupdate.py b/beetsplug/plexupdate.py index 42620c0cd..2261a55f4 100644 --- a/beetsplug/plexupdate.py +++ b/beetsplug/plexupdate.py @@ -10,7 +10,7 @@ Put something like the following in your config.yaml to configure: import requests from xml.etree import ElementTree -from six.moves.urllib.parse import urljoin, urlencode +from urllib.parse import urljoin, urlencode from beets import config from beets.plugins import BeetsPlugin diff --git a/beetsplug/replaygain.py b/beetsplug/replaygain.py index 6592da2c6..b6297d937 100644 --- a/beetsplug/replaygain.py +++ b/beetsplug/replaygain.py @@ -18,7 +18,6 @@ import enum import math import os import signal -import six import subprocess import sys import warnings diff --git a/beetsplug/smartplaylist.py b/beetsplug/smartplaylist.py index d2043f0c9..4c921eccc 100644 --- a/beetsplug/smartplaylist.py +++ b/beetsplug/smartplaylist.py @@ -24,7 +24,6 @@ from beets.library import Item, Album, parse_query_string from beets.dbcore import OrQuery from beets.dbcore.query import MultipleSort, ParsingError import os -import six try: from urllib.request import pathname2url @@ -72,8 +71,8 @@ class SmartPlaylistPlugin(BeetsPlugin): args.add(f"{a}.m3u") playlists = {(name, q, a_q) - for name, q, a_q in self._unmatched_playlists - if name in args} + for name, q, a_q in self._unmatched_playlists + if name in args} if not playlists: raise ui.UserError( 'No playlist matching any of {} found'.format( diff --git a/beetsplug/spotify.py b/beetsplug/spotify.py index 69d4f1c0d..2529160dd 100644 --- a/beetsplug/spotify.py +++ b/beetsplug/spotify.py @@ -22,7 +22,6 @@ import base64 import webbrowser import collections -import six import unidecode import requests import confuse diff --git a/beetsplug/subsonicplaylist.py b/beetsplug/subsonicplaylist.py index 42b486f44..ead78919e 100644 --- a/beetsplug/subsonicplaylist.py +++ b/beetsplug/subsonicplaylist.py @@ -43,8 +43,8 @@ def filter_to_be_removed(items, keys): def to_be_removed(item): for artist, album, title in keys: if artist == item['artist'] and\ - album == item['album'] and\ - title == item['title']: + album == item['album'] and\ + title == item['title']: return False return True diff --git a/beetsplug/thumbnails.py b/beetsplug/thumbnails.py index c4ad024dc..6bd9cbac6 100644 --- a/beetsplug/thumbnails.py +++ b/beetsplug/thumbnails.py @@ -33,7 +33,6 @@ from beets.plugins import BeetsPlugin from beets.ui import Subcommand, decargs from beets import util from beets.util.artresizer import ArtResizer, get_im_version, get_pil_version -import six BASE_DIR = os.path.join(BaseDirectory.xdg_cache_home, "thumbnails") diff --git a/beetsplug/unimported.py b/beetsplug/unimported.py index 7c92def91..6c8cb6897 100644 --- a/beetsplug/unimported.py +++ b/beetsplug/unimported.py @@ -42,7 +42,7 @@ class Unimported(BeetsPlugin): in self.config['ignore_extensions'].as_str_seq()] in_folder = { os.path.join(r, file) for r, d, f in os.walk(lib.directory) - for file in f if not any( + for file in f if not any( [file.endswith(extension) for extension in ignore_exts])} in_library = {x.path for x in lib.items()} diff --git a/beetsplug/zero.py b/beetsplug/zero.py index 8d90e952a..f05b1b5a5 100644 --- a/beetsplug/zero.py +++ b/beetsplug/zero.py @@ -14,7 +14,6 @@ """ Clears tag fields in media files.""" -import six import re diff --git a/setup.py b/setup.py index 1f10b345f..8fc2d7f8b 100755 --- a/setup.py +++ b/setup.py @@ -85,7 +85,6 @@ setup( }, install_requires=[ - 'six>=1.9', 'unidecode', 'musicbrainzngs>=0.4', 'pyyaml', diff --git a/test/__init__.py b/test/__init__.py index f34357b7c..52514cb73 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -1,2 +1 @@ # Make python -m testall.py work. - diff --git a/test/helper.py b/test/helper.py index fcd997648..ba71ddc24 100644 --- a/test/helper.py +++ b/test/helper.py @@ -30,7 +30,6 @@ information or mock the environment. """ - import sys import os import os.path @@ -54,7 +53,6 @@ from beets.util import MoveOperation # TODO Move AutotagMock here from test import _common -import six class LogCapture(logging.Handler): @@ -578,6 +576,7 @@ def generate_album_info(album_id, track_values): return album + ALBUM_INFO_FIELDS = ['album', 'album_id', 'artist', 'artist_id', 'asin', 'albumtype', 'va', 'label', 'artist_sort', 'releasegroup_id', 'catalognum', @@ -602,6 +601,7 @@ def generate_track_info(track_id='track info', values={}): setattr(track, field, value) return track + TRACK_INFO_FIELDS = ['artist', 'artist_id', 'artist_sort', 'disctitle', 'artist_credit', 'data_source', 'data_url'] diff --git a/test/test_art_resize.py b/test/test_art_resize.py index fc5af07f4..fd9cc094e 100644 --- a/test/test_art_resize.py +++ b/test/test_art_resize.py @@ -15,7 +15,6 @@ """Tests for image resizing based on filesize.""" - import unittest import os diff --git a/test/test_beatport.py b/test/test_beatport.py index 53330fc26..6e75e5874 100644 --- a/test/test_beatport.py +++ b/test/test_beatport.py @@ -18,7 +18,6 @@ import unittest from test import _common from test.helper import TestHelper -import six from datetime import timedelta from beetsplug import beatport diff --git a/test/test_config_command.py b/test/test_config_command.py index 973f43b61..14e9df32b 100644 --- a/test/test_config_command.py +++ b/test/test_config_command.py @@ -10,7 +10,6 @@ from beets import config from test.helper import TestHelper from beets.library import Library -import six class ConfigCommandTest(unittest.TestCase, TestHelper): diff --git a/test/test_convert.py b/test/test_convert.py index ba7015179..3a248f3f3 100644 --- a/test/test_convert.py +++ b/test/test_convert.py @@ -49,7 +49,7 @@ class TestHelper(helper.TestHelper): # A Python script that copies the file and appends a tag. stub = os.path.join(_common.RSRC, b'convert_stub.py').decode('utf-8') return "{} {} $source $dest {}".format(shell_quote(sys.executable), - shell_quote(stub), tag) + shell_quote(stub), tag) def assertFileTag(self, path, tag): # noqa """Assert that the path is a file and the files content ends with `tag`. @@ -135,6 +135,7 @@ class ConvertCommand: """A mixin providing a utility method to run the `convert`command in tests. """ + def run_convert_path(self, path, *args): """Run the `convert` command on a given path.""" # The path is currently a filesystem bytestring. Convert it to diff --git a/test/test_dbcore.py b/test/test_dbcore.py index 0485756fa..603d85bad 100644 --- a/test/test_dbcore.py +++ b/test/test_dbcore.py @@ -19,12 +19,10 @@ import os import shutil import sqlite3 import unittest -from six import assertRaisesRegex from test import _common from beets import dbcore from tempfile import mkstemp -import six # Fixture: concrete database and model classes. For migration tests, we diff --git a/test/test_importadded.py b/test/test_importadded.py index 263ab34d0..bb160d190 100644 --- a/test/test_importadded.py +++ b/test/test_importadded.py @@ -112,7 +112,7 @@ class ImportAddedTest(unittest.TestCase, ImportHelper): album = self.lib.albums().get() album_added_before = album.added items_added_before = {item.path: item.added - for item in album.items()} + for item in album.items()} # Newer Item path mtimes as if Beets had modified them modify_mtimes(items_added_before.keys(), offset=10000) # Reimport @@ -122,7 +122,7 @@ class ImportAddedTest(unittest.TestCase, ImportHelper): album = self.lib.albums().get() self.assertEqualTimes(album.added, album_added_before) items_added_after = {item.path: item.added - for item in album.items()} + for item in album.items()} for item_path, added_after in items_added_after.items(): self.assertEqualTimes(items_added_before[item_path], added_after, "reimport modified Item.added for " + @@ -151,7 +151,7 @@ class ImportAddedTest(unittest.TestCase, ImportHelper): # Import and record the original added dates self.importer.run() items_added_before = {item.path: item.added - for item in self.lib.items()} + for item in self.lib.items()} # Newer Item path mtimes as if Beets had modified them modify_mtimes(items_added_before.keys(), offset=10000) # Reimport @@ -160,7 +160,7 @@ class ImportAddedTest(unittest.TestCase, ImportHelper): self.importer.run() # Verify the reimported items items_added_after = {item.path: item.added - for item in self.lib.items()} + for item in self.lib.items()} for item_path, added_after in items_added_after.items(): self.assertEqualTimes(items_added_before[item_path], added_after, "reimport modified Item.added for " + @@ -170,5 +170,6 @@ class ImportAddedTest(unittest.TestCase, ImportHelper): def suite(): return unittest.TestLoader().loadTestsFromName(__name__) + if __name__ == '__main__': unittest.main(defaultTest='suite') diff --git a/test/test_ipfs.py b/test/test_ipfs.py index c0b0b6854..8f72f5132 100644 --- a/test/test_ipfs.py +++ b/test/test_ipfs.py @@ -52,7 +52,7 @@ class IPFSPluginTest(unittest.TestCase, TestHelper): _fsencoding(), ) want_path = '/ipfs/{}/{}'.format(test_album.ipfs, - ipfs_item) + ipfs_item) want_path = bytestring_path(want_path) self.assertEqual(check_item.path, want_path) self.assertEqual(check_item.get('ipfs', with_album=False), @@ -95,5 +95,6 @@ class IPFSPluginTest(unittest.TestCase, TestHelper): def suite(): return unittest.TestLoader().loadTestsFromName(__name__) + if __name__ == '__main__': unittest.main(defaultTest='suite') diff --git a/test/test_lastgenre.py b/test/test_lastgenre.py index 07b12a73e..a7a939caa 100644 --- a/test/test_lastgenre.py +++ b/test/test_lastgenre.py @@ -23,7 +23,6 @@ from beetsplug import lastgenre from beets import config from test.helper import TestHelper -import six class LastGenrePluginTest(unittest.TestCase, TestHelper): diff --git a/test/test_library.py b/test/test_library.py index 6ce913602..8b04e50d0 100644 --- a/test/test_library.py +++ b/test/test_library.py @@ -35,7 +35,6 @@ from beets import config from mediafile import MediaFile, UnreadableFileError from beets.util import syspath, bytestring_path from test.helper import TestHelper -import six # Shortcut to path normalization. np = util.normpath diff --git a/test/test_logging.py b/test/test_logging.py index a5f4ffc84..76a73e931 100644 --- a/test/test_logging.py +++ b/test/test_logging.py @@ -12,7 +12,6 @@ import beetsplug from test import _common from test._common import TestCase from test import helper -import six class LoggingTest(TestCase): diff --git a/test/test_lyrics.py b/test/test_lyrics.py index 0f09af82e..cc4a907f1 100644 --- a/test/test_lyrics.py +++ b/test/test_lyrics.py @@ -18,7 +18,6 @@ import itertools import os import re -import six import sys import unittest diff --git a/test/test_playlist.py b/test/test_playlist.py index 3b5ac2660..74783fde8 100644 --- a/test/test_playlist.py +++ b/test/test_playlist.py @@ -12,13 +12,13 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -from six.moves import shlex_quote import os import shutil import tempfile import unittest +from shlex import quote from test import _common from test import helper @@ -88,7 +88,7 @@ class PlaylistQueryTestHelper(PlaylistTestHelper): }) def test_path_query_with_absolute_paths_in_playlist(self): - q = 'playlist:{}'.format(shlex_quote(os.path.join( + q = 'playlist:{}'.format(quote(os.path.join( self.playlist_dir, 'absolute.m3u', ))) @@ -107,7 +107,7 @@ class PlaylistQueryTestHelper(PlaylistTestHelper): }) def test_path_query_with_relative_paths_in_playlist(self): - q = 'playlist:{}'.format(shlex_quote(os.path.join( + q = 'playlist:{}'.format(quote(os.path.join( self.playlist_dir, 'relative.m3u', ))) @@ -123,7 +123,7 @@ class PlaylistQueryTestHelper(PlaylistTestHelper): self.assertEqual(set(results), set()) def test_path_query_with_nonexisting_playlist(self): - q = 'playlist:{}'.format(shlex_quote(os.path.join( + q = 'playlist:{}'.format(quote(os.path.join( self.playlist_dir, self.playlist_dir, 'nonexisting.m3u', @@ -218,7 +218,7 @@ class PlaylistUpdateTestHelper(PlaylistTestHelper): class PlaylistTestItemMoved(PlaylistUpdateTestHelper, unittest.TestCase): def test_item_moved(self): # Emit item_moved event for an item that is in a playlist - results = self.lib.items('path:{}'.format(shlex_quote( + results = self.lib.items('path:{}'.format(quote( os.path.join(self.music_dir, 'd', 'e', 'f.mp3')))) item = results[0] beets.plugins.send( @@ -227,7 +227,7 @@ class PlaylistTestItemMoved(PlaylistUpdateTestHelper, unittest.TestCase): os.path.join(self.music_dir, 'g', 'h', 'i.mp3'))) # Emit item_moved event for an item that is not in a playlist - results = self.lib.items('path:{}'.format(shlex_quote( + results = self.lib.items('path:{}'.format(quote( os.path.join(self.music_dir, 'x', 'y', 'z.mp3')))) item = results[0] beets.plugins.send( @@ -264,13 +264,13 @@ class PlaylistTestItemMoved(PlaylistUpdateTestHelper, unittest.TestCase): class PlaylistTestItemRemoved(PlaylistUpdateTestHelper, unittest.TestCase): def test_item_removed(self): # Emit item_removed event for an item that is in a playlist - results = self.lib.items('path:{}'.format(shlex_quote( + results = self.lib.items('path:{}'.format(quote( os.path.join(self.music_dir, 'd', 'e', 'f.mp3')))) item = results[0] beets.plugins.send('item_removed', item=item) # Emit item_removed event for an item that is not in a playlist - results = self.lib.items('path:{}'.format(shlex_quote( + results = self.lib.items('path:{}'.format(quote( os.path.join(self.music_dir, 'x', 'y', 'z.mp3')))) item = results[0] beets.plugins.send('item_removed', item=item) @@ -302,5 +302,6 @@ class PlaylistTestItemRemoved(PlaylistUpdateTestHelper, unittest.TestCase): def suite(): return unittest.TestLoader().loadTestsFromName(__name__) + if __name__ == '__main__': unittest.main(defaultTest='suite') diff --git a/test/test_plugin_mediafield.py b/test/test_plugin_mediafield.py index e5d336b9a..6179ce646 100644 --- a/test/test_plugin_mediafield.py +++ b/test/test_plugin_mediafield.py @@ -16,7 +16,6 @@ """ import os -import six import shutil import unittest diff --git a/test/test_query.py b/test/test_query.py index 68fd40cc1..e8161c41c 100644 --- a/test/test_query.py +++ b/test/test_query.py @@ -32,7 +32,6 @@ from beets.dbcore.query import (NoneQuery, ParsingError, from beets.library import Library, Item from beets import util import platform -import six class TestHelper(helper.TestHelper): diff --git a/test/test_replaygain.py b/test/test_replaygain.py index a2fdf5dbd..b39a4e990 100644 --- a/test/test_replaygain.py +++ b/test/test_replaygain.py @@ -13,8 +13,6 @@ # included in all copies or substantial portions of the Software. - -import six import unittest from mediafile import MediaFile @@ -214,5 +212,6 @@ class ReplayGainFfmpegTest(ReplayGainCliTestBase, unittest.TestCase): def suite(): return unittest.TestLoader().loadTestsFromName(__name__) + if __name__ == '__main__': unittest.main(defaultTest='suite') diff --git a/test/test_smartplaylist.py b/test/test_smartplaylist.py index b1fc11108..0c0add4ab 100644 --- a/test/test_smartplaylist.py +++ b/test/test_smartplaylist.py @@ -83,7 +83,7 @@ class SmartPlaylistTest(unittest.TestCase): spl.build_queries() sorts = {name: sort - for name, (_, sort), _ in spl._unmatched_playlists} + for name, (_, sort), _ in spl._unmatched_playlists} asseq = self.assertEqual # less cluttered code sort = FixedFieldSort # short cut since we're only dealing with this diff --git a/test/test_subsonicupdate.py b/test/test_subsonicupdate.py index 70a37d09d..a516e3487 100644 --- a/test/test_subsonicupdate.py +++ b/test/test_subsonicupdate.py @@ -8,11 +8,12 @@ from test import _common from beets import config from beetsplug import subsonicupdate from test.helper import TestHelper -from six.moves.urllib.parse import parse_qs, urlparse +from urllib.parse import parse_qs, urlparse class ArgumentsMock: """Argument mocks for tests.""" + def __init__(self, mode, show_failures): """Constructs ArgumentsMock.""" self.mode = mode diff --git a/test/test_template.py b/test/test_template.py index efc6da1d8..adc9b60d1 100644 --- a/test/test_template.py +++ b/test/test_template.py @@ -16,7 +16,6 @@ """ import unittest -import six from beets.util import functemplate diff --git a/test/test_ui_importer.py b/test/test_ui_importer.py index 43b833669..88daa0ce1 100644 --- a/test/test_ui_importer.py +++ b/test/test_ui_importer.py @@ -25,7 +25,6 @@ from test import test_importer from beets.ui.commands import TerminalImportSession from beets import importer from beets import config -import six class TerminalImportSessionFixture(TerminalImportSession): diff --git a/test/test_util.py b/test/test_util.py index 230d28bd6..32614ab72 100644 --- a/test/test_util.py +++ b/test/test_util.py @@ -24,7 +24,6 @@ from unittest.mock import patch, Mock from test import _common from beets import util -import six class UtilTest(unittest.TestCase): diff --git a/test/test_web.py b/test/test_web.py index c9e8ce678..9a18b1dba 100644 --- a/test/test_web.py +++ b/test/test_web.py @@ -4,7 +4,6 @@ import json import unittest import os.path -from six import assertCountEqual import shutil from test import _common @@ -672,5 +671,6 @@ class WebPluginTest(_common.LibTestCase): def suite(): return unittest.TestLoader().loadTestsFromName(__name__) + if __name__ == '__main__': unittest.main(defaultTest='suite') From 277f4e72d1892f51fbe9b565a49d93b66374a9b7 Mon Sep 17 00:00:00 2001 From: Andrew Rogl Date: Fri, 27 Aug 2021 08:26:07 +1000 Subject: [PATCH 11/26] Remove unrequired flake8 checks --- setup.cfg | 7 ------- setup.py | 2 -- 2 files changed, 9 deletions(-) diff --git a/setup.cfg b/setup.cfg index 778341a9b..6aab6b7e6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -19,13 +19,6 @@ ignore = F405, # name be undefined, or defined from star imports: module # mccabe error C901, # function is too complex - # future-import errors - # FI12, # `__future__` import "with_statement" missing - # FI14, # `__future__` import "unicode_literals" missing - # FI15, # `__future__` import "generator_stop" missing - # FI50, # `__future__` import "division" present - # FI51, # `__future__` import "absolute_import" present - # FI53, # `__future__` import "print_function" present N818, # Exception subclasses should be named with an Error suffix per-file-ignores = ./beet:D diff --git a/setup.py b/setup.py index 8fc2d7f8b..17294d446 100755 --- a/setup.py +++ b/setup.py @@ -116,9 +116,7 @@ setup( ], 'lint': [ 'flake8', - 'flake8-coding', 'flake8-docstrings', - 'flake8-future-import', 'pep8-naming', ], From 9106b41a50cc380922d4328172a9438b5c4699f0 Mon Sep 17 00:00:00 2001 From: Andrew Rogl Date: Fri, 27 Aug 2021 10:24:27 +1000 Subject: [PATCH 12/26] Remove as many as possible sys.version tests --- beets/autotag/mb.py | 5 +---- beets/util/__init__.py | 15 ++++----------- beets/util/artresizer.py | 5 +---- beets/util/functemplate.py | 26 ++------------------------ beetsplug/fetchart.py | 23 +++++------------------ test/rsrc/convert_stub.py | 9 +++------ test/test_art.py | 8 ++------ test/test_convert.py | 8 ++------ test/test_lyrics.py | 15 ++++++--------- test/test_query.py | 7 +++---- 10 files changed, 29 insertions(+), 92 deletions(-) diff --git a/beets/autotag/mb.py b/beets/autotag/mb.py index f659f15cb..d246e1d9f 100644 --- a/beets/autotag/mb.py +++ b/beets/autotag/mb.py @@ -29,10 +29,7 @@ from urllib.parse import urljoin VARIOUS_ARTISTS_ID = '89ad4ac3-39f7-470e-963a-56509c546377' -if util.SNI_SUPPORTED: - BASE_URL = 'https://musicbrainz.org/' -else: - BASE_URL = 'http://musicbrainz.org/' +BASE_URL = 'https://musicbrainz.org/' SKIPPED_TRACKS = ['[data track]'] diff --git a/beets/util/__init__.py b/beets/util/__init__.py index 6ee8d68a3..7ae71164e 100644 --- a/beets/util/__init__.py +++ b/beets/util/__init__.py @@ -35,7 +35,6 @@ from enum import Enum MAX_FILENAME_LENGTH = 200 WINDOWS_MAGIC_PREFIX = '\\\\?\\' -SNI_SUPPORTED = sys.version_info >= (2, 7, 9) class HumanReadableException(Exception): @@ -1043,16 +1042,10 @@ def par_map(transform, items): The parallelism uses threads (not processes), so this is only useful for IO-bound `transform`s. """ - if sys.version_info[0] < 3: - # multiprocessing.pool.ThreadPool does not seem to work on - # Python 2. We could consider switching to futures instead. - for item in items: - transform(item) - else: - pool = ThreadPool() - pool.map(transform, items) - pool.close() - pool.join() + pool = ThreadPool() + pool.map(transform, items) + pool.close() + pool.join() def lazy_property(func): diff --git a/beets/util/artresizer.py b/beets/util/artresizer.py index 725712ed4..b11c89c8c 100644 --- a/beets/util/artresizer.py +++ b/beets/util/artresizer.py @@ -29,10 +29,7 @@ PIL = 1 IMAGEMAGICK = 2 WEBPROXY = 3 -if util.SNI_SUPPORTED: - PROXY_URL = 'https://images.weserv.nl/' -else: - PROXY_URL = 'http://images.weserv.nl/' +PROXY_URL = 'https://images.weserv.nl/' log = logging.getLogger('beets') diff --git a/beets/util/functemplate.py b/beets/util/functemplate.py index 802076649..289a436de 100644 --- a/beets/util/functemplate.py +++ b/beets/util/functemplate.py @@ -71,26 +71,7 @@ def ex_literal(val): """An int, float, long, bool, string, or None literal with the given value. """ - if sys.version_info[:2] < (3, 4): - if val is None: - return ast.Name('None', ast.Load()) - elif isinstance(val, int): - return ast.Num(val) - elif isinstance(val, bool): - return ast.Name(bytes(val), ast.Load()) - elif isinstance(val, str): - return ast.Str(val) - raise TypeError('no literal for {}'.format(type(val))) - elif sys.version_info[:2] < (3, 6): - if val in [None, True, False]: - return ast.NameConstant(val) - elif isinstance(val, int): - return ast.Num(val) - elif isinstance(val, str): - return ast.Str(val) - raise TypeError('no literal for {}'.format(type(val))) - else: - return ast.Constant(val) + return ast.Constant(val) def ex_varassign(name, expr): @@ -115,10 +96,7 @@ def ex_call(func, args): if not isinstance(args[i], ast.expr): args[i] = ex_literal(args[i]) - if sys.version_info[:2] < (3, 5): - return ast.Call(func, args, [], None, None) - else: - return ast.Call(func, args, []) + return ast.Call(func, args, []) def compile_func(arg_names, statements, name='_the_func', debug=False): diff --git a/beetsplug/fetchart.py b/beetsplug/fetchart.py index 2076d5986..64b66fdc5 100644 --- a/beetsplug/fetchart.py +++ b/beetsplug/fetchart.py @@ -332,12 +332,8 @@ class CoverArtArchive(RemoteArtSource): VALID_MATCHING_CRITERIA = ['release', 'releasegroup'] VALID_THUMBNAIL_SIZES = [250, 500, 1200] - if util.SNI_SUPPORTED: - URL = 'https://coverartarchive.org/release/{mbid}' - GROUP_URL = 'https://coverartarchive.org/release-group/{mbid}' - else: - URL = 'http://coverartarchive.org/release/{mbid}' - GROUP_URL = 'http://coverartarchive.org/release-group/{mbid}' + URL = 'https://coverartarchive.org/release/{mbid}' + GROUP_URL = 'https://coverartarchive.org/release-group/{mbid}' def get(self, album, plugin, paths): """Return the Cover Art Archive and Cover Art Archive release group URLs @@ -393,10 +389,7 @@ class CoverArtArchive(RemoteArtSource): class Amazon(RemoteArtSource): NAME = "Amazon" - if util.SNI_SUPPORTED: - URL = 'https://images.amazon.com/images/P/%s.%02i.LZZZZZZZ.jpg' - else: - URL = 'http://images.amazon.com/images/P/%s.%02i.LZZZZZZZ.jpg' + URL = 'https://images.amazon.com/images/P/%s.%02i.LZZZZZZZ.jpg' INDICES = (1, 2) def get(self, album, plugin, paths): @@ -410,10 +403,7 @@ class Amazon(RemoteArtSource): class AlbumArtOrg(RemoteArtSource): NAME = "AlbumArt.org scraper" - if util.SNI_SUPPORTED: - URL = 'https://www.albumart.org/index_detail.php' - else: - URL = 'http://www.albumart.org/index_detail.php' + URL = 'https://www.albumart.org/index_detail.php' PAT = r'href\s*=\s*"([^>"]*)"[^>]*title\s*=\s*"View larger image"' def get(self, album, plugin, paths): @@ -830,10 +820,7 @@ class LastFM(RemoteArtSource): ('small', (34, 34)), ]) - if util.SNI_SUPPORTED: - API_URL = 'https://ws.audioscrobbler.com/2.0' - else: - API_URL = 'http://ws.audioscrobbler.com/2.0' + API_URL = 'https://ws.audioscrobbler.com/2.0' def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/test/rsrc/convert_stub.py b/test/rsrc/convert_stub.py index 28dbfffc5..f58fa1d96 100755 --- a/test/rsrc/convert_stub.py +++ b/test/rsrc/convert_stub.py @@ -5,11 +5,8 @@ a specified text tag. """ import sys -import platform import locale -PY2 = sys.version_info[0] == 2 - # From `beets.util`. def arg_encoding(): @@ -28,9 +25,9 @@ def convert(in_file, out_file, tag): # On Windows, use Unicode paths. On Python 3, we get the actual, # Unicode filenames. On Python 2, we get them as UTF-8 byes. - if platform.system() == 'Windows' and PY2: - in_file = in_file.decode('utf-8') - out_file = out_file.decode('utf-8') + # if platform.system() == 'Windows' and PY2: + # in_file = in_file.decode('utf-8') + # out_file = out_file.decode('utf-8') with open(out_file, 'wb') as out_f: with open(in_file, 'rb') as in_f: diff --git a/test/test_art.py b/test/test_art.py index 0377da8d8..498c4cedc 100644 --- a/test/test_art.py +++ b/test/test_art.py @@ -84,12 +84,8 @@ class CAAHelper(): GROUP_URL = 'coverartarchive.org/release-group/{}' \ .format(MBID_GROUP) - if util.SNI_SUPPORTED: - RELEASE_URL = "https://" + RELEASE_URL - GROUP_URL = "https://" + GROUP_URL - else: - RELEASE_URL = "http://" + RELEASE_URL - GROUP_URL = "http://" + GROUP_URL + RELEASE_URL = "https://" + RELEASE_URL + GROUP_URL = "https://" + GROUP_URL RESPONSE_RELEASE = """{ "images": [ diff --git a/test/test_convert.py b/test/test_convert.py index 3a248f3f3..ce0750119 100644 --- a/test/test_convert.py +++ b/test/test_convert.py @@ -28,12 +28,8 @@ from beets import util def shell_quote(text): - if sys.version_info[0] < 3: - import pipes - return pipes.quote(text) - else: - import shlex - return shlex.quote(text) + import shlex + return shlex.quote(text) class TestHelper(helper.TestHelper): diff --git a/test/test_lyrics.py b/test/test_lyrics.py index cc4a907f1..019cf3d88 100644 --- a/test/test_lyrics.py +++ b/test/test_lyrics.py @@ -18,7 +18,6 @@ import itertools import os import re -import sys import unittest import confuse @@ -240,6 +239,7 @@ def is_lyrics_content_ok(title, text): words = {x.strip(".?, ") for x in text.lower().split()} return keywords <= words + LYRICS_ROOT_DIR = os.path.join(_common.RSRC, b'lyrics') yaml_path = os.path.join(_common.RSRC, b'lyricstext.yaml') LYRICS_TEXTS = confuse.load_yaml(yaml_path) @@ -253,8 +253,6 @@ class LyricsGoogleBaseTest(unittest.TestCase): __import__('bs4') except ImportError: self.skipTest('Beautiful Soup 4 not available') - if sys.version_info[:3] < (2, 7, 3): - self.skipTest("Python's built-in HTML parser is not good enough") class LyricsPluginSourcesTest(LyricsGoogleBaseTest): @@ -429,8 +427,6 @@ class GeniusBaseTest(unittest.TestCase): __import__('bs4') except ImportError: self.skipTest('Beautiful Soup 4 not available') - if sys.version_info[:3] < (2, 7, 3): - self.skipTest("Python's built-in HTML parser is not good enough") class GeniusScrapeLyricsFromHtmlTest(GeniusBaseTest): @@ -480,17 +476,17 @@ class GeniusFetchTest(GeniusBaseTest): "result": { "primary_artist": { "name": "\u200Bblackbear", - }, + }, "url": "blackbear_url" - } + } }, { "result": { "primary_artist": { "name": "El\u002Dp" - }, + }, "url": "El-p_url" - } + } } ] } @@ -552,5 +548,6 @@ class SlugTests(unittest.TestCase): def suite(): return unittest.TestLoader().loadTestsFromName(__name__) + if __name__ == '__main__': unittest.main(defaultTest='suite') diff --git a/test/test_query.py b/test/test_query.py index e8161c41c..709f42bd5 100644 --- a/test/test_query.py +++ b/test/test_query.py @@ -317,10 +317,7 @@ class GetTest(DummyDataTestCase): dbcore.query.RegexpQuery('year', '199(') exception_text = str(raised.exception) self.assertIn('not a regular expression', exception_text) - if sys.version_info >= (3, 5): - self.assertIn('unterminated subpattern', exception_text) - else: - self.assertIn('unbalanced parenthesis', exception_text) + self.assertIn('unterminated subpattern', exception_text) self.assertIsInstance(raised.exception, ParsingError) @@ -803,6 +800,7 @@ class NotQueryMatchTest(_common.TestCase): cases and assertions as on `MatchTest`, plus assertion on the negated queries (ie. assertTrue(q) -> assertFalse(NotQuery(q))). """ + def setUp(self): super().setUp() self.item = _common.item() @@ -867,6 +865,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): # noqa """Given a Query `q`, assert that: - q OR not(q) == all items From f04059bfee4aa7c0b4e1036d31ba75c1543ac9ad Mon Sep 17 00:00:00 2001 From: Andrew Rogl Date: Sun, 26 Sep 2021 15:31:48 +1000 Subject: [PATCH 13/26] Commit #4038 --- docs/changelog.rst | 6 ++++++ test/test_ui.py | 2 ++ 2 files changed, 8 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 0e1cc30a5..800230e46 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -7,6 +7,12 @@ Changelog This release now requires Python 3.6 or later (it removes support for Python 2.7, 3.4, and 3.5). +For packagers: + ++* We fixed a flaky test, named `test_album_art` in the `test_zero.py` file, + that some distributions had disabled. Disabling this test should no longer + be necessary. + :bug:`4037` :bug:`4038` 1.5.0 (August 19, 2021) ----------------------- diff --git a/test/test_ui.py b/test/test_ui.py index 9f3fbdaa5..9804b0a12 100644 --- a/test/test_ui.py +++ b/test/test_ui.py @@ -768,6 +768,7 @@ class ConfigTest(unittest.TestCase, TestHelper, _common.Assertions): os.makedirs(self.beetsdir) self._reset_config() + self.load_plugins() def tearDown(self): commands.default_commands.pop() @@ -778,6 +779,7 @@ class ConfigTest(unittest.TestCase, TestHelper, _common.Assertions): del os.environ['APPDATA'] else: os.environ['APPDATA'] = self._old_appdata + self.unload_plugins() self.teardown_beets() def _make_test_cmd(self): From 463ad55ce968dbb72cddad4dc0d4a7ed46bcacb5 Mon Sep 17 00:00:00 2001 From: Andrew Rogl Date: Sun, 26 Sep 2021 15:37:56 +1000 Subject: [PATCH 14/26] Commit #4044 --- beets/autotag/mb.py | 15 ++++++++++++--- docs/changelog.rst | 8 +++++++- docs/reference/config.rst | 8 ++++---- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/beets/autotag/mb.py b/beets/autotag/mb.py index d246e1d9f..cb7d8e26f 100644 --- a/beets/autotag/mb.py +++ b/beets/autotag/mb.py @@ -25,6 +25,7 @@ import beets.autotag.hooks import beets from beets import util from beets import config +from collections import Counter from urllib.parse import urljoin VARIOUS_ARTISTS_ID = '89ad4ac3-39f7-470e-963a-56509c546377' @@ -454,9 +455,17 @@ def album_info(release): first_medium = release['medium-list'][0] info.media = first_medium.get('format') - genres = release.get('genre-list') - if config['musicbrainz']['genres'] and genres: - info.genre = ';'.join(g['name'] for g in genres) + if config['musicbrainz']['genres']: + sources = [ + release['release-group'].get('genre-list', []), + release.get('genre-list', []), + ] + genres = Counter() + for source in sources: + for genreitem in source: + genres[genreitem['name']] += int(genreitem['count']) + info.genre = '; '.join(g[0] for g in sorted(genres.items(), + key=lambda g: -g[1])) extra_albumdatas = plugins.send('mb_album_extract', data=release) for extra_albumdata in extra_albumdatas: diff --git a/docs/changelog.rst b/docs/changelog.rst index 800230e46..1a0424811 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -9,11 +9,17 @@ This release now requires Python 3.6 or later (it removes support for Python For packagers: -+* We fixed a flaky test, named `test_album_art` in the `test_zero.py` file, +* We fixed a flaky test, named `test_album_art` in the `test_zero.py` file, that some distributions had disabled. Disabling this test should no longer be necessary. :bug:`4037` :bug:`4038` +Major new features: + +* Include the genre tags from the release group when the musicbrainz genre + option is set, and sort them by the number of votes. Thanks to + :user:`aereaux`. + 1.5.0 (August 19, 2021) ----------------------- diff --git a/docs/reference/config.rst b/docs/reference/config.rst index aabe732c2..cc85cd269 100644 --- a/docs/reference/config.rst +++ b/docs/reference/config.rst @@ -753,10 +753,10 @@ Default: ``[]`` genres ~~~~~~ -Use MusicBrainz genre tags to populate the ``genre`` tag. This will make it a -semicolon-separated list of all the genres tagged for the release on -MusicBrainz. - +Use MusicBrainz genre tags to populate (and replace if it's already set) the +``genre`` tag. This will make it a list of all the genres tagged for the +release and the release-group on MusicBrainz, separated by "; " and sorted by +the total number of votes. Default: ``no`` .. _match-config: From 7360681f30590adcc67abd01d0051fbd90d97343 Mon Sep 17 00:00:00 2001 From: Andrew Rogl Date: Sun, 26 Sep 2021 15:50:29 +1000 Subject: [PATCH 15/26] Commit #4045 --- beets/autotag/mb.py | 13 ++++++------- beets/library.py | 3 +++ docs/changelog.rst | 4 ++++ 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/beets/autotag/mb.py b/beets/autotag/mb.py index cb7d8e26f..e6a2e277f 100644 --- a/beets/autotag/mb.py +++ b/beets/autotag/mb.py @@ -413,18 +413,17 @@ def album_info(release): if reltype: info.albumtype = reltype.lower() - # Log the new-style "primary" and "secondary" release types. - # Eventually, we'd like to actually store this data, but we just log - # it for now to help understand the differences. + # Set the new-style "primary" and "secondary" release types. + albumtypes = [] if 'primary-type' in release['release-group']: rel_primarytype = release['release-group']['primary-type'] if rel_primarytype: - log.debug('primary MB release type: ' + rel_primarytype.lower()) + albumtypes.append(rel_primarytype.lower()) if 'secondary-type-list' in release['release-group']: if release['release-group']['secondary-type-list']: - log.debug('secondary MB release type(s): ' + ', '.join( - [secondarytype.lower() for secondarytype in - release['release-group']['secondary-type-list']])) + for sec_type in release['release-group']['secondary-type-list']: + albumtypes.append(sec_type.lower()) + info.albumtypes = '; '.join(albumtypes) # Release events. info.country, release_date = _preferred_release_event(release) diff --git a/beets/library.py b/beets/library.py index 38b129cab..d94468800 100644 --- a/beets/library.py +++ b/beets/library.py @@ -494,6 +494,7 @@ class Item(LibModel): 'mb_releasetrackid': types.STRING, 'trackdisambig': types.STRING, 'albumtype': types.STRING, + 'albumtypes': types.STRING, 'label': types.STRING, 'acoustid_fingerprint': types.STRING, 'acoustid_id': types.STRING, @@ -1042,6 +1043,7 @@ class Album(LibModel): 'mb_albumid': types.STRING, 'mb_albumartistid': types.STRING, 'albumtype': types.STRING, + 'albumtypes': types.STRING, 'label': types.STRING, 'mb_releasegroupid': types.STRING, 'asin': types.STRING, @@ -1091,6 +1093,7 @@ class Album(LibModel): 'mb_albumid', 'mb_albumartistid', 'albumtype', + 'albumtypes', 'label', 'mb_releasegroupid', 'asin', diff --git a/docs/changelog.rst b/docs/changelog.rst index 1a0424811..149489272 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -7,6 +7,10 @@ Changelog This release now requires Python 3.6 or later (it removes support for Python 2.7, 3.4, and 3.5). +* Primary and secondary release types from MusicBrainz are now stored in + ``albumtypes`` field. Thanks to :user:`edgars-supe`. + :bug:`2200` + For packagers: * We fixed a flaky test, named `test_album_art` in the `test_zero.py` file, From d818f4642eaffd1c6979ac50003d9bd7436a4052 Mon Sep 17 00:00:00 2001 From: Andrew Rogl Date: Sun, 26 Sep 2021 15:57:02 +1000 Subject: [PATCH 16/26] Commit #4049 --- docs/plugins/index.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/plugins/index.rst b/docs/plugins/index.rst index c19e557b7..f3d587038 100644 --- a/docs/plugins/index.rst +++ b/docs/plugins/index.rst @@ -307,6 +307,9 @@ Here are a few of the plugins written by the beets community: * `beets-ibroadcast`_ uploads tracks to the `iBroadcast`_ cloud service. +* `beets-importreplace`_ lets you perform regex replacements on incoming + metadata. + * `beets-mosaic`_ generates a montage of a mosaic from cover art. * `beets-noimport`_ adds and removes directories from the incremental import skip list. @@ -348,6 +351,7 @@ Here are a few of the plugins written by the beets community: .. _beets-follow: https://github.com/nolsto/beets-follow .. _beets-ibroadcast: https://github.com/ctrueden/beets-ibroadcast .. _iBroadcast: https://ibroadcast.com/ +.. _beets-importreplace: https://github.com/edgars-supe/beets-importreplace .. _beets-setlister: https://github.com/tomjaspers/beets-setlister .. _beets-noimport: https://gitlab.com/tiago.dias/beets-noimport .. _whatlastgenre: https://github.com/YetAnotherNerd/whatlastgenre/tree/master/plugin/beets From 8205b901db55d08ad0776495e3cad1181378e8ae Mon Sep 17 00:00:00 2001 From: Andrew Rogl Date: Sun, 26 Sep 2021 16:03:00 +1000 Subject: [PATCH 17/26] Commits #4048 & #4050 --- beetsplug/albumtypes.py | 68 ++++++++++++++++++++++ docs/changelog.rst | 3 + docs/plugins/albumtypes.rst | 57 ++++++++++++++++++ docs/plugins/index.rst | 2 + test/test_albumtypes.py | 113 ++++++++++++++++++++++++++++++++++++ 5 files changed, 243 insertions(+) create mode 100644 beetsplug/albumtypes.py create mode 100644 docs/plugins/albumtypes.rst create mode 100644 test/test_albumtypes.py diff --git a/beetsplug/albumtypes.py b/beetsplug/albumtypes.py new file mode 100644 index 000000000..a73d41b4e --- /dev/null +++ b/beetsplug/albumtypes.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- + +# This file is part of beets. +# Copyright 2021, Edgars Supe. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +"""Adds an album template field for formatted album types.""" + +from __future__ import division, absolute_import, print_function + +from beets.autotag.mb import VARIOUS_ARTISTS_ID +from beets.library import Album +from beets.plugins import BeetsPlugin + + +class AlbumTypesPlugin(BeetsPlugin): + """Adds an album template field for formatted album types.""" + + def __init__(self): + """Init AlbumTypesPlugin.""" + super(AlbumTypesPlugin, self).__init__() + self.album_template_fields['atypes'] = self._atypes + self.config.add({ + 'types': [ + ('ep', 'EP'), + ('single', 'Single'), + ('soundtrack', 'OST'), + ('live', 'Live'), + ('compilation', 'Anthology'), + ('remix', 'Remix') + ], + 'ignore_va': ['compilation'], + 'bracket': '[]' + }) + + def _atypes(self, item: Album): + """Returns a formatted string based on album's types.""" + types = self.config['types'].as_pairs() + ignore_va = self.config['ignore_va'].as_str_seq() + bracket = self.config['bracket'].as_str() + + # Assign a left and right bracket or leave blank if argument is empty. + if len(bracket) == 2: + bracket_l = bracket[0] + bracket_r = bracket[1] + else: + bracket_l = u'' + bracket_r = u'' + + res = '' + albumtypes = item.albumtypes.split('; ') + is_va = item.mb_albumartistid == VARIOUS_ARTISTS_ID + for type in types: + if type[0] in albumtypes and type[1]: + if not is_va or (type[0] not in ignore_va and is_va): + res += f'{bracket_l}{type[1]}{bracket_r}' + + return res diff --git a/docs/changelog.rst b/docs/changelog.rst index 149489272..216556ec4 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -11,6 +11,9 @@ This release now requires Python 3.6 or later (it removes support for Python ``albumtypes`` field. Thanks to :user:`edgars-supe`. :bug:`2200` +* :doc:`/plugins/albumtypes`: An accompanying plugin for formatting + ``albumtypes``. Thanks to :user:`edgars-supe`. + For packagers: * We fixed a flaky test, named `test_album_art` in the `test_zero.py` file, diff --git a/docs/plugins/albumtypes.rst b/docs/plugins/albumtypes.rst new file mode 100644 index 000000000..8ae7cb633 --- /dev/null +++ b/docs/plugins/albumtypes.rst @@ -0,0 +1,57 @@ +AlbumTypes Plugin +================= + +The ``albumtypes`` plugin adds the ability to format and output album types, +such as "Album", "EP", "Single", etc. For the list of available album types, +see the `MusicBrainz documentation`_. + +To use the ``albumtypes`` plugin, enable it in your configuration +(see :ref:`using-plugins`). The plugin defines a new field ``$atypes``, which +you can use in your path formats or elsewhere. + +.. _MusicBrainz documentation: https://musicbrainz.org/doc/Release_Group/Type + +Configuration +------------- + +To configure the plugin, make a ``albumtypes:`` section in your configuration +file. The available options are: + +- **types**: An ordered list of album type to format mappings. The order of the + mappings determines their order in the output. If a mapping is missing or + blank, it will not be in the output. +- **ignore_va**: A list of types that should not be output for Various Artists + albums. Useful for not adding redundant information - various artist albums + are often compilations. +- **bracket**: Defines the brackets to enclose each album type in the output. + +The default configuration looks like this:: + + albumtypes: + types: + - ep: 'EP' + - single: 'Single' + - soundtrack: 'OST' + - live: 'Live' + - compilation: 'Anthology' + - remix: 'Remix' + ignore_va: compilation + bracket: '[]' + +Examples +-------- +With path formats configured like:: + + paths: + default: $albumartist/[$year]$atypes $album/... + albumtype:soundtrack Various Artists/$album [$year]$atypes/... + comp: Various Artists/$album [$year]$atypes/... + + +The default plugin configuration generates paths that look like this, for example:: + + Aphex Twin/[1993][EP][Remix] On Remixes + Pink Floyd/[1995][Live] p·u·l·s·e + Various Artists/20th Century Lullabies [1999] + Various Artists/Ocean's Eleven [2001][OST] + diff --git a/docs/plugins/index.rst b/docs/plugins/index.rst index f3d587038..9c628951a 100644 --- a/docs/plugins/index.rst +++ b/docs/plugins/index.rst @@ -61,6 +61,7 @@ following to your configuration:: absubmit acousticbrainz + albumtypes aura badfiles bareasc @@ -176,6 +177,7 @@ Metadata Path Formats ------------ +* :doc:`albumtypes`: Format album type in path formats. * :doc:`bucket`: Group your files into bucket directories that cover different field values ranges. * :doc:`inline`: Use Python snippets to customize path format strings. diff --git a/test/test_albumtypes.py b/test/test_albumtypes.py new file mode 100644 index 000000000..a9db12c30 --- /dev/null +++ b/test/test_albumtypes.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- +# This file is part of beets. +# Copyright 2021, Edgars Supe. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +"""Tests for the 'albumtypes' plugin.""" + +from __future__ import division, absolute_import, print_function + +import unittest + +from beets.autotag.mb import VARIOUS_ARTISTS_ID +from beetsplug.albumtypes import AlbumTypesPlugin +from test.helper import TestHelper + + +class AlbumTypesPluginTest(unittest.TestCase, TestHelper): + """Tests for albumtypes plugin.""" + + def setUp(self): + """Set up tests.""" + self.setup_beets() + self.load_plugins('albumtypes') + + def tearDown(self): + """Tear down tests.""" + self.unload_plugins() + self.teardown_beets() + + def test_renames_types(self): + """Tests if the plugin correctly renames the specified types.""" + self._set_config( + types=[('ep', 'EP'), ('remix', 'Remix')], + ignore_va=[], + bracket='()' + ) + album = self._create_album(album_types=['ep', 'remix']) + subject = AlbumTypesPlugin() + result = subject._atypes(album) + self.assertEqual('(EP)(Remix)', result) + return + + def test_returns_only_specified_types(self): + """Tests if the plugin returns only non-blank types given in config.""" + self._set_config( + types=[('ep', 'EP'), ('soundtrack', '')], + ignore_va=[], + bracket='()' + ) + album = self._create_album(album_types=['ep', 'remix', 'soundtrack']) + subject = AlbumTypesPlugin() + result = subject._atypes(album) + self.assertEqual('(EP)', result) + + def test_respects_type_order(self): + """Tests if the types are returned in the same order as config.""" + self._set_config( + types=[('remix', 'Remix'), ('ep', 'EP')], + ignore_va=[], + bracket='()' + ) + album = self._create_album(album_types=['ep', 'remix']) + subject = AlbumTypesPlugin() + result = subject._atypes(album) + self.assertEqual('(Remix)(EP)', result) + return + + def test_ignores_va(self): + """Tests if the specified type is ignored for VA albums.""" + self._set_config( + types=[('ep', 'EP'), ('soundtrack', 'OST')], + ignore_va=['ep'], + bracket='()' + ) + album = self._create_album( + album_types=['ep', 'soundtrack'], + artist_id=VARIOUS_ARTISTS_ID + ) + subject = AlbumTypesPlugin() + result = subject._atypes(album) + self.assertEqual('(OST)', result) + + def test_respects_defaults(self): + """Tests if the plugin uses the default values if config not given.""" + album = self._create_album( + album_types=['ep', 'single', 'soundtrack', 'live', 'compilation', + 'remix'], + artist_id=VARIOUS_ARTISTS_ID + ) + subject = AlbumTypesPlugin() + result = subject._atypes(album) + self.assertEqual('[EP][Single][OST][Live][Remix]', result) + + def _set_config(self, types: [(str, str)], ignore_va: [str], bracket: str): + self.config['albumtypes']['types'] = types + self.config['albumtypes']['ignore_va'] = ignore_va + self.config['albumtypes']['bracket'] = bracket + + def _create_album(self, album_types: [str], artist_id: str = 0): + return self.add_album( + albumtypes='; '.join(album_types), + mb_albumartistid=artist_id + ) From 6cd7998c3d9b23a64bc534f46eb53c1607f44752 Mon Sep 17 00:00:00 2001 From: Andrew Rogl Date: Sun, 26 Sep 2021 16:27:20 +1000 Subject: [PATCH 18/26] Forgot to pyupgrade new file --- beetsplug/albumtypes.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/beetsplug/albumtypes.py b/beetsplug/albumtypes.py index a73d41b4e..47f8dc64e 100644 --- a/beetsplug/albumtypes.py +++ b/beetsplug/albumtypes.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - # This file is part of beets. # Copyright 2021, Edgars Supe. # @@ -16,7 +14,6 @@ """Adds an album template field for formatted album types.""" -from __future__ import division, absolute_import, print_function from beets.autotag.mb import VARIOUS_ARTISTS_ID from beets.library import Album @@ -28,7 +25,7 @@ class AlbumTypesPlugin(BeetsPlugin): def __init__(self): """Init AlbumTypesPlugin.""" - super(AlbumTypesPlugin, self).__init__() + super().__init__() self.album_template_fields['atypes'] = self._atypes self.config.add({ 'types': [ @@ -54,8 +51,8 @@ class AlbumTypesPlugin(BeetsPlugin): bracket_l = bracket[0] bracket_r = bracket[1] else: - bracket_l = u'' - bracket_r = u'' + bracket_l = '' + bracket_r = '' res = '' albumtypes = item.albumtypes.split('; ') From 5859d31405f2e48c776072345caf47ebea667dc6 Mon Sep 17 00:00:00 2001 From: Andrew Rogl Date: Sun, 26 Sep 2021 16:41:13 +1000 Subject: [PATCH 19/26] Commit #4036 --- beetsplug/permissions.py | 41 +++++++++++++++++++++------------------- docs/changelog.rst | 4 ++++ test/test_permissions.py | 20 ++++++++++++++++++++ 3 files changed, 46 insertions(+), 19 deletions(-) diff --git a/beetsplug/permissions.py b/beetsplug/permissions.py index cd4577e4f..f5aab056c 100644 --- a/beetsplug/permissions.py +++ b/beetsplug/permissions.py @@ -65,10 +65,29 @@ class Permissions(BeetsPlugin): self.register_listener('item_imported', self.fix) self.register_listener('album_imported', self.fix) + self.register_listener('art_set', self.fix_art) def fix(self, lib, item=None, album=None): """Fix the permissions for an imported Item or Album. """ + files = [] + dirs = set() + if item: + files.append(item.path) + dirs.update(dirs_in_library(lib.directory, item.path)) + elif album: + for album_item in album.items(): + files.append(album_item.path) + dirs.update(dirs_in_library(lib.directory, album_item.path)) + self.set_permissions(files=files, dirs=dirs) + + def fix_art(self, album): + """Fix the permission for Album art file. + """ + if album.artpath: + self.set_permissions(files=[album.artpath]) + + def set_permissions(self, files=[], dirs=[]): # Get the configured permissions. The user can specify this either a # string (in YAML quotes) or, for convenience, as an integer so the # quotes can be omitted. In the latter case, we need to reinterpret the @@ -78,18 +97,7 @@ class Permissions(BeetsPlugin): file_perm = convert_perm(file_perm) dir_perm = convert_perm(dir_perm) - # Create chmod_queue. - file_chmod_queue = [] - if item: - file_chmod_queue.append(item.path) - elif album: - for album_item in album.items(): - file_chmod_queue.append(album_item.path) - - # A set of directories to change permissions for. - dir_chmod_queue = set() - - for path in file_chmod_queue: + for path in files: # Changing permissions on the destination file. self._log.debug( 'setting file permissions on {}', @@ -100,14 +108,9 @@ class Permissions(BeetsPlugin): # Checks if the destination path has the permissions configured. assert_permissions(path, file_perm, self._log) - # Adding directories to the directory chmod queue. - dir_chmod_queue.update( - dirs_in_library(lib.directory, - path)) - # Change permissions for the directories. - for path in dir_chmod_queue: - # Chaning permissions on the destination directory. + for path in dirs: + # Changing permissions on the destination directory. self._log.debug( 'setting directory permissions on {}', util.displayable_path(path), diff --git a/docs/changelog.rst b/docs/changelog.rst index 216556ec4..cf99baae2 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -27,6 +27,10 @@ Major new features: option is set, and sort them by the number of votes. Thanks to :user:`aereaux`. +Other new things: + +* Permissions plugin now sets cover art permissions to the file permissions. + 1.5.0 (August 19, 2021) ----------------------- diff --git a/test/test_permissions.py b/test/test_permissions.py index a67c219c9..c84de5e97 100644 --- a/test/test_permissions.py +++ b/test/test_permissions.py @@ -7,6 +7,7 @@ import unittest from unittest.mock import patch, Mock from test.helper import TestHelper +from test._common import touch from beets.util import displayable_path from beetsplug.permissions import (check_permissions, convert_perm, @@ -79,6 +80,25 @@ class PermissionsPluginTest(unittest.TestCase, TestHelper): def test_convert_perm_from_int(self): self.assertEqual(convert_perm(10), 8) + def test_permissions_on_set_art(self): + self.do_set_art(True) + + @patch("os.chmod", Mock()) + def test_failing_permissions_on_set_art(self): + self.do_set_art(False) + + def do_set_art(self, expect_success): + if platform.system() == 'Windows': + self.skipTest('permissions not available on Windows') + self.importer = self.create_importer() + self.importer.run() + album = self.lib.albums().get() + artpath = os.path.join(self.temp_dir, b'cover.jpg') + touch(artpath) + album.set_art(artpath) + self.assertEqual(expect_success, + check_permissions(album.artpath, 0o777)) + def suite(): return unittest.TestLoader().loadTestsFromName(__name__) From 94d73aebe9a8e2fa81a8a682f7f4e2e39fb5bdf5 Mon Sep 17 00:00:00 2001 From: Andrew Rogl Date: Sun, 26 Sep 2021 16:42:57 +1000 Subject: [PATCH 20/26] Commit #4067 --- docs/reference/config.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/reference/config.rst b/docs/reference/config.rst index cc85cd269..f041be625 100644 --- a/docs/reference/config.rst +++ b/docs/reference/config.rst @@ -165,6 +165,10 @@ take place before applying the :ref:`replace` configuration and are roughly equivalent to wrapping all your path templates in the ``%asciify{}`` :ref:`template function `. +This uses the `unidecode module`_ which is language agnostic, so some +characters may be transliterated from a different language than expected. +For example, Japanese kanji will usually use their Chinese readings. + Default: ``no``. .. _unidecode module: https://pypi.org/project/Unidecode From e953aed6cd6acffe19533c0e682bd671beac2720 Mon Sep 17 00:00:00 2001 From: Andrew Rogl Date: Sun, 26 Sep 2021 16:45:01 +1000 Subject: [PATCH 21/26] Use RC-2 for 3.10 --- .github/workflows/ci.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c9e1750ea..5310df2d5 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -12,7 +12,7 @@ jobs: strategy: matrix: platform: [ubuntu-latest, windows-latest] - python-version: [3.6, 3.7, 3.8, 3.9, 3.10-dev] + python-version: [3.6, 3.7, 3.8, 3.9, 3.10-rc.2] env: PY_COLORS: 1 @@ -45,7 +45,7 @@ jobs: sudo apt install ffmpeg # For replaygain - name: Test older Python versions with tox - if: matrix.python-version != '3.9' && matrix.python-version != '3.10-dev' + if: matrix.python-version != '3.9' && matrix.python-version != '3.10-rc.2' run: | tox -e py-test @@ -55,7 +55,7 @@ jobs: tox -vv -e py-cov - name: Test nightly Python version with tox - if: matrix.python-version == '3.10-dev' + if: matrix.python-version == '3.10-rc.2' # continue-on-error is not ideal since it doesn't give a visible # warning, but there doesn't seem to be anything better: # https://github.com/actions/toolkit/issues/399 From ce77816ad9c2e9232e69df1c07dfbb8085cb2c63 Mon Sep 17 00:00:00 2001 From: Andrew Rogl Date: Sun, 26 Sep 2021 16:47:38 +1000 Subject: [PATCH 22/26] Commit #4069 --- docs/guides/main.rst | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/docs/guides/main.rst b/docs/guides/main.rst index 04bbc2ae9..5776dc1c3 100644 --- a/docs/guides/main.rst +++ b/docs/guides/main.rst @@ -13,8 +13,10 @@ You will need Python. Beets works on Python 3.6 or later. * **macOS** 11 (Big Sur) includes Python 3.8 out of the box. - You can opt for a more recent Python installing it via `Homebrew`_: - ``brew install python3`` + You can opt for a more recent Python installing it via `Homebrew`_ + (``brew install python3``). + There's also a `MacPorts`_ port. Run ``port install beets`` or + ``port install beets-full`` to include many third-party plugins. * On **Debian or Ubuntu**, depending on the version, beets is available as an official package (`Debian details`_, `Ubuntu details`_), so try typing: @@ -55,6 +57,7 @@ Beets works on Python 3.6 or later. .. _OpenBSD: http://openports.se/audio/beets .. _Arch community: https://www.archlinux.org/packages/community/any/beets/ .. _NixOS: https://github.com/NixOS/nixpkgs/tree/master/pkgs/tools/audio/beets +.. _MacPorts: https://www.macports.org If you have `pip`_, just say ``pip install beets`` (or ``pip install --user beets`` if you run into permissions problems). @@ -71,14 +74,14 @@ new versions. .. _@b33ts: https://twitter.com/b33ts -Installing on macOS 10.11 and Higher -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Installing by Hand on macOS 10.11 and Higher +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Starting with version 10.11 (El Capitan), macOS has a new security feature called `System Integrity Protection`_ (SIP) that prevents you from modifying -some parts of the system. This means that some ``pip`` commands may fail with -a permissions error. (You probably *won't* run into this if you've installed -Python yourself with `Homebrew`_ or otherwise.) +some parts of the system. This means that some ``pip`` commands may fail with a +permissions error. (You probably *won't* run into this if you've installed +Python yourself with `Homebrew`_ or otherwise. You can also try `MacPorts`_.) If this happens, you can install beets for the current user only by typing ``pip install --user beets``. If you do that, you might want to add From fc4d65a3877f1be49f923e5ac029de37ae2610fa Mon Sep 17 00:00:00 2001 From: Andrew Rogl Date: Sun, 26 Sep 2021 16:51:01 +1000 Subject: [PATCH 23/26] Missed a few unicode strings --- beets/ui/__init__.py | 2 +- beetsplug/lyrics.py | 6 +++--- extra/release.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index 1540a2ee0..8cf6cbbf6 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -875,7 +875,7 @@ class CommonOptionsParser(optparse.OptionParser): By default this affects both items and albums. If add_album_option() is used then the target will be autodetected. - Sets the format property to u'$path' on the options extracted from the + Sets the format property to '$path' on the options extracted from the CLI. """ path = optparse.Option(*flags, nargs=0, action='callback', diff --git a/beetsplug/lyrics.py b/beetsplug/lyrics.py index 8a9896bc3..f13db7ad0 100644 --- a/beetsplug/lyrics.py +++ b/beetsplug/lyrics.py @@ -94,9 +94,9 @@ Artist index: # The content for the base conf.py generated. REST_CONF_TEMPLATE = '''# -*- coding: utf-8 -*- master_doc = 'index' -project = u'Lyrics' -copyright = u'none' -author = u'Various Authors' +project = 'Lyrics' +copyright = 'none' +author = 'Various Authors' latex_documents = [ (master_doc, 'Lyrics.tex', project, author, 'manual'), diff --git a/extra/release.py b/extra/release.py index 0309f0bb0..2a98e06e8 100755 --- a/extra/release.py +++ b/extra/release.py @@ -35,7 +35,7 @@ VERSION_LOCS = [ [ ( r'__version__\s*=\s*u[\'"]([0-9\.]+)[\'"]', - "__version__ = u'{version}'", + "__version__ = '{version}'", ) ] ), From d9be9a0d9bb90b2abf449b0e69dbb79f3750993e Mon Sep 17 00:00:00 2001 From: Andrew Rogl Date: Sun, 26 Sep 2021 17:08:57 +1000 Subject: [PATCH 24/26] MIssed another new file --- test/test_albumtypes.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/test_albumtypes.py b/test/test_albumtypes.py index a9db12c30..91808553d 100644 --- a/test/test_albumtypes.py +++ b/test/test_albumtypes.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2021, Edgars Supe. # @@ -15,7 +14,6 @@ """Tests for the 'albumtypes' plugin.""" -from __future__ import division, absolute_import, print_function import unittest From ac6cc2ffa411a8ef76f83f05e4607e901bacc8fc Mon Sep 17 00:00:00 2001 From: Andrew Rogl Date: Sun, 26 Sep 2021 17:33:15 +1000 Subject: [PATCH 25/26] Fix up invalid master merges --- beets/autotag/mb.py | 2 - beets/library.py | 138 ++++++++++++++++++++-------------------- beetsplug/albumtypes.py | 3 - docs/changelog.rst | 7 -- test/test_albumtypes.py | 2 - 5 files changed, 69 insertions(+), 83 deletions(-) diff --git a/beets/autotag/mb.py b/beets/autotag/mb.py index 7765e970c..e6a2e277f 100644 --- a/beets/autotag/mb.py +++ b/beets/autotag/mb.py @@ -18,8 +18,6 @@ import musicbrainzngs import re import traceback -from collections import Counter -from six.moves.urllib.parse import urljoin from beets import logging from beets import plugins diff --git a/beets/library.py b/beets/library.py index 79e84e3e6..d94468800 100644 --- a/beets/library.py +++ b/beets/library.py @@ -455,47 +455,47 @@ class Item(LibModel): 'path': PathType(), 'album_id': types.FOREIGN_ID, - 'title': types.STRING, - 'artist': types.STRING, - 'artist_sort': types.STRING, - 'artist_credit': types.STRING, - 'album': types.STRING, - 'albumartist': types.STRING, - 'albumartist_sort': types.STRING, - 'albumartist_credit': types.STRING, - 'genre': types.STRING, - 'style': types.STRING, - 'discogs_albumid': types.INTEGER, - 'discogs_artistid': types.INTEGER, - 'discogs_labelid': types.INTEGER, - 'lyricist': types.STRING, - 'composer': types.STRING, - 'composer_sort': types.STRING, - 'work': types.STRING, - 'mb_workid': types.STRING, - 'work_disambig': types.STRING, - 'arranger': types.STRING, - 'grouping': types.STRING, - 'year': types.PaddedInt(4), - 'month': types.PaddedInt(2), - 'day': types.PaddedInt(2), - 'track': types.PaddedInt(2), - 'tracktotal': types.PaddedInt(2), - 'disc': types.PaddedInt(2), - 'disctotal': types.PaddedInt(2), - 'lyrics': types.STRING, - 'comments': types.STRING, - 'bpm': types.INTEGER, - 'comp': types.BOOLEAN, - 'mb_trackid': types.STRING, - 'mb_albumid': types.STRING, - 'mb_artistid': types.STRING, - 'mb_albumartistid': types.STRING, - 'mb_releasetrackid': types.STRING, - 'trackdisambig': types.STRING, - 'albumtype': types.STRING, - 'albumtypes': types.STRING, - 'label': types.STRING, + 'title': types.STRING, + 'artist': types.STRING, + 'artist_sort': types.STRING, + 'artist_credit': types.STRING, + 'album': types.STRING, + 'albumartist': types.STRING, + 'albumartist_sort': types.STRING, + 'albumartist_credit': types.STRING, + 'genre': types.STRING, + 'style': types.STRING, + 'discogs_albumid': types.INTEGER, + 'discogs_artistid': types.INTEGER, + 'discogs_labelid': types.INTEGER, + 'lyricist': types.STRING, + 'composer': types.STRING, + 'composer_sort': types.STRING, + 'work': types.STRING, + 'mb_workid': types.STRING, + 'work_disambig': types.STRING, + 'arranger': types.STRING, + 'grouping': types.STRING, + 'year': types.PaddedInt(4), + 'month': types.PaddedInt(2), + 'day': types.PaddedInt(2), + 'track': types.PaddedInt(2), + 'tracktotal': types.PaddedInt(2), + 'disc': types.PaddedInt(2), + 'disctotal': types.PaddedInt(2), + 'lyrics': types.STRING, + 'comments': types.STRING, + 'bpm': types.INTEGER, + 'comp': types.BOOLEAN, + 'mb_trackid': types.STRING, + 'mb_albumid': types.STRING, + 'mb_artistid': types.STRING, + 'mb_albumartistid': types.STRING, + 'mb_releasetrackid': types.STRING, + 'trackdisambig': types.STRING, + 'albumtype': types.STRING, + 'albumtypes': types.STRING, + 'label': types.STRING, 'acoustid_fingerprint': types.STRING, 'acoustid_id': types.STRING, 'mb_releasegroupid': types.STRING, @@ -1024,35 +1024,35 @@ class Album(LibModel): _fields = { 'id': types.PRIMARY_ID, 'artpath': PathType(True), - 'added': DateType(), + 'added': DateType(), - 'albumartist': types.STRING, - 'albumartist_sort': types.STRING, - 'albumartist_credit': types.STRING, - 'album': types.STRING, - 'genre': types.STRING, - 'style': types.STRING, - 'discogs_albumid': types.INTEGER, - 'discogs_artistid': types.INTEGER, - 'discogs_labelid': types.INTEGER, - 'year': types.PaddedInt(4), - 'month': types.PaddedInt(2), - 'day': types.PaddedInt(2), - 'disctotal': types.PaddedInt(2), - 'comp': types.BOOLEAN, - 'mb_albumid': types.STRING, - 'mb_albumartistid': types.STRING, - 'albumtype': types.STRING, - 'albumtypes': types.STRING, - 'label': types.STRING, - 'mb_releasegroupid': types.STRING, - 'asin': types.STRING, - 'catalognum': types.STRING, - 'script': types.STRING, - 'language': types.STRING, - 'country': types.STRING, - 'albumstatus': types.STRING, - 'albumdisambig': types.STRING, + 'albumartist': types.STRING, + 'albumartist_sort': types.STRING, + 'albumartist_credit': types.STRING, + 'album': types.STRING, + 'genre': types.STRING, + 'style': types.STRING, + 'discogs_albumid': types.INTEGER, + 'discogs_artistid': types.INTEGER, + 'discogs_labelid': types.INTEGER, + 'year': types.PaddedInt(4), + 'month': types.PaddedInt(2), + 'day': types.PaddedInt(2), + 'disctotal': types.PaddedInt(2), + 'comp': types.BOOLEAN, + 'mb_albumid': types.STRING, + 'mb_albumartistid': types.STRING, + 'albumtype': types.STRING, + 'albumtypes': types.STRING, + 'label': types.STRING, + 'mb_releasegroupid': types.STRING, + 'asin': types.STRING, + 'catalognum': types.STRING, + 'script': types.STRING, + 'language': types.STRING, + 'country': types.STRING, + 'albumstatus': types.STRING, + 'albumdisambig': types.STRING, 'releasegroupdisambig': types.STRING, 'rg_album_gain': types.NULL_FLOAT, 'rg_album_peak': types.NULL_FLOAT, diff --git a/beetsplug/albumtypes.py b/beetsplug/albumtypes.py index 4d7948c7b..47f8dc64e 100644 --- a/beetsplug/albumtypes.py +++ b/beetsplug/albumtypes.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - # This file is part of beets. # Copyright 2021, Edgars Supe. # @@ -16,7 +14,6 @@ """Adds an album template field for formatted album types.""" -from __future__ import division, absolute_import, print_function from beets.autotag.mb import VARIOUS_ARTISTS_ID from beets.library import Album diff --git a/docs/changelog.rst b/docs/changelog.rst index ecf43b803..22a3e7120 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -7,13 +7,6 @@ Changelog This release now requires Python 3.6 or later (it removes support for Python 2.7, 3.4, and 3.5). -* Primary and secondary release types from MusicBrainz are now stored in - ``albumtypes`` field. Thanks to :user:`edgars-supe`. - :bug:`2200` - -* :doc:`/plugins/albumtypes`: An accompanying plugin for formatting - ``albumtypes``. Thanks to :user:`edgars-supe`. - For packagers: * We fixed a flaky test, named `test_album_art` in the `test_zero.py` file, diff --git a/test/test_albumtypes.py b/test/test_albumtypes.py index a9db12c30..91808553d 100644 --- a/test/test_albumtypes.py +++ b/test/test_albumtypes.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # This file is part of beets. # Copyright 2021, Edgars Supe. # @@ -15,7 +14,6 @@ """Tests for the 'albumtypes' plugin.""" -from __future__ import division, absolute_import, print_function import unittest From 237bd07508619c908592f433da1b77ac02440082 Mon Sep 17 00:00:00 2001 From: Andrew Rogl Date: Tue, 28 Sep 2021 18:05:44 +1000 Subject: [PATCH 26/26] Address feedback from @sampsyo --- beetsplug/mpdstats.py | 1 - test/rsrc/convert_stub.py | 7 ------- 2 files changed, 8 deletions(-) diff --git a/beetsplug/mpdstats.py b/beetsplug/mpdstats.py index dd91228e5..96291cf4c 100644 --- a/beetsplug/mpdstats.py +++ b/beetsplug/mpdstats.py @@ -55,7 +55,6 @@ class MPDClientWrapper: self._log.debug('music_directory: {0}', self.music_directory) self._log.debug('strip_path: {0}', self.strip_path) - # On Python 3, python-mpd2 always uses Unicode self.client = mpd.MPDClient() def connect(self): diff --git a/test/rsrc/convert_stub.py b/test/rsrc/convert_stub.py index f58fa1d96..409c3e336 100755 --- a/test/rsrc/convert_stub.py +++ b/test/rsrc/convert_stub.py @@ -19,16 +19,9 @@ def arg_encoding(): def convert(in_file, out_file, tag): """Copy `in_file` to `out_file` and append the string `tag`. """ - # On Python 3, encode the tag argument as bytes. if not isinstance(tag, bytes): tag = tag.encode('utf-8') - # On Windows, use Unicode paths. On Python 3, we get the actual, - # Unicode filenames. On Python 2, we get them as UTF-8 byes. - # if platform.system() == 'Windows' and PY2: - # in_file = in_file.decode('utf-8') - # out_file = out_file.decode('utf-8') - with open(out_file, 'wb') as out_f: with open(in_file, 'rb') as in_f: out_f.write(in_f.read())