Continue type annotations for utils.utils

This commit is contained in:
Florian Bruhin 2019-10-11 16:58:26 +02:00
parent ef88663ecd
commit 28a2e44bd3
2 changed files with 71 additions and 46 deletions

View File

@ -161,3 +161,7 @@ disallow_incomplete_defs = True
[mypy-qutebrowser.utils.usertypes]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.utils.utils]
disallow_untyped_defs = True
disallow_incomplete_defs = True

View File

@ -35,6 +35,7 @@ import socket
import shlex
import glob
import mimetypes
import typing
from PyQt5.QtCore import QUrl
from PyQt5.QtGui import QColor, QClipboard, QDesktopServices
@ -88,7 +89,7 @@ class SelectionUnsupportedError(ClipboardError):
"""Raised if [gs]et_clipboard is used and selection=True is unsupported."""
def __init__(self):
def __init__(self) -> None:
super().__init__("Primary selection is not supported on this "
"platform!")
@ -98,7 +99,7 @@ class ClipboardEmptyError(ClipboardError):
"""Raised if get_clipboard is used and the clipboard is empty."""
def elide(text, length):
def elide(text: str, length: int) -> str:
"""Elide text so it uses a maximum of length chars."""
if length < 1:
raise ValueError("length must be >= 1!")
@ -108,7 +109,7 @@ def elide(text, length):
return text[:length - 1] + '\u2026'
def elide_filename(filename, length):
def elide_filename(filename: str, length: int) -> str:
"""Elide a filename to the given length.
The difference to the elide() is that the text is removed from
@ -140,7 +141,7 @@ def elide_filename(filename, length):
return filename[:left] + elidestr + filename[-right:]
def compact_text(text, elidelength=None):
def compact_text(text: str, elidelength: int = None) -> str:
"""Remove leading whitespace and newlines from a text and maybe elide it.
Args:
@ -156,7 +157,7 @@ def compact_text(text, elidelength=None):
return out
def preload_resources():
def preload_resources() -> None:
"""Load resource files into the cache."""
for subdir, pattern in [('html', '*.html'), ('javascript', '*.js')]:
path = resource_filename(subdir)
@ -165,7 +166,7 @@ def preload_resources():
_resource_cache[sub_path] = read_file(sub_path)
def read_file(filename, binary=False):
def read_file(filename: str, binary: bool = False) -> typing.Union[str, bytes]:
"""Get the contents of a file contained with qutebrowser.
Args:
@ -187,19 +188,22 @@ def read_file(filename, binary=False):
# https://github.com/pyinstaller/pyinstaller/wiki/FAQ#misc
fn = os.path.join(os.path.dirname(sys.executable), filename)
if binary:
with open(fn, 'rb') as f:
with open(fn, 'rb') as f: # type: typing.IO
return f.read()
else:
with open(fn, 'r', encoding='utf-8') as f:
return f.read()
else:
data = pkg_resources.resource_string(qutebrowser.__name__, filename)
if not binary:
data = data.decode('UTF-8')
return data
data = pkg_resources.resource_string(
qutebrowser.__name__, filename)
if binary:
return data
return data.decode('UTF-8')
def resource_filename(filename):
def resource_filename(filename: str) -> str:
"""Get the absolute filename of a file contained with qutebrowser.
Args:
@ -213,7 +217,9 @@ def resource_filename(filename):
return pkg_resources.resource_filename(qutebrowser.__name__, filename)
def _get_color_percentage(a_c1, a_c2, a_c3, b_c1, b_c2, b_c3, percent):
def _get_color_percentage(a_c1: int, a_c2: int, a_c3:
int, b_c1: int, b_c2: int, b_c3: int,
percent: int) -> typing.Tuple[int, int, int]:
"""Get a color which is percent% interpolated between start and end.
Args:
@ -234,7 +240,12 @@ def _get_color_percentage(a_c1, a_c2, a_c3, b_c1, b_c2, b_c3, percent):
return (out_c1, out_c2, out_c3)
def interpolate_color(start, end, percent, colorspace=QColor.Rgb):
def interpolate_color(
start: QColor,
end: QColor,
percent: int,
colorspace: typing.Optional[QColor.Spec] = QColor.Rgb
) -> QColor:
"""Get an interpolated color value.
Args:
@ -283,7 +294,7 @@ def interpolate_color(start, end, percent, colorspace=QColor.Rgb):
return out
def format_seconds(total_seconds):
def format_seconds(total_seconds: int) -> str:
"""Format a count of seconds to get a [H:]M:SS string."""
prefix = '-' if total_seconds < 0 else ''
hours, rem = divmod(abs(round(total_seconds)), 3600)
@ -299,7 +310,9 @@ def format_seconds(total_seconds):
return prefix + ':'.join(chunks)
def format_size(size, base=1024, suffix=''):
def format_size(size: typing.Optional[float],
base: int = 1024,
suffix: str = '') -> str:
"""Format a byte size so it's human readable.
Inspired by http://stackoverflow.com/q/1094841
@ -318,13 +331,13 @@ class FakeIOStream(io.TextIOBase):
"""A fake file-like stream which calls a function for write-calls."""
def __init__(self, write_func):
def __init__(self, write_func: typing.Callable[[str], int]) -> None:
super().__init__()
self.write = write_func
self.write = write_func # type: ignore
@contextlib.contextmanager
def fake_io(write_func):
def fake_io(write_func: typing.Callable[[str], int]) -> typing.Iterator[None]:
"""Run code with stdout and stderr replaced by FakeIOStreams.
Args:
@ -334,21 +347,21 @@ def fake_io(write_func):
old_stderr = sys.stderr
fake_stderr = FakeIOStream(write_func)
fake_stdout = FakeIOStream(write_func)
sys.stderr = fake_stderr
sys.stdout = fake_stdout
sys.stderr = fake_stderr # type: ignore
sys.stdout = fake_stdout # type: ignore
try:
yield
finally:
# If the code we did run did change sys.stdout/sys.stderr, we leave it
# unchanged. Otherwise, we reset it.
if sys.stdout is fake_stdout:
if sys.stdout is fake_stdout: # type: ignore
sys.stdout = old_stdout
if sys.stderr is fake_stderr:
if sys.stderr is fake_stderr: # type: ignore
sys.stderr = old_stderr
@contextlib.contextmanager
def disabled_excepthook():
def disabled_excepthook() -> typing.Iterator[None]:
"""Run code with the exception hook temporarily disabled."""
old_excepthook = sys.excepthook
sys.excepthook = sys.__excepthook__
@ -381,7 +394,7 @@ class prevent_exceptions: # noqa: N801,N806 pylint: disable=invalid-name
_predicate: The condition which needs to be True to prevent exceptions
"""
def __init__(self, retval, predicate=True):
def __init__(self, retval: typing.Any, predicate: bool = True) -> None:
"""Save decorator arguments.
Gets called on parse-time with the decorator arguments.
@ -392,7 +405,7 @@ class prevent_exceptions: # noqa: N801,N806 pylint: disable=invalid-name
self._retval = retval
self._predicate = predicate
def __call__(self, func):
def __call__(self, func: typing.Callable) -> typing.Callable:
"""Called when a function should be decorated.
Args:
@ -407,7 +420,7 @@ class prevent_exceptions: # noqa: N801,N806 pylint: disable=invalid-name
retval = self._retval
@functools.wraps(func)
def wrapper(*args, **kwargs):
def wrapper(*args: typing.Any, **kwargs: typing.Any) -> typing.Any:
"""Call the original function."""
try:
return func(*args, **kwargs)
@ -418,7 +431,7 @@ class prevent_exceptions: # noqa: N801,N806 pylint: disable=invalid-name
return wrapper
def is_enum(obj):
def is_enum(obj: typing.Any) -> bool:
"""Check if a given object is an enum."""
try:
return issubclass(obj, enum.Enum)
@ -426,7 +439,9 @@ def is_enum(obj):
return False
def get_repr(obj, constructor=False, **attrs):
def get_repr(obj: typing.Any,
constructor: bool = False,
**attrs: typing.Mapping) -> str:
"""Get a suitable __repr__ string for an object.
Args:
@ -449,7 +464,7 @@ def get_repr(obj, constructor=False, **attrs):
return '<{}>'.format(cls)
def qualname(obj):
def qualname(obj: typing.Any) -> str:
"""Get the fully qualified name of an object.
Based on twisted.python.reflect.fullyQualifiedName.
@ -477,7 +492,9 @@ def qualname(obj):
return repr(obj)
def raises(exc, func, *args):
def raises(exc: typing.Union[BaseException, typing.Tuple[BaseException]],
func: typing.Callable,
*args: typing.Any) -> bool:
"""Check if a function raises a given exception.
Args:
@ -490,13 +507,13 @@ def raises(exc, func, *args):
"""
try:
func(*args)
except exc:
except exc: # type: ignore
return True
else:
return False
def force_encoding(text, encoding):
def force_encoding(text: str, encoding: str) -> str:
"""Make sure a given text is encodable with the given encoding.
This replaces all chars not encodable with question marks.
@ -504,7 +521,8 @@ def force_encoding(text, encoding):
return text.encode(encoding, errors='replace').decode(encoding)
def sanitize_filename(name, replacement='_'):
def sanitize_filename(name: str,
replacement: typing.Optional[str] = '_') -> str:
"""Replace invalid filename characters.
Note: This should be used for the basename, as it also removes the path
@ -537,7 +555,7 @@ def sanitize_filename(name, replacement='_'):
return name
def set_clipboard(data, selection=False):
def set_clipboard(data: str, selection: bool = False) -> None:
"""Set the clipboard to some given data."""
global fake_clipboard
if selection and not supports_selection():
@ -551,7 +569,7 @@ def set_clipboard(data, selection=False):
QApplication.clipboard().setText(data, mode=mode)
def get_clipboard(selection=False, fallback=False):
def get_clipboard(selection: bool = False, fallback: bool = False) -> str:
"""Get data from the clipboard.
Args:
@ -584,12 +602,12 @@ def get_clipboard(selection=False, fallback=False):
return data
def supports_selection():
def supports_selection() -> bool:
"""Check if the OS supports primary selection."""
return QApplication.clipboard().supportsSelection()
def random_port():
def random_port() -> int:
"""Get a random free port."""
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('localhost', 0))
@ -598,7 +616,7 @@ def random_port():
return port
def open_file(filename, cmdline=None):
def open_file(filename: str, cmdline: str = None) -> None:
"""Open the given file.
If cmdline is not given, downloads.open_dispatcher is used.
@ -634,6 +652,8 @@ def open_file(filename, cmdline=None):
if cmdline is None and override:
cmdline = override
assert cmdline is not None
cmd, *args = shlex.split(cmdline)
args = [arg.replace('{}', filename) for arg in args]
if '{}' not in cmdline:
@ -644,11 +664,11 @@ def open_file(filename, cmdline=None):
proc.start_detached(cmd, args)
def unused(_arg):
def unused(_arg: typing.Any) -> None:
"""Function which does nothing to avoid pylint complaining."""
def expand_windows_drive(path):
def expand_windows_drive(path: str) -> str:
r"""Expand a drive-path like E: into E:\.
Does nothing for other paths.
@ -666,7 +686,7 @@ def expand_windows_drive(path):
return path
def yaml_load(f):
def yaml_load(f: typing.Union[str, typing.IO[str]]) -> typing.Any:
"""Wrapper over yaml.load using the C loader if possible."""
start = datetime.datetime.now()
@ -696,7 +716,8 @@ def yaml_load(f):
return data
def yaml_dump(data, f=None):
def yaml_dump(data: typing.Any,
f: typing.IO[str] = None) -> typing.Optional[str]:
"""Wrapper over yaml.dump using the C dumper if possible.
Also returns a str instead of bytes.
@ -709,7 +730,7 @@ def yaml_dump(data, f=None):
return yaml_data.decode('utf-8')
def chunk(elems, n):
def chunk(elems: typing.Sequence, n: int) -> typing.Iterator[typing.Sequence]:
"""Yield successive n-sized chunks from elems.
If elems % n != 0, the last chunk will be smaller.
@ -720,7 +741,7 @@ def chunk(elems, n):
yield elems[i:i + n]
def guess_mimetype(filename, fallback=False):
def guess_mimetype(filename: str, fallback: bool = False) -> str:
"""Guess a mimetype based on a filename.
Args:
@ -736,7 +757,7 @@ def guess_mimetype(filename, fallback=False):
return mimetype
def ceil_log(number, base):
def ceil_log(number: int, base: int) -> int:
"""Compute max(1, ceil(log(number, base))).
Use only integer arithmetic in order to avoid numerical error.