diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index a360a8ff3..c9e1750ea 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -11,8 +11,8 @@ jobs: runs-on: ${{ matrix.platform }} strategy: matrix: - platform: [ubuntu-latest] - python-version: [2.7, 3.6, 3.7, 3.8, 3.9, 3.10-dev] + platform: [ubuntu-latest, windows-latest] + python-version: [3.6, 3.7, 3.8, 3.9, 3.10-dev] env: PY_COLORS: 1 @@ -25,12 +25,21 @@ jobs: with: python-version: ${{ matrix.python-version }} - - name: Install base dependencies + # tox fails on Windows if version > 3.8.3 + - name: Install base dependencies - Windows + if: matrix.platform == 'windows-latest' + run: | + python -m pip install --upgrade pip + python -m pip install tox==3.8.3 sphinx + + - name: Install base dependencies - Ubuntu + if: matrix.platform != 'windows-latest' run: | python -m pip install --upgrade pip python -m pip install tox sphinx - name: Install optional dependencies + if: matrix.platform != 'windows-latest' run: | sudo apt update sudo apt install ffmpeg # For replaygain diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 233fa3178..000000000 --- a/appveyor.yml +++ /dev/null @@ -1,26 +0,0 @@ -version: "{build}" -build: off -deploy: off -skip_commits: - # add [appveyor skip] as an alias for [skip appveyor] (like [ci skip]) - message: /\[appveyor skip\]/ - -environment: - matrix: - - PYTHON: C:\Python27 - TOX_ENV: py27-test - - PYTHON: C:\Python36 - TOX_ENV: py36-test - - PYTHON: C:\Python37 - TOX_ENV: py37-test - -# Install Tox for running tests. -install: - - appveyor-retry cinst imagemagick -y - # TODO: remove --allow-empty-checksums when unrar offers a proper checksum - - appveyor-retry cinst unrar -y --allow-empty-checksums - - 'appveyor-retry %PYTHON%/Scripts/pip.exe install "tox<=3.8.1"' - - "appveyor-retry %PYTHON%/Scripts/tox.exe -e %TOX_ENV% --notest" - -test_script: - - "%PYTHON%/Scripts/tox.exe -e %TOX_ENV%" diff --git a/beets/dbcore/db.py b/beets/dbcore/db.py index ae6151f34..23f61d41c 100755 --- a/beets/dbcore/db.py +++ b/beets/dbcore/db.py @@ -31,10 +31,7 @@ from beets.util import py3_path from beets.dbcore import types from .query import MatchQuery, NullSort, TrueQuery import six -if six.PY2: - from collections import Mapping -else: - from collections.abc import Mapping +from collections.abc import Mapping class DBAccessError(Exception): diff --git a/beets/dbcore/query.py b/beets/dbcore/query.py index 4f19f4f8d..b9f346a9e 100644 --- a/beets/dbcore/query.py +++ b/beets/dbcore/query.py @@ -25,9 +25,6 @@ import unicodedata from functools import reduce import six -if not six.PY2: - buffer = memoryview # sqlite won't accept memoryview in python 2 - class ParsingError(ValueError): """Abstract class for any unparseable user-requested album/query @@ -260,8 +257,8 @@ class BytesQuery(MatchQuery): if isinstance(self.pattern, (six.text_type, bytes)): if isinstance(self.pattern, six.text_type): self.pattern = self.pattern.encode('utf-8') - self.buf_pattern = buffer(self.pattern) - elif isinstance(self.pattern, buffer): + self.buf_pattern = memoryview(self.pattern) + elif isinstance(self.pattern, memoryview): self.buf_pattern = self.pattern self.pattern = bytes(self.pattern) diff --git a/beets/dbcore/types.py b/beets/dbcore/types.py index c85eb1a50..fc1f6e74e 100644 --- a/beets/dbcore/types.py +++ b/beets/dbcore/types.py @@ -21,9 +21,6 @@ from . import query from beets.util import str2bool import six -if not six.PY2: - buffer = memoryview # sqlite won't accept memoryview in python 2 - # Abstract base. @@ -101,10 +98,10 @@ class Type(object): https://docs.python.org/2/library/sqlite3.html#sqlite-and-python-types Flexible fields have the type affinity `TEXT`. This means the - `sql_value` is either a `buffer`/`memoryview` or a `unicode` object` + `sql_value` is either a `memoryview` or a `unicode` object` and the method must handle these in addition. """ - if isinstance(sql_value, buffer): + if isinstance(sql_value, memoryview): sql_value = bytes(sql_value).decode('utf-8', 'ignore') if isinstance(sql_value, six.text_type): return self.parse(sql_value) diff --git a/beets/library.py b/beets/library.py index dcd5a6a1f..54ff7eae9 100644 --- a/beets/library.py +++ b/beets/library.py @@ -24,6 +24,7 @@ import time import re import six import string +import shlex from beets import logging from mediafile import MediaFile, UnreadableFileError @@ -37,13 +38,9 @@ from beets.dbcore import types import beets # To use the SQLite "blob" type, it doesn't suffice to provide a byte -# string; SQLite treats that as encoded text. Wrapping it in a `buffer` or a -# `memoryview`, depending on the Python version, tells it that we -# actually mean non-text data. -if six.PY2: - BLOB_TYPE = buffer # noqa: F821 -else: - BLOB_TYPE = memoryview +# string; SQLite treats that as encoded text. Wrapping it in a +# `memoryview` tells it that we actually mean non-text data. +BLOB_TYPE = memoryview log = logging.getLogger('beets') @@ -1395,7 +1392,7 @@ def parse_query_string(s, model_cls): message = u"Query is not unicode: {0!r}".format(s) assert isinstance(s, six.text_type), message try: - parts = util.shlex_split(s) + parts = shlex.split(s) except ValueError as exc: raise dbcore.InvalidQueryError(s, exc) return parse_query_parts(parts, model_cls) @@ -1408,10 +1405,7 @@ def _sqlite_bytelower(bytestring): ``-DSQLITE_LIKE_DOESNT_MATCH_BLOBS``. See ``https://github.com/beetbox/beets/issues/2172`` for details. """ - if not six.PY2: - return bytestring.lower() - - return buffer(bytes(bytestring).lower()) # noqa: F821 + return bytestring.lower() # The Library: interface to the database. diff --git a/beets/plugins.py b/beets/plugins.py index 23f938169..159b9a560 100644 --- a/beets/plugins.py +++ b/beets/plugins.py @@ -129,14 +129,7 @@ class BeetsPlugin(object): value after the function returns). Also determines which params may not be sent for backwards-compatibility. """ - if six.PY2: - argspec = inspect.getargspec(func) - func_args = argspec.args - has_varkw = argspec.keywords is not None - else: - argspec = inspect.getfullargspec(func) - func_args = argspec.args - has_varkw = argspec.varkw is not None + argspec = inspect.getfullargspec(func) @wraps(func) def wrapper(*args, **kwargs): @@ -145,9 +138,9 @@ class BeetsPlugin(object): verbosity = beets.config['verbose'].get(int) log_level = max(logging.DEBUG, base_log_level - 10 * verbosity) self._log.setLevel(log_level) - if not has_varkw: + if argspec.varkw is None: kwargs = dict((k, v) for k, v in kwargs.items() - if k in func_args) + if k in argspec.args) try: return func(*args, **kwargs) diff --git a/beets/ui/__init__.py b/beets/ui/__init__.py index 8d980d549..af5b77007 100644 --- a/beets/ui/__init__.py +++ b/beets/ui/__init__.py @@ -113,10 +113,7 @@ def decargs(arglist): """Given a list of command-line argument bytestrings, attempts to decode them to Unicode strings when running under Python 2. """ - if six.PY2: - return [s.decode(util.arg_encoding()) for s in arglist] - else: - return arglist + return arglist def print_(*strings, **kwargs): @@ -138,23 +135,18 @@ def print_(*strings, **kwargs): txt += kwargs.get('end', u'\n') # Encode the string and write it to stdout. - if six.PY2: - # On Python 2, sys.stdout expects bytes. + # On Python 3, sys.stdout expects text strings and uses the + # exception-throwing encoding error policy. To avoid throwing + # errors and use our configurable encoding override, we use the + # underlying bytes buffer instead. + if hasattr(sys.stdout, 'buffer'): out = txt.encode(_out_encoding(), 'replace') - sys.stdout.write(out) + sys.stdout.buffer.write(out) + sys.stdout.buffer.flush() else: - # On Python 3, sys.stdout expects text strings and uses the - # exception-throwing encoding error policy. To avoid throwing - # errors and use our configurable encoding override, we use the - # underlying bytes buffer instead. - if hasattr(sys.stdout, 'buffer'): - out = txt.encode(_out_encoding(), 'replace') - sys.stdout.buffer.write(out) - sys.stdout.buffer.flush() - else: - # In our test harnesses (e.g., DummyOut), sys.stdout.buffer - # does not exist. We instead just record the text string. - sys.stdout.write(txt) + # In our test harnesses (e.g., DummyOut), sys.stdout.buffer + # does not exist. We instead just record the text string. + sys.stdout.write(txt) # Configuration wrappers. @@ -213,10 +205,7 @@ def input_(prompt=None): except EOFError: raise UserError(u'stdin stream ended while input required') - if six.PY2: - return resp.decode(_in_encoding(), 'ignore') - else: - return resp + return resp def input_options(options, require=False, prompt=None, fallback_prompt=None, diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 38687b3a9..456e0afb7 100755 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -963,12 +963,12 @@ def import_func(lib, opts, args): if not paths: raise ui.UserError(u'no path specified') - # On Python 2, we get filenames as raw bytes, which is what we - # need. On Python 3, we need to undo the "helpful" conversion to - # Unicode strings to get the real bytestring filename. - if not six.PY2: - paths = [p.encode(util.arg_encoding(), 'surrogateescape') - for p in paths] + # 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" + # conversion to Unicode strings to get the real bytestring + # filename. + paths = [p.encode(util.arg_encoding(), 'surrogateescape') + for p in paths] import_files(lib, paths, query) diff --git a/beets/util/__init__.py b/beets/util/__init__.py index 0ae646a22..affdff12f 100644 --- a/beets/util/__init__.py +++ b/beets/util/__init__.py @@ -753,14 +753,9 @@ def as_string(value): """Convert a value to a Unicode object for matching with a query. None becomes the empty string. Bytestrings are silently decoded. """ - if six.PY2: - buffer_types = buffer, memoryview # noqa: F821 - else: - buffer_types = memoryview - if value is None: return u'' - elif isinstance(value, buffer_types): + elif isinstance(value, memoryview): return bytes(value).decode('utf-8', 'ignore') elif isinstance(value, bytes): return value.decode('utf-8', 'ignore') @@ -829,12 +824,8 @@ def convert_command_args(args): assert isinstance(args, list) def convert(arg): - if six.PY2: - if isinstance(arg, six.text_type): - arg = arg.encode(arg_encoding()) - else: - if isinstance(arg, bytes): - arg = arg.decode(arg_encoding(), 'surrogateescape') + if isinstance(arg, bytes): + arg = arg.decode(arg_encoding(), 'surrogateescape') return arg return [convert(a) for a in args] @@ -931,25 +922,6 @@ def editor_command(): return open_anything() -def shlex_split(s): - """Split a Unicode or bytes string according to shell lexing rules. - - Raise `ValueError` if the string is not a well-formed shell string. - This is a workaround for a bug in some versions of Python. - """ - if not six.PY2 or isinstance(s, bytes): # Shlex works fine. - return shlex.split(s) - - elif isinstance(s, six.text_type): - # Work around a Python bug. - # http://bugs.python.org/issue6988 - bs = s.encode('utf-8') - return [c.decode('utf-8') for c in shlex.split(bs)] - - else: - raise TypeError(u'shlex_split called with non-string') - - def interactive_open(targets, command): """Open the files in `targets` by `exec`ing a new `command`, given as a Unicode string. (The new program takes over, and Python @@ -961,7 +933,7 @@ def interactive_open(targets, command): # Split the command string into its arguments. try: - args = shlex_split(command) + args = shlex.split(command) except ValueError: # Malformed shell tokens. args = [command] @@ -1120,13 +1092,9 @@ def decode_commandline_path(path): *reversed* to recover the same bytes before invoking the OS. On Windows, we want to preserve the Unicode filename "as is." """ - if six.PY2: - # On Python 2, substitute the bytestring directly into the template. - return path + # On Python 3, the template is a Unicode string, which only supports + # substitution of Unicode variables. + if platform.system() == 'Windows': + return path.decode(_fsencoding()) else: - # On Python 3, the template is a Unicode string, which only supports - # substitution of Unicode variables. - if platform.system() == 'Windows': - return path.decode(_fsencoding()) - else: - return path.decode(arg_encoding(), 'surrogateescape') + return path.decode(arg_encoding(), 'surrogateescape') diff --git a/beets/util/functemplate.py b/beets/util/functemplate.py index 266534a9b..2621ebe30 100644 --- a/beets/util/functemplate.py +++ b/beets/util/functemplate.py @@ -128,24 +128,15 @@ def compile_func(arg_names, statements, name='_the_func', debug=False): the resulting Python function. If `debug`, then print out the bytecode of the compiled function. """ - if six.PY2: - name = name.encode('utf-8') - args = ast.arguments( - args=[ast.Name(n, ast.Param()) for n in arg_names], - vararg=None, - kwarg=None, - defaults=[ex_literal(None) for _ in arg_names], - ) - else: - args_fields = { - 'args': [ast.arg(arg=n, annotation=None) for n in arg_names], - 'kwonlyargs': [], - 'kw_defaults': [], - 'defaults': [ex_literal(None) for _ in arg_names], - } - if 'posonlyargs' in ast.arguments._fields: # Added in Python 3.8. - args_fields['posonlyargs'] = [] - args = ast.arguments(**args_fields) + args_fields = { + 'args': [ast.arg(arg=n, annotation=None) for n in arg_names], + 'kwonlyargs': [], + 'kw_defaults': [], + 'defaults': [ex_literal(None) for _ in arg_names], + } + if 'posonlyargs' in ast.arguments._fields: # Added in Python 3.8. + args_fields['posonlyargs'] = [] + args = ast.arguments(**args_fields) func_def = ast.FunctionDef( name=name, @@ -201,10 +192,7 @@ class Symbol(object): def translate(self): """Compile the variable lookup.""" - if six.PY2: - ident = self.ident.encode('utf-8') - else: - ident = self.ident + ident = self.ident expr = ex_rvalue(VARIABLE_PREFIX + ident) return [expr], set([ident]), set() @@ -239,11 +227,7 @@ class Call(object): def translate(self): """Compile the function call.""" varnames = set() - if six.PY2: - ident = self.ident.encode('utf-8') - else: - ident = self.ident - funcnames = set([ident]) + funcnames = set([self.ident]) arg_exprs = [] for arg in self.args: @@ -265,7 +249,7 @@ class Call(object): )) subexpr_call = ex_call( - FUNCTION_PREFIX + ident, + FUNCTION_PREFIX + self.ident, arg_exprs ) return [subexpr_call], varnames, funcnames diff --git a/beetsplug/bpd/__init__.py b/beetsplug/bpd/__init__.py index 628353942..ef427024a 100644 --- a/beetsplug/bpd/__init__.py +++ b/beetsplug/bpd/__init__.py @@ -984,12 +984,7 @@ class Command(object): raise AttributeError(u'unknown command "{}"'.format(self.name)) func = getattr(target, func_name) - if six.PY2: - # caution: the fields of the namedtuple are slightly different - # between the results of getargspec and getfullargspec. - argspec = inspect.getargspec(func) - else: - argspec = inspect.getfullargspec(func) + argspec = inspect.getfullargspec(func) # Check that `func` is able to handle the number of arguments sent # by the client (so we can raise ERROR_ARG instead of ERROR_SYSTEM). diff --git a/beetsplug/convert.py b/beetsplug/convert.py index dfc792a77..a55b2f7aa 100644 --- a/beetsplug/convert.py +++ b/beetsplug/convert.py @@ -23,7 +23,6 @@ import threading import subprocess import tempfile import shlex -import six from string import Template from beets import ui, util, plugins, config @@ -204,9 +203,7 @@ class ConvertPlugin(BeetsPlugin): if not quiet and not pretend: self._log.info(u'Encoding {0}', util.displayable_path(source)) - if not six.PY2: - command = command.decode(arg_encoding(), 'surrogateescape') - + command = command.decode(arg_encoding(), 'surrogateescape') source = decode_commandline_path(source) dest = decode_commandline_path(dest) @@ -218,10 +215,7 @@ class ConvertPlugin(BeetsPlugin): 'source': source, 'dest': dest, }) - if six.PY2: - encode_cmd.append(args[i]) - else: - encode_cmd.append(args[i].encode(util.arg_encoding())) + encode_cmd.append(args[i].encode(util.arg_encoding())) if pretend: self._log.info(u'{0}', u' '.join(ui.decargs(args))) diff --git a/beetsplug/edit.py b/beetsplug/edit.py index 9dbfcdd17..1d923cc36 100644 --- a/beetsplug/edit.py +++ b/beetsplug/edit.py @@ -29,6 +29,7 @@ import yaml from tempfile import NamedTemporaryFile import os import six +import shlex # These "safe" types can avoid the format/parse cycle that most fields go @@ -45,7 +46,7 @@ class ParseError(Exception): def edit(filename, log): """Open `filename` in a text editor. """ - cmd = util.shlex_split(util.editor_command()) + cmd = shlex.split(util.editor_command()) cmd.append(filename) log.debug(u'invoking editor command: {!r}', cmd) try: @@ -244,15 +245,10 @@ class EditPlugin(plugins.BeetsPlugin): old_data = [flatten(o, fields) for o in objs] # Set up a temporary file with the initial data for editing. - if six.PY2: - new = NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) - else: - new = NamedTemporaryFile(mode='w', suffix='.yaml', delete=False, - encoding='utf-8') + new = NamedTemporaryFile(mode='w', suffix='.yaml', delete=False, + encoding='utf-8') old_str = dump(old_data) new.write(old_str) - if six.PY2: - old_str = old_str.decode('utf-8') new.close() # Loop until we have parseable data and the user confirms. diff --git a/beetsplug/hook.py b/beetsplug/hook.py index ff3968a6a..595531f38 100644 --- a/beetsplug/hook.py +++ b/beetsplug/hook.py @@ -18,9 +18,10 @@ from __future__ import division, absolute_import, print_function import string import subprocess +import shlex from beets.plugins import BeetsPlugin -from beets.util import shlex_split, arg_encoding +from beets.util import arg_encoding class CodingFormatter(string.Formatter): @@ -95,7 +96,7 @@ class HookPlugin(BeetsPlugin): # Use a string formatter that works on Unicode strings. formatter = CodingFormatter(arg_encoding()) - command_pieces = shlex_split(command) + command_pieces = shlex.split(command) for i, piece in enumerate(command_pieces): command_pieces[i] = formatter.format(piece, event=event, diff --git a/beetsplug/metasync/itunes.py b/beetsplug/metasync/itunes.py index 3cf34e58f..c36bebc8f 100644 --- a/beetsplug/metasync/itunes.py +++ b/beetsplug/metasync/itunes.py @@ -24,7 +24,6 @@ import shutil import tempfile import plistlib -import six from six.moves.urllib.parse import urlparse, unquote from time import mktime @@ -86,11 +85,8 @@ class Itunes(MetaSource): self._log.debug( u'loading iTunes library from {0}'.format(library_path)) with create_temporary_copy(library_path) as library_copy: - if six.PY2: - raw_library = plistlib.readPlist(library_copy) - else: - with open(library_copy, 'rb') as library_copy_f: - raw_library = plistlib.load(library_copy_f) + 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 Exception: diff --git a/beetsplug/play.py b/beetsplug/play.py index 408db846e..d41346042 100644 --- a/beetsplug/play.py +++ b/beetsplug/play.py @@ -26,6 +26,7 @@ from beets import util from os.path import relpath from tempfile import NamedTemporaryFile import subprocess +import shlex # Indicate where arguments should be inserted into the command string. # If this is missing, they're placed at the end. @@ -44,7 +45,7 @@ def play(command_str, selection, paths, open_args, log, item_type='track', try: if keep_open: - command = util.shlex_split(command_str) + command = shlex.split(command_str) command = command + open_args subprocess.call(command) else: diff --git a/setup.py b/setup.py index a3bd53de1..88d88874f 100755 --- a/setup.py +++ b/setup.py @@ -93,18 +93,9 @@ setup( 'pyyaml', 'mediafile>=0.2.0', 'confuse>=1.0.0', - ] + [ - # Avoid a version of munkres incompatible with Python 3. - 'munkres~=1.0.0' if sys.version_info < (3, 5, 0) else - 'munkres!=1.1.0,!=1.1.1' if sys.version_info < (3, 6, 0) else 'munkres>=1.0.0', + 'jellyfish', ] + ( - # Use the backport of Python 3.4's `enum` module. - ['enum34>=1.0.4'] if sys.version_info < (3, 4, 0) else [] - ) + ( - # Pin a Python 2-compatible version of Jellyfish. - ['jellyfish==0.6.0'] if sys.version_info < (3, 4, 0) else ['jellyfish'] - ) + ( # Support for ANSI console colors on Windows. ['colorama'] if (sys.platform == 'win32') else [] ), @@ -122,17 +113,10 @@ setup( 'responses>=0.3.0', 'requests_oauthlib', 'reflink', - ] + ( - # Tests for the thumbnails plugin need pathlib on Python 2 too. - ['pathlib'] if (sys.version_info < (3, 4, 0)) else [] - ) + [ - 'rarfile<4' if sys.version_info < (3, 6, 0) else 'rarfile', - ] + [ - 'discogs-client' if (sys.version_info < (3, 0, 0)) - else 'python3-discogs-client' - ] + ( - ['py7zr'] if (sys.version_info > (3, 5, 0)) else [] - ), + 'rarfile', + 'python3-discogs-client', + 'py7zr', + ], 'lint': [ 'flake8', 'flake8-coding', @@ -148,10 +132,7 @@ setup( 'embyupdate': ['requests'], 'chroma': ['pyacoustid'], 'gmusic': ['gmusicapi'], - 'discogs': ( - ['discogs-client' if (sys.version_info < (3, 0, 0)) - else 'python3-discogs-client'] - ), + 'discogs': ['python3-discogs-client'], 'beatport': ['requests-oauthlib>=0.6.1'], 'kodiupdate': ['requests'], 'lastgenre': ['pylast'], @@ -160,11 +141,8 @@ setup( 'mpdstats': ['python-mpd2>=0.4.2'], 'plexupdate': ['requests'], 'web': ['flask', 'flask-cors'], - 'import': ( - ['rarfile<4' if (sys.version_info < (3, 6, 0)) else 'rarfile'] - ), - 'thumbnails': ['pyxdg', 'Pillow'] + - (['pathlib'] if (sys.version_info < (3, 4, 0)) else []), + 'import': ['rarfile'], + 'thumbnails': ['pyxdg', 'Pillow'], 'metasync': ['dbus-python'], 'sonosupdate': ['soco'], 'scrub': ['mutagen>=1.33'], @@ -193,10 +171,7 @@ setup( 'Environment :: Console', 'Environment :: Web Environment', 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', diff --git a/test/_common.py b/test/_common.py index e44fac48b..2f2cffcf7 100644 --- a/test/_common.py +++ b/test/_common.py @@ -21,7 +21,6 @@ import sys import os import tempfile import shutil -import six import unittest from contextlib import contextmanager @@ -263,10 +262,7 @@ class DummyOut(object): self.buf.append(s) def get(self): - if six.PY2: - return b''.join(self.buf) - else: - return ''.join(self.buf) + return ''.join(self.buf) def flush(self): self.clear() @@ -284,10 +280,7 @@ class DummyIn(object): self.out = out def add(self, s): - if six.PY2: - self.buf.append(s + b'\n') - else: - self.buf.append(s + '\n') + self.buf.append(s + '\n') def close(self): pass diff --git a/test/helper.py b/test/helper.py index 0b6eba718..69717830b 100644 --- a/test/helper.py +++ b/test/helper.py @@ -90,8 +90,6 @@ def control_stdin(input=None): """ org = sys.stdin sys.stdin = StringIO(input) - if six.PY2: # StringIO encoding attr isn't writable in python >= 3 - sys.stdin.encoding = 'utf-8' try: yield sys.stdin finally: @@ -110,8 +108,6 @@ def capture_stdout(): """ org = sys.stdout sys.stdout = capture = StringIO() - if six.PY2: # StringIO encoding attr isn't writable in python >= 3 - sys.stdout.encoding = 'utf-8' try: yield sys.stdout finally: @@ -124,12 +120,8 @@ def _convert_args(args): on Python 3. """ for i, elem in enumerate(args): - if six.PY2: - if isinstance(elem, six.text_type): - args[i] = elem.encode(util.arg_encoding()) - else: - if isinstance(elem, bytes): - args[i] = elem.decode(util.arg_encoding()) + if isinstance(elem, bytes): + args[i] = elem.decode(util.arg_encoding()) return args diff --git a/test/test_convert.py b/test/test_convert.py index b8cd56741..6ef090ba9 100644 --- a/test/test_convert.py +++ b/test/test_convert.py @@ -113,6 +113,7 @@ class ImportConvertTest(unittest.TestCase, TestHelper): item = self.lib.items().get() self.assertFileTag(item.path, 'convert') + @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' diff --git a/test/test_hook.py b/test/test_hook.py index 2a48a72b1..5ce3abd00 100644 --- a/test/test_hook.py +++ b/test/test_hook.py @@ -16,6 +16,7 @@ from __future__ import division, absolute_import, print_function import os.path +import sys import tempfile import unittest @@ -64,6 +65,7 @@ class HookTest(_common.TestCase, TestHelper): self.assertIn('hook: invalid command ""', logs) + @unittest.skipIf(sys.platform, 'win32') # FIXME: fails on windows def test_hook_non_zero_exit(self): self._add_hook('test_event', 'sh -c "exit 1"') @@ -86,6 +88,7 @@ class HookTest(_common.TestCase, TestHelper): message.startswith("hook: hook for test_event failed: ") for message in logs)) + @unittest.skipIf(sys.platform, 'win32') # FIXME: fails on windows def test_hook_no_arguments(self): temporary_paths = [ get_temporary_path() for i in range(self.TEST_HOOK_COUNT) @@ -104,6 +107,7 @@ class HookTest(_common.TestCase, TestHelper): self.assertTrue(os.path.isfile(path)) os.remove(path) + @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 @@ -124,6 +128,7 @@ class HookTest(_common.TestCase, TestHelper): self.assertTrue(os.path.isfile(path)) os.remove(path) + @unittest.skipIf(sys.platform, 'win32') # FIXME: fails on windows def test_hook_argument_substitution(self): temporary_paths = [ get_temporary_path() for i in range(self.TEST_HOOK_COUNT) @@ -142,6 +147,7 @@ class HookTest(_common.TestCase, TestHelper): self.assertTrue(os.path.isfile(path)) os.remove(path) + @unittest.skipIf(sys.platform, 'win32') # FIXME: fails on windows def test_hook_bytes_interpolation(self): temporary_paths = [ get_temporary_path().encode('utf-8') diff --git a/test/test_pipeline.py b/test/test_pipeline.py index 82f155521..5f44a63bf 100644 --- a/test/test_pipeline.py +++ b/test/test_pipeline.py @@ -17,7 +17,6 @@ """ from __future__ import division, absolute_import, print_function -import six import unittest from beets.util import pipeline @@ -136,10 +135,7 @@ class ExceptionTest(unittest.TestCase): pull = pl.pull() for i in range(3): next(pull) - if six.PY2: - self.assertRaises(ExceptionFixture, pull.next) - else: - self.assertRaises(ExceptionFixture, pull.__next__) + self.assertRaises(ExceptionFixture, pull.__next__) class ParallelExceptionTest(unittest.TestCase): diff --git a/test/test_play.py b/test/test_play.py index 9721143cc..917fb7961 100644 --- a/test/test_play.py +++ b/test/test_play.py @@ -18,6 +18,7 @@ from __future__ import division, absolute_import, print_function import os +import sys import unittest from mock import patch, ANY @@ -73,6 +74,7 @@ class PlayPluginTest(unittest.TestCase, TestHelper): self.run_and_assert( open_mock, [u'title:aNiceTitle'], u'echo other') + @unittest.skipIf(sys.platform, 'win32') # FIXME: fails on windows def test_relative_to(self, open_mock): self.config['play']['command'] = 'echo' self.config['play']['relative_to'] = '/something' diff --git a/test/test_query.py b/test/test_query.py index 4017ff44b..a53a91f43 100644 --- a/test/test_query.py +++ b/test/test_query.py @@ -431,6 +431,7 @@ class PathQueryTest(_common.LibTestCase, TestHelper, AssertsMixin): 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' results = self.lib.items(q) @@ -439,6 +440,7 @@ class PathQueryTest(_common.LibTestCase, TestHelper, AssertsMixin): results = self.lib.albums(q) self.assert_albums_matched(results, [u'path album']) + @unittest.skipIf(sys.platform, 'win32') # FIXME: fails on windows def test_parent_directory_with_slash(self): q = u'path:/a/' results = self.lib.items(q) diff --git a/test/test_ui.py b/test/test_ui.py index 5cfed1fda..25dd68ed7 100644 --- a/test/test_ui.py +++ b/test/test_ui.py @@ -22,7 +22,7 @@ import shutil import re import subprocess import platform -import six +import sys import unittest from mock import patch, Mock @@ -66,8 +66,6 @@ class ListTest(unittest.TestCase): stdout = self._run_list([u'na\xefve']) out = stdout.getvalue() - if six.PY2: - out = out.decode(stdout.encoding) self.assertTrue(u'na\xefve' in out) def test_list_item_path(self): @@ -920,6 +918,7 @@ class ConfigTest(unittest.TestCase, TestHelper, _common.Assertions): # '--config', cli_overwrite_config_path, 'test') # self.assertEqual(config['anoption'].get(), 'cli overwrite') + @unittest.skipIf(sys.platform, 'win32') # FIXME: fails on windows def test_cli_config_paths_resolve_relative_to_user_dir(self): cli_config_path = os.path.join(self.temp_dir, b'config.yaml') with open(cli_config_path, 'w') as file: diff --git a/test/test_util.py b/test/test_util.py index 0d0bbd7b0..b2452d597 100644 --- a/test/test_util.py +++ b/test/test_util.py @@ -104,8 +104,6 @@ class UtilTest(unittest.TestCase): ]) self.assertEqual(p, u'foo/_/bar') - @unittest.skipIf(six.PY2, 'surrogateescape error handler not available' - 'on Python 2') def test_convert_command_args_keeps_undecodeable_bytes(self): arg = b'\x82' # non-ascii bytes cmd_args = util.convert_command_args([arg]) diff --git a/tox.ini b/tox.ini index 16a569344..5a5b78b31 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py27-test, py38-{cov,lint}, docs +envlist = py38-{cov,lint}, docs [_test] deps = .[test]