summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortoofar <toofar@spalge.com>2024-10-29 10:22:44 +1300
committertoofar <toofar@spalge.com>2024-10-29 10:22:44 +1300
commit7535207fd3ce5b97f23a423baa7052230b0f3b72 (patch)
treebf972b1cc0682785e7683b7bc99791c01f871629
parente55624703bb2be91bfb89ebbca40ebe1580443e8 (diff)
parenta138ab89789f3624da78a62629de84d5c42cbdac (diff)
downloadqutebrowser-7535207fd3ce5b97f23a423baa7052230b0f3b72.tar.gz
qutebrowser-7535207fd3ce5b97f23a423baa7052230b0f3b72.zip
Merge remote-tracking branch 'upstream/main' into feat/68_permissions_askeverytime
-rw-r--r--.flake82
-rw-r--r--.github/workflows/ci.yml20
-rw-r--r--.github/workflows/nightly.yml14
-rw-r--r--.github/workflows/recompile-requirements.yml6
-rw-r--r--.github/workflows/release.yml1
-rw-r--r--.mypy.ini2
-rw-r--r--.pylintrc5
-rw-r--r--README.asciidoc6
-rw-r--r--doc/changelog.asciidoc20
-rw-r--r--doc/contributing.asciidoc14
-rw-r--r--doc/help/configuring.asciidoc2
-rw-r--r--doc/help/settings.asciidoc2
-rw-r--r--doc/install.asciidoc2
-rwxr-xr-xmisc/nsis/install.nsh36
-rwxr-xr-xmisc/nsis/qutebrowser.nsi3
-rw-r--r--misc/org.qutebrowser.qutebrowser.desktop2
-rw-r--r--misc/requirements/requirements-check-manifest.txt6
-rw-r--r--misc/requirements/requirements-dev.txt14
-rw-r--r--misc/requirements/requirements-dev.txt-raw1
-rw-r--r--misc/requirements/requirements-mypy.txt12
-rw-r--r--misc/requirements/requirements-mypy.txt-raw3
-rw-r--r--misc/requirements/requirements-pyinstaller.txt4
-rw-r--r--misc/requirements/requirements-pylint.txt10
-rw-r--r--misc/requirements/requirements-pylint.txt-raw1
-rw-r--r--misc/requirements/requirements-pyroma.txt10
-rw-r--r--misc/requirements/requirements-qutebrowser.txt-raw5
-rw-r--r--misc/requirements/requirements-sphinx.txt22
-rw-r--r--misc/requirements/requirements-tests-bleeding.txt6
-rw-r--r--misc/requirements/requirements-tests.txt18
-rw-r--r--misc/requirements/requirements-tests.txt-raw1
-rw-r--r--misc/requirements/requirements-tox.txt11
-rw-r--r--misc/requirements/requirements-vulture.txt4
-rw-r--r--qutebrowser/api/cmdutils.py5
-rw-r--r--qutebrowser/api/hook.py3
-rw-r--r--qutebrowser/app.py5
-rw-r--r--qutebrowser/browser/browsertab.py16
-rw-r--r--qutebrowser/browser/commands.py10
-rw-r--r--qutebrowser/browser/downloads.py7
-rw-r--r--qutebrowser/browser/downloadview.py7
-rw-r--r--qutebrowser/browser/greasemonkey.py21
-rw-r--r--qutebrowser/browser/hints.py21
-rw-r--r--qutebrowser/browser/history.py3
-rw-r--r--qutebrowser/browser/navigate.py4
-rw-r--r--qutebrowser/browser/network/pac.py2
-rw-r--r--qutebrowser/browser/network/proxy.py4
-rw-r--r--qutebrowser/browser/qtnetworkdownloads.py4
-rw-r--r--qutebrowser/browser/qutescheme.py13
-rw-r--r--qutebrowser/browser/shared.py11
-rw-r--r--qutebrowser/browser/urlmarks.py2
-rw-r--r--qutebrowser/browser/webelem.py7
-rw-r--r--qutebrowser/browser/webengine/darkmode.py16
-rw-r--r--qutebrowser/browser/webengine/notification.py17
-rw-r--r--qutebrowser/browser/webengine/webengineelem.py9
-rw-r--r--qutebrowser/browser/webengine/webenginesettings.py6
-rw-r--r--qutebrowser/browser/webengine/webview.py5
-rw-r--r--qutebrowser/browser/webkit/certificateerror.py3
-rw-r--r--qutebrowser/browser/webkit/cookies.py2
-rw-r--r--qutebrowser/browser/webkit/httpheaders.py3
-rw-r--r--qutebrowser/browser/webkit/mhtml.py4
-rw-r--r--qutebrowser/browser/webkit/network/networkmanager.py7
-rw-r--r--qutebrowser/browser/webkit/tabhistory.py5
-rw-r--r--qutebrowser/browser/webkit/webkitelem.py9
-rw-r--r--qutebrowser/browser/webkit/webkittab.py3
-rw-r--r--qutebrowser/commands/cmdexc.py3
-rw-r--r--qutebrowser/commands/command.py10
-rw-r--r--qutebrowser/commands/parser.py10
-rw-r--r--qutebrowser/commands/runners.py7
-rw-r--r--qutebrowser/commands/userscripts.py7
-rw-r--r--qutebrowser/completion/completer.py2
-rw-r--r--qutebrowser/completion/models/__init__.py3
-rw-r--r--qutebrowser/completion/models/completionmodel.py3
-rw-r--r--qutebrowser/completion/models/filepathcategory.py5
-rw-r--r--qutebrowser/completion/models/listcategory.py4
-rw-r--r--qutebrowser/completion/models/miscmodels.py6
-rw-r--r--qutebrowser/completion/models/urlmodel.py4
-rw-r--r--qutebrowser/completion/models/util.py2
-rw-r--r--qutebrowser/components/braveadblock.py3
-rw-r--r--qutebrowser/components/hostblock.py10
-rw-r--r--qutebrowser/components/misccommands.py3
-rw-r--r--qutebrowser/components/readlinecommands.py3
-rw-r--r--qutebrowser/components/scrollcommands.py4
-rw-r--r--qutebrowser/components/utils/blockutils.py6
-rw-r--r--qutebrowser/config/config.py14
-rw-r--r--qutebrowser/config/configcache.py4
-rw-r--r--qutebrowser/config/configcommands.py5
-rw-r--r--qutebrowser/config/configdata.py10
-rw-r--r--qutebrowser/config/configdata.yml3
-rw-r--r--qutebrowser/config/configexc.py5
-rw-r--r--qutebrowser/config/configfiles.py16
-rw-r--r--qutebrowser/config/configtypes.py69
-rw-r--r--qutebrowser/config/configutils.py10
-rw-r--r--qutebrowser/config/qtargs.py16
-rw-r--r--qutebrowser/config/stylesheet.py4
-rw-r--r--qutebrowser/config/websettings.py11
-rw-r--r--qutebrowser/extensions/interceptors.py5
-rw-r--r--qutebrowser/extensions/loader.py9
-rw-r--r--qutebrowser/keyinput/basekeyparser.py3
-rw-r--r--qutebrowser/keyinput/keyutils.py9
-rw-r--r--qutebrowser/keyinput/macros.py8
-rw-r--r--qutebrowser/keyinput/modeman.py5
-rw-r--r--qutebrowser/keyinput/modeparsers.py3
-rw-r--r--qutebrowser/mainwindow/mainwindow.py7
-rw-r--r--qutebrowser/mainwindow/messageview.py3
-rw-r--r--qutebrowser/mainwindow/prompt.py5
-rw-r--r--qutebrowser/mainwindow/tabbedbrowser.py9
-rw-r--r--qutebrowser/mainwindow/tabwidget.py4
-rw-r--r--qutebrowser/mainwindow/windowundo.py3
-rw-r--r--qutebrowser/misc/backendproblem.py5
-rw-r--r--qutebrowser/misc/binparsing.py4
-rw-r--r--qutebrowser/misc/checkpyver.py4
-rw-r--r--qutebrowser/misc/cmdhistory.py2
-rw-r--r--qutebrowser/misc/consolewidget.py3
-rw-r--r--qutebrowser/misc/crashdialog.py3
-rw-r--r--qutebrowser/misc/crashsignal.py7
-rw-r--r--qutebrowser/misc/debugcachestats.py18
-rw-r--r--qutebrowser/misc/earlyinit.py6
-rw-r--r--qutebrowser/misc/editor.py2
-rw-r--r--qutebrowser/misc/elf.py6
-rw-r--r--qutebrowser/misc/guiprocess.py10
-rw-r--r--qutebrowser/misc/httpclient.py2
-rw-r--r--qutebrowser/misc/keyhintwidget.py2
-rw-r--r--qutebrowser/misc/lineparser.py2
-rw-r--r--qutebrowser/misc/nativeeventfilter.py4
-rw-r--r--qutebrowser/misc/objects.py6
-rw-r--r--qutebrowser/misc/pakjoy.py9
-rw-r--r--qutebrowser/misc/quitter.py3
-rw-r--r--qutebrowser/misc/savemanager.py2
-rw-r--r--qutebrowser/misc/sessions.py3
-rw-r--r--qutebrowser/misc/sql.py21
-rw-r--r--qutebrowser/misc/throttle.py3
-rw-r--r--qutebrowser/misc/utilcmds.py5
-rw-r--r--qutebrowser/qt/_core_pyqtproperty.py4
-rw-r--r--qutebrowser/qt/machinery.py4
-rw-r--r--qutebrowser/utils/debug.py25
-rw-r--r--qutebrowser/utils/docutils.py9
-rw-r--r--qutebrowser/utils/error.py4
-rw-r--r--qutebrowser/utils/javascript.py3
-rw-r--r--qutebrowser/utils/jinja.py13
-rw-r--r--qutebrowser/utils/log.py13
-rw-r--r--qutebrowser/utils/message.py5
-rw-r--r--qutebrowser/utils/objreg.py6
-rw-r--r--qutebrowser/utils/qtlog.py3
-rw-r--r--qutebrowser/utils/qtutils.py14
-rw-r--r--qutebrowser/utils/resources.py23
-rw-r--r--qutebrowser/utils/standarddir.py5
-rw-r--r--qutebrowser/utils/urlmatch.py6
-rw-r--r--qutebrowser/utils/urlutils.py13
-rw-r--r--qutebrowser/utils/usertypes.py3
-rw-r--r--qutebrowser/utils/utils.py47
-rw-r--r--qutebrowser/utils/version.py37
-rw-r--r--requirements.txt4
-rwxr-xr-xscripts/dev/build_release.py74
-rw-r--r--scripts/dev/changelog_urls.json1
-rw-r--r--scripts/dev/check_coverage.py2
-rw-r--r--scripts/dev/misc_checks.py9
-rw-r--r--scripts/dev/pylint_checkers/qute_pylint/config.py2
-rw-r--r--scripts/dev/recompile_requirements.py15
-rw-r--r--scripts/dev/run_pylint_on_tests.py1
-rwxr-xr-xscripts/dev/update_3rdparty.py5
-rwxr-xr-xscripts/mkvenv.py12
-rwxr-xr-xsetup.py7
-rw-r--r--tests/conftest.py8
-rw-r--r--tests/end2end/features/conftest.py20
-rw-r--r--tests/end2end/features/utilcmds.feature1
-rw-r--r--tests/end2end/fixtures/notificationserver.py5
-rw-r--r--tests/end2end/test_dirbrowser.py7
-rw-r--r--tests/end2end/test_invocations.py4
-rw-r--r--tests/helpers/stubs.py5
-rw-r--r--tests/unit/browser/test_notification.py8
-rw-r--r--tests/unit/browser/webengine/test_darkmode.py5
-rw-r--r--tests/unit/browser/webengine/test_webview.py4
-rw-r--r--tests/unit/browser/webkit/network/test_filescheme.py5
-rw-r--r--tests/unit/components/test_braveadblock.py4
-rw-r--r--tests/unit/javascript/test_js_quirks.py4
-rw-r--r--tests/unit/keyinput/test_keyutils.py4
-rw-r--r--tests/unit/misc/test_checkpyver.py2
-rw-r--r--tests/unit/misc/test_editor.py2
-rw-r--r--tests/unit/misc/test_ipc.py4
-rw-r--r--tests/unit/misc/test_split.py5
-rw-r--r--tests/unit/test_qt_machinery.py12
-rw-r--r--tests/unit/utils/test_resources.py4
-rw-r--r--tests/unit/utils/test_utils.py2
-rw-r--r--tests/unit/utils/test_version.py1
-rw-r--r--tox.ini3
184 files changed, 741 insertions, 713 deletions
diff --git a/.flake8 b/.flake8
index 6c4dd923e..8bf2b3efd 100644
--- a/.flake8
+++ b/.flake8
@@ -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'
diff --git a/.mypy.ini b/.mypy.ini
index 81f69a09e..59a3f8670 100644
--- a/.mypy.ini
+++ b/.mypy.ini
@@ -1,5 +1,5 @@
[mypy]
-python_version = 3.8
+python_version = 3.9
### --strict
warn_unused_configs = True
diff --git a/.pylintrc b/.pylintrc
index a6784c0e4..1cb4e0f1b 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -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
diff --git a/setup.py b/setup.py
index feb949595..9562a8e22 100755
--- a/setup.py
+++ b/setup.py
@@ -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):
diff --git a/tox.ini b/tox.ini
index 607b19fb3..274638969 100644
--- a/tox.ini
+++ b/tox.ini
@@ -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}