Skip to content

Commit

Permalink
Merge pull request #1072 from getzze/drop-old-python
Browse files Browse the repository at this point in the history
Drop old python versions (<3.8)
  • Loading branch information
getzze authored May 14, 2024
2 parents 348ee7b + 2d53d3d commit 9a7e2be
Show file tree
Hide file tree
Showing 58 changed files with 145 additions and 157 deletions.
1 change: 0 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# subliminal documentation build configuration file, created by
# sphinx-quickstart on Sat Jul 11 00:40:28 2015.
Expand Down
14 changes: 2 additions & 12 deletions docs/conftest.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
# -*- coding: utf-8 -*-
import os
import sys
from unittest.mock import Mock

import pytest
try:
from unittest.mock import Mock
except ImportError:
from mock import Mock
from vcr import VCR

from subliminal.cache import region
Expand All @@ -28,13 +24,7 @@ def chdir(tmpdir, monkeypatch):
monkeypatch.chdir(str(tmpdir))


@pytest.yield_fixture(autouse=True)
@pytest.fixture(autouse=True)
def use_cassette(request):
with vcr.use_cassette('test_' + request.fspath.purebasename):
yield


@pytest.fixture(autouse=True)
def skip_python_2():
if sys.version_info < (3, 0):
return pytest.skip('Requires python 3')
2 changes: 1 addition & 1 deletion pytest.ini
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[pytest]
norecursedirs = build dist env .tox .eggs
#addopts = --pep8 --flakes --doctest-glob='*.rst'
addopts = --doctest-glob='*.rst'
pep8maxlinelength = 120
pep8ignore =
docs/conf.py ALL
Expand Down
19 changes: 6 additions & 13 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import io
import os
import re
Expand Down Expand Up @@ -28,15 +27,10 @@ def find_version(*file_paths):
setup_requirements = ['pytest-runner'] if {'pytest', 'test', 'ptr'}.intersection(sys.argv) else []

install_requirements = ['guessit>=3.0.0', 'babelfish>=0.5.2', 'enzyme>=0.4.1', 'beautifulsoup4>=4.4.0',
'requests>=2.0', 'requests_cache', 'click>=4.0', 'dogpile.cache>=0.6.0',
'chardet>=2.3.0', 'srt>=3.5.0', 'six>=1.9.0', 'appdirs>=1.3', 'rarfile>=2.7',
'pytz>=2012c', 'stevedore>=1.20.0', 'setuptools']
if sys.version_info < (3, 2):
install_requirements.append('futures>=3.0')
'rebulk>=3.0', 'requests>=2.0', 'requests_cache', 'click>=4.0', 'dogpile.cache>=1.0',
'stevedore>=3.0', 'chardet>=5.0', 'srt>=3.5.0', 'appdirs>=1.3', 'rarfile>=2.7']

test_requirements = ['sympy', 'vcrpy>=1.6.1', 'pytest', 'pytest-pep8', 'pytest-flakes', 'pytest-cov']
if sys.version_info < (3, 3):
test_requirements.append('mock')

dev_requirements = ['tox', 'sphinx', 'sphinx_rtd_theme', 'sphinxcontrib-programoutput', 'wheel']

Expand All @@ -57,13 +51,12 @@ def find_version(*file_paths):
'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: Multimedia :: Video'
],
Expand Down
3 changes: 2 additions & 1 deletion subliminal/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import annotations

__title__ = 'subliminal'
__version__ = '2.1.1-dev'
__short_version__ = '.'.join(__version__.split('.')[:2])
Expand Down
19 changes: 5 additions & 14 deletions subliminal/cache.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from __future__ import annotations

import datetime

import six
from dogpile.cache import make_region
from dogpile.cache.util import function_key_generator

Expand All @@ -16,18 +16,9 @@


def _to_native_str(value):
if six.PY2:
# In Python 2, the native string type is bytes
if isinstance(value, six.text_type): # unicode for Python 2
return value.encode('utf-8')
else:
return six.binary_type(value)
else:
# In Python 3, the native string type is unicode
if isinstance(value, six.binary_type): # bytes for Python 3
return value.decode('utf-8')
else:
return six.text_type(value)
if isinstance(value, bytes):
return value.decode('utf-8')
return str(value)


def to_native_str_key_generator(namespace, fn, to_str=_to_native_str):
Expand Down
10 changes: 5 additions & 5 deletions subliminal/cli.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
"""
Subliminal uses `click <http://click.pocoo.org>`_ to provide a powerful :abbr:`CLI (command-line interface)`.
"""
from __future__ import division
from __future__ import annotations

from collections import defaultdict
from datetime import timedelta
import glob
Expand All @@ -17,7 +17,7 @@
import click
from dogpile.cache.backends.file import AbstractFileLock
from dogpile.util.readwrite_lock import ReadWriteMutex
from six.moves import configparser
from configparser import ConfigParser

from subliminal import (AsyncProviderPool, Episode, Movie, Video, __version__, check_video, compute_score, get_scores,
provider_manager, refine, refiner_manager, region, save_subtitles, scan_video, scan_videos)
Expand Down Expand Up @@ -46,7 +46,7 @@ def release_write_lock(self):
return self.mutex.release_write_lock()


class Config(object):
class Config:
"""A :class:`~configparser.ConfigParser` wrapper to store configuration.
Interaction with the configuration is done with the properties.
Expand All @@ -59,7 +59,7 @@ def __init__(self, path):
self.path = path

#: The underlying configuration object
self.config = configparser.SafeConfigParser()
self.config = ConfigParser()
self.config.add_section('general')
self.config.set('general', 'languages', json.dumps(['en']))
self.config.set('general', 'providers', json.dumps(sorted([p.name for p in provider_manager])))
Expand Down
3 changes: 2 additions & 1 deletion subliminal/converters/addic7ed.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import annotations

from babelfish import LanguageReverseConverter, language_converters


Expand Down
3 changes: 2 additions & 1 deletion subliminal/converters/opensubtitlescom.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import annotations

from babelfish import LanguageReverseConverter, language_converters


Expand Down
3 changes: 2 additions & 1 deletion subliminal/converters/shooter.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import annotations

from babelfish import LanguageReverseConverter

from ..exceptions import ConfigurationError
Expand Down
3 changes: 2 additions & 1 deletion subliminal/converters/thesubdb.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import annotations

from babelfish import LanguageReverseConverter

from ..exceptions import ConfigurationError
Expand Down
3 changes: 2 additions & 1 deletion subliminal/converters/tvsubtitles.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import annotations

from babelfish import LanguageReverseConverter, language_converters


Expand Down
23 changes: 12 additions & 11 deletions subliminal/core.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
from __future__ import annotations

from collections import defaultdict
from concurrent.futures import ThreadPoolExecutor
from datetime import datetime
from datetime import datetime, timezone
import io
import itertools
import logging
Expand All @@ -25,7 +26,7 @@
logger = logging.getLogger(__name__)


class ProviderPool(object):
class ProviderPool:
"""A pool of providers with the same API as a single :class:`~subliminal.providers.Provider`.
It has a few extra features:
Expand Down Expand Up @@ -246,13 +247,13 @@ class AsyncProviderPool(ProviderPool):
"""
def __init__(self, max_workers=None, *args, **kwargs):
super(AsyncProviderPool, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)

#: Maximum number of threads to use
self.max_workers = max_workers or len(self.providers)

def list_subtitles_provider(self, provider, video, languages):
return provider, super(AsyncProviderPool, self).list_subtitles_provider(provider, video, languages)
return provider, super().list_subtitles_provider(provider, video, languages)

def list_subtitles(self, video, languages):
subtitles = []
Expand Down Expand Up @@ -492,12 +493,12 @@ def scan_videos(path, age=None, archives=True):

# skip old files
try:
file_age = datetime.utcfromtimestamp(os.path.getmtime(filepath))
file_age = datetime.fromtimestamp(os.path.getmtime(filepath), timezone.utc)
except ValueError:
logger.warning('Could not get age of file %r in %r', filename, dirpath)
continue
else:
if age and datetime.utcnow() - file_age > age:
if age and datetime.now(timezone.utc) - file_age > age:
logger.debug('Skipping old file %r in %r', filename, dirpath)
continue

Expand Down Expand Up @@ -535,7 +536,7 @@ def refine(video, episode_refiners=None, movie_refiners=None, refiner_configs=No
:param tuple movie_refiners: refiners to use for movies.
:param dict refiner_configs: refiner configuration as keyword arguments per refiner name to pass when
calling the refine method
:param \*\*kwargs: additional parameters for the :func:`~subliminal.refiners.refine` functions.
:param kwargs: additional parameters for the :func:`~subliminal.refiners.refine` functions.
"""
refiners = ()
Expand All @@ -562,7 +563,7 @@ def list_subtitles(videos, languages, pool_class=ProviderPool, **kwargs):
:type languages: set of :class:`~babelfish.language.Language`
:param pool_class: class to use as provider pool.
:type pool_class: :class:`ProviderPool`, :class:`AsyncProviderPool` or similar
:param \*\*kwargs: additional parameters for the provided `pool_class` constructor.
:param kwargs: additional parameters for the provided `pool_class` constructor.
:return: found subtitles per video.
:rtype: dict of :class:`~subliminal.video.Video` to list of :class:`~subliminal.subtitle.Subtitle`
Expand Down Expand Up @@ -599,7 +600,7 @@ def download_subtitles(subtitles, pool_class=ProviderPool, **kwargs):
:type subtitles: list of :class:`~subliminal.subtitle.Subtitle`
:param pool_class: class to use as provider pool.
:type pool_class: :class:`ProviderPool`, :class:`AsyncProviderPool` or similar
:param \*\*kwargs: additional parameters for the provided `pool_class` constructor.
:param kwargs: additional parameters for the provided `pool_class` constructor.
"""
with pool_class(**kwargs) as pool:
Expand All @@ -625,7 +626,7 @@ def download_best_subtitles(videos, languages, min_score=0, hearing_impaired=Fal
`hearing_impaired` as keyword argument and returns the score.
:param pool_class: class to use as provider pool.
:type pool_class: :class:`ProviderPool`, :class:`AsyncProviderPool` or similar
:param \*\*kwargs: additional parameters for the provided `pool_class` constructor.
:param kwargs: additional parameters for the provided `pool_class` constructor.
:return: downloaded subtitles per video.
:rtype: dict of :class:`~subliminal.video.Video` to list of :class:`~subliminal.subtitle.Subtitle`
Expand Down
6 changes: 5 additions & 1 deletion subliminal/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
class Error(Exception):
"""Base class for exceptions in subliminal."""
pass
Expand All @@ -9,6 +8,11 @@ class ProviderError(Error):
pass


class NotInitializedProviderError(ProviderError):
"""Exception raised by providers when not initialized."""
pass


class ConfigurationError(ProviderError):
"""Exception raised by providers when badly configured."""
pass
Expand Down
32 changes: 22 additions & 10 deletions subliminal/extensions.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# -*- coding: utf-8 -*-
from pkg_resources import EntryPoint
from __future__ import annotations

import re
from importlib.metadata import EntryPoint

from stevedore import ExtensionManager

Expand All @@ -17,31 +19,31 @@ class RegistrableExtensionManager(ExtensionManager):
:param str namespace: namespace argument for :class:~stevedore.extensions.ExtensionManager`.
:param list internal_extensions: internal extensions to use with entry point syntax.
:param \*\*kwargs: additional parameters for the :class:~stevedore.extensions.ExtensionManager` constructor.
:param kwargs: additional parameters for the :class:~stevedore.extensions.ExtensionManager` constructor.
"""
def __init__(self, namespace, internal_extensions, **kwargs):
#: Registered extensions with entry point syntax
self.registered_extensions = []

#: Internal extensions with entry point syntax
self.internal_extensions = internal_extensions
self.internal_extensions = list(internal_extensions)

super(RegistrableExtensionManager, self).__init__(namespace, **kwargs)
super().__init__(namespace, **kwargs)

def list_entry_points(self):
# copy of default extensions
eps = list(super(RegistrableExtensionManager, self).list_entry_points())
eps = list(super().list_entry_points())

# internal extensions
for iep in self.internal_extensions:
ep = EntryPoint.parse(iep)
ep = parse_entry_point(iep, self.namespace)
if ep.name not in [e.name for e in eps]:
eps.append(ep)

# registered extensions
for rep in self.registered_extensions:
ep = EntryPoint.parse(rep)
ep = parse_entry_point(rep, self.namespace)
if ep.name not in [e.name for e in eps]:
eps.append(ep)

Expand All @@ -57,7 +59,7 @@ def register(self, entry_point):
if entry_point in self.registered_extensions:
raise ValueError('Extension already registered')

ep = EntryPoint.parse(entry_point)
ep = parse_entry_point(entry_point, self.namespace)
if ep.name in self.names():
raise ValueError('An extension with the same name already exist')

Expand All @@ -76,7 +78,7 @@ def unregister(self, entry_point):
if entry_point not in self.registered_extensions:
raise ValueError('Extension not registered')

ep = EntryPoint.parse(entry_point)
ep = parse_entry_point(entry_point, self.namespace)
self.registered_extensions.remove(entry_point)
if self._extensions_by_name is not None:
del self._extensions_by_name[ep.name]
Expand All @@ -86,6 +88,16 @@ def unregister(self, entry_point):
break


def parse_entry_point(src: str, group: str) -> EntryPoint:
pattern = re.compile(r"\s*(?P<name>.+?)\s*=\s*(?P<value>.+)")
m = pattern.match(src)
if not m:
msg = "EntryPoint must be in the 'name = module:attrs' format"
raise ValueError(msg, src)
res = m.groupdict()
return EntryPoint(res["name"], res["value"], group)


#: Provider manager
provider_manager = RegistrableExtensionManager('subliminal.providers', [
'addic7ed = subliminal.providers.addic7ed:Addic7edProvider',
Expand Down
3 changes: 2 additions & 1 deletion subliminal/matches.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import annotations

from rebulk.loose import ensure_list

from .score import get_equivalent_release_groups, score_keys
Expand Down
Loading

0 comments on commit 9a7e2be

Please sign in to comment.