summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Bruhin <me@the-compiler.org>2023-11-20 11:18:19 +0100
committerFlorian Bruhin <me@the-compiler.org>2023-11-20 11:18:19 +0100
commit4c08a3393cff1f39e779979abeec72db7f1a3d6d (patch)
treed664595f4ba4d86160695364da7025f7349391d0
parent3759738f52bb0b15c740e302294aaf95d687c39d (diff)
downloadqutebrowser-4c08a3393cff1f39e779979abeec72db7f1a3d6d.tar.gz
qutebrowser-4c08a3393cff1f39e779979abeec72db7f1a3d6d.zip
Fix/improve typing for qtutils.savefile_open
Contrary to what I thought at the time when initially writing this, typing.AnyStr isn't just an alias for IO[str] | IO[bytes], but is actually a TypeVar. As per the Python docs, it should be used when there are *multiple* places where the types need to match: def concat(a: AnyStr, b: AnyStr) -> AnyStr: return a + b What we do instead is somewhat akin to "def fun() -> T:", which mypy already comments on: error: A function returning TypeVar should receive at least one argument containing the same TypeVar [type-var] def t() -> T: Not quite sure why it doesn't in this case, or why it now raises an additional error (possibly the new inferrence code or something?). Either way, with this commit the annotations are now more correctly using Union[IO[str], IO[bytes]], including typing.Literal overloads so that mypy actually knows what specific type will be returned by a call.
-rw-r--r--qutebrowser/utils/qtutils.py28
1 files changed, 24 insertions, 4 deletions
diff --git a/qutebrowser/utils/qtutils.py b/qutebrowser/utils/qtutils.py
index 2c103c6b8..363d5607a 100644
--- a/qutebrowser/utils/qtutils.py
+++ b/qutebrowser/utils/qtutils.py
@@ -18,8 +18,8 @@ import enum
import pathlib
import operator
import contextlib
-from typing import (Any, AnyStr, TYPE_CHECKING, BinaryIO, IO, Iterator,
- Optional, Union, Tuple, Protocol, cast, TypeVar)
+from typing import (Any, TYPE_CHECKING, BinaryIO, IO, Iterator, Literal,
+ Optional, Union, Tuple, Protocol, cast, overload, TypeVar)
from qutebrowser.qt import machinery, sip
from qutebrowser.qt.core import (qVersion, QEventLoop, QDataStream, QByteArray,
@@ -236,12 +236,32 @@ def deserialize_stream(stream: QDataStream, obj: _QtSerializableType) -> None:
check_qdatastream(stream)
+@overload
+@contextlib.contextmanager
+def savefile_open(
+ filename: str,
+ binary: Literal[False] = ...,
+ encoding: str = 'utf-8'
+) -> Iterator[IO[str]]:
+ ...
+
+
+@overload
+@contextlib.contextmanager
+def savefile_open(
+ filename: str,
+ binary: Literal[True] = ...,
+ encoding: str = 'utf-8'
+) -> Iterator[IO[str]]:
+ ...
+
+
@contextlib.contextmanager
def savefile_open(
filename: str,
binary: bool = False,
encoding: str = 'utf-8'
-) -> Iterator[IO[AnyStr]]:
+) -> Iterator[Union[IO[str], IO[bytes]]]:
"""Context manager to easily use a QSaveFile."""
f = QSaveFile(filename)
cancelled = False
@@ -253,7 +273,7 @@ def savefile_open(
dev = cast(BinaryIO, PyQIODevice(f))
if binary:
- new_f: IO[Any] = dev # FIXME:mypy Why doesn't AnyStr work?
+ new_f: Union[IO[str], IO[bytes]] = dev
else:
new_f = io.TextIOWrapper(dev, encoding=encoding)