code/code: type annotations & doc cleanups

This commit is contained in:
Ran Benita 2020-07-10 11:50:36 +03:00
parent 087b047426
commit 77f3cb4baa
1 changed files with 85 additions and 95 deletions

View File

@ -51,7 +51,7 @@ if TYPE_CHECKING:
class Code:
""" wrapper around Python code objects """
"""Wrapper around Python code objects."""
def __init__(self, rawcode) -> None:
if not hasattr(rawcode, "co_filename"):
@ -74,7 +74,7 @@ class Code:
@property
def path(self) -> Union[py.path.local, str]:
""" return a path object pointing to source code (or a str in case
"""Return a path object pointing to source code (or a str in case
of OSError / non-existing file).
"""
if not self.raw.co_filename:
@ -92,24 +92,22 @@ class Code:
@property
def fullsource(self) -> Optional["Source"]:
""" return a _pytest._code.Source object for the full source file of the code
"""
"""Return a _pytest._code.Source object for the full source file of the code."""
full, _ = findsource(self.raw)
return full
def source(self) -> "Source":
""" return a _pytest._code.Source object for the code object's source only
"""
"""Return a _pytest._code.Source object for the code object's source only."""
# return source only for that part of code
return Source(self.raw)
def getargs(self, var: bool = False) -> Tuple[str, ...]:
""" return a tuple with the argument names for the code object
"""Return a tuple with the argument names for the code object.
if 'var' is set True also return the names of the variable and
keyword arguments when present
If 'var' is set True also return the names of the variable and
keyword arguments when present.
"""
# handfull shortcut for getting args
# Handy shortcut for getting args.
raw = self.raw
argcount = raw.co_argcount
if var:
@ -131,44 +129,43 @@ class Frame:
@property
def statement(self) -> "Source":
""" statement this frame is at """
"""Statement this frame is at."""
if self.code.fullsource is None:
return Source("")
return self.code.fullsource.getstatement(self.lineno)
def eval(self, code, **vars):
""" evaluate 'code' in the frame
"""Evaluate 'code' in the frame.
'vars' are optional additional local variables
'vars' are optional additional local variables.
returns the result of the evaluation
Returns the result of the evaluation.
"""
f_locals = self.f_locals.copy()
f_locals.update(vars)
return eval(code, self.f_globals, f_locals)
def exec_(self, code, **vars) -> None:
""" exec 'code' in the frame
"""Exec 'code' in the frame.
'vars' are optional; additional local variables
'vars' are optional; additional local variables.
"""
f_locals = self.f_locals.copy()
f_locals.update(vars)
exec(code, self.f_globals, f_locals)
def repr(self, object: object) -> str:
""" return a 'safe' (non-recursive, one-line) string repr for 'object'
"""
"""Return a 'safe' (non-recursive, one-line) string repr for 'object'."""
return saferepr(object)
def is_true(self, object):
return object
def getargs(self, var: bool = False):
""" return a list of tuples (name, value) for all arguments
"""Return a list of tuples (name, value) for all arguments.
if 'var' is set True also include the variable and keyword
arguments when present
If 'var' is set True, also include the variable and keyword arguments
when present.
"""
retval = []
for arg in self.code.getargs(var):
@ -180,12 +177,16 @@ class Frame:
class TracebackEntry:
""" a single entry in a traceback """
"""A single entry in a Traceback."""
_repr_style = None # type: Optional[Literal["short", "long"]]
exprinfo = None
def __init__(self, rawentry: TracebackType, excinfo=None) -> None:
def __init__(
self,
rawentry: TracebackType,
excinfo: Optional["ReferenceType[ExceptionInfo[BaseException]]"] = None,
) -> None:
self._excinfo = excinfo
self._rawentry = rawentry
self.lineno = rawentry.tb_lineno - 1
@ -207,26 +208,26 @@ class TracebackEntry:
@property
def statement(self) -> "Source":
""" _pytest._code.Source object for the current statement """
"""_pytest._code.Source object for the current statement."""
source = self.frame.code.fullsource
assert source is not None
return source.getstatement(self.lineno)
@property
def path(self) -> Union[py.path.local, str]:
""" path to the source code """
"""Path to the source code."""
return self.frame.code.path
@property
def locals(self) -> Dict[str, Any]:
""" locals of underlying frame """
"""Locals of underlying frame."""
return self.frame.f_locals
def getfirstlinesource(self) -> int:
return self.frame.code.firstlineno
def getsource(self, astcache=None) -> Optional["Source"]:
""" return failing source code. """
"""Return failing source code."""
# we use the passed in astcache to not reparse asttrees
# within exception info printing
source = self.frame.code.fullsource
@ -251,19 +252,19 @@ class TracebackEntry:
source = property(getsource)
def ishidden(self):
""" return True if the current frame has a var __tracebackhide__
resolving to True.
def ishidden(self) -> bool:
"""Return True if the current frame has a var __tracebackhide__
resolving to True.
If __tracebackhide__ is a callable, it gets called with the
ExceptionInfo instance and can decide whether to hide the traceback.
If __tracebackhide__ is a callable, it gets called with the
ExceptionInfo instance and can decide whether to hide the traceback.
mostly for internal use
Mostly for internal use.
"""
f = self.frame
tbh = f.f_locals.get(
"__tracebackhide__", f.f_globals.get("__tracebackhide__", False)
)
) # type: Union[bool, Callable[[Optional[ExceptionInfo[BaseException]]], bool]]
if tbh and callable(tbh):
return tbh(None if self._excinfo is None else self._excinfo())
return tbh
@ -280,21 +281,19 @@ class TracebackEntry:
@property
def name(self) -> str:
""" co_name of underlying code """
"""co_name of underlying code."""
return self.frame.code.raw.co_name
class Traceback(List[TracebackEntry]):
""" Traceback objects encapsulate and offer higher level
access to Traceback entries.
"""
"""Traceback objects encapsulate and offer higher level access to Traceback entries."""
def __init__(
self,
tb: Union[TracebackType, Iterable[TracebackEntry]],
excinfo: Optional["ReferenceType[ExceptionInfo]"] = None,
excinfo: Optional["ReferenceType[ExceptionInfo[BaseException]]"] = None,
) -> None:
""" initialize from given python traceback object and ExceptionInfo """
"""Initialize from given python traceback object and ExceptionInfo."""
self._excinfo = excinfo
if isinstance(tb, TracebackType):
@ -313,16 +312,16 @@ class Traceback(List[TracebackEntry]):
path=None,
lineno: Optional[int] = None,
firstlineno: Optional[int] = None,
excludepath=None,
excludepath: Optional[py.path.local] = None,
) -> "Traceback":
""" return a Traceback instance wrapping part of this Traceback
"""Return a Traceback instance wrapping part of this Traceback.
by providing any combination of path, lineno and firstlineno, the
first frame to start the to-be-returned traceback is determined
By providing any combination of path, lineno and firstlineno, the
first frame to start the to-be-returned traceback is determined.
this allows cutting the first part of a Traceback instance e.g.
for formatting reasons (removing some uninteresting bits that deal
with handling of the exception/traceback)
This allows cutting the first part of a Traceback instance e.g.
for formatting reasons (removing some uninteresting bits that deal
with handling of the exception/traceback).
"""
for x in self:
code = x.frame.code
@ -359,21 +358,19 @@ class Traceback(List[TracebackEntry]):
def filter(
self, fn: Callable[[TracebackEntry], bool] = lambda x: not x.ishidden()
) -> "Traceback":
""" return a Traceback instance with certain items removed
"""Return a Traceback instance with certain items removed
fn is a function that gets a single argument, a TracebackEntry
instance, and should return True when the item should be added
to the Traceback, False when not
fn is a function that gets a single argument, a TracebackEntry
instance, and should return True when the item should be added
to the Traceback, False when not.
by default this removes all the TracebackEntries which are hidden
(see ishidden() above)
By default this removes all the TracebackEntries which are hidden
(see ishidden() above).
"""
return Traceback(filter(fn, self), self._excinfo)
def getcrashentry(self) -> TracebackEntry:
""" return last non-hidden traceback entry that lead
to the exception of a traceback.
"""
"""Return last non-hidden traceback entry that lead to the exception of a traceback."""
for i in range(-1, -len(self) - 1, -1):
entry = self[i]
if not entry.ishidden():
@ -381,9 +378,8 @@ class Traceback(List[TracebackEntry]):
return self[-1]
def recursionindex(self) -> Optional[int]:
""" return the index of the frame/TracebackEntry where recursion
originates if appropriate, None if no recursion occurred
"""
"""Return the index of the frame/TracebackEntry where recursion originates if
appropriate, None if no recursion occurred."""
cache = {} # type: Dict[Tuple[Any, int, int], List[Dict[str, Any]]]
for i, entry in enumerate(self):
# id for the code.raw is needed to work around
@ -414,14 +410,12 @@ co_equal = compile(
)
_E = TypeVar("_E", bound=BaseException)
_E = TypeVar("_E", bound=BaseException, covariant=True)
@attr.s(repr=False)
class ExceptionInfo(Generic[_E]):
""" wraps sys.exc_info() objects and offers
help for navigating the traceback.
"""
"""Wraps sys.exc_info() objects and offers help for navigating the traceback."""
_assert_start_repr = "AssertionError('assert "
@ -435,13 +429,12 @@ class ExceptionInfo(Generic[_E]):
exc_info: Tuple["Type[_E]", "_E", TracebackType],
exprinfo: Optional[str] = None,
) -> "ExceptionInfo[_E]":
"""returns an ExceptionInfo for an existing exc_info tuple.
"""Returns an ExceptionInfo for an existing exc_info tuple.
.. warning::
Experimental API
:param exprinfo: a text string helping to determine if we should
strip ``AssertionError`` from the output, defaults
to the exception message/``__str__()``
@ -460,13 +453,12 @@ class ExceptionInfo(Generic[_E]):
def from_current(
cls, exprinfo: Optional[str] = None
) -> "ExceptionInfo[BaseException]":
"""returns an ExceptionInfo matching the current traceback
"""Returns an ExceptionInfo matching the current traceback.
.. warning::
Experimental API
:param exprinfo: a text string helping to determine if we should
strip ``AssertionError`` from the output, defaults
to the exception message/``__str__()``
@ -480,8 +472,7 @@ class ExceptionInfo(Generic[_E]):
@classmethod
def for_later(cls) -> "ExceptionInfo[_E]":
"""return an unfilled ExceptionInfo
"""
"""Return an unfilled ExceptionInfo."""
return cls(None)
def fill_unfilled(self, exc_info: Tuple["Type[_E]", _E, TracebackType]) -> None:
@ -491,7 +482,7 @@ class ExceptionInfo(Generic[_E]):
@property
def type(self) -> "Type[_E]":
"""the exception class"""
"""The exception class."""
assert (
self._excinfo is not None
), ".type can only be used after the context manager exits"
@ -499,7 +490,7 @@ class ExceptionInfo(Generic[_E]):
@property
def value(self) -> _E:
"""the exception value"""
"""The exception value."""
assert (
self._excinfo is not None
), ".value can only be used after the context manager exits"
@ -507,7 +498,7 @@ class ExceptionInfo(Generic[_E]):
@property
def tb(self) -> TracebackType:
"""the exception raw traceback"""
"""The exception raw traceback."""
assert (
self._excinfo is not None
), ".tb can only be used after the context manager exits"
@ -515,7 +506,7 @@ class ExceptionInfo(Generic[_E]):
@property
def typename(self) -> str:
"""the type name of the exception"""
"""The type name of the exception."""
assert (
self._excinfo is not None
), ".typename can only be used after the context manager exits"
@ -523,7 +514,7 @@ class ExceptionInfo(Generic[_E]):
@property
def traceback(self) -> Traceback:
"""the traceback"""
"""The traceback."""
if self._traceback is None:
self._traceback = Traceback(self.tb, excinfo=ref(self))
return self._traceback
@ -540,12 +531,12 @@ class ExceptionInfo(Generic[_E]):
)
def exconly(self, tryshort: bool = False) -> str:
""" return the exception as a string
"""Return the exception as a string.
when 'tryshort' resolves to True, and the exception is a
_pytest._code._AssertionError, only the actual exception part of
the exception representation is returned (so 'AssertionError: ' is
removed from the beginning)
When 'tryshort' resolves to True, and the exception is a
_pytest._code._AssertionError, only the actual exception part of
the exception representation is returned (so 'AssertionError: ' is
removed from the beginning).
"""
lines = format_exception_only(self.type, self.value)
text = "".join(lines)
@ -580,8 +571,7 @@ class ExceptionInfo(Generic[_E]):
truncate_locals: bool = True,
chain: bool = True,
) -> Union["ReprExceptionInfo", "ExceptionChainRepr"]:
"""
Return str()able representation of this exception info.
"""Return str()able representation of this exception info.
:param bool showlocals:
Show locals per traceback entry.
@ -630,11 +620,10 @@ class ExceptionInfo(Generic[_E]):
return fmt.repr_excinfo(self)
def match(self, regexp: "Union[str, Pattern]") -> "Literal[True]":
"""
Check whether the regular expression `regexp` matches the string
"""Check whether the regular expression `regexp` matches the string
representation of the exception using :func:`python:re.search`.
If it matches `True` is returned.
If it doesn't match an `AssertionError` is raised.
If it matches `True` is returned, otherwise an `AssertionError` is raised.
"""
__tracebackhide__ = True
assert re.search(
@ -646,7 +635,7 @@ class ExceptionInfo(Generic[_E]):
@attr.s
class FormattedExcinfo:
""" presenting information about failing Functions and Generators. """
"""Presenting information about failing Functions and Generators."""
# for traceback entries
flow_marker = ">"
@ -697,7 +686,7 @@ class FormattedExcinfo:
excinfo: Optional[ExceptionInfo] = None,
short: bool = False,
) -> List[str]:
""" return formatted and marked up source lines. """
"""Return formatted and marked up source lines."""
lines = []
if source is None or line_index >= len(source.lines):
source = Source("???")
@ -938,7 +927,7 @@ class ExceptionRepr(TerminalRepr):
reprcrash = None # type: Optional[ReprFileLocation]
reprtraceback = None # type: ReprTraceback
def __attrs_post_init__(self):
def __attrs_post_init__(self) -> None:
self.sections = [] # type: List[Tuple[str, str, str]]
def addsection(self, name: str, content: str, sep: str = "-") -> None:
@ -958,7 +947,7 @@ class ExceptionChainRepr(ExceptionRepr):
]
)
def __attrs_post_init__(self):
def __attrs_post_init__(self) -> None:
super().__attrs_post_init__()
# reprcrash and reprtraceback of the outermost (the newest) exception
# in the chain
@ -1160,8 +1149,9 @@ class ReprFuncArgs(TerminalRepr):
tw.line("")
def getfslineno(obj: Any) -> Tuple[Union[str, py.path.local], int]:
""" Return source location (path, lineno) for the given object.
def getfslineno(obj: object) -> Tuple[Union[str, py.path.local], int]:
"""Return source location (path, lineno) for the given object.
If the source cannot be determined return ("", -1).
The line number is 0-based.
@ -1171,13 +1161,13 @@ def getfslineno(obj: Any) -> Tuple[Union[str, py.path.local], int]:
# in 6ec13a2b9. It ("place_as") appears to be something very custom.
obj = get_real_func(obj)
if hasattr(obj, "place_as"):
obj = obj.place_as
obj = obj.place_as # type: ignore[attr-defined]
try:
code = Code(obj)
except TypeError:
try:
fn = inspect.getsourcefile(obj) or inspect.getfile(obj)
fn = inspect.getsourcefile(obj) or inspect.getfile(obj) # type: ignore[arg-type]
except TypeError:
return "", -1
@ -1189,8 +1179,8 @@ def getfslineno(obj: Any) -> Tuple[Union[str, py.path.local], int]:
except OSError:
pass
return fspath, lineno
else:
return code.path, code.firstlineno
return code.path, code.firstlineno
# relative paths that we use to filter traceback entries from appearing to the user;