From 9d802b2a912aa0e459cf9ad0e9ec2fb51832b59e Mon Sep 17 00:00:00 2001 From: Calum Lind Date: Fri, 6 Sep 2024 20:20:55 +0100 Subject: [PATCH 01/10] [Tests] Fix missing __qualname__ for mock callback test_pop_alerts raised the following error: File "/home/runner/work/deluge/deluge/deluge/core/alertmanager.py", line 177, in handle_alerts handler=handler.__qualname__, File "lib/python3.10/unittest/mock.py", line 645, in __getattr__ raise AttributeError(name) AttributeError: __qualname__ Mocks don't generate dunder methods like `__qualname__` attribute so we need to manually specify it. --- deluge/conftest.py | 1 + requirements.txt | 3 +-- setup.py | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/deluge/conftest.py b/deluge/conftest.py index c3070140df..19a0cff57b 100644 --- a/deluge/conftest.py +++ b/deluge/conftest.py @@ -53,6 +53,7 @@ def reset(timeout=0.5, *args, **kwargs): mock.deferred = deferred mock = Mock() + mock.__qualname__ = 'mock' original_reset_mock = mock.reset_mock mock.reset_mock = reset mock.reset_mock() diff --git a/requirements.txt b/requirements.txt index 3b2a4080de..a26ec758d5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,5 @@ libtorrent -twisted[tls]>=17.1; sys_platform != 'win32' -twisted[tls]<23,>=17.1; sys_platform == 'win32' +twisted[tls]>=17.1 rencode pyopenssl pyxdg diff --git a/setup.py b/setup.py index 509d383e40..ef70f20b0a 100755 --- a/setup.py +++ b/setup.py @@ -538,8 +538,7 @@ def run(self): setup_requires = ['setuptools', 'wheel'] install_requires = [ - "twisted[tls]>=17.1; sys_platform != 'win32'", - "twisted[tls]<23,>=17.1; sys_platform == 'win32'", + 'twisted[tls]>=17.1', # Add pyasn1 for setuptools workaround: # https://github.com/pypa/setuptools/issues/1510 'pyasn1', From 3bceb4bfc1f89d76004a1be50d3d864bd42215e1 Mon Sep 17 00:00:00 2001 From: zakary Date: Mon, 26 Aug 2024 03:34:27 -0500 Subject: [PATCH 02/10] [UI][Common] Wrap torrent comment and tracker status URLs in HTML (clickable) Closes: https://github.com/deluge-torrent/deluge/pull/460 --- deluge/common.py | 10 ++++++++++ deluge/ui/gtk3/details_tab.py | 6 +++--- deluge/ui/gtk3/trackers_tab.py | 7 +++++-- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/deluge/common.py b/deluge/common.py index be655447fd..256f1f7831 100644 --- a/deluge/common.py +++ b/deluge/common.py @@ -720,6 +720,16 @@ def parse_human_size(size): raise InvalidSize(msg % (size, tokens)) +def anchorify_urls(text: str) -> str: + """ + Wrap all occurrences of text URLs with HTML + """ + url_pattern = r'((htt)|(ft)|(ud))ps?://\S+' + html_href_pattern = r'\g<0>' + + return re.sub(url_pattern, html_href_pattern, text) + + def is_url(url): """ A simple test to check if the URL is valid diff --git a/deluge/ui/gtk3/details_tab.py b/deluge/ui/gtk3/details_tab.py index 04a5eabfe0..95b4ab8e36 100644 --- a/deluge/ui/gtk3/details_tab.py +++ b/deluge/ui/gtk3/details_tab.py @@ -10,7 +10,7 @@ from xml.sax.saxutils import escape as xml_escape import deluge.component as component -from deluge.common import decode_bytes, fdate, fsize, is_url +from deluge.common import anchorify_urls, decode_bytes, fdate, fsize from .tab_data_funcs import fdate_or_dash, fpieces_num_size from .torrentdetails import Tab @@ -61,8 +61,8 @@ def _on_get_torrent_status(self, status): for widget in self.tab_widgets.values(): txt = xml_escape(self.widget_status_as_fstr(widget, status)) if decode_bytes(widget.obj.get_text()) != txt: - if 'comment' in widget.status_keys and is_url(txt): - widget.obj.set_markup(f'{txt}') + if 'comment' in widget.status_keys: + widget.obj.set_markup(anchorify_urls(txt)) else: widget.obj.set_markup(txt) diff --git a/deluge/ui/gtk3/trackers_tab.py b/deluge/ui/gtk3/trackers_tab.py index d671471b02..5fad631e4f 100644 --- a/deluge/ui/gtk3/trackers_tab.py +++ b/deluge/ui/gtk3/trackers_tab.py @@ -9,7 +9,7 @@ import logging import deluge.component as component -from deluge.common import ftime +from deluge.common import anchorify_urls, ftime from .tab_data_funcs import fcount, ftranslate, fyes_no from .torrentdetails import Tab @@ -54,7 +54,10 @@ def _on_get_torrent_status(self, status): for widget in self.tab_widgets.values(): txt = self.widget_status_as_fstr(widget, status) if widget.obj.get_text() != txt: - widget.obj.set_text(txt) + if 'tracker_status' in widget.status_keys: + widget.obj.set_markup(anchorify_urls(txt)) + else: + widget.obj.set_text(txt) def clear(self): for widget in self.tab_widgets.values(): From 5d96cfc72f0bfa36d90afd2725aa2216b8073d66 Mon Sep 17 00:00:00 2001 From: Mamoru TASAKA Date: Thu, 29 Aug 2024 15:31:25 +0900 Subject: [PATCH 03/10] [UI] Replace deprecated cgi module with email As PEP 594 says, cgi module is marked as deprecated in python 3.11, and will be removed in 3.13 (actually removed at least in 3.13 rc1). As suggested on PEP 594, replace cgi.parse_header with email.message.EmailMessage introduced in python 3.6. Updated test modify test_download_with_rename_sanitised - With RFC2045 specification, Content-Disposition filenames parameter containing slash (directory separator) must be quoted, so changing as such. Ref: https://peps.python.org/pep-0594/#deprecated-modules Ref: https://peps.python.org/pep-0594/#cgi Closes: https://github.com/deluge-torrent/deluge/pull/462 --- deluge/httpdownloader.py | 14 +++++++++----- deluge/tests/test_httpdownloader.py | 4 ++-- deluge/ui/web/json_api.py | 6 ++++-- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/deluge/httpdownloader.py b/deluge/httpdownloader.py index 700ade06bf..c19e3aa7ed 100644 --- a/deluge/httpdownloader.py +++ b/deluge/httpdownloader.py @@ -6,7 +6,7 @@ # See LICENSE for more details. # -import cgi +import email.message import logging import os.path import zlib @@ -133,9 +133,10 @@ def request_callback(self, response): content_disp = headers.getRawHeaders(b'content-disposition')[0].decode( 'utf-8' ) - content_disp_params = cgi.parse_header(content_disp)[1] - if 'filename' in content_disp_params: - new_file_name = content_disp_params['filename'] + message = email.message.EmailMessage() + message['content-disposition'] = content_disp + new_file_name = message.get_filename() + if new_file_name: new_file_name = sanitise_filename(new_file_name) new_file_name = os.path.join( os.path.split(self.filename)[0], new_file_name @@ -152,7 +153,10 @@ def request_callback(self, response): self.filename = new_file_name cont_type_header = headers.getRawHeaders(b'content-type')[0].decode() - cont_type, params = cgi.parse_header(cont_type_header) + message = email.message.EmailMessage() + message['content-type'] = cont_type_header + cont_type = message.get_content_type() + params = message['content-type'].params # Only re-ecode text content types. encoding = None if cont_type.startswith('text/'): diff --git a/deluge/tests/test_httpdownloader.py b/deluge/tests/test_httpdownloader.py index 1c27045603..0a4695b5ce 100644 --- a/deluge/tests/test_httpdownloader.py +++ b/deluge/tests/test_httpdownloader.py @@ -206,10 +206,10 @@ async def test_download_with_rename_exists(self): self.assert_contains(filename, 'This file should be called renamed') async def test_download_with_rename_sanitised(self): - url = self.get_url('rename?filename=/etc/passwd') + url = self.get_url('rename?filename="/etc/passwd"') filename = await download_file(url, fname('original')) assert filename == fname('passwd') - self.assert_contains(filename, 'This file should be called /etc/passwd') + self.assert_contains(filename, 'This file should be called "/etc/passwd"') async def test_download_with_attachment_no_filename(self): url = self.get_url('attachment') diff --git a/deluge/ui/web/json_api.py b/deluge/ui/web/json_api.py index 1a0e66f77d..dd921c801e 100644 --- a/deluge/ui/web/json_api.py +++ b/deluge/ui/web/json_api.py @@ -6,7 +6,7 @@ # See LICENSE for more details. # -import cgi +import email.message import json import logging import os @@ -191,7 +191,9 @@ def _on_json_request(self, request): Handler to take the json data as a string and pass it on to the _handle_request method for further processing. """ - content_type, _ = cgi.parse_header(request.getHeader(b'content-type').decode()) + message = email.message.EmailMessage() + message['content-type'] = request.getHeader(b'content-type').decode() + content_type = message.get_content_type() if content_type != 'application/json': message = 'Invalid JSON request content-type: %s' % content_type raise JSONException(message) From 491458c4ad512794956bdac7b88ccacb34e0a46a Mon Sep 17 00:00:00 2001 From: Calum Lind Date: Sun, 8 Sep 2024 09:49:18 +0100 Subject: [PATCH 04/10] [CI] Fix accidental revert of Twisted pin for Windows In commit 9d802b2 I pushed a change to tests which included a revert of Windows pinned dependencies which was an accident. The actual change should only have reverted setup.py pinned dependency since we down want a release to not be able to use a later fixed version of Twisted. --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index a26ec758d5..4aee26d2da 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ libtorrent twisted[tls]>=17.1 +twisted[tls]<23,>=17.1; sys_platform == 'win32' rencode pyopenssl pyxdg From c88f750108ede64cdd1e4ea143981b9212b13360 Mon Sep 17 00:00:00 2001 From: Martin Hertz Date: Fri, 16 Aug 2024 14:21:03 +0200 Subject: [PATCH 05/10] [Console] Block interactive-mode on Windows even with windows-curses Testing with window-curses results in hangs on initial loading with background error: File "C:\Users\Docker\Deluge\.venv\lib\site-packages\twisted\internet\selectreactor.py", line 39, in win32select r, w, e = select.select(r, w, w, timeout) builtins.OSError: [WinError 10038] An operation was attempted on something that is not a socket This is due to passing a Console class to addReader but this fails since select on Windows cannot handle non-socket file object unlike Unix which accepts sockets and file objects. There is likely a further issue where windows-curses has not implemented resizeterm so would need to use resize_term instead. Refs: https://docs.python.org/3/library/select.html#select.select Refs: https://stackoverflow.com/questions/11731175/python-twisted-addreader-works-in-linux-but-not-windows Refs: https://github.com/zephyrproject-rtos/windows-curses/issues/40 Refs: https://docs.python.org/3/library/curses.html#curses.resize_term Closes: https://github.com/deluge-torrent/deluge/pull/457 Co-authored-by: Calum Lind --- deluge/ui/console/main.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deluge/ui/console/main.py b/deluge/ui/console/main.py index 106169f0ea..667b0c9243 100644 --- a/deluge/ui/console/main.py +++ b/deluge/ui/console/main.py @@ -138,7 +138,7 @@ def start_ui(self): except ImportError: wrapper = None - if deluge.common.windows_check() and not wrapper: + if deluge.common.windows_check(): print( """\nDeluge-console does not run in interactive mode on Windows. \n Please use commands from the command line, e.g.:\n @@ -148,6 +148,7 @@ def start_ui(self): deluge-console.exe "add -p c:\\mytorrents c:\\new.torrent" """ ) + return # We don't ever want log output to terminal when running in # interactive mode, so insert a dummy here From 90c5e75373b9bcba1730ea8f8dcb39b3236748d3 Mon Sep 17 00:00:00 2001 From: Calum Lind Date: Mon, 19 Feb 2024 17:52:47 +0000 Subject: [PATCH 06/10] [Lint] Replace black/flake8/isort with ruff Use ruff as a single performant tool to lint and format Python code. --- .pre-commit-config.yaml | 28 ++++-------- .../Stats/deluge_stats/tests/test_stats.py | 1 + pyproject.toml | 43 ++++++++++++++++--- setup.cfg | 20 --------- tox.ini | 13 ------ 5 files changed, 47 insertions(+), 58 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4d0d922efd..84d82235c7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,11 +6,15 @@ exclude: > deluge/tests/data/.*svg| )$ repos: - - repo: https://github.com/psf/black - rev: 23.1.0 + - repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.6.4 hooks: - - id: black - name: Fmt Black + - id: ruff + name: Chk Ruff + args: [--fix] + - id: ruff-format + name: Fmt Ruff - repo: https://github.com/pre-commit/mirrors-prettier rev: v2.7.1 hooks: @@ -18,23 +22,9 @@ repos: name: Fmt Prettier # Workaround to list modified files only. args: [--list-different] - - repo: https://github.com/pycqa/isort - rev: 5.12.0 - hooks: - - id: isort - name: Fmt isort - - repo: https://github.com/pycqa/flake8 - rev: 6.0.0 - hooks: - - id: flake8 - name: Chk Flake8 - additional_dependencies: - - pep8-naming==0.12.1 - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 hooks: - - id: double-quote-string-fixer - name: Fix Double-quotes - id: end-of-file-fixer name: Fix End-of-files exclude_types: [javascript, css] @@ -47,5 +37,5 @@ repos: rev: v3.3.1 hooks: - id: pyupgrade - args: [--py36-plus] + args: [--py37-plus] stages: [manual] diff --git a/deluge/plugins/Stats/deluge_stats/tests/test_stats.py b/deluge/plugins/Stats/deluge_stats/tests/test_stats.py index d61cd46669..8af1b739ef 100644 --- a/deluge/plugins/Stats/deluge_stats/tests/test_stats.py +++ b/deluge/plugins/Stats/deluge_stats/tests/test_stats.py @@ -64,6 +64,7 @@ def test_write(self, tmp_path): Not strictly a unit test, but tests if calls do not fail... """ + # ruff: noqa: I001 from deluge_stats import graph, gtkui from deluge.configmanager import ConfigManager diff --git a/pyproject.toml b/pyproject.toml index 169561f131..beec7960aa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,12 +1,6 @@ [build-system] requires = ["setuptools", "wheel"] -[tool.black] -skip-string-normalization = true - -[tool.isort] -profile = "black" - [tool.pytest.ini_options] # Dump tracebacks if a test takes longer than X seconds faulthandler_timeout = 60 @@ -27,3 +21,40 @@ filterwarnings = [ "ignore:Using readBody.*:DeprecationWarning", "ignore:resume_data is deprecated.*:DeprecationWarning:deluge.core.alertmanager", ] + +[tool.ruff] +line-length = 88 +builtins = ["_", "_n", "__request__"] +extend-exclude = ["dist", "build"] +target-version = "py37" + +[tool.ruff.format] +# Prefer single quotes over double quotes +quote-style = "single" + +[tool.ruff.lint] +ignore = [ + "N818", # error-suffix-on-exception-name + "E501", # Line-too-long, let formatter resolve it + +] +select = [ + "A", # flake8-builtins + "E", # pycodestyle + "F", # Pyflakes + "N", # pep8-naming + "W", # pycodestyle + "INT", # flake8-gettext + "I", # isort + +] + +[tool.ruff.lint.flake8-gettext] +extend-function-names = ["_n"] + +[tool.ruff.lint.extend-per-file-ignores] +# E402 import not top of file (gi version check required before import) +"deluge/**/gtkui/*.py" = ["E402"] +"deluge/**/gtkui.py" = ["E402"] +"deluge/plugins/Stats/deluge_stats/graph.py" = ["E402"] +"deluge/ui/gtk3/*.py" = ["E402"] diff --git a/setup.cfg b/setup.cfg index 9403fb0cb1..47c80c29da 100644 --- a/setup.cfg +++ b/setup.cfg @@ -23,23 +23,3 @@ includes = glib, gio, cairo, pango, pangocairo, atk, gobject, gtk.keysyms, zope.interface, mako.cache, email.mime, libtorrent, gtkosx_application, HTMLParser frameworks = CoreFoundation, Foundation, AppKit - -[flake8] -max-line-length = 120 -builtins = _,_n,__request__ -extend-exclude = dist,build -extend-ignore = -# flake8-builtins: A003 class attribute is shadowing a python builtin - A003, -# E203 whitespace before ':' - E203, -# N818 pep8-naming: error suffix in exception names - N818 -per-file-ignores = -# import not top of file (gi checks required before import) - deluge/ui/gtk3/*.py : E402 - deluge/**/gtkui.py: E402 - deluge/**/gtkui/*.py: E402 - deluge/plugins/Stats/deluge_stats/graph.py: E402 -[pycodestyle] -max-line-length = 88 diff --git a/tox.ini b/tox.ini index 3756a5406a..a896eb7d26 100644 --- a/tox.ini +++ b/tox.ini @@ -94,19 +94,6 @@ passenv = HOMEPATH,SSH_AUTH_SOCK deps = {[baselint]deps} commands = pre-commit run --all-files -[testenv:flake8] -sitepackages = False -deps = {[baselint]deps} -commands = - flake8 --version - python -c 'import isort; print(isort.__version__)' - flake8 *.py deluge - -[testenv:flake8-complexity] -sitepackages = False -deps = {[baselint]deps} -commands = flake8 --exit-zero --max-complexity 15 deluge - [testenv:pylint] sitepackages = False ignore_errors = True From e7d08d7645dfbd5bff2eed98298012037231c173 Mon Sep 17 00:00:00 2001 From: Calum Lind Date: Sun, 8 Sep 2024 14:33:07 +0100 Subject: [PATCH 07/10] [Typing] Add pyright config to suppress warnings VSCode uses pylance/pyright, a performant type checker. So setup the builtins used by Deluge and set missing imports to informational due to OS-specific imports. It might be possible using extraPaths with extra stubs or use defineConstants to make pyright not check Windows or Macos conditional paths but that would require changing all usage so leaving for another time. Refs: https://github.com/microsoft/pyright/blob/main/docs/configuration.md Refs: https://github.com/microsoft/pyright/blob/main/docs/builtins.md --- __builtins__.pyi | 6 ++++++ pyproject.toml | 4 ++++ 2 files changed, 10 insertions(+) create mode 100644 __builtins__.pyi diff --git a/__builtins__.pyi b/__builtins__.pyi new file mode 100644 index 0000000000..d034605548 --- /dev/null +++ b/__builtins__.pyi @@ -0,0 +1,6 @@ +from twisted.web.http import Request + +__request__: Request + +def _(string: str) -> str: ... +def _n(string: str) -> str: ... diff --git a/pyproject.toml b/pyproject.toml index beec7960aa..8b65e52dc6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,3 +58,7 @@ extend-function-names = ["_n"] "deluge/**/gtkui.py" = ["E402"] "deluge/plugins/Stats/deluge_stats/graph.py" = ["E402"] "deluge/ui/gtk3/*.py" = ["E402"] + +[tool.pyright] +reportMissingImports = "information" +reportMissingModuleSource = "information" From 22476685713e8f2387ecf40570f563597fe56bfb Mon Sep 17 00:00:00 2001 From: Calum Lind Date: Sun, 8 Sep 2024 13:24:44 +0100 Subject: [PATCH 08/10] [Lint] Fix ruff lint gettext and type comparison The gettext strings cannot be formatted until after the function return from gettext. Refs: https://docs.astral.sh/ruff/rules/f-string-in-get-text-func-call/ Refs: https://docs.astral.sh/ruff/rules/printf-in-get-text-func-call/ --- .../Blocklist/deluge_blocklist/common.py | 6 +-- deluge/tests/test_json_api.py | 2 +- deluge/tests/test_log.py | 2 +- deluge/tests/test_ui_entry.py | 2 +- deluge/tests/test_web_api.py | 2 +- deluge/ui/console/cmdline/command.py | 2 +- deluge/ui/console/cmdline/commands/info.py | 2 +- deluge/ui/gtk3/addtorrentdialog.py | 11 ++--- deluge/ui/gtk3/gtkui.py | 3 +- deluge/ui/gtk3/preferences.py | 40 ++++++++++--------- 10 files changed, 37 insertions(+), 35 deletions(-) diff --git a/deluge/plugins/Blocklist/deluge_blocklist/common.py b/deluge/plugins/Blocklist/deluge_blocklist/common.py index 35b2f87c50..f1059413de 100644 --- a/deluge/plugins/Blocklist/deluge_blocklist/common.py +++ b/deluge/plugins/Blocklist/deluge_blocklist/common.py @@ -107,11 +107,11 @@ def parse(cls, ip): try: q1, q2, q3, q4 = (int(q) for q in ip.split('.')) except ValueError: - raise BadIP(_('The IP address "%s" is badly formed' % ip)) + raise BadIP(_('The IP address "%s" is badly formed') % ip) if q1 < 0 or q2 < 0 or q3 < 0 or q4 < 0: - raise BadIP(_('The IP address "%s" is badly formed' % ip)) + raise BadIP(_('The IP address "%s" is badly formed') % ip) elif q1 > 255 or q2 > 255 or q3 > 255 or q4 > 255: - raise BadIP(_('The IP address "%s" is badly formed' % ip)) + raise BadIP(_('The IP address "%s" is badly formed') % ip) return cls(q1, q2, q3, q4) def quadrants(self): diff --git a/deluge/tests/test_json_api.py b/deluge/tests/test_json_api.py index ef21e94106..d8e382f381 100644 --- a/deluge/tests/test_json_api.py +++ b/deluge/tests/test_json_api.py @@ -33,7 +33,7 @@ class TestJSON: async def test_get_remote_methods(self): json = JSON() methods = await json.get_remote_methods() - assert type(methods) == tuple + assert isinstance(methods, tuple) assert len(methods) > 0 def test_render_fail_disconnected(self): diff --git a/deluge/tests/test_log.py b/deluge/tests/test_log.py index f0dcbee86a..24bd27f9c5 100644 --- a/deluge/tests/test_log.py +++ b/deluge/tests/test_log.py @@ -28,7 +28,7 @@ def test_old_log_deprecation_warning(self): # Cause all warnings to always be triggered. warnings.simplefilter('always') LOG.debug('foo') - assert w[-1].category == DeprecationWarning + assert w[-1].category is DeprecationWarning # def test_twisted_error_log(self): # from twisted.internet import defer diff --git a/deluge/tests/test_ui_entry.py b/deluge/tests/test_ui_entry.py index 9a1330ed50..b0948f4672 100644 --- a/deluge/tests/test_ui_entry.py +++ b/deluge/tests/test_ui_entry.py @@ -330,7 +330,7 @@ def set_up(self): return UIWithDaemonBaseTestCase.set_up(self) def patch_arg_command(self, command): - if type(command) == str: + if isinstance(command, str): command = [command] username, password = get_localhost_auth() self.patch( diff --git a/deluge/tests/test_web_api.py b/deluge/tests/test_web_api.py index 4dfa58d7c3..eac7163770 100644 --- a/deluge/tests/test_web_api.py +++ b/deluge/tests/test_web_api.py @@ -34,7 +34,7 @@ def test_connect(self, client): d = self.deluge_web.web_api.connect(self.host_id) def on_connect(result): - assert type(result) == tuple + assert isinstance(result, tuple) assert len(result) > 0 return result diff --git a/deluge/ui/console/cmdline/command.py b/deluge/ui/console/cmdline/command.py index 63dc9265af..1de0357220 100644 --- a/deluge/ui/console/cmdline/command.py +++ b/deluge/ui/console/cmdline/command.py @@ -201,7 +201,7 @@ def add_subparser(self, subparsers): for cmd_name in sorted([self.name] + self.aliases): if cmd_name not in subparsers._name_parser_map: if cmd_name in self.aliases: - opts['help'] = _('`%s` alias' % self.name) + opts['help'] = _('`%s` alias') % self.name parser = subparsers.add_parser(cmd_name, **opts) break diff --git a/deluge/ui/console/cmdline/commands/info.py b/deluge/ui/console/cmdline/commands/info.py index 7ea9a6773e..09b2458eed 100644 --- a/deluge/ui/console/cmdline/commands/info.py +++ b/deluge/ui/console/cmdline/commands/info.py @@ -111,7 +111,7 @@ def add_arguments(self, parser): '--state', action='store', dest='state', - help=_('Show torrents with state STATE: %s.' % (', '.join(STATES))), + help=_('Show torrents with state STATE: %s.') % (', '.join(STATES)), ) parser.add_argument( '--sort', diff --git a/deluge/ui/gtk3/addtorrentdialog.py b/deluge/ui/gtk3/addtorrentdialog.py index aa71cc434c..7e752bc990 100644 --- a/deluge/ui/gtk3/addtorrentdialog.py +++ b/deluge/ui/gtk3/addtorrentdialog.py @@ -236,8 +236,9 @@ def show_already_added_dialog(self, count): _('Duplicate torrent(s)'), _( 'You cannot add the same torrent twice.' - ' %d torrents were already added.' % count - ), + ' %d torrents were already added.' + ) + % count, self.dialog, ).run() @@ -585,9 +586,9 @@ def build_priorities(self, _iter, priorities): self.files_treestore.iter_children(_iter), priorities ) elif not self.files_treestore.get_value(_iter, 1).endswith('/'): - priorities[ - self.files_treestore.get_value(_iter, 3) - ] = self.files_treestore.get_value(_iter, 0) + priorities[self.files_treestore.get_value(_iter, 3)] = ( + self.files_treestore.get_value(_iter, 0) + ) _iter = self.files_treestore.iter_next(_iter) return priorities diff --git a/deluge/ui/gtk3/gtkui.py b/deluge/ui/gtk3/gtkui.py index 73a7c97aea..a0c5d8942f 100644 --- a/deluge/ui/gtk3/gtkui.py +++ b/deluge/ui/gtk3/gtkui.py @@ -324,8 +324,7 @@ def _on_reactor_start(self): err_msg = _( 'Only Thin Client mode is available due to libtorrent import error: %s\n' 'To use Standalone mode, please see logs for error details.' - % (str(ex)) - ) + ) % (str(ex)) except ImportError as ex: log.exception(ex) diff --git a/deluge/ui/gtk3/preferences.py b/deluge/ui/gtk3/preferences.py index cd67a5b09d..59b22264b2 100644 --- a/deluge/ui/gtk3/preferences.py +++ b/deluge/ui/gtk3/preferences.py @@ -226,20 +226,20 @@ def load_languages(self): self.language_checkbox = self.builder.get_object('checkbutton_language') lang_model = self.language_combo.get_model() langs = get_languages() - index = -1 - for i, l in enumerate(langs): - lang_code, name = l + lang_idx = -1 + for idx, lang in enumerate(langs): + lang_code, name = lang lang_model.append([lang_code, name]) if self.gtkui_config['language'] == lang_code: - index = i + lang_idx = idx if self.gtkui_config['language'] is None: self.language_checkbox.set_active(True) self.language_combo.set_visible(False) else: self.language_combo.set_visible(True) - if index != -1: - self.language_combo.set_active(index) + if lang_idx != -1: + self.language_combo.set_active(lang_idx) def __del__(self): del self.gtkui_config @@ -647,15 +647,15 @@ def set_config(self, hide=False): 'chk_move_completed' ).get_active() - new_core_config[ - 'download_location' - ] = self.download_location_path_chooser.get_text() - new_core_config[ - 'move_completed_path' - ] = self.move_completed_path_chooser.get_text() - new_core_config[ - 'torrentfiles_location' - ] = self.copy_torrent_files_path_chooser.get_text() + new_core_config['download_location'] = ( + self.download_location_path_chooser.get_text() + ) + new_core_config['move_completed_path'] = ( + self.move_completed_path_chooser.get_text() + ) + new_core_config['torrentfiles_location'] = ( + self.copy_torrent_files_path_chooser.get_text() + ) new_core_config['prioritize_first_last_pieces'] = self.builder.get_object( 'chk_prioritize_first_last_pieces' ).get_active() @@ -956,7 +956,7 @@ def on_response(response): mode = _('Thinclient') if was_standalone else _('Standalone') dialog = YesNoDialog( _('Switching Deluge Client Mode...'), - _('Do you want to restart to use %s mode?' % mode), + _('Do you want to restart to use %s mode?') % mode, ) dialog.run().addCallback(on_response) @@ -1381,7 +1381,9 @@ async def on_accounts_add_clicked(self, widget): except Exception as ex: return ErrorDialog( _('Error Adding Account'), - _(f'An error occurred while adding account: {account}'), + _('An error occurred while adding account: {account}').format( + account=account + ), parent=self.pref_dialog, details=ex, ).run() @@ -1434,8 +1436,8 @@ def on_accounts_delete_clicked(self, widget): header = _('Remove Account') text = _( 'Are you sure you want to remove the account with the ' - 'username "%(username)s"?' % {'username': username} - ) + 'username "%(username)s"?' + ) % {'username': username} dialog = YesNoDialog(header, text, parent=self.pref_dialog) def dialog_finished(response_id): From 0d72195281d0855423f836ca0f85a02feafd6c50 Mon Sep 17 00:00:00 2001 From: Calum Lind Date: Sun, 8 Sep 2024 12:36:41 +0100 Subject: [PATCH 09/10] [Lint] Format code with ruff `pre-commit run --all-files` --- deluge/_libtorrent.py | 1 + deluge/common.py | 1 + deluge/config.py | 1 + deluge/core/alertmanager.py | 1 + deluge/core/daemon.py | 1 + deluge/core/pluginmanager.py | 1 + deluge/core/rpcserver.py | 13 +- deluge/core/torrent.py | 23 ++-- deluge/core/torrentmanager.py | 1 + deluge/decorators.py | 2 +- deluge/event.py | 1 + deluge/log.py | 1 + deluge/pluginmanagerbase.py | 1 + deluge/plugins/AutoAdd/deluge_autoadd/core.py | 2 +- deluge/plugins/Label/deluge_label/core.py | 3 +- .../Scheduler/deluge_scheduler/gtkui.py | 24 ++-- deluge/plugins/Stats/deluge_stats/gtkui.py | 4 +- deluge/plugins/init.py | 1 + deluge/tests/test_alertmanager.py | 6 +- deluge/tests/test_ui_common.py | 6 +- deluge/ui/common.py | 1 + .../modes/preferences/preference_panes.py | 18 +-- .../console/modes/torrentlist/queue_mode.py | 6 +- .../console/modes/torrentlist/torrentlist.py | 10 +- deluge/ui/console/widgets/inputpane.py | 4 +- deluge/ui/console/widgets/popup.py | 8 +- deluge/ui/gtk3/common.py | 1 + deluge/ui/gtk3/path_combo_chooser.py | 124 +++++++++--------- deluge/ui/gtk3/torrentdetails.py | 1 + deluge/ui/gtk3/torrentview.py | 3 +- deluge/ui/ui_entry.py | 1 + gen_web_gettext.py | 4 +- msgfmt.py | 1 + setup.py | 4 +- 34 files changed, 148 insertions(+), 132 deletions(-) diff --git a/deluge/_libtorrent.py b/deluge/_libtorrent.py index 642855c526..182a5a0578 100644 --- a/deluge/_libtorrent.py +++ b/deluge/_libtorrent.py @@ -14,6 +14,7 @@ >>> from deluge._libtorrent import lt """ + from deluge.common import VersionSplit, get_version from deluge.error import LibtorrentImportError diff --git a/deluge/common.py b/deluge/common.py index 256f1f7831..7b76d245c2 100644 --- a/deluge/common.py +++ b/deluge/common.py @@ -7,6 +7,7 @@ # """Common functions for various parts of Deluge to use.""" + import base64 import binascii import functools diff --git a/deluge/config.py b/deluge/config.py index c5cb3122be..f32038ed14 100644 --- a/deluge/config.py +++ b/deluge/config.py @@ -38,6 +38,7 @@ version as this will be done internally. """ + import json import logging import os diff --git a/deluge/core/alertmanager.py b/deluge/core/alertmanager.py index cf541f0152..24f47fddda 100644 --- a/deluge/core/alertmanager.py +++ b/deluge/core/alertmanager.py @@ -14,6 +14,7 @@ `:mod:EventManager` for similar functionality. """ + import contextlib import logging import threading diff --git a/deluge/core/daemon.py b/deluge/core/daemon.py index 0185dd8cb0..01d1c75054 100644 --- a/deluge/core/daemon.py +++ b/deluge/core/daemon.py @@ -7,6 +7,7 @@ # """The Deluge daemon""" + import logging import os import socket diff --git a/deluge/core/pluginmanager.py b/deluge/core/pluginmanager.py index 0482b16e78..63c33c78bc 100644 --- a/deluge/core/pluginmanager.py +++ b/deluge/core/pluginmanager.py @@ -8,6 +8,7 @@ """PluginManager for Core""" + import logging from twisted.internet import defer diff --git a/deluge/core/rpcserver.py b/deluge/core/rpcserver.py index 81ab2e0a59..d84ac3a47a 100644 --- a/deluge/core/rpcserver.py +++ b/deluge/core/rpcserver.py @@ -7,6 +7,7 @@ # """RPCServer Module""" + import logging import os import sys @@ -46,13 +47,11 @@ @overload -def export(func: TCallable) -> TCallable: - ... +def export(func: TCallable) -> TCallable: ... @overload -def export(auth_level: int) -> Callable[[TCallable], TCallable]: - ... +def export(auth_level: int) -> Callable[[TCallable], TCallable]: ... def export(auth_level=AUTH_LEVEL_DEFAULT): @@ -274,9 +273,9 @@ def send_error(): raise IncompatibleClient(deluge.common.get_version()) ret = component.get('AuthManager').authorize(*args, **kwargs) if ret: - self.factory.authorized_sessions[ - self.transport.sessionno - ] = self.AuthLevel(ret, args[0]) + self.factory.authorized_sessions[self.transport.sessionno] = ( + self.AuthLevel(ret, args[0]) + ) self.factory.session_protocols[self.transport.sessionno] = self except Exception as ex: send_error() diff --git a/deluge/core/torrent.py b/deluge/core/torrent.py index 57ec26f37a..dbcf8f101c 100644 --- a/deluge/core/torrent.py +++ b/deluge/core/torrent.py @@ -1138,9 +1138,8 @@ def _create_status_funcs(self): 'download_location': lambda: self.options['download_location'], 'seeds_peers_ratio': lambda: -1.0 if self.status.num_incomplete == 0 - else ( # Use -1.0 to signify infinity - self.status.num_complete / self.status.num_incomplete - ), + # Use -1.0 to signify infinity + else (self.status.num_complete / self.status.num_incomplete), 'seed_rank': lambda: self.status.seed_rank, 'state': lambda: self.state, 'stop_at_ratio': lambda: self.options['stop_at_ratio'], @@ -1544,20 +1543,18 @@ def _get_pieces_info(self): self.status.pieces, self.handle.piece_availability() ): if piece: - pieces.append(3) # Completed. + # Completed. + pieces.append(3) elif avail_piece: - pieces.append( - 1 - ) # Available, just not downloaded nor being downloaded. + # Available, just not downloaded nor being downloaded. + pieces.append(1) else: - pieces.append( - 0 - ) # Missing, no known peer with piece, or not asked for yet. + # Missing, no known peer with piece, or not asked for yet. + pieces.append(0) for peer_info in self.handle.get_peer_info(): if peer_info.downloading_piece_index >= 0: - pieces[ - peer_info.downloading_piece_index - ] = 2 # Being downloaded from peer. + # Being downloaded from peer. + pieces[peer_info.downloading_piece_index] = 2 return pieces diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index c43a7a262d..1233553a8f 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -7,6 +7,7 @@ # """TorrentManager handles Torrent objects""" + import datetime import logging import operator diff --git a/deluge/decorators.py b/deluge/decorators.py index 92e3ecf59b..7b1896c13a 100644 --- a/deluge/decorators.py +++ b/deluge/decorators.py @@ -206,7 +206,7 @@ def addCallbacks(self, *args, **kwargs): # noqa: N802 def maybe_coroutine( - f: Callable[..., Coroutine[Any, Any, _RetT]] + f: Callable[..., Coroutine[Any, Any, _RetT]], ) -> 'Callable[..., defer.Deferred[_RetT]]': """Wraps a coroutine function to make it usable as a normal function that returns a Deferred.""" diff --git a/deluge/event.py b/deluge/event.py index 38fc32ff82..c51fca4de1 100644 --- a/deluge/event.py +++ b/deluge/event.py @@ -13,6 +13,7 @@ and subsequently emitted to the clients. """ + known_events = {} diff --git a/deluge/log.py b/deluge/log.py index ef31f4ddc6..fc1b330774 100644 --- a/deluge/log.py +++ b/deluge/log.py @@ -8,6 +8,7 @@ # """Logging functions""" + import inspect import logging import logging.handlers diff --git a/deluge/pluginmanagerbase.py b/deluge/pluginmanagerbase.py index 835dbb2686..52dae615bc 100644 --- a/deluge/pluginmanagerbase.py +++ b/deluge/pluginmanagerbase.py @@ -8,6 +8,7 @@ """PluginManagerBase""" + import email import logging import os.path diff --git a/deluge/plugins/AutoAdd/deluge_autoadd/core.py b/deluge/plugins/AutoAdd/deluge_autoadd/core.py index 271d5f0d9f..4b1a27fa57 100644 --- a/deluge/plugins/AutoAdd/deluge_autoadd/core.py +++ b/deluge/plugins/AutoAdd/deluge_autoadd/core.py @@ -292,7 +292,7 @@ def on_torrent_added(torrent_id, filename, filepath): if 'Label' in component.get('CorePluginManager').get_enabled_plugins(): if watchdir.get('label_toggle', True) and watchdir.get('label'): label = component.get('CorePlugin.Label') - if not watchdir['label'] in label.get_labels(): + if watchdir['label'] not in label.get_labels(): label.add(watchdir['label']) try: label.set_torrent(torrent_id, watchdir['label']) diff --git a/deluge/plugins/Label/deluge_label/core.py b/deluge/plugins/Label/deluge_label/core.py index c28490b465..8af9805933 100644 --- a/deluge/plugins/Label/deluge_label/core.py +++ b/deluge/plugins/Label/deluge_label/core.py @@ -14,6 +14,7 @@ torrent-label core plugin. adds a status field for tracker. """ + import logging import re @@ -182,7 +183,7 @@ def add(self, label_id): RE_VALID.match(label_id), _('Invalid label, valid characters:[a-z0-9_-]') ) check_input(label_id, _('Empty Label')) - check_input(not (label_id in self.labels), _('Label already exists')) + check_input(label_id not in self.labels, _('Label already exists')) self.labels[label_id] = dict(OPTIONS_DEFAULTS) self.config.save() diff --git a/deluge/plugins/Scheduler/deluge_scheduler/gtkui.py b/deluge/plugins/Scheduler/deluge_scheduler/gtkui.py index 16222c835c..a5a92739a3 100644 --- a/deluge/plugins/Scheduler/deluge_scheduler/gtkui.py +++ b/deluge/plugins/Scheduler/deluge_scheduler/gtkui.py @@ -192,12 +192,12 @@ def on_state_deferred(state): def disable(self): component.get('Preferences').remove_page(_('Scheduler')) # Reset statusbar dict. - self.statusbar.config_value_changed_dict[ - 'max_download_speed' - ] = self.statusbar._on_max_download_speed - self.statusbar.config_value_changed_dict[ - 'max_upload_speed' - ] = self.statusbar._on_max_upload_speed + self.statusbar.config_value_changed_dict['max_download_speed'] = ( + self.statusbar._on_max_download_speed + ) + self.statusbar.config_value_changed_dict['max_upload_speed'] = ( + self.statusbar._on_max_upload_speed + ) # Remove statusbar item. self.statusbar.remove_item(self.status_item) del self.status_item @@ -246,12 +246,12 @@ def on_scheduler_event(self, state): # Skip error due to Plugin being enabled before statusbar items created on startup. pass else: - self.statusbar.config_value_changed_dict[ - 'max_download_speed' - ] = self.statusbar._on_max_download_speed - self.statusbar.config_value_changed_dict[ - 'max_upload_speed' - ] = self.statusbar._on_max_upload_speed + self.statusbar.config_value_changed_dict['max_download_speed'] = ( + self.statusbar._on_max_download_speed + ) + self.statusbar.config_value_changed_dict['max_upload_speed'] = ( + self.statusbar._on_max_upload_speed + ) def update_config_values(config): try: diff --git a/deluge/plugins/Stats/deluge_stats/gtkui.py b/deluge/plugins/Stats/deluge_stats/gtkui.py index 39c1d4c37d..09a8f11587 100644 --- a/deluge/plugins/Stats/deluge_stats/gtkui.py +++ b/deluge/plugins/Stats/deluge_stats/gtkui.py @@ -193,7 +193,9 @@ def set_colors(self, colors): self.colors = colors # Fake switch page to update the graph colors (HACKY) self._on_notebook_switch_page( - self.notebook, None, self.notebook.get_current_page() # This is unused + self.notebook, + None, + self.notebook.get_current_page(), # This is unused ) def _on_intervals_changed(self, intervals): diff --git a/deluge/plugins/init.py b/deluge/plugins/init.py index 56b31977df..cab1036abb 100644 --- a/deluge/plugins/init.py +++ b/deluge/plugins/init.py @@ -9,6 +9,7 @@ """ This base class is used in plugin's __init__ for the plugin entry points. """ + import logging log = logging.getLogger(__name__) diff --git a/deluge/tests/test_alertmanager.py b/deluge/tests/test_alertmanager.py index bfab1b345d..bb84610a50 100644 --- a/deluge/tests/test_alertmanager.py +++ b/deluge/tests/test_alertmanager.py @@ -61,8 +61,7 @@ def set_up(self, component): component.start(['AlertManager']) def test_register_handler(self): - def handler(alert): - ... + def handler(alert): ... self.am.register_handler('dummy1', handler) self.am.register_handler('dummy2_alert', handler) @@ -97,8 +96,7 @@ async def test_pause_not_pop_alert( assert len(self.am.session.alerts) == 2 def test_deregister_handler(self): - def handler(alert): - ... + def handler(alert): ... self.am.register_handler('dummy1', handler) self.am.register_handler('dummy2_alert', handler) diff --git a/deluge/tests/test_ui_common.py b/deluge/tests/test_ui_common.py index 87a4a2c042..5412713bb5 100644 --- a/deluge/tests/test_ui_common.py +++ b/deluge/tests/test_ui_common.py @@ -229,7 +229,11 @@ def test_bittorrent_v2_hybrid_path(self): ti = TorrentInfo(filename, filetree=1, force_bt_version=1) assert ti.files_tree == files_tree del files_tree['torrent_test']['.pad'] - files_tree['torrent_test']['還在一個人無聊嗎~還不趕緊上來聊天美.txt'] = (1, 32, True) + files_tree['torrent_test']['還在一個人無聊嗎~還不趕緊上來聊天美.txt'] = ( + 1, + 32, + True, + ) ti = TorrentInfo(filename, filetree=1, force_bt_version=2) assert ti.files_tree == files_tree diff --git a/deluge/ui/common.py b/deluge/ui/common.py index 64d5ca2162..b3f8bc7137 100644 --- a/deluge/ui/common.py +++ b/deluge/ui/common.py @@ -10,6 +10,7 @@ """ The ui common module contains methods and classes that are deemed useful for all the interfaces. """ + import logging import os from hashlib import sha1 as sha diff --git a/deluge/ui/console/modes/preferences/preference_panes.py b/deluge/ui/console/modes/preferences/preference_panes.py index 61e86ae0be..8ae0deaca8 100644 --- a/deluge/ui/console/modes/preferences/preference_panes.py +++ b/deluge/ui/console/modes/preferences/preference_panes.py @@ -109,13 +109,13 @@ def add_config_values(self, conf_dict): elif ipt.name == 'proxy_port': conf_dict.setdefault('proxy', {})['port'] = ipt.get_value() elif ipt.name == 'proxy_hostnames': - conf_dict.setdefault('proxy', {})[ - 'proxy_hostnames' - ] = ipt.get_value() + conf_dict.setdefault('proxy', {})['proxy_hostnames'] = ( + ipt.get_value() + ) elif ipt.name == 'proxy_peer_connections': - conf_dict.setdefault('proxy', {})[ - 'proxy_peer_connections' - ] = ipt.get_value() + conf_dict.setdefault('proxy', {})['proxy_peer_connections'] = ( + ipt.get_value() + ) elif ipt.name == 'proxy_tracker_connections': conf_dict.setdefault('proxy', {})[ 'proxy_tracker_connections' @@ -123,9 +123,9 @@ def add_config_values(self, conf_dict): elif ipt.name == 'force_proxy': conf_dict.setdefault('proxy', {})['force_proxy'] = ipt.get_value() elif ipt.name == 'anonymous_mode': - conf_dict.setdefault('proxy', {})[ - 'anonymous_mode' - ] = ipt.get_value() + conf_dict.setdefault('proxy', {})['anonymous_mode'] = ( + ipt.get_value() + ) else: conf_dict[ipt.name] = ipt.get_value() diff --git a/deluge/ui/console/modes/torrentlist/queue_mode.py b/deluge/ui/console/modes/torrentlist/queue_mode.py index 33af0135d7..4ec86133ac 100644 --- a/deluge/ui/console/modes/torrentlist/queue_mode.py +++ b/deluge/ui/console/modes/torrentlist/queue_mode.py @@ -42,9 +42,9 @@ def __init__(self, torrentslist, torrent_ids): self.torrent_ids = torrent_ids def set_statusbar_args(self, statusbar_args): - statusbar_args[ - 'bottombar' - ] = '{!black,white!}Queue mode: change queue position of selected torrents.' + statusbar_args['bottombar'] = ( + '{!black,white!}Queue mode: change queue position of selected torrents.' + ) statusbar_args['bottombar_help'] = ' Press [h] for help' def update_cursor(self): diff --git a/deluge/ui/console/modes/torrentlist/torrentlist.py b/deluge/ui/console/modes/torrentlist/torrentlist.py index d3c32ec0e7..9a55e19659 100644 --- a/deluge/ui/console/modes/torrentlist/torrentlist.py +++ b/deluge/ui/console/modes/torrentlist/torrentlist.py @@ -219,11 +219,11 @@ def refresh(self, lines=None): # Update the status bars statusbar_args = {'scr': self.stdscr, 'bottombar_help': True} if self.torrentview.curr_filter is not None: - statusbar_args[ - 'topbar' - ] = '{} {{!filterstatus!}}Current filter: {}'.format( - self.statusbars.topbar, - self.torrentview.curr_filter, + statusbar_args['topbar'] = ( + '{} {{!filterstatus!}}Current filter: {}'.format( + self.statusbars.topbar, + self.torrentview.curr_filter, + ) ) if self.minor_mode: diff --git a/deluge/ui/console/widgets/inputpane.py b/deluge/ui/console/widgets/inputpane.py index d8d217501d..f9c4f6f6d3 100644 --- a/deluge/ui/console/widgets/inputpane.py +++ b/deluge/ui/console/widgets/inputpane.py @@ -49,7 +49,7 @@ def __init__( border_off_east=0, border_off_south=0, active_wrap=False, - **kwargs + **kwargs, ): InputKeyHandler.__init__(self) self.inputs = [] @@ -155,7 +155,7 @@ def add_text_input(self, name, message, value='', col='+1', **kwargs): self.visible_content_pane_width, value, col=col, - **kwargs + **kwargs, ) ) diff --git a/deluge/ui/console/widgets/popup.py b/deluge/ui/console/widgets/popup.py index 07d667d272..c17ca8a86a 100644 --- a/deluge/ui/console/widgets/popup.py +++ b/deluge/ui/console/widgets/popup.py @@ -69,7 +69,7 @@ def __init__( close_cb=None, encoding=None, base_popup=None, - **kwargs + **kwargs, ): """ Init a new popup. The default constructor will handle sizing and borders and the like. @@ -212,7 +212,7 @@ def __init__( input_cb=None, allow_rearrange=False, immediate_action=False, - **kwargs + **kwargs, ): """ Args: @@ -262,7 +262,7 @@ def add_line( foreground=None, selectable=True, selected=False, - **kwargs + **kwargs, ): hotkey = None self.cb_arg[name] = cb_arg @@ -335,7 +335,7 @@ def __init__( align=ALIGN.DEFAULT, height_req=0.75, width_req=0.5, - **kwargs + **kwargs, ): self.message = message Popup.__init__( diff --git a/deluge/ui/gtk3/common.py b/deluge/ui/gtk3/common.py index 42a14b407d..a588db66e6 100644 --- a/deluge/ui/gtk3/common.py +++ b/deluge/ui/gtk3/common.py @@ -6,6 +6,7 @@ # See LICENSE for more details. # """Common functions for various parts of gtkui to use.""" + import contextlib import logging import os diff --git a/deluge/ui/gtk3/path_combo_chooser.py b/deluge/ui/gtk3/path_combo_chooser.py index aeb4c7a9e2..6ef8fafc35 100755 --- a/deluge/ui/gtk3/path_combo_chooser.py +++ b/deluge/ui/gtk3/path_combo_chooser.py @@ -322,20 +322,20 @@ def __init__(self): self.paths_without_trailing_path_sep = False # Add signal handlers - self.signal_handlers[ - 'on_stored_values_treeview_mouse_button_press_event' - ] = self.on_treeview_mouse_button_press_event + self.signal_handlers['on_stored_values_treeview_mouse_button_press_event'] = ( + self.on_treeview_mouse_button_press_event + ) - self.signal_handlers[ - 'on_stored_values_treeview_key_press_event' - ] = self.on_stored_values_treeview_key_press_event - self.signal_handlers[ - 'on_stored_values_treeview_key_release_event' - ] = self.on_stored_values_treeview_key_release_event + self.signal_handlers['on_stored_values_treeview_key_press_event'] = ( + self.on_stored_values_treeview_key_press_event + ) + self.signal_handlers['on_stored_values_treeview_key_release_event'] = ( + self.on_stored_values_treeview_key_release_event + ) - self.signal_handlers[ - 'on_cellrenderertext_edited' - ] = self.on_cellrenderertext_edited + self.signal_handlers['on_cellrenderertext_edited'] = ( + self.on_cellrenderertext_edited + ) def on_cellrenderertext_edited(self, cellrenderertext, path, new_text): """ @@ -447,9 +447,7 @@ def on_stored_values_treeview_key_release_event(self, widget, event): return True # Add current value to saved list elif is_ascii_value(keyval, 's'): - super( - PathChooserComboBox, self - ).add_current_value_to_saved_list() # pylint: disable=bad-super-call + super(PathChooserComboBox, self).add_current_value_to_saved_list() # pylint: disable=bad-super-call return True # Edit selected value elif is_ascii_value(keyval, 'e'): @@ -465,17 +463,17 @@ def __init__(self): self.completion_scrolled_window = self.builder.get_object( 'completion_scrolled_window' ) - self.signal_handlers[ - 'on_completion_treeview_key_press_event' - ] = self.on_completion_treeview_key_press_event - self.signal_handlers[ - 'on_completion_treeview_motion_notify_event' - ] = self.on_completion_treeview_motion_notify_event + self.signal_handlers['on_completion_treeview_key_press_event'] = ( + self.on_completion_treeview_key_press_event + ) + self.signal_handlers['on_completion_treeview_motion_notify_event'] = ( + self.on_completion_treeview_motion_notify_event + ) # Add super class signal handler - self.signal_handlers[ - 'on_completion_treeview_mouse_button_press_event' - ] = super().on_treeview_mouse_button_press_event + self.signal_handlers['on_completion_treeview_mouse_button_press_event'] = ( + super().on_treeview_mouse_button_press_event + ) def reduce_values(self, prefix): """ @@ -756,23 +754,23 @@ def __init__(self, builder, path_entry, max_visible_rows, popup_alignment_widget self.popup_buttonbox = self.builder.get_object('buttonbox') # Add signal handlers - self.signal_handlers[ - 'on_buttonbox_key_press_event' - ] = self.on_buttonbox_key_press_event - self.signal_handlers[ - 'on_stored_values_treeview_scroll_event' - ] = self.on_scroll_event - self.signal_handlers[ - 'on_button_toggle_dropdown_scroll_event' - ] = self.on_scroll_event + self.signal_handlers['on_buttonbox_key_press_event'] = ( + self.on_buttonbox_key_press_event + ) + self.signal_handlers['on_stored_values_treeview_scroll_event'] = ( + self.on_scroll_event + ) + self.signal_handlers['on_button_toggle_dropdown_scroll_event'] = ( + self.on_scroll_event + ) self.signal_handlers['on_entry_text_scroll_event'] = self.on_scroll_event - self.signal_handlers[ - 'on_stored_values_popup_window_focus_out_event' - ] = self.on_stored_values_popup_window_focus_out_event + self.signal_handlers['on_stored_values_popup_window_focus_out_event'] = ( + self.on_stored_values_popup_window_focus_out_event + ) # For when clicking outside the popup - self.signal_handlers[ - 'on_stored_values_popup_window_button_press_event' - ] = self.on_popup_window_button_press_event + self.signal_handlers['on_stored_values_popup_window_button_press_event'] = ( + self.on_popup_window_button_press_event + ) # Buttons for manipulating the list self.signal_handlers['on_button_add_clicked'] = self.on_button_add_clicked @@ -780,12 +778,12 @@ def __init__(self, builder, path_entry, max_visible_rows, popup_alignment_widget self.signal_handlers['on_button_remove_clicked'] = self.on_button_remove_clicked self.signal_handlers['on_button_up_clicked'] = self.on_button_up_clicked self.signal_handlers['on_button_down_clicked'] = self.on_button_down_clicked - self.signal_handlers[ - 'on_button_default_clicked' - ] = self.on_button_default_clicked - self.signal_handlers[ - 'on_button_properties_clicked' - ] = self.path_entry._on_button_properties_clicked + self.signal_handlers['on_button_default_clicked'] = ( + self.on_button_default_clicked + ) + self.signal_handlers['on_button_properties_clicked'] = ( + self.path_entry._on_button_properties_clicked + ) def popup(self): """ @@ -911,17 +909,17 @@ def __init__(self, builder, path_entry, max_visible_rows): CompletionList.__init__(self) # Add signal handlers - self.signal_handlers[ - 'on_completion_treeview_scroll_event' - ] = self.on_scroll_event - self.signal_handlers[ - 'on_completion_popup_window_focus_out_event' - ] = self.on_completion_popup_window_focus_out_event + self.signal_handlers['on_completion_treeview_scroll_event'] = ( + self.on_scroll_event + ) + self.signal_handlers['on_completion_popup_window_focus_out_event'] = ( + self.on_completion_popup_window_focus_out_event + ) # For when clicking outside the popup - self.signal_handlers[ - 'on_completion_popup_window_button_press_event' - ] = self.on_popup_window_button_press_event + self.signal_handlers['on_completion_popup_window_button_press_event'] = ( + self.on_popup_window_button_press_event + ) def popup(self): """ @@ -986,15 +984,15 @@ def __init__(self, builder, path_entry, max_visible_rows): self.auto_complete_enabled = True self.signal_handlers = self.completion_popup.signal_handlers - self.signal_handlers[ - 'on_completion_popup_window_key_press_event' - ] = self.on_completion_popup_window_key_press_event - self.signal_handlers[ - 'on_entry_text_delete_text' - ] = self.on_entry_text_delete_text - self.signal_handlers[ - 'on_entry_text_insert_text' - ] = self.on_entry_text_insert_text + self.signal_handlers['on_completion_popup_window_key_press_event'] = ( + self.on_completion_popup_window_key_press_event + ) + self.signal_handlers['on_entry_text_delete_text'] = ( + self.on_entry_text_delete_text + ) + self.signal_handlers['on_entry_text_insert_text'] = ( + self.on_entry_text_insert_text + ) self.accelerator_string = Gtk.accelerator_name(Gdk.KEY_Tab, 0) def on_entry_text_insert_text(self, entry, new_text, new_text_length, position): diff --git a/deluge/ui/gtk3/torrentdetails.py b/deluge/ui/gtk3/torrentdetails.py index 08c37a1de8..982a3d85e5 100644 --- a/deluge/ui/gtk3/torrentdetails.py +++ b/deluge/ui/gtk3/torrentdetails.py @@ -8,6 +8,7 @@ """The torrent details component shows info about the selected torrent.""" + import logging from collections import namedtuple diff --git a/deluge/ui/gtk3/torrentview.py b/deluge/ui/gtk3/torrentview.py index 16de16ea7e..27771d5744 100644 --- a/deluge/ui/gtk3/torrentview.py +++ b/deluge/ui/gtk3/torrentview.py @@ -7,6 +7,7 @@ # """The torrent view component that lists all torrents in the session.""" + import logging from locale import strcoll @@ -577,7 +578,7 @@ def send_status_request(self, columns=None, select_row=False): status_keys = self.set_columns_to_update(columns) # If there is nothing in status_keys then we must not continue - if status_keys is []: + if status_keys == []: return # Remove duplicates from status_key list diff --git a/deluge/ui/ui_entry.py b/deluge/ui/ui_entry.py index e185fda33b..f0a9831069 100644 --- a/deluge/ui/ui_entry.py +++ b/deluge/ui/ui_entry.py @@ -11,6 +11,7 @@ # user runs the command 'deluge'. """Main starting point for Deluge""" + import argparse import logging import os diff --git a/gen_web_gettext.py b/gen_web_gettext.py index 80186e9381..37ffba71ca 100755 --- a/gen_web_gettext.py +++ b/gen_web_gettext.py @@ -60,7 +60,7 @@ def check_missing_markup(js_dir): # Ignore string that contains only digits or specificied strings in skip. if ( not string - or string.split('\'')[1].isdigit() + or string.split("'")[1].isdigit() or any(x in string for x in skip) ): continue @@ -82,7 +82,7 @@ def check_missing_markup(js_dir): def create_gettext_js(js_dir): - string_re = re.compile('_\\(\'(.*?)\'\\)') + string_re = re.compile("_\\('(.*?)'\\)") strings = {} for root, dnames, files in os.walk(js_dir): for filename in files: diff --git a/msgfmt.py b/msgfmt.py index 0d5367c3b6..e5d62d0028 100755 --- a/msgfmt.py +++ b/msgfmt.py @@ -24,6 +24,7 @@ --version Display version information and exit. """ + import array import ast import getopt diff --git a/setup.py b/setup.py index ef70f20b0a..6161d60d03 100755 --- a/setup.py +++ b/setup.py @@ -323,7 +323,7 @@ def finalize_options(self): self.set_undefined_options('clean', ('all', 'all')) def run(self): - print('Cleaning the plugin\'s folders...') + print("Cleaning the plugin's folders...") plugin_path = 'deluge/plugins/*' @@ -332,7 +332,7 @@ def run(self): c = 'cd ' + path + ' && ' + sys.executable + ' setup.py clean' if self.all: c += ' -a' - print('Calling \'%s\'' % c) + print("Calling '%s'" % c) os.system(c) # Delete the .eggs From d064ad06c5f93c9596ae92238bf50e447a06af4c Mon Sep 17 00:00:00 2001 From: Calum Lind Date: Sun, 8 Sep 2024 18:32:06 +0100 Subject: [PATCH 10/10] [Docs] Replace black/flake8 with pre-commit --- docs/source/contributing/code.md | 23 +++++++++++------------ requirements-dev.txt | 7 ------- requirements-tests.txt | 5 ----- 3 files changed, 11 insertions(+), 24 deletions(-) diff --git a/docs/source/contributing/code.md b/docs/source/contributing/code.md index 20ccb820b9..f2e78ee333 100644 --- a/docs/source/contributing/code.md +++ b/docs/source/contributing/code.md @@ -51,18 +51,19 @@ time. They are both run with [pre-commit]. We follow [PEP8](http://www.python.org/dev/peps/pep-0008/) and [Python Code Style](http://docs.python-guide.org/en/latest/writing/style/) -which is adhered to with [Black]. +which is adhered to with [ruff]. -- Code '''must''' pass [Black], [flake8] and [isort] source code checkers. - (Optionally [Pylint]) +- Code **must** pass [ruff] linting and formatting with [pre-commit]: - flake8 deluge - isort -rc -df deluge - pylint deluge - pylint deluge/plugins/\*/deluge/ + pre-commit run --all-files -- Using the [pre-commit] application can aid in identifying issues while - creating git commits. +- Optionally [Pylint]: + + pylint deluge + pylint deluge/plugins/\*/deluge/ + +- Installing the [pre-commit] application can aid in identifying issues + while creating git commits. #### Strings and bytes @@ -116,9 +117,7 @@ Verify that the documentation parses correctly with: python setup.py build_docs [pre-commit]: http://pre-commit.com/ -[flake8]: https://pypi.python.org/pypi/flake8 -[isort]: https://pypi.python.org/pypi/isort +[ruff]: https://docs.astral.sh/ruff/ [pylint]: http://www.pylint.org/ -[black]: https://github.com/python/black/ [gtk3 unicode]: http://python-gtk-3-tutorial.readthedocs.org/en/latest/unicode.html [napoleon sections]: http://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html#docstring-sections diff --git a/requirements-dev.txt b/requirements-dev.txt index 7e11944578..c8e339d628 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -4,11 +4,4 @@ sphinx-autobuild tox pre-commit -flake8-blind-except -flake8-builtins -flake8-commas -flake8-comprehensions -flake8-debugger -flake8-mock -flake8-mutable rjsmin diff --git a/requirements-tests.txt b/requirements-tests.txt index a570108adb..346a27500e 100644 --- a/requirements-tests.txt +++ b/requirements-tests.txt @@ -4,10 +4,5 @@ pytest-twisted pytest-cov mock pre-commit -flake8<=3.7.9 -flake8-quotes -flake8-isort -pep8-naming -mccabe pylint asyncmock; python_version <= '3.7'