diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 000000000..cbd6381e1 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,77 @@ +name: ci +on: push +jobs: + test-27: + runs-on: ${{ matrix.platform }} + strategy: + matrix: + platform: [ ubuntu-latest ] + env: + NOSE_SHOW_SKIPPED: 1 + steps: + - uses: actions/checkout@v2 + - name: Set up python 2.7 + uses: actions/setup-python@v2 + with: + python-version: 2.7 + - uses: actions/cache@v1 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + - name: Install base dependencies + run: | + python -m pip install --upgrade pip + pip install tox sphinx + - name: Test with tox + run: | + tox -e py27-test + tox -e docs + test-3x: + runs-on: ${{ matrix.platform }} + strategy: + matrix: + platform: [ ubuntu-latest ] + python-version: [ 5, 6, 7, 8 ] + env: + NOSE_SHOW_SKIPPED: 1 + steps: + - uses: actions/checkout@v2 + - name: Set up python 3.${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: 3.${{ matrix.python-version }} + - uses: actions/cache@v1 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + - name: Install base dependencies + run: | + python -m pip install --upgrade pip + pip install tox sphinx + - name: Test with tox + run: | + if [[ ${{ matrix.python-version }} == '8' ]]; then + tox -e py3${{ matrix.python-version }}-test + tox -e py3${{ matrix.python-version }}-cov + tox -e py3${{ matrix.python-version }}-flake8 + else + tox -e py3${{ matrix.python-version }}-test + fi + docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python to build docs with sphinx + uses: actions/setup-python@v2 + with: + python-version: 2.7 + - name: Install base dependencies + run: | + python -m pip install --upgrade pip + python -m pip install tox sphinx + - name: Build and check docs using tox + run: tox -e docs \ No newline at end of file diff --git a/beetsplug/export.py b/beetsplug/export.py index f7e84a570..0b4876bbf 100644 --- a/beetsplug/export.py +++ b/beetsplug/export.py @@ -21,7 +21,7 @@ import sys import codecs import json import csv -import xml.etree.ElementTree as ET +import xml.etree.ElementTree as et from datetime import datetime, date from beets.plugins import BeetsPlugin @@ -188,18 +188,18 @@ class XMLFormat(ExportFormat): def export(self, data, **kwargs): # Creates the XML file structure. - library = ET.Element(u'library') - tracks = ET.SubElement(library, u'tracks') + library = et.Element(u'library') + tracks = et.SubElement(library, u'tracks') if data and isinstance(data[0], dict): for index, item in enumerate(data): - track = ET.SubElement(tracks, u'track') + track = et.SubElement(tracks, u'track') for key, value in item.items(): - track_details = ET.SubElement(track, key) + track_details = et.SubElement(track, key) track_details.text = value # Depending on the version of python the encoding needs to change try: - data = ET.tostring(library, encoding='unicode', **kwargs) + data = et.tostring(library, encoding='unicode', **kwargs) except LookupError: - data = ET.tostring(library, encoding='utf-8', **kwargs) + data = et.tostring(library, encoding='utf-8', **kwargs) self.out_stream.write(data) diff --git a/beetsplug/lyrics.py b/beetsplug/lyrics.py index 71e1d4ef3..f73d72dfd 100644 --- a/beetsplug/lyrics.py +++ b/beetsplug/lyrics.py @@ -907,7 +907,7 @@ class LyricsPlugin(plugins.BeetsPlugin): return _scrape_strip_cruft(lyrics, True) def append_translation(self, text, to_lang): - import xml.etree.ElementTree as ET + import xml.etree.ElementTree as et if not self.bing_auth_token: self.bing_auth_token = self.get_bing_access_token() @@ -925,7 +925,7 @@ class LyricsPlugin(plugins.BeetsPlugin): self.bing_auth_token = None return self.append_translation(text, to_lang) return text - lines_translated = ET.fromstring(r.text.encode('utf-8')).text + lines_translated = et.fromstring(r.text.encode('utf-8')).text # Use a translation mapping dict to build resulting lyrics translations = dict(zip(text_lines, lines_translated.split('|'))) result = '' diff --git a/beetsplug/metasync/amarok.py b/beetsplug/metasync/amarok.py index 0622fc17a..1d28eb3c9 100644 --- a/beetsplug/metasync/amarok.py +++ b/beetsplug/metasync/amarok.py @@ -49,7 +49,7 @@ class Amarok(MetaSource): 'amarok_lastplayed': DateType(), } - queryXML = u' \ + query_xml = u' \ \ \ \ @@ -72,7 +72,7 @@ class Amarok(MetaSource): # of the result set. So query for the filename and then try to match # the correct item from the results we get back results = self.collection.Query( - self.queryXML % quoteattr(basename(path)) + self.query_xml % quoteattr(basename(path)) ) for result in results: if result['xesam:url'] != path: diff --git a/beetsplug/plexupdate.py b/beetsplug/plexupdate.py index 17fd8208d..d3d7caf5c 100644 --- a/beetsplug/plexupdate.py +++ b/beetsplug/plexupdate.py @@ -12,7 +12,7 @@ Put something like the following in your config.yaml to configure: from __future__ import division, absolute_import, print_function import requests -import xml.etree.ElementTree as ET +import xml.etree.ElementTree as et from six.moves.urllib.parse import urljoin, urlencode from beets import config from beets.plugins import BeetsPlugin @@ -28,7 +28,7 @@ def get_music_section(host, port, token, library_name): r = requests.get(url) # Parse xml tree and extract music section key. - tree = ET.fromstring(r.content) + tree = et.fromstring(r.content) for child in tree.findall('Directory'): if child.get('title') == library_name: return child.get('key') diff --git a/beetsplug/smartplaylist.py b/beetsplug/smartplaylist.py index 4ffdd21e7..f3157c96f 100644 --- a/beetsplug/smartplaylist.py +++ b/beetsplug/smartplaylist.py @@ -105,17 +105,17 @@ class SmartPlaylistPlugin(BeetsPlugin): playlist_data = (playlist['name'],) try: - for key, Model in (('query', Item), ('album_query', Album)): + for key, model in (('query', Item), ('album_query', Album)): qs = playlist.get(key) if qs is None: query_and_sort = None, None elif isinstance(qs, six.string_types): - query_and_sort = parse_query_string(qs, Model) + query_and_sort = parse_query_string(qs, model) elif len(qs) == 1: - query_and_sort = parse_query_string(qs[0], Model) + query_and_sort = parse_query_string(qs[0], model) else: # multiple queries and sorts - queries, sorts = zip(*(parse_query_string(q, Model) + queries, sorts = zip(*(parse_query_string(q, model) for q in qs)) query = OrQuery(queries) final_sorts = [] diff --git a/beetsplug/subsonicplaylist.py b/beetsplug/subsonicplaylist.py index c537c4093..a4577278a 100644 --- a/beetsplug/subsonicplaylist.py +++ b/beetsplug/subsonicplaylist.py @@ -17,7 +17,7 @@ from __future__ import absolute_import, division, print_function import random import string -import xml.etree.ElementTree as ET +import xml.etree.ElementTree as et from hashlib import md5 from urllib.parse import urlencode @@ -85,7 +85,7 @@ class SubsonicPlaylistPlugin(BeetsPlugin): def get_playlist(self, playlist_id): xml = self.send('getPlaylist', {'id': playlist_id}).text - playlist = ET.fromstring(xml)[0] + playlist = et.fromstring(xml)[0] if playlist.attrib.get('code', '200') != '200': alt_error = 'error getting playlist, but no error message found' self._log.warn(playlist.attrib.get('message', alt_error)) @@ -101,8 +101,9 @@ class SubsonicPlaylistPlugin(BeetsPlugin): self.config.set_args(opts) ids = self.config['playlist_ids'].as_str_seq() if self.config['playlist_names'].as_str_seq(): - playlists = ET.fromstring(self.send('getPlaylists').text)[ - 0] + playlists = et.fromstring( + self.send('getPlaylists').text + )[0] if playlists.attrib.get('code', '200') != '200': alt_error = 'error getting playlists,' \ ' but no error message found' diff --git a/beetsplug/thumbnails.py b/beetsplug/thumbnails.py index fe36fbd13..01da5afff 100644 --- a/beetsplug/thumbnails.py +++ b/beetsplug/thumbnails.py @@ -25,7 +25,7 @@ from hashlib import md5 import os import shutil from itertools import chain -from pathlib import PurePosixPath +from pathlib2 import PurePosixPath import ctypes import ctypes.util diff --git a/setup.py b/setup.py index c50e65bf5..0be671f5d 100755 --- a/setup.py +++ b/setup.py @@ -122,7 +122,7 @@ setup( 'requests_oauthlib' ] + ( # Tests for the thumbnails plugin need pathlib on Python 2 too. - ['pathlib'] if (sys.version_info < (3, 4, 0)) else [] + ['pathlib2'] if (sys.version_info < (3, 4, 0)) else [] ), # Plugin (optional) dependencies: @@ -144,7 +144,7 @@ setup( 'web': ['flask', 'flask-cors'], 'import': ['rarfile'], 'thumbnails': ['pyxdg', 'Pillow'] + - (['pathlib'] if (sys.version_info < (3, 4, 0)) else []), + (['pathlib2'] if (sys.version_info < (3, 4, 0)) else []), 'metasync': ['dbus-python'], 'sonosupdate': ['soco'], 'scrub': ['mutagen>=1.33'], diff --git a/test/test_export.py b/test/test_export.py index 757212a38..18b2b17be 100644 --- a/test/test_export.py +++ b/test/test_export.py @@ -23,7 +23,7 @@ from test.helper import TestHelper import re # used to test csv format import json from xml.etree.ElementTree import Element -import xml.etree.ElementTree as ET +import xml.etree.ElementTree as et class ExportPluginTest(unittest.TestCase, TestHelper): @@ -85,7 +85,7 @@ class ExportPluginTest(unittest.TestCase, TestHelper): format_type='xml', artist=item1.artist ) - library = ET.fromstring(out) + library = et.fromstring(out) self.assertIsInstance(library, Element) for track in library[0]: for details in track: diff --git a/tox.ini b/tox.ini index 19cb3069b..06f0bda8c 100644 --- a/tox.ini +++ b/tox.ini @@ -4,54 +4,63 @@ # and then run "tox" from this directory. [tox] -envlist = py27-test, py37-test, py27-flake8, docs +envlist = + py27-{test} + py38-{test,cov,flake8} + docs -# The exhaustive list of environments is: -# envlist = py{27,34,35}-{test,cov}, py{27,34,35}-flake8, docs +# exhaustive list +# py{27,35,36,37,38}-{test,cov,flake8}, docs -[_test] -deps = - beautifulsoup4 - flask - mock - nose - nose-show-skipped - pylast - rarfile - responses>=0.3.0 - pyxdg - python-mpd2 - coverage - discogs-client - requests_oauthlib - -[_flake8] -deps = - flake8 - flake8-coding - flake8-future-import - flake8-blind-except - pep8-naming~=0.7.0 -files = beets beetsplug beet test setup.py docs +[gh-actions:env] +PLATFORM = + ubuntu-latest: linux + windows-latest: windows [testenv] passenv = NOSE_SHOW_SKIPPED # Undocumented feature of nose-show-skipped. deps = {test,cov}: {[_test]deps} - py27: pathlib - py{27,34,35,36,37,38}-flake8: {[_flake8]deps} + flake8: {[_flake8]deps} + py27: pathlib2 commands = - py27-cov: python -m nose --with-coverage {posargs} py27-test: python -m nose {posargs} - py3{4,5,6,7,8}-cov: python -bb -m nose --with-coverage {posargs} + py38-cov: python -bb -m nose --with-coverage {posargs} + py38-flake8: flake8 {posargs} {[_flake8]files} py3{4,5,6,7,8}-test: python -bb -m nose {posargs} - py27-flake8: flake8 --min-version 2.7 {posargs} {[_flake8]files} - py34-flake8: flake8 --min-version 3.4 {posargs} {[_flake8]files} - py35-flake8: flake8 --min-version 3.5 {posargs} {[_flake8]files} - py36-flake8: flake8 --min-version 3.6 {posargs} {[_flake8]files} - py37-flake8: flake8 --min-version 3.7 {posargs} {[_flake8]files} - py38-flake8: flake8 --min-version 3.8 {posargs} {[_flake8]files} + +[_test] +deps = + beautifulsoup4 + coverage + flask + discogs-client + mock + nose + nose-show-skipped + pylast + python-mpd2 + pyxdg + rarfile + requests_oauthlib + responses + sphinx + +[_flake8] +deps = + flake8 + flake8-blind-except + flake8-coding + flake8-future-import + pep8-naming +files = + beet + beets + beetsplug + docs + setup.py + test [testenv:docs] basepython = python2.7