diff options
author | Florian Bruhin <me@the-compiler.org> | 2021-01-11 22:22:30 +0100 |
---|---|---|
committer | Florian Bruhin <me@the-compiler.org> | 2021-01-13 20:26:41 +0100 |
commit | c970c6335521fee359c1a68c0431c612631e73fb (patch) | |
tree | 1062d9ffd9fbfcafc0753308b8e7a15a5f5f6920 | |
parent | 4b7d52ae7cdf52ebef038b4a90b9fe95ab002105 (diff) | |
download | qutebrowser-c970c6335521fee359c1a68c0431c612631e73fb.tar.gz qutebrowser-c970c6335521fee359c1a68c0431c612631e73fb.zip |
dataclasses: Initial switch
See #6023
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', [ |