From 2c8b25e77fb424d63f9f73318e85dd96cef865e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Alvergnat?= Date: Wed, 23 Dec 2020 11:29:57 +0100 Subject: [PATCH] feat: add python 3.9 support, drop python 2.7 support --- .github/workflows/ci.yml | 9 +-- guessit/__main__.py | 35 ++-------- guessit/api.py | 78 +++++++++-------------- guessit/backports.py | 27 -------- guessit/monkeypatch.py | 5 +- guessit/options.py | 12 ++-- guessit/rules/common/comparators.py | 14 ++-- guessit/rules/common/quantity.py | 4 +- guessit/rules/processors.py | 8 +-- guessit/rules/properties/audio_codec.py | 10 +-- guessit/rules/properties/episode_title.py | 10 +-- guessit/rules/properties/episodes.py | 10 +-- guessit/rules/properties/language.py | 6 +- guessit/rules/properties/release_group.py | 4 +- guessit/rules/properties/screen_size.py | 2 +- guessit/rules/properties/title.py | 4 +- guessit/test/test_api.py | 33 +++------- guessit/test/test_api_unicode_literals.py | 29 +++------ guessit/test/test_main.py | 2 +- guessit/test/test_yml.py | 15 +---- guessit/yamlutils.py | 9 +-- setup.py | 6 +- tox.ini | 7 +- 23 files changed, 112 insertions(+), 227 deletions(-) delete mode 100644 guessit/backports.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3069917..ad396b9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [ 2.7, 3.5, 3.6, 3.7, 3.8 ] + python-version: [ 3.5, 3.6, 3.7, 3.8, 3.9, pypy-3.6 ] steps: - name: Setup python ${{ matrix.python-version }} @@ -28,11 +28,8 @@ jobs: - name: Install Dependencies run: | - pip install pip --upgrade - pip install -e .[dev,test] --upgrade - pip install pytest --upgrade + pip install -e .[dev,test] pip install coveralls - pytest --version - run: pylint guessit if: matrix.python-version != '3.9' @@ -74,7 +71,7 @@ jobs: git config --global user.name "github-actions" - name: Install Dependencies - run: pip install -r requirements-dev.txt + run: pip install -e .[test] - name: Install python-semantic-release run: pip install python-semantic-release diff --git a/guessit/__main__.py b/guessit/__main__.py index fad196d..1adf078 100644 --- a/guessit/__main__.py +++ b/guessit/__main__.py @@ -4,14 +4,12 @@ Entry point module """ # pragma: no cover -from __future__ import print_function - import json import logging -import os import sys -import six +from collections import OrderedDict + from rebulk.__version__ import __version__ as __rebulk_version__ from guessit import api @@ -20,12 +18,6 @@ from guessit.jsonutils import GuessitEncoder from guessit.options import argument_parser, parse_options, load_config, merge_options -try: - from collections import OrderedDict -except ImportError: # pragma: no-cover - from ordereddict import OrderedDict # pylint:disable=import-error - - def guess_filename(filename, options): """ Guess a single filename using given options @@ -48,6 +40,7 @@ def guess_filename(filename, options): if options.get('json'): print(json.dumps(guess, cls=GuessitEncoder, ensure_ascii=False)) elif options.get('yaml'): + # pylint:disable=import-outside-toplevel import yaml from guessit import yamlutils @@ -78,6 +71,7 @@ def display_properties(options): else: print(json.dumps(list(properties.keys()), cls=GuessitEncoder, ensure_ascii=False)) elif options.get('yaml'): + # pylint:disable=import-outside-toplevel import yaml from guessit import yamlutils if options.get('values'): @@ -97,24 +91,10 @@ def display_properties(options): print(4 * ' ' + '[!] %s' % (property_value,)) -def fix_argv_encoding(): - """ - Fix encoding of sys.argv on windows Python 2 - """ - if six.PY2 and os.name == 'nt': # pragma: no cover - # see http://bugs.python.org/issue2128 - import locale - - for i, j in enumerate(sys.argv): - sys.argv[i] = j.decode(locale.getpreferredencoding()) - - def main(args=None): # pylint:disable=too-many-branches """ Main function for entry point """ - fix_argv_encoding() - if args is None: # pragma: no cover options = parse_options() else: @@ -142,7 +122,7 @@ def main(args=None): # pylint:disable=too-many-branches if options.get('yaml'): try: - import yaml # pylint:disable=unused-variable,unused-import + import yaml # pylint:disable=unused-variable,unused-import,import-outside-toplevel except ImportError: # pragma: no cover del options['yaml'] print('PyYAML is not installed. \'--yaml\' option will be ignored ...', file=sys.stderr) @@ -156,10 +136,7 @@ def main(args=None): # pylint:disable=too-many-branches for filename in options.get('filename'): filenames.append(filename) if options.get('input_file'): - if six.PY2: - input_file = open(options.get('input_file'), 'r') - else: - input_file = open(options.get('input_file'), 'r', encoding='utf-8') + input_file = open(options.get('input_file'), 'r', encoding='utf-8') try: filenames.extend([line.strip() for line in input_file.readlines()]) finally: diff --git a/guessit/api.py b/guessit/api.py index 8e30634..81904b6 100644 --- a/guessit/api.py +++ b/guessit/api.py @@ -4,15 +4,12 @@ API functions that can be used by external software """ -try: - from collections import OrderedDict -except ImportError: # pragma: no-cover - from ordereddict import OrderedDict # pylint:disable=import-error +from collections import OrderedDict +from pathlib import Path import os import traceback -import six from rebulk.introspector import introspect from .__version__ import __version__ @@ -26,18 +23,18 @@ class GuessitException(Exception): """ def __init__(self, string, options): - super(GuessitException, self).__init__("An internal error has occured in guessit.\n" - "===================== Guessit Exception Report =====================\n" - "version=%s\n" - "string=%s\n" - "options=%s\n" - "--------------------------------------------------------------------\n" - "%s" - "--------------------------------------------------------------------\n" - "Please report at " - "https://github.com/guessit-io/guessit/issues.\n" - "====================================================================" % - (__version__, str(string), str(options), traceback.format_exc())) + super().__init__("An internal error has occured in guessit.\n" + "===================== Guessit Exception Report =====================\n" + "version=%s\n" + "string=%s\n" + "options=%s\n" + "--------------------------------------------------------------------\n" + "%s" + "--------------------------------------------------------------------\n" + "Please report at " + "https://github.com/guessit-io/guessit/issues.\n" + "====================================================================" % + (__version__, str(string), str(options), traceback.format_exc())) self.string = string self.options = options @@ -113,9 +110,7 @@ class GuessItApi(object): return [cls._fix_encoding(item) for item in value] if isinstance(value, dict): return {cls._fix_encoding(k): cls._fix_encoding(v) for k, v in value.items()} - if six.PY2 and isinstance(value, six.text_type): - return value.encode('utf-8') - if six.PY3 and isinstance(value, six.binary_type): + if isinstance(value, bytes): return value.decode('ascii') return value @@ -175,16 +170,12 @@ class GuessItApi(object): :return: :rtype: """ - try: - from pathlib import Path - if isinstance(string, Path): - try: - # Handle path-like object - string = os.fspath(string) - except AttributeError: - string = str(string) - except ImportError: - pass + if isinstance(string, Path): + try: + # Handle path-like object + string = os.fspath(string) + except AttributeError: + string = str(string) try: options = parse_options(options, True) @@ -194,32 +185,23 @@ class GuessItApi(object): result_decode = False result_encode = False - if six.PY2: - if isinstance(string, six.text_type): - string = string.encode("utf-8") - result_decode = True - elif isinstance(string, six.binary_type): - string = six.binary_type(string) - if six.PY3: - if isinstance(string, six.binary_type): - string = string.decode('ascii') - result_encode = True - elif isinstance(string, six.text_type): - string = six.text_type(string) + if isinstance(string, bytes): + string = string.decode('ascii') + result_encode = True matches = self.rebulk.matches(string, options) if result_decode: for match in matches: - if isinstance(match.value, six.binary_type): + if isinstance(match.value, bytes): match.value = match.value.decode("utf-8") if result_encode: for match in matches: - if isinstance(match.value, six.text_type): + if isinstance(match.value, str): match.value = match.value.encode("ascii") return matches.to_dict(options.get('advanced', False), options.get('single_value', False), options.get('enforce_list', False)) - except: - raise GuessitException(string, options) + except Exception as err: + raise GuessitException(string, options) from err def properties(self, options=None): """ @@ -235,8 +217,8 @@ class GuessItApi(object): options = merge_options(config, options) unordered = introspect(self.rebulk, options).properties ordered = OrderedDict() - for k in sorted(unordered.keys(), key=six.text_type): - ordered[k] = list(sorted(unordered[k], key=six.text_type)) + for k in sorted(unordered.keys(), key=str): + ordered[k] = list(sorted(unordered[k], key=str)) if hasattr(self.rebulk, 'customize_properties'): ordered = self.rebulk.customize_properties(ordered) return ordered diff --git a/guessit/backports.py b/guessit/backports.py deleted file mode 100644 index c149a6b..0000000 --- a/guessit/backports.py +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -Backports -""" -# pragma: no-cover -# pylint: skip-file - -def cmp_to_key(mycmp): - """functools.cmp_to_key backport""" - class KeyClass(object): - """Key class""" - def __init__(self, obj, *args): # pylint: disable=unused-argument - self.obj = obj - def __lt__(self, other): - return mycmp(self.obj, other.obj) < 0 - def __gt__(self, other): - return mycmp(self.obj, other.obj) > 0 - def __eq__(self, other): - return mycmp(self.obj, other.obj) == 0 - def __le__(self, other): - return mycmp(self.obj, other.obj) <= 0 - def __ge__(self, other): - return mycmp(self.obj, other.obj) >= 0 - def __ne__(self, other): - return mycmp(self.obj, other.obj) != 0 - return KeyClass diff --git a/guessit/monkeypatch.py b/guessit/monkeypatch.py index 33e7c46..14ddf6e 100644 --- a/guessit/monkeypatch.py +++ b/guessit/monkeypatch.py @@ -4,10 +4,7 @@ Monkeypatch initialisation functions """ -try: - from collections import OrderedDict -except ImportError: # pragma: no-cover - from ordereddict import OrderedDict # pylint:disable=import-error +from collections import OrderedDict from rebulk.match import Match diff --git a/guessit/options.py b/guessit/options.py index 8fa6825..21654ce 100644 --- a/guessit/options.py +++ b/guessit/options.py @@ -11,8 +11,6 @@ import shlex from argparse import ArgumentParser -import six - def build_argument_parser(): """ @@ -108,7 +106,7 @@ def parse_options(options=None, api=False): :return: :rtype: """ - if isinstance(options, six.string_types): + if isinstance(options, str): args = shlex.split(options) options = vars(argument_parser.parse_args(args)) elif options is None: @@ -153,7 +151,7 @@ def load_config(options): cwd = os.getcwd() yaml_supported = False try: - import yaml # pylint:disable=unused-variable,unused-import + import yaml # pylint:disable=unused-variable,unused-import,import-outside-toplevel yaml_supported = True except ImportError: pass @@ -250,13 +248,13 @@ def load_config_file(filepath): return json.load(config_file_data) if filepath.endswith('.yaml') or filepath.endswith('.yml'): try: - import yaml + import yaml # pylint:disable=import-outside-toplevel with open(filepath) as config_file_data: return yaml.load(config_file_data, yaml.SafeLoader) - except ImportError: # pragma: no cover + except ImportError as err: # pragma: no cover raise ConfigurationException('Configuration file extension is not supported. ' 'PyYAML should be installed to support "%s" file' % ( - filepath,)) + filepath,)) from err try: # Try to load input as JSON diff --git a/guessit/rules/common/comparators.py b/guessit/rules/common/comparators.py index f46f0c1..2774931 100644 --- a/guessit/rules/common/comparators.py +++ b/guessit/rules/common/comparators.py @@ -3,10 +3,8 @@ """ Comparators """ -try: - from functools import cmp_to_key -except ImportError: - from ...backports import cmp_to_key + +from functools import cmp_to_key def marker_comparator_predicate(match): @@ -14,10 +12,10 @@ def marker_comparator_predicate(match): Match predicate used in comparator """ return ( - not match.private - and match.name not in ('proper_count', 'title') - and not (match.name == 'container' and 'extension' in match.tags) - and not (match.name == 'other' and match.value == 'Rip') + not match.private + and match.name not in ('proper_count', 'title') + and not (match.name == 'container' and 'extension' in match.tags) + and not (match.name == 'other' and match.value == 'Rip') ) diff --git a/guessit/rules/common/quantity.py b/guessit/rules/common/quantity.py index bbd41fb..770fc2f 100644 --- a/guessit/rules/common/quantity.py +++ b/guessit/rules/common/quantity.py @@ -6,8 +6,6 @@ Quantities: Size import re from abc import abstractmethod -import six - from ..common import seps @@ -50,7 +48,7 @@ class Quantity(object): return hash(str(self)) def __eq__(self, other): - if isinstance(other, six.string_types): + if isinstance(other, str): return str(self) == other if not isinstance(other, self.__class__): return NotImplemented diff --git a/guessit/rules/processors.py b/guessit/rules/processors.py index 5b01814..069e75d 100644 --- a/guessit/rules/processors.py +++ b/guessit/rules/processors.py @@ -6,8 +6,6 @@ Processors from collections import defaultdict import copy -import six - from rebulk import Rebulk, Rule, CustomRule, POST_PROCESS, PRE_PROCESS, AppendMatch, RemoveMatch from .common import seps_no_groups @@ -68,7 +66,7 @@ class EquivalentHoles(Rule): for name in matches.names: for hole in list(holes): for current_match in matches.named(name): - if isinstance(current_match.value, six.string_types) and \ + if isinstance(current_match.value, str) and \ hole.value.lower() == current_match.value.lower(): if 'equivalent-ignore' in current_match.tags: continue @@ -96,7 +94,7 @@ class RemoveAmbiguous(Rule): consequence = RemoveMatch def __init__(self, sort_function=marker_sorted, predicate=None): - super(RemoveAmbiguous, self).__init__() + super().__init__() self.sort_function = sort_function self.predicate = predicate @@ -131,7 +129,7 @@ class RemoveLessSpecificSeasonEpisode(RemoveAmbiguous): keep the one tagged as 'SxxExx' or in the rightmost filepart. """ def __init__(self, name): - super(RemoveLessSpecificSeasonEpisode, self).__init__( + super().__init__( sort_function=(lambda markers, matches: marker_sorted(list(reversed(markers)), matches, lambda match: match.name == name and 'SxxExx' in match.tags)), diff --git a/guessit/rules/properties/audio_codec.py b/guessit/rules/properties/audio_codec.py index 815caff..0aa7d31 100644 --- a/guessit/rules/properties/audio_codec.py +++ b/guessit/rules/properties/audio_codec.py @@ -130,7 +130,7 @@ class AudioProfileRule(Rule): consequence = RemoveMatch def __init__(self, codec): - super(AudioProfileRule, self).__init__() + super().__init__() self.codec = codec def enabled(self, context): @@ -166,7 +166,7 @@ class DtsHDRule(AudioProfileRule): """ def __init__(self): - super(DtsHDRule, self).__init__('DTS-HD') + super().__init__('DTS-HD') class DtsRule(AudioProfileRule): @@ -175,7 +175,7 @@ class DtsRule(AudioProfileRule): """ def __init__(self): - super(DtsRule, self).__init__('DTS') + super().__init__('DTS') class AacRule(AudioProfileRule): @@ -184,7 +184,7 @@ class AacRule(AudioProfileRule): """ def __init__(self): - super(AacRule, self).__init__('AAC') + super().__init__('AAC') class DolbyDigitalRule(AudioProfileRule): @@ -193,7 +193,7 @@ class DolbyDigitalRule(AudioProfileRule): """ def __init__(self): - super(DolbyDigitalRule, self).__init__('Dolby Digital') + super().__init__('Dolby Digital') class HqConflictRule(Rule): diff --git a/guessit/rules/properties/episode_title.py b/guessit/rules/properties/episode_title.py index ece8921..2c4fab6 100644 --- a/guessit/rules/properties/episode_title.py +++ b/guessit/rules/properties/episode_title.py @@ -47,7 +47,7 @@ class RemoveConflictsWithEpisodeTitle(Rule): consequence = RemoveMatch def __init__(self, previous_names): - super(RemoveConflictsWithEpisodeTitle, self).__init__() + super().__init__() self.previous_names = previous_names self.next_names = ('streaming_service', 'screen_size', 'source', 'video_codec', 'audio_codec', 'other', 'container') @@ -129,7 +129,7 @@ class EpisodeTitleFromPosition(TitleBaseRule): dependency = TitleToEpisodeTitle def __init__(self, previous_names): - super(EpisodeTitleFromPosition, self).__init__('episode_title', ['title']) + super().__init__('episode_title', ['title']) self.previous_names = previous_names def hole_filter(self, hole, matches): @@ -150,12 +150,12 @@ class EpisodeTitleFromPosition(TitleBaseRule): def should_remove(self, match, matches, filepart, hole, context): if match.name == 'episode_details': return False - return super(EpisodeTitleFromPosition, self).should_remove(match, matches, filepart, hole, context) + return super().should_remove(match, matches, filepart, hole, context) def when(self, matches, context): # pylint:disable=inconsistent-return-statements if matches.named('episode_title'): return - return super(EpisodeTitleFromPosition, self).when(matches, context) + return super().when(matches, context) class AlternativeTitleReplace(Rule): @@ -166,7 +166,7 @@ class AlternativeTitleReplace(Rule): consequence = RenameMatch def __init__(self, previous_names): - super(AlternativeTitleReplace, self).__init__() + super().__init__() self.previous_names = previous_names def when(self, matches, context): # pylint:disable=inconsistent-return-statements diff --git a/guessit/rules/properties/episodes.py b/guessit/rules/properties/episodes.py index 345c785..7aa2245 100644 --- a/guessit/rules/properties/episodes.py +++ b/guessit/rules/properties/episodes.py @@ -479,7 +479,7 @@ class SeePatternRange(Rule): consequence = [RemoveMatch, AppendMatch] def __init__(self, range_separators): - super(SeePatternRange, self).__init__() + super().__init__() self.range_separators = range_separators def when(self, matches, context): @@ -516,7 +516,7 @@ class AbstractSeparatorRange(Rule): consequence = [RemoveMatch, AppendMatch] def __init__(self, range_separators, property_name): - super(AbstractSeparatorRange, self).__init__() + super().__init__() self.range_separators = range_separators self.property_name = property_name @@ -608,7 +608,7 @@ class EpisodeNumberSeparatorRange(AbstractSeparatorRange): """ def __init__(self, range_separators): - super(EpisodeNumberSeparatorRange, self).__init__(range_separators, "episode") + super().__init__(range_separators, "episode") class SeasonSeparatorRange(AbstractSeparatorRange): @@ -617,7 +617,7 @@ class SeasonSeparatorRange(AbstractSeparatorRange): """ def __init__(self, range_separators): - super(SeasonSeparatorRange, self).__init__(range_separators, "season") + super().__init__(range_separators, "season") class RemoveWeakIfMovie(Rule): @@ -662,7 +662,7 @@ class RemoveWeak(Rule): consequence = RemoveMatch, AppendMatch def __init__(self, episode_words): - super(RemoveWeak, self).__init__() + super().__init__() self.episode_words = episode_words def when(self, matches, context): diff --git a/guessit/rules/properties/language.py b/guessit/rules/properties/language.py index 3f83bc3..c1f9e6a 100644 --- a/guessit/rules/properties/language.py +++ b/guessit/rules/properties/language.py @@ -396,7 +396,7 @@ class SubtitlePrefixLanguageRule(Rule): def then(self, matches, when_response, context): to_rename, to_remove = when_response - super(SubtitlePrefixLanguageRule, self).then(matches, to_remove, context) + super().then(matches, to_remove, context) for prefix, match in to_rename: # Remove suffix equivalent of prefix. suffix = copy.copy(prefix) @@ -435,7 +435,7 @@ class SubtitleSuffixLanguageRule(Rule): def then(self, matches, when_response, context): to_rename, to_remove = when_response - super(SubtitleSuffixLanguageRule, self).then(matches, to_remove, context) + super().then(matches, to_remove, context) for match in to_rename: matches.remove(match) match.name = 'subtitle_language' @@ -488,7 +488,7 @@ class RemoveInvalidLanguages(Rule): def __init__(self, common_words): """Constructor.""" - super(RemoveInvalidLanguages, self).__init__() + super().__init__() self.common_words = common_words def when(self, matches, context): diff --git a/guessit/rules/properties/release_group.py b/guessit/rules/properties/release_group.py index 71998a4..09e845f 100644 --- a/guessit/rules/properties/release_group.py +++ b/guessit/rules/properties/release_group.py @@ -98,7 +98,7 @@ class DashSeparatedReleaseGroup(Rule): def __init__(self, value_formatter): """Default constructor.""" - super(DashSeparatedReleaseGroup, self).__init__() + super().__init__() self.value_formatter = value_formatter @classmethod @@ -212,7 +212,7 @@ class SceneReleaseGroup(Rule): def __init__(self, value_formatter): """Default constructor.""" - super(SceneReleaseGroup, self).__init__() + super().__init__() self.value_formatter = value_formatter @staticmethod diff --git a/guessit/rules/properties/screen_size.py b/guessit/rules/properties/screen_size.py index 77d5d05..966fc3c 100644 --- a/guessit/rules/properties/screen_size.py +++ b/guessit/rules/properties/screen_size.py @@ -69,7 +69,7 @@ class PostProcessScreenSize(Rule): consequence = AppendMatch def __init__(self, standard_heights, min_ar, max_ar): - super(PostProcessScreenSize, self).__init__() + super().__init__() self.standard_heights = standard_heights self.min_ar = min_ar self.max_ar = max_ar diff --git a/guessit/rules/properties/title.py b/guessit/rules/properties/title.py index 0d26301..2a065cb 100644 --- a/guessit/rules/properties/title.py +++ b/guessit/rules/properties/title.py @@ -53,7 +53,7 @@ class TitleBaseRule(Rule): consequence = [AppendMatch, RemoveMatch] def __init__(self, match_name, match_tags=None, alternative_match_name=None): - super(TitleBaseRule, self).__init__() + super().__init__() self.match_name = match_name self.match_tags = match_tags self.alternative_match_name = alternative_match_name @@ -299,7 +299,7 @@ class TitleFromPosition(TitleBaseRule): properties = {'title': [None], 'alternative_title': [None]} def __init__(self): - super(TitleFromPosition, self).__init__('title', ['title'], 'alternative_title') + super().__init__('title', ['title'], 'alternative_title') def enabled(self, context): return not is_disabled(context, 'alternative_title') diff --git a/guessit/test/test_api.py b/guessit/test/test_api.py index 391dbce..5422925 100644 --- a/guessit/test/test_api.py +++ b/guessit/test/test_api.py @@ -3,10 +3,9 @@ # pylint: disable=no-self-use, pointless-statement, missing-docstring, invalid-name, pointless-string-statement import json import os -import sys +from pathlib import Path import pytest -import six from ..api import guessit, properties, suggested_expected, GuessitException @@ -19,25 +18,19 @@ def test_default(): def test_forced_unicode(): - ret = guessit(u'Fear.and.Loathing.in.Las.Vegas.FRENCH.ENGLISH.720p.HDDVD.DTS.x264-ESiR.mkv') - assert ret and 'title' in ret and isinstance(ret['title'], six.text_type) + ret = guessit('Fear.and.Loathing.in.Las.Vegas.FRENCH.ENGLISH.720p.HDDVD.DTS.x264-ESiR.mkv') + assert ret and 'title' in ret and isinstance(ret['title'], str) def test_forced_binary(): ret = guessit(b'Fear.and.Loathing.in.Las.Vegas.FRENCH.ENGLISH.720p.HDDVD.DTS.x264-ESiR.mkv') - assert ret and 'title' in ret and isinstance(ret['title'], six.binary_type) + assert ret and 'title' in ret and isinstance(ret['title'], bytes) -@pytest.mark.skipif(sys.version_info < (3, 4), reason="Path is not available") def test_pathlike_object(): - try: - from pathlib import Path - - path = Path('Fear.and.Loathing.in.Las.Vegas.FRENCH.ENGLISH.720p.HDDVD.DTS.x264-ESiR.mkv') - ret = guessit(path) - assert ret and 'title' in ret - except ImportError: # pragma: no-cover - pass + path = Path('Fear.and.Loathing.in.Las.Vegas.FRENCH.ENGLISH.720p.HDDVD.DTS.x264-ESiR.mkv') + ret = guessit(path) + assert ret and 'title' in ret def test_unicode_japanese(): @@ -51,16 +44,8 @@ def test_unicode_japanese_options(): def test_forced_unicode_japanese_options(): - ret = guessit(u"[阿维达].Avida.2006.FRENCH.DVDRiP.XViD-PROD.avi", options={"expected_title": [u"阿维达"]}) - assert ret and 'title' in ret and ret['title'] == u"阿维达" - -# TODO: This doesn't compile on python 3, but should be tested on python 2. -""" -if six.PY2: - def test_forced_binary_japanese_options(): - ret = guessit(b"[阿维达].Avida.2006.FRENCH.DVDRiP.XViD-PROD.avi", options={"expected_title": [b"阿维达"]}) - assert ret and 'title' in ret and ret['title'] == b"阿维达" -""" + ret = guessit("[阿维达].Avida.2006.FRENCH.DVDRiP.XViD-PROD.avi", options={"expected_title": ["阿维达"]}) + assert ret and 'title' in ret and ret['title'] == "阿维达" def test_properties(): diff --git a/guessit/test/test_api_unicode_literals.py b/guessit/test/test_api_unicode_literals.py index 826f7cd..79bbbca 100644 --- a/guessit/test/test_api_unicode_literals.py +++ b/guessit/test/test_api_unicode_literals.py @@ -3,12 +3,9 @@ # pylint: disable=no-self-use, pointless-statement, missing-docstring, invalid-name, pointless-string-statement -from __future__ import unicode_literals - import os import pytest -import six from ..api import guessit, properties, GuessitException @@ -21,13 +18,13 @@ def test_default(): def test_forced_unicode(): - ret = guessit(u'Fear.and.Loathing.in.Las.Vegas.FRENCH.ENGLISH.720p.HDDVD.DTS.x264-ESiR.mkv') - assert ret and 'title' in ret and isinstance(ret['title'], six.text_type) + ret = guessit('Fear.and.Loathing.in.Las.Vegas.FRENCH.ENGLISH.720p.HDDVD.DTS.x264-ESiR.mkv') + assert ret and 'title' in ret and isinstance(ret['title'], str) def test_forced_binary(): ret = guessit(b'Fear.and.Loathing.in.Las.Vegas.FRENCH.ENGLISH.720p.HDDVD.DTS.x264-ESiR.mkv') - assert ret and 'title' in ret and isinstance(ret['title'], six.binary_type) + assert ret and 'title' in ret and isinstance(ret['title'], bytes) def test_unicode_japanese(): @@ -41,24 +38,18 @@ def test_unicode_japanese_options(): def test_forced_unicode_japanese_options(): - ret = guessit(u"[阿维达].Avida.2006.FRENCH.DVDRiP.XViD-PROD.avi", options={"expected_title": [u"阿维达"]}) - assert ret and 'title' in ret and ret['title'] == u"阿维达" - -# TODO: This doesn't compile on python 3, but should be tested on python 2. -""" -if six.PY2: - def test_forced_binary_japanese_options(): - ret = guessit(b"[阿维达].Avida.2006.FRENCH.DVDRiP.XViD-PROD.avi", options={"expected_title": [b"阿维达"]}) - assert ret and 'title' in ret and ret['title'] == b"阿维达" -""" + ret = guessit("[阿维达].Avida.2006.FRENCH.DVDRiP.XViD-PROD.avi", options={"expected_title": ["阿维达"]}) + assert ret and 'title' in ret and ret['title'] == "阿维达" -def test_ensure_standard_string_class(): +def test_ensure_custom_string_class(): class CustomStr(str): pass - ret = guessit(CustomStr('1080p'), options={'advanced': True}) - assert ret and 'screen_size' in ret and not isinstance(ret['screen_size'].input_string, CustomStr) + ret = guessit(CustomStr('some.title.1080p.mkv'), options={'advanced': True}) + assert ret and 'screen_size' in ret and isinstance(ret['screen_size'].input_string, CustomStr) + assert ret and 'title' in ret and isinstance(ret['title'].input_string, CustomStr) + assert ret and 'container' in ret and isinstance(ret['container'].input_string, CustomStr) def test_properties(): diff --git a/guessit/test/test_main.py b/guessit/test/test_main.py index cbdba7a..974b822 100644 --- a/guessit/test/test_main.py +++ b/guessit/test/test_main.py @@ -24,7 +24,7 @@ def test_main_unicode(): def test_main_forced_unicode(): - main([u'Fear.and.Loathing.in.Las.Vegas.FRENCH.ENGLISH.720p.HDDVD.DTS.x264-ESiR.mkv']) + main(['Fear.and.Loathing.in.Las.Vegas.FRENCH.ENGLISH.720p.HDDVD.DTS.x264-ESiR.mkv']) def test_main_verbose(): diff --git a/guessit/test/test_yml.py b/guessit/test/test_yml.py index 040796d..93ea619 100644 --- a/guessit/test/test_yml.py +++ b/guessit/test/test_yml.py @@ -7,7 +7,6 @@ import os from io import open # pylint: disable=redefined-builtin import babelfish -import six # pylint:disable=wrong-import-order import yaml # pylint:disable=wrong-import-order from rebulk.remodule import re from rebulk.utils import is_iterable @@ -161,7 +160,7 @@ class TestYml(object): for string, expected in data.items(): TestYml.set_default(expected, default) - string = TestYml.fix_encoding(string, expected) + string = TestYml.fix_encoding(string) entries.append((filename, string, expected)) unique_id = self._get_unique_id(entry_set, '[' + filename + '] ' + str(string)) @@ -178,17 +177,7 @@ class TestYml(object): expected[k] = v @classmethod - def fix_encoding(cls, string, expected): - if six.PY2: - if isinstance(string, six.text_type): - string = string.encode('utf-8') - converts = [] - for k, v in expected.items(): - if isinstance(v, six.text_type): - v = v.encode('utf-8') - converts.append((k, v)) - for k, v in converts: - expected[k] = v + def fix_encoding(cls, string): if not isinstance(string, str): string = str(string) return string diff --git a/guessit/yamlutils.py b/guessit/yamlutils.py index d04be64..f038a99 100644 --- a/guessit/yamlutils.py +++ b/guessit/yamlutils.py @@ -4,10 +4,7 @@ Options """ -try: - from collections import OrderedDict -except ImportError: # pragma: no-cover - from ordereddict import OrderedDict # pylint:disable=import-error +from collections import OrderedDict import babelfish import yaml # pylint:disable=wrong-import-order @@ -24,8 +21,8 @@ class OrderedDictYAMLLoader(yaml.SafeLoader): def __init__(self, *args, **kwargs): yaml.SafeLoader.__init__(self, *args, **kwargs) - self.add_constructor(u'tag:yaml.org,2002:map', type(self).construct_yaml_map) - self.add_constructor(u'tag:yaml.org,2002:omap', type(self).construct_yaml_map) + self.add_constructor('tag:yaml.org,2002:map', type(self).construct_yaml_map) + self.add_constructor('tag:yaml.org,2002:omap', type(self).construct_yaml_map) def construct_yaml_map(self, node): data = OrderedDict() diff --git a/setup.py b/setup.py index f352dc7..0f1900f 100644 --- a/setup.py +++ b/setup.py @@ -16,13 +16,13 @@ with io.open(os.path.join(here, 'README.md'), encoding='utf-8') as f: with io.open(os.path.join(here, 'CHANGELOG.md'), encoding='utf-8') as f: changelog = f.read() -install_requires = ['rebulk==2.*', 'babelfish', 'python-dateutil', 'six'] +install_requires = ['rebulk==2.*', 'babelfish', 'python-dateutil'] setup_requires = ['pytest-runner'] -dev_require = ['pylint', 'mkdocs', 'mkdocs-material'] +dev_require = ['tox', 'mkdocs', 'mkdocs-material'] -tests_require = ['pytest>=3.3', 'pytest-benchmark', 'PyYAML'] +tests_require = ['pytest', 'pytest-benchmark', 'pylint', 'PyYAML'] package_data = ['config/*'] diff --git a/tox.ini b/tox.ini index bf188c0..4331201 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,10 @@ [tox] -envlist = py27,py35,py36,py37,py38,pypy2,pypy +envlist = py35,py36,py37,py38,py39,pypy3 + +[testenv:py39] +commands = + {envbindir}/pip install -e .[dev,test] + {envpython} setup.py test [testenv] commands =