Merge pull request #6122 from bluetech/type-annotations-6

Add type annotations to _pytest._io.saferepr and _pytest.assertion.util._diff_text
This commit is contained in:
Ran Benita 2019-11-04 09:58:09 +02:00 committed by GitHub
commit 08c25b7fe0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 51 additions and 48 deletions

View File

@ -1,8 +1,9 @@
import pprint import pprint
import reprlib import reprlib
from typing import Any
def _format_repr_exception(exc, obj): def _format_repr_exception(exc: Exception, obj: Any) -> str:
exc_name = type(exc).__name__ exc_name = type(exc).__name__
try: try:
exc_info = str(exc) exc_info = str(exc)
@ -13,7 +14,7 @@ def _format_repr_exception(exc, obj):
) )
def _ellipsize(s, maxsize): def _ellipsize(s: str, maxsize: int) -> str:
if len(s) > maxsize: if len(s) > maxsize:
i = max(0, (maxsize - 3) // 2) i = max(0, (maxsize - 3) // 2)
j = max(0, maxsize - 3 - i) j = max(0, maxsize - 3 - i)
@ -26,19 +27,19 @@ class SafeRepr(reprlib.Repr):
and includes information on exceptions raised during the call. and includes information on exceptions raised during the call.
""" """
def __init__(self, maxsize): def __init__(self, maxsize: int) -> None:
super().__init__() super().__init__()
self.maxstring = maxsize self.maxstring = maxsize
self.maxsize = maxsize self.maxsize = maxsize
def repr(self, x): def repr(self, x: Any) -> str:
try: try:
s = super().repr(x) s = super().repr(x)
except Exception as exc: except Exception as exc:
s = _format_repr_exception(exc, x) s = _format_repr_exception(exc, x)
return _ellipsize(s, self.maxsize) return _ellipsize(s, self.maxsize)
def repr_instance(self, x, level): def repr_instance(self, x: Any, level: int) -> str:
try: try:
s = repr(x) s = repr(x)
except Exception as exc: except Exception as exc:
@ -46,7 +47,7 @@ class SafeRepr(reprlib.Repr):
return _ellipsize(s, self.maxsize) return _ellipsize(s, self.maxsize)
def safeformat(obj): def safeformat(obj: Any) -> str:
"""return a pretty printed string for the given object. """return a pretty printed string for the given object.
Failing __repr__ functions of user instances will be represented Failing __repr__ functions of user instances will be represented
with a short exception info. with a short exception info.
@ -57,7 +58,7 @@ def safeformat(obj):
return _format_repr_exception(exc, obj) return _format_repr_exception(exc, obj)
def saferepr(obj, maxsize=240): def saferepr(obj: Any, maxsize: int = 240) -> str:
"""return a size-limited safe repr-string for the given object. """return a size-limited safe repr-string for the given object.
Failing __repr__ functions of user instances will be represented Failing __repr__ functions of user instances will be represented
with a short exception info and 'saferepr' generally takes with a short exception info and 'saferepr' generally takes

View File

@ -1,9 +1,15 @@
"""Utilities for assertion debugging""" """Utilities for assertion debugging"""
import collections.abc
import pprint import pprint
from collections.abc import Sequence from typing import AbstractSet
from typing import Any
from typing import Callable from typing import Callable
from typing import Iterable
from typing import List from typing import List
from typing import Mapping
from typing import Optional from typing import Optional
from typing import Sequence
from typing import Tuple
import _pytest._code import _pytest._code
from _pytest import outcomes from _pytest import outcomes
@ -22,7 +28,7 @@ _reprcompare = None # type: Optional[Callable[[str, object, object], Optional[s
_assertion_pass = None # type: Optional[Callable[[int, str, str], None]] _assertion_pass = None # type: Optional[Callable[[int, str, str], None]]
def format_explanation(explanation): def format_explanation(explanation: str) -> str:
"""This formats an explanation """This formats an explanation
Normally all embedded newlines are escaped, however there are Normally all embedded newlines are escaped, however there are
@ -38,7 +44,7 @@ def format_explanation(explanation):
return "\n".join(result) return "\n".join(result)
def _split_explanation(explanation): def _split_explanation(explanation: str) -> List[str]:
"""Return a list of individual lines in the explanation """Return a list of individual lines in the explanation
This will return a list of lines split on '\n{', '\n}' and '\n~'. This will return a list of lines split on '\n{', '\n}' and '\n~'.
@ -55,7 +61,7 @@ def _split_explanation(explanation):
return lines return lines
def _format_lines(lines): def _format_lines(lines: Sequence[str]) -> List[str]:
"""Format the individual lines """Format the individual lines
This will replace the '{', '}' and '~' characters of our mini This will replace the '{', '}' and '~' characters of our mini
@ -64,7 +70,7 @@ def _format_lines(lines):
Return a list of formatted lines. Return a list of formatted lines.
""" """
result = lines[:1] result = list(lines[:1])
stack = [0] stack = [0]
stackcnt = [0] stackcnt = [0]
for line in lines[1:]: for line in lines[1:]:
@ -90,31 +96,31 @@ def _format_lines(lines):
return result return result
def issequence(x): def issequence(x: Any) -> bool:
return isinstance(x, Sequence) and not isinstance(x, str) return isinstance(x, collections.abc.Sequence) and not isinstance(x, str)
def istext(x): def istext(x: Any) -> bool:
return isinstance(x, str) return isinstance(x, str)
def isdict(x): def isdict(x: Any) -> bool:
return isinstance(x, dict) return isinstance(x, dict)
def isset(x): def isset(x: Any) -> bool:
return isinstance(x, (set, frozenset)) return isinstance(x, (set, frozenset))
def isdatacls(obj): def isdatacls(obj: Any) -> bool:
return getattr(obj, "__dataclass_fields__", None) is not None return getattr(obj, "__dataclass_fields__", None) is not None
def isattrs(obj): def isattrs(obj: Any) -> bool:
return getattr(obj, "__attrs_attrs__", None) is not None return getattr(obj, "__attrs_attrs__", None) is not None
def isiterable(obj): def isiterable(obj: Any) -> bool:
try: try:
iter(obj) iter(obj)
return not istext(obj) return not istext(obj)
@ -122,7 +128,7 @@ def isiterable(obj):
return False return False
def assertrepr_compare(config, op, left, right): def assertrepr_compare(config, op: str, left: Any, right: Any) -> Optional[List[str]]:
"""Return specialised explanations for some operators/operands""" """Return specialised explanations for some operators/operands"""
verbose = config.getoption("verbose") verbose = config.getoption("verbose")
if verbose > 1: if verbose > 1:
@ -180,33 +186,16 @@ def assertrepr_compare(config, op, left, right):
return [summary] + explanation return [summary] + explanation
def _diff_text(left, right, verbose=0): def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]:
"""Return the explanation for the diff between text or bytes. """Return the explanation for the diff between text.
Unless --verbose is used this will skip leading and trailing Unless --verbose is used this will skip leading and trailing
characters which are identical to keep the diff minimal. characters which are identical to keep the diff minimal.
If the input are bytes they will be safely converted to text.
""" """
from difflib import ndiff from difflib import ndiff
explanation = [] # type: List[str] explanation = [] # type: List[str]
def escape_for_readable_diff(binary_text):
"""
Ensures that the internal string is always valid unicode, converting any bytes safely to valid unicode.
This is done using repr() which then needs post-processing to fix the encompassing quotes and un-escape
newlines and carriage returns (#429).
"""
r = str(repr(binary_text)[1:-1])
r = r.replace(r"\n", "\n")
r = r.replace(r"\r", "\r")
return r
if isinstance(left, bytes):
left = escape_for_readable_diff(left)
if isinstance(right, bytes):
right = escape_for_readable_diff(right)
if verbose < 1: if verbose < 1:
i = 0 # just in case left or right has zero length i = 0 # just in case left or right has zero length
for i in range(min(len(left), len(right))): for i in range(min(len(left), len(right))):
@ -243,7 +232,7 @@ def _diff_text(left, right, verbose=0):
return explanation return explanation
def _compare_eq_verbose(left, right): def _compare_eq_verbose(left: Any, right: Any) -> List[str]:
keepends = True keepends = True
left_lines = repr(left).splitlines(keepends) left_lines = repr(left).splitlines(keepends)
right_lines = repr(right).splitlines(keepends) right_lines = repr(right).splitlines(keepends)
@ -255,7 +244,7 @@ def _compare_eq_verbose(left, right):
return explanation return explanation
def _surrounding_parens_on_own_lines(lines): # type: (List) -> None def _surrounding_parens_on_own_lines(lines: List[str]) -> None:
"""Move opening/closing parenthesis/bracket to own lines.""" """Move opening/closing parenthesis/bracket to own lines."""
opening = lines[0][:1] opening = lines[0][:1]
if opening in ["(", "[", "{"]: if opening in ["(", "[", "{"]:
@ -267,7 +256,9 @@ def _surrounding_parens_on_own_lines(lines): # type: (List) -> None
lines[:] = lines + [closing] lines[:] = lines + [closing]
def _compare_eq_iterable(left, right, verbose=0): def _compare_eq_iterable(
left: Iterable[Any], right: Iterable[Any], verbose: int = 0
) -> List[str]:
if not verbose: if not verbose:
return ["Use -v to get the full diff"] return ["Use -v to get the full diff"]
# dynamic import to speedup pytest # dynamic import to speedup pytest
@ -300,7 +291,9 @@ def _compare_eq_iterable(left, right, verbose=0):
return explanation return explanation
def _compare_eq_sequence(left, right, verbose=0): def _compare_eq_sequence(
left: Sequence[Any], right: Sequence[Any], verbose: int = 0
) -> List[str]:
comparing_bytes = isinstance(left, bytes) and isinstance(right, bytes) comparing_bytes = isinstance(left, bytes) and isinstance(right, bytes)
explanation = [] # type: List[str] explanation = [] # type: List[str]
len_left = len(left) len_left = len(left)
@ -354,7 +347,9 @@ def _compare_eq_sequence(left, right, verbose=0):
return explanation return explanation
def _compare_eq_set(left, right, verbose=0): def _compare_eq_set(
left: AbstractSet[Any], right: AbstractSet[Any], verbose: int = 0
) -> List[str]:
explanation = [] explanation = []
diff_left = left - right diff_left = left - right
diff_right = right - left diff_right = right - left
@ -369,7 +364,9 @@ def _compare_eq_set(left, right, verbose=0):
return explanation return explanation
def _compare_eq_dict(left, right, verbose=0): def _compare_eq_dict(
left: Mapping[Any, Any], right: Mapping[Any, Any], verbose: int = 0
) -> List[str]:
explanation = [] # type: List[str] explanation = [] # type: List[str]
set_left = set(left) set_left = set(left)
set_right = set(right) set_right = set(right)
@ -408,7 +405,12 @@ def _compare_eq_dict(left, right, verbose=0):
return explanation return explanation
def _compare_eq_cls(left, right, verbose, type_fns): def _compare_eq_cls(
left: Any,
right: Any,
verbose: int,
type_fns: Tuple[Callable[[Any], bool], Callable[[Any], bool]],
) -> List[str]:
isdatacls, isattrs = type_fns isdatacls, isattrs = type_fns
if isdatacls(left): if isdatacls(left):
all_fields = left.__dataclass_fields__ all_fields = left.__dataclass_fields__
@ -442,7 +444,7 @@ def _compare_eq_cls(left, right, verbose, type_fns):
return explanation return explanation
def _notin_text(term, text, verbose=0): def _notin_text(term: str, text: str, verbose: int = 0) -> List[str]:
index = text.find(term) index = text.find(term)
head = text[:index] head = text[:index]
tail = text[index + len(term) :] tail = text[index + len(term) :]