diff options
48 files changed, 253 insertions, 336 deletions
diff --git a/libs/guessit/__main__.py b/libs/guessit/__main__.py index fad196d6b..1adf078a7 100644 --- a/libs/guessit/__main__.py +++ b/libs/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/libs/guessit/__version__.py b/libs/guessit/__version__.py index e505897bb..357f94c75 100644 --- a/libs/guessit/__version__.py +++ b/libs/guessit/__version__.py @@ -4,4 +4,4 @@ Version module """ # pragma: no cover -__version__ = '3.1.1' +__version__ = '3.3.1' diff --git a/libs/guessit/api.py b/libs/guessit/api.py index 8e306340b..eeb8def1c 100644 --- a/libs/guessit/api.py +++ b/libs/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,27 @@ 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) + matches_dict = matches.to_dict(options.get('advanced', False), options.get('single_value', False), + options.get('enforce_list', False)) + output_input_string = options.get('output_input_string', False) + if output_input_string: + matches_dict['input_string'] = matches.input_string + return matches_dict + except Exception as err: + raise GuessitException(string, options) from err def properties(self, options=None): """ @@ -235,8 +221,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/libs/guessit/backports.py b/libs/guessit/backports.py deleted file mode 100644 index c149a6b5d..000000000 --- a/libs/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/libs/guessit/config/options.json b/libs/guessit/config/options.json index da7c70306..5a343bcb4 100644 --- a/libs/guessit/config/options.json +++ b/libs/guessit/config/options.json @@ -416,6 +416,10 @@ "Animal Planet": "ANPL", "AnimeLab": "ANLB", "AOL": "AOL", + "AppleTV": [ + "ATVP", + "ATV+" + ], "ARD": "ARD", "BBC iPlayer": [ "iP", @@ -482,6 +486,7 @@ "HBO", "re:HBO-?Go" ], + "HBO Max": "HMAX", "HGTV": "HGTV", "History": [ "HIST", @@ -490,7 +495,10 @@ "Hulu": "HULU", "Investigation Discovery": "ID", "IFC": "IFC", - "iTunes": "iTunes", + "iTunes": [ + "iTunes", + {"pattern": "iT", "ignore_case": false} + ], "ITV": "ITV", "Knowledge Network": "KNOW", "Lifetime": "LIFE", @@ -537,6 +545,7 @@ "SeeSo" ], "Shomi": "SHMI", + "Showtime": "SHO", "Spike": "SPIK", "Spike TV": [ "SPKE", diff --git a/libs/guessit/tlds-alpha-by-domain.txt b/libs/guessit/data/tlds-alpha-by-domain.txt index 280c794c5..280c794c5 100644 --- a/libs/guessit/tlds-alpha-by-domain.txt +++ b/libs/guessit/data/tlds-alpha-by-domain.txt diff --git a/libs/guessit/monkeypatch.py b/libs/guessit/monkeypatch.py index 33e7c46ee..14ddf6e89 100644 --- a/libs/guessit/monkeypatch.py +++ b/libs/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/libs/guessit/options.py b/libs/guessit/options.py index 8fa6825cc..d1a76b037 100644 --- a/libs/guessit/options.py +++ b/libs/guessit/options.py @@ -11,8 +11,6 @@ import shlex from argparse import ArgumentParser -import six - def build_argument_parser(): """ @@ -68,6 +66,8 @@ def build_argument_parser(): help='Display information for filename guesses as json output') output_opts.add_argument('-y', '--yaml', dest='yaml', action='store_true', default=None, help='Display information for filename guesses as yaml output') + output_opts.add_argument('-i', '--output-input-string', dest='output_input_string', action='store_true', + default=False, help='Add input_string property in the output') conf_opts = opts.add_argument_group("Configuration") conf_opts.add_argument('-c', '--config', dest='config', action='append', default=None, @@ -108,7 +108,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 +153,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 @@ -225,7 +225,7 @@ def merge_option_value(option, value, merged): if value is not None and option != 'pristine': if option in merged.keys() and isinstance(merged[option], list): for val in value: - if val not in merged[option]: + if val not in merged[option] and val is not None: merged[option].append(val) elif option in merged.keys() and isinstance(merged[option], dict): merged[option] = merge_options(merged[option], value) @@ -250,13 +250,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/libs/guessit/rules/common/__init__.py b/libs/guessit/rules/common/__init__.py index 444dc72a9..24f9433dd 100644 --- a/libs/guessit/rules/common/__init__.py +++ b/libs/guessit/rules/common/__init__.py @@ -3,7 +3,7 @@ """ Common module """ -import re +from rebulk.remodule import re seps = r' [](){}+*|=-_~#/\\.,;:' # list of tags/words separators seps_no_groups = seps.replace('[](){}', '') diff --git a/libs/guessit/rules/common/comparators.py b/libs/guessit/rules/common/comparators.py index f46f0c119..277493144 100644 --- a/libs/guessit/rules/common/comparators.py +++ b/libs/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/libs/guessit/rules/common/expected.py b/libs/guessit/rules/common/expected.py index eae562a2d..19f2f8874 100644 --- a/libs/guessit/rules/common/expected.py +++ b/libs/guessit/rules/common/expected.py @@ -3,7 +3,7 @@ """ Expected property factory """ -import re +from rebulk.remodule import re from rebulk import Rebulk from rebulk.utils import find_all diff --git a/libs/guessit/rules/common/quantity.py b/libs/guessit/rules/common/quantity.py index bbd41fbb9..8d3f21d4a 100644 --- a/libs/guessit/rules/common/quantity.py +++ b/libs/guessit/rules/common/quantity.py @@ -3,10 +3,9 @@ """ Quantities: Size """ -import re from abc import abstractmethod -import six +from rebulk.remodule import re from ..common import seps @@ -50,7 +49,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/libs/guessit/rules/markers/groups.py b/libs/guessit/rules/markers/groups.py index 4716d15d7..ac66f0443 100644 --- a/libs/guessit/rules/markers/groups.py +++ b/libs/guessit/rules/markers/groups.py @@ -5,6 +5,7 @@ Groups markers (...), [...] and {...} """ from rebulk import Rebulk +from ...options import ConfigurationException def groups(config): """ @@ -21,6 +22,9 @@ def groups(config): starting = config['starting'] ending = config['ending'] + if len(starting) != len(ending): + raise ConfigurationException("Starting and ending groups must have the same length") + def mark_groups(input_string): """ Functional pattern to mark groups (...), [...] and {...}. @@ -28,7 +32,7 @@ def groups(config): :param input_string: :return: """ - openings = ([], [], []) + openings = ([], ) * len(starting) i = 0 ret = [] diff --git a/libs/guessit/rules/processors.py b/libs/guessit/rules/processors.py index 5b018140c..069e75d2d 100644 --- a/libs/guessit/rules/processors.py +++ b/libs/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/libs/guessit/rules/properties/audio_codec.py b/libs/guessit/rules/properties/audio_codec.py index 815caff99..0aa7d31bc 100644 --- a/libs/guessit/rules/properties/audio_codec.py +++ b/libs/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/libs/guessit/rules/properties/bit_rate.py b/libs/guessit/rules/properties/bit_rate.py index d279c9f1c..640a6a2e4 100644 --- a/libs/guessit/rules/properties/bit_rate.py +++ b/libs/guessit/rules/properties/bit_rate.py @@ -3,7 +3,7 @@ """ video_bit_rate and audio_bit_rate properties """ -import re +from rebulk.remodule import re from rebulk import Rebulk from rebulk.rules import Rule, RemoveMatch, RenameMatch diff --git a/libs/guessit/rules/properties/episode_title.py b/libs/guessit/rules/properties/episode_title.py index ece8921d2..2c4fab669 100644 --- a/libs/guessit/rules/properties/episode_title.py +++ b/libs/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/libs/guessit/rules/properties/episodes.py b/libs/guessit/rules/properties/episodes.py index 345c785de..7aa2245ae 100644 --- a/libs/guessit/rules/properties/episodes.py +++ b/libs/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/libs/guessit/rules/properties/language.py b/libs/guessit/rules/properties/language.py index 3f83bc344..c1f9e6a17 100644 --- a/libs/guessit/rules/properties/language.py +++ b/libs/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/libs/guessit/rules/properties/other.py b/libs/guessit/rules/properties/other.py index c7dc9a88e..bfcbe680f 100644 --- a/libs/guessit/rules/properties/other.py +++ b/libs/guessit/rules/properties/other.py @@ -86,7 +86,7 @@ def other(config): # pylint:disable=unused-argument,too-many-statements rebulk.regex('(HD)(?P<another>Rip)', value={'other': 'HD', 'another': 'Rip'}, private_parent=True, children=True, validator={'__parent__': seps_surround}, validate_all=True) - for value in ('Screener', 'Remux', 'PAL', 'SECAM', 'NTSC', 'XXX'): + for value in ('Screener', 'Remux', 'Hybrid', 'PAL', 'SECAM', 'NTSC', 'XXX'): rebulk.string(value, value=value) rebulk.string('3D', value='3D', tags='has-neighbor') diff --git a/libs/guessit/rules/properties/release_group.py b/libs/guessit/rules/properties/release_group.py index ecff808b4..09e845f56 100644 --- a/libs/guessit/rules/properties/release_group.py +++ b/libs/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 @@ -321,7 +321,6 @@ class AnimeReleaseGroup(Rule): for filepart in marker_sorted(matches.markers.named('path'), matches): - # pylint:disable=bad-continuation empty_group = matches.markers.range(filepart.start, filepart.end, lambda marker: (marker.name == 'group' diff --git a/libs/guessit/rules/properties/screen_size.py b/libs/guessit/rules/properties/screen_size.py index 77d5d0521..966fc3c1d 100644 --- a/libs/guessit/rules/properties/screen_size.py +++ b/libs/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/libs/guessit/rules/properties/size.py b/libs/guessit/rules/properties/size.py index c61580c04..0a0970020 100644 --- a/libs/guessit/rules/properties/size.py +++ b/libs/guessit/rules/properties/size.py @@ -3,7 +3,7 @@ """ size property """ -import re +from rebulk.remodule import re from rebulk import Rebulk diff --git a/libs/guessit/rules/properties/streaming_service.py b/libs/guessit/rules/properties/streaming_service.py index f467f20a6..b27fc4401 100644 --- a/libs/guessit/rules/properties/streaming_service.py +++ b/libs/guessit/rules/properties/streaming_service.py @@ -3,7 +3,7 @@ """ streaming_service property """ -import re +from rebulk.remodule import re from rebulk import Rebulk from rebulk.rules import Rule, RemoveMatch @@ -25,13 +25,22 @@ def streaming_service(config): # pylint: disable=too-many-statements,unused-arg rebulk = rebulk.string_defaults(ignore_case=True).regex_defaults(flags=re.IGNORECASE, abbreviations=[dash]) rebulk.defaults(name='streaming_service', tags=['source-prefix']) + regex_prefix = 're:' + for value, items in config.items(): patterns = items if isinstance(items, list) else [items] for pattern in patterns: - if pattern.startswith('re:'): - rebulk.regex(pattern, value=value) + if isinstance(pattern, dict): + pattern_value = pattern.pop('pattern') + kwargs = pattern + pattern = pattern_value + else: + kwargs = {} + regex = kwargs.pop('regex', False) + if regex or pattern.startswith(regex_prefix): + rebulk.regex(pattern[len(regex_prefix):], value=value, **kwargs) else: - rebulk.string(pattern, value=value) + rebulk.string(pattern, value=value, **kwargs) rebulk.rules(ValidateStreamingService) diff --git a/libs/guessit/rules/properties/title.py b/libs/guessit/rules/properties/title.py index 0d2630167..2a065cb04 100644 --- a/libs/guessit/rules/properties/title.py +++ b/libs/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/libs/guessit/rules/properties/website.py b/libs/guessit/rules/properties/website.py index c19653117..96bed6400 100644 --- a/libs/guessit/rules/properties/website.py +++ b/libs/guessit/rules/properties/website.py @@ -27,7 +27,7 @@ def website(config): rebulk = rebulk.regex_defaults(flags=re.IGNORECASE).string_defaults(ignore_case=True) rebulk.defaults(name="website") - with resource_stream('guessit', 'tlds-alpha-by-domain.txt') as tld_file: + with resource_stream('guessit', 'data/tlds-alpha-by-domain.txt') as tld_file: tlds = [ tld.strip().decode('utf-8') for tld in tld_file.readlines() diff --git a/libs/guessit/test/movies.yml b/libs/guessit/test/movies.yml index a534ca0f2..6b503d13a 100644 --- a/libs/guessit/test/movies.yml +++ b/libs/guessit/test/movies.yml @@ -1752,6 +1752,7 @@ year: 2018 other: - 3D + - Hybrid - Proper - Remux proper_count: 1 diff --git a/libs/guessit/test/rules/other.yml b/libs/guessit/test/rules/other.yml index 447f1787d..0ae0c990f 100644 --- a/libs/guessit/test/rules/other.yml +++ b/libs/guessit/test/rules/other.yml @@ -80,6 +80,9 @@ ? Remux : other: Remux +? Hybrid +: other: Hybrid + ? 3D.2019 : other: 3D diff --git a/libs/guessit/test/streaming_services.yaml b/libs/guessit/test/streaming_services.yaml index adf52e715..227d72d75 100644 --- a/libs/guessit/test/streaming_services.yaml +++ b/libs/guessit/test/streaming_services.yaml @@ -577,13 +577,13 @@ release_group: BTW type: episode -# Streaming service: RTÉ One +# Streaming service: RTE One ? Show.Name.S10E01.576p.RTE.WEBRip.AAC2.0.H.264-RTN : title: Show Name season: 10 episode: 1 screen_size: 576p - streaming_service: RTÉ One + streaming_service: RTE One source: Web other: Rip audio_codec: AAC @@ -818,7 +818,6 @@ episode: 0 episode_details: Pilot episode_title: Pilot - language: zh other: - Proper - Rip @@ -862,7 +861,6 @@ ? What.The.Fuck.France.S01E01.Le.doublage.CNLP.WEBRip.AAC2.0.x264-TURTLE : audio_channels: '2.0' audio_codec: AAC - country: FR episode: 1 episode_title: Le doublage other: Rip @@ -870,7 +868,7 @@ season: 1 source: Web streaming_service: Canal+ - title: What The Fuck + title: What The Fuck France type: episode video_codec: H.264 @@ -943,14 +941,13 @@ ? The.Amazing.Race.Canada.S03.720p.CTV.WEBRip.AAC2.0.H.264-BTW : audio_channels: '2.0' audio_codec: AAC - country: CA other: Rip release_group: BTW screen_size: 720p season: 3 source: Web streaming_service: CTV - title: The Amazing Race + title: The Amazing Race Canada type: episode video_codec: H.264 @@ -1240,13 +1237,12 @@ ? Big.Brother.Canada.S05.GLBL.WEBRip.AAC2.0.H.264-RTN : audio_channels: '2.0' audio_codec: AAC - country: CA other: Rip release_group: RTN season: 5 source: Web streaming_service: Global - title: Big Brother + title: Big Brother Canada type: episode video_codec: H.264 @@ -1330,7 +1326,6 @@ ? Handmade.in.Japan.S01E01.720p.iP.WEBRip.AAC2.0.H.264-SUP : audio_channels: '2.0' audio_codec: AAC - country: JP episode: 1 other: Rip release_group: SUP @@ -1338,7 +1333,7 @@ season: 1 source: Web streaming_service: BBC iPlayer - title: Handmade in + title: Handmade in Japan type: episode video_codec: H.264 @@ -1463,9 +1458,8 @@ ? Bunsen.is.a.Beast.S01E23.Guinea.Some.Lovin.1080p.NICK.WEBRip.AAC2.0.x264-TVSmash : audio_channels: '2.0' audio_codec: AAC - country: GN episode: 23 - episode_title: Some Lovin + episode_title: Guinea Some Lovin other: Rip release_group: TVSmash screen_size: 1080p @@ -1538,13 +1532,14 @@ episode_title: The Masquerade other: Rip part: 2 - release_group: VP9-BTW + release_group: BTW screen_size: 1080p season: 2 source: Web streaming_service: YouTube Red title: Escape The Night type: episode + video_codec: VP9 ? Escape.The.Night.S02E02.The.Masquerade.Part.II.2160p.RED.WEBRip.AAC5.1.VP9-BTW : audio_channels: '5.1' @@ -1553,13 +1548,14 @@ episode_title: The Masquerade other: Rip part: 2 - release_group: VP9-BTW + release_group: BTW screen_size: 2160p season: 2 source: Web streaming_service: YouTube Red title: Escape The Night type: episode + video_codec: VP9 ? Escape.The.Night.S02E02.The.Masquerade.Part.II.720p.RED.WEBRip.AAC5.1.VP9-BTW : audio_channels: '5.1' @@ -1568,13 +1564,14 @@ episode_title: The Masquerade other: Rip part: 2 - release_group: VP9-BTW + release_group: BTW screen_size: 720p season: 2 source: Web streaming_service: YouTube Red title: Escape The Night type: episode + video_codec: VP9 ? The.Family.Law.S02E01.720p.SBS.WEB-DL.AAC2.0.H.264-BTN : audio_channels: '2.0' @@ -1892,7 +1889,7 @@ season: 1 source: Web streaming_service: Vimeo - title: '555' + # title: '555' type: episode video_codec: H.264 diff --git a/libs/guessit/test/test_api.py b/libs/guessit/test/test_api.py index 391dbced8..542292580 100644 --- a/libs/guessit/test/test_api.py +++ b/libs/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) [email protected](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/libs/guessit/test/test_api_unicode_literals.py b/libs/guessit/test/test_api_unicode_literals.py index 826f7cd16..79bbbca15 100644 --- a/libs/guessit/test/test_api_unicode_literals.py +++ b/libs/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/libs/guessit/test/test_main.py b/libs/guessit/test/test_main.py index cbdba7aa4..30de4ccae 100644 --- a/libs/guessit/test/test_main.py +++ b/libs/guessit/test/test_main.py @@ -1,16 +1,25 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # pylint: disable=no-self-use, pointless-statement, missing-docstring, invalid-name - +import json import os +import sys import pytest +from _pytest.capture import CaptureFixture from ..__main__ import main __location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) +# Prevent output from spamming the console [email protected](scope="function", autouse=True) +def no_stdout(monkeypatch): + with open(os.devnull, "w") as f: + monkeypatch.setattr(sys, "stdout", f) + yield + def test_main_no_args(): main([]) @@ -24,7 +33,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(): @@ -70,3 +79,22 @@ def test_main_help(): def test_main_version(): main(['--version']) + + +def test_json_output_input_string(capsys: CaptureFixture): + main(['--json', '--output-input-string', 'test.avi']) + + outerr = capsys.readouterr() + data = json.loads(outerr.out) + + assert 'input_string' in data + assert data['input_string'] == 'test.avi' + + +def test_json_no_output_input_string(capsys: CaptureFixture): + main(['--json', 'test.avi']) + + outerr = capsys.readouterr() + data = json.loads(outerr.out) + + assert 'input_string' not in data diff --git a/libs/guessit/test/test_yml.py b/libs/guessit/test/test_yml.py index 040796dea..d7cb4b5e0 100644 --- a/libs/guessit/test/test_yml.py +++ b/libs/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 @@ -53,16 +52,16 @@ class EntryResult(object): if self.ok: return self.string + ': OK!' if self.warning: - return '%s%s: WARNING! (valid=%i, extra=%i)' % ('-' if self.negates else '', self.string, len(self.valid), - len(self.extra)) + return '%s%s: WARNING! (valid=%i, extra=%s)' % ('-' if self.negates else '', self.string, len(self.valid), + self.extra) if self.error: - return '%s%s: ERROR! (valid=%i, missing=%i, different=%i, extra=%i, others=%i)' % \ - ('-' if self.negates else '', self.string, len(self.valid), len(self.missing), len(self.different), - len(self.extra), len(self.others)) + return '%s%s: ERROR! (valid=%i, extra=%s, missing=%s, different=%s, others=%s)' % \ + ('-' if self.negates else '', self.string, len(self.valid), self.extra, self.missing, + self.different, self.others) - return '%s%s: UNKOWN! (valid=%i, missing=%i, different=%i, extra=%i, others=%i)' % \ - ('-' if self.negates else '', self.string, len(self.valid), len(self.missing), len(self.different), - len(self.extra), len(self.others)) + return '%s%s: UNKOWN! (valid=%i, extra=%s, missing=%s, different=%s, others=%s)' % \ + ('-' if self.negates else '', self.string, len(self.valid), self.extra, self.missing, self.different, + self.others) @property def details(self): @@ -110,7 +109,7 @@ def files_and_ids(predicate=None): for filename in filenames: name, ext = os.path.splitext(filename) filepath = os.path.join(dirpath_rel, filename) - if ext == '.yml' and (not predicate or predicate(filepath)): + if ext in ['.yml', '.yaml'] and (not predicate or predicate(filepath)): files.append(filepath) ids.append(os.path.join(dirpath_rel, name)) @@ -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/libs/guessit/yamlutils.py b/libs/guessit/yamlutils.py index d04be641b..f038a99bc 100644 --- a/libs/guessit/yamlutils.py +++ b/libs/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/libs/rebulk/__init__.py b/libs/rebulk/__init__.py index 93d5e4774..360731bc5 100644 --- a/libs/rebulk/__init__.py +++ b/libs/rebulk/__init__.py @@ -7,4 +7,4 @@ Define simple search patterns in bulk to perform advanced matching on any string from .rebulk import Rebulk from .rules import Rule, CustomRule, AppendMatch, RemoveMatch, RenameMatch, AppendTags, RemoveTags from .processors import ConflictSolver, PrivateRemover, POST_PROCESS, PRE_PROCESS -from .pattern import REGEX_AVAILABLE +from .pattern import REGEX_ENABLED diff --git a/libs/rebulk/__version__.py b/libs/rebulk/__version__.py index 939c554ca..1312bb689 100644 --- a/libs/rebulk/__version__.py +++ b/libs/rebulk/__version__.py @@ -4,4 +4,4 @@ Version module """ # pragma: no cover -__version__ = '2.0.1' +__version__ = '3.0.1' diff --git a/libs/rebulk/builder.py b/libs/rebulk/builder.py index c91420aa3..c0053f2ac 100644 --- a/libs/rebulk/builder.py +++ b/libs/rebulk/builder.py @@ -7,16 +7,13 @@ from abc import ABCMeta, abstractmethod from copy import deepcopy from logging import getLogger -from six import add_metaclass - from .loose import set_defaults from .pattern import RePattern, StringPattern, FunctionalPattern log = getLogger(__name__).log -@add_metaclass(ABCMeta) -class Builder(object): +class Builder(metaclass=ABCMeta): """ Base builder class for patterns """ @@ -147,7 +144,7 @@ class Builder(object): :return: :rtype: """ - from .chain import Chain + from .chain import Chain # pylint:disable=import-outside-toplevel set_defaults(self._chain_defaults, kwargs) set_defaults(self._defaults, kwargs) chain = Chain(self, **kwargs) diff --git a/libs/rebulk/chain.py b/libs/rebulk/chain.py index ba31ec9a5..f2ed66c01 100644 --- a/libs/rebulk/chain.py +++ b/libs/rebulk/chain.py @@ -125,7 +125,7 @@ class Chain(Pattern, Builder): :rtype: """ # pylint: disable=too-many-locals - ret = super(Chain, self)._process_match(match, match_index, child=child) + ret = super()._process_match(match, match_index, child=child) if ret: return True @@ -144,7 +144,7 @@ class Chain(Pattern, Builder): for last_match in last_matches: match.children.remove(last_match) match.end = match.children[-1].end if match.children else match.start - ret = super(Chain, self)._process_match(match, match_index, child=child) + ret = super()._process_match(match, match_index, child=child) if ret: return True diff --git a/libs/rebulk/introspector.py b/libs/rebulk/introspector.py index bfefcb757..f4a4f70a4 100644 --- a/libs/rebulk/introspector.py +++ b/libs/rebulk/introspector.py @@ -6,13 +6,11 @@ Introspect rebulk object to retrieve capabilities. from abc import ABCMeta, abstractmethod from collections import defaultdict -import six from .pattern import StringPattern, RePattern, FunctionalPattern from .utils import extend_safe [email protected]_metaclass(ABCMeta) -class Description(object): +class Description(metaclass=ABCMeta): """ Abstract class for a description. """ diff --git a/libs/rebulk/match.py b/libs/rebulk/match.py index d8e72df42..b3f84c4f4 100644 --- a/libs/rebulk/match.py +++ b/libs/rebulk/match.py @@ -15,7 +15,6 @@ try: from collections import OrderedDict # pylint:disable=ungrouped-imports except ImportError: # pragma: no cover from ordereddict import OrderedDict # pylint:disable=import-error -import six from .loose import ensure_list, filter_index from .utils import is_iterable @@ -28,7 +27,7 @@ class MatchesDict(OrderedDict): """ def __init__(self): - super(MatchesDict, self).__init__() + super().__init__() self.matches = defaultdict(list) self.values_list = defaultdict(list) @@ -67,7 +66,7 @@ class _BaseMatches(MutableSequence): def _start_dict(self): if self.__start_dict is None: self.__start_dict = defaultdict(_BaseMatches._base) - for start, values in itertools.groupby([m for m in self._delegate], lambda item: item.start): + for start, values in itertools.groupby(list(self._delegate), lambda item: item.start): _BaseMatches._base_extend(self.__start_dict[start], values) return self.__start_dict @@ -76,7 +75,7 @@ class _BaseMatches(MutableSequence): def _end_dict(self): if self.__end_dict is None: self.__end_dict = defaultdict(_BaseMatches._base) - for start, values in itertools.groupby([m for m in self._delegate], lambda item: item.end): + for start, values in itertools.groupby(list(self._delegate), lambda item: item.end): _BaseMatches._base_extend(self.__end_dict[start], values) return self.__end_dict @@ -534,13 +533,6 @@ class _BaseMatches(MutableSequence): ret[match.name] = value return ret - if six.PY2: # pragma: no cover - def clear(self): - """ - Python 3 backport - """ - del self[:] - def __len__(self): return len(self._delegate) @@ -583,11 +575,11 @@ class Matches(_BaseMatches): def __init__(self, matches=None, input_string=None): self.markers = Markers(input_string=input_string) - super(Matches, self).__init__(matches=matches, input_string=input_string) + super().__init__(matches=matches, input_string=input_string) def _add_match(self, match): assert not match.marker, "A marker match should not be added to <Matches> object" - super(Matches, self)._add_match(match) + super()._add_match(match) class Markers(_BaseMatches): @@ -596,11 +588,11 @@ class Markers(_BaseMatches): """ def __init__(self, matches=None, input_string=None): - super(Markers, self).__init__(matches=None, input_string=input_string) + super().__init__(matches=None, input_string=input_string) def _add_match(self, match): assert match.marker, "A non-marker match should not be added to <Markers> object" - super(Markers, self)._add_match(match) + super()._add_match(match) class Match(object): diff --git a/libs/rebulk/pattern.py b/libs/rebulk/pattern.py index beb8b2731..890ad4a67 100644 --- a/libs/rebulk/pattern.py +++ b/libs/rebulk/pattern.py @@ -7,19 +7,16 @@ Abstract pattern class definition along with various implementations (regexp, st from abc import ABCMeta, abstractmethod, abstractproperty -import six - from . import debug from .formatters import default_formatter from .loose import call, ensure_list, ensure_dict from .match import Match -from .remodule import re, REGEX_AVAILABLE +from .remodule import re, REGEX_ENABLED from .utils import find_all, is_iterable, get_first_defined from .validators import allways_true [email protected]_metaclass(ABCMeta) -class BasePattern(object): +class BasePattern(metaclass=ABCMeta): """ Base class for Pattern like objects """ @@ -41,8 +38,7 @@ class BasePattern(object): pass [email protected]_metaclass(ABCMeta) -class Pattern(BasePattern): +class Pattern(BasePattern, metaclass=ABCMeta): """ Definition of a particular pattern to search for. """ @@ -396,7 +392,7 @@ class StringPattern(Pattern): """ def __init__(self, *patterns, **kwargs): - super(StringPattern, self).__init__(**kwargs) + super().__init__(**kwargs) self._patterns = patterns self._kwargs = kwargs self._match_kwargs = filter_match_kwargs(kwargs) @@ -422,11 +418,11 @@ class RePattern(Pattern): """ def __init__(self, *patterns, **kwargs): - super(RePattern, self).__init__(**kwargs) - self.repeated_captures = REGEX_AVAILABLE + super().__init__(**kwargs) + self.repeated_captures = REGEX_ENABLED if 'repeated_captures' in kwargs: self.repeated_captures = kwargs.get('repeated_captures') - if self.repeated_captures and not REGEX_AVAILABLE: # pragma: no cover + if self.repeated_captures and not REGEX_ENABLED: # pragma: no cover raise NotImplementedError("repeated_capture is available only with regex module.") self.abbreviations = kwargs.get('abbreviations', []) self._kwargs = kwargs @@ -434,7 +430,7 @@ class RePattern(Pattern): self._children_match_kwargs = filter_match_kwargs(kwargs, children=True) self._patterns = [] for pattern in patterns: - if isinstance(pattern, six.string_types): + if isinstance(pattern, str): if self.abbreviations and pattern: for key, replacement in self.abbreviations: pattern = pattern.replace(key, replacement) @@ -494,7 +490,7 @@ class FunctionalPattern(Pattern): """ def __init__(self, *patterns, **kwargs): - super(FunctionalPattern, self).__init__(**kwargs) + super().__init__(**kwargs) self._patterns = patterns self._kwargs = kwargs self._match_kwargs = filter_match_kwargs(kwargs) diff --git a/libs/rebulk/rebulk.py b/libs/rebulk/rebulk.py index a6a0fd2fb..972b92abe 100644 --- a/libs/rebulk/rebulk.py +++ b/libs/rebulk/rebulk.py @@ -53,7 +53,7 @@ class Rebulk(Builder): :return: :rtype: """ - super(Rebulk, self).__init__() + super().__init__() if not callable(disabled): self.disabled = lambda context: disabled else: diff --git a/libs/rebulk/remodule.py b/libs/rebulk/remodule.py index d1d68d194..7bdd42d65 100644 --- a/libs/rebulk/remodule.py +++ b/libs/rebulk/remodule.py @@ -5,13 +5,17 @@ Uniform re module """ # pylint: disable-all import os +import logging -REGEX_AVAILABLE = False -if os.environ.get('REGEX_DISABLED') in ["1", "true", "True", "Y"]: - import re -else: +log = logging.getLogger(__name__).log + +REGEX_ENABLED = False +if os.environ.get('REBULK_REGEX_ENABLED') in ["1", "true", "True", "Y"]: try: import regex as re - REGEX_AVAILABLE = True + REGEX_ENABLED = True except ImportError: + log.warning('regex module is not available. Unset REBULK_REGEX_ENABLED environment variable, or install regex module to enabled it.') import re +else: + import re diff --git a/libs/rebulk/rules.py b/libs/rebulk/rules.py index 2514904f4..264792a65 100644 --- a/libs/rebulk/rules.py +++ b/libs/rebulk/rules.py @@ -8,7 +8,6 @@ import inspect from itertools import groupby from logging import getLogger -import six from .utils import is_iterable from .toposort import toposort @@ -18,8 +17,7 @@ from . import debug log = getLogger(__name__).log [email protected]_metaclass(ABCMeta) -class Consequence(object): +class Consequence(metaclass=ABCMeta): """ Definition of a consequence to apply. """ @@ -40,8 +38,7 @@ class Consequence(object): pass [email protected]_metaclass(ABCMeta) -class Condition(object): +class Condition(metaclass=ABCMeta): """ Definition of a condition to check. """ @@ -60,8 +57,7 @@ class Condition(object): pass [email protected]_metaclass(ABCMeta) -class CustomRule(Condition, Consequence): +class CustomRule(Condition, Consequence, metaclass=ABCMeta): """ Definition of a rule to apply """ @@ -243,7 +239,7 @@ class Rules(list): """ def __init__(self, *rules): - super(Rules, self).__init__() + super().__init__() self.load(*rules) def load(self, *rules): diff --git a/libs/rebulk/test/test_match.py b/libs/rebulk/test/test_match.py index 8750733a5..2359149b2 100644 --- a/libs/rebulk/test/test_match.py +++ b/libs/rebulk/test/test_match.py @@ -3,7 +3,6 @@ # pylint: disable=no-self-use, pointless-statement, missing-docstring, unneeded-not, len-as-condition import pytest -import six from ..match import Match, Matches from ..pattern import StringPattern, RePattern @@ -72,23 +71,18 @@ class TestMatchClass(object): assert match2 > match1 assert match2 >= match1 - if six.PY3: - with pytest.raises(TypeError): - match1 < other + with pytest.raises(TypeError): + match1 < other - with pytest.raises(TypeError): - match1 <= other + with pytest.raises(TypeError): + match1 <= other - with pytest.raises(TypeError): - match1 > other + with pytest.raises(TypeError): + match1 > other + + with pytest.raises(TypeError): + match1 >= other - with pytest.raises(TypeError): - match1 >= other - else: - assert match1 < other - assert match1 <= other - assert not match1 > other - assert not match1 >= other def test_value(self): match1 = Match(1, 3) diff --git a/libs/rebulk/test/test_pattern.py b/libs/rebulk/test/test_pattern.py index beee17042..b80b034c3 100644 --- a/libs/rebulk/test/test_pattern.py +++ b/libs/rebulk/test/test_pattern.py @@ -1,11 +1,11 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# pylint: disable=no-self-use, pointless-statement, missing-docstring, unbalanced-tuple-unpacking, len-as-condition +# pylint: disable=no-self-use, pointless-statement, missing-docstring, unbalanced-tuple-unpacking, len-as-condition, no-member import re import pytest -from ..pattern import StringPattern, RePattern, FunctionalPattern, REGEX_AVAILABLE +from ..pattern import StringPattern, RePattern, FunctionalPattern, REGEX_ENABLED from ..match import Match class TestStringPattern(object): @@ -706,7 +706,7 @@ class TestFormatter(object): assert len(matches) == 1 match = matches[0] - if REGEX_AVAILABLE: + if REGEX_ENABLED: assert len(match.children) == 5 assert [child.value for child in match.children] == ["02", "03", "04", "05", "06"] else: diff --git a/libs/rebulk/toposort.py b/libs/rebulk/toposort.py index 2bcba9ae6..9f3a7310c 100644 --- a/libs/rebulk/toposort.py +++ b/libs/rebulk/toposort.py @@ -22,7 +22,7 @@ from functools import reduce class CyclicDependency(ValueError): def __init__(self, cyclic): s = 'Cyclic dependencies exist among these items: {0}'.format(', '.join(repr(x) for x in cyclic.items())) - super(CyclicDependency, self).__init__(s) + super().__init__(s) self.cyclic = cyclic diff --git a/libs/version.txt b/libs/version.txt index 31a365c47..cbf33f1b2 100644 --- a/libs/version.txt +++ b/libs/version.txt @@ -12,7 +12,7 @@ ffsubsync=2020-08-04 Flask=1.1.1 gevent-websocker=0.10.1 gitpython=2.1.9 -guessit=3.1.1 +guessit=3.3.1 guess_language-spirit=0.5.3 Js2Py=0.63 <-- modified: manually merged from upstream: https://github.com/PiotrDabkowski/Js2Py/pull/192/files knowit=0.3.0-dev @@ -22,7 +22,7 @@ pyga=2.6.1 pysrt=1.1.1 pytz=2018.4 rarfile=3.0 -rebulk=2.0.1 +rebulk=3.0.1 requests=2.18.4 semver=2.13.0 SimpleConfigParser=0.1.0 <-- modified version: do not update!!! |