From 8c4c2dc1e3a1407241e5a50b2e32658cfe369a54 Mon Sep 17 00:00:00 2001 From: Trim21 Date: Mon, 10 Apr 2023 00:11:52 +0800 Subject: [PATCH] upgrade to transmission-rpc v4 (#3716) transmission: Update to use transmission-rpc >=4.0 --- Dockerfile | 4 +- dev-requirements-extras.txt | 2 +- dev-requirements.txt | 2 +- flexget/_version.py | 2 +- flexget/plugins/clients/transmission.py | 209 ++++++++++++++---------- poetry.lock | 39 ++--- pyproject.toml | 1 + requirements.txt | 2 +- 8 files changed, 138 insertions(+), 123 deletions(-) diff --git a/Dockerfile b/Dockerfile index b4523cd88e..a4cfa2d5ed 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,7 @@ COPY . /flexget RUN pip install -U pip && \ pip wheel -e /flexget && \ - pip wheel 'transmission-rpc>=3.0.0,<4.0.0' && \ + pip wheel 'transmission-rpc>=4.1.4,<5.0.0' && \ pip wheel deluge-client && \ pip wheel cloudscraper @@ -41,7 +41,7 @@ RUN pip install -U pip && \ --no-index \ -f /wheels \ FlexGet \ - 'transmission-rpc>=3.0.0,<4.0.0' \ + transmission-rpc \ deluge-client \ cloudscraper && \ rm -rf /wheels diff --git a/dev-requirements-extras.txt b/dev-requirements-extras.txt index 18666cc263..4a32117456 100644 --- a/dev-requirements-extras.txt +++ b/dev-requirements-extras.txt @@ -13,7 +13,7 @@ dogpile-cache==1.1.8 ; python_version >= "3.7" and python_version < "4.0" enzyme==0.4.1 ; python_version >= "3.7" and python_version < "4.0" guessit==3.5.0 ; python_version >= "3.7" and python_version < "4.0" idna==3.4 ; python_version >= "3.7" and python_version < "4" -importlib-metadata==5.1.0 ; python_version >= "3.7" and python_version < "3.8" +importlib-metadata==6.1.0 ; python_version >= "3.7" and python_version < "3.8" importlib-resources==5.10.1 ; python_version >= "3.7" and python_version < "3.9" jmespath==1.0.1 ; python_version >= "3.7" and python_version < "4.0" pbr==5.11.0 ; python_version >= "3.7" and python_version < "4.0" diff --git a/dev-requirements.txt b/dev-requirements.txt index b75745d0d4..9475817b48 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -27,7 +27,7 @@ gitpython==3.1.29 ; python_version >= "3.7" and python_version < "4.0" identify==2.5.9 ; python_version >= "3.7" and python_version < "4.0" idna==3.4 ; python_version >= "3.7" and python_version < "4" imagesize==1.4.1 ; python_version >= "3.7" and python_version < "4.0" -importlib-metadata==5.1.0 ; python_version >= "3.7" and python_version < "4.0" +importlib-metadata==6.1.0 ; python_version >= "3.7" and python_version < "4.0" iniconfig==1.1.1 ; python_version >= "3.7" and python_version < "4.0" isort==5.10.1 ; python_version >= "3.7" and python_version < "4.0" jaraco-classes==3.2.3 ; python_version >= "3.7" and python_version < "4.0" diff --git a/flexget/_version.py b/flexget/_version.py index 91ee3acea9..0c3e9f4441 100644 --- a/flexget/_version.py +++ b/flexget/_version.py @@ -7,4 +7,4 @@ The github actions release job will automatically strip the .dev for release, and update the version again for continued development. """ -__version__ = '3.5.37.dev' +__version__ = '3.6.0.dev' diff --git a/flexget/plugins/clients/transmission.py b/flexget/plugins/clients/transmission.py index 4834640fbc..e88371fa91 100644 --- a/flexget/plugins/clients/transmission.py +++ b/flexget/plugins/clients/transmission.py @@ -1,12 +1,21 @@ import os +import pathlib import re from datetime import datetime, timedelta from fnmatch import fnmatch from functools import partial from netrc import NetrcParseError, netrc from time import sleep +from typing import TYPE_CHECKING from urllib.parse import urlparse +try: + import importlib.metadata as importlib_metadata +except ImportError: + import importlib_metadata # TODO: remove this after we drop python 3.7 + +import packaging.specifiers +import packaging.version from loguru import logger from flexget import plugin @@ -18,15 +27,30 @@ from flexget.utils.tools import parse_timedelta try: - import requests.exceptions - import transmission_rpc as transmissionrpc - from transmission_rpc import TransmissionError + import transmission_rpc + from transmission_rpc.error import ( + TransmissionAuthError, + TransmissionConnectError, + TransmissionError, + TransmissionTimeoutError, + ) except ImportError: # If transmissionrpc is not found, errors will be shown later - pass + transmission_rpc = None + TransmissionError = None + TransmissionAuthError = None + TransmissionConnectError = None + TransmissionTimeoutError = None + +if TYPE_CHECKING: + from transmission_rpc import Torrent logger = logger.bind(name='transmission') +__version__ = '>=4.1.4,<5.0.0' +__package__ = 'transmission-rpc' +__requirement__ = packaging.specifiers.SpecifierSet(__version__) + class TransmissionBase: def prepare_config(self, config): @@ -48,7 +72,7 @@ def prepare_config(self, config): logger.error('netrc: {}, file: {}, line: {}', e.msg, e.filename, e.lineno) return config - def create_rpc_client(self, config) -> 'transmissionrpc.Client': + def create_rpc_client(self, config) -> 'transmission_rpc.Client': user, password = config.get('username'), config.get('password') urlo = urlparse(config['host']) @@ -62,7 +86,7 @@ def create_rpc_client(self, config) -> 'transmissionrpc.Client': logger.debug('Connecting to {}://{}:{}{}', protocol, urlo.hostname, port, path) try: - cli = transmissionrpc.Client( + cli = transmission_rpc.Client( protocol=protocol, host=urlo.hostname, port=port, @@ -71,73 +95,73 @@ def create_rpc_client(self, config) -> 'transmissionrpc.Client': password=password, timeout=30, ) - except TransmissionError as e: - if e.original and e.original.code == 401: - raise plugin.PluginError( - "Username/password for transmission is incorrect. Cannot connect." - ) - else: - raise plugin.PluginError("Error connecting to transmission: %s" % e.message) - except requests.exceptions.ConnectTimeout as e: + except TransmissionAuthError as e: + raise plugin.PluginError( + "Username/password for transmission is incorrect. Cannot connect." + ) + except TransmissionTimeoutError as e: raise plugin.PluginError("Cannot connect to transmission: Connection timed out.") - except requests.exceptions.ConnectionError as e: + except TransmissionConnectError as e: raise plugin.PluginError("Error connecting to transmission: %s" % e.args[0].reason) - except ValueError as e: + except TransmissionError as e: raise plugin.PluginError("Error connecting to transmission") return cli - def torrent_info(self, torrent, config): - done = torrent.totalSize > 0 + def torrent_info(self, torrent: 'Torrent', config): + done = torrent.total_size > 0 vloc = None best = None - for t in torrent.files().items(): - tf = t[1] - if tf['selected']: - if tf['size'] <= 0 or tf['completed'] < tf['size']: + for _, tf in enumerate(torrent.get_files()): + if tf.selected: + if tf.size <= 0 or tf.completed < tf.size: done = False break - if not best or tf['size'] > best[1]: - best = (tf['name'], tf['size']) + if not best or tf.size > best[1]: + best = (tf.name, tf.size) if ( done and best - and (100 * float(best[1]) / float(torrent.totalSize)) + and (100 * float(best[1]) / float(torrent.total_size)) >= (config['main_file_ratio'] * 100) ): - vloc = ('%s/%s' % (torrent.downloadDir, best[0])).replace('/', os.sep) + vloc = ('%s/%s' % (torrent.download_dir, best[0])).replace('/', os.sep) return done, vloc - def check_seed_limits(self, torrent, session): + def check_seed_limits(self, torrent: 'Torrent', session): seed_limit_ok = True # will remain if no seed ratio defined idle_limit_ok = True # will remain if no idle limit defined - if torrent.seedRatioMode == 1: # use torrent's own seed ratio limit - seed_limit_ok = torrent.uploadRatio >= torrent.seedRatioLimit - elif torrent.seedRatioMode == 0: # use global rules + if torrent.get('seedRatioMode') == 1: # use torrent's own seed ratio limit + seed_limit_ok = torrent.upload_ratio >= torrent.seed_ratio_limit + elif torrent.get('seedRatioMode') == 0: # use global rules if session.seedRatioLimited: - seed_limit_ok = torrent.uploadRatio >= session.seedRatioLimit + seed_limit_ok = torrent.upload_ratio >= session.seedRatioLimit - if torrent.seedIdleMode == 1: # use torrent's own idle limit + if torrent.get('seedIdleMode') == 1: # use torrent's own idle limit idle_limit_ok = ( - torrent.date_active + timedelta(minutes=torrent.seedIdleLimit) + torrent.activity_date + timedelta(minutes=torrent.seed_idle_limit) < datetime.now().astimezone() ) - elif torrent.seedIdleMode == 0: # use global rules + elif torrent.get('seedIdleMode') == 0: # use global rules if session.idle_seeding_limit_enabled: idle_limit_ok = ( - torrent.date_active + timedelta(minutes=session.idle_seeding_limit) + torrent.activity_date + timedelta(minutes=session.idle_seeding_limit) < datetime.now().astimezone() ) return seed_limit_ok, idle_limit_ok def on_task_start(self, task, config): - try: - import transmission_rpc as transmissionrpc - from transmission_rpc import TransmissionError # noqa - except: + if transmission_rpc is None: + raise plugin.PluginError( + f'{__package__} module version {__version__} required.', logger + ) + + v = importlib_metadata.version(__package__) + if not __requirement__.contains(v): raise plugin.PluginError( - 'transmission-rpc module version 3.0 or higher required.', logger + f'{__package__} module version mismatch, requiring {__package__}{__version__}', + logger, ) # Mark rpc client for garbage collector so every task can start @@ -198,53 +222,56 @@ def on_task_input(self, task, config): title=torrent.name, url='', torrent_info_hash=torrent.hashString, - content_size=torrent.totalSize / (1024 * 1024), + content_size=torrent.total_size / (1024 * 1024), ) # Location of torrent is only valid if transmission is on same machine as flexget if config['host'] in ('localhost', '127.0.0.1'): - entry['location'] = torrent.torrentFile - entry['url'] = 'file://' + torrent.torrentFile - for attr in [ - 'id', - 'activityDate', - 'comment', - 'desiredAvailable', - 'downloadDir', - 'isFinished', - 'isPrivate', - 'isStalled', - 'leftUntilDone', - 'ratio', - 'status', - 'date_active', - 'date_added', - 'date_done', - 'date_started', - 'errorString', - 'priority', - 'progress', - 'secondsDownloading', - 'secondsSeeding', - 'torrentFile', - 'labels', - ]: + entry['location'] = torrent.torrent_file + entry['url'] = pathlib.Path(torrent.torrent_file) + for attr, field in { + 'id': 'id', + 'activityDate': 'activity_date', + 'comment': 'comment', + 'desiredAvailable': 'desired_available', + 'downloadDir': 'download_dir', + 'isFinished': 'is_finished', + 'isPrivate': 'is_private', + 'isStalled': 'is_stalled', + 'leftUntilDone': 'left_until_done', + 'ratio': 'ratio', + 'status': 'status', + 'date_active': 'date_active', + 'date_added': 'date_added', + 'date_done': 'date_done', + 'date_started': 'date_started', + 'errorString': 'error_string', + 'priority': 'priority', + 'progress': 'progress', + 'secondsDownloading': 'seconds_downloading', + 'secondsSeeding': 'seconds_seeding', + 'torrentFile': 'torrent_file', + 'labels': 'labels', + }.items(): try: - value = getattr(torrent, attr) + value = getattr(torrent, field) except Exception: logger.opt(exception=True).debug( 'error when requesting transmissionrpc attribute {}', attr ) else: - # transmission-rpc adds timezone info to datetimes, which makes them hard to deal with. Strip it. + # transmission-rpc adds timezone info to datetimes, + # which makes them hard to deal with. Strip it. if isinstance(value, datetime): value = value.replace(tzinfo=None) entry['transmission_' + attr] = value # Availability in percent entry['transmission_availability'] = ( - (torrent.desiredAvailable / torrent.leftUntilDone) if torrent.leftUntilDone else 0 + (torrent.desired_available / torrent.left_until_done) + if torrent.left_until_done + else 0 ) - entry['transmission_trackers'] = [t['announce'] for t in torrent.trackers] + entry['transmission_trackers'] = [t.announce for t in torrent.trackers] entry['transmission_seed_ratio_ok'] = seed_ratio_ok entry['transmission_idle_limit_ok'] = idle_limit_ok st_error_to_desc = { @@ -256,9 +283,8 @@ def on_task_input(self, task, config): entry['transmission_error_state'] = st_error_to_desc[torrent.error] # Built in done_date doesn't work when user adds an already completed file to transmission if torrent.progress == 100: - entry['transmission_date_done'] = datetime.fromtimestamp( - max(torrent.addedDate, torrent.doneDate) - ) + entry['transmission_date_done'] = max(torrent.added_date, torrent.done_date) + entries.append(entry) return entries @@ -419,7 +445,7 @@ def on_task_output(self, task, config): try: if downloaded: with open(entry['file'], 'rb') as f: - torrent_info = client.add_torrent(f.read(), **options['add']) + torrent_info = client.add_torrent(f, 30, **options['add']) else: if options['post'].get('magnetization_timeout', 0) > 0: options['add']['paused'] = False @@ -439,8 +465,11 @@ def on_task_output(self, task, config): else: # Torrent already loaded in transmission if options['add'].get('download_dir'): - logger.verbose( - 'Moving {} to "{}"', torrent_info.name, options['add']['download_dir'] + logger.log( + "VERBOSE", + 'Moving {} to "{}"', + torrent_info.name, + options['add']['download_dir'], ) # Move data even if current reported torrent location matches new location # as transmission may fail to automatically move completed file to final @@ -448,10 +477,12 @@ def on_task_output(self, task, config): # In such case this will kick transmission to really move data. # If data is already located at new location then transmission just ignore # this command. - client.move_torrent_data(torrent_info.id, options['add']['download_dir'], 120) + client.move_torrent_data( + torrent_info.hashString, options['add']['download_dir'], 120 + ) try: - total_size = torrent_info.totalSize + total_size = torrent_info.total_size main_id = None find_main_file = ( options['post'].get('main_file_only') or 'content_filename' in options['post'] @@ -459,7 +490,7 @@ def on_task_output(self, task, config): skip_files = options['post'].get('skip_files') # We need to index the files if any of the following are defined if find_main_file or skip_files: - file_list = client.get_files(torrent_info.id)[torrent_info.id] + file_list = torrent_info.get_files() if options['post'].get('magnetization_timeout', 0) > 0 and not file_list: logger.debug( @@ -469,11 +500,11 @@ def on_task_output(self, task, config): ) for _ in range(options['post']['magnetization_timeout']): sleep(1) - file_list = client.get_files(torrent_info.id)[torrent_info.id] + file_list = torrent_info.get_files() if file_list: total_size = client.get_torrent( torrent_info.id, ['id', 'totalSize'] - ).totalSize + ).total_size break else: logger.warning( @@ -842,12 +873,13 @@ def on_task_exit(self, task, config): remove_ids = [] for torrent in client.get_torrents(): - logger.verbose( + logger.log( + "VERBOSE", 'Torrent "{}": status: "{}" - ratio: {} - date added: {}', torrent.name, torrent.status, torrent.ratio, - torrent.date_added, + torrent.added_date, ) downloaded, dummy = self.torrent_info(torrent, config) if not downloaded: @@ -861,12 +893,10 @@ def on_task_exit(self, task, config): continue if 'finished_for' in config: # done date might be invalid if this torrent was added to transmission when already completed - started_seeding = datetime.fromtimestamp(max(torrent.addedDate, torrent.doneDate)) + started_seeding = max(torrent.added_date, torrent.done_date) if started_seeding + parse_timedelta(config['finished_for']) > datetime.now(): continue - tracker_hosts = ( - urlparse(tracker['announce']).hostname for tracker in torrent.trackers - ) + tracker_hosts = [urlparse(tracker.announce).hostname for tracker in torrent.trackers] if 'tracker' in config: if not any(tracker_re.search(tracker) for tracker in tracker_hosts): continue @@ -875,7 +905,8 @@ def on_task_exit(self, task, config): continue if config.get('directories'): if not any( - re.search(d, torrent.downloadDir, re.IGNORECASE) for d in config['directories'] + re.search(d, torrent.download_dir, re.IGNORECASE) + for d in config['directories'] ): continue if task.options.test: diff --git a/poetry.lock b/poetry.lock index 4d201de503..b7a25e7a27 100644 --- a/poetry.lock +++ b/poetry.lock @@ -350,16 +350,6 @@ files = [ {file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b83bb06a0192cccf1eb8d0a28672a1b79c74c3a8a5f2619625aeb6f28b3a82bb"}, {file = "Brotli-1.0.9-cp310-cp310-win32.whl", hash = "sha256:26d168aac4aaec9a4394221240e8a5436b5634adc3cd1cdf637f6645cecbf181"}, {file = "Brotli-1.0.9-cp310-cp310-win_amd64.whl", hash = "sha256:622a231b08899c864eb87e85f81c75e7b9ce05b001e59bbfbf43d4a71f5f32b2"}, - {file = "Brotli-1.0.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cc0283a406774f465fb45ec7efb66857c09ffefbe49ec20b7882eff6d3c86d3a"}, - {file = "Brotli-1.0.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:11d3283d89af7033236fa4e73ec2cbe743d4f6a81d41bd234f24bf63dde979df"}, - {file = "Brotli-1.0.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c1306004d49b84bd0c4f90457c6f57ad109f5cc6067a9664e12b7b79a9948ad"}, - {file = "Brotli-1.0.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1375b5d17d6145c798661b67e4ae9d5496920d9265e2f00f1c2c0b5ae91fbde"}, - {file = "Brotli-1.0.9-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cab1b5964b39607a66adbba01f1c12df2e55ac36c81ec6ed44f2fca44178bf1a"}, - {file = "Brotli-1.0.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8ed6a5b3d23ecc00ea02e1ed8e0ff9a08f4fc87a1f58a2530e71c0f48adf882f"}, - {file = "Brotli-1.0.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cb02ed34557afde2d2da68194d12f5719ee96cfb2eacc886352cb73e3808fc5d"}, - {file = "Brotli-1.0.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b3523f51818e8f16599613edddb1ff924eeb4b53ab7e7197f85cbc321cdca32f"}, - {file = "Brotli-1.0.9-cp311-cp311-win32.whl", hash = "sha256:ba72d37e2a924717990f4d7482e8ac88e2ef43fb95491eb6e0d124d77d2a150d"}, - {file = "Brotli-1.0.9-cp311-cp311-win_amd64.whl", hash = "sha256:3ffaadcaeafe9d30a7e4e1e97ad727e4f5610b9fa2f7551998471e3736738679"}, {file = "Brotli-1.0.9-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:c83aa123d56f2e060644427a882a36b3c12db93727ad7a7b9efd7d7f3e9cc2c4"}, {file = "Brotli-1.0.9-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:6b2ae9f5f67f89aade1fab0f7fd8f2832501311c363a21579d02defa844d9296"}, {file = "Brotli-1.0.9-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:68715970f16b6e92c574c30747c95cf8cf62804569647386ff032195dc89a430"}, @@ -404,17 +394,7 @@ files = [ {file = "Brotli-1.0.9-cp39-cp39-win32.whl", hash = "sha256:cfc391f4429ee0a9370aa93d812a52e1fee0f37a81861f4fdd1f4fb28e8547c3"}, {file = "Brotli-1.0.9-cp39-cp39-win_amd64.whl", hash = "sha256:854c33dad5ba0fbd6ab69185fec8dab89e13cda6b7d191ba111987df74f38761"}, {file = "Brotli-1.0.9-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9749a124280a0ada4187a6cfd1ffd35c350fb3af79c706589d98e088c5044267"}, - {file = "Brotli-1.0.9-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:73fd30d4ce0ea48010564ccee1a26bfe39323fde05cb34b5863455629db61dc7"}, - {file = "Brotli-1.0.9-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:02177603aaca36e1fd21b091cb742bb3b305a569e2402f1ca38af471777fb019"}, {file = "Brotli-1.0.9-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:76ffebb907bec09ff511bb3acc077695e2c32bc2142819491579a695f77ffd4d"}, - {file = "Brotli-1.0.9-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b43775532a5904bc938f9c15b77c613cb6ad6fb30990f3b0afaea82797a402d8"}, - {file = "Brotli-1.0.9-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5bf37a08493232fbb0f8229f1824b366c2fc1d02d64e7e918af40acd15f3e337"}, - {file = "Brotli-1.0.9-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:330e3f10cd01da535c70d09c4283ba2df5fb78e915bea0a28becad6e2ac010be"}, - {file = "Brotli-1.0.9-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e1abbeef02962596548382e393f56e4c94acd286bd0c5afba756cffc33670e8a"}, - {file = "Brotli-1.0.9-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3148362937217b7072cf80a2dcc007f09bb5ecb96dae4617316638194113d5be"}, - {file = "Brotli-1.0.9-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:336b40348269f9b91268378de5ff44dc6fbaa2268194f85177b53463d313842a"}, - {file = "Brotli-1.0.9-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b8b09a16a1950b9ef495a0f8b9d0a87599a9d1f179e2d4ac014b2ec831f87e7"}, - {file = "Brotli-1.0.9-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c8e521a0ce7cf690ca84b8cc2272ddaf9d8a50294fd086da67e517439614c755"}, {file = "Brotli-1.0.9.zip", hash = "sha256:4d1b810aa0ed773f81dceda2cc7b403d01057458730e309856356d4ef4188438"}, ] @@ -1127,6 +1107,7 @@ files = [ {file = "greenlet-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5b0ff9878333823226d270417f24f4d06f235cb3e54d1103b71ea537a6a86ce"}, {file = "greenlet-2.0.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be9e0fb2ada7e5124f5282d6381903183ecc73ea019568d6d63d33f25b2a9000"}, {file = "greenlet-2.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b493db84d124805865adc587532ebad30efa68f79ad68f11b336e0a51ec86c2"}, + {file = "greenlet-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0459d94f73265744fee4c2d5ec44c6f34aa8a31017e6e9de770f7bcf29710be9"}, {file = "greenlet-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a20d33124935d27b80e6fdacbd34205732660e0a1d35d8b10b3328179a2b51a1"}, {file = "greenlet-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:ea688d11707d30e212e0110a1aac7f7f3f542a259235d396f88be68b649e47d1"}, {file = "greenlet-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:afe07421c969e259e9403c3bb658968702bc3b78ec0b6fde3ae1e73440529c23"}, @@ -1135,6 +1116,7 @@ files = [ {file = "greenlet-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:659f167f419a4609bc0516fb18ea69ed39dbb25594934bd2dd4d0401660e8a1e"}, {file = "greenlet-2.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:356e4519d4dfa766d50ecc498544b44c0249b6de66426041d7f8b751de4d6b48"}, {file = "greenlet-2.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:811e1d37d60b47cb8126e0a929b58c046251f28117cb16fcd371eed61f66b764"}, + {file = "greenlet-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d38ffd0e81ba8ef347d2be0772e899c289b59ff150ebbbbe05dc61b1246eb4e0"}, {file = "greenlet-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0109af1138afbfb8ae647e31a2b1ab030f58b21dd8528c27beaeb0093b7938a9"}, {file = "greenlet-2.0.1-cp38-cp38-win32.whl", hash = "sha256:88c8d517e78acdf7df8a2134a3c4b964415b575d2840a2746ddb1cc6175f8608"}, {file = "greenlet-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:d6ee1aa7ab36475035eb48c01efae87d37936a8173fc4d7b10bb02c2d75dd8f6"}, @@ -1143,6 +1125,7 @@ files = [ {file = "greenlet-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:505138d4fa69462447a562a7c2ef723c6025ba12ac04478bc1ce2fcc279a2db5"}, {file = "greenlet-2.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cce1e90dd302f45716a7715517c6aa0468af0bf38e814ad4eab58e88fc09f7f7"}, {file = "greenlet-2.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e9744c657d896c7b580455e739899e492a4a452e2dd4d2b3e459f6b244a638d"}, + {file = "greenlet-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:662e8f7cad915ba75d8017b3e601afc01ef20deeeabf281bd00369de196d7726"}, {file = "greenlet-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:41b825d65f31e394b523c84db84f9383a2f7eefc13d987f308f4663794d2687e"}, {file = "greenlet-2.0.1-cp39-cp39-win32.whl", hash = "sha256:db38f80540083ea33bdab614a9d28bcec4b54daa5aff1668d7827a9fc769ae0a"}, {file = "greenlet-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:b23d2a46d53210b498e5b701a1913697671988f4bf8e10f935433f6e7c332fb6"}, @@ -1238,14 +1221,14 @@ files = [ [[package]] name = "importlib-metadata" -version = "5.1.0" +version = "6.1.0" description = "Read metadata from Python packages" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "importlib_metadata-5.1.0-py3-none-any.whl", hash = "sha256:d84d17e21670ec07990e1044a99efe8d615d860fd176fc29ef5c306068fda313"}, - {file = "importlib_metadata-5.1.0.tar.gz", hash = "sha256:d5059f9f1e8e41f80e9c56c2ee58811450c31984dfa625329ffd7c0dad88a73b"}, + {file = "importlib_metadata-6.1.0-py3-none-any.whl", hash = "sha256:ff80f3b5394912eb1b108fcfd444dc78b7f1f3e16b16188054bd01cb9cb86f09"}, + {file = "importlib_metadata-6.1.0.tar.gz", hash = "sha256:43ce9281e097583d758c2c708c4376371261a02c34682491a8e98352365aad20"}, ] [package.dependencies] @@ -1253,7 +1236,7 @@ typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] @@ -2930,7 +2913,7 @@ importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} [package.extras] aiomysql = ["aiomysql", "greenlet (!=0.4.17)"] -aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"] +aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] asyncio = ["greenlet (!=0.4.17)"] asyncmy = ["asyncmy (>=0.2.3,!=0.2.4)", "greenlet (!=0.4.17)"] mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2)"] @@ -2940,14 +2923,14 @@ mssql-pyodbc = ["pyodbc"] mypy = ["mypy (>=0.910)", "sqlalchemy2-stubs"] mysql = ["mysqlclient (>=1.4.0)", "mysqlclient (>=1.4.0,<2)"] mysql-connector = ["mysql-connector-python"] -oracle = ["cx-oracle (>=7)", "cx-oracle (>=7,<8)"] +oracle = ["cx_oracle (>=7)", "cx_oracle (>=7,<8)"] postgresql = ["psycopg2 (>=2.7)"] postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] postgresql-pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)"] postgresql-psycopg2binary = ["psycopg2-binary"] postgresql-psycopg2cffi = ["psycopg2cffi"] pymysql = ["pymysql", "pymysql (<1)"] -sqlcipher = ["sqlcipher3-binary"] +sqlcipher = ["sqlcipher3_binary"] [[package]] name = "sqlalchemy-stubs" @@ -3473,4 +3456,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.7" -content-hash = "8f43486a34ac5ecab39cda488cd24500b251b8073b91dd2c54a3f40c7369192a" +content-hash = "c013b9580232bfcd33b2dbe63db9de9a1f8b7b98d6dd32413d149c64b3e588fa" diff --git a/pyproject.toml b/pyproject.toml index 4ca1eb205d..50960ccfc7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,6 +61,7 @@ colorama = ">=0.4.4" feedparser = ">=6.0.2" guessit = ">=3.4,<4.0" html5lib = ">=0.11" +importlib-metadata = { version = "*", python = "<3.8" } # TODO: remove this after we drop python3.7 jinja2 = ">=3.0,<4.0" jsonschema = ">=2.0" loguru = ">=0.4.1" diff --git a/requirements.txt b/requirements.txt index 4ab149a751..c58a08a9eb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -24,7 +24,7 @@ greenlet==2.0.1 ; python_version >= "3.7" and platform_machine == "aarch64" and guessit==3.5.0 ; python_version >= "3.7" and python_version < "4.0" html5lib==1.1 ; python_version >= "3.7" and python_version < "4.0" idna==3.4 ; python_version >= "3.7" and python_version < "4" -importlib-metadata==5.1.0 ; python_version >= "3.7" and python_version < "3.10" +importlib-metadata==6.1.0 ; python_version >= "3.7" and python_version < "3.10" importlib-resources==5.10.1 ; python_version >= "3.7" and python_version < "3.9" inflect==6.0.2 ; python_version >= "3.7" and python_version < "4.0" itsdangerous==2.1.2 ; python_version >= "3.7" and python_version < "4.0"