summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Bruhin <me@the-compiler.org>2021-01-11 22:22:30 +0100
committerFlorian Bruhin <me@the-compiler.org>2021-01-13 20:26:41 +0100
commitc970c6335521fee359c1a68c0431c612631e73fb (patch)
tree1062d9ffd9fbfcafc0753308b8e7a15a5f5f6920
parent4b7d52ae7cdf52ebef038b4a90b9fe95ab002105 (diff)
downloadqutebrowser-c970c6335521fee359c1a68c0431c612631e73fb.tar.gz
qutebrowser-c970c6335521fee359c1a68c0431c612631e73fb.zip
dataclasses: Initial switch
See #6023
-rw-r--r--misc/requirements/requirements-qutebrowser.txt-raw4
-rw-r--r--qutebrowser/browser/browsertab.py26
-rw-r--r--qutebrowser/browser/greasemonkey.py12
-rw-r--r--qutebrowser/browser/hints.py32
-rw-r--r--qutebrowser/browser/qtnetworkdownloads.py10
-rw-r--r--qutebrowser/browser/webengine/interceptor.py15
-rw-r--r--qutebrowser/browser/webkit/mhtml.py14
-rw-r--r--qutebrowser/browser/webkit/network/networkmanager.py13
-rw-r--r--qutebrowser/browser/webkit/rfc6266.py9
-rw-r--r--qutebrowser/commands/command.py19
-rw-r--r--qutebrowser/commands/runners.py13
-rw-r--r--qutebrowser/completion/completer.py14
-rw-r--r--qutebrowser/config/configdata.py28
-rw-r--r--qutebrowser/config/configexc.py10
-rw-r--r--qutebrowser/config/configtypes.py12
-rw-r--r--qutebrowser/config/websettings.py14
-rw-r--r--qutebrowser/extensions/interceptors.py12
-rw-r--r--qutebrowser/extensions/loader.py22
-rw-r--r--qutebrowser/keyinput/basekeyparser.py12
-rw-r--r--qutebrowser/keyinput/keyutils.py8
-rw-r--r--qutebrowser/keyinput/modeman.py8
-rw-r--r--qutebrowser/mainwindow/prompt.py8
-rw-r--r--qutebrowser/mainwindow/statusbar/bar.py16
-rw-r--r--qutebrowser/mainwindow/tabbedbrowser.py15
-rw-r--r--qutebrowser/mainwindow/tabwidget.py10
-rw-r--r--qutebrowser/mainwindow/windowundo.py12
-rw-r--r--qutebrowser/misc/backendproblem.py24
-rw-r--r--qutebrowser/misc/crashsignal.py12
-rw-r--r--qutebrowser/misc/throttle.py8
-rw-r--r--qutebrowser/utils/usertypes.py12
-rw-r--r--qutebrowser/utils/version.py24
-rw-r--r--requirements.txt1
-rw-r--r--scripts/dev/check_coverage.py10
-rw-r--r--scripts/dev/get_coredumpctl_traces.py18
-rw-r--r--scripts/dev/recompile_requirements.py1
-rwxr-xr-xscripts/dictcli.py16
-rw-r--r--tests/end2end/fixtures/testprocess.py8
-rw-r--r--tests/end2end/fixtures/webserver.py8
-rw-r--r--tests/end2end/test_dirbrowser.py21
-rw-r--r--tests/end2end/test_hints_html.py9
-rw-r--r--tests/helpers/fixtures.py12
-rw-r--r--tests/helpers/messagemock.py8
-rw-r--r--tests/helpers/stubs.py23
-rw-r--r--tests/unit/browser/test_signalfilter.py8
-rw-r--r--tests/unit/browser/webkit/network/test_filescheme.py17
-rw-r--r--tests/unit/browser/webkit/test_tabhistory.py10
-rw-r--r--tests/unit/browser/webkit/test_webkitelem.py13
-rw-r--r--tests/unit/config/test_configtypes.py14
-rw-r--r--tests/unit/keyinput/key_data.py30
-rw-r--r--tests/unit/misc/test_ipc.py13
-rw-r--r--tests/unit/misc/test_split.py12
-rw-r--r--tests/unit/misc/userscripts/test_qute_lastpass.py13
-rw-r--r--tests/unit/utils/test_javascript.py10
-rw-r--r--tests/unit/utils/test_log.py12
-rw-r--r--tests/unit/utils/test_qtutils.py8
-rw-r--r--tests/unit/utils/test_urlutils.py18
-rw-r--r--tests/unit/utils/test_version.py22
57 files changed, 407 insertions, 376 deletions
diff --git a/misc/requirements/requirements-qutebrowser.txt-raw b/misc/requirements/requirements-qutebrowser.txt-raw
index 03e6324f6..1781775f4 100644
--- a/misc/requirements/requirements-qutebrowser.txt-raw
+++ b/misc/requirements/requirements-qutebrowser.txt-raw
@@ -2,7 +2,10 @@ Jinja2
pyPEG2
PyYAML
attrs
+
+## stdlib backports
importlib-resources
+dataclasses
## Optional dependencies
Pygments # For :view-source --pygments or on QtWebKit
@@ -10,3 +13,4 @@ colorama # Colored log output on Windows
adblock # Improved adblocking
#@ markers: importlib-resources python_version<"3.9"
+#@ markers: dataclasses python_version<"3.7"
diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py
index c62c75d48..b6774e38b 100644
--- a/qutebrowser/browser/browsertab.py
+++ b/qutebrowser/browser/browsertab.py
@@ -25,7 +25,7 @@ import functools
from typing import (cast, TYPE_CHECKING, Any, Callable, Iterable, List, Optional,
Sequence, Set, Type, Union)
-import attr
+import dataclasses
from PyQt5.QtCore import (pyqtSignal, pyqtSlot, QUrl, QObject, QSizeF, Qt,
QEvent, QPoint)
from PyQt5.QtGui import QKeyEvent, QIcon
@@ -108,7 +108,7 @@ class TerminationStatus(enum.Enum):
killed = 3
-@attr.s
+@dataclasses.dataclass
class TabData:
"""A simple namespace with a fixed set of attributes.
@@ -130,17 +130,17 @@ class TabData:
splitter: InspectorSplitter used to show inspector inside the tab.
"""
- keep_icon: bool = attr.ib(False)
- viewing_source: bool = attr.ib(False)
- inspector: Optional['AbstractWebInspector'] = attr.ib(None)
- open_target: usertypes.ClickTarget = attr.ib(usertypes.ClickTarget.normal)
- override_target: Optional[usertypes.ClickTarget] = attr.ib(None)
- pinned: bool = attr.ib(False)
- fullscreen: bool = attr.ib(False)
- netrc_used: bool = attr.ib(False)
- input_mode: usertypes.KeyMode = attr.ib(usertypes.KeyMode.normal)
- last_navigation: usertypes.NavigationRequest = attr.ib(None)
- splitter: miscwidgets.InspectorSplitter = attr.ib(None)
+ keep_icon: bool = False
+ viewing_source: bool = False
+ inspector: Optional['AbstractWebInspector'] = None
+ open_target: usertypes.ClickTarget = usertypes.ClickTarget.normal
+ override_target: Optional[usertypes.ClickTarget] = None
+ pinned: bool = False
+ fullscreen: bool = False
+ netrc_used: bool = False
+ input_mode: usertypes.KeyMode = usertypes.KeyMode.normal
+ last_navigation: Optional[usertypes.NavigationRequest] = None
+ splitter: Optional[miscwidgets.InspectorSplitter] = None
def should_show_icon(self) -> bool:
return (config.val.tabs.favicons.show == 'always' or
diff --git a/qutebrowser/browser/greasemonkey.py b/qutebrowser/browser/greasemonkey.py
index 9e25e49bd..a8fdd47ed 100644
--- a/qutebrowser/browser/greasemonkey.py
+++ b/qutebrowser/browser/greasemonkey.py
@@ -28,7 +28,7 @@ import glob
import textwrap
from typing import cast, List, Sequence
-import attr
+import dataclasses
from PyQt5.QtCore import pyqtSignal, QObject, QUrl
from qutebrowser.utils import (log, standarddir, jinja, objreg, utils,
@@ -198,15 +198,15 @@ class GreasemonkeyScript:
self._code = "\n".join([textwrap.indent(source, " "), self._code])
-@attr.s
+@dataclasses.dataclass
class MatchingScripts:
"""All userscripts registered to run on a particular url."""
- url = attr.ib()
- start = attr.ib(default=attr.Factory(list))
- end = attr.ib(default=attr.Factory(list))
- idle = attr.ib(default=attr.Factory(list))
+ 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)
class GreasemonkeyMatcher:
diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py
index f914f3085..5a22ad61f 100644
--- a/qutebrowser/browser/hints.py
+++ b/qutebrowser/browser/hints.py
@@ -29,7 +29,7 @@ from string import ascii_lowercase
from typing import (TYPE_CHECKING, Callable, Dict, Iterable, Iterator, List, Mapping,
MutableSequence, Optional, Sequence, Set)
-import attr
+import dataclasses
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, Qt, QUrl
from PyQt5.QtWidgets import QLabel
@@ -150,7 +150,7 @@ class HintLabel(QLabel):
self.deleteLater()
-@attr.s
+@dataclasses.dataclass
class HintContext:
"""Context namespace used for hinting.
@@ -181,20 +181,20 @@ class HintContext:
group: The group of web elements to hint.
"""
- all_labels: List[HintLabel] = attr.ib(attr.Factory(list))
- labels: Dict[str, HintLabel] = attr.ib(attr.Factory(dict))
- target: Target = attr.ib(None)
- baseurl: QUrl = attr.ib(None)
- to_follow: str = attr.ib(None)
- rapid: bool = attr.ib(False)
- first_run: bool = attr.ib(True)
- add_history: bool = attr.ib(False)
- filterstr: str = attr.ib(None)
- args: List[str] = attr.ib(attr.Factory(list))
- tab: 'browsertab.AbstractTab' = attr.ib(None)
- group: str = attr.ib(None)
- hint_mode: str = attr.ib(None)
- first: bool = attr.ib(False)
+ all_labels: List[HintLabel] = dataclasses.field(default_factory=list)
+ labels: Dict[str, HintLabel] = dataclasses.field(default_factory=dict)
+ target: Optional[Target] = None
+ baseurl: Optional[QUrl] = None
+ to_follow: Optional[str] = None
+ rapid: bool = False
+ first_run: bool = True
+ add_history: bool = False
+ filterstr: Optional[str] = None
+ args: List[str] = dataclasses.field(default_factory=list)
+ tab: Optional['browsertab.AbstractTab'] = None
+ group: Optional[str] = None
+ hint_mode: Optional[str] = None
+ first: bool = False
def get_args(self, urlstr: str) -> Sequence[str]:
"""Get the arguments, with {hint-url} replaced by the given URL."""
diff --git a/qutebrowser/browser/qtnetworkdownloads.py b/qutebrowser/browser/qtnetworkdownloads.py
index 3aebbb6dd..cad575a6e 100644
--- a/qutebrowser/browser/qtnetworkdownloads.py
+++ b/qutebrowser/browser/qtnetworkdownloads.py
@@ -24,11 +24,11 @@ import os.path
import shutil
import functools
from typing import Dict, IO, Optional
+import dataclasses
-import attr
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QTimer, QUrl
from PyQt5.QtWidgets import QApplication
-from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply
+from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply, QNetworkAccessManager
from qutebrowser.config import config, websettings
from qutebrowser.utils import message, usertypes, log, urlutils, utils, debug, objreg
@@ -38,11 +38,11 @@ from qutebrowser.browser.webkit import http
from qutebrowser.browser.webkit.network import networkmanager
-@attr.s
+@dataclasses.dataclass
class _RetryInfo:
- request = attr.ib()
- manager = attr.ib()
+ request: QNetworkRequest
+ manager: QNetworkAccessManager
class DownloadItem(downloads.AbstractDownloadItem):
diff --git a/qutebrowser/browser/webengine/interceptor.py b/qutebrowser/browser/webengine/interceptor.py
index 8804bea6e..98798983b 100644
--- a/qutebrowser/browser/webengine/interceptor.py
+++ b/qutebrowser/browser/webengine/interceptor.py
@@ -19,7 +19,8 @@
"""A request interceptor taking care of adblocking and custom headers."""
-import attr
+import dataclasses
+from typing import Optional
from PyQt5.QtCore import QUrl, QByteArray
from PyQt5.QtWebEngineCore import (QWebEngineUrlRequestInterceptor,
@@ -32,29 +33,29 @@ from qutebrowser.extensions import interceptors
from qutebrowser.misc import objects
-@attr.s
+@dataclasses.dataclass
class WebEngineRequest(interceptors.Request):
"""QtWebEngine-specific request interceptor functionality."""
_WHITELISTED_REQUEST_METHODS = {QByteArray(b'GET'), QByteArray(b'HEAD')}
- _webengine_info: QWebEngineUrlRequestInfo = attr.ib(default=None)
+ webengine_info: Optional[QWebEngineUrlRequestInfo] = None # FIXME private
#: If this request has been redirected already
- _redirected: bool = attr.ib(init=False, default=False)
+ _redirected: bool = dataclasses.field(init=False, default=False)
def redirect(self, url: QUrl) -> None:
if self._redirected:
raise interceptors.RedirectException("Request already redirected.")
- if self._webengine_info is None:
+ if self.webengine_info is None:
raise interceptors.RedirectException("Request improperly initialized.")
# Redirecting a request that contains payload data is not allowed.
# To be safe, abort on any request not in a whitelist.
- if (self._webengine_info.requestMethod()
+ if (self.webengine_info.requestMethod()
not in self._WHITELISTED_REQUEST_METHODS):
raise interceptors.RedirectException(
"Request method does not support redirection.")
- self._webengine_info.redirect(url)
+ self.webengine_info.redirect(url)
self._redirected = True
diff --git a/qutebrowser/browser/webkit/mhtml.py b/qutebrowser/browser/webkit/mhtml.py
index 794e1b73b..bf008d009 100644
--- a/qutebrowser/browser/webkit/mhtml.py
+++ b/qutebrowser/browser/webkit/mhtml.py
@@ -33,9 +33,9 @@ import email.encoders
import email.mime.multipart
import email.message
import quopri
-from typing import MutableMapping, Set, Tuple
+import dataclasses
+from typing import MutableMapping, Set, Tuple, Callable
-import attr
from PyQt5.QtCore import QUrl
from qutebrowser.browser import downloads
@@ -44,13 +44,13 @@ from qutebrowser.utils import log, objreg, message, usertypes, utils, urlutils
from qutebrowser.extensions import interceptors
-@attr.s
+@dataclasses.dataclass
class _File:
- content = attr.ib()
- content_type = attr.ib()
- content_location = attr.ib()
- transfer_encoding = attr.ib()
+ content: bytes
+ content_type: str
+ content_location: str
+ transfer_encoding: Callable[[email.message.Message], None]
_CSS_URL_PATTERNS = [re.compile(x) for x in [
diff --git a/qutebrowser/browser/webkit/network/networkmanager.py b/qutebrowser/browser/webkit/network/networkmanager.py
index b56518675..9eac7fea3 100644
--- a/qutebrowser/browser/webkit/network/networkmanager.py
+++ b/qutebrowser/browser/webkit/network/networkmanager.py
@@ -23,9 +23,10 @@ import collections
import html
from typing import TYPE_CHECKING, Dict, MutableMapping, Optional, Sequence
-import attr
+import dataclasses
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QUrl, QByteArray
-from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkReply, QSslSocket, QSslError
+from PyQt5.QtNetwork import (QNetworkAccessManager, QNetworkReply, QSslSocket,
+ QSslError, QNetworkProxy)
from qutebrowser.config import config
from qutebrowser.utils import (message, log, usertypes, utils, objreg,
@@ -46,14 +47,14 @@ HOSTBLOCK_ERROR_STRING = '%HOSTBLOCK%'
_proxy_auth_cache: Dict['ProxyId', 'prompt.AuthInfo'] = {}
-@attr.s(frozen=True)
+@dataclasses.dataclass(frozen=True)
class ProxyId:
"""Information identifying a proxy server."""
- type = attr.ib()
- hostname = attr.ib()
- port = attr.ib()
+ type: QNetworkProxy.ProxyType
+ hostname: str
+ port: int
def _is_secure_cipher(cipher):
diff --git a/qutebrowser/browser/webkit/rfc6266.py b/qutebrowser/browser/webkit/rfc6266.py
index b43bfb808..10c337ee1 100644
--- a/qutebrowser/browser/webkit/rfc6266.py
+++ b/qutebrowser/browser/webkit/rfc6266.py
@@ -22,8 +22,9 @@
import urllib.parse
import string
import re
+import dataclasses
+from typing import Optional
-import attr
import pypeg2 as peg
from qutebrowser.utils import utils
@@ -210,13 +211,13 @@ class ContentDispositionValue:
peg.optional(';'))
-@attr.s
+@dataclasses.dataclass
class LangTagged:
"""A string with an associated language."""
- string = attr.ib()
- langtag = attr.ib()
+ string: str
+ langtag: Optional[str]
class Error(Exception):
diff --git a/qutebrowser/commands/command.py b/qutebrowser/commands/command.py
index 61b44d555..d8d08e50b 100644
--- a/qutebrowser/commands/command.py
+++ b/qutebrowser/commands/command.py
@@ -23,9 +23,8 @@ import inspect
import collections
import traceback
import typing
-from typing import Any, MutableMapping, MutableSequence, Tuple, Union
-
-import attr
+from typing import Any, MutableMapping, MutableSequence, Tuple, Union, List, Optional
+import dataclasses
from qutebrowser.api import cmdutils
from qutebrowser.commands import cmdexc, argparser
@@ -34,17 +33,17 @@ from qutebrowser.utils import debug as debug_utils
from qutebrowser.misc import objects
-@attr.s
+@dataclasses.dataclass
class ArgInfo:
"""Information about an argument."""
- value = attr.ib(None)
- hide = attr.ib(False)
- metavar = attr.ib(None)
- flag = attr.ib(None)
- completion = attr.ib(None)
- choices = attr.ib(None)
+ value: Optional[usertypes.CommandValue] = None
+ hide: bool = False
+ metavar: Optional[str] = None
+ flag: Optional[str] = None
+ completion: Any = None # FIXME
+ choices: List[str] = None
class Command:
diff --git a/qutebrowser/commands/runners.py b/qutebrowser/commands/runners.py
index c195a8be9..4913f9e3e 100644
--- a/qutebrowser/commands/runners.py
+++ b/qutebrowser/commands/runners.py
@@ -22,9 +22,10 @@
import traceback
import re
import contextlib
-from typing import TYPE_CHECKING, Callable, Dict, Iterator, Mapping, MutableMapping
+from typing import (TYPE_CHECKING, Callable, Dict, Iterator, Mapping, MutableMapping,
+ List, Optional)
+import dataclasses
-import attr
from PyQt5.QtCore import pyqtSlot, QUrl, QObject
from qutebrowser.api import cmdutils
@@ -42,14 +43,14 @@ _ReplacementFunction = Callable[['tabbedbrowser.TabbedBrowser'], str]
last_command = {}
-@attr.s
+@dataclasses.dataclass
class ParseResult:
"""The result of parsing a commandline."""
- cmd = attr.ib()
- args = attr.ib()
- cmdline = attr.ib()
+ cmd: Optional[str]
+ args: Optional[List[str]]
+ cmdline: List[str]
def _url(tabbed_browser):
diff --git a/qutebrowser/completion/completer.py b/qutebrowser/completion/completer.py
index d66e3ee40..5b9ac2a76 100644
--- a/qutebrowser/completion/completer.py
+++ b/qutebrowser/completion/completer.py
@@ -19,7 +19,9 @@
"""Completer attached to a CompletionView."""
-import attr
+import dataclasses
+from typing import Any
+
from PyQt5.QtCore import pyqtSlot, QObject, QTimer
from qutebrowser.config import config
@@ -29,15 +31,15 @@ from qutebrowser.utils import log, utils, debug, objreg
from qutebrowser.completion.models import miscmodels
-@attr.s
+@dataclasses.dataclass
class CompletionInfo:
"""Context passed into all completion functions."""
- config = attr.ib()
- keyconf = attr.ib()
- win_id = attr.ib()
- cur_tab = attr.ib()
+ config: Any # FIXME
+ keyconf: Any # FIXME
+ win_id: int
+ cur_tab: Any # FIXME
class Completer(QObject):
diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py
index 065527bb9..14e1c30ef 100644
--- a/qutebrowser/config/configdata.py
+++ b/qutebrowser/config/configdata.py
@@ -28,7 +28,7 @@ from typing import (Any, Dict, Iterable, List, Mapping, MutableMapping, Optional
Sequence, Tuple, Union, cast)
import functools
-import attr
+import dataclasses
from qutebrowser.config import configtypes
from qutebrowser.utils import usertypes, qtutils, utils
from qutebrowser.misc import debugcachestats
@@ -39,7 +39,7 @@ MIGRATIONS = cast('Migrations', None)
_BackendDict = Mapping[str, Union[str, bool]]
-@attr.s
+@dataclasses.dataclass
class Option:
"""Description of an Option in the config.
@@ -47,18 +47,18 @@ class Option:
Note that this is just an option which exists, with no value associated.
"""
- name: str = attr.ib()
- typ: configtypes.BaseType = attr.ib()
- default: Any = attr.ib()
- backends: Iterable[usertypes.Backend] = attr.ib()
- raw_backends: Optional[Mapping[str, bool]] = attr.ib()
- description: str = attr.ib()
- supports_pattern: bool = attr.ib(default=False)
- restart: bool = attr.ib(default=False)
- no_autoconfig: bool = attr.ib(default=False)
+ name: str
+ typ: configtypes.BaseType
+ default: Any
+ backends: Iterable[usertypes.Backend]
+ raw_backends: Optional[Mapping[str, bool]]
+ description: str
+ supports_pattern: bool = False
+ restart: bool = False
+ no_autoconfig: bool = False
-@attr.s
+@dataclasses.dataclass
class Migrations:
"""Migrated options in configdata.yml.
@@ -68,8 +68,8 @@ class Migrations:
deleted: A list of option names which have been removed.
"""
- renamed: Dict[str, str] = attr.ib(default=attr.Factory(dict))
- deleted: List[str] = attr.ib(default=attr.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) -> None:
diff --git a/qutebrowser/config/configexc.py b/qutebrowser/config/configexc.py
index 872f777ff..c155510bd 100644
--- a/qutebrowser/config/configexc.py
+++ b/qutebrowser/config/configexc.py
@@ -21,7 +21,7 @@
from typing import Any, Mapping, Optional, Sequence, Union
-import attr
+import dataclasses
from qutebrowser.utils import usertypes, log
@@ -105,7 +105,7 @@ class NoOptionError(Error):
self.option = option
-@attr.s
+@dataclasses.dataclass
class ConfigErrorDesc:
"""A description of an error happening while reading the config.
@@ -116,9 +116,9 @@ class ConfigErrorDesc:
traceback: The formatted traceback of the exception.
"""
- text: str = attr.ib()
- exception: Union[str, Exception] = attr.ib()
- traceback: str = attr.ib(None)
+ text: str
+ exception: Union[str, Exception]
+ traceback: Optional[str] = None
def __str__(self) -> str:
if self.traceback:
diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py
index 6328c3140..cc3410648 100644
--- a/qutebrowser/config/configtypes.py
+++ b/qutebrowser/config/configtypes.py
@@ -53,7 +53,7 @@ import json
from typing import (Any, Callable, Dict as DictType, Iterable, Iterator,
List as ListType, Optional, Pattern, Sequence, Tuple, Union)
-import attr
+import dataclasses
import yaml
from PyQt5.QtCore import QUrl, Qt
from PyQt5.QtGui import QColor
@@ -1645,15 +1645,15 @@ class FuzzyUrl(BaseType):
raise configexc.ValidationError(value, str(e))
-@attr.s
+@dataclasses.dataclass
class PaddingValues:
"""Four padding values."""
- top: int = attr.ib()
- bottom: int = attr.ib()
- left: int = attr.ib()
- right: int = attr.ib()
+ top: int
+ bottom: int
+ left: int
+ right: int
class Padding(Dict):
diff --git a/qutebrowser/config/websettings.py b/qutebrowser/config/websettings.py
index e1cb393dc..5093c6185 100644
--- a/qutebrowser/config/websettings.py
+++ b/qutebrowser/config/websettings.py
@@ -24,7 +24,7 @@ import argparse
import functools
from typing import Any, Callable, Dict, Optional
-import attr
+import dataclasses
from PyQt5.QtCore import QUrl, pyqtSlot, qVersion
from PyQt5.QtGui import QFont
@@ -36,16 +36,16 @@ from qutebrowser.misc import objects, debugcachestats
UNSET = object()
-@attr.s
+@dataclasses.dataclass
class UserAgent:
"""A parsed user agent."""
- os_info: str = attr.ib()
- webkit_version: str = attr.ib()
- upstream_browser_key: str = attr.ib()
- upstream_browser_version: str = attr.ib()
- qt_key: str = attr.ib()
+ os_info: str
+ webkit_version: str
+ upstream_browser_key: str
+ upstream_browser_version: str
+ qt_key: str
@classmethod
def parse(cls, ua: str) -> 'UserAgent':
diff --git a/qutebrowser/extensions/interceptors.py b/qutebrowser/extensions/interceptors.py
index 9c50346d7..3d7f99deb 100644
--- a/qutebrowser/extensions/interceptors.py
+++ b/qutebrowser/extensions/interceptors.py
@@ -22,7 +22,7 @@
import enum
from typing import Callable, List, Optional
-import attr
+import dataclasses
from PyQt5.QtCore import QUrl
@@ -62,21 +62,21 @@ class RedirectException(Exception):
"""Raised when the request was invalid, or a request was already made."""
-@attr.s
+@dataclasses.dataclass
class Request:
"""A request which can be intercepted/blocked."""
#: The URL of the page being shown.
- first_party_url: Optional[QUrl] = attr.ib()
+ first_party_url: Optional[QUrl]
#: The URL of the file being requested.
- request_url: QUrl = attr.ib()
+ request_url: QUrl
- is_blocked: bool = attr.ib(False)
+ is_blocked: bool = False
#: The resource type of the request. None if not supported on this backend.
- resource_type: Optional[ResourceType] = attr.ib(None)
+ resource_type: Optional[ResourceType] = None
def block(self) -> None:
"""Block this request."""
diff --git a/qutebrowser/extensions/loader.py b/qutebrowser/extensions/loader.py
index b6d86f517..066d3b793 100644
--- a/qutebrowser/extensions/loader.py
+++ b/qutebrowser/extensions/loader.py
@@ -27,7 +27,7 @@ import importlib
import argparse
from typing import Callable, Iterator, List, Optional, Set, Tuple
-import attr
+import dataclasses
from PyQt5.QtCore import pyqtSlot
@@ -41,17 +41,17 @@ from qutebrowser.misc import objects
_module_infos = []
-@attr.s
+@dataclasses.dataclass
class InitContext:
"""Context an extension gets in its init hook."""
- data_dir: pathlib.Path = attr.ib()
- config_dir: pathlib.Path = attr.ib()
- args: argparse.Namespace = attr.ib()
+ data_dir: pathlib.Path
+ config_dir: pathlib.Path
+ args: argparse.Namespace
-@attr.s
+@dataclasses.dataclass
class ModuleInfo:
"""Information attached to an extension module.
@@ -61,17 +61,17 @@ class ModuleInfo:
_ConfigChangedHooksType = List[Tuple[Optional[str], Callable]]
- skip_hooks: bool = attr.ib(False)
- init_hook: Optional[Callable] = attr.ib(None)
- config_changed_hooks: _ConfigChangedHooksType = attr.ib(attr.Factory(list))
+ skip_hooks: bool = False
+ init_hook: Optional[Callable] = None
+ config_changed_hooks: _ConfigChangedHooksType = dataclasses.field(default_factory=list)
-@attr.s
+@dataclasses.dataclass
class ExtensionInfo:
"""Information about a qutebrowser extension."""
- name: str = attr.ib()
+ name: str
def add_module_info(module: types.ModuleType) -> ModuleInfo:
diff --git a/qutebrowser/keyinput/basekeyparser.py b/qutebrowser/keyinput/basekeyparser.py
index 23b77cba1..890effb17 100644
--- a/qutebrowser/keyinput/basekeyparser.py
+++ b/qutebrowser/keyinput/basekeyparser.py
@@ -23,7 +23,7 @@ import string
import types
from typing import Mapping, MutableMapping, Optional, Sequence
-import attr
+import dataclasses
from PyQt5.QtCore import pyqtSignal, QObject, Qt
from PyQt5.QtGui import QKeySequence, QKeyEvent
@@ -32,16 +32,16 @@ from qutebrowser.utils import usertypes, log, utils
from qutebrowser.keyinput import keyutils
-@attr.s(frozen=True)
+@dataclasses.dataclass(frozen=True)
class MatchResult:
"""The result of matching a keybinding."""
- match_type: QKeySequence.SequenceMatch = attr.ib()
- command: Optional[str] = attr.ib()
- sequence: keyutils.KeySequence = attr.ib()
+ match_type: QKeySequence.SequenceMatch
+ command: Optional[str]
+ sequence: keyutils.KeySequence
- def __attrs_post_init__(self) -> None:
+ def __post_init__(self) -> None:
if self.match_type == QKeySequence.ExactMatch:
assert self.command is not None
else:
diff --git a/qutebrowser/keyinput/keyutils.py b/qutebrowser/keyinput/keyutils.py
index bc8c4a5fe..f27703110 100644
--- a/qutebrowser/keyinput/keyutils.py
+++ b/qutebrowser/keyinput/keyutils.py
@@ -34,7 +34,7 @@ handle what we actually think we do.
import itertools
from typing import cast, overload, Iterable, Iterator, List, Mapping, Optional, Union
-import attr
+import dataclasses
from PyQt5.QtCore import Qt, QEvent
from PyQt5.QtGui import QKeySequence, QKeyEvent
@@ -336,7 +336,7 @@ def _parse_single_key(keystr: str) -> str:
return 'Shift+' + keystr if keystr.isupper() else keystr
-@attr.s(frozen=True)
+@dataclasses.dataclass(frozen=True)
class KeyInfo:
"""A key with optional modifiers.
@@ -346,8 +346,8 @@ class KeyInfo:
modifiers: A Qt::KeyboardModifiers enum value.
"""
- key: Qt.Key = attr.ib()
- modifiers: _ModifierType = attr.ib()
+ key: Qt.Key
+ modifiers: _ModifierType
@classmethod
def from_event(cls, e: QKeyEvent) -> 'KeyInfo':
diff --git a/qutebrowser/keyinput/modeman.py b/qutebrowser/keyinput/modeman.py
index df5f08e2c..c40aacdd6 100644
--- a/qutebrowser/keyinput/modeman.py
+++ b/qutebrowser/keyinput/modeman.py
@@ -22,7 +22,7 @@
import functools
from typing import Mapping, Callable, MutableMapping, Union, Set, cast
-import attr
+import dataclasses
from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QObject, QEvent
from PyQt5.QtGui import QKeyEvent
@@ -40,7 +40,7 @@ PROMPT_MODES = [usertypes.KeyMode.prompt, usertypes.KeyMode.yesno]
ParserDictType = MutableMapping[usertypes.KeyMode, basekeyparser.BaseKeyParser]
-@attr.s(frozen=True)
+@dataclasses.dataclass(frozen=True)
class KeyEvent:
"""A small wrapper over a QKeyEvent storing its data.
@@ -54,8 +54,8 @@ class KeyEvent:
text: A string (QKeyEvent::text).
"""
- key: Qt.Key = attr.ib()
- text: str = attr.ib()
+ key: Qt.Key
+ text: str
@classmethod
def from_event(cls, event: QKeyEvent) -> 'KeyEvent':
diff --git a/qutebrowser/mainwindow/prompt.py b/qutebrowser/mainwindow/prompt.py
index 85363a3fc..f214e7782 100644
--- a/qutebrowser/mainwindow/prompt.py
+++ b/qutebrowser/mainwindow/prompt.py
@@ -25,7 +25,7 @@ import collections
import functools
from typing import Deque, MutableSequence, Optional, cast
-import attr
+import dataclasses
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, Qt, QTimer, QDir, QModelIndex,
QItemSelectionModel, QObject, QEventLoop)
from PyQt5.QtWidgets import (QWidget, QGridLayout, QVBoxLayout, QLineEdit,
@@ -43,13 +43,13 @@ from qutebrowser.utils import urlmatch
prompt_queue = cast('PromptQueue', None)
-@attr.s
+@dataclasses.dataclass
class AuthInfo:
"""Authentication info returned by a prompt."""
- user = attr.ib()
- password = attr.ib()
+ user: str
+ password: str
class Error(Exception):
diff --git a/qutebrowser/mainwindow/statusbar/bar.py b/qutebrowser/mainwindow/statusbar/bar.py
index 821ea030b..cb059f682 100644
--- a/qutebrowser/mainwindow/statusbar/bar.py
+++ b/qutebrowser/mainwindow/statusbar/bar.py
@@ -20,7 +20,7 @@
"""The main statusbar widget."""
import enum
-import attr
+import dataclasses
from PyQt5.QtCore import (pyqtSignal, pyqtSlot, # type: ignore[attr-defined]
pyqtProperty, Qt, QSize, QTimer)
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QStackedLayout, QSizePolicy
@@ -34,7 +34,7 @@ from qutebrowser.mainwindow.statusbar import (backforward, command, progress,
tabindex, textbase)
-@attr.s
+@dataclasses.dataclass
class ColorFlags:
"""Flags which change the appearance of the statusbar.
@@ -56,12 +56,12 @@ class ColorFlags:
on = enum.auto()
selection = enum.auto()
- prompt = attr.ib(False)
- insert = attr.ib(False)
- command = attr.ib(False)
- caret = attr.ib(CaretMode.off)
- private = attr.ib(False)
- passthrough = attr.ib(False)
+ prompt: bool = False
+ insert: bool = False
+ command: bool = False
+ caret: CaretMode = CaretMode.off
+ private: bool = False
+ passthrough: bool = False
def to_stringlist(self):
"""Get a string list of set flags used in the stylesheet.
diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py
index 78eb864a6..33b5708d9 100644
--- a/qutebrowser/mainwindow/tabbedbrowser.py
+++ b/qutebrowser/mainwindow/tabbedbrowser.py
@@ -26,7 +26,7 @@ import datetime
from typing import (
Any, Deque, List, Mapping, MutableMapping, MutableSequence, Optional, Tuple)
-import attr
+import dataclasses
from PyQt5.QtWidgets import QSizePolicy, QWidget, QApplication
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QTimer, QUrl
@@ -39,16 +39,17 @@ from qutebrowser.utils import (log, usertypes, utils, qtutils, objreg,
from qutebrowser.misc import quitter
-@attr.s
+@dataclasses.dataclass
class _UndoEntry:
"""Information needed for :undo."""
- url = attr.ib()
- history = attr.ib()
- index = attr.ib()
- pinned = attr.ib()
- created_at = attr.ib(attr.Factory(datetime.datetime.now))
+ url: QUrl
+ history: bytes # FIXME
+ index: int
+ pinned: bool
+ created_at: datetime.datetime = dataclasses.field(
+ default_factory=datetime.datetime.now)
class TabDeque:
diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py
index 5263ecff9..7d1f0da0b 100644
--- a/qutebrowser/mainwindow/tabwidget.py
+++ b/qutebrowser/mainwindow/tabwidget.py
@@ -23,7 +23,7 @@ import functools
import contextlib
from typing import Optional, cast
-import attr
+import dataclasses
from PyQt5.QtCore import (pyqtSignal, pyqtSlot, Qt, QSize, QRect, QPoint,
QTimer, QUrl)
from PyQt5.QtWidgets import (QTabWidget, QTabBar, QSizePolicy, QCommonStyle,
@@ -712,7 +712,7 @@ class TabBar(QTabBar):
tabbed_browser.wheelEvent(e)
-@attr.s
+@dataclasses.dataclass
class Layouts:
"""Layout information for tab.
@@ -720,9 +720,9 @@ class Layouts:
Used by TabBarStyle._tab_layout().
"""
- text = attr.ib()
- icon = attr.ib()
- indicator = attr.ib()
+ text: QRect
+ icon: QRect
+ indicator: QRect
class TabBarStyle(QCommonStyle):
diff --git a/qutebrowser/mainwindow/windowundo.py b/qutebrowser/mainwindow/windowundo.py
index 34e1097c2..4335ad18d 100644
--- a/qutebrowser/mainwindow/windowundo.py
+++ b/qutebrowser/mainwindow/windowundo.py
@@ -20,10 +20,10 @@
"""Code for :undo --window."""
import collections
-from typing import MutableSequence, cast
+from typing import Any, MutableSequence, cast
-import attr
-from PyQt5.QtCore import QObject
+import dataclasses
+from PyQt5.QtCore import QObject, QByteArray
from qutebrowser.config import config
from qutebrowser.mainwindow import mainwindow
@@ -33,13 +33,13 @@ from qutebrowser.misc import objects
instance = cast('WindowUndoManager', None)
-@attr.s
+@dataclasses.dataclass
class _WindowUndoEntry:
"""Information needed for :undo -w."""
- geometry = attr.ib()
- tab_stack = attr.ib()
+ geometry: QByteArray
+ tab_stack: Any # FIXME
class WindowUndoManager(QObject):
diff --git a/qutebrowser/misc/backendproblem.py b/qutebrowser/misc/backendproblem.py
index fc8afe603..9f28fcef3 100644
--- a/qutebrowser/misc/backendproblem.py
+++ b/qutebrowser/misc/backendproblem.py
@@ -26,9 +26,9 @@ import html
import enum
import shutil
import argparse
-from typing import Any, List, Sequence, Tuple
+from typing import Any, List, Sequence, Tuple, Optional
-import attr
+import dataclasses
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (QDialog, QPushButton, QHBoxLayout, QVBoxLayout, QLabel,
QMessageBox, QWidget)
@@ -50,15 +50,15 @@ class _Result(enum.IntEnum):
restart_webengine = QDialog.Accepted + 4
-@attr.s
+@dataclasses.dataclass
class _Button:
"""A button passed to BackendProblemDialog."""
- text: str = attr.ib()
- setting: str = attr.ib()
- value: Any = attr.ib()
- default: bool = attr.ib(default=False)
+ text: str
+ setting: str
+ value: Any
+ default: bool = False
def _other_backend(backend: usertypes.Backend) -> Tuple[usertypes.Backend, str]:
@@ -150,15 +150,15 @@ class _Dialog(QDialog):
self.done(_Result.restart)
-@attr.s
+@dataclasses.dataclass
class _BackendImports:
"""Whether backend modules could be imported."""
- webkit_available: bool = attr.ib(default=None)
- webengine_available: bool = attr.ib(default=None)
- webkit_error: str = attr.ib(default=None)
- webengine_error: str = attr.ib(default=None)
+ webkit_available: Optional[bool] = None
+ webengine_available: Optional[bool] = None
+ webkit_error: Optional[str] = None
+ webengine_error: Optional[str] = None
class _BackendProblemChecker:
diff --git a/qutebrowser/misc/crashsignal.py b/qutebrowser/misc/crashsignal.py
index 1eaa608ba..47fb4c21b 100644
--- a/qutebrowser/misc/crashsignal.py
+++ b/qutebrowser/misc/crashsignal.py
@@ -29,9 +29,9 @@ import argparse
import functools
import threading
import faulthandler
-from typing import TYPE_CHECKING, Optional, MutableMapping, cast
+from typing import TYPE_CHECKING, Optional, MutableMapping, cast, List
+import dataclasses
-import attr
from PyQt5.QtCore import (pyqtSlot, qInstallMessageHandler, QObject,
QSocketNotifier, QTimer, QUrl)
from PyQt5.QtWidgets import QApplication
@@ -44,14 +44,14 @@ if TYPE_CHECKING:
from qutebrowser.misc import quitter
-@attr.s
+@dataclasses.dataclass
class ExceptionInfo:
"""Information stored when there was an exception."""
- pages = attr.ib()
- cmd_history = attr.ib()
- objects = attr.ib()
+ pages: List[List[str]]
+ cmd_history: List[str]
+ objects: str
crash_handler = cast('CrashHandler', None)
diff --git a/qutebrowser/misc/throttle.py b/qutebrowser/misc/throttle.py
index 3540d8824..777177ec8 100644
--- a/qutebrowser/misc/throttle.py
+++ b/qutebrowser/misc/throttle.py
@@ -22,17 +22,17 @@
import time
from typing import Any, Callable, Mapping, Optional, Sequence
-import attr
+import dataclasses
from PyQt5.QtCore import QObject
from qutebrowser.utils import usertypes
-@attr.s
+@dataclasses.dataclass
class _CallArgs:
- args: Sequence[Any] = attr.ib()
- kwargs: Mapping[str, Any] = attr.ib()
+ args: Sequence[Any]
+ kwargs: Mapping[str, Any]
class Throttle(QObject):
diff --git a/qutebrowser/utils/usertypes.py b/qutebrowser/utils/usertypes.py
index 893dae877..d180a6699 100644
--- a/qutebrowser/utils/usertypes.py
+++ b/qutebrowser/utils/usertypes.py
@@ -23,7 +23,7 @@ import operator
import enum
from typing import Any, Optional, Sequence, TypeVar, Union
-import attr
+import dataclasses
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QTimer
from PyQt5.QtCore import QUrl
@@ -496,7 +496,7 @@ class AbstractCertificateErrorWrapper:
raise NotImplementedError
-@attr.s
+@dataclasses.dataclass
class NavigationRequest:
"""A request to navigate to the given URL."""
@@ -526,7 +526,7 @@ class NavigationRequest:
#: None of the above.
other = 8
- url: QUrl = attr.ib()
- navigation_type: Type = attr.ib()
- is_main_frame: bool = attr.ib()
- accepted: bool = attr.ib(default=True)
+ url: QUrl
+ navigation_type: Type
+ is_main_frame: bool
+ accepted: bool = True
diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py
index 3640aef10..10b73f167 100644
--- a/qutebrowser/utils/version.py
+++ b/qutebrowser/utils/version.py
@@ -33,7 +33,7 @@ import getpass
import functools
from typing import Mapping, Optional, Sequence, Tuple, cast
-import attr
+import dataclasses
from PyQt5.QtCore import PYQT_VERSION_STR, QLibraryInfo
from PyQt5.QtNetwork import QSslSocket
from PyQt5.QtGui import (QOpenGLContext, QOpenGLVersionProfile,
@@ -76,15 +76,15 @@ _LOGO = r'''
'''
-@attr.s
+@dataclasses.dataclass
class DistributionInfo:
"""Information about the running distribution."""
- id: Optional[str] = attr.ib()
- parsed: 'Distribution' = attr.ib()
- version: Optional[utils.VersionNumber] = attr.ib()
- pretty: str = attr.ib()
+ id: Optional[str]
+ parsed: 'Distribution'
+ version: Optional[utils.VersionNumber]
+ pretty: str
pastebin_url = None
@@ -623,27 +623,27 @@ def version_info() -> str:
return '\n'.join(lines)
-@attr.s
+@dataclasses.dataclass
class OpenGLInfo:
"""Information about the OpenGL setup in use."""
# If we're using OpenGL ES. If so, no further information is available.
- gles: bool = attr.ib(False)
+ gles: bool = False
# The name of the vendor. Examples:
# - nouveau
# - "Intel Open Source Technology Center", "Intel", "Intel Inc."
- vendor: Optional[str] = attr.ib(None)
+ vendor: Optional[str] = None
# The OpenGL version as a string. See tests for examples.
- version_str: Optional[str] = attr.ib(None)
+ version_str: Optional[str] = None
# The parsed version as a (major, minor) tuple of ints
- version: Optional[Tuple[int, ...]] = attr.ib(None)
+ version: Optional[Tuple[int, ...]] = None
# The vendor specific information following the version number
- vendor_specific: Optional[str] = attr.ib(None)
+ vendor_specific: Optional[str] = None
def __str__(self) -> str:
if self.gles:
diff --git a/requirements.txt b/requirements.txt
index f253f0f9b..b8fab9abf 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,6 +3,7 @@
adblock==0.4.0
attrs==20.3.0
colorama==0.4.4
+dataclasses==0.6 ; python_version<"3.7"
importlib-resources==5.0.0 ; python_version<"3.9"
Jinja2==2.11.2
MarkupSafe==1.1.1
diff --git a/scripts/dev/check_coverage.py b/scripts/dev/check_coverage.py
index dae90d636..0074bfd56 100644
--- a/scripts/dev/check_coverage.py
+++ b/scripts/dev/check_coverage.py
@@ -27,7 +27,7 @@ import enum
import subprocess
from xml.etree import ElementTree
-import attr
+import dataclasses
sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir,
os.pardir))
@@ -36,14 +36,14 @@ from scripts import utils as scriptutils
from qutebrowser.utils import utils
-@attr.s
+@dataclasses.dataclass
class Message:
"""A message shown by coverage.py."""
- typ = attr.ib()
- filename = attr.ib()
- text = attr.ib()
+ typ: str
+ filename: str
+ text: str
def show(self):
"""Print this message."""
diff --git a/scripts/dev/get_coredumpctl_traces.py b/scripts/dev/get_coredumpctl_traces.py
index 31d70dd74..0b53e367a 100644
--- a/scripts/dev/get_coredumpctl_traces.py
+++ b/scripts/dev/get_coredumpctl_traces.py
@@ -27,7 +27,7 @@ import argparse
import subprocess
import tempfile
-import attr
+import dataclasses
sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir,
os.pardir))
@@ -35,18 +35,18 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir,
from scripts import utils
-@attr.s
+@dataclasses.dataclass
class Line:
"""A line in "coredumpctl list"."""
- time = attr.ib()
- pid = attr.ib()
- uid = attr.ib()
- gid = attr.ib()
- sig = attr.ib()
- present = attr.ib()
- exe = attr.ib()
+ time: str
+ pid: int
+ uid: int
+ gid: int
+ sig: int
+ present: bool
+ exe: str
def _convert_present(data):
diff --git a/scripts/dev/recompile_requirements.py b/scripts/dev/recompile_requirements.py
index e43a3111d..52ecf61f2 100644
--- a/scripts/dev/recompile_requirements.py
+++ b/scripts/dev/recompile_requirements.py
@@ -176,6 +176,7 @@ CHANGELOG_URLS = {
'adblock': 'https://github.com/ArniDagur/python-adblock/blob/master/CHANGELOG.md',
'pyPEG2': None,
'importlib-resources': 'https://importlib-resources.readthedocs.io/en/latest/history.html',
+ 'dataclasses': 'https://github.com/ericvsmith/dataclasses#release-history',
}
diff --git a/scripts/dictcli.py b/scripts/dictcli.py
index 4e38727dd..7c575a641 100755
--- a/scripts/dictcli.py
+++ b/scripts/dictcli.py
@@ -31,8 +31,8 @@ import os
import sys
import re
import urllib.request
-
-import attr
+import dataclasses
+from typing import Optional
sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir))
from qutebrowser.browser.webengine import spell
@@ -52,17 +52,17 @@ class InvalidLanguageError(Exception):
super().__init__(msg)
-@attr.s
+@dataclasses.dataclass
class Language:
"""Dictionary language specs."""
- code = attr.ib()
- name = attr.ib()
- remote_filename = attr.ib()
- local_filename = attr.ib(default=None)
+ code: str
+ name: str
+ remote_filename: str
+ local_filename: Optional[str] = None
- def __attrs_post_init__(self):
+ def __post_init__(self):
if self.local_filename is None:
self.local_filename = spell.local_filename(self.code)
diff --git a/tests/end2end/fixtures/testprocess.py b/tests/end2end/fixtures/testprocess.py
index 7a70e4de9..dca65088d 100644
--- a/tests/end2end/fixtures/testprocess.py
+++ b/tests/end2end/fixtures/testprocess.py
@@ -23,7 +23,7 @@ import re
import time
import warnings
-import attr
+import dataclasses
import pytest
import pytestqt.plugin
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, QProcess, QObject,
@@ -55,7 +55,7 @@ class BlacklistedMessageError(Exception):
"""Raised when ensure_not_logged found a message."""
-@attr.s
+@dataclasses.dataclass
class Line:
"""Container for a line of data the process emits.
@@ -65,8 +65,8 @@ class Line:
waited_for: If Process.wait_for was used on this line already.
"""
- data = attr.ib()
- waited_for = attr.ib(False)
+ data: str
+ waited_for: bool = False
def _render_log(data, *, verbose, threshold=100):
diff --git a/tests/end2end/fixtures/webserver.py b/tests/end2end/fixtures/webserver.py
index d40739724..e4ba6969c 100644
--- a/tests/end2end/fixtures/webserver.py
+++ b/tests/end2end/fixtures/webserver.py
@@ -26,7 +26,7 @@ import os.path
import socket
from http import HTTPStatus
-import attr
+import dataclasses
import pytest
from PyQt5.QtCore import pyqtSignal, QUrl
@@ -100,13 +100,13 @@ class Request(testprocess.Line):
return NotImplemented
-@attr.s(frozen=True, eq=False, hash=True)
+@dataclasses.dataclass(frozen=True)
class ExpectedRequest:
"""Class to compare expected requests easily."""
- verb = attr.ib()
- path = attr.ib()
+ verb: str
+ path: int
@classmethod
def from_request(cls, request):
diff --git a/tests/end2end/test_dirbrowser.py b/tests/end2end/test_dirbrowser.py
index 7b13c678e..8a91fe494 100644
--- a/tests/end2end/test_dirbrowser.py
+++ b/tests/end2end/test_dirbrowser.py
@@ -21,8 +21,9 @@
"""Test the built-in directory browser."""
import os
+import dataclasses
+from typing import List
-import attr
import pytest
import bs4
@@ -104,21 +105,21 @@ class DirLayout:
return os.path.normpath(str(self.base))
-@attr.s
+@dataclasses.dataclass
class Parsed:
- path = attr.ib()
- parent = attr.ib()
- folders = attr.ib()
- files = attr.ib()
+ path: str
+ parent: str
+ folders: List[str]
+ files: List[str]
-@attr.s
+@dataclasses.dataclass
class Item:
- path = attr.ib()
- link = attr.ib()
- text = attr.ib()
+ path: str
+ link: str
+ text: str
def parse(quteproc):
diff --git a/tests/end2end/test_hints_html.py b/tests/end2end/test_hints_html.py
index 6d3bed175..14868590c 100644
--- a/tests/end2end/test_hints_html.py
+++ b/tests/end2end/test_hints_html.py
@@ -22,8 +22,9 @@
import os
import os.path
import textwrap
+import dataclasses
+from typing import Optional
-import attr
import pytest
import bs4
@@ -37,11 +38,11 @@ def collect_tests():
return files
-@attr.s
+@dataclasses.dataclass
class ParsedFile:
- target = attr.ib()
- qtwebengine_todo = attr.ib()
+ target: str
+ qtwebengine_todo: Optional[str]
class InvalidFile(Exception):
diff --git a/tests/helpers/fixtures.py b/tests/helpers/fixtures.py
index b814a6ea7..c62c9960b 100644
--- a/tests/helpers/fixtures.py
+++ b/tests/helpers/fixtures.py
@@ -34,7 +34,7 @@ import types
import mimetypes
import os.path
-import attr
+import dataclasses
import pytest
import py.path # pylint: disable=no-name-in-module
from PyQt5.QtCore import QSize, Qt
@@ -87,12 +87,12 @@ class WinRegistryHelper:
"""Helper class for win_registry."""
- @attr.s
+ @dataclasses.dataclass
class FakeWindow:
"""A fake window object for the registry."""
- registry = attr.ib()
+ registry: objreg.ObjectRegistry
def windowTitle(self):
return 'window title - qutebrowser'
@@ -276,11 +276,11 @@ def web_tab(request):
def _generate_cmdline_tests():
"""Generate testcases for test_split_binding."""
- @attr.s
+ @dataclasses.dataclass
class TestCase:
- cmd = attr.ib()
- valid = attr.ib()
+ cmd: str
+ valid: bool
separators = [';;', ' ;; ', ';; ', ' ;;']
invalid = ['foo', '']
diff --git a/tests/helpers/messagemock.py b/tests/helpers/messagemock.py
index 03320a98f..72f170408 100644
--- a/tests/helpers/messagemock.py
+++ b/tests/helpers/messagemock.py
@@ -21,19 +21,19 @@
import logging
-import attr
+import dataclasses
import pytest
from qutebrowser.utils import usertypes, message
-@attr.s
+@dataclasses.dataclass
class Message:
"""Information about a shown message."""
- level = attr.ib()
- text = attr.ib()
+ level: usertypes.MessageLevel
+ text: str
class MessageMock:
diff --git a/tests/helpers/stubs.py b/tests/helpers/stubs.py
index 997f160ce..0e4dd3b92 100644
--- a/tests/helpers/stubs.py
+++ b/tests/helpers/stubs.py
@@ -21,11 +21,12 @@
"""Fake objects/stubs."""
+from typing import Any, Callable, Tuple
from unittest import mock
import contextlib
import shutil
+import dataclasses
-import attr
from PyQt5.QtCore import pyqtSignal, QPoint, QProcess, QObject, QUrl
from PyQt5.QtGui import QIcon
from PyQt5.QtNetwork import (QNetworkRequest, QAbstractNetworkCache,
@@ -324,20 +325,20 @@ class FakeSignal:
"""
-@attr.s(frozen=True)
+@dataclasses.dataclass(frozen=True)
class FakeCommand:
"""A simple command stub which has a description."""
- name = attr.ib('')
- desc = attr.ib('')
- hide = attr.ib(False)
- debug = attr.ib(False)
- deprecated = attr.ib(False)
- completion = attr.ib(None)
- maxsplit = attr.ib(None)
- takes_count = attr.ib(lambda: False)
- modes = attr.ib((usertypes.KeyMode.normal, ))
+ name: str = ''
+ desc: str = ''
+ hide: bool = False
+ debug: bool = False
+ deprecated: bool = False
+ completion: Any = None
+ maxsplit: int = None
+ takes_count: Callable[[], bool] = lambda: False
+ modes: Tuple[usertypes.KeyMode] = (usertypes.KeyMode.normal, )
class FakeTimer(QObject):
diff --git a/tests/unit/browser/test_signalfilter.py b/tests/unit/browser/test_signalfilter.py
index 0e45026be..239f7e0de 100644
--- a/tests/unit/browser/test_signalfilter.py
+++ b/tests/unit/browser/test_signalfilter.py
@@ -21,7 +21,7 @@
import logging
-import attr
+import dataclasses
import pytest
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject
@@ -46,11 +46,11 @@ class Signaller(QObject):
self.filtered_signal_arg = s
-@attr.s
+@dataclasses.dataclass
class Objects:
- signal_filter = attr.ib()
- signaller = attr.ib()
+ signal_filter: signalfilter.SignalFilter
+ signaller: Signaller
@pytest.fixture
diff --git a/tests/unit/browser/webkit/network/test_filescheme.py b/tests/unit/browser/webkit/network/test_filescheme.py
index adcd1a7e5..5136fcf45 100644
--- a/tests/unit/browser/webkit/network/test_filescheme.py
+++ b/tests/unit/browser/webkit/network/test_filescheme.py
@@ -19,8 +19,9 @@
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
import os
+import dataclasses
+from typing import List
-import attr
import pytest
import bs4
from PyQt5.QtCore import QUrl
@@ -111,18 +112,18 @@ def _file_url(path):
class TestDirbrowserHtml:
- @attr.s
+ @dataclasses.dataclass
class Parsed:
- parent = attr.ib()
- folders = attr.ib()
- files = attr.ib()
+ parent: str
+ folders: List[str]
+ files: List[str]
- @attr.s
+ @dataclasses.dataclass
class Item:
- link = attr.ib()
- text = attr.ib()
+ link: str
+ text: str
@pytest.fixture
def parser(self):
diff --git a/tests/unit/browser/webkit/test_tabhistory.py b/tests/unit/browser/webkit/test_tabhistory.py
index 48e0c98fc..bf3ea2be4 100644
--- a/tests/unit/browser/webkit/test_tabhistory.py
+++ b/tests/unit/browser/webkit/test_tabhistory.py
@@ -19,7 +19,9 @@
"""Tests for webelement.tabhistory."""
-import attr
+import dataclasses
+from typing import Any
+
from PyQt5.QtCore import QUrl, QPoint
import pytest
@@ -50,11 +52,11 @@ ITEMS = [
]
-@attr.s
+@dataclasses.dataclass
class Objects:
- history = attr.ib()
- user_data = attr.ib()
+ history: Any # FIXME
+ user_data: Any # FIXME
@pytest.fixture
diff --git a/tests/unit/browser/webkit/test_webkitelem.py b/tests/unit/browser/webkit/test_webkitelem.py
index 37262a7b3..f12df66f8 100644
--- a/tests/unit/browser/webkit/test_webkitelem.py
+++ b/tests/unit/browser/webkit/test_webkitelem.py
@@ -19,12 +19,13 @@
"""Tests for the webelement utils."""
+from typing import TYPE_CHECKING
from unittest import mock
import collections.abc
import operator
import itertools
-import attr
+import dataclasses
import pytest
from PyQt5.QtCore import QRect, QPoint, QUrl
QWebElement = pytest.importorskip('PyQt5.QtWebKit').QWebElement
@@ -33,6 +34,8 @@ from qutebrowser.browser import browsertab
from qutebrowser.browser.webkit import webkitelem
from qutebrowser.misc import objects
from qutebrowser.utils import usertypes
+if TYPE_CHECKING:
+ from helpers import stubs
def get_webelem(geometry=None, frame=None, *, null=False, style=None,
@@ -527,12 +530,12 @@ class TestIsVisibleIframe:
elem1-elem4: FakeWebElements to test.
"""
- @attr.s
+ @dataclasses.dataclass
class Objects:
- frame = attr.ib()
- iframe = attr.ib()
- elems = attr.ib()
+ frame: 'stubs.FakeWebFrame'
+ iframe: 'stubs.FakeWebFrame'
+ elems: webkitelem.WebKitElement
@pytest.fixture
def objects(self, stubs):
diff --git a/tests/unit/config/test_configtypes.py b/tests/unit/config/test_configtypes.py
index 710018604..40ba3a3f3 100644
--- a/tests/unit/config/test_configtypes.py
+++ b/tests/unit/config/test_configtypes.py
@@ -26,7 +26,7 @@ import warnings
import inspect
import functools
-import attr
+import dataclasses
import pytest
import hypothesis
from hypothesis import strategies
@@ -1350,14 +1350,14 @@ class TestQssColor:
klass().to_py(val)
-@attr.s
+@dataclasses.dataclass
class FontDesc:
- style = attr.ib()
- weight = attr.ib()
- pt = attr.ib()
- px = attr.ib()
- family = attr.ib()
+ style: QFont.Style
+ weight: QFont.Weight
+ pt: int
+ px: int
+ family: str
class TestFont:
diff --git a/tests/unit/keyinput/key_data.py b/tests/unit/keyinput/key_data.py
index 7675ee6dd..016854ab5 100644
--- a/tests/unit/keyinput/key_data.py
+++ b/tests/unit/keyinput/key_data.py
@@ -23,11 +23,13 @@
"""Data used by test_keyutils.py to test all keys."""
-import attr
+import dataclasses
+from typing import Optional
+
from PyQt5.QtCore import Qt
-@attr.s
+@dataclasses.dataclass
class Key:
"""A key with expected values.
@@ -40,21 +42,21 @@ class Key:
member: The numeric value.
"""
- attribute = attr.ib()
- name = attr.ib(None)
- text = attr.ib('')
- uppertext = attr.ib('')
- member = attr.ib(None)
- qtest = attr.ib(True)
+ attribute: str
+ name: Optional[str] = None
+ text: str = ''
+ uppertext: str = ''
+ member: Optional[int] = None
+ qtest: bool = True
- def __attrs_post_init__(self):
+ def __post_init__(self):
if self.attribute:
self.member = getattr(Qt, 'Key_' + self.attribute, None)
if self.name is None:
self.name = self.attribute
-@attr.s
+@dataclasses.dataclass
class Modifier:
"""A modifier with expected values.
@@ -66,11 +68,11 @@ class Modifier:
member: The numeric value.
"""
- attribute = attr.ib()
- name = attr.ib(None)
- member = attr.ib(None)
+ attribute: str
+ name: Optional[str] = None
+ member: Optional[int] = None
- def __attrs_post_init__(self):
+ def __post_init__(self):
self.member = getattr(Qt, self.attribute + 'Modifier')
if self.name is None:
self.name = self.attribute
diff --git a/tests/unit/misc/test_ipc.py b/tests/unit/misc/test_ipc.py
index 3f53ca238..6f77b7ec1 100644
--- a/tests/unit/misc/test_ipc.py
+++ b/tests/unit/misc/test_ipc.py
@@ -25,9 +25,10 @@ import getpass
import logging
import json
import hashlib
+import dataclasses
from unittest import mock
+from typing import Optional, List
-import attr
import pytest
from PyQt5.QtCore import pyqtSignal, QObject
from PyQt5.QtNetwork import QLocalServer, QLocalSocket, QAbstractSocket
@@ -616,13 +617,13 @@ def test_ipcserver_socket_none_error(ipc_server, caplog):
class TestSendOrListen:
- @attr.s
+ @dataclasses.dataclass
class Args:
- no_err_windows = attr.ib()
- basedir = attr.ib()
- command = attr.ib()
- target = attr.ib()
+ no_err_windows: bool
+ basedir: str
+ command: List[str]
+ target: Optional[str]
@pytest.fixture
def args(self):
diff --git a/tests/unit/misc/test_split.py b/tests/unit/misc/test_split.py
index 77dde75bc..e6bc3cc0c 100644
--- a/tests/unit/misc/test_split.py
+++ b/tests/unit/misc/test_split.py
@@ -19,7 +19,9 @@
"""Tests for qutebrowser.misc.split."""
-import attr
+import dataclasses
+from typing import List
+
import pytest
from qutebrowser.misc import split
@@ -109,12 +111,12 @@ def _parse_split_test_data_str():
Returns:
A list of TestCase objects with str attributes: inp, keep, no_keep
"""
- @attr.s
+ @dataclasses.dataclass
class TestCase:
- inp = attr.ib()
- keep = attr.ib()
- no_keep = attr.ib()
+ inp: str
+ keep: List[str]
+ no_keep: List[str]
for line in test_data_str.splitlines():
if not line:
diff --git a/tests/unit/misc/userscripts/test_qute_lastpass.py b/tests/unit/misc/userscripts/test_qute_lastpass.py
index 229fcf09e..cd4cf7096 100644
--- a/tests/unit/misc/userscripts/test_qute_lastpass.py
+++ b/tests/unit/misc/userscripts/test_qute_lastpass.py
@@ -23,7 +23,7 @@ import json
from types import SimpleNamespace
from unittest.mock import ANY, call
-import attr
+import dataclasses
import pytest
from helpers import utils
@@ -41,10 +41,15 @@ default_lpass_match = [
]
-@attr.s
+@dataclasses.dataclass
class FakeOutput:
- stdout = attr.ib(default='', converter=str.encode)
- stderr = attr.ib(default='', converter=str.encode)
+ stdout: str = ''
+ stderr: str = ''
+
+ def __post_init__(self):
+ # FIXME
+ self.stdout = self.stdout.encode()
+ self.stderr = self.stderr.encode()
@pytest.fixture
diff --git a/tests/unit/utils/test_javascript.py b/tests/unit/utils/test_javascript.py
index fc8267435..b1f64c95c 100644
--- a/tests/unit/utils/test_javascript.py
+++ b/tests/unit/utils/test_javascript.py
@@ -22,17 +22,17 @@
import pytest
import hypothesis
import hypothesis.strategies
-import attr
+import dataclasses
from qutebrowser.utils import javascript, usertypes
-@attr.s
+@dataclasses.dataclass
class Case:
- original = attr.ib()
- replacement = attr.ib()
- webkit_only = attr.ib(False)
+ original: str
+ replacement: str
+ webkit_only: bool = False
def __str__(self):
return self.original
diff --git a/tests/unit/utils/test_log.py b/tests/unit/utils/test_log.py
index 8e8fa47a4..fb722c054 100644
--- a/tests/unit/utils/test_log.py
+++ b/tests/unit/utils/test_log.py
@@ -25,7 +25,7 @@ import itertools
import sys
import warnings
-import attr
+import dataclasses
import pytest
import _pytest.logging
from PyQt5 import QtCore
@@ -412,15 +412,15 @@ def test_warning_still_errors():
class TestQtMessageHandler:
- @attr.s
+ @dataclasses.dataclass
class Context:
"""Fake QMessageLogContext."""
- function = attr.ib(default=None)
- category = attr.ib(default=None)
- file = attr.ib(default=None)
- line = attr.ib(default=None)
+ function: str = None
+ category: str = None
+ file: str = None
+ line: int = None
@pytest.fixture(autouse=True)
def init_args(self):
diff --git a/tests/unit/utils/test_qtutils.py b/tests/unit/utils/test_qtutils.py
index e68136695..7a953542a 100644
--- a/tests/unit/utils/test_qtutils.py
+++ b/tests/unit/utils/test_qtutils.py
@@ -26,7 +26,7 @@ import os.path
import unittest
import unittest.mock
-import attr
+import dataclasses
import pytest
from PyQt5.QtCore import (QDataStream, QPoint, QUrl, QByteArray, QIODevice,
QTimer, QBuffer, QFile, QProcess, QFileDevice)
@@ -953,11 +953,11 @@ class Color(QColor):
class TestInterpolateColor:
- @attr.s
+ @dataclasses.dataclass
class Colors:
- white = attr.ib()
- black = attr.ib()
+ white: Color
+ black: Color
@pytest.fixture
def colors(self):
diff --git a/tests/unit/utils/test_urlutils.py b/tests/unit/utils/test_urlutils.py
index 13c94d00e..456e6b0b0 100644
--- a/tests/unit/utils/test_urlutils.py
+++ b/tests/unit/utils/test_urlutils.py
@@ -23,7 +23,7 @@ import os.path
import logging
import urllib.parse
-import attr
+import dataclasses
from PyQt5.QtCore import QUrl
from PyQt5.QtNetwork import QNetworkProxy
import pytest
@@ -50,10 +50,10 @@ class FakeDNS:
when fromname_mock is called.
"""
- @attr.s
+ @dataclasses.dataclass
class FakeDNSAnswer:
- error = attr.ib()
+ error: bool
def __init__(self):
self.used = False
@@ -350,14 +350,14 @@ def test_get_search_url_invalid(url):
urlutils._get_search_url(url)
-@attr.s
+@dataclasses.dataclass
class UrlParams:
- url = attr.ib()
- is_url = attr.ib(True)
- is_url_no_autosearch = attr.ib(True)
- use_dns = attr.ib(True)
- is_url_in_schemeless = attr.ib(False)
+ url: QUrl
+ is_url: bool = True
+ is_url_no_autosearch: bool = True
+ use_dns: bool = True
+ is_url_in_schemeless: bool = False
@pytest.mark.parametrize('auto_search',
diff --git a/tests/unit/utils/test_version.py b/tests/unit/utils/test_version.py
index 7633cd92b..042d58b0d 100644
--- a/tests/unit/utils/test_version.py
+++ b/tests/unit/utils/test_version.py
@@ -32,7 +32,7 @@ import logging
import textwrap
import datetime
-import attr
+import dataclasses
import pytest
import hypothesis
import hypothesis.strategies
@@ -943,18 +943,18 @@ class TestChromiumVersion:
assert version._chromium_version() == 'avoided'
-@attr.s
+@dataclasses.dataclass
class VersionParams:
- name = attr.ib()
- git_commit = attr.ib(True)
- frozen = attr.ib(False)
- qapp = attr.ib(True)
- with_webkit = attr.ib(True)
- known_distribution = attr.ib(True)
- ssl_support = attr.ib(True)
- autoconfig_loaded = attr.ib(True)
- config_py_loaded = attr.ib(True)
+ name: str
+ git_commit: bool = True
+ frozen: bool = False
+ qapp: bool = True
+ with_webkit: bool = True
+ known_distribution: bool = True
+ ssl_support: bool = True
+ autoconfig_loaded: bool = True
+ config_py_loaded: bool = True
@pytest.mark.parametrize('params', [