diff options
author | toofar <toofar@spalge.com> | 2024-10-29 10:22:44 +1300 |
---|---|---|
committer | toofar <toofar@spalge.com> | 2024-10-29 10:22:44 +1300 |
commit | 7535207fd3ce5b97f23a423baa7052230b0f3b72 (patch) | |
tree | bf972b1cc0682785e7683b7bc99791c01f871629 | |
parent | e55624703bb2be91bfb89ebbca40ebe1580443e8 (diff) | |
parent | a138ab89789f3624da78a62629de84d5c42cbdac (diff) | |
download | qutebrowser-7535207fd3ce5b97f23a423baa7052230b0f3b72.tar.gz qutebrowser-7535207fd3ce5b97f23a423baa7052230b0f3b72.zip |
Merge remote-tracking branch 'upstream/main' into feat/68_permissions_askeverytime
184 files changed, 741 insertions, 713 deletions
@@ -58,7 +58,7 @@ ignore = PT004, PT011, PT012 -min-version = 3.8.0 +min-version = 3.9.0 max-complexity = 12 per-file-ignores = qutebrowser/api/hook.py : N801 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1e0c84bc7..9039bc9a3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,7 +50,7 @@ jobs: python-version: '3.10' - uses: actions/setup-node@v4 with: - node-version: '16.x' + node-version: '20.x' if: "matrix.testenv == 'eslint'" - name: Set up problem matchers run: "python scripts/dev/ci/problemmatchers.py ${{ matrix.testenv }} ${{ runner.temp }}" @@ -144,10 +144,10 @@ jobs: fail-fast: false matrix: include: - ### PyQt 5.15.2 (Python 3.8) - - testenv: py38-pyqt5152 + ### PyQt 5.15.2 (Python 3.9) + - testenv: py39-pyqt5152 os: ubuntu-20.04 - python: "3.8" + python: "3.9" ### PyQt 5.15 (Python 3.10, with coverage) # FIXME:qt6 # - testenv: py310-pyqt515-cov @@ -157,14 +157,14 @@ jobs: - testenv: py311-pyqt515 os: ubuntu-20.04 python: "3.11" - ### PyQt 6.2 (Python 3.8) - - testenv: py38-pyqt62 + ### PyQt 6.2 (Python 3.9) + - testenv: py39-pyqt62 os: ubuntu-20.04 - python: "3.8" - ### PyQt 6.3 (Python 3.8) - - testenv: py38-pyqt63 + python: "3.9" + ### PyQt 6.3 (Python 3.9) + - testenv: py39-pyqt63 os: ubuntu-20.04 - python: "3.8" + python: "3.9" ## PyQt 6.4 (Python 3.9) - testenv: py39-pyqt64 os: ubuntu-20.04 diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index d4e106bc2..07d8812b9 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -15,20 +15,6 @@ jobs: matrix: include: - os: macos-12 - toxenv: build-release-qt5 - name: qt5-macos - - os: windows-2019 - toxenv: build-release-qt5 - name: qt5-windows - - os: macos-12 - args: --debug - toxenv: build-release-qt5 - name: qt5-macos-debug - - os: windows-2019 - args: --debug - toxenv: build-release-qt5 - name: qt5-windows-debug - - os: macos-12 toxenv: build-release name: macos-intel - os: macos-14 diff --git a/.github/workflows/recompile-requirements.yml b/.github/workflows/recompile-requirements.yml index 6d42c3137..5310336c8 100644 --- a/.github/workflows/recompile-requirements.yml +++ b/.github/workflows/recompile-requirements.yml @@ -21,10 +21,10 @@ jobs: - uses: actions/checkout@v4 with: persist-credentials: false - - name: Set up Python 3.8 + - name: Set up Python 3.9 uses: actions/setup-python@v5 with: - python-version: '3.8' + python-version: '3.9' - name: Recompile requirements run: "python3 scripts/dev/recompile_requirements.py ${{ github.event.input.environments }}" id: requirements @@ -41,7 +41,7 @@ jobs: - name: Run qutebrowser smoke test run: "xvfb-run .venv/bin/python3 -m qutebrowser --no-err-windows --nowindow --temp-basedir about:blank ':later 500 quit'" - name: Create pull request - uses: peter-evans/create-pull-request@v6 + uses: peter-evans/create-pull-request@v7 with: committer: qutebrowser bot <bot@qutebrowser.org> author: qutebrowser bot <bot@qutebrowser.org> diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 826970384..e7eab6371 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,7 +19,6 @@ on: default: '3.12' type: choice options: - - '3.8' - '3.9' - '3.10' - '3.11' @@ -1,5 +1,5 @@ [mypy] -python_version = 3.8 +python_version = 3.9 ### --strict warn_unused_configs = True @@ -16,7 +16,7 @@ load-plugins=qute_pylint.config, pylint.extensions.dunder persistent=n -py-version=3.8 +py-version=3.9 [MESSAGES CONTROL] enable=all @@ -71,7 +71,8 @@ argument-rgx=[a-z_][a-z0-9_]{0,30}$ variable-rgx=[a-z_][a-z0-9_]{0,30}$ docstring-min-length=3 no-docstring-rgx=(^_|^main$) -class-const-naming-style = snake_case +class-const-naming-style=snake_case +max-positional-arguments=7 [FORMAT] # FIXME:v4 (lint) down to 88 again once we use black diff --git a/README.asciidoc b/README.asciidoc index b670e1800..9567473ee 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -84,7 +84,7 @@ Requirements The following software and libraries are required to run qutebrowser: -* https://www.python.org/[Python] 3.8 or newer +* https://www.python.org/[Python] 3.9 or newer * https://www.qt.io/[Qt], either 6.2.0 or newer, or 5.15.0 or newer, with the following modules: - QtCore / qtbase - QtQuick (part of qtbase or qtdeclarative in some distributions) @@ -105,10 +105,6 @@ websites and using it for transmission of sensitive data._ * https://palletsprojects.com/p/jinja/[jinja2] * https://github.com/yaml/pyyaml[PyYAML] -On Python 3.8, the following backport is also required: - -* https://importlib-resources.readthedocs.io/[importlib_resources] - On macOS, the following libraries are also required: * https://pyobjc.readthedocs.io/en/latest/[pyobjc-core and pyobjc-framework-Cocoa] diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 61bf9d187..eb072f4d0 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -22,11 +22,17 @@ v3.4.0 (unreleased) Deprecated ~~~~~~~~~~ -- **In either this (v3.4.0) or the next release (v3.5.0)**, support for Python - 3.8 will be dropped, and Python 3.9 will be required. - **In the next release (v3.5.0)**, support for macOS 12 Monterey will be dropped, and binaries will be built on macOS 13 Ventura. +Removed +~~~~~~~ + +- Support for Python 3.8 is dropped, and Python 3.9 is now required. +- When using the installer on Windows 10, build 1809 or newer is now required + (previous versions required 1607 or newer, but that's not officialy supported by + Qt upstream). + Added ~~~~~ @@ -38,8 +44,18 @@ Added Changed ~~~~~~~ +- The `.desktop` file now also declares qutebrowser as a valid viewer for + `image/webp`. +- Updated mimetype information for getting a suitable extension when downloading + a `data:` URL. - The `content.javascript.clipboard` setting now defaults to "ask". +Fixed +~~~~~ + +- Crash with recent Jinja/Markupsafe versions when viewing a finished userscript + (or potentially editor) process via `:process`. + [[v3.3.1]] v3.3.1 (2024-10-12) diff --git a/doc/contributing.asciidoc b/doc/contributing.asciidoc index c77d93f18..6e609334e 100644 --- a/doc/contributing.asciidoc +++ b/doc/contributing.asciidoc @@ -111,9 +111,9 @@ unittests and several linters/checkers. Currently, the following tox environments are available: * Tests using https://www.pytest.org[pytest]: - - `py38`, `py39`, ...: Run pytest for python 3.8/3.9/... with the system-wide PyQt. - - `py38-pyqt515`, ..., `py38-pyqt65`: Run pytest with the given PyQt version (`py39-*` etc. also works). - - `py38-pyqt515-cov`: Run with coverage support (other Python/PyQt versions work too). + - `py39`, `py310`, ...: Run pytest for python 3.9/3.10/... with the system-wide PyQt. + - `py39-pyqt515`, ..., `py39-pyqt65`: Run pytest with the given PyQt version (`py310-*` etc. also works). + - `py39-pyqt515-cov`: Run with coverage support (other Python/PyQt versions work too). * `flake8`: Run various linting checks via https://pypi.python.org/pypi/flake8[flake8]. * `vulture`: Run https://pypi.python.org/pypi/vulture[vulture] to find unused code portions. @@ -171,16 +171,16 @@ Examples: ---- # run only pytest tests which failed in last run: -tox -e py38 -- --lf +tox -e py39 -- --lf # run only the end2end feature tests: -tox -e py38 -- tests/end2end/features +tox -e py39 -- tests/end2end/features # run everything with undo in the generated name, based on the scenario text -tox -e py38 -- tests/end2end/features/test_tabs_bdd.py -k undo +tox -e py39 -- tests/end2end/features/test_tabs_bdd.py -k undo # run coverage test for specific file (updates htmlcov/index.html) -tox -e py38-cov -- tests/unit/browser/test_webelem.py +tox -e py39-cov -- tests/unit/browser/test_webelem.py ---- Specifying the backend for tests diff --git a/doc/help/configuring.asciidoc b/doc/help/configuring.asciidoc index 54f4f34f9..b166477c1 100644 --- a/doc/help/configuring.asciidoc +++ b/doc/help/configuring.asciidoc @@ -452,7 +452,7 @@ Various emacs/conkeror-like keybinding configs exist: - https://gitlab.com/Kaligule/qutebrowser-emacs-config/blob/master/config.py[Kaligule] - https://web.archive.org/web/20210512185023/https://me0w.net/pit/1540882719[nm0i] - https://www.reddit.com/r/qutebrowser/comments/eh10i7/config_share_qute_with_emacs_keybindings/[jasonsun0310] -- https://git.sr.ht/~willvaughn/dots/tree/mjolnir/item/.config/qutebrowser/qutemacs.py[willvaughn] +- https://git.sr.ht/~willvaughn/dots/tree/main/item/.config/qutebrowser/qutemacs.py[willvaughn] It's also mostly possible to get rid of modal keybindings by setting `input.insert_mode.auto_enter` to `false`, and `input.forward_unbound_keys` to diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index 629efa96c..ce6320e90 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -3891,7 +3891,7 @@ Chromium has various sandboxing layers, which should be enabled for normal brows Open `chrome://sandbox` to see the current sandbox status. Changing this setting is only recommended if you know what you're doing, as it **disables one of Chromium's security layers**. To avoid sandboxing being accidentally disabled persistently, this setting can only be set via `config.py`, not via `:set`. See the Chromium documentation for more details: -- https://chromium.googlesource.com/chromium/src/\+/HEAD/docs/linux/sandboxing.md[Linux] - https://chromium.googlesource.com/chromium/src/\+/HEAD/docs/design/sandbox.md[Windows] - https://chromium.googlesource.com/chromium/src/\+/HEAD/docs/design/sandbox_faq.md[FAQ (Windows-centric)] +- https://chromium.googlesource.com/chromium/src/\+/HEAD/sandbox/linux/README.md[Linux] - https://chromium.googlesource.com/chromium/src/\+/HEAD/docs/design/sandbox.md[Windows] - https://chromium.googlesource.com/chromium/src/\+/HEAD/sandbox/mac/README.md[Mac] - https://chromium.googlesource.com/chromium/src/\+/HEAD/docs/design/sandbox_faq.md[FAQ (Windows-centric)] This setting requires a restart. diff --git a/doc/install.asciidoc b/doc/install.asciidoc index 20dc29597..75c9b9777 100644 --- a/doc/install.asciidoc +++ b/doc/install.asciidoc @@ -438,7 +438,7 @@ This installs all needed Python dependencies in a `.venv` subfolder This comes with an up-to-date Qt/PyQt including a pre-compiled QtWebEngine binary, but has a few caveats: -- Make sure your `python3` is Python 3.8 or newer, otherwise you'll get a "No +- Make sure your `python3` is Python 3.9 or newer, otherwise you'll get a "No matching distribution found" error and/or qutebrowser will not run. - It only works on 64-bit x86 systems, with other architectures you'll get the same error. diff --git a/misc/nsis/install.nsh b/misc/nsis/install.nsh index 5086dcb0d..5c23c4ace 100755 --- a/misc/nsis/install.nsh +++ b/misc/nsis/install.nsh @@ -432,32 +432,18 @@ Function .onInit StrCpy $KeepReg 1 ; OS version check - ${If} ${RunningX64} - ; https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-osversioninfoa#remarks - GetWinVer $R0 Major - !if "${QT5}" == "True" - IntCmpU $R0 6 0 _os_check_fail _os_check_pass - GetWinVer $R1 Minor - IntCmpU $R1 2 _os_check_pass _os_check_fail _os_check_pass - !else - IntCmpU $R0 10 0 _os_check_fail _os_check_pass - GetWinVer $R1 Build - ${If} $R1 >= 22000 ; Windows 11 21H2 - Goto _os_check_pass - ${ElseIf} $R1 >= 14393 ; Windows 10 1607 - ${AndIf} ${IsNativeAMD64} ; Windows 10 has no x86_64 emulation on arm64 - Goto _os_check_pass - ${EndIf} - !endif + ; https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-osversioninfoa#remarks + ; https://learn.microsoft.com/en-us/windows/release-health/release-information + ; https://learn.microsoft.com/en-us/windows/release-health/windows11-release-information + ${If} ${AtLeastWin11} + Goto _os_check_pass + ${ElseIf} ${IsNativeAMD64} ; Windows 10 has no x86_64 emulation on arm64 + ${AndIf} ${AtLeastWin10} + ${AndIf} ${AtLeastBuild} 17763 ; Windows 10 1809 (also in error message below) + Goto _os_check_pass ${EndIf} - _os_check_fail: - !if "${QT5}" == "True" - MessageBox MB_OK|MB_ICONSTOP "This version of ${PRODUCT_NAME} requires a 64-bit$\r$\n\ - version of Windows 8 or later." - !else - MessageBox MB_OK|MB_ICONSTOP "This version of ${PRODUCT_NAME} requires a 64-bit$\r$\n\ - version of Windows 10 1607 or later." - !endif + MessageBox MB_OK|MB_ICONSTOP "This version of ${PRODUCT_NAME} requires a 64-bit$\r$\n\ + version of Windows 10 1809 or later." Abort _os_check_pass: diff --git a/misc/nsis/qutebrowser.nsi b/misc/nsis/qutebrowser.nsi index bd5156e83..dcdb047f6 100755 --- a/misc/nsis/qutebrowser.nsi +++ b/misc/nsis/qutebrowser.nsi @@ -131,9 +131,6 @@ ShowUninstDetails hide !define /ifndef DIST_DIR ".\..\..\dist\${PRODUCT_NAME}-${VERSION}"
!endif
-; If not defined, assume Qt6 (requires a more recent windows version)
-!define /ifndef QT5 "False"
-
; Pack the exe header with upx if UPX is defined.
!ifdef UPX
!packhdr "$%TEMP%\exehead.tmp" '"upx" "--ultra-brute" "$%TEMP%\exehead.tmp"'
diff --git a/misc/org.qutebrowser.qutebrowser.desktop b/misc/org.qutebrowser.qutebrowser.desktop index 741a00371..71097a353 100644 --- a/misc/org.qutebrowser.qutebrowser.desktop +++ b/misc/org.qutebrowser.qutebrowser.desktop @@ -48,7 +48,7 @@ Categories=Network;WebBrowser; Exec=qutebrowser --untrusted-args %u Terminal=false StartupNotify=true -MimeType=text/html;text/xml;application/xhtml+xml;application/xml;application/rdf+xml;image/gif;image/jpeg;image/png;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/qute; +MimeType=text/html;text/xml;application/xhtml+xml;application/xml;application/rdf+xml;image/gif;image/webp;image/jpeg;image/png;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/qute; Keywords=Browser Actions=new-window;preferences; diff --git a/misc/requirements/requirements-check-manifest.txt b/misc/requirements/requirements-check-manifest.txt index d4d85c486..349e4399b 100644 --- a/misc/requirements/requirements-check-manifest.txt +++ b/misc/requirements/requirements-check-manifest.txt @@ -1,9 +1,9 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -build==1.2.2 -check-manifest==0.49 +build==1.2.2.post1 +check-manifest==0.50 importlib_metadata==8.5.0 packaging==24.1 pyproject_hooks==1.2.0 -tomli==2.0.1 +tomli==2.0.2 zipp==3.20.2 diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt index 063738395..ae3a154f7 100644 --- a/misc/requirements/requirements-dev.txt +++ b/misc/requirements/requirements-dev.txt @@ -2,13 +2,13 @@ autocommand==2.2.2 backports.tarfile==1.2.0 -build==1.2.2 +build==1.2.2.post1 bump2version==1.0.1 certifi==2024.8.30 cffi==1.17.1 -charset-normalizer==3.3.2 -cryptography==43.0.1 -docutils==0.20.1 +charset-normalizer==3.4.0 +cryptography==43.0.3 +docutils==0.21.2 github3.py==4.0.1 hunter==3.7.0 idna==3.10 @@ -37,15 +37,15 @@ Pympler==1.1 pyproject_hooks==1.2.0 PyQt-builder==1.16.4 python-dateutil==2.9.0.post0 -readme_renderer==43.0 +readme_renderer==44.0 requests==2.32.3 requests-toolbelt==1.0.0 rfc3986==2.0.0 -rich==13.8.1 +rich==13.9.2 SecretStorage==3.3.3 sip==6.8.6 six==1.16.0 -tomli==2.0.1 +tomli==2.0.2 twine==5.1.1 typeguard==4.3.0 typing_extensions==4.12.2 diff --git a/misc/requirements/requirements-dev.txt-raw b/misc/requirements/requirements-dev.txt-raw index fc991474c..8bf0d9772 100644 --- a/misc/requirements/requirements-dev.txt-raw +++ b/misc/requirements/requirements-dev.txt-raw @@ -9,6 +9,7 @@ twine # Included to override setuptools' vendored version that is being included in # the lock file by pip freeze. +importlib_resources platformdirs # Already included via test requirements diff --git a/misc/requirements/requirements-mypy.txt b/misc/requirements/requirements-mypy.txt index 5a6eb2344..abfdc7986 100644 --- a/misc/requirements/requirements-mypy.txt +++ b/misc/requirements/requirements-mypy.txt @@ -2,20 +2,18 @@ chardet==5.2.0 diff_cover==9.2.0 -importlib_resources==6.4.5 Jinja2==3.1.4 lxml==5.3.0 -MarkupSafe==2.1.5 -mypy==1.11.2 +MarkupSafe==3.0.2 +mypy==1.12.1 mypy-extensions==1.0.0 pluggy==1.5.0 Pygments==2.18.0 PyQt5-stubs==5.15.6.0 -tomli==2.0.1 +tomli==2.0.2 types-colorama==0.4.15.20240311 -types-docutils==0.21.0.20240907 +types-docutils==0.21.0.20241005 types-Pygments==2.18.0.20240506 types-PyYAML==6.0.12.20240917 -types-setuptools==75.1.0.20240917 +types-setuptools==75.2.0.20241019 typing_extensions==4.12.2 -zipp==3.20.2 diff --git a/misc/requirements/requirements-mypy.txt-raw b/misc/requirements/requirements-mypy.txt-raw index 027f4fef6..683e8bec7 100644 --- a/misc/requirements/requirements-mypy.txt-raw +++ b/misc/requirements/requirements-mypy.txt-raw @@ -6,6 +6,3 @@ PyQt5-stubs types-PyYAML types-colorama types-Pygments - -# So stubs are available even on newer Python versions -importlib_resources diff --git a/misc/requirements/requirements-pyinstaller.txt b/misc/requirements/requirements-pyinstaller.txt index d7e92fc61..20e298a71 100644 --- a/misc/requirements/requirements-pyinstaller.txt +++ b/misc/requirements/requirements-pyinstaller.txt @@ -3,6 +3,6 @@ altgraph==0.17.4 importlib_metadata==8.5.0 packaging==24.1 -pyinstaller==6.10.0 -pyinstaller-hooks-contrib==2024.8 +pyinstaller==6.11.0 +pyinstaller-hooks-contrib==2024.9 zipp==3.20.2 diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index d053a6673..c1fffcd9c 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -1,10 +1,10 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -astroid==3.2.4 +astroid==3.3.5 certifi==2024.8.30 cffi==1.17.1 -charset-normalizer==3.3.2 -cryptography==43.0.1 +charset-normalizer==3.4.0 +cryptography==43.0.3 dill==0.3.9 github3.py==4.0.1 idna==3.10 @@ -14,12 +14,12 @@ pefile==2024.8.26 platformdirs==4.3.6 pycparser==2.22 PyJWT==2.9.0 -pylint==3.2.7 +pylint==3.3.1 python-dateutil==2.9.0.post0 ./scripts/dev/pylint_checkers requests==2.32.3 six==1.16.0 -tomli==2.0.1 +tomli==2.0.2 tomlkit==0.13.2 typing_extensions==4.12.2 uritemplate==4.1.1 diff --git a/misc/requirements/requirements-pylint.txt-raw b/misc/requirements/requirements-pylint.txt-raw index 99a2cf02f..78da3a1a3 100644 --- a/misc/requirements/requirements-pylint.txt-raw +++ b/misc/requirements/requirements-pylint.txt-raw @@ -7,7 +7,6 @@ pefile # fix qute-pylint location #@ replace: qute[_-]pylint.* ./scripts/dev/pylint_checkers -#@ markers: typed-ast python_version<"3.8" # Already included via test requirements #@ ignore: urllib3 diff --git a/misc/requirements/requirements-pyroma.txt b/misc/requirements/requirements-pyroma.txt index 223ada625..f67c21e82 100644 --- a/misc/requirements/requirements-pyroma.txt +++ b/misc/requirements/requirements-pyroma.txt @@ -1,9 +1,9 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -build==1.2.2 +build==1.2.2.post1 certifi==2024.8.30 -charset-normalizer==3.3.2 -docutils==0.20.1 +charset-normalizer==3.4.0 +docutils==0.21.2 idna==3.10 importlib_metadata==8.5.0 packaging==24.1 @@ -11,7 +11,7 @@ Pygments==2.18.0 pyproject_hooks==1.2.0 pyroma==4.2 requests==2.32.3 -tomli==2.0.1 -trove-classifiers==2024.9.12 +tomli==2.0.2 +trove-classifiers==2024.10.16 urllib3==2.2.3 zipp==3.20.2 diff --git a/misc/requirements/requirements-qutebrowser.txt-raw b/misc/requirements/requirements-qutebrowser.txt-raw index ca4081d1d..586049b82 100644 --- a/misc/requirements/requirements-qutebrowser.txt-raw +++ b/misc/requirements/requirements-qutebrowser.txt-raw @@ -12,12 +12,7 @@ PyYAML #@ add: pyobjc-core ; sys_platform=="darwin" #@ add: pyobjc-framework-Cocoa ; sys_platform=="darwin" -## stdlib backports -importlib_resources - ## Optional dependencies Pygments # For :view-source --pygments or on QtWebKit colorama # Colored log output on Windows adblock # Improved adblocking - -#@ markers: importlib_resources python_version=="3.8.*" diff --git a/misc/requirements/requirements-sphinx.txt b/misc/requirements/requirements-sphinx.txt index 2f0ba5b9a..7e0cd41b4 100644 --- a/misc/requirements/requirements-sphinx.txt +++ b/misc/requirements/requirements-sphinx.txt @@ -1,26 +1,26 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -alabaster==0.7.13 +alabaster==0.7.16 babel==2.16.0 certifi==2024.8.30 -charset-normalizer==3.3.2 -docutils==0.20.1 +charset-normalizer==3.4.0 +docutils==0.21.2 idna==3.10 imagesize==1.4.1 importlib_metadata==8.5.0 Jinja2==3.1.4 -MarkupSafe==2.1.5 +MarkupSafe==3.0.2 packaging==24.1 Pygments==2.18.0 -pytz==2024.2 requests==2.32.3 snowballstemmer==2.2.0 -Sphinx==7.1.2 -sphinxcontrib-applehelp==1.0.4 -sphinxcontrib-devhelp==1.0.2 -sphinxcontrib-htmlhelp==2.0.1 +Sphinx==7.4.7 +sphinxcontrib-applehelp==2.0.0 +sphinxcontrib-devhelp==2.0.0 +sphinxcontrib-htmlhelp==2.1.0 sphinxcontrib-jsmath==1.0.1 -sphinxcontrib-qthelp==1.0.3 -sphinxcontrib-serializinghtml==1.1.5 +sphinxcontrib-qthelp==2.0.0 +sphinxcontrib-serializinghtml==2.0.0 +tomli==2.0.2 urllib3==2.2.3 zipp==3.20.2 diff --git a/misc/requirements/requirements-tests-bleeding.txt b/misc/requirements/requirements-tests-bleeding.txt index 10369fc30..79d19910a 100644 --- a/misc/requirements/requirements-tests-bleeding.txt +++ b/misc/requirements/requirements-tests-bleeding.txt @@ -7,7 +7,11 @@ git+https://github.com/pallets/flask.git git+https://github.com/pallets/werkzeug.git # transitive dep, but needed to work git+https://github.com/HypothesisWorks/hypothesis.git#subdirectory=hypothesis-python git+https://github.com/pytest-dev/pytest.git -git+https://github.com/pytest-dev/pytest-bdd.git + +# https://github.com/qutebrowser/qutebrowser/issues/8342 +# git+https://github.com/pytest-dev/pytest-bdd.git +pytest-bdd + git+https://github.com/ionelmc/pytest-benchmark.git git+https://github.com/pytest-dev/pytest-instafail.git git+https://github.com/pytest-dev/pytest-mock.git diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index dcd27b11a..6a360e7bd 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -6,19 +6,19 @@ backports.tarfile==1.2.0 beautifulsoup4==4.12.3 blinker==1.8.2 certifi==2024.8.30 -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 cheroot==10.0.1 click==8.1.7 -coverage==7.6.1 +coverage==7.6.4 exceptiongroup==1.2.2 execnet==2.1.1 filelock==3.16.1 Flask==3.0.3 hunter==3.7.0 -hypothesis==6.112.2 +hypothesis==6.115.3 idna==3.10 importlib_metadata==8.5.0 -importlib_resources==6.4.5 ; python_version=="3.8.*" +importlib_resources==6.4.5 inflect==7.3.1 iniconfig==2.0.0 itsdangerous==2.2.0 @@ -29,13 +29,13 @@ jaraco.text==3.12.1 # Jinja2==3.1.4 Mako==1.3.5 manhole==1.8.1 -# MarkupSafe==2.1.5 +# MarkupSafe==3.0.2 more-itertools==10.5.0 packaging==24.1 parse==1.20.2 -parse_type==0.6.3 +parse_type==0.6.4 +pillow==11.0.0 platformdirs==4.3.6 -pillow==10.4.0 pluggy==1.5.0 py-cpuinfo==9.0.0 Pygments==2.18.0 @@ -57,10 +57,10 @@ six==1.16.0 sortedcontainers==2.4.0 soupsieve==2.6 tldextract==5.1.2 -tomli==2.0.1 +tomli==2.0.2 typeguard==4.3.0 typing_extensions==4.12.2 urllib3==2.2.3 -vulture==2.12 +vulture==2.13 Werkzeug==3.0.4 zipp==3.20.2 diff --git a/misc/requirements/requirements-tests.txt-raw b/misc/requirements/requirements-tests.txt-raw index 58984f164..1df954e53 100644 --- a/misc/requirements/requirements-tests.txt-raw +++ b/misc/requirements/requirements-tests.txt-raw @@ -39,7 +39,6 @@ tldextract # Include them here even though we don't need them to make sure we at least # get an up to date version. importlib_resources -#@ markers: importlib_resources python_version=="3.8.*" jaraco.context platformdirs diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt index f66945528..d76fa3fc0 100644 --- a/misc/requirements/requirements-tox.txt +++ b/misc/requirements/requirements-tox.txt @@ -3,15 +3,16 @@ cachetools==5.5.0 chardet==5.2.0 colorama==0.4.6 -distlib==0.3.8 +distlib==0.3.9 filelock==3.16.1 packaging==24.1 pip==24.2 platformdirs==4.3.6 pluggy==1.5.0 pyproject-api==1.8.0 -setuptools==75.1.0 -tomli==2.0.1 -tox==4.20.0 -virtualenv==20.26.6 +setuptools==75.2.0 +tomli==2.0.2 +tox==4.23.0 +typing_extensions==4.12.2 +virtualenv==20.27.0 wheel==0.44.0 diff --git a/misc/requirements/requirements-vulture.txt b/misc/requirements/requirements-vulture.txt index 09294ac8d..bdedceb1d 100644 --- a/misc/requirements/requirements-vulture.txt +++ b/misc/requirements/requirements-vulture.txt @@ -1,4 +1,4 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -tomli==2.0.1 -vulture==2.12 +tomli==2.0.2 +vulture==2.13 diff --git a/qutebrowser/api/cmdutils.py b/qutebrowser/api/cmdutils.py index e5466f072..3939dbb0a 100644 --- a/qutebrowser/api/cmdutils.py +++ b/qutebrowser/api/cmdutils.py @@ -35,7 +35,8 @@ Possible values: import inspect -from typing import Any, Callable, Iterable, Protocol, Optional, Dict, cast +from typing import Any, Protocol, Optional, cast +from collections.abc import Iterable, Callable from qutebrowser.utils import qtutils from qutebrowser.commands import command, cmdexc @@ -101,7 +102,7 @@ class _CmdHandlerType(Protocol): Below, we cast the decorated function to _CmdHandlerType to make mypy aware of this. """ - qute_args: Optional[Dict[str, 'command.ArgInfo']] + qute_args: Optional[dict[str, 'command.ArgInfo']] def __call__(self, *args: Any, **kwargs: Any) -> Any: ... diff --git a/qutebrowser/api/hook.py b/qutebrowser/api/hook.py index 9a1a7bc9c..f62514e6a 100644 --- a/qutebrowser/api/hook.py +++ b/qutebrowser/api/hook.py @@ -7,7 +7,8 @@ """Hooks for extensions.""" import importlib -from typing import Callable, Any +from typing import Any +from collections.abc import Callable from qutebrowser.extensions import loader diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 51603a2b9..66bd485fc 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -29,7 +29,8 @@ import tempfile import pathlib import datetime import argparse -from typing import Iterable, Optional, List, Tuple +from typing import Optional +from collections.abc import Iterable from qutebrowser.qt import machinery from qutebrowser.qt.widgets import QApplication, QWidget @@ -330,7 +331,7 @@ def _open_special_pages(args): tabbed_browser = objreg.get('tabbed-browser', scope='window', window='last-focused') - pages: List[Tuple[str, bool, str]] = [ + pages: list[tuple[str, bool, str]] = [ # state, condition, URL ('quickstart-done', True, diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index 625046a9c..74eacfcd0 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -9,8 +9,8 @@ import pathlib import itertools import functools import dataclasses -from typing import (cast, TYPE_CHECKING, Any, Callable, Iterable, List, Optional, - Sequence, Set, Type, Union, Tuple) +from typing import (cast, TYPE_CHECKING, Any, Optional, Union) +from collections.abc import Iterable, Sequence, Callable from qutebrowser.qt import machinery from qutebrowser.qt.core import (pyqtSignal, pyqtSlot, QUrl, QObject, QSizeF, Qt, @@ -60,7 +60,7 @@ def create(win_id: int, mode_manager = modeman.instance(win_id) if objects.backend == usertypes.Backend.QtWebEngine: from qutebrowser.browser.webengine import webenginetab - tab_class: Type[AbstractTab] = webenginetab.WebEngineTab + tab_class: type[AbstractTab] = webenginetab.WebEngineTab elif objects.backend == usertypes.Backend.QtWebKit: from qutebrowser.browser.webkit import webkittab tab_class = webkittab.WebKitTab @@ -142,7 +142,7 @@ class AbstractAction: """Attribute ``action`` of AbstractTab for Qt WebActions.""" - action_base: Type[Union['QWebPage.WebAction', 'QWebEnginePage.WebAction']] + action_base: type[Union['QWebPage.WebAction', 'QWebEnginePage.WebAction']] def __init__(self, tab: 'AbstractTab') -> None: self._widget = cast(_WidgetType, None) @@ -639,7 +639,7 @@ class AbstractScroller(QObject): def pos_px(self) -> QPoint: raise NotImplementedError - def pos_perc(self) -> Tuple[int, int]: + def pos_perc(self) -> tuple[int, int]: raise NotImplementedError def to_perc(self, x: float = None, y: float = None) -> None: @@ -765,10 +765,10 @@ class AbstractHistory: def _go_to_item(self, item: Any) -> None: raise NotImplementedError - def back_items(self) -> List[Any]: + def back_items(self) -> list[Any]: raise NotImplementedError - def forward_items(self) -> List[Any]: + def forward_items(self) -> list[Any]: raise NotImplementedError @@ -1018,7 +1018,7 @@ class AbstractTab(QWidget): # Note that we remember hosts here, without scheme/port: # QtWebEngine/Chromium also only remembers hostnames, and certificates are # for a given hostname anyways. - _insecure_hosts: Set[str] = set() + _insecure_hosts: set[str] = set() # Sub-APIs initialized by subclasses history: AbstractHistory diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 32533709c..65d6635a3 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -2,12 +2,15 @@ # # SPDX-License-Identifier: GPL-3.0-or-later +# pylint: disable=too-many-positional-arguments + """Command dispatcher for TabbedBrowser.""" import os.path import shlex import functools -from typing import cast, Callable, Dict, Union, Optional +from typing import cast, Union, Optional +from collections.abc import Callable from qutebrowser.qt.widgets import QApplication, QTabBar from qutebrowser.qt.core import Qt, QUrl, QEvent, QUrlQuery @@ -636,7 +639,7 @@ class CommandDispatcher: widget = self._current_widget() url = self._current_url() - handlers: Dict[str, Callable[..., QUrl]] = { + handlers: dict[str, Callable[..., QUrl]] = { 'prev': functools.partial(navigate.prevnext, prev=True), 'next': functools.partial(navigate.prevnext, prev=False), 'up': navigate.path_up, @@ -1136,8 +1139,7 @@ class CommandDispatcher: else: cmd = os.path.expanduser(cmd) proc = guiprocess.GUIProcess(what='command', verbose=verbose, - output_messages=output_messages, - parent=self._tabbed_browser) + output_messages=output_messages) if detach: ok = proc.start_detached(cmd, args) if not ok: diff --git a/qutebrowser/browser/downloads.py b/qutebrowser/browser/downloads.py index fbbf4ff11..53cc823f0 100644 --- a/qutebrowser/browser/downloads.py +++ b/qutebrowser/browser/downloads.py @@ -13,7 +13,8 @@ import functools import pathlib import tempfile import enum -from typing import Any, Dict, IO, List, MutableSequence, Optional, Union +from typing import Any, IO, Optional, Union +from collections.abc import MutableSequence from qutebrowser.qt.core import (pyqtSlot, pyqtSignal, Qt, QObject, QModelIndex, QTimer, QAbstractListModel, QUrl) @@ -447,7 +448,7 @@ class AbstractDownloadItem(QObject): UnsupportedAttribute, IO[bytes], None ] = UnsupportedAttribute() self.raw_headers: Union[ - UnsupportedAttribute, Dict[bytes, bytes] + UnsupportedAttribute, dict[bytes, bytes] ] = UnsupportedAttribute() self._filename: Optional[str] = None @@ -899,7 +900,7 @@ class AbstractDownloadManager(QObject): def __init__(self, parent=None): super().__init__(parent) - self.downloads: List[AbstractDownloadItem] = [] + self.downloads: list[AbstractDownloadItem] = [] self._update_timer = usertypes.Timer(self, 'download-update') self._update_timer.timeout.connect(self._update_gui) self._update_timer.setInterval(_REFRESH_INTERVAL) diff --git a/qutebrowser/browser/downloadview.py b/qutebrowser/browser/downloadview.py index 4b6a8b2c8..5f67b344d 100644 --- a/qutebrowser/browser/downloadview.py +++ b/qutebrowser/browser/downloadview.py @@ -5,7 +5,8 @@ """The ListView to display downloads in.""" import functools -from typing import Callable, MutableSequence, Tuple, Union +from typing import Union +from collections.abc import MutableSequence, Callable from qutebrowser.qt.core import pyqtSlot, QSize, Qt from qutebrowser.qt.widgets import QListView, QSizePolicy, QMenu, QStyleFactory @@ -17,8 +18,8 @@ from qutebrowser.utils import qtutils, utils _ActionListType = MutableSequence[ Union[ - Tuple[None, None], # separator - Tuple[str, Callable[[], None]], + tuple[None, None], # separator + tuple[str, Callable[[], None]], ] ] diff --git a/qutebrowser/browser/greasemonkey.py b/qutebrowser/browser/greasemonkey.py index d41d46361..ab63046db 100644 --- a/qutebrowser/browser/greasemonkey.py +++ b/qutebrowser/browser/greasemonkey.py @@ -12,7 +12,8 @@ import functools import glob import textwrap import dataclasses -from typing import cast, List, Sequence, Tuple, Optional +from typing import cast, Optional +from collections.abc import Sequence from qutebrowser.qt.core import pyqtSignal, QObject, QUrl @@ -207,9 +208,9 @@ class MatchingScripts: """All userscripts registered to run on a particular url.""" url: QUrl - start: List[GreasemonkeyScript] = dataclasses.field(default_factory=list) - end: List[GreasemonkeyScript] = dataclasses.field(default_factory=list) - idle: List[GreasemonkeyScript] = dataclasses.field(default_factory=list) + start: list[GreasemonkeyScript] = dataclasses.field(default_factory=list) + end: list[GreasemonkeyScript] = dataclasses.field(default_factory=list) + idle: list[GreasemonkeyScript] = dataclasses.field(default_factory=list) @dataclasses.dataclass @@ -217,8 +218,8 @@ class LoadResults: """The results of loading all Greasemonkey scripts.""" - successful: List[GreasemonkeyScript] = dataclasses.field(default_factory=list) - errors: List[Tuple[str, str]] = dataclasses.field(default_factory=list) + successful: list[GreasemonkeyScript] = dataclasses.field(default_factory=list) + errors: list[tuple[str, str]] = dataclasses.field(default_factory=list) def successful_str(self) -> str: """Get a string with all successfully loaded scripts. @@ -294,10 +295,10 @@ class GreasemonkeyManager(QObject): def __init__(self, parent=None): super().__init__(parent) - self._run_start: List[GreasemonkeyScript] = [] - self._run_end: List[GreasemonkeyScript] = [] - self._run_idle: List[GreasemonkeyScript] = [] - self._in_progress_dls: List[downloads.AbstractDownloadItem] = [] + self._run_start: list[GreasemonkeyScript] = [] + self._run_end: list[GreasemonkeyScript] = [] + self._run_idle: list[GreasemonkeyScript] = [] + self._in_progress_dls: list[downloads.AbstractDownloadItem] = [] def load_scripts(self, *, force: bool = False) -> LoadResults: """Re-read Greasemonkey scripts from disk. diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index dc5c55b05..b3f45610d 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -12,8 +12,15 @@ import html import enum import dataclasses from string import ascii_lowercase -from typing import (TYPE_CHECKING, Callable, Dict, Iterable, Iterator, List, Mapping, - MutableSequence, Optional, Sequence, Set) +from typing import (TYPE_CHECKING, Optional) +from collections.abc import ( + Iterable, + Iterator, + Mapping, + MutableSequence, + Sequence, + Callable, +) from qutebrowser.qt.core import pyqtSignal, pyqtSlot, QObject, Qt, QUrl from qutebrowser.qt.widgets import QLabel @@ -175,11 +182,11 @@ class HintContext: add_history: bool first: bool baseurl: QUrl - args: List[str] + args: list[str] group: str - all_labels: List[HintLabel] = dataclasses.field(default_factory=list) - labels: Dict[str, HintLabel] = dataclasses.field(default_factory=dict) + all_labels: list[HintLabel] = dataclasses.field(default_factory=list) + labels: dict[str, HintLabel] = dataclasses.field(default_factory=dict) to_follow: Optional[str] = None first_run: bool = True filterstr: Optional[str] = None @@ -1033,7 +1040,7 @@ class WordHinter: def __init__(self) -> None: # will be initialized on first use. - self.words: Set[str] = set() + self.words: set[str] = set() self.dictionary = None def ensure_initialized(self) -> None: @@ -1143,7 +1150,7 @@ class WordHinter: """ self.ensure_initialized() hints = [] - used_hints: Set[str] = set() + used_hints: set[str] = set() words = iter(self.words) for elem in elems: hint = self.new_hint_for(elem, used_hints, words) diff --git a/qutebrowser/browser/history.py b/qutebrowser/browser/history.py index 45bfeddbf..ebcd26e72 100644 --- a/qutebrowser/browser/history.py +++ b/qutebrowser/browser/history.py @@ -8,7 +8,8 @@ import os import time import contextlib import pathlib -from typing import cast, Mapping, MutableSequence, Optional +from typing import cast, Optional +from collections.abc import Mapping, MutableSequence from qutebrowser.qt import machinery from qutebrowser.qt.core import pyqtSlot, QUrl, QObject, pyqtSignal diff --git a/qutebrowser/browser/navigate.py b/qutebrowser/browser/navigate.py index e75365bcd..956f222b4 100644 --- a/qutebrowser/browser/navigate.py +++ b/qutebrowser/browser/navigate.py @@ -6,7 +6,7 @@ import re import posixpath -from typing import Optional, Set +from typing import Optional from qutebrowser.qt.core import QUrl @@ -79,7 +79,7 @@ def incdec(url, count, inc_or_dec): inc_or_dec: Either 'increment' or 'decrement'. """ urlutils.ensure_valid(url) - segments: Optional[Set[str]] = ( + segments: Optional[set[str]] = ( set(config.val.url.incdec_segments) ) diff --git a/qutebrowser/browser/network/pac.py b/qutebrowser/browser/network/pac.py index 656c620db..20516366e 100644 --- a/qutebrowser/browser/network/pac.py +++ b/qutebrowser/browser/network/pac.py @@ -242,7 +242,7 @@ class PACFetcher(QObject): pac_prefix = "pac+" assert url.scheme().startswith(pac_prefix) - url.setScheme(url.scheme()[len(pac_prefix):]) + url.setScheme(url.scheme().removeprefix(pac_prefix)) self._pac_url = url with qtlog.disable_qt_msghandler(): diff --git a/qutebrowser/browser/network/proxy.py b/qutebrowser/browser/network/proxy.py index 5b29c29fc..3e549dfb7 100644 --- a/qutebrowser/browser/network/proxy.py +++ b/qutebrowser/browser/network/proxy.py @@ -4,7 +4,7 @@ """Handling of proxies.""" -from typing import Optional, List +from typing import Optional from qutebrowser.qt.core import QUrl, pyqtSlot from qutebrowser.qt.network import QNetworkProxy, QNetworkProxyFactory, QNetworkProxyQuery @@ -71,7 +71,7 @@ class ProxyFactory(QNetworkProxyFactory): capabilities &= ~lookup_cap proxy.setCapabilities(capabilities) - def queryProxy(self, query: QNetworkProxyQuery = QNetworkProxyQuery()) -> List[QNetworkProxy]: + def queryProxy(self, query: QNetworkProxyQuery = QNetworkProxyQuery()) -> list[QNetworkProxy]: """Get the QNetworkProxies for a query. Args: diff --git a/qutebrowser/browser/qtnetworkdownloads.py b/qutebrowser/browser/qtnetworkdownloads.py index 63122208f..3d3c0475a 100644 --- a/qutebrowser/browser/qtnetworkdownloads.py +++ b/qutebrowser/browser/qtnetworkdownloads.py @@ -9,7 +9,7 @@ import os.path import shutil import functools import dataclasses -from typing import Dict, IO, Optional +from typing import IO, Optional from qutebrowser.qt.core import pyqtSlot, pyqtSignal, QTimer, QUrl from qutebrowser.qt.widgets import QApplication @@ -73,7 +73,7 @@ class DownloadItem(downloads.AbstractDownloadItem): """ super().__init__(manager=manager, parent=manager) self.fileobj: Optional[IO[bytes]] = None - self.raw_headers: Dict[bytes, bytes] = {} + self.raw_headers: dict[bytes, bytes] = {} self._autoclose = True self._retry_info = None diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index 508d510d7..a6a4e8763 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -18,7 +18,8 @@ import textwrap import urllib import collections import secrets -from typing import TypeVar, Callable, Dict, List, Optional, Union, Sequence, Tuple +from typing import TypeVar, Optional, Union +from collections.abc import Sequence, Callable from qutebrowser.qt.core import QUrlQuery, QUrl @@ -35,7 +36,7 @@ pyeval_output = ":pyeval was never called" csrf_token: Optional[str] = None -_HANDLERS: Dict[str, "_HandlerCallable"] = {} +_HANDLERS: dict[str, "_HandlerCallable"] = {} class Error(Exception): @@ -77,7 +78,7 @@ class Redirect(Exception): # Return value: (mimetype, data) (encoded as utf-8 if a str is returned) -_HandlerRet = Tuple[str, Union[str, bytes]] +_HandlerRet = tuple[str, Union[str, bytes]] _HandlerCallable = Callable[[QUrl], _HandlerRet] _Handler = TypeVar('_Handler', bound=_HandlerCallable) @@ -105,7 +106,7 @@ class add_handler: # noqa: N801,N806 pylint: disable=invalid-name return self._function(url) -def data_for_url(url: QUrl) -> Tuple[str, bytes]: +def data_for_url(url: QUrl) -> tuple[str, bytes]: """Get the data to show for the given URL. Args: @@ -180,7 +181,7 @@ def qute_bookmarks(_url: QUrl) -> _HandlerRet: @add_handler('tabs') def qute_tabs(_url: QUrl) -> _HandlerRet: """Handler for qute://tabs. Display information about all open tabs.""" - tabs: Dict[str, List[Tuple[str, str]]] = collections.defaultdict(list) + tabs: dict[str, list[tuple[str, str]]] = collections.defaultdict(list) for win_id, window in objreg.window_registry.items(): if sip.isdeleted(window): continue @@ -201,7 +202,7 @@ def qute_tabs(_url: QUrl) -> _HandlerRet: def history_data( start_time: float, offset: int = None -) -> Sequence[Dict[str, Union[str, int]]]: +) -> Sequence[dict[str, Union[str, int]]]: """Return history data. Arguments: diff --git a/qutebrowser/browser/shared.py b/qutebrowser/browser/shared.py index 3fa52c996..061b820b9 100644 --- a/qutebrowser/browser/shared.py +++ b/qutebrowser/browser/shared.py @@ -10,7 +10,8 @@ import html import enum import netrc import tempfile -from typing import Callable, Mapping, List, Optional, Iterable, Iterator +from typing import Optional +from collections.abc import Mapping, Iterable, Iterator, Callable from qutebrowser.qt.core import QUrl, pyqtBoundSignal @@ -457,7 +458,7 @@ class FileSelectionMode(enum.Enum): folder = enum.auto() -def choose_file(qb_mode: FileSelectionMode) -> List[str]: +def choose_file(qb_mode: FileSelectionMode) -> list[str]: """Select file(s)/folder for up-/downloading, using an external command. Args: @@ -497,10 +498,10 @@ def choose_file(qb_mode: FileSelectionMode) -> List[str]: def _execute_fileselect_command( - command: List[str], + command: list[str], qb_mode: FileSelectionMode, tmpfilename: Optional[str] = None -) -> List[str]: +) -> list[str]: """Execute external command to choose file. Args: @@ -534,7 +535,7 @@ def _execute_fileselect_command( def _validated_selected_files( qb_mode: FileSelectionMode, - selected_files: List[str], + selected_files: list[str], ) -> Iterator[str]: """Validates selected files if they are. diff --git a/qutebrowser/browser/urlmarks.py b/qutebrowser/browser/urlmarks.py index 2d2563a1a..f9879274b 100644 --- a/qutebrowser/browser/urlmarks.py +++ b/qutebrowser/browser/urlmarks.py @@ -15,7 +15,7 @@ import os.path import html import functools import collections -from typing import MutableMapping +from collections.abc import MutableMapping from qutebrowser.qt.core import pyqtSignal, QUrl, QObject diff --git a/qutebrowser/browser/webelem.py b/qutebrowser/browser/webelem.py index 556623ee5..82960cc8d 100644 --- a/qutebrowser/browser/webelem.py +++ b/qutebrowser/browser/webelem.py @@ -4,7 +4,8 @@ """Generic web element related code.""" -from typing import Iterator, Optional, Set, TYPE_CHECKING, Union, Dict +from typing import Optional, TYPE_CHECKING, Union +from collections.abc import Iterator import collections.abc from qutebrowser.qt import machinery @@ -93,7 +94,7 @@ class AbstractWebElement(collections.abc.MutableMapping): # type: ignore[type-a """Get the geometry for this element.""" raise NotImplementedError - def classes(self) -> Set[str]: + def classes(self) -> set[str]: """Get a set of classes assigned to this element.""" raise NotImplementedError @@ -336,7 +337,7 @@ class AbstractWebElement(collections.abc.MutableMapping): # type: ignore[type-a log.webelem.debug("Sending fake click to {!r} at position {} with " "target {}".format(self, pos, click_target)) - target_modifiers: Dict[usertypes.ClickTarget, KeyboardModifierType] = { + target_modifiers: dict[usertypes.ClickTarget, KeyboardModifierType] = { usertypes.ClickTarget.normal: Qt.KeyboardModifier.NoModifier, usertypes.ClickTarget.window: Qt.KeyboardModifier.AltModifier | Qt.KeyboardModifier.ShiftModifier, usertypes.ClickTarget.tab: Qt.KeyboardModifier.ControlModifier, diff --git a/qutebrowser/browser/webengine/darkmode.py b/qutebrowser/browser/webengine/darkmode.py index 8f1908547..88b71a8fe 100644 --- a/qutebrowser/browser/webengine/darkmode.py +++ b/qutebrowser/browser/webengine/darkmode.py @@ -125,8 +125,8 @@ import copy import enum import dataclasses import collections -from typing import (Any, Iterator, Mapping, MutableMapping, Optional, Set, Tuple, Union, - Sequence, List) +from typing import (Any, Optional, Union) +from collections.abc import Iterator, Mapping, MutableMapping, Sequence from qutebrowser.config import config from qutebrowser.utils import usertypes, utils, log, version @@ -212,7 +212,7 @@ class _Setting: return str(value) return str(self.mapping[value]) - def chromium_tuple(self, value: Any) -> Optional[Tuple[str, str]]: + def chromium_tuple(self, value: Any) -> Optional[tuple[str, str]]: """Get the Chromium key and value, or None if no value should be set.""" if self.mapping is not None and self.mapping[value] is None: return None @@ -242,7 +242,7 @@ class _Definition: def __init__( self, *args: _Setting, - mandatory: Set[str], + mandatory: set[str], prefix: str, switch_names: Mapping[Optional[str], str] = None, ) -> None: @@ -255,7 +255,7 @@ class _Definition: else: self._switch_names = {None: _BLINK_SETTINGS} - def prefixed_settings(self) -> Iterator[Tuple[str, _Setting]]: + def prefixed_settings(self) -> Iterator[tuple[str, _Setting]]: """Get all "prepared" settings. Yields tuples which contain the Chromium setting key (e.g. 'blink-settings' or @@ -399,7 +399,7 @@ def settings( *, versions: version.WebEngineVersions, special_flags: Sequence[str], -) -> Mapping[str, Sequence[Tuple[str, str]]]: +) -> Mapping[str, Sequence[tuple[str, str]]]: """Get necessary blink settings to configure dark mode for QtWebEngine. Args: @@ -413,12 +413,12 @@ def settings( variant = _variant(versions) log.init.debug(f"Darkmode variant: {variant.name}") - result: Mapping[str, List[Tuple[str, str]]] = collections.defaultdict(list) + result: Mapping[str, list[tuple[str, str]]] = collections.defaultdict(list) blink_settings_flag = f'--{_BLINK_SETTINGS}=' for flag in special_flags: if flag.startswith(blink_settings_flag): - for pair in flag[len(blink_settings_flag):].split(','): + for pair in flag.removeprefix(blink_settings_flag).split(','): key, val = pair.split('=', maxsplit=1) result[_BLINK_SETTINGS].append((key, val)) diff --git a/qutebrowser/browser/webengine/notification.py b/qutebrowser/browser/webengine/notification.py index e8b2e27f1..d49446065 100644 --- a/qutebrowser/browser/webengine/notification.py +++ b/qutebrowser/browser/webengine/notification.py @@ -33,7 +33,8 @@ import dataclasses import itertools import functools import subprocess -from typing import Any, List, Dict, Optional, Iterator, Type, TYPE_CHECKING +from typing import Any, Optional, TYPE_CHECKING +from collections.abc import Iterator from qutebrowser.qt import machinery from qutebrowser.qt.core import (Qt, QObject, QVariant, QMetaType, QByteArray, pyqtSlot, @@ -195,7 +196,7 @@ class NotificationBridgePresenter(QObject): def __init__(self, parent: QObject = None) -> None: super().__init__(parent) - self._active_notifications: Dict[int, 'QWebEngineNotification'] = {} + self._active_notifications: dict[int, 'QWebEngineNotification'] = {} self._adapter: Optional[AbstractNotificationAdapter] = None config.instance.changed.connect(self._init_adapter) @@ -232,8 +233,8 @@ class NotificationBridgePresenter(QObject): def _get_adapter_candidates( self, setting: str, - ) -> List[Type[AbstractNotificationAdapter]]: - candidates: Dict[str, List[Type[AbstractNotificationAdapter]]] = { + ) -> list[type[AbstractNotificationAdapter]]: + candidates: dict[str, list[type[AbstractNotificationAdapter]]] = { "libnotify": [ DBusNotificationAdapter, SystrayNotificationAdapter, @@ -665,7 +666,7 @@ class _ServerCapabilities: kde_origin_name: bool @classmethod - def from_list(cls, capabilities: List[str]) -> "_ServerCapabilities": + def from_list(cls, capabilities: list[str]) -> "_ServerCapabilities": return cls( actions='actions' in capabilities, body_markup='body-markup' in capabilities, @@ -951,10 +952,10 @@ class DBusNotificationAdapter(AbstractNotificationAdapter): qtutils.extract_enum_val(QMetaType.Type.QStringList), ) - def _get_hints_arg(self, *, origin_url: QUrl, icon: QImage) -> Dict[str, Any]: + def _get_hints_arg(self, *, origin_url: QUrl, icon: QImage) -> dict[str, Any]: """Get the hints argument for present().""" origin_url_str = origin_url.toDisplayString() - hints: Dict[str, Any] = { + hints: dict[str, Any] = { # Include the origin in case the user wants to do different things # with different origin's notifications. "x-qutebrowser-origin": origin_url_str, @@ -984,7 +985,7 @@ class DBusNotificationAdapter(AbstractNotificationAdapter): title: str, body: str, actions: QDBusArgument, - hints: Dict[str, Any], + hints: dict[str, Any], timeout: int, ) -> Any: """Wrapper around DBus call to use keyword args.""" diff --git a/qutebrowser/browser/webengine/webengineelem.py b/qutebrowser/browser/webengine/webengineelem.py index c387ebbcf..f65044998 100644 --- a/qutebrowser/browser/webengine/webengineelem.py +++ b/qutebrowser/browser/webengine/webengineelem.py @@ -5,7 +5,8 @@ """QtWebEngine specific part of the web element API.""" from typing import ( - TYPE_CHECKING, Any, Callable, Dict, Iterator, Optional, Set, Tuple, Union) + TYPE_CHECKING, Any, Optional, Union) +from collections.abc import Iterator, Callable from qutebrowser.qt.core import QRect, QEventLoop from qutebrowser.qt.widgets import QApplication @@ -24,11 +25,11 @@ class WebEngineElement(webelem.AbstractWebElement): _tab: "webenginetab.WebEngineTab" - def __init__(self, js_dict: Dict[str, Any], + def __init__(self, js_dict: dict[str, Any], tab: 'webenginetab.WebEngineTab') -> None: super().__init__(tab) # Do some sanity checks on the data we get from JS - js_dict_types: Dict[str, Union[type, Tuple[type, ...]]] = { + js_dict_types: dict[str, Union[type, tuple[type, ...]]] = { 'id': int, 'text': str, 'value': (str, int, float), @@ -105,7 +106,7 @@ class WebEngineElement(webelem.AbstractWebElement): log.stub() return QRect() - def classes(self) -> Set[str]: + def classes(self) -> set[str]: """Get a list of classes assigned to this element.""" return set(self._js_dict['class_name'].split()) diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index 83c54a3b3..c7b07f6e3 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -12,7 +12,7 @@ Module attributes: import os import operator import pathlib -from typing import cast, Any, List, Optional, Tuple, Union, TYPE_CHECKING +from typing import cast, Any, Optional, Union, TYPE_CHECKING from qutebrowser.qt import machinery from qutebrowser.qt.gui import QFont @@ -543,7 +543,7 @@ def _init_default_settings(): - Make sure the devtools always get images/JS permissions. - On Qt 6, make sure files in the data path can load external resources. """ - devtools_settings: List[Tuple[str, Any]] = [ + devtools_settings: list[tuple[str, Any]] = [ ('content.javascript.enabled', True), ('content.images', True), ('content.cookies.accept', 'all'), @@ -556,7 +556,7 @@ def _init_default_settings(): hide_userconfig=True) if machinery.IS_QT6: - userscripts_settings: List[Tuple[str, Any]] = [ + userscripts_settings: list[tuple[str, Any]] = [ ("content.local_content_can_access_remote_urls", True), ("content.local_content_can_access_file_urls", False), ] diff --git a/qutebrowser/browser/webengine/webview.py b/qutebrowser/browser/webengine/webview.py index 96c0c97e5..0b1d41599 100644 --- a/qutebrowser/browser/webengine/webview.py +++ b/qutebrowser/browser/webengine/webview.py @@ -5,7 +5,8 @@ """The main browser widget for QtWebEngine.""" import mimetypes -from typing import List, Iterable, Optional +from typing import Optional +from collections.abc import Iterable from qutebrowser.qt import machinery from qutebrowser.qt.core import pyqtSignal, pyqtSlot, QUrl @@ -316,7 +317,7 @@ class WebEnginePage(QWebEnginePage): mode: QWebEnginePage.FileSelectionMode, old_files: Iterable[Optional[str]], accepted_mimetypes: Iterable[Optional[str]], - ) -> List[str]: + ) -> list[str]: """Override chooseFiles to (optionally) invoke custom file uploader.""" accepted_mimetypes_filtered = [m for m in accepted_mimetypes if m is not None] old_files_filtered = [f for f in old_files if f is not None] diff --git a/qutebrowser/browser/webkit/certificateerror.py b/qutebrowser/browser/webkit/certificateerror.py index 59d9cc897..2c18af62e 100644 --- a/qutebrowser/browser/webkit/certificateerror.py +++ b/qutebrowser/browser/webkit/certificateerror.py @@ -4,7 +4,8 @@ """A wrapper over a list of QSslErrors.""" -from typing import Sequence, Optional +from typing import Optional +from collections.abc import Sequence from qutebrowser.qt.network import QSslError, QNetworkReply diff --git a/qutebrowser/browser/webkit/cookies.py b/qutebrowser/browser/webkit/cookies.py index 9e6ae2f1b..af881175d 100644 --- a/qutebrowser/browser/webkit/cookies.py +++ b/qutebrowser/browser/webkit/cookies.py @@ -4,7 +4,7 @@ """Handling of HTTP cookies.""" -from typing import Sequence +from collections.abc import Sequence from qutebrowser.qt.network import QNetworkCookie, QNetworkCookieJar from qutebrowser.qt.core import pyqtSignal, QDateTime diff --git a/qutebrowser/browser/webkit/httpheaders.py b/qutebrowser/browser/webkit/httpheaders.py index 95b7b7104..5c22405e0 100644 --- a/qutebrowser/browser/webkit/httpheaders.py +++ b/qutebrowser/browser/webkit/httpheaders.py @@ -8,7 +8,6 @@ import email.headerregistry import email.errors import dataclasses import os.path -from typing import Type from qutebrowser.qt.network import QNetworkRequest @@ -25,7 +24,7 @@ class DefectWrapper: """Wrapper around a email.error for comparison.""" - error_class: Type[email.errors.MessageDefect] + error_class: type[email.errors.MessageDefect] line: str def __eq__(self, other): diff --git a/qutebrowser/browser/webkit/mhtml.py b/qutebrowser/browser/webkit/mhtml.py index 692689b0a..11e381929 100644 --- a/qutebrowser/browser/webkit/mhtml.py +++ b/qutebrowser/browser/webkit/mhtml.py @@ -19,7 +19,7 @@ import email.mime.multipart import email.message import quopri import dataclasses -from typing import MutableMapping, Set, Tuple, Callable +from collections.abc import MutableMapping, Callable from qutebrowser.qt.core import QUrl @@ -177,7 +177,7 @@ class MHTMLWriter: return msg -_PendingDownloadType = Set[Tuple[QUrl, downloads.AbstractDownloadItem]] +_PendingDownloadType = set[tuple[QUrl, downloads.AbstractDownloadItem]] class _Downloader: diff --git a/qutebrowser/browser/webkit/network/networkmanager.py b/qutebrowser/browser/webkit/network/networkmanager.py index 06402a547..a950d4239 100644 --- a/qutebrowser/browser/webkit/network/networkmanager.py +++ b/qutebrowser/browser/webkit/network/networkmanager.py @@ -7,7 +7,8 @@ import collections import html import dataclasses -from typing import TYPE_CHECKING, Dict, MutableMapping, Optional, Set +from typing import TYPE_CHECKING, Optional +from collections.abc import MutableMapping from qutebrowser.qt.core import pyqtSlot, pyqtSignal, QUrl, QByteArray from qutebrowser.qt.network import (QNetworkAccessManager, QNetworkReply, QSslConfiguration, @@ -29,7 +30,7 @@ if TYPE_CHECKING: HOSTBLOCK_ERROR_STRING = '%HOSTBLOCK%' -_proxy_auth_cache: Dict['ProxyId', 'prompt.AuthInfo'] = {} +_proxy_auth_cache: dict['ProxyId', 'prompt.AuthInfo'] = {} @dataclasses.dataclass(frozen=True) @@ -110,7 +111,7 @@ def init(): _SavedErrorsType = MutableMapping[ urlutils.HostTupleType, - Set[certificateerror.CertificateErrorWrapper], + set[certificateerror.CertificateErrorWrapper], ] diff --git a/qutebrowser/browser/webkit/tabhistory.py b/qutebrowser/browser/webkit/tabhistory.py index 80a572385..458f493d1 100644 --- a/qutebrowser/browser/webkit/tabhistory.py +++ b/qutebrowser/browser/webkit/tabhistory.py @@ -4,7 +4,8 @@ """Utilities related to QWebHistory.""" -from typing import Any, List, Mapping +from typing import Any +from collections.abc import Mapping from qutebrowser.qt.core import QByteArray, QDataStream, QIODevice, QUrl @@ -66,7 +67,7 @@ def serialize(items): """ data = QByteArray() stream = QDataStream(data, QIODevice.OpenModeFlag.ReadWrite) - user_data: List[Mapping[str, Any]] = [] + user_data: list[Mapping[str, Any]] = [] current_idx = None diff --git a/qutebrowser/browser/webkit/webkitelem.py b/qutebrowser/browser/webkit/webkitelem.py index 0400358af..6088a29d3 100644 --- a/qutebrowser/browser/webkit/webkitelem.py +++ b/qutebrowser/browser/webkit/webkitelem.py @@ -4,7 +4,8 @@ """QtWebKit specific part of the web element API.""" -from typing import cast, TYPE_CHECKING, Iterator, List, Optional, Set +from typing import cast, TYPE_CHECKING, Optional +from collections.abc import Iterator from qutebrowser.qt.core import QRect, Qt # pylint: disable=no-name-in-module @@ -90,7 +91,7 @@ class WebKitElement(webelem.AbstractWebElement): self._check_vanished() return self._elem.geometry() - def classes(self) -> Set[str]: + def classes(self) -> set[str]: self._check_vanished() return set(self._elem.classes()) @@ -364,7 +365,7 @@ class WebKitElement(webelem.AbstractWebElement): super()._click_fake_event(click_target) -def get_child_frames(startframe: QWebFrame) -> List[QWebFrame]: +def get_child_frames(startframe: QWebFrame) -> list[QWebFrame]: """Get all children recursively of a given QWebFrame. Loosely based on https://blog.nextgenetics.net/?e=64 @@ -378,7 +379,7 @@ def get_child_frames(startframe: QWebFrame) -> List[QWebFrame]: results = [] frames = [startframe] while frames: - new_frames: List[QWebFrame] = [] + new_frames: list[QWebFrame] = [] for frame in frames: results.append(frame) new_frames += frame.childFrames() diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index 1ae976bea..d89295440 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -7,7 +7,8 @@ import re import functools import xml.etree.ElementTree -from typing import cast, Iterable, Optional +from typing import cast, Optional +from collections.abc import Iterable from qutebrowser.qt.core import pyqtSlot, Qt, QUrl, QPoint, QTimer, QSizeF, QSize from qutebrowser.qt.gui import QIcon diff --git a/qutebrowser/commands/cmdexc.py b/qutebrowser/commands/cmdexc.py index 4335a10e6..9bb5decc3 100644 --- a/qutebrowser/commands/cmdexc.py +++ b/qutebrowser/commands/cmdexc.py @@ -7,7 +7,6 @@ Defined here to avoid circular dependency hell. """ -from typing import List import difflib @@ -21,7 +20,7 @@ class NoSuchCommandError(Error): """Raised when a command isn't found.""" @classmethod - def for_cmd(cls, cmd: str, all_commands: List[str] = None) -> "NoSuchCommandError": + def for_cmd(cls, cmd: str, all_commands: list[str] = None) -> "NoSuchCommandError": """Raise an exception for the given command.""" suffix = '' if all_commands: diff --git a/qutebrowser/commands/command.py b/qutebrowser/commands/command.py index effdcc9b0..620f6a4ae 100644 --- a/qutebrowser/commands/command.py +++ b/qutebrowser/commands/command.py @@ -9,8 +9,8 @@ import collections import traceback import typing import dataclasses -from typing import (Any, MutableMapping, MutableSequence, Tuple, Union, List, Optional, - Callable) +from typing import (Any, Union, Optional) +from collections.abc import MutableMapping, MutableSequence, Callable from qutebrowser.api import cmdutils from qutebrowser.commands import cmdexc, argparser @@ -30,7 +30,7 @@ class ArgInfo: metavar: Optional[str] = None flag: Optional[str] = None completion: Optional[Callable[..., completionmodel.CompletionModel]] = None - choices: Optional[List[str]] = None + choices: Optional[list[str]] = None class Command: @@ -105,10 +105,10 @@ class Command: self.parser.add_argument('-h', '--help', action=argparser.HelpAction, default=argparser.SUPPRESS, nargs=0, help=argparser.SUPPRESS) - self.opt_args: MutableMapping[str, Tuple[str, str]] = collections.OrderedDict() + self.opt_args: MutableMapping[str, tuple[str, str]] = collections.OrderedDict() self.namespace = None self._count = None - self.pos_args: MutableSequence[Tuple[str, str]] = [] + self.pos_args: MutableSequence[tuple[str, str]] = [] self.flags_with_args: MutableSequence[str] = [] self._has_vararg = False diff --git a/qutebrowser/commands/parser.py b/qutebrowser/commands/parser.py index d45a18aea..00e5c9083 100644 --- a/qutebrowser/commands/parser.py +++ b/qutebrowser/commands/parser.py @@ -5,7 +5,7 @@ """Module for parsing commands entered into the browser.""" import dataclasses -from typing import List, Iterator +from collections.abc import Iterator from qutebrowser.commands import cmdexc, command from qutebrowser.misc import split, objects @@ -18,8 +18,8 @@ class ParseResult: """The result of parsing a commandline.""" cmd: command.Command - args: List[str] - cmdline: List[str] + args: list[str] + cmdline: list[str] class CommandParser: @@ -107,7 +107,7 @@ class CommandParser: for sub in sub_texts: yield self.parse(sub, **kwargs) - def parse_all(self, text: str, **kwargs: bool) -> List[ParseResult]: + def parse_all(self, text: str, **kwargs: bool) -> list[ParseResult]: """Wrapper over _parse_all_gen.""" return list(self._parse_all_gen(text, **kwargs)) @@ -161,7 +161,7 @@ class CommandParser: cmdstr = matches[0] return cmdstr - def _split_args(self, cmd: command.Command, argstr: str, keep: bool) -> List[str]: + def _split_args(self, cmd: command.Command, argstr: str, keep: bool) -> list[str]: """Split the arguments from an arg string. Args: diff --git a/qutebrowser/commands/runners.py b/qutebrowser/commands/runners.py index dec1fb4f5..636f1bf6b 100644 --- a/qutebrowser/commands/runners.py +++ b/qutebrowser/commands/runners.py @@ -7,7 +7,8 @@ import traceback import re import contextlib -from typing import TYPE_CHECKING, Callable, Dict, Tuple, Iterator, Mapping, MutableMapping +from typing import TYPE_CHECKING +from collections.abc import Iterator, Mapping, MutableMapping, Callable from qutebrowser.qt.core import pyqtSlot, QUrl, QObject @@ -21,7 +22,7 @@ if TYPE_CHECKING: _ReplacementFunction = Callable[['tabbedbrowser.TabbedBrowser'], str] -last_command: Dict[usertypes.KeyMode, Tuple[str, int]] = {} +last_command: dict[usertypes.KeyMode, tuple[str, int]] = {} def _url(tabbed_browser): @@ -38,7 +39,7 @@ def _url(tabbed_browser): def _init_variable_replacements() -> Mapping[str, _ReplacementFunction]: """Return a dict from variable replacements to fns processing them.""" - replacements: Dict[str, _ReplacementFunction] = { + replacements: dict[str, _ReplacementFunction] = { 'url': lambda tb: _url(tb).toString( QUrl.ComponentFormattingOption.FullyEncoded | QUrl.UrlFormattingOption.RemovePassword), 'url:pretty': lambda tb: _url(tb).toString( diff --git a/qutebrowser/commands/userscripts.py b/qutebrowser/commands/userscripts.py index 01710a63c..e929de3d2 100644 --- a/qutebrowser/commands/userscripts.py +++ b/qutebrowser/commands/userscripts.py @@ -7,7 +7,8 @@ import os import os.path import tempfile -from typing import cast, Any, MutableMapping, Tuple +from typing import cast, Any +from collections.abc import MutableMapping from qutebrowser.qt.core import pyqtSignal, pyqtSlot, QObject, QSocketNotifier @@ -106,7 +107,7 @@ class _BaseUserscriptRunner(QObject): self._env: MutableMapping[str, str] = {} self._text_stored = False self._html_stored = False - self._args: Tuple[Any, ...] = () + self._args: tuple[Any, ...] = () self._kwargs = {} def store_text(self, text): @@ -155,7 +156,7 @@ class _BaseUserscriptRunner(QObject): self.proc = guiprocess.GUIProcess( 'userscript', additional_env=self._env, - output_messages=output_messages, verbose=verbose, parent=self) + output_messages=output_messages, verbose=verbose) self.proc.finished.connect(self.on_proc_finished) self.proc.error.connect(self.on_proc_error) self.proc.start(cmd, args) diff --git a/qutebrowser/completion/completer.py b/qutebrowser/completion/completer.py index 846fa7c22..408660c3a 100644 --- a/qutebrowser/completion/completer.py +++ b/qutebrowser/completion/completer.py @@ -127,7 +127,7 @@ class Completer(QObject): Return: ([parts_before_cursor], 'part_under_cursor', [parts_after_cursor]) """ - text = self._cmd.text()[len(self._cmd.prefix()):] + text = self._cmd.text().removeprefix(self._cmd.prefix()) if not text or not text.strip(): # Only ":", empty part under the cursor with nothing before/after return [], '', [] diff --git a/qutebrowser/completion/models/__init__.py b/qutebrowser/completion/models/__init__.py index 4fd45e160..a55a91215 100644 --- a/qutebrowser/completion/models/__init__.py +++ b/qutebrowser/completion/models/__init__.py @@ -4,7 +4,8 @@ """Models for the command completion.""" -from typing import Sequence, Optional +from typing import Optional +from collections.abc import Sequence from qutebrowser.completion.models.util import DeleteFuncType from qutebrowser.qt.core import QAbstractItemModel diff --git a/qutebrowser/completion/models/completionmodel.py b/qutebrowser/completion/models/completionmodel.py index 5a85f7281..a4eed93d1 100644 --- a/qutebrowser/completion/models/completionmodel.py +++ b/qutebrowser/completion/models/completionmodel.py @@ -4,7 +4,8 @@ """A model that proxies access to one or more completion categories.""" -from typing import MutableSequence, overload, Optional, Any, cast +from typing import overload, Optional, Any, cast +from collections.abc import MutableSequence from qutebrowser.qt import machinery from qutebrowser.qt.core import Qt, QModelIndex, QAbstractItemModel, QObject diff --git a/qutebrowser/completion/models/filepathcategory.py b/qutebrowser/completion/models/filepathcategory.py index 23ad0d173..0b2d887b8 100644 --- a/qutebrowser/completion/models/filepathcategory.py +++ b/qutebrowser/completion/models/filepathcategory.py @@ -14,7 +14,8 @@ is harder to achieve via pathlib. import glob import os import os.path -from typing import List, Optional, Iterable +from typing import Optional +from collections.abc import Iterable from qutebrowser.qt.core import QAbstractListModel, QModelIndex, QObject, Qt, QUrl @@ -28,7 +29,7 @@ class FilePathCategory(QAbstractListModel, BaseCategory): def __init__(self, name: str, parent: QObject = None) -> None: super().__init__(parent) - self._paths: List[str] = [] + self._paths: list[str] = [] self.name = name self.columns_to_filter = [0] diff --git a/qutebrowser/completion/models/listcategory.py b/qutebrowser/completion/models/listcategory.py index 10639f47d..088f93791 100644 --- a/qutebrowser/completion/models/listcategory.py +++ b/qutebrowser/completion/models/listcategory.py @@ -5,7 +5,7 @@ """Completion category that uses a list of tuples as a data source.""" import re -from typing import Iterable, Tuple +from collections.abc import Iterable from qutebrowser.qt.core import QSortFilterProxyModel, QRegularExpression from qutebrowser.qt.gui import QStandardItem, QStandardItemModel @@ -21,7 +21,7 @@ class ListCategory(QSortFilterProxyModel, BaseCategory): def __init__(self, name: str, - items: Iterable[Tuple[str, ...]], + items: Iterable[tuple[str, ...]], sort: bool = True, delete_func: util.DeleteFuncType = None, parent: QWidget = None): diff --git a/qutebrowser/completion/models/miscmodels.py b/qutebrowser/completion/models/miscmodels.py index ea3febe4d..da7c65094 100644 --- a/qutebrowser/completion/models/miscmodels.py +++ b/qutebrowser/completion/models/miscmodels.py @@ -6,7 +6,7 @@ import datetime import itertools -from typing import List, Sequence, Tuple +from collections.abc import Sequence from qutebrowser.config import config, configdata from qutebrowser.utils import objreg, log, utils @@ -113,7 +113,7 @@ def _tabs(*, win_id_filter=lambda _win_id: True, add_win_id=True, cur_win_id=Non tabs_are_windows = config.val.tabs.tabs_are_windows # list storing all single-tabbed windows when tabs_are_windows - windows: List[Tuple[str, str, str, str]] = [] + windows: list[tuple[str, str, str, str]] = [] for win_id in objreg.window_registry: if not win_id_filter(win_id): @@ -123,7 +123,7 @@ def _tabs(*, win_id_filter=lambda _win_id: True, add_win_id=True, cur_win_id=Non window=win_id) if tabbed_browser.is_shutting_down: continue - tab_entries: List[Tuple[str, str, str, str]] = [] + tab_entries: list[tuple[str, str, str, str]] = [] for idx in range(tabbed_browser.widget.count()): tab = tabbed_browser.widget.widget(idx) tab_str = ("{}/{}".format(win_id, idx + 1) if add_win_id diff --git a/qutebrowser/completion/models/urlmodel.py b/qutebrowser/completion/models/urlmodel.py index 10bee0393..7532428f1 100644 --- a/qutebrowser/completion/models/urlmodel.py +++ b/qutebrowser/completion/models/urlmodel.py @@ -4,7 +4,7 @@ """Function to return the url completion model for the `open` command.""" -from typing import Dict, Sequence +from collections.abc import Sequence from qutebrowser.completion.models import (completionmodel, filepathcategory, listcategory, histcategory, @@ -58,7 +58,7 @@ def url(*, info): in sorted(config.val.url.searchengines.items()) if k != 'DEFAULT'] categories = config.val.completion.open_categories - models: Dict[str, BaseCategory] = {} + models: dict[str, BaseCategory] = {} if searchengines and 'searchengines' in categories: models['searchengines'] = listcategory.ListCategory( diff --git a/qutebrowser/completion/models/util.py b/qutebrowser/completion/models/util.py index 492e1b2e5..fb48017e8 100644 --- a/qutebrowser/completion/models/util.py +++ b/qutebrowser/completion/models/util.py @@ -4,7 +4,7 @@ """Utility functions for completion models.""" -from typing import Callable, Sequence +from collections.abc import Sequence, Callable from qutebrowser.utils import usertypes from qutebrowser.misc import objects diff --git a/qutebrowser/components/braveadblock.py b/qutebrowser/components/braveadblock.py index a827eb546..5be1efbfe 100644 --- a/qutebrowser/components/braveadblock.py +++ b/qutebrowser/components/braveadblock.py @@ -10,7 +10,8 @@ import pathlib import functools import contextlib import subprocess -from typing import Optional, IO, Iterator +from typing import Optional, IO +from collections.abc import Iterator from qutebrowser.qt.core import QUrl diff --git a/qutebrowser/components/hostblock.py b/qutebrowser/components/hostblock.py index 672a530df..7777e1429 100644 --- a/qutebrowser/components/hostblock.py +++ b/qutebrowser/components/hostblock.py @@ -9,7 +9,7 @@ import posixpath import zipfile import logging import pathlib -from typing import cast, IO, Set +from typing import cast, IO from qutebrowser.qt.core import QUrl @@ -92,8 +92,8 @@ class HostBlocker: ) -> None: self.enabled = _should_be_used() self._has_basedir = has_basedir - self._blocked_hosts: Set[str] = set() - self._config_blocked_hosts: Set[str] = set() + self._blocked_hosts: set[str] = set() + self._config_blocked_hosts: set[str] = set() self._local_hosts_file = str(data_dir / "blocked-hosts") self.update_files() @@ -139,7 +139,7 @@ class HostBlocker: ) info.block() - def _read_hosts_line(self, raw_line: bytes) -> Set[str]: + def _read_hosts_line(self, raw_line: bytes) -> set[str]: """Read hosts from the given line. Args: @@ -175,7 +175,7 @@ class HostBlocker: return filtered_hosts - def _read_hosts_file(self, filename: str, target: Set[str]) -> bool: + def _read_hosts_file(self, filename: str, target: set[str]) -> bool: """Read hosts from the given filename. Args: diff --git a/qutebrowser/components/misccommands.py b/qutebrowser/components/misccommands.py index 0d8fa0b2e..b4eaa55d1 100644 --- a/qutebrowser/components/misccommands.py +++ b/qutebrowser/components/misccommands.py @@ -11,7 +11,8 @@ import os import signal import logging import pathlib -from typing import Optional, Sequence, Callable +from typing import Optional +from collections.abc import Sequence, Callable try: import hunter diff --git a/qutebrowser/components/readlinecommands.py b/qutebrowser/components/readlinecommands.py index a9626637d..a26f7ea4c 100644 --- a/qutebrowser/components/readlinecommands.py +++ b/qutebrowser/components/readlinecommands.py @@ -5,7 +5,8 @@ """Bridge to provide readline-like shortcuts for QLineEdits.""" import os -from typing import Iterable, Optional, MutableMapping, Any, Callable +from typing import Optional, Any +from collections.abc import Iterable, MutableMapping, Callable from qutebrowser.qt.widgets import QApplication, QLineEdit diff --git a/qutebrowser/components/scrollcommands.py b/qutebrowser/components/scrollcommands.py index 3ee525535..da4544bd7 100644 --- a/qutebrowser/components/scrollcommands.py +++ b/qutebrowser/components/scrollcommands.py @@ -4,7 +4,7 @@ """Scrolling-related commands.""" -from typing import Dict, Callable +from collections.abc import Callable from qutebrowser.api import cmdutils, apitypes @@ -41,7 +41,7 @@ def scroll(tab: apitypes.Tab, direction: str, count: int = 1) -> None: count: multiplier """ # FIXME:mypy Use a callback protocol to enforce having 'count'? - funcs: Dict[str, Callable[..., None]] = { + funcs: dict[str, Callable[..., None]] = { 'up': tab.scroller.up, 'down': tab.scroller.down, 'left': tab.scroller.left, diff --git a/qutebrowser/components/utils/blockutils.py b/qutebrowser/components/utils/blockutils.py index a65085949..154c04317 100644 --- a/qutebrowser/components/utils/blockutils.py +++ b/qutebrowser/components/utils/blockutils.py @@ -6,7 +6,7 @@ import os import functools -from typing import IO, List, Optional +from typing import IO, Optional from qutebrowser.qt.core import QUrl, QObject, pyqtSignal @@ -47,11 +47,11 @@ class BlocklistDownloads(QObject): single_download_finished = pyqtSignal(object) # arg: the file object all_downloads_finished = pyqtSignal(int) # arg: download count - def __init__(self, urls: List[QUrl], parent: Optional[QObject] = None) -> None: + def __init__(self, urls: list[QUrl], parent: Optional[QObject] = None) -> None: super().__init__(parent) self._urls = urls - self._in_progress: List[downloads.TempDownload] = [] + self._in_progress: list[downloads.TempDownload] = [] self._done_count = 0 self._finished_registering_downloads = False self._started = False diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index cb7fe77b3..d286bf733 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -7,8 +7,8 @@ import copy import contextlib import functools -from typing import (TYPE_CHECKING, Any, Callable, Dict, Iterator, List, Mapping, - MutableMapping, MutableSequence, Optional, Tuple, cast) +from typing import (TYPE_CHECKING, Any, Optional, cast) +from collections.abc import Iterator, Mapping, MutableMapping, MutableSequence, Callable from qutebrowser.qt.core import pyqtSignal, QObject, QUrl @@ -29,7 +29,7 @@ key_instance = cast('KeyConfig', None) cache = cast('configcache.ConfigCache', None) # Keeping track of all change filters to validate them later. -change_filters: List["change_filter"] = [] +change_filters: list["change_filter"] = [] # Sentinel UNSET = object() @@ -131,7 +131,7 @@ class KeyConfig: _config: The Config object to be used. """ - _ReverseBindings = Dict[str, MutableSequence[str]] + _ReverseBindings = dict[str, MutableSequence[str]] def __init__(self, config: 'Config') -> None: self._config = config @@ -143,7 +143,7 @@ class KeyConfig: if mode not in configdata.DATA['bindings.default'].default: raise configexc.KeybindingError("Invalid mode {}!".format(mode)) - def get_bindings_for(self, mode: str) -> Dict[keyutils.KeySequence, str]: + def get_bindings_for(self, mode: str) -> dict[keyutils.KeySequence, str]: """Get the combined bindings for the given mode.""" bindings = dict(val.bindings.default[mode]) for key, binding in val.bindings.commands[mode].items(): @@ -291,7 +291,7 @@ class Config(QObject): yaml_config: 'configfiles.YamlConfig', parent: QObject = None) -> None: super().__init__(parent) - self._mutables: MutableMapping[str, Tuple[Any, Any]] = {} + self._mutables: MutableMapping[str, tuple[Any, Any]] = {} self._yaml = yaml_config self._init_values() self.yaml_loaded = False @@ -554,7 +554,7 @@ class Config(QObject): Return: The changed config part as string. """ - lines: List[str] = [] + lines: list[str] = [] for values in sorted(self, key=lambda v: v.opt.name): lines += values.dump(include_hidden=include_hidden) diff --git a/qutebrowser/config/configcache.py b/qutebrowser/config/configcache.py index 9e76466d9..13ddce227 100644 --- a/qutebrowser/config/configcache.py +++ b/qutebrowser/config/configcache.py @@ -4,7 +4,7 @@ """Implementation of a basic config cache.""" -from typing import Any, Dict +from typing import Any from qutebrowser.config import config @@ -22,7 +22,7 @@ class ConfigCache: """ def __init__(self) -> None: - self._cache: Dict[str, Any] = {} + self._cache: dict[str, Any] = {} config.instance.changed.connect(self._on_config_changed) def _on_config_changed(self, attr: str) -> None: diff --git a/qutebrowser/config/configcommands.py b/qutebrowser/config/configcommands.py index c4065ceb9..9012cc2c4 100644 --- a/qutebrowser/config/configcommands.py +++ b/qutebrowser/config/configcommands.py @@ -6,7 +6,8 @@ import os.path import contextlib -from typing import TYPE_CHECKING, Iterator, List, Optional, Any, Tuple +from typing import TYPE_CHECKING, Optional, Any +from collections.abc import Iterator from qutebrowser.qt.core import QUrl, QUrlQuery @@ -473,7 +474,7 @@ class ConfigCommands: raise cmdutils.CommandError("{} already exists - use --force to " "overwrite!".format(filename)) - options: List[Tuple[Optional[urlmatch.UrlPattern], configdata.Option, Any]] = [] + options: list[tuple[Optional[urlmatch.UrlPattern], configdata.Option, Any]] = [] if defaults: options = [(None, opt, opt.default) for _name, opt in sorted(configdata.DATA.items())] diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 90486f702..d939f7ea6 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -9,8 +9,8 @@ Module attributes: DATA: A dict of Option objects after init() has been called. """ -from typing import (Any, Dict, Iterable, List, Mapping, MutableMapping, Optional, - Sequence, Tuple, Union, NoReturn, cast) +from typing import (Any, Optional, Union, NoReturn, cast) +from collections.abc import Iterable, Mapping, MutableMapping, Sequence import functools import dataclasses @@ -53,8 +53,8 @@ class Migrations: deleted: A list of option names which have been removed. """ - renamed: Dict[str, str] = dataclasses.field(default_factory=dict) - deleted: List[str] = dataclasses.field(default_factory=list) + renamed: dict[str, str] = dataclasses.field(default_factory=dict) + deleted: list[str] = dataclasses.field(default_factory=list) def _raise_invalid_node(name: str, what: str, node: Any) -> NoReturn: @@ -186,7 +186,7 @@ def _parse_yaml_backends( def _read_yaml( yaml_data: str, -) -> Tuple[Mapping[str, Option], Migrations]: +) -> tuple[Mapping[str, Option], Migrations]: """Read config data from a YAML file. Args: diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index 0bbc95960..aa65981eb 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -322,8 +322,9 @@ qt.chromium.sandboxing: See the Chromium documentation for more details: - - https://chromium.googlesource.com/chromium/src/\+/HEAD/docs/linux/sandboxing.md[Linux] + - https://chromium.googlesource.com/chromium/src/\+/HEAD/sandbox/linux/README.md[Linux] - https://chromium.googlesource.com/chromium/src/\+/HEAD/docs/design/sandbox.md[Windows] + - https://chromium.googlesource.com/chromium/src/\+/HEAD/sandbox/mac/README.md[Mac] - https://chromium.googlesource.com/chromium/src/\+/HEAD/docs/design/sandbox_faq.md[FAQ (Windows-centric)] # yamllint enable rule:line-length diff --git a/qutebrowser/config/configexc.py b/qutebrowser/config/configexc.py index 4c8291580..85845f6fc 100644 --- a/qutebrowser/config/configexc.py +++ b/qutebrowser/config/configexc.py @@ -6,7 +6,8 @@ import difflib import dataclasses -from typing import Any, Mapping, Optional, Sequence, Union, List +from typing import Any, Optional, Union +from collections.abc import Mapping, Sequence from qutebrowser.utils import usertypes, log @@ -77,7 +78,7 @@ class NoOptionError(Error): """Raised when an option was not found.""" def __init__(self, option: str, *, - all_names: List[str] = None, + all_names: list[str] = None, deleted: bool = False, renamed: str = None) -> None: if deleted: diff --git a/qutebrowser/config/configfiles.py b/qutebrowser/config/configfiles.py index 0680cd0e7..4dc5a9373 100644 --- a/qutebrowser/config/configfiles.py +++ b/qutebrowser/config/configfiles.py @@ -14,8 +14,8 @@ import traceback import configparser import contextlib import re -from typing import (TYPE_CHECKING, Any, Dict, Iterable, Iterator, List, Mapping, - MutableMapping, Optional, Tuple, cast) +from typing import (TYPE_CHECKING, Any, Optional, cast) +from collections.abc import Iterable, Iterator, Mapping, MutableMapping import yaml from qutebrowser.qt.core import pyqtSignal, pyqtSlot, QObject, QSettings, qVersion @@ -34,7 +34,7 @@ if TYPE_CHECKING: state = cast('StateConfig', None) -_SettingsType = Dict[str, Dict[str, Any]] +_SettingsType = dict[str, dict[str, Any]] class VersionChange(enum.Enum): @@ -55,7 +55,7 @@ class VersionChange(enum.Enum): This is intended to use filters like "major" (show major only), "minor" (show major/minor) or "patch" (show all changes). """ - allowed_values: Dict[str, List[VersionChange]] = { + allowed_values: dict[str, list[VersionChange]] = { 'major': [VersionChange.major], 'minor': [VersionChange.major, VersionChange.minor], 'patch': [VersionChange.major, VersionChange.minor, VersionChange.patch], @@ -250,7 +250,7 @@ class YamlConfig(QObject): 'autoconfig.yml') self._dirty = False - self._values: Dict[str, configutils.Values] = {} + self._values: dict[str, configutils.Values] = {} for name, opt in configdata.DATA.items(): self._values[name] = configutils.Values(opt) @@ -702,7 +702,7 @@ class ConfigAPI: ): self._config = conf self._keyconfig = keyconfig - self.errors: List[configexc.ConfigErrorDesc] = [] + self.errors: list[configexc.ConfigErrorDesc] = [] self.configdir = pathlib.Path(standarddir.config()) self.datadir = pathlib.Path(standarddir.data()) self._warn_autoconfig = warn_autoconfig @@ -803,8 +803,8 @@ class ConfigPyWriter: def __init__( self, - options: List[ - Tuple[ + options: list[ + tuple[ Optional[urlmatch.UrlPattern], configdata.Option, Any diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index 56bd7b13a..69288e0f4 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -36,8 +36,9 @@ import functools import operator import json import dataclasses -from typing import (Any, Callable, Dict as DictType, Iterable, Iterator, - List as ListType, Optional, Pattern, Sequence, Tuple, Union) +from typing import Any, Optional, Union +from re import Pattern +from collections.abc import Iterable, Iterator, Sequence, Callable import yaml from qutebrowser.qt.core import QUrl, Qt @@ -65,7 +66,7 @@ BOOLEAN_STATES = {'1': True, 'yes': True, 'true': True, 'on': True, '0': False, 'no': False, 'false': False, 'off': False} -_Completions = Optional[Iterable[Tuple[str, str]]] +_Completions = Optional[Iterable[tuple[str, str]]] _StrUnset = Union[str, usertypes.Unset] _UnsetNone = Union[None, usertypes.Unset] _StrUnsetNone = Union[str, _UnsetNone] @@ -102,16 +103,16 @@ class ValidValues: self, *values: Union[ str, - DictType[str, Optional[str]], - Tuple[str, Optional[str]], + dict[str, Optional[str]], + tuple[str, Optional[str]], ], generate_docs: bool = True, others_permitted: bool = False ) -> None: if not values: raise ValueError("ValidValues with no values makes no sense!") - self.descriptions: DictType[str, str] = {} - self.values: ListType[str] = [] + self.descriptions: dict[str, str] = {} + self.values: list[str] = [] self.generate_docs = generate_docs self.others_permitted = others_permitted for value in values: @@ -189,7 +190,7 @@ class BaseType: def _basic_py_validation( self, value: Any, - pytype: Union[type, Tuple[type, ...]]) -> None: + pytype: Union[type, tuple[type, ...]]) -> None: """Do some basic validation for Python values (emptyness, type). Arguments: @@ -355,7 +356,7 @@ class MappingType(BaseType): MAPPING: A mapping from config values to (translated_value, docs) tuples. """ - MAPPING: DictType[str, Tuple[Any, Optional[str]]] = {} + MAPPING: dict[str, tuple[Any, Optional[str]]] = {} def __init__( self, *, @@ -507,7 +508,7 @@ class List(BaseType): def get_valid_values(self) -> Optional[ValidValues]: return self.valtype.get_valid_values() - def from_str(self, value: str) -> Optional[ListType]: + def from_str(self, value: str) -> Optional[list]: self._basic_str_validation(value) if not value: return None @@ -522,15 +523,15 @@ class List(BaseType): self.to_py(yaml_val) return yaml_val - def from_obj(self, value: Optional[ListType]) -> ListType: + def from_obj(self, value: Optional[list]) -> list: if value is None: return [] return [self.valtype.from_obj(v) for v in value] def to_py( self, - value: Union[ListType, usertypes.Unset] - ) -> Union[ListType, usertypes.Unset]: + value: Union[list, usertypes.Unset] + ) -> Union[list, usertypes.Unset]: self._basic_py_validation(value, list) if isinstance(value, usertypes.Unset): return value @@ -545,13 +546,13 @@ class List(BaseType): "be set!".format(self.length)) return [self.valtype.to_py(v) for v in value] - def to_str(self, value: ListType) -> str: + def to_str(self, value: list) -> str: if not value: # An empty list is treated just like None -> empty string return '' return json.dumps(value) - def to_doc(self, value: ListType, indent: int = 0) -> str: + def to_doc(self, value: list, indent: int = 0) -> str: if not value: return 'empty' @@ -596,7 +597,7 @@ class ListOrValue(BaseType): self.listtype = List(valtype=valtype, none_ok=none_ok, **kwargs) self.valtype = valtype - def _val_and_type(self, value: Any) -> Tuple[Any, BaseType]: + def _val_and_type(self, value: Any) -> tuple[Any, BaseType]: """Get the value and type to use for to_str/to_doc/from_str.""" if isinstance(value, list): if len(value) == 1: @@ -677,15 +678,15 @@ class FlagList(List): ) self.valtype.valid_values = valid_values - def _check_duplicates(self, values: ListType) -> None: + def _check_duplicates(self, values: list) -> None: if len(set(values)) != len(values): raise configexc.ValidationError( values, "List contains duplicate values!") def to_py( self, - value: Union[usertypes.Unset, ListType], - ) -> Union[usertypes.Unset, ListType]: + value: Union[usertypes.Unset, list], + ) -> Union[usertypes.Unset, list]: vals = super().to_py(value) if not isinstance(vals, usertypes.Unset): self._check_duplicates(vals) @@ -1121,7 +1122,7 @@ class QtColor(BaseType): kind = value[:openparen] vals = value[openparen+1:-1].split(',') - converters: DictType[str, Callable[..., QColor]] = { + converters: dict[str, Callable[..., QColor]] = { 'rgba': QColor.fromRgb, 'rgb': QColor.fromRgb, 'hsva': QColor.fromHsv, @@ -1211,7 +1212,7 @@ class FontBase(BaseType): (?P<family>.+) # mandatory font family""", re.VERBOSE) @classmethod - def set_defaults(cls, default_family: ListType[str], default_size: str) -> None: + def set_defaults(cls, default_family: list[str], default_size: str) -> None: """Make sure default_family/default_size are available. If the given family value (fonts.default_family in the config) is @@ -1384,7 +1385,7 @@ class Dict(BaseType): self.fixed_keys = fixed_keys self.required_keys = required_keys - def _validate_keys(self, value: DictType) -> None: + def _validate_keys(self, value: dict) -> None: if (self.fixed_keys is not None and not set(value.keys()).issubset(self.fixed_keys)): raise configexc.ValidationError( @@ -1395,7 +1396,7 @@ class Dict(BaseType): raise configexc.ValidationError( value, "Required keys {}".format(self.required_keys)) - def from_str(self, value: str) -> Optional[DictType]: + def from_str(self, value: str) -> Optional[dict]: self._basic_str_validation(value) if not value: return None @@ -1410,14 +1411,14 @@ class Dict(BaseType): self.to_py(yaml_val) return yaml_val - def from_obj(self, value: Optional[DictType]) -> DictType: + def from_obj(self, value: Optional[dict]) -> dict: if value is None: return {} return {self.keytype.from_obj(key): self.valtype.from_obj(val) for key, val in value.items()} - def _fill_fixed_keys(self, value: DictType) -> DictType: + def _fill_fixed_keys(self, value: dict) -> dict: """Fill missing fixed keys with a None-value.""" if self.fixed_keys is None: return value @@ -1428,8 +1429,8 @@ class Dict(BaseType): def to_py( self, - value: Union[DictType, _UnsetNone] - ) -> Union[DictType, usertypes.Unset]: + value: Union[dict, _UnsetNone] + ) -> Union[dict, usertypes.Unset]: self._basic_py_validation(value, dict) if isinstance(value, usertypes.Unset): return value @@ -1445,13 +1446,13 @@ class Dict(BaseType): for key, val in value.items()} return self._fill_fixed_keys(d) - def to_str(self, value: DictType) -> str: + def to_str(self, value: dict) -> str: if not value: # An empty Dict is treated just like None -> empty string return '' return json.dumps(value, sort_keys=True) - def to_doc(self, value: DictType, indent: int = 0) -> str: + def to_doc(self, value: dict, indent: int = 0) -> str: if not value: return 'empty' lines = ['\n'] @@ -1605,8 +1606,8 @@ class ShellCommand(List): def to_py( self, - value: Union[ListType, usertypes.Unset], - ) -> Union[ListType, usertypes.Unset]: + value: Union[list, usertypes.Unset], + ) -> Union[list, usertypes.Unset]: py_value = super().to_py(value) if isinstance(py_value, usertypes.Unset): return py_value @@ -1763,7 +1764,7 @@ class Padding(Dict): def to_py( # type: ignore[override] self, - value: Union[DictType, _UnsetNone], + value: Union[dict, _UnsetNone], ) -> Union[usertypes.Unset, PaddingValues]: d = super().to_py(value) if isinstance(d, usertypes.Unset): @@ -1916,8 +1917,8 @@ class ConfirmQuit(FlagList): def to_py( self, - value: Union[usertypes.Unset, ListType], - ) -> Union[ListType, usertypes.Unset]: + value: Union[usertypes.Unset, list], + ) -> Union[list, usertypes.Unset]: values = super().to_py(value) if isinstance(values, usertypes.Unset): return values diff --git a/qutebrowser/config/configutils.py b/qutebrowser/config/configutils.py index fda9552dd..2aaef7a97 100644 --- a/qutebrowser/config/configutils.py +++ b/qutebrowser/config/configutils.py @@ -9,8 +9,8 @@ import collections import itertools import operator from typing import ( - TYPE_CHECKING, Any, Dict, Iterator, List, Optional, Sequence, Set, Union, - MutableMapping) + TYPE_CHECKING, Any, Optional, Union) +from collections.abc import Iterator, Sequence, MutableMapping from qutebrowser.qt.core import QUrl from qutebrowser.qt.gui import QFontDatabase @@ -78,8 +78,8 @@ class Values: self._vmap: MutableMapping[ Values._VmapKeyType, ScopedValue] = collections.OrderedDict() # A map from domain parts to rules that fall under them. - self._domain_map: Dict[ - Optional[str], Set[ScopedValue]] = collections.defaultdict(set) + self._domain_map: dict[ + Optional[str], set[ScopedValue]] = collections.defaultdict(set) for scoped in values: self._add_scoped(scoped) @@ -203,7 +203,7 @@ class Values: return self._get_fallback(fallback) qtutils.ensure_valid(url) - candidates: List[ScopedValue] = [] + candidates: list[ScopedValue] = [] # Urls trailing with '.' are equivalent to non-trailing types. # urlutils strips them, so in order to match we will need to as well. widened_hosts = urlutils.widened_hostnames(url.host().rstrip('.')) diff --git a/qutebrowser/config/qtargs.py b/qutebrowser/config/qtargs.py index 3a648524e..cafc6ff38 100644 --- a/qutebrowser/config/qtargs.py +++ b/qutebrowser/config/qtargs.py @@ -8,7 +8,11 @@ import os import sys import argparse import pathlib -from typing import Any, Dict, Iterator, List, Optional, Sequence, Tuple, Union, Callable +# Using deprecated typing.Callable as a WORKAROUND because +# collections.abc.Callable inside Optional[...]/Union[...] +# is broken on Python 3.9.0 and 3.9.1 +from typing import Any, Optional, Union, Callable +from collections.abc import Iterator, Sequence from qutebrowser.qt import machinery from qutebrowser.qt.core import QLocale @@ -23,7 +27,7 @@ _DISABLE_FEATURES = '--disable-features=' _BLINK_SETTINGS = '--blink-settings=' -def qt_args(namespace: argparse.Namespace) -> List[str]: +def qt_args(namespace: argparse.Namespace) -> list[str]: """Get the Qt QApplication arguments based on an argparse namespace. Args: @@ -77,7 +81,7 @@ def qt_args(namespace: argparse.Namespace) -> List[str]: def _qtwebengine_features( versions: version.WebEngineVersions, special_flags: Sequence[str], -) -> Tuple[Sequence[str], Sequence[str]]: +) -> tuple[Sequence[str], Sequence[str]]: """Get a tuple of --enable-features/--disable-features flags for QtWebEngine. Args: @@ -91,10 +95,10 @@ def _qtwebengine_features( for flag in special_flags: if flag.startswith(_ENABLE_FEATURES): - flag = flag[len(_ENABLE_FEATURES):] + flag = flag.removeprefix(_ENABLE_FEATURES) enabled_features += flag.split(',') elif flag.startswith(_DISABLE_FEATURES): - flag = flag[len(_DISABLE_FEATURES):] + flag = flag.removeprefix(_DISABLE_FEATURES) disabled_features += flag.split(',') elif flag.startswith(_BLINK_SETTINGS): pass @@ -285,7 +289,7 @@ _SettingValueType = Union[ Optional[str], ], ] -_WEBENGINE_SETTINGS: Dict[str, Dict[Any, Optional[_SettingValueType]]] = { +_WEBENGINE_SETTINGS: dict[str, dict[Any, Optional[_SettingValueType]]] = { 'qt.force_software_rendering': { 'software-opengl': None, 'qt-quick': None, diff --git a/qutebrowser/config/stylesheet.py b/qutebrowser/config/stylesheet.py index d9032e2a9..258e26002 100644 --- a/qutebrowser/config/stylesheet.py +++ b/qutebrowser/config/stylesheet.py @@ -5,7 +5,7 @@ """Handling of Qt qss stylesheets.""" import functools -from typing import Optional, FrozenSet +from typing import Optional from qutebrowser.qt.core import pyqtSlot, QObject from qutebrowser.qt.widgets import QWidget @@ -72,7 +72,7 @@ class _StyleSheetObserver(QObject): self._stylesheet = stylesheet if update: - self._options: Optional[FrozenSet[str]] = jinja.template_config_variables( + self._options: Optional[frozenset[str]] = jinja.template_config_variables( self._stylesheet) else: self._options = None diff --git a/qutebrowser/config/websettings.py b/qutebrowser/config/websettings.py index 7824ae258..2b71216bd 100644 --- a/qutebrowser/config/websettings.py +++ b/qutebrowser/config/websettings.py @@ -8,7 +8,8 @@ import re import argparse import functools import dataclasses -from typing import Any, Callable, Dict, Optional, Union +from typing import Any, Optional, Union +from collections.abc import Callable from qutebrowser.qt.core import QUrl, pyqtSlot, qVersion from qutebrowser.qt.gui import QFont @@ -86,10 +87,10 @@ class AbstractSettings: """Abstract base class for settings set via QWeb(Engine)Settings.""" - _ATTRIBUTES: Dict[str, AttributeInfo] = {} - _FONT_SIZES: Dict[str, Any] = {} - _FONT_FAMILIES: Dict[str, Any] = {} - _FONT_TO_QFONT: Dict[Any, QFont.StyleHint] = {} + _ATTRIBUTES: dict[str, AttributeInfo] = {} + _FONT_SIZES: dict[str, Any] = {} + _FONT_FAMILIES: dict[str, Any] = {} + _FONT_TO_QFONT: dict[Any, QFont.StyleHint] = {} def __init__(self, settings: Any) -> None: self._settings = settings diff --git a/qutebrowser/extensions/interceptors.py b/qutebrowser/extensions/interceptors.py index 8aaa9b28c..1032fc6d0 100644 --- a/qutebrowser/extensions/interceptors.py +++ b/qutebrowser/extensions/interceptors.py @@ -6,7 +6,8 @@ import enum import dataclasses -from typing import Callable, List, Optional +from typing import Optional +from collections.abc import Callable from qutebrowser.qt.core import QUrl @@ -89,7 +90,7 @@ class Request: InterceptorType = Callable[[Request], None] -_interceptors: List[InterceptorType] = [] +_interceptors: list[InterceptorType] = [] def register(interceptor: InterceptorType) -> None: diff --git a/qutebrowser/extensions/loader.py b/qutebrowser/extensions/loader.py index ff9974d9d..a6917be35 100644 --- a/qutebrowser/extensions/loader.py +++ b/qutebrowser/extensions/loader.py @@ -10,7 +10,8 @@ import pathlib import importlib import argparse import dataclasses -from typing import Callable, Iterator, List, Optional, Tuple +from typing import Optional +from collections.abc import Iterator, Callable from qutebrowser.qt.core import pyqtSlot @@ -21,7 +22,7 @@ from qutebrowser.misc import objects # ModuleInfo objects for all loaded plugins -_module_infos: List["ModuleInfo"] = [] +_module_infos: list["ModuleInfo"] = [] InitHookType = Callable[['InitContext'], None] ConfigChangedHookType = Callable[[], None] @@ -47,8 +48,8 @@ class ModuleInfo: skip_hooks: bool = False init_hook: Optional[InitHookType] = None - config_changed_hooks: List[ - Tuple[ + config_changed_hooks: list[ + tuple[ Optional[str], ConfigChangedHookType, ] diff --git a/qutebrowser/keyinput/basekeyparser.py b/qutebrowser/keyinput/basekeyparser.py index df6b66f7f..c97570369 100644 --- a/qutebrowser/keyinput/basekeyparser.py +++ b/qutebrowser/keyinput/basekeyparser.py @@ -8,7 +8,8 @@ import string import types import dataclasses import traceback -from typing import Mapping, MutableMapping, Optional, Sequence +from typing import Optional +from collections.abc import Mapping, MutableMapping, Sequence from qutebrowser.qt.core import QObject, pyqtSignal from qutebrowser.qt.gui import QKeySequence, QKeyEvent diff --git a/qutebrowser/keyinput/keyutils.py b/qutebrowser/keyinput/keyutils.py index 18730c74b..8bb63bbe6 100644 --- a/qutebrowser/keyinput/keyutils.py +++ b/qutebrowser/keyinput/keyutils.py @@ -18,7 +18,8 @@ handle what we actually think we do. import itertools import dataclasses -from typing import Iterator, Iterable, List, Mapping, Optional, Union, overload, cast +from typing import Optional, Union, overload, cast +from collections.abc import Iterator, Iterable, Mapping from qutebrowser.qt import machinery from qutebrowser.qt.core import Qt, QEvent @@ -523,7 +524,7 @@ class KeySequence: _MAX_LEN = 4 def __init__(self, *keys: KeyInfo) -> None: - self._sequences: List[QKeySequence] = [] + self._sequences: list[QKeySequence] = [] for sub in utils.chunk(keys, self._MAX_LEN): try: args = [info.to_qt() for info in sub] @@ -546,7 +547,7 @@ class KeySequence: """Iterate over KeyInfo objects.""" # FIXME:mypy Stubs seem to be unaware that iterating a QKeySequence produces # _KeyInfoType - sequences = cast(List[Iterable[_KeyInfoType]], self._sequences) + sequences = cast(list[Iterable[_KeyInfoType]], self._sequences) for combination in itertools.chain.from_iterable(sequences): yield KeyInfo.from_qt(combination) @@ -719,7 +720,7 @@ class KeySequence: mappings: Mapping['KeySequence', 'KeySequence'] ) -> 'KeySequence': """Get a new KeySequence with the given mappings applied.""" - infos: List[KeyInfo] = [] + infos: list[KeyInfo] = [] for info in self: key_seq = KeySequence(info) if key_seq in mappings: diff --git a/qutebrowser/keyinput/macros.py b/qutebrowser/keyinput/macros.py index 69198ecfb..0eb7244d6 100644 --- a/qutebrowser/keyinput/macros.py +++ b/qutebrowser/keyinput/macros.py @@ -5,7 +5,7 @@ """Keyboard macro system.""" -from typing import cast, Dict, List, Optional, Tuple +from typing import cast, Optional from qutebrowser.commands import runners from qutebrowser.api import cmdutils @@ -13,7 +13,7 @@ from qutebrowser.keyinput import modeman from qutebrowser.utils import message, objreg, usertypes -_CommandType = Tuple[str, int] # command, type +_CommandType = tuple[str, int] # command, type macro_recorder = cast('MacroRecorder', None) @@ -32,9 +32,9 @@ class MacroRecorder: """ def __init__(self) -> None: - self._macros: Dict[str, List[_CommandType]] = {} + self._macros: dict[str, list[_CommandType]] = {} self._recording_macro: Optional[str] = None - self._macro_count: Dict[int, int] = {} + self._macro_count: dict[int, int] = {} self._last_register: Optional[str] = None @cmdutils.register(instance='macro-recorder') diff --git a/qutebrowser/keyinput/modeman.py b/qutebrowser/keyinput/modeman.py index f0337ec88..681deeff6 100644 --- a/qutebrowser/keyinput/modeman.py +++ b/qutebrowser/keyinput/modeman.py @@ -6,7 +6,8 @@ import functools import dataclasses -from typing import Mapping, Callable, MutableMapping, Union, Set, cast +from typing import Union, cast +from collections.abc import Mapping, MutableMapping, Callable from qutebrowser.qt import machinery from qutebrowser.qt.core import pyqtSlot, pyqtSignal, Qt, QObject, QEvent @@ -252,7 +253,7 @@ class ModeManager(QObject): self.parsers: ParserDictType = {} self._prev_mode = usertypes.KeyMode.normal self.mode = usertypes.KeyMode.normal - self._releaseevents_to_pass: Set[KeyEvent] = set() + self._releaseevents_to_pass: set[KeyEvent] = set() # Set after __init__ self.hintmanager = cast(hints.HintManager, None) diff --git a/qutebrowser/keyinput/modeparsers.py b/qutebrowser/keyinput/modeparsers.py index 05e560111..b9e5951db 100644 --- a/qutebrowser/keyinput/modeparsers.py +++ b/qutebrowser/keyinput/modeparsers.py @@ -10,7 +10,8 @@ Module attributes: import traceback import enum -from typing import TYPE_CHECKING, Sequence +from typing import TYPE_CHECKING +from collections.abc import Sequence from qutebrowser.qt.core import pyqtSlot, Qt, QObject from qutebrowser.qt.gui import QKeySequence, QKeyEvent diff --git a/qutebrowser/mainwindow/mainwindow.py b/qutebrowser/mainwindow/mainwindow.py index e39ac4f9a..6e6821612 100644 --- a/qutebrowser/mainwindow/mainwindow.py +++ b/qutebrowser/mainwindow/mainwindow.py @@ -8,7 +8,8 @@ import binascii import base64 import itertools import functools -from typing import List, MutableSequence, Optional, Tuple, cast +from typing import Optional, cast +from collections.abc import MutableSequence from qutebrowser.qt import machinery from qutebrowser.qt.core import (pyqtBoundSignal, pyqtSlot, QRect, QPoint, QTimer, Qt, @@ -100,7 +101,7 @@ def get_target_window(): return None -_OverlayInfoType = Tuple[QWidget, pyqtBoundSignal, bool, str] +_OverlayInfoType = tuple[QWidget, pyqtBoundSignal, bool, str] class MainWindow(QWidget): @@ -414,7 +415,7 @@ class MainWindow(QWidget): self._vbox.removeWidget(self.tabbed_browser.widget) self._vbox.removeWidget(self._downloadview) self._vbox.removeWidget(self.status) - widgets: List[QWidget] = [self.tabbed_browser.widget] + widgets: list[QWidget] = [self.tabbed_browser.widget] downloads_position = config.val.downloads.position if downloads_position == 'top': diff --git a/qutebrowser/mainwindow/messageview.py b/qutebrowser/mainwindow/messageview.py index 95bbed724..66d065360 100644 --- a/qutebrowser/mainwindow/messageview.py +++ b/qutebrowser/mainwindow/messageview.py @@ -4,7 +4,8 @@ """Showing messages above the statusbar.""" -from typing import MutableSequence, Optional +from typing import Optional +from collections.abc import MutableSequence from qutebrowser.qt.core import pyqtSlot, pyqtSignal, Qt from qutebrowser.qt.widgets import QWidget, QVBoxLayout, QLabel, QSizePolicy diff --git a/qutebrowser/mainwindow/prompt.py b/qutebrowser/mainwindow/prompt.py index 5d00b429d..2e797970f 100644 --- a/qutebrowser/mainwindow/prompt.py +++ b/qutebrowser/mainwindow/prompt.py @@ -9,7 +9,8 @@ import html import collections import functools import dataclasses -from typing import Deque, MutableSequence, Optional, cast +from typing import Optional, cast +from collections.abc import MutableSequence from qutebrowser.qt.core import (pyqtSlot, pyqtSignal, Qt, QTimer, QDir, QModelIndex, QItemSelectionModel, QObject, QEventLoop, QUrl) @@ -89,7 +90,7 @@ class PromptQueue(QObject): self._question = None self._shutting_down = False self._loops: MutableSequence[qtutils.EventLoop] = [] - self._queue: Deque[usertypes.Question] = collections.deque() + self._queue: collections.deque[usertypes.Question] = collections.deque() message.global_bridge.mode_left.connect(self._on_mode_left) def __repr__(self): diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 47d8dc680..42efe0bbf 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -10,7 +10,8 @@ import weakref import datetime import dataclasses from typing import ( - Any, Deque, List, Mapping, MutableMapping, MutableSequence, Optional, Tuple) + Any, Optional) +from collections.abc import Mapping, MutableMapping, MutableSequence from qutebrowser.qt.widgets import QSizePolicy, QWidget, QApplication from qutebrowser.qt.core import pyqtSignal, pyqtSlot, QTimer, QUrl, QPoint @@ -58,10 +59,10 @@ class TabDeque: size = config.val.tabs.focus_stack_size if size < 0: size = None - self._stack: Deque[weakref.ReferenceType[browsertab.AbstractTab]] = ( + self._stack: collections.deque[weakref.ReferenceType[browsertab.AbstractTab]] = ( collections.deque(maxlen=size)) # Items that have been removed from the primary stack. - self._stack_deleted: List[weakref.ReferenceType[browsertab.AbstractTab]] = [] + self._stack_deleted: list[weakref.ReferenceType[browsertab.AbstractTab]] = [] self._ignore_next = False self._keep_deleted_next = False @@ -235,7 +236,7 @@ class TabbedBrowser(QWidget): self.search_text = None self.search_options: Mapping[str, Any] = {} self._local_marks: MutableMapping[QUrl, MutableMapping[str, QPoint]] = {} - self._global_marks: MutableMapping[str, Tuple[QPoint, QUrl]] = {} + self._global_marks: MutableMapping[str, tuple[QPoint, QUrl]] = {} self.default_window_icon = self._window().windowIcon() self.is_private = private self.tab_deque = TabDeque() diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index 6cc466c93..8d50ac45d 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -7,7 +7,7 @@ import functools import contextlib import dataclasses -from typing import Optional, Dict, Any +from typing import Optional, Any from qutebrowser.qt.core import (pyqtSignal, pyqtSlot, Qt, QSize, QRect, QPoint, QTimer, QUrl) @@ -169,7 +169,7 @@ class TabWidget(QTabWidget): page_title = self.page_title(idx) - fields: Dict[str, Any] = {} + fields: dict[str, Any] = {} fields['id'] = tab.tab_id fields['current_title'] = page_title fields['title_sep'] = ' - ' if page_title else '' diff --git a/qutebrowser/mainwindow/windowundo.py b/qutebrowser/mainwindow/windowundo.py index 46ff3c8c5..5efb77c32 100644 --- a/qutebrowser/mainwindow/windowundo.py +++ b/qutebrowser/mainwindow/windowundo.py @@ -6,7 +6,8 @@ import collections import dataclasses -from typing import MutableSequence, cast, TYPE_CHECKING +from typing import cast, TYPE_CHECKING +from collections.abc import MutableSequence from qutebrowser.qt.core import QObject, QByteArray diff --git a/qutebrowser/misc/backendproblem.py b/qutebrowser/misc/backendproblem.py index 51d3a35c3..9d9aef35c 100644 --- a/qutebrowser/misc/backendproblem.py +++ b/qutebrowser/misc/backendproblem.py @@ -13,7 +13,8 @@ import shutil import os.path import argparse import dataclasses -from typing import Any, Optional, Sequence, Tuple +from typing import Any, Optional +from collections.abc import Sequence from qutebrowser.qt import machinery from qutebrowser.qt.core import Qt @@ -48,7 +49,7 @@ class _Button: default: bool = False -def _other_backend(backend: usertypes.Backend) -> Tuple[usertypes.Backend, str]: +def _other_backend(backend: usertypes.Backend) -> tuple[usertypes.Backend, str]: """Get the other backend enum/setting for a given backend.""" other_backend = { usertypes.Backend.QtWebKit: usertypes.Backend.QtWebEngine, diff --git a/qutebrowser/misc/binparsing.py b/qutebrowser/misc/binparsing.py index 81e2e6dbb..acb4cc5f8 100644 --- a/qutebrowser/misc/binparsing.py +++ b/qutebrowser/misc/binparsing.py @@ -8,7 +8,7 @@ Used by elf.py as well as pakjoy.py. """ import struct -from typing import Any, IO, Tuple +from typing import Any, IO class ParseError(Exception): @@ -16,7 +16,7 @@ class ParseError(Exception): """Raised when the file can't be parsed.""" -def unpack(fmt: str, fobj: IO[bytes]) -> Tuple[Any, ...]: +def unpack(fmt: str, fobj: IO[bytes]) -> tuple[Any, ...]: """Unpack the given struct format from the given file.""" size = struct.calcsize(fmt) data = safe_read(fobj, size) diff --git a/qutebrowser/misc/checkpyver.py b/qutebrowser/misc/checkpyver.py index 596a7803a..e93a124e5 100644 --- a/qutebrowser/misc/checkpyver.py +++ b/qutebrowser/misc/checkpyver.py @@ -28,11 +28,11 @@ except ImportError: # pragma: no cover # to stderr. def check_python_version(): """Check if correct python version is run.""" - if sys.hexversion < 0x03080000: + if sys.hexversion < 0x03090000: # We don't use .format() and print_function here just in case someone # still has < 2.6 installed. version_str = '.'.join(map(str, sys.version_info[:3])) - text = ("At least Python 3.8 is required to run qutebrowser, but " + + text = ("At least Python 3.9 is required to run qutebrowser, but " + "it's running with " + version_str + ".\n") show_errors = '--no-err-windows' not in sys.argv diff --git a/qutebrowser/misc/cmdhistory.py b/qutebrowser/misc/cmdhistory.py index aa2df63e0..e52dd77dd 100644 --- a/qutebrowser/misc/cmdhistory.py +++ b/qutebrowser/misc/cmdhistory.py @@ -4,7 +4,7 @@ """Command history for the status bar.""" -from typing import MutableSequence +from collections.abc import MutableSequence from qutebrowser.qt.core import pyqtSlot, pyqtSignal, QObject diff --git a/qutebrowser/misc/consolewidget.py b/qutebrowser/misc/consolewidget.py index 08f5dc5ff..d74478b4e 100644 --- a/qutebrowser/misc/consolewidget.py +++ b/qutebrowser/misc/consolewidget.py @@ -6,7 +6,8 @@ import sys import code -from typing import MutableSequence, Optional +from typing import Optional +from collections.abc import MutableSequence from qutebrowser.qt.core import pyqtSignal, pyqtSlot, Qt from qutebrowser.qt.widgets import QTextEdit, QWidget, QVBoxLayout, QApplication diff --git a/qutebrowser/misc/crashdialog.py b/qutebrowser/misc/crashdialog.py index ad9ce83a7..5b940a8a3 100644 --- a/qutebrowser/misc/crashdialog.py +++ b/qutebrowser/misc/crashdialog.py @@ -13,7 +13,6 @@ import fnmatch import traceback import datetime import enum -from typing import List, Tuple from qutebrowser.qt.core import pyqtSlot, Qt, QSize from qutebrowser.qt.widgets import (QDialog, QLabel, QTextEdit, QPushButton, @@ -103,7 +102,7 @@ class _CrashDialog(QDialog): super().__init__(parent) # We don't set WA_DeleteOnClose here as on an exception, we'll get # closed anyways, and it only could have unintended side-effects. - self._crash_info: List[Tuple[str, str]] = [] + self._crash_info: list[tuple[str, str]] = [] self._btn_box = None self._paste_text = None self.setWindowTitle("Whoops!") diff --git a/qutebrowser/misc/crashsignal.py b/qutebrowser/misc/crashsignal.py index c33ae1173..7bde12fe5 100644 --- a/qutebrowser/misc/crashsignal.py +++ b/qutebrowser/misc/crashsignal.py @@ -15,7 +15,8 @@ import functools import threading import faulthandler import dataclasses -from typing import TYPE_CHECKING, Optional, MutableMapping, cast, List +from typing import TYPE_CHECKING, Optional, cast +from collections.abc import MutableMapping from qutebrowser.qt.core import (pyqtSlot, qInstallMessageHandler, QObject, QSocketNotifier, QTimer, QUrl) @@ -35,8 +36,8 @@ class ExceptionInfo: """Information stored when there was an exception.""" - pages: List[List[str]] - cmd_history: List[str] + pages: list[list[str]] + cmd_history: list[str] objects: str diff --git a/qutebrowser/misc/debugcachestats.py b/qutebrowser/misc/debugcachestats.py index d3ac9819b..b12995c5c 100644 --- a/qutebrowser/misc/debugcachestats.py +++ b/qutebrowser/misc/debugcachestats.py @@ -9,14 +9,14 @@ dependencies as possible to avoid cyclic dependencies. """ import weakref -import sys -from typing import Any, Callable, Optional, TypeVar, Mapping +from typing import Any, Optional, TypeVar +from collections.abc import MutableMapping, Callable from qutebrowser.utils import log # The callable should be a lru_cache wrapped function -_CACHE_FUNCTIONS: Mapping[str, Any] = weakref.WeakValueDictionary() +_CACHE_FUNCTIONS: MutableMapping[str, Any] = weakref.WeakValueDictionary() _T = TypeVar('_T', bound=Callable[..., Any]) @@ -26,16 +26,8 @@ def register(name: Optional[str] = None) -> Callable[[_T], _T]: """Register a lru_cache wrapped function for debug_cache_stats.""" def wrapper(fn: _T) -> _T: fn_name = fn.__name__ if name is None else name - if sys.version_info < (3, 9): - log.misc.vdebug( # type: ignore[attr-defined] - "debugcachestats not supported on python < 3.9, not adding '%s'", - fn_name, - ) - return fn - - else: - _CACHE_FUNCTIONS[fn_name] = fn - return fn + _CACHE_FUNCTIONS[fn_name] = fn + return fn return wrapper diff --git a/qutebrowser/misc/earlyinit.py b/qutebrowser/misc/earlyinit.py index 06de668b0..60d2c7c09 100644 --- a/qutebrowser/misc/earlyinit.py +++ b/qutebrowser/misc/earlyinit.py @@ -4,7 +4,7 @@ """Things which need to be done really early (e.g. before importing Qt). -At this point we can be sure we have all python 3.8 features available. +At this point we can be sure we have all python 3.9 features available. """ try: @@ -246,10 +246,6 @@ def check_libraries(): package = f'{machinery.INFO.wrapper}.{subpkg}' modules[package] = _missing_str(package) - if sys.version_info < (3, 9): - # Backport required - modules['importlib_resources'] = _missing_str("importlib_resources") - if sys.platform.startswith('darwin'): from qutebrowser.qt.core import QVersionNumber qt_ver = get_qt_version() diff --git a/qutebrowser/misc/editor.py b/qutebrowser/misc/editor.py index 948b4ab9e..9f77fa75e 100644 --- a/qutebrowser/misc/editor.py +++ b/qutebrowser/misc/editor.py @@ -180,7 +180,7 @@ class ExternalEditor(QObject): line: the line number to pass to the editor column: the column number to pass to the editor """ - self._proc = guiprocess.GUIProcess(what='editor', parent=self) + self._proc = guiprocess.GUIProcess(what='editor') self._proc.finished.connect(self._on_proc_closed) self._proc.error.connect(self._on_proc_error) editor = config.val.editor.command diff --git a/qutebrowser/misc/elf.py b/qutebrowser/misc/elf.py index e44d8b573..a012f4c69 100644 --- a/qutebrowser/misc/elf.py +++ b/qutebrowser/misc/elf.py @@ -49,7 +49,7 @@ import re import dataclasses import mmap import pathlib -from typing import IO, ClassVar, Dict, Optional, cast +from typing import IO, ClassVar, Optional, cast from qutebrowser.qt import machinery from qutebrowser.utils import log, version, qtutils @@ -131,7 +131,7 @@ class Header: shnum: int shstrndx: int - _FORMATS: ClassVar[Dict[Bitness, str]] = { + _FORMATS: ClassVar[dict[Bitness, str]] = { Bitness.x64: '<HHIQQQIHHHHHH', Bitness.x32: '<HHIIIIIHHHHHH', } @@ -162,7 +162,7 @@ class SectionHeader: addralign: int entsize: int - _FORMATS: ClassVar[Dict[Bitness, str]] = { + _FORMATS: ClassVar[dict[Bitness, str]] = { Bitness.x64: '<IIQQQQIIQQ', Bitness.x32: '<IIIIIIIIII', } diff --git a/qutebrowser/misc/guiprocess.py b/qutebrowser/misc/guiprocess.py index d20b4ba0f..2e4f33748 100644 --- a/qutebrowser/misc/guiprocess.py +++ b/qutebrowser/misc/guiprocess.py @@ -9,7 +9,8 @@ import locale import shlex import shutil import signal -from typing import Mapping, Sequence, Dict, Optional +from typing import Optional +from collections.abc import Mapping, Sequence from qutebrowser.qt.core import (pyqtSlot, pyqtSignal, QObject, QProcess, QProcessEnvironment, QByteArray, QUrl, Qt) @@ -19,7 +20,7 @@ from qutebrowser.api import cmdutils, apitypes from qutebrowser.completion.models import miscmodels -all_processes: Dict[int, Optional['GUIProcess']] = {} +all_processes: dict[int, Optional['GUIProcess']] = {} last_pid: Optional[int] = None @@ -176,9 +177,10 @@ class GUIProcess(QObject): verbose: bool = False, additional_env: Mapping[str, str] = None, output_messages: bool = False, - parent: QObject = None, ): - super().__init__(parent) + # We do not accept a parent, as GUIProcesses keep track of themselves + # (see all_processes and _post_start() / _on_cleanup_timer()) + super().__init__() self.what = what self.verbose = verbose self._output_messages = output_messages diff --git a/qutebrowser/misc/httpclient.py b/qutebrowser/misc/httpclient.py index a6a6025c3..097fdcd43 100644 --- a/qutebrowser/misc/httpclient.py +++ b/qutebrowser/misc/httpclient.py @@ -6,7 +6,7 @@ import functools import urllib.parse -from typing import MutableMapping +from collections.abc import MutableMapping from qutebrowser.qt.core import pyqtSignal, QObject, QTimer from qutebrowser.qt.network import (QNetworkAccessManager, QNetworkRequest, diff --git a/qutebrowser/misc/keyhintwidget.py b/qutebrowser/misc/keyhintwidget.py index 32867c17a..5662763b8 100644 --- a/qutebrowser/misc/keyhintwidget.py +++ b/qutebrowser/misc/keyhintwidget.py @@ -123,7 +123,7 @@ class KeyHintView(QLabel): ).format( html.escape(prefix), suffix_color, - html.escape(str(seq)[len(prefix):]), + html.escape(str(seq).removeprefix(prefix)), html.escape(cmd) ) text = '<table>{}</table>'.format(text) diff --git a/qutebrowser/misc/lineparser.py b/qutebrowser/misc/lineparser.py index c96109e9e..c253c3ef5 100644 --- a/qutebrowser/misc/lineparser.py +++ b/qutebrowser/misc/lineparser.py @@ -7,7 +7,7 @@ import os import os.path import contextlib -from typing import Sequence +from collections.abc import Sequence from qutebrowser.qt.core import pyqtSlot, pyqtSignal, QObject diff --git a/qutebrowser/misc/nativeeventfilter.py b/qutebrowser/misc/nativeeventfilter.py index 06533bd42..2b2d3f635 100644 --- a/qutebrowser/misc/nativeeventfilter.py +++ b/qutebrowser/misc/nativeeventfilter.py @@ -7,7 +7,7 @@ This entire file is a giant WORKAROUND for https://bugreports.qt.io/browse/QTBUG-114334. """ -from typing import Tuple, Union, cast, Optional +from typing import Union, cast, Optional import enum import ctypes import ctypes.util @@ -138,7 +138,7 @@ class NativeEventFilter(QAbstractNativeEventFilter): def nativeEventFilter( self, evtype: Union[bytes, QByteArray], message: Optional[sip.voidptr] - ) -> Tuple[bool, _PointerRetType]: + ) -> tuple[bool, _PointerRetType]: """Handle XCB events.""" # We're only installed when the platform plugin is xcb assert evtype == b"xcb_generic_event_t", evtype diff --git a/qutebrowser/misc/objects.py b/qutebrowser/misc/objects.py index 1b91c6fdd..4a997ffd2 100644 --- a/qutebrowser/misc/objects.py +++ b/qutebrowser/misc/objects.py @@ -8,7 +8,7 @@ # earlyinit. import argparse -from typing import TYPE_CHECKING, Any, Dict, Set, Union, cast +from typing import TYPE_CHECKING, Any, Union, cast if TYPE_CHECKING: from qutebrowser import app @@ -29,7 +29,7 @@ class NoBackend: backend: Union['usertypes.Backend', NoBackend] = NoBackend() -commands: Dict[str, 'command.Command'] = {} -debug_flags: Set[str] = set() +commands: dict[str, 'command.Command'] = {} +debug_flags: set[str] = set() args = cast(argparse.Namespace, None) qapp = cast('app.Application', None) diff --git a/qutebrowser/misc/pakjoy.py b/qutebrowser/misc/pakjoy.py index b2ebbc4b3..dcf8616f4 100644 --- a/qutebrowser/misc/pakjoy.py +++ b/qutebrowser/misc/pakjoy.py @@ -30,7 +30,8 @@ import shutil import pathlib import dataclasses import contextlib -from typing import ClassVar, IO, Optional, Dict, Tuple, Iterator +from typing import ClassVar, IO, Optional +from collections.abc import Iterator from qutebrowser.config import config from qutebrowser.misc import binparsing, objects @@ -128,7 +129,7 @@ class PakParser: return data - def _read_header(self) -> Dict[int, PakEntry]: + def _read_header(self) -> dict[int, PakEntry]: """Read the header and entry index from the .pak file.""" entries = [] @@ -147,7 +148,7 @@ class PakParser: return {entry.resource_id: entry for entry in entries} - def _find_manifest(self, entries: Dict[int, PakEntry]) -> Tuple[PakEntry, bytes]: + def _find_manifest(self, entries: dict[int, PakEntry]) -> tuple[PakEntry, bytes]: to_check = list(entries.values()) for hangouts_id in HANGOUTS_IDS: if hangouts_id in entries: @@ -175,7 +176,7 @@ def _find_webengine_resources() -> pathlib.Path: qt_data_path = qtutils.library_path(qtutils.LibraryPath.data) if utils.is_mac: # pragma: no cover # I'm not sure how to arrive at this path without hardcoding it - # ourselves. importlib_resources("PyQt6.Qt6") can serve as a + # ourselves. importlib.resources.files("PyQt6.Qt6") can serve as a # replacement for the qtutils bit but it doesn't seem to help find the # actual Resources folder. candidates.append( diff --git a/qutebrowser/misc/quitter.py b/qutebrowser/misc/quitter.py index 9fe743414..62438001f 100644 --- a/qutebrowser/misc/quitter.py +++ b/qutebrowser/misc/quitter.py @@ -15,7 +15,8 @@ import tokenize import functools import warnings import subprocess -from typing import Iterable, Mapping, MutableSequence, Sequence, cast +from typing import cast +from collections.abc import Iterable, Mapping, MutableSequence, Sequence from qutebrowser.qt.core import QObject, pyqtSignal, QTimer try: diff --git a/qutebrowser/misc/savemanager.py b/qutebrowser/misc/savemanager.py index 6017b3d2a..567cba803 100644 --- a/qutebrowser/misc/savemanager.py +++ b/qutebrowser/misc/savemanager.py @@ -6,7 +6,7 @@ import os.path import collections -from typing import MutableMapping +from collections.abc import MutableMapping from qutebrowser.qt.core import pyqtSlot, QObject, QTimer diff --git a/qutebrowser/misc/sessions.py b/qutebrowser/misc/sessions.py index dd63904cd..b487fcd2c 100644 --- a/qutebrowser/misc/sessions.py +++ b/qutebrowser/misc/sessions.py @@ -10,7 +10,8 @@ import itertools import urllib import shutil import pathlib -from typing import Any, Iterable, MutableMapping, MutableSequence, Optional, Union, cast +from typing import Any, Optional, Union, cast +from collections.abc import Iterable, MutableMapping, MutableSequence from qutebrowser.qt.core import Qt, QUrl, QObject, QPoint, QTimer, QDateTime import yaml diff --git a/qutebrowser/misc/sql.py b/qutebrowser/misc/sql.py index b23b862a3..e2140c242 100644 --- a/qutebrowser/misc/sql.py +++ b/qutebrowser/misc/sql.py @@ -9,7 +9,8 @@ import collections import contextlib import dataclasses import types -from typing import Any, Dict, Iterator, List, Mapping, MutableSequence, Optional, Type, Union +from typing import Any, Optional, Union +from collections.abc import Iterator, Mapping, MutableSequence from qutebrowser.qt.core import QObject, pyqtSignal from qutebrowser.qt.sql import QSqlDatabase, QSqlError, QSqlQuery @@ -226,8 +227,8 @@ class Database: """Return a Query instance linked to this Database.""" return Query(self, querystr, forward_only) - def table(self, name: str, fields: List[str], - constraints: Optional[Dict[str, str]] = None, + def table(self, name: str, fields: list[str], + constraints: Optional[dict[str, str]] = None, parent: Optional[QObject] = None) -> 'SqlTable': """Return a SqlTable instance linked to this Database.""" return SqlTable(self, name, fields, constraints, parent) @@ -276,7 +277,7 @@ class Transaction(contextlib.AbstractContextManager): # type: ignore[type-arg] raise_sqlite_error(msg, error) def __exit__(self, - _exc_type: Optional[Type[BaseException]], + _exc_type: Optional[type[BaseException]], exc_val: Optional[BaseException], _exc_tb: Optional[types.TracebackType]) -> None: db = self._database.qt_database() @@ -313,7 +314,7 @@ class Query: ok = self.query.prepare(querystr) self._check_ok('prepare', ok) self.query.setForwardOnly(forward_only) - self._placeholders: List[str] = [] + self._placeholders: list[str] = [] def __iter__(self) -> Iterator[Any]: if not self.query.isActive(): @@ -348,7 +349,7 @@ class Query: if None in values: raise BugError("Missing bound values!") - def _bind_values(self, values: Mapping[str, Any]) -> Dict[str, Any]: + def _bind_values(self, values: Mapping[str, Any]) -> dict[str, Any]: self._placeholders = list(values) for key, val in values.items(): self.query.bindValue(f':{key}', val) @@ -404,7 +405,7 @@ class Query: assert rows != -1 return rows - def bound_values(self) -> Dict[str, Any]: + def bound_values(self) -> dict[str, Any]: return { f":{key}": self.query.boundValue(f":{key}") for key in self._placeholders @@ -426,8 +427,8 @@ class SqlTable(QObject): changed = pyqtSignal() database: Database - def __init__(self, database: Database, name: str, fields: List[str], - constraints: Optional[Dict[str, str]] = None, + def __init__(self, database: Database, name: str, fields: list[str], + constraints: Optional[dict[str, str]] = None, parent: Optional[QObject] = None) -> None: """Wrapper over a table in the SQL database. @@ -442,7 +443,7 @@ class SqlTable(QObject): self.database = database self._create_table(fields, constraints) - def _create_table(self, fields: List[str], constraints: Optional[Dict[str, str]], + def _create_table(self, fields: list[str], constraints: Optional[dict[str, str]], *, force: bool = False) -> None: """Create the table if the database is uninitialized. diff --git a/qutebrowser/misc/throttle.py b/qutebrowser/misc/throttle.py index 43325fb08..78bc7f29b 100644 --- a/qutebrowser/misc/throttle.py +++ b/qutebrowser/misc/throttle.py @@ -6,7 +6,8 @@ import dataclasses import time -from typing import Any, Callable, Mapping, Optional, Sequence +from typing import Any, Optional +from collections.abc import Mapping, Sequence, Callable from qutebrowser.qt.core import QObject diff --git a/qutebrowser/misc/utilcmds.py b/qutebrowser/misc/utilcmds.py index 6689ad074..548c1e54b 100644 --- a/qutebrowser/misc/utilcmds.py +++ b/qutebrowser/misc/utilcmds.py @@ -8,7 +8,6 @@ import functools import os -import sys import traceback from typing import Optional @@ -114,9 +113,7 @@ def debug_all_objects() -> None: @cmdutils.register(debug=True) def debug_cache_stats() -> None: """Print LRU cache stats.""" - if sys.version_info < (3, 9): - raise cmdutils.CommandError('debugcachestats not supported on python < 3.9') - debugcachestats.debug_cache_stats() # type: ignore[unreachable] + debugcachestats.debug_cache_stats() @cmdutils.register(debug=True) diff --git a/qutebrowser/qt/_core_pyqtproperty.py b/qutebrowser/qt/_core_pyqtproperty.py index 02c1cab97..c2b034df3 100644 --- a/qutebrowser/qt/_core_pyqtproperty.py +++ b/qutebrowser/qt/_core_pyqtproperty.py @@ -9,7 +9,7 @@ https://github.com/python-qt-tools/PyQt5-stubs/blob/5.15.6.0/PyQt5-stubs/QtCore. """ # flake8: noqa -# pylint: disable=invalid-name,missing-class-docstring,too-many-arguments,redefined-builtin,unused-argument +# pylint: disable=invalid-name,missing-class-docstring,too-many-arguments,redefined-builtin,unused-argument,deprecated-typing-alias import typing from PyQt6.QtCore import QObject, pyqtSignal @@ -33,7 +33,7 @@ if typing.TYPE_CHECKING: ) class pyqtProperty: - def __init__( + def __init__( # pylint: disable=too-many-positional-arguments self, type: typing.Union[type, str], fget: typing.Optional[typing.Callable[[QObjectT], TPropertyTypeVal]] = None, diff --git a/qutebrowser/qt/machinery.py b/qutebrowser/qt/machinery.py index dcb3b3243..f39fb7d7f 100644 --- a/qutebrowser/qt/machinery.py +++ b/qutebrowser/qt/machinery.py @@ -30,7 +30,7 @@ import argparse import warnings import importlib import dataclasses -from typing import Optional, Dict +from typing import Optional from qutebrowser.utils import log @@ -106,7 +106,7 @@ class SelectionInfo: """Information about outcomes of importing Qt wrappers.""" wrapper: Optional[str] = None - outcomes: Dict[str, str] = dataclasses.field(default_factory=dict) + outcomes: dict[str, str] = dataclasses.field(default_factory=dict) reason: SelectionReason = SelectionReason.unknown def set_module_error(self, name: str, error: Exception) -> None: diff --git a/qutebrowser/utils/debug.py b/qutebrowser/utils/debug.py index 230c965ef..de7f87f1e 100644 --- a/qutebrowser/utils/debug.py +++ b/qutebrowser/utils/debug.py @@ -12,7 +12,8 @@ import functools import datetime import types from typing import ( - Any, Callable, List, Mapping, MutableSequence, Optional, Sequence, Type, Union) + Any, Optional, Union) +from collections.abc import Mapping, MutableSequence, Sequence, Callable from qutebrowser.qt.core import Qt, QEvent, QMetaMethod, QObject, pyqtBoundSignal @@ -21,7 +22,7 @@ from qutebrowser.misc import objects from qutebrowser.qt import sip, machinery -def log_events(klass: Type[QObject]) -> Type[QObject]: +def log_events(klass: type[QObject]) -> type[QObject]: """Class decorator to log Qt events.""" old_event = klass.event @@ -38,7 +39,7 @@ def log_events(klass: Type[QObject]) -> Type[QObject]: return klass -def log_signals(obj: Union[QObject, Type[QObject]]) -> Union[QObject, Type[QObject]]: +def log_signals(obj: Union[QObject, type[QObject]]) -> Union[QObject, type[QObject]]: """Log all signals of an object or class. Can be used as class decorator. @@ -94,7 +95,7 @@ else: def _qenum_key_python( value: _EnumValueType, - klass: Type[_EnumValueType], + klass: type[_EnumValueType], ) -> Optional[str]: """New-style PyQt6: Try getting value from Python enum.""" if isinstance(value, enum.Enum) and value.name: @@ -114,9 +115,9 @@ def _qenum_key_python( def _qenum_key_qt( - base: Type[sip.simplewrapper], + base: type[sip.simplewrapper], value: _EnumValueType, - klass: Type[_EnumValueType], + klass: type[_EnumValueType], ) -> Optional[str]: # On PyQt5, or PyQt6 with int passed: Try to ask Qt's introspection. # However, not every Qt enum value has a staticMetaObject @@ -139,9 +140,9 @@ def _qenum_key_qt( def qenum_key( - base: Type[sip.simplewrapper], + base: type[sip.simplewrapper], value: _EnumValueType, - klass: Type[_EnumValueType] = None, + klass: type[_EnumValueType] = None, ) -> str: """Convert a Qt Enum value to its key as a string. @@ -173,9 +174,9 @@ def qenum_key( return '0x{:04x}'.format(int(value)) # type: ignore[arg-type] -def qflags_key(base: Type[sip.simplewrapper], +def qflags_key(base: type[sip.simplewrapper], value: _EnumValueType, - klass: Type[_EnumValueType] = None) -> str: + klass: type[_EnumValueType] = None) -> str: """Convert a Qt QFlags value to its keys as string. Note: Passing a combined value (such as Qt.AlignmentFlag.AlignCenter) will get the names @@ -325,7 +326,7 @@ class log_time: # noqa: N801,N806 pylint: disable=invalid-name self._started = datetime.datetime.now() def __exit__(self, - _exc_type: Optional[Type[BaseException]], + _exc_type: Optional[type[BaseException]], _exc_val: Optional[BaseException], _exc_tb: Optional[types.TracebackType]) -> None: assert self._started is not None @@ -372,7 +373,7 @@ def get_all_objects(start_obj: QObject = None) -> str: if start_obj is None: start_obj = objects.qapp - pyqt_lines: List[str] = [] + pyqt_lines: list[str] = [] _get_pyqt_objects(pyqt_lines, start_obj) pyqt_lines = [' ' + e for e in pyqt_lines] pyqt_lines.insert(0, 'Qt objects - {} objects:'.format(len(pyqt_lines))) diff --git a/qutebrowser/utils/docutils.py b/qutebrowser/utils/docutils.py index 6cd16730c..c357a2cd4 100644 --- a/qutebrowser/utils/docutils.py +++ b/qutebrowser/utils/docutils.py @@ -10,7 +10,8 @@ import inspect import os.path import collections import enum -from typing import Any, Callable, MutableMapping, Optional, List, Union +from typing import Any, Optional, Union +from collections.abc import MutableMapping, Callable import qutebrowser from qutebrowser.utils import log, utils @@ -81,10 +82,10 @@ class DocstringParser: """ self._state = self.State.short self._cur_arg_name: Optional[str] = None - self._short_desc_parts: List[str] = [] - self._long_desc_parts: List[str] = [] + self._short_desc_parts: list[str] = [] + self._long_desc_parts: list[str] = [] self.arg_descs: MutableMapping[ - str, Union[str, List[str]]] = collections.OrderedDict() + str, Union[str, list[str]]] = collections.OrderedDict() doc = inspect.getdoc(func) handlers = { self.State.short: self._parse_short, diff --git a/qutebrowser/utils/error.py b/qutebrowser/utils/error.py index 010970861..10dad90f7 100644 --- a/qutebrowser/utils/error.py +++ b/qutebrowser/utils/error.py @@ -11,11 +11,11 @@ from qutebrowser.utils import log, utils def _get_name(exc: BaseException) -> str: """Get a suitable exception name as a string.""" - prefixes = ['qutebrowser', 'builtins'] + prefixes = ['qutebrowser.', 'builtins.'] name = utils.qualname(exc.__class__) for prefix in prefixes: if name.startswith(prefix): - name = name[len(prefix) + 1:] + name = name.removeprefix(prefix) break return name diff --git a/qutebrowser/utils/javascript.py b/qutebrowser/utils/javascript.py index 9890be446..66470155a 100644 --- a/qutebrowser/utils/javascript.py +++ b/qutebrowser/utils/javascript.py @@ -4,7 +4,8 @@ """Utilities related to javascript interaction.""" -from typing import Sequence, Union +from typing import Union +from collections.abc import Sequence _InnerJsArgType = Union[None, str, bool, int, float] _JsArgType = Union[_InnerJsArgType, Sequence[_InnerJsArgType]] diff --git a/qutebrowser/utils/jinja.py b/qutebrowser/utils/jinja.py index d7c261942..c12bac5aa 100644 --- a/qutebrowser/utils/jinja.py +++ b/qutebrowser/utils/jinja.py @@ -10,7 +10,8 @@ import posixpath import functools import contextlib import html -from typing import Any, Callable, FrozenSet, Iterator, List, Set, Tuple +from typing import Any +from collections.abc import Iterator, Callable import jinja2 import jinja2.nodes @@ -54,7 +55,7 @@ class Loader(jinja2.BaseLoader): self, _env: jinja2.Environment, template: str - ) -> Tuple[str, str, Callable[[], bool]]: + ) -> tuple[str, str, Callable[[], bool]]: path = os.path.join(self._subdir, template) try: source = resources.read_file(path) @@ -128,10 +129,10 @@ js_environment = jinja2.Environment(loader=Loader('javascript')) @debugcachestats.register() @functools.lru_cache -def template_config_variables(template: str) -> FrozenSet[str]: +def template_config_variables(template: str) -> frozenset[str]: """Return the config variables used in the template.""" - unvisted_nodes: List[jinja2.nodes.Node] = [environment.parse(template)] - result: Set[str] = set() + unvisted_nodes: list[jinja2.nodes.Node] = [environment.parse(template)] + result: set[str] = set() while unvisted_nodes: node = unvisted_nodes.pop() if not isinstance(node, jinja2.nodes.Getattr): @@ -140,7 +141,7 @@ def template_config_variables(template: str) -> FrozenSet[str]: # List of attribute names in reverse order. # For example it's ['ab', 'c', 'd'] for 'conf.d.c.ab'. - attrlist: List[str] = [] + attrlist: list[str] = [] while isinstance(node, jinja2.nodes.Getattr): attrlist.append(node.attr) node = node.node diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py index aa3ea4123..01701b3b5 100644 --- a/qutebrowser/utils/log.py +++ b/qutebrowser/utils/log.py @@ -15,8 +15,9 @@ import warnings import json import inspect import argparse -from typing import (TYPE_CHECKING, Any, Iterator, Mapping, MutableSequence, - Optional, Set, Tuple, Union, TextIO, Literal, cast) +from typing import (TYPE_CHECKING, Any, + Optional, Union, TextIO, Literal, cast) +from collections.abc import Iterator, Mapping, MutableSequence # NOTE: This is a Qt-free zone! All imports related to Qt logging should be done in # qutebrowser.utils.qtlog (see https://github.com/qutebrowser/qutebrowser/issues/7769). @@ -240,7 +241,7 @@ def _init_handlers( force_color: bool, json_logging: bool, ram_capacity: int -) -> Tuple[Optional["logging.StreamHandler[TextIO]"], Optional['RAMHandler']]: +) -> tuple[Optional["logging.StreamHandler[TextIO]"], Optional['RAMHandler']]: """Init log handlers. Args: @@ -294,7 +295,7 @@ def _init_formatters( color: bool, force_color: bool, json_logging: bool, -) -> Tuple[ +) -> tuple[ Union['JSONFormatter', 'ColoredFormatter', None], 'ColoredFormatter', 'HTMLFormatter', @@ -396,7 +397,7 @@ class InvalidLogFilterError(Exception): """Raised when an invalid filter string is passed to LogFilter.parse().""" - def __init__(self, names: Set[str]): + def __init__(self, names: set[str]): invalid = names - set(LOGGER_NAMES) super().__init__("Invalid log category {} - valid categories: {}" .format(', '.join(sorted(invalid)), @@ -417,7 +418,7 @@ class LogFilter(logging.Filter): than debug. """ - def __init__(self, names: Set[str], *, negated: bool = False, + def __init__(self, names: set[str], *, negated: bool = False, only_debug: bool = True) -> None: super().__init__() self.names = names diff --git a/qutebrowser/utils/message.py b/qutebrowser/utils/message.py index 275ed2f3d..8fc8f6fbe 100644 --- a/qutebrowser/utils/message.py +++ b/qutebrowser/utils/message.py @@ -10,7 +10,8 @@ import dataclasses import traceback -from typing import Any, Callable, Iterable, List, Union, Optional +from typing import Any, Union, Optional +from collections.abc import Iterable, Callable from qutebrowser.qt.core import pyqtSignal, pyqtBoundSignal, QObject @@ -239,7 +240,7 @@ class GlobalMessageBridge(QObject): def __init__(self, parent: QObject = None) -> None: super().__init__(parent) self._connected = False - self._cache: List[MessageInfo] = [] + self._cache: list[MessageInfo] = [] def ask(self, question: usertypes.Question, blocking: bool, *, diff --git a/qutebrowser/utils/objreg.py b/qutebrowser/utils/objreg.py index 8a3489d09..c027b3cf6 100644 --- a/qutebrowser/utils/objreg.py +++ b/qutebrowser/utils/objreg.py @@ -7,8 +7,9 @@ import collections import functools -from typing import (TYPE_CHECKING, Any, Callable, MutableMapping, MutableSequence, - Optional, Sequence, Union) +from typing import (TYPE_CHECKING, Any, + Optional, Union) +from collections.abc import MutableMapping, MutableSequence, Sequence, Callable from qutebrowser.qt.core import QObject, QTimer from qutebrowser.qt.widgets import QApplication @@ -240,6 +241,7 @@ def get(name: str, def register(name: str, obj: Any, + *, update: bool = False, scope: str = None, registry: ObjectRegistry = None, diff --git a/qutebrowser/utils/qtlog.py b/qutebrowser/utils/qtlog.py index 78b48ebee..215123f4a 100644 --- a/qutebrowser/utils/qtlog.py +++ b/qutebrowser/utils/qtlog.py @@ -10,7 +10,8 @@ import faulthandler import logging import sys import traceback -from typing import Iterator, Optional +from typing import Optional +from collections.abc import Iterator from qutebrowser.qt import core as qtcore from qutebrowser.utils import log diff --git a/qutebrowser/utils/qtutils.py b/qutebrowser/utils/qtutils.py index c1f05b78d..a027db74a 100644 --- a/qutebrowser/utils/qtutils.py +++ b/qutebrowser/utils/qtutils.py @@ -18,8 +18,9 @@ import enum import pathlib import operator import contextlib -from typing import (Any, TYPE_CHECKING, BinaryIO, IO, Iterator, Literal, - Optional, Union, Tuple, Protocol, cast, overload, TypeVar) +from typing import (Any, TYPE_CHECKING, BinaryIO, IO, Literal, + Optional, Union, Protocol, cast, overload, TypeVar) +from collections.abc import Iterator from qutebrowser.qt import machinery, sip from qutebrowser.qt.core import (qVersion, QEventLoop, QDataStream, QByteArray, @@ -33,7 +34,6 @@ except ImportError: # pragma: no cover if TYPE_CHECKING: from qutebrowser.qt.webkit import QWebHistory from qutebrowser.qt.webenginecore import QWebEngineHistory - from typing_extensions import TypeGuard # added in Python 3.10 from qutebrowser.misc import objects from qutebrowser.utils import usertypes, utils @@ -529,9 +529,11 @@ class EventLoop(QEventLoop): return status -def _get_color_percentage(x1: int, y1: int, z1: int, a1: int, - x2: int, y2: int, z2: int, a2: int, - percent: int) -> Tuple[int, int, int, int]: +def _get_color_percentage( # pylint: disable=too-many-positional-arguments + x1: int, y1: int, z1: int, a1: int, + x2: int, y2: int, z2: int, a2: int, + percent: int +) -> tuple[int, int, int, int]: """Get a color which is percent% interpolated between start and end. Args: diff --git a/qutebrowser/utils/resources.py b/qutebrowser/utils/resources.py index a97a2e994..35fd62f75 100644 --- a/qutebrowser/utils/resources.py +++ b/qutebrowser/utils/resources.py @@ -9,24 +9,19 @@ import sys import contextlib import posixpath import pathlib -from typing import Iterator, Iterable, Union, Dict +import importlib.resources +from typing import Union +from collections.abc import Iterator, Iterable - -# We cannot use the stdlib version on 3.8 because we need the files() API. if sys.version_info >= (3, 11): # pragma: no cover # https://github.com/python/cpython/issues/90276 - import importlib.resources as importlib_resources from importlib.resources.abc import Traversable -elif sys.version_info >= (3, 9): - import importlib.resources as importlib_resources +else: from importlib.abc import Traversable -else: # pragma: no cover - import importlib_resources - from importlib_resources.abc import Traversable import qutebrowser -_cache: Dict[str, str] = {} -_bin_cache: Dict[str, bytes] = {} +_cache: dict[str, str] = {} +_bin_cache: dict[str, bytes] = {} _ResourceType = Union[Traversable, pathlib.Path] @@ -37,7 +32,7 @@ def _path(filename: str) -> _ResourceType: assert not posixpath.isabs(filename), filename assert os.path.pardir not in filename.split(posixpath.sep), filename - return importlib_resources.files(qutebrowser) / filename + return importlib.resources.files(qutebrowser) / filename @contextlib.contextmanager def _keyerror_workaround() -> Iterator[None]: @@ -46,7 +41,7 @@ def _keyerror_workaround() -> Iterator[None]: WORKAROUND for zipfile.Path resources raising KeyError when a file was notfound: https://bugs.python.org/issue43063 - Only needed for Python 3.8 and 3.9. + Only needed for Python 3.9. """ try: yield @@ -71,7 +66,7 @@ def _glob( assert isinstance(glob_path, pathlib.Path) for full_path in glob_path.glob(f'*{ext}'): # . is contained in ext yield full_path.relative_to(resource_path).as_posix() - else: # zipfile.Path or other importlib_resources.abc.Traversable + else: # zipfile.Path or other importlib.resources.abc.Traversable assert glob_path.is_dir(), glob_path for subpath in glob_path.iterdir(): if subpath.name.endswith(ext): diff --git a/qutebrowser/utils/standarddir.py b/qutebrowser/utils/standarddir.py index 1eb296e50..b82845a96 100644 --- a/qutebrowser/utils/standarddir.py +++ b/qutebrowser/utils/standarddir.py @@ -11,7 +11,8 @@ import contextlib import enum import argparse import tempfile -from typing import Iterator, Optional, Dict +from typing import Optional +from collections.abc import Iterator from qutebrowser.qt.core import QStandardPaths from qutebrowser.qt.widgets import QApplication @@ -19,7 +20,7 @@ from qutebrowser.qt.widgets import QApplication from qutebrowser.utils import log, debug, utils, version, qtutils # The cached locations -_locations: Dict["_Location", str] = {} +_locations: dict["_Location", str] = {} class _Location(enum.Enum): diff --git a/qutebrowser/utils/urlmatch.py b/qutebrowser/utils/urlmatch.py index 55b0037dc..1a558f307 100644 --- a/qutebrowser/utils/urlmatch.py +++ b/qutebrowser/utils/urlmatch.py @@ -17,7 +17,7 @@ https://chromium.googlesource.com/chromium/src/+/6f4a6681eae01c2036336c18b06303e import ipaddress import fnmatch import urllib.parse -from typing import Any, Optional, Tuple +from typing import Any, Optional from qutebrowser.qt.core import QUrl @@ -89,7 +89,7 @@ class UrlPattern: self._init_path(parsed) self._init_port(parsed) - def _to_tuple(self) -> Tuple[ + def _to_tuple(self) -> tuple[ bool, # _match_all bool, # _match_subdomains Optional[str], # _scheme @@ -128,7 +128,7 @@ class UrlPattern: # FIXME This doesn't actually strip the hostname correctly. if (pattern.startswith('file://') and not pattern.startswith('file:///')): - pattern = 'file:///' + pattern[len("file://"):] + pattern = 'file:///' + pattern.removeprefix("file://") return pattern diff --git a/qutebrowser/utils/urlutils.py b/qutebrowser/utils/urlutils.py index 6f0e910cf..839fdbe84 100644 --- a/qutebrowser/utils/urlutils.py +++ b/qutebrowser/utils/urlutils.py @@ -11,7 +11,8 @@ import ipaddress import posixpath import urllib.parse import mimetypes -from typing import Optional, Tuple, Union, Iterable, cast +from typing import Optional, Union, cast +from collections.abc import Iterable from qutebrowser.qt import machinery from qutebrowser.qt.core import QUrl, QUrlQuery @@ -111,7 +112,7 @@ class InvalidUrlError(Error): super().__init__(self.msg) -def _parse_search_term(s: str) -> Tuple[Optional[str], Optional[str]]: +def _parse_search_term(s: str) -> tuple[Optional[str], Optional[str]]: """Get a search engine name and search term from a string. Args: @@ -464,7 +465,7 @@ def filename_from_url(url: QUrl, fallback: str = None) -> Optional[str]: return fallback -HostTupleType = Tuple[str, str, int] +HostTupleType = tuple[str, str, int] def host_tuple(url: QUrl) -> HostTupleType: @@ -553,8 +554,8 @@ def same_domain(url1: QUrl, url2: QUrl) -> bool: if suffix1 != suffix2: return False - domain1 = url1.host()[:-len(suffix1)].split('.')[-1] - domain2 = url2.host()[:-len(suffix2)].split('.')[-1] + domain1 = url1.host().removesuffix(suffix1).split('.')[-1] + domain2 = url2.host().removesuffix(suffix2).split('.')[-1] return domain1 == domain2 @@ -668,7 +669,7 @@ def parse_javascript_url(url: QUrl) -> str: urlstr = url.toString(FormatOption.ENCODED) urlstr = urllib.parse.unquote(urlstr) - code = urlstr[len('javascript:'):] + code = urlstr.removeprefix('javascript:') if not code: raise Error("Resulted in empty JavaScript code") diff --git a/qutebrowser/utils/usertypes.py b/qutebrowser/utils/usertypes.py index d61d4aba7..c8e92bf17 100644 --- a/qutebrowser/utils/usertypes.py +++ b/qutebrowser/utils/usertypes.py @@ -10,7 +10,8 @@ import enum import time import dataclasses import logging -from typing import Optional, Sequence, TypeVar, Union +from typing import Optional, TypeVar, Union +from collections.abc import Sequence from qutebrowser.qt.core import pyqtSignal, pyqtSlot, QObject, QTimer from qutebrowser.qt.core import QUrl diff --git a/qutebrowser/utils/utils.py b/qutebrowser/utils/utils.py index cf340e444..9c506471d 100644 --- a/qutebrowser/utils/utils.py +++ b/qutebrowser/utils/utils.py @@ -19,9 +19,10 @@ import contextlib import shlex import sysconfig import mimetypes -from typing import (Any, Callable, IO, Iterator, - Optional, Sequence, Tuple, List, Type, Union, +from typing import (Any, IO, + Optional, Union, TypeVar, Protocol) +from collections.abc import Iterator, Sequence, Callable from qutebrowser.qt.core import QUrl, QVersionNumber, QRect, QPoint from qutebrowser.qt.gui import QClipboard, QDesktopServices @@ -407,7 +408,7 @@ def qualname(obj: Any) -> str: return repr(obj) -_ExceptionType = Union[Type[BaseException], Tuple[Type[BaseException]]] +_ExceptionType = Union[type[BaseException], tuple[type[BaseException]]] def raises(exc: _ExceptionType, func: Callable[..., Any], *args: Any) -> bool: @@ -776,14 +777,38 @@ def mimetype_extension(mimetype: str) -> Optional[str]: This mostly delegates to Python's mimetypes.guess_extension(), but backports some changes (via a simple override dict) which are missing from earlier Python versions. - Most likely, this can be dropped once the minimum Python version is raised to 3.10. """ - overrides = { - # Added in 3.10 - "application/x-hdf5": ".h5", - # Added around 3.8 - "application/manifest+json": ".webmanifest", - } + overrides = {} + if sys.version_info[:2] < (3, 13): + overrides.update({ + "text/rtf": ".rtf", + "text/markdown": ".md", + "text/x-rst": ".rst", + }) + if sys.version_info[:2] < (3, 12): + overrides.update({ + "text/javascript": ".js", + }) + if sys.version_info[:2] < (3, 11): + overrides.update({ + "application/n-quads": ".nq", + "application/n-triples": ".nt", + "application/trig": ".trig", + "image/avif": ".avif", + "image/webp": ".webp", + "text/n3": ".n3", + "text/vtt": ".vtt", + }) + if sys.version_info[:2] < (3, 10): + overrides.update({ + "application/x-hdf5": ".h5", + "audio/3gpp": ".3gp", + "audio/3gpp2": ".3g2", + "audio/aac": ".aac", + "audio/opus": ".opus", + "image/heic": ".heic", + "image/heif": ".heif", + }) if mimetype in overrides: return overrides[mimetype] return mimetypes.guess_extension(mimetype, strict=False) @@ -846,7 +871,7 @@ def parse_point(s: str) -> QPoint: raise ValueError(e) -def match_globs(patterns: List[str], value: str) -> Optional[str]: +def match_globs(patterns: list[str], value: str) -> Optional[str]: """Match a list of glob-like patterns against a value. Return: diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index 298eba9ca..ec167377c 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -19,8 +19,9 @@ import getpass import functools import dataclasses import importlib.metadata -from typing import (Mapping, Optional, Sequence, Tuple, ClassVar, Dict, Any, +from typing import (Optional, ClassVar, Any, TYPE_CHECKING) +from collections.abc import Mapping, Sequence from qutebrowser.qt import machinery from qutebrowser.qt.core import PYQT_VERSION_STR @@ -105,7 +106,7 @@ class Distribution(enum.Enum): solus = enum.auto() -def _parse_os_release() -> Optional[Dict[str, str]]: +def _parse_os_release() -> Optional[dict[str, str]]: """Parse an /etc/os-release file.""" filename = os.environ.get('QUTE_FAKE_OS_RELEASE', '/etc/os-release') info = {} @@ -250,7 +251,7 @@ def _git_str_subprocess(gitpath: str) -> Optional[str]: return None -def _release_info() -> Sequence[Tuple[str, str]]: +def _release_info() -> Sequence[tuple[str, str]]: """Try to gather distribution release information. Return: @@ -379,7 +380,7 @@ class ModuleInfo: return text -def _create_module_info() -> Dict[str, ModuleInfo]: +def _create_module_info() -> dict[str, ModuleInfo]: packages = [ ('colorama', ['VERSION', '__version__']), ('jinja2', ['__version__']), @@ -538,20 +539,22 @@ class WebEngineVersions: chromium_security: Optional[str] = None chromium_major: Optional[int] = dataclasses.field(init=False) - _BASES: ClassVar[Dict[int, str]] = { - 83: '83.0.4103.122', # ~2020-06-24 - 87: '87.0.4280.144', # ~2020-12-02 - 90: '90.0.4430.228', # 2021-06-22 - 94: '94.0.4606.126', # 2021-11-17 - 102: '102.0.5005.177', # ~2022-05-24 + # Dates based on https://chromium.googlesource.com/chromium/src/+refs + _BASES: ClassVar[dict[int, str]] = { + 83: '83.0.4103.122', # 2020-06-27, Qt 5.15.2 + 87: '87.0.4280.144', # 2021-01-08, Qt 5.15 + 90: '90.0.4430.228', # 2021-06-22, Qt 6.2 + 94: '94.0.4606.126', # 2021-11-17, Qt 6.3 + 102: '102.0.5005.177', # 2022-09-01, Qt 6.4 # (.220 claimed by code, .181 claimed by CHROMIUM_VERSION) - 108: '108.0.5359.220', # ~2022-12-23 - 112: '112.0.5615.213', # ~2023-04-18 - 118: '118.0.5993.220', # ~2023-10-24 - 122: '122.0.6261.171', # ~2024-??-?? + 108: '108.0.5359.220', # 2023-01-27, Qt 6.5 + 112: '112.0.5615.213', # 2023-05-24, Qt 6.6 + 118: '118.0.5993.220', # 2024-01-25, Qt 6.7 + 122: '122.0.6261.171', # 2024-04-15, Qt 6.8 } - _CHROMIUM_VERSIONS: ClassVar[Dict[utils.VersionNumber, Tuple[str, Optional[str]]]] = { + # Dates based on https://chromereleases.googleblog.com/ + _CHROMIUM_VERSIONS: ClassVar[dict[utils.VersionNumber, tuple[str, Optional[str]]]] = { # ====== UNSUPPORTED ===== # Qt 5.12: Chromium 69 @@ -707,7 +710,7 @@ class WebEngineVersions: def _infer_chromium_version( cls, pyqt_webengine_version: utils.VersionNumber, - ) -> Tuple[Optional[str], Optional[str]]: + ) -> tuple[Optional[str], Optional[str]]: """Infer the Chromium version based on the PyQtWebEngine version. Returns: @@ -1017,7 +1020,7 @@ class OpenGLInfo: version_str: Optional[str] = None # The parsed version as a (major, minor) tuple of ints - version: Optional[Tuple[int, ...]] = None + version: Optional[tuple[int, ...]] = None # The vendor specific information following the version number vendor_specific: Optional[str] = None diff --git a/requirements.txt b/requirements.txt index 4102f1af6..4afe7fb9c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,12 +2,10 @@ adblock==0.6.0 colorama==0.4.6 -importlib_resources==6.4.5 ; python_version=="3.8.*" Jinja2==3.1.4 -MarkupSafe==2.1.5 +MarkupSafe==3.0.2 Pygments==2.18.0 PyYAML==6.0.2 -zipp==3.20.2 # Unpinned due to recompile_requirements.py limitations pyobjc-core ; sys_platform=="darwin" pyobjc-framework-Cocoa ; sys_platform=="darwin" diff --git a/scripts/dev/build_release.py b/scripts/dev/build_release.py index ed653316b..c0482f414 100755 --- a/scripts/dev/build_release.py +++ b/scripts/dev/build_release.py @@ -20,7 +20,8 @@ import platform import collections import dataclasses import re -from typing import Iterable, List, Optional +from typing import Optional +from collections.abc import Iterable try: import winreg @@ -125,7 +126,7 @@ def _smoke_test_run( return subprocess.run(argv, check=True, capture_output=True) -def smoke_test(executable: pathlib.Path, debug: bool, qt5: bool) -> None: +def smoke_test(executable: pathlib.Path, debug: bool) -> None: """Try starting the given qutebrowser executable.""" stdout_whitelist = [] stderr_whitelist = [ @@ -164,18 +165,15 @@ def smoke_test(executable: pathlib.Path, debug: bool, qt5: bool) -> None: (r'\[.*:ERROR:command_buffer_proxy_impl.cc\([0-9]*\)\] ' r'ContextResult::kTransientFailure: Failed to send ' r'.*CreateCommandBuffer\.'), + # FIXME:qt6 Qt 6.3 on macOS + r'[0-9:]* WARNING: Incompatible version of OpenSSL', + r'[0-9:]* WARNING: Qt WebEngine resources not found at .*', + (r'[0-9:]* WARNING: Installed Qt WebEngine locales directory not found at ' + r'location /qtwebengine_locales\. Trying application directory\.\.\.'), + # Qt 6.7, only seen on macos for some reason + (r'.*Path override failed for key base::DIR_APP_DICTIONARIES ' + r"and path '.*/qtwebengine_dictionaries'"), ]) - if not qt5: - stderr_whitelist.extend([ - # FIXME:qt6 Qt 6.3 on macOS - r'[0-9:]* WARNING: Incompatible version of OpenSSL', - r'[0-9:]* WARNING: Qt WebEngine resources not found at .*', - (r'[0-9:]* WARNING: Installed Qt WebEngine locales directory not found at ' - r'location /qtwebengine_locales\. Trying application directory\.\.\.'), - # Qt 6.7, only seen on macos for some reason - (r'.*Path override failed for key base::DIR_APP_DICTIONARIES ' - r"and path '.*/qtwebengine_dictionaries'"), - ]) elif IS_WINDOWS: stderr_whitelist.extend([ # Windows N: @@ -267,10 +265,9 @@ def _mac_bin_path(base: pathlib.Path) -> pathlib.Path: def build_mac( *, gh_token: Optional[str], - qt5: bool, skip_packaging: bool, debug: bool, -) -> List[Artifact]: +) -> list[Artifact]: """Build macOS .dmg/.app.""" utils.print_title("Cleaning up...") for f in ['wc.dmg', 'template.dmg']: @@ -282,18 +279,18 @@ def build_mac( shutil.rmtree(d, ignore_errors=True) utils.print_title("Updating 3rdparty content") - update_3rdparty.run(ace=False, pdfjs=True, legacy_pdfjs=qt5, fancy_dmg=False, + update_3rdparty.run(ace=False, pdfjs=True, legacy_pdfjs=False, fancy_dmg=False, gh_token=gh_token) utils.print_title("Building .app via pyinstaller") - call_tox(f'pyinstaller{"-qt5" if qt5 else ""}', '-r', debug=debug) + call_tox('pyinstaller', '-r', debug=debug) utils.print_title("Verifying .app") verify_mac_app() dist_path = pathlib.Path("dist") utils.print_title("Running pre-dmg smoke test") - smoke_test(_mac_bin_path(dist_path), debug=debug, qt5=qt5) + smoke_test(_mac_bin_path(dist_path), debug=debug) if skip_packaging: return [] @@ -304,7 +301,6 @@ def build_mac( arch = platform.machine() suffix = "-debug" if debug else "" - suffix += "-qt5" if qt5 else "" suffix += f"-{arch}" dmg_path = dist_path / f'qutebrowser-{qutebrowser.__version__}{suffix}.dmg' pathlib.Path('qutebrowser.dmg').rename(dmg_path) @@ -317,7 +313,7 @@ def build_mac( subprocess.run(['hdiutil', 'attach', dmg_path, '-mountpoint', tmp_path], check=True) try: - smoke_test(_mac_bin_path(tmp_path), debug=debug, qt5=qt5) + smoke_test(_mac_bin_path(tmp_path), debug=debug) finally: print("Waiting 10s for dmg to be detachable...") time.sleep(10) @@ -355,10 +351,9 @@ def _get_windows_python_path() -> pathlib.Path: def _build_windows_single( *, - qt5: bool, skip_packaging: bool, debug: bool, -) -> List[Artifact]: +) -> list[Artifact]: """Build on Windows for a single build type.""" utils.print_title("Running pyinstaller") dist_path = pathlib.Path("dist") @@ -367,9 +362,7 @@ def _build_windows_single( _maybe_remove(out_path) python = _get_windows_python_path() - # FIXME:qt6 does this regress 391623d5ec983ecfc4512c7305c4b7a293ac3872? - suffix = "-qt5" if qt5 else "" - call_tox(f'pyinstaller{suffix}', '-r', python=python, debug=debug) + call_tox('pyinstaller', '-r', python=python, debug=debug) out_pyinstaller = dist_path / "qutebrowser" shutil.move(out_pyinstaller, out_path) @@ -379,7 +372,7 @@ def _build_windows_single( verify_windows_exe(exe_path) utils.print_title("Running smoke test") - smoke_test(exe_path, debug=debug, qt5=qt5) + smoke_test(exe_path, debug=debug) if skip_packaging: return [] @@ -388,19 +381,17 @@ def _build_windows_single( return _package_windows_single( out_path=out_path, debug=debug, - qt5=qt5, ) def build_windows( *, gh_token: str, skip_packaging: bool, - qt5: bool, debug: bool, -) -> List[Artifact]: +) -> list[Artifact]: """Build windows executables/setups.""" utils.print_title("Updating 3rdparty content") - update_3rdparty.run(nsis=True, ace=False, pdfjs=True, legacy_pdfjs=qt5, + update_3rdparty.run(nsis=True, ace=False, pdfjs=True, legacy_pdfjs=False, fancy_dmg=False, gh_token=gh_token) utils.print_title("Building Windows binaries") @@ -412,7 +403,6 @@ def build_windows( artifacts = _build_windows_single( skip_packaging=skip_packaging, debug=debug, - qt5=qt5, ) return artifacts @@ -421,8 +411,7 @@ def _package_windows_single( *, out_path: pathlib.Path, debug: bool, - qt5: bool, -) -> List[Artifact]: +) -> list[Artifact]: """Build the given installer/zip for windows.""" artifacts = [] @@ -430,7 +419,6 @@ def _package_windows_single( utils.print_subtitle("Building installer...") subprocess.run(['makensis.exe', f'/DVERSION={qutebrowser.__version__}', - f'/DQT5={qt5}', 'misc/nsis/qutebrowser.nsi'], check=True) name_parts = [ @@ -439,8 +427,6 @@ def _package_windows_single( ] if debug: name_parts.append('debug') - if qt5: - name_parts.append('qt5') name_parts.append('amd64') # FIXME:qt6 temporary until new installer name = '-'.join(name_parts) + '.exe' @@ -460,8 +446,6 @@ def _package_windows_single( ] if debug: zip_name_parts.append('debug') - if qt5: - zip_name_parts.append('qt5') zip_name = '-'.join(zip_name_parts) + '.zip' zip_path = dist_path / zip_name @@ -475,7 +459,7 @@ def _package_windows_single( return artifacts -def build_sdist() -> List[Artifact]: +def build_sdist() -> list[Artifact]: """Build an sdist and list the contents.""" utils.print_title("Building sdist") @@ -565,7 +549,7 @@ def read_github_token( def github_upload( - artifacts: List[Artifact], + artifacts: list[Artifact], tag: str, gh_token: str, experimental: bool, @@ -643,7 +627,7 @@ def github_upload( break -def pypi_upload(artifacts: List[Artifact], experimental: bool) -> None: +def pypi_upload(artifacts: list[Artifact], experimental: bool) -> None: """Upload the given artifacts to PyPI using twine.""" # https://blog.pypi.org/posts/2023-05-23-removing-pgp/ artifacts = [a for a in artifacts if a.mimetype != 'application/pgp-signature'] @@ -655,13 +639,13 @@ def pypi_upload(artifacts: List[Artifact], experimental: bool) -> None: run_twine('upload', artifacts) -def twine_check(artifacts: List[Artifact]) -> None: +def twine_check(artifacts: list[Artifact]) -> None: """Check packages using 'twine check'.""" utils.print_title("Running twine check...") run_twine('check', artifacts, '--strict') -def run_twine(command: str, artifacts: List[Artifact], *args: str) -> None: +def run_twine(command: str, artifacts: list[Artifact], *args: str) -> None: paths = [a.path for a in artifacts] subprocess.run([sys.executable, '-m', 'twine', command, *args, *paths], check=True) @@ -680,8 +664,6 @@ def main() -> None: help="Skip Windows installer/zip generation or macOS DMG.") parser.add_argument('--debug', action='store_true', required=False, help="Build a debug build.") - parser.add_argument('--qt5', action='store_true', required=False, - help="Build against PyQt5") parser.add_argument('--experimental', action='store_true', required=False, default=os.environ.get("GITHUB_REPOSITORY") == "qutebrowser/experiments", help="Upload to experiments repo and test PyPI. Set automatically if on qutebrowser/experiments CI.") @@ -712,14 +694,12 @@ def main() -> None: artifacts = build_windows( gh_token=gh_token, skip_packaging=args.skip_packaging, - qt5=args.qt5, debug=args.debug, ) elif IS_MACOS: artifacts = build_mac( gh_token=gh_token, skip_packaging=args.skip_packaging, - qt5=args.qt5, debug=args.debug, ) else: diff --git a/scripts/dev/changelog_urls.json b/scripts/dev/changelog_urls.json index b614a91d8..215696bf9 100644 --- a/scripts/dev/changelog_urls.json +++ b/scripts/dev/changelog_urls.json @@ -71,7 +71,6 @@ "babel": "https://github.com/python-babel/babel/blob/master/CHANGES.rst", "alabaster": "https://alabaster.readthedocs.io/en/latest/changelog.html", "imagesize": "https://github.com/shibukawa/imagesize_py/commits/master", - "pytz": "https://mm.icann.org/pipermail/tz-announce/", "sphinxcontrib-applehelp": "https://www.sphinx-doc.org/en/master/changes.html", "sphinxcontrib-devhelp": "https://www.sphinx-doc.org/en/master/changes.html", "sphinxcontrib-htmlhelp": "https://www.sphinx-doc.org/en/master/changes.html", diff --git a/scripts/dev/check_coverage.py b/scripts/dev/check_coverage.py index e1d0d8642..6de04703f 100644 --- a/scripts/dev/check_coverage.py +++ b/scripts/dev/check_coverage.py @@ -242,7 +242,7 @@ def _get_filename(filename): os.path.join(os.path.dirname(__file__), '..', '..')) common_path = os.path.commonprefix([basedir, filename]) if common_path: - filename = filename[len(common_path):].lstrip('/') + filename = filename.removeprefix(common_path).lstrip('/') return filename diff --git a/scripts/dev/misc_checks.py b/scripts/dev/misc_checks.py index 4b838b5fe..5ffeb6019 100644 --- a/scripts/dev/misc_checks.py +++ b/scripts/dev/misc_checks.py @@ -15,7 +15,8 @@ import subprocess import tokenize import traceback import pathlib -from typing import List, Iterator, Optional, Tuple +from typing import Optional +from collections.abc import Iterator REPO_ROOT = pathlib.Path(__file__).resolve().parents[2] sys.path.insert(0, str(REPO_ROOT)) @@ -30,7 +31,7 @@ BINARY_EXTS = {'.png', '.icns', '.ico', '.bmp', '.gz', '.bin', '.pdf', def _get_files( *, verbose: bool, - ignored: List[pathlib.Path] = None + ignored: list[pathlib.Path] = None ) -> Iterator[pathlib.Path]: """Iterate over all files and yield filenames.""" filenames = subprocess.run( @@ -142,8 +143,8 @@ def _check_spelling_file(path, fobj, patterns): def _check_spelling_all( args: argparse.Namespace, - ignored: List[pathlib.Path], - patterns: List[Tuple[re.Pattern, str]], + ignored: list[pathlib.Path], + patterns: list[tuple[re.Pattern, str]], ) -> Optional[bool]: try: ok = True diff --git a/scripts/dev/pylint_checkers/qute_pylint/config.py b/scripts/dev/pylint_checkers/qute_pylint/config.py index be5bae082..6effc8836 100644 --- a/scripts/dev/pylint_checkers/qute_pylint/config.py +++ b/scripts/dev/pylint_checkers/qute_pylint/config.py @@ -50,7 +50,7 @@ class ConfigChecker(checkers.BaseChecker): node_str = node.as_string() prefix = 'config.val.' if node_str.startswith(prefix): - self._check_config(node, node_str[len(prefix):]) + self._check_config(node, node_str.removeprefix(prefix)) def _check_config(self, node, name): """Check that we're accessing proper config options.""" diff --git a/scripts/dev/recompile_requirements.py b/scripts/dev/recompile_requirements.py index 838e75931..f844e82a8 100644 --- a/scripts/dev/recompile_requirements.py +++ b/scripts/dev/recompile_requirements.py @@ -114,7 +114,7 @@ def get_all_names(): """Get all requirement names based on filenames.""" for filename in glob.glob(os.path.join(REQ_DIR, 'requirements-*.txt-raw')): basename = os.path.basename(filename) - yield basename[len('requirements-'):-len('.txt-raw')] + yield basename.removeprefix('requirements-').removesuffix('.txt-raw') def run_pip(venv_dir, *args, quiet=False, **kwargs): @@ -161,8 +161,11 @@ def parse_args(): def git_diff(*args): """Run a git diff command.""" - command = (['git', '--no-pager', 'diff'] + list(args) + [ - '--', 'requirements.txt', 'misc/requirements/requirements-*.txt']) + command = ( + ["git", "--no-pager", "-c", "diff.mnemonicPrefix=false", "diff"] + + list(args) + + ["--", "requirements.txt", "misc/requirements/requirements-*.txt"] + ) proc = subprocess.run(command, stdout=subprocess.PIPE, encoding='utf-8', @@ -231,7 +234,7 @@ def extract_requirement_name(path: pathlib.Path) -> str: prefix = "requirements-" assert path.suffix == ".txt", path assert path.stem.startswith(prefix), path - return path.stem[len(prefix):] + return path.stem.removeprefix(prefix) def parse_versioned_line(line): @@ -274,11 +277,11 @@ def _get_changes(diff): continue elif line.startswith('--- '): prefix = '--- a/' - current_path = pathlib.Path(line[len(prefix):]) + current_path = pathlib.Path(line.removeprefix(prefix)) continue elif line.startswith('+++ '): prefix = '+++ b/' - new_path = pathlib.Path(line[len(prefix):]) + new_path = pathlib.Path(line.removeprefix(prefix)) assert current_path == new_path, (current_path, new_path) continue elif not line.strip(): diff --git a/scripts/dev/run_pylint_on_tests.py b/scripts/dev/run_pylint_on_tests.py index 580ef988f..d55caaf36 100644 --- a/scripts/dev/run_pylint_on_tests.py +++ b/scripts/dev/run_pylint_on_tests.py @@ -40,6 +40,7 @@ def main(): 'redefined-outer-name', 'unused-argument', 'too-many-arguments', + 'too-many-positional-arguments', # things which are okay in tests 'missing-docstring', 'protected-access', diff --git a/scripts/dev/update_3rdparty.py b/scripts/dev/update_3rdparty.py index 4d8a5e562..7ad27c915 100755 --- a/scripts/dev/update_3rdparty.py +++ b/scripts/dev/update_3rdparty.py @@ -104,8 +104,7 @@ def update_pdfjs(target_version=None, legacy=False, gh_token=None): else: # We need target_version as x.y.z, without the 'v' prefix, though the # user might give it on the command line - if target_version.startswith('v'): - target_version = target_version[1:] + target_version = target_version.removeprefix('v') # version should have the prefix to be consistent with the return value # of get_latest_pdfjs_url() version = 'v' + target_version @@ -172,7 +171,7 @@ def test_dicts(): print('ERROR: {}'.format(response.status)) -def run(nsis=False, ace=False, pdfjs=True, legacy_pdfjs=False, fancy_dmg=False, +def run(*, nsis=False, ace=False, pdfjs=True, legacy_pdfjs=False, fancy_dmg=False, pdfjs_version=None, dicts=False, gh_token=None): """Update components based on the given arguments.""" if nsis: diff --git a/scripts/mkvenv.py b/scripts/mkvenv.py index 4ab5d8c10..301780ac8 100755 --- a/scripts/mkvenv.py +++ b/scripts/mkvenv.py @@ -17,7 +17,7 @@ import shutil import venv as pyvenv import subprocess import platform -from typing import List, Tuple, Dict, Union +from typing import Union sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir)) from scripts import utils, link_pyqt @@ -49,7 +49,7 @@ def print_command(*cmd: Union[str, pathlib.Path], venv: bool) -> None: utils.print_col(prefix + ' '.join([str(e) for e in cmd]), 'blue') -def parse_args(argv: List[str] = None) -> argparse.Namespace: +def parse_args(argv: list[str] = None) -> argparse.Namespace: """Parse commandline arguments.""" parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--update', @@ -105,7 +105,7 @@ def _version_key(v): return (999,) -def pyqt_versions() -> List[str]: +def pyqt_versions() -> list[str]: """Get a list of all available PyQt versions. The list is based on the filenames of misc/requirements/ files. @@ -276,7 +276,7 @@ def install_pyqt_wheels(venv_dir: pathlib.Path, pip_install(venv_dir, *wheels) -def install_pyqt_snapshot(venv_dir: pathlib.Path, packages: List[str]) -> None: +def install_pyqt_snapshot(venv_dir: pathlib.Path, packages: list[str]) -> None: """Install PyQt packages from the snapshot server.""" utils.print_title("Installing PyQt snapshots") pip_install(venv_dir, '-U', *packages, '--no-deps', '--pre', @@ -348,9 +348,9 @@ def apply_xcb_util_workaround( link_path.symlink_to(libxcb_util_path) -def _find_libs() -> Dict[Tuple[str, str], List[str]]: +def _find_libs() -> dict[tuple[str, str], list[str]]: """Find all system-wide .so libraries.""" - all_libs: Dict[Tuple[str, str], List[str]] = {} + all_libs: dict[tuple[str, str], list[str]] = {} if pathlib.Path("/sbin/ldconfig").exists(): # /sbin might not be in PATH on e.g. Debian @@ -57,9 +57,8 @@ try: entry_points={'gui_scripts': ['qutebrowser = qutebrowser.qutebrowser:main']}, zip_safe=True, - install_requires=['jinja2', 'PyYAML', - 'importlib_resources>=1.1.0; python_version < "3.9"'], - python_requires='>=3.8', + install_requires=['jinja2', 'PyYAML'], + python_requires='>=3.9', name='qutebrowser', version=_get_constant('version'), description=_get_constant('description'), @@ -81,10 +80,10 @@ try: 'Operating System :: MacOS', 'Operating System :: POSIX :: BSD', 'Programming Language :: Python :: 3', - '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 :: Internet', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Internet :: WWW/HTTP :: Browsers', diff --git a/tests/conftest.py b/tests/conftest.py index 89f32fed1..add8cd036 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -350,12 +350,8 @@ def apply_fake_os(monkeypatch, request): @pytest.fixture(scope='session', autouse=True) def check_yaml_c_exts(): - """Make sure PyYAML C extensions are available on CI. - - Not available yet with a nightly Python, see: - https://github.com/yaml/pyyaml/issues/630 - """ - if testutils.ON_CI and sys.version_info[:2] != (3, 11): + """Make sure PyYAML C extensions are available on CI.""" + if testutils.ON_CI: from yaml import CLoader # pylint: disable=unused-import diff --git a/tests/end2end/features/conftest.py b/tests/end2end/features/conftest.py index b7f112182..22fde0001 100644 --- a/tests/end2end/features/conftest.py +++ b/tests/end2end/features/conftest.py @@ -216,22 +216,22 @@ def open_path(quteproc, server, path): while True: if path.endswith(new_tab_suffix): - path = path[:-len(new_tab_suffix)] + path = path.removesuffix(new_tab_suffix) new_tab = True elif path.endswith(new_bg_tab_suffix): - path = path[:-len(new_bg_tab_suffix)] + path = path.removesuffix(new_bg_tab_suffix) new_bg_tab = True elif path.endswith(new_window_suffix): - path = path[:-len(new_window_suffix)] + path = path.removesuffix(new_window_suffix) new_window = True elif path.endswith(private_suffix): - path = path[:-len(private_suffix)] + path = path.removesuffix(private_suffix) private = True elif path.endswith(as_url_suffix): - path = path[:-len(as_url_suffix)] + path = path.removesuffix(as_url_suffix) as_url = True elif path.endswith(do_not_wait_suffix): - path = path[:-len(do_not_wait_suffix)] + path = path.removesuffix(do_not_wait_suffix) wait = False else: break @@ -264,7 +264,7 @@ def run_command(quteproc, server, tmpdir, command): invalid_tag = ' (invalid command)' if command.endswith(invalid_tag): - command = command[:-len(invalid_tag)] + command = command.removesuffix(invalid_tag) invalid = True else: invalid = False @@ -639,11 +639,11 @@ def check_open_tabs(quteproc, request, tabs): while line.endswith(active_suffix) or line.endswith(pinned_suffix): if line.endswith(active_suffix): # active - line = line[:-len(active_suffix)] + line = line.removesuffix(active_suffix) active = True else: # pinned - line = line[:-len(pinned_suffix)] + line = line.removesuffix(pinned_suffix) pinned = True session_tab = session['windows'][0]['tabs'][i] @@ -739,7 +739,7 @@ def set_up_fileselector(quteproc, py_proc, tmpdir, kind, files, output_type): tmp_file = None for i, arg in enumerate(sys.argv): if arg.startswith('--file='): - tmp_file = arg[len('--file='):] + tmp_file = arg.removeprefix('--file=') sys.argv.pop(i) break selected_files = sys.argv[1:] diff --git a/tests/end2end/features/utilcmds.feature b/tests/end2end/features/utilcmds.feature index ebacea890..70fb26afc 100644 --- a/tests/end2end/features/utilcmds.feature +++ b/tests/end2end/features/utilcmds.feature @@ -92,7 +92,6 @@ Feature: Miscellaneous utility commands exposed to the user. ## :debug-cache-stats - @python>=3.9.0 Scenario: :debug-cache-stats When I run :debug-cache-stats Then "is_valid_prefix: CacheInfo(*)" should be logged diff --git a/tests/end2end/fixtures/notificationserver.py b/tests/end2end/fixtures/notificationserver.py index 61cff7767..21cbb0e8d 100644 --- a/tests/end2end/fixtures/notificationserver.py +++ b/tests/end2end/fixtures/notificationserver.py @@ -4,7 +4,6 @@ import dataclasses import itertools -from typing import Dict, List from qutebrowser.qt.core import QObject, QByteArray, QUrl, pyqtSlot from qutebrowser.qt.gui import QImage @@ -43,7 +42,7 @@ class TestNotificationServer(QObject): self._bus = QDBusConnection.sessionBus() self._message_id_gen = itertools.count(1) # A dict mapping notification IDs to currently-displayed notifications. - self.messages: Dict[int, NotificationProperties] = {} + self.messages: dict[int, NotificationProperties] = {} self.supports_body_markup = True self.last_id = None @@ -195,7 +194,7 @@ class TestNotificationServer(QObject): return message_id @pyqtSlot(QDBusMessage, result="QStringList") - def GetCapabilities(self, message: QDBusMessage) -> List[str]: + def GetCapabilities(self, message: QDBusMessage) -> list[str]: assert not message.signature() assert not message.arguments() assert message.type() == QDBusMessage.MessageType.MethodCallMessage diff --git a/tests/end2end/test_dirbrowser.py b/tests/end2end/test_dirbrowser.py index 58cf66d6a..c1762b183 100644 --- a/tests/end2end/test_dirbrowser.py +++ b/tests/end2end/test_dirbrowser.py @@ -7,7 +7,6 @@ import pathlib import dataclasses -from typing import List import pytest import bs4 @@ -97,8 +96,8 @@ class Parsed: path: str parent: str - folders: List[str] - files: List[str] + folders: list[str] + files: list[str] @dataclasses.dataclass @@ -124,7 +123,7 @@ def parse(quteproc): title_prefix = 'Browse directory: ' # Strip off the title prefix to obtain the path of the folder that # we're browsing - path = pathlib.Path(soup.title.string[len(title_prefix):]) + path = pathlib.Path(soup.title.string.removeprefix(title_prefix)) container = soup('div', id='dirbrowserContainer')[0] diff --git a/tests/end2end/test_invocations.py b/tests/end2end/test_invocations.py index 75df387fb..47e67571e 100644 --- a/tests/end2end/test_invocations.py +++ b/tests/end2end/test_invocations.py @@ -335,7 +335,7 @@ def test_launching_with_old_python(python): except FileNotFoundError: pytest.skip(f"{python} not found") assert proc.returncode == 1 - error = "At least Python 3.8 is required to run qutebrowser" + error = "At least Python 3.9 is required to run qutebrowser" assert proc.stderr.decode('ascii').startswith(error) @@ -1018,7 +1018,7 @@ def test_restart(request, quteproc_new): quteproc_new.wait_for_quit() assert line.message.startswith(prefix) - pid = int(line.message[len(prefix):]) + pid = int(line.message.removeprefix(prefix)) os.kill(pid, signal.SIGTERM) try: diff --git a/tests/helpers/stubs.py b/tests/helpers/stubs.py index 658b027cb..50528ee8b 100644 --- a/tests/helpers/stubs.py +++ b/tests/helpers/stubs.py @@ -6,7 +6,8 @@ """Fake objects/stubs.""" -from typing import Any, Callable, Tuple +from typing import Any +from collections.abc import Callable from unittest import mock import contextlib import shutil @@ -327,7 +328,7 @@ class FakeCommand: completion: Any = None maxsplit: int = None takes_count: Callable[[], bool] = lambda: False - modes: Tuple[usertypes.KeyMode] = (usertypes.KeyMode.normal, ) + modes: tuple[usertypes.KeyMode] = (usertypes.KeyMode.normal, ) class FakeTimer(QObject): diff --git a/tests/unit/browser/test_notification.py b/tests/unit/browser/test_notification.py index 3d6c16f87..6c888f084 100644 --- a/tests/unit/browser/test_notification.py +++ b/tests/unit/browser/test_notification.py @@ -7,7 +7,7 @@ import logging import itertools import inspect -from typing import List, Dict, Any, Optional, TYPE_CHECKING +from typing import Any, Optional, TYPE_CHECKING import pytest from qutebrowser.qt.core import pyqtSignal, pyqtSlot, QUrl, QObject @@ -36,7 +36,7 @@ class FakeDBusMessage: self._type = typ self._error_name = error_name - def arguments(self) -> List[Any]: + def arguments(self) -> list[Any]: return self._arguments def signature(self) -> str: @@ -107,8 +107,8 @@ class FakeDBusInterface: icon: str, title: str, body: str, - actions: List[str], - hints: Dict[str, Any], + actions: list[str], + hints: dict[str, Any], timeout: int, ) -> FakeDBusMessage: assert self.notify_reply is not None diff --git a/tests/unit/browser/webengine/test_darkmode.py b/tests/unit/browser/webengine/test_darkmode.py index 2f7021e95..8b8959a15 100644 --- a/tests/unit/browser/webengine/test_darkmode.py +++ b/tests/unit/browser/webengine/test_darkmode.py @@ -4,7 +4,6 @@ import logging -from typing import List, Tuple import pytest QWebEngineSettings = pytest.importorskip("qutebrowser.qt.webenginecore").QWebEngineSettings @@ -70,7 +69,7 @@ class TestDefinition: ) -> darkmode._Definition: return darkmode._Definition(setting1, setting2, mandatory=set(), prefix="") - def _get_settings(self, definition: darkmode._Definition) -> List[darkmode._Setting]: + def _get_settings(self, definition: darkmode._Definition) -> list[darkmode._Setting]: return [setting for _key, setting in definition.prefixed_settings()] @pytest.mark.parametrize("prefix", ["", "prefix"]) @@ -365,7 +364,7 @@ def test_customization(config_stub, setting, value, exp_key, exp_val): ('6.5.3', 'policy.images', 'smart', [('ImagePolicy', '2')]), ('6.5.3', 'policy.images', 'smart-simple', [('ImagePolicy', '2')]), ]) -def test_image_policy(config_stub, qtwe_version: str, setting: str, value: str, expected: List[Tuple[str, str]]): +def test_image_policy(config_stub, qtwe_version: str, setting: str, value: str, expected: list[tuple[str, str]]): config_stub.val.colors.webpage.darkmode.enabled = True config_stub.set_obj('colors.webpage.darkmode.' + setting, value) diff --git a/tests/unit/browser/webengine/test_webview.py b/tests/unit/browser/webengine/test_webview.py index f14a896b6..8ffc81d60 100644 --- a/tests/unit/browser/webengine/test_webview.py +++ b/tests/unit/browser/webengine/test_webview.py @@ -25,10 +25,10 @@ class Naming: def camel_to_snake(naming, name): if naming.prefix: assert name.startswith(naming.prefix) - name = name[len(naming.prefix):] + name = name.removeprefix(naming.prefix) if naming.suffix: assert name.endswith(naming.suffix) - name = name[:-len(naming.suffix)] + name = name.removesuffix(naming.suffix) # https://stackoverflow.com/a/1176023 return re.sub(r'(?<!^)(?=[A-Z])', '_', name).lower() diff --git a/tests/unit/browser/webkit/network/test_filescheme.py b/tests/unit/browser/webkit/network/test_filescheme.py index 3ed04ec01..6eee5da52 100644 --- a/tests/unit/browser/webkit/network/test_filescheme.py +++ b/tests/unit/browser/webkit/network/test_filescheme.py @@ -5,7 +5,6 @@ import os import dataclasses -from typing import List import pytest import bs4 @@ -101,8 +100,8 @@ class TestDirbrowserHtml: class Parsed: parent: str - folders: List[str] - files: List[str] + folders: list[str] + files: list[str] @dataclasses.dataclass class Item: diff --git a/tests/unit/components/test_braveadblock.py b/tests/unit/components/test_braveadblock.py index 54ef85115..dab842139 100644 --- a/tests/unit/components/test_braveadblock.py +++ b/tests/unit/components/test_braveadblock.py @@ -5,7 +5,7 @@ import pathlib import logging import csv -from typing import Iterable, Tuple +from collections.abc import Iterable from qutebrowser.qt.core import QUrl @@ -165,7 +165,7 @@ def assert_only_one_success_message(messages): def assert_urls( ad_blocker: braveadblock.BraveAdBlocker, - urls: Iterable[Tuple[str, str, ResourceType]], + urls: Iterable[tuple[str, str, ResourceType]], should_be_blocked: bool, ) -> None: for (str_url, source_str_url, request_type) in urls: diff --git a/tests/unit/javascript/test_js_quirks.py b/tests/unit/javascript/test_js_quirks.py index 9218c6d0d..981f9d9e8 100644 --- a/tests/unit/javascript/test_js_quirks.py +++ b/tests/unit/javascript/test_js_quirks.py @@ -55,7 +55,7 @@ def test_js_quirks(config_stub, js_tester_webengine, base_url, source, expected) def test_js_quirks_match_files(webengine_tab): quirks_path = pathlib.Path(qutebrowser.__file__).parent / "javascript" / "quirks" suffix = ".user.js" - quirks_files = {p.name[:-len(suffix)] for p in quirks_path.glob(f"*{suffix}")} + quirks_files = {p.name.removesuffix(suffix) for p in quirks_path.glob(f"*{suffix}")} quirks_code = {q.filename for q in webengine_tab._scripts._get_quirks()} assert quirks_code == quirks_files @@ -66,7 +66,7 @@ def test_js_quirks_match_settings(webengine_tab, configdata_init): valid_values = opt.typ.get_valid_values() assert valid_values is not None quirks_config = { - val[len(prefix):].replace("-", "_") + val.removeprefix(prefix).replace("-", "_") for val in valid_values if val.startswith(prefix) } diff --git a/tests/unit/keyinput/test_keyutils.py b/tests/unit/keyinput/test_keyutils.py index 572456a22..257bd89a2 100644 --- a/tests/unit/keyinput/test_keyutils.py +++ b/tests/unit/keyinput/test_keyutils.py @@ -57,7 +57,7 @@ def qtest_key(request): def test_key_data_keys(): """Make sure all possible keys are in key_data.KEYS.""" - key_names = {name[len("Key_"):] + key_names = {name.removeprefix("Key_") for name in testutils.enum_members(Qt, Qt.Key)} key_data_names = {key.attribute for key in sorted(key_data.KEYS)} diff = key_names - key_data_names @@ -66,7 +66,7 @@ def test_key_data_keys(): def test_key_data_modifiers(): """Make sure all possible modifiers are in key_data.MODIFIERS.""" - mod_names = {name[:-len("Modifier")] + mod_names = {name.removesuffix("Modifier") for name, value in testutils.enum_members(Qt, Qt.KeyboardModifier).items() if value not in [Qt.KeyboardModifier.NoModifier, Qt.KeyboardModifier.KeyboardModifierMask]} mod_data_names = {mod.attribute for mod in sorted(key_data.MODIFIERS)} diff --git a/tests/unit/misc/test_checkpyver.py b/tests/unit/misc/test_checkpyver.py index fddf9e9e8..8bcdf9772 100644 --- a/tests/unit/misc/test_checkpyver.py +++ b/tests/unit/misc/test_checkpyver.py @@ -14,7 +14,7 @@ import pytest from qutebrowser.misc import checkpyver -TEXT = (r"At least Python 3.8 is required to run qutebrowser, but it's " +TEXT = (r"At least Python 3.9 is required to run qutebrowser, but it's " r"running with \d+\.\d+\.\d+.") diff --git a/tests/unit/misc/test_editor.py b/tests/unit/misc/test_editor.py index 3f7edd143..debfa7b9e 100644 --- a/tests/unit/misc/test_editor.py +++ b/tests/unit/misc/test_editor.py @@ -163,7 +163,7 @@ class TestFileHandling: msg = message_mock.getmsg(usertypes.MessageLevel.info) prefix = 'Editor backup at ' assert msg.text.startswith(prefix) - fname = msg.text[len(prefix):] + fname = msg.text.removeprefix(prefix) with qtbot.wait_signal(editor.editing_finished): editor._proc._proc.finished.emit(0, QProcess.ExitStatus.NormalExit) diff --git a/tests/unit/misc/test_ipc.py b/tests/unit/misc/test_ipc.py index 79c2c7b7d..f611428af 100644 --- a/tests/unit/misc/test_ipc.py +++ b/tests/unit/misc/test_ipc.py @@ -12,7 +12,7 @@ import json import hashlib import dataclasses from unittest import mock -from typing import Optional, List +from typing import Optional import pytest from qutebrowser.qt.core import pyqtSignal, QObject @@ -631,7 +631,7 @@ class TestSendOrListen: no_err_windows: bool basedir: str - command: List[str] + command: list[str] target: Optional[str] @pytest.fixture diff --git a/tests/unit/misc/test_split.py b/tests/unit/misc/test_split.py index f8b700982..2e991dc51 100644 --- a/tests/unit/misc/test_split.py +++ b/tests/unit/misc/test_split.py @@ -5,7 +5,6 @@ """Tests for qutebrowser.misc.split.""" import dataclasses -from typing import List import pytest @@ -100,8 +99,8 @@ def _parse_split_test_data_str(): class TestCase: inp: str - keep: List[str] - no_keep: List[str] + keep: list[str] + no_keep: list[str] for line in test_data_str.splitlines(): if not line: diff --git a/tests/unit/test_qt_machinery.py b/tests/unit/test_qt_machinery.py index cf7990393..677494ee5 100644 --- a/tests/unit/test_qt_machinery.py +++ b/tests/unit/test_qt_machinery.py @@ -9,7 +9,7 @@ import sys import html import argparse import typing -from typing import Any, Optional, List, Dict, Union, Type +from typing import Any, Optional, Union import dataclasses import pytest @@ -51,7 +51,7 @@ def undo_init(monkeypatch: pytest.MonkeyPatch) -> None: (machinery.NoWrapperAvailableError(machinery.SelectionInfo()), ImportError), ], ) -def test_importerror_exceptions(exception: Exception, base: Type[Exception]): +def test_importerror_exceptions(exception: Exception, base: type[Exception]): with pytest.raises(base): raise exception @@ -118,7 +118,7 @@ def test_selectioninfo_str(info: machinery.SelectionInfo, expected: str): @pytest.mark.parametrize("order", [["PyQt5", "PyQt6"], ["PyQt6", "PyQt5"]]) -def test_selectioninfo_str_wrapper_precedence(order: List[str]): +def test_selectioninfo_str_wrapper_precedence(order: list[str]): """The order of the wrappers should be the same as in machinery.WRAPPERS.""" info = machinery.SelectionInfo( wrapper="PyQt6", @@ -210,7 +210,7 @@ def modules(): ) def test_autoselect( stubs: Any, - available: Dict[str, Union[bool, Exception]], + available: dict[str, Union[bool, Exception]], expected: machinery.SelectionInfo, monkeypatch: pytest.MonkeyPatch, ): @@ -417,7 +417,7 @@ class TestInit: def test_none_available_implicit( self, stubs: Any, - modules: Dict[str, bool], + modules: dict[str, bool], monkeypatch: pytest.MonkeyPatch, qt_auto_env: None, ): @@ -441,7 +441,7 @@ class TestInit: def test_none_available_explicit( self, stubs: Any, - modules: Dict[str, bool], + modules: dict[str, bool], monkeypatch: pytest.MonkeyPatch, empty_args: argparse.Namespace, qt_auto_env: None, diff --git a/tests/unit/utils/test_resources.py b/tests/unit/utils/test_resources.py index 911f072f1..3669e523a 100644 --- a/tests/unit/utils/test_resources.py +++ b/tests/unit/utils/test_resources.py @@ -79,7 +79,7 @@ class TestReadFile: 'html/error.html']) def test_read_cached_file(self, mocker, filename): resources.preload() - m = mocker.patch('qutebrowser.utils.resources.importlib_resources.files') + m = mocker.patch('qutebrowser.utils.resources.importlib.resources.files') resources.read_file(filename) m.assert_not_called() @@ -111,7 +111,7 @@ class TestReadFile: return self if fake_exception is not None: - monkeypatch.setattr(resources.importlib_resources, 'files', + monkeypatch.setattr(resources.importlib.resources, 'files', lambda _pkg: BrokenFileFake(fake_exception)) meth = getattr(resources, name) diff --git a/tests/unit/utils/test_utils.py b/tests/unit/utils/test_utils.py index 51255aa61..fc4a3e652 100644 --- a/tests/unit/utils/test_utils.py +++ b/tests/unit/utils/test_utils.py @@ -474,7 +474,7 @@ def test_get_repr(constructor, attrs, expected): assert utils.get_repr(Obj(), constructor, **attrs) == expected -class QualnameObj(): +class QualnameObj: """Test object for test_qualname.""" diff --git a/tests/unit/utils/test_version.py b/tests/unit/utils/test_version.py index 149b1d855..91d737dd2 100644 --- a/tests/unit/utils/test_version.py +++ b/tests/unit/utils/test_version.py @@ -737,7 +737,6 @@ class TestModuleVersions: ('yaml', True), ('adblock', True), ('dataclasses', False), - ('importlib_resources', False), ('objc', True), ]) def test_existing_attributes(self, name, has_version): @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py38-pyqt515-cov,mypy-pyqt5,misc,vulture,flake8,pylint,pyroma,check-manifest,eslint,yamllint,actionlint +envlist = py39-pyqt515-cov,mypy-pyqt5,misc,vulture,flake8,pylint,pyroma,check-manifest,eslint,yamllint,actionlint distshare = {toxworkdir} skipsdist = true minversion = 3.20 @@ -36,7 +36,6 @@ passenv = basepython = py: {env:PYTHON:python3} py3: {env:PYTHON:python3} - py38: {env:PYTHON:python3.8} py39: {env:PYTHON:python3.9} py310: {env:PYTHON:python3.10} py311: {env:PYTHON:python3.11} |