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