diff options
author | deshevoy <deshevoy@yandex-team.ru> | 2022-02-10 16:46:56 +0300 |
---|---|---|
committer | Daniil Cherednik <dcherednik@yandex-team.ru> | 2022-02-10 16:46:56 +0300 |
commit | e988f30484abe5fdeedcc7a5d3c226c01a21800c (patch) | |
tree | 0a217b173aabb57b7e51f8a169989b1a3e0309fe /contrib/python/pytest/py3/_pytest/_code | |
parent | 33ee501c05d3f24036ae89766a858930ae66c548 (diff) | |
download | ydb-e988f30484abe5fdeedcc7a5d3c226c01a21800c.tar.gz |
Restoring authorship annotation for <deshevoy@yandex-team.ru>. Commit 1 of 2.
Diffstat (limited to 'contrib/python/pytest/py3/_pytest/_code')
-rw-r--r-- | contrib/python/pytest/py3/_pytest/_code/code.py | 1082 | ||||
-rw-r--r-- | contrib/python/pytest/py3/_pytest/_code/source.py | 240 |
2 files changed, 661 insertions, 661 deletions
diff --git a/contrib/python/pytest/py3/_pytest/_code/code.py b/contrib/python/pytest/py3/_pytest/_code/code.py index 423069330a..576a491d70 100644 --- a/contrib/python/pytest/py3/_pytest/_code/code.py +++ b/contrib/python/pytest/py3/_pytest/_code/code.py @@ -1,9 +1,9 @@ -import inspect -import re -import sys -import traceback -from inspect import CO_VARARGS -from inspect import CO_VARKEYWORDS +import inspect +import re +import sys +import traceback +from inspect import CO_VARARGS +from inspect import CO_VARKEYWORDS from io import StringIO from pathlib import Path from traceback import format_exception_only @@ -27,13 +27,13 @@ from typing import Type from typing import TYPE_CHECKING from typing import TypeVar from typing import Union -from weakref import ref - -import attr -import pluggy -import py - -import _pytest +from weakref import ref + +import attr +import pluggy +import py + +import _pytest from _pytest._code.source import findsource from _pytest._code.source import getrawcode from _pytest._code.source import getstatementrange_ast @@ -43,19 +43,19 @@ from _pytest._io.saferepr import safeformat from _pytest._io.saferepr import saferepr from _pytest.compat import final from _pytest.compat import get_real_func - + if TYPE_CHECKING: from typing_extensions import Literal from weakref import ReferenceType - + _TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"] - + class Code: """Wrapper around Python code objects.""" - + __slots__ = ("raw",) - + def __init__(self, obj: CodeType) -> None: self.raw = obj @@ -63,17 +63,17 @@ class Code: def from_function(cls, obj: object) -> "Code": return cls(getrawcode(obj)) - def __eq__(self, other): - return self.raw == other.raw - + def __eq__(self, other): + return self.raw == other.raw + # Ignore type because of https://github.com/python/mypy/issues/4266. __hash__ = None # type: ignore - + @property def firstlineno(self) -> int: return self.raw.co_firstlineno - 1 - - @property + + @property def name(self) -> str: return self.raw.co_name @@ -83,53 +83,53 @@ class Code: case of ``OSError`` / non-existing file.""" if not self.raw.co_filename: return "" - try: - p = py.path.local(self.raw.co_filename) - # maybe don't try this checking - if not p.check(): - raise OSError("py.path check failed.") + try: + p = py.path.local(self.raw.co_filename) + # maybe don't try this checking + if not p.check(): + raise OSError("py.path check failed.") return p - except OSError: - # XXX maybe try harder like the weird logic - # in the standard lib [linecache.updatecache] does? + except OSError: + # XXX maybe try harder like the weird logic + # in the standard lib [linecache.updatecache] does? return self.raw.co_filename - - @property + + @property def fullsource(self) -> Optional["Source"]: """Return a _pytest._code.Source object for the full source file of the code.""" full, _ = findsource(self.raw) - return full - + return full + def source(self) -> "Source": """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) - + def getargs(self, var: bool = False) -> Tuple[str, ...]: """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. - """ + """ # Handy shortcut for getting args. - raw = self.raw - argcount = raw.co_argcount - if var: - argcount += raw.co_flags & CO_VARARGS - argcount += raw.co_flags & CO_VARKEYWORDS - return raw.co_varnames[:argcount] - - + raw = self.raw + argcount = raw.co_argcount + if var: + argcount += raw.co_flags & CO_VARARGS + argcount += raw.co_flags & CO_VARKEYWORDS + return raw.co_varnames[:argcount] + + class Frame: - """Wrapper around a Python frame holding f_locals and f_globals - in which expressions can be evaluated.""" - + """Wrapper around a Python frame holding f_locals and f_globals + in which expressions can be evaluated.""" + __slots__ = ("raw",) def __init__(self, frame: FrameType) -> None: - self.raw = frame - - @property + self.raw = frame + + @property def lineno(self) -> int: return self.raw.f_lineno - 1 @@ -148,128 +148,128 @@ class Frame: @property def statement(self) -> "Source": """Statement this frame is at.""" - if self.code.fullsource is None: + if self.code.fullsource is None: return Source("") - return self.code.fullsource.getstatement(self.lineno) - - def eval(self, code, **vars): + return self.code.fullsource.getstatement(self.lineno) + + def eval(self, code, **vars): """Evaluate 'code' in the frame. - + 'vars' are optional additional local variables. - + Returns the result of the evaluation. - """ - f_locals = self.f_locals.copy() - f_locals.update(vars) - return eval(code, self.f_globals, f_locals) - + """ + f_locals = self.f_locals.copy() + f_locals.update(vars) + return eval(code, self.f_globals, f_locals) + def repr(self, object: object) -> str: """Return a 'safe' (non-recursive, one-line) string repr for 'object'.""" return saferepr(object) - + def getargs(self, var: bool = False): """Return a list of tuples (name, value) for all arguments. - + If 'var' is set True, also include the variable and keyword arguments when present. - """ - retval = [] - for arg in self.code.getargs(var): - try: - retval.append((arg, self.f_locals[arg])) - except KeyError: - pass # this can occur when using Psyco - return retval - - + """ + retval = [] + for arg in self.code.getargs(var): + try: + retval.append((arg, self.f_locals[arg])) + except KeyError: + pass # this can occur when using Psyco + return retval + + class TracebackEntry: """A single entry in a Traceback.""" - + __slots__ = ("_rawentry", "_excinfo", "_repr_style") - + def __init__( self, rawentry: TracebackType, excinfo: Optional["ReferenceType[ExceptionInfo[BaseException]]"] = None, ) -> None: self._rawentry = rawentry - self._excinfo = excinfo + self._excinfo = excinfo self._repr_style: Optional['Literal["short", "long"]'] = None - + @property def lineno(self) -> int: return self._rawentry.tb_lineno - 1 def set_repr_style(self, mode: "Literal['short', 'long']") -> None: - assert mode in ("short", "long") - self._repr_style = mode - - @property + assert mode in ("short", "long") + self._repr_style = mode + + @property def frame(self) -> Frame: return Frame(self._rawentry.tb_frame) - - @property + + @property def relline(self) -> int: - return self.lineno - self.frame.code.firstlineno - + return self.lineno - self.frame.code.firstlineno + def __repr__(self) -> str: - return "<TracebackEntry %s:%d>" % (self.frame.code.path, self.lineno + 1) - - @property + return "<TracebackEntry %s:%d>" % (self.frame.code.path, self.lineno + 1) + + @property def statement(self) -> "Source": """_pytest._code.Source object for the current statement.""" - source = self.frame.code.fullsource + source = self.frame.code.fullsource assert source is not None - return source.getstatement(self.lineno) - - @property + return source.getstatement(self.lineno) + + @property def path(self) -> Union[py.path.local, str]: """Path to the source code.""" - return self.frame.code.path - + return self.frame.code.path + @property def locals(self) -> Dict[str, Any]: """Locals of underlying frame.""" - return self.frame.f_locals - + 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.""" - # we use the passed in astcache to not reparse asttrees - # within exception info printing - source = self.frame.code.fullsource - if source is None: - return None - key = astnode = None - if astcache is not None: - key = self.frame.code.path - if key is not None: - astnode = astcache.get(key, None) - start = self.getfirstlinesource() - try: - astnode, _, end = getstatementrange_ast( - self.lineno, source, astnode=astnode - ) - except SyntaxError: - end = self.lineno + 1 - else: - if key is not None: - astcache[key] = astnode - return source[start:end] - - source = property(getsource) - + # we use the passed in astcache to not reparse asttrees + # within exception info printing + source = self.frame.code.fullsource + if source is None: + return None + key = astnode = None + if astcache is not None: + key = self.frame.code.path + if key is not None: + astnode = astcache.get(key, None) + start = self.getfirstlinesource() + try: + astnode, _, end = getstatementrange_ast( + self.lineno, source, astnode=astnode + ) + except SyntaxError: + end = self.lineno + 1 + else: + if key is not None: + astcache[key] = astnode + return source[start:end] + + source = property(getsource) + 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. - + Mostly for internal use. - """ + """ tbh: Union[bool, Callable[[Optional[ExceptionInfo[BaseException]]], bool]] = ( False ) @@ -285,17 +285,17 @@ class TracebackEntry: else: break 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 - + def __str__(self) -> str: - name = self.frame.code.name - try: - line = str(self.statement).lstrip() - except KeyboardInterrupt: - raise + name = self.frame.code.name + try: + line = str(self.statement).lstrip() + except KeyboardInterrupt: + raise except BaseException: - line = "???" + line = "???" # This output does not quite match Python's repr for traceback entries, # but changing it to do so would break certain plugins. See # https://github.com/pytest-dev/pytest/pull/7535/ for details. @@ -305,35 +305,35 @@ class TracebackEntry: name, line, ) - + @property def name(self) -> str: """co_name of underlying code.""" - return self.frame.code.raw.co_name - - + return self.frame.code.raw.co_name + + class Traceback(List[TracebackEntry]): """Traceback objects encapsulate and offer higher level access to Traceback entries.""" - + def __init__( self, tb: Union[TracebackType, Iterable[TracebackEntry]], excinfo: Optional["ReferenceType[ExceptionInfo[BaseException]]"] = None, ) -> None: """Initialize from given python traceback object and ExceptionInfo.""" - self._excinfo = excinfo + self._excinfo = excinfo if isinstance(tb, TracebackType): - + def f(cur: TracebackType) -> Iterable[TracebackEntry]: cur_: Optional[TracebackType] = cur while cur_ is not None: yield TracebackEntry(cur_, excinfo=excinfo) cur_ = cur_.tb_next - + super().__init__(f(tb)) - else: + else: super().__init__(tb) - + def cut( self, path=None, @@ -342,34 +342,34 @@ class Traceback(List[TracebackEntry]): excludepath: Optional[py.path.local] = None, ) -> "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. - + 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 - codepath = code.path - if ( - (path is None or codepath == path) - and ( - excludepath is None + """ + for x in self: + code = x.frame.code + codepath = code.path + if ( + (path is None or codepath == path) + and ( + excludepath is None or not isinstance(codepath, py.path.local) - or not codepath.relto(excludepath) - ) - and (lineno is None or x.lineno == lineno) - and (firstlineno is None or x.frame.code.firstlineno == firstlineno) - ): - return Traceback(x._rawentry, self._excinfo) - return self - + or not codepath.relto(excludepath) + ) + and (lineno is None or x.lineno == lineno) + and (firstlineno is None or x.frame.code.firstlineno == firstlineno) + ): + return Traceback(x._rawentry, self._excinfo) + return self + @overload def __getitem__(self, key: int) -> TracebackEntry: ... - + @overload def __getitem__(self, key: slice) -> "Traceback": ... @@ -384,55 +384,55 @@ class Traceback(List[TracebackEntry]): self, fn: Callable[[TracebackEntry], bool] = lambda x: not x.ishidden() ) -> "Traceback": """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. - + By default this removes all the TracebackEntries which are hidden (see ishidden() above). - """ - return Traceback(filter(fn, self), self._excinfo) - + """ + 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.""" - for i in range(-1, -len(self) - 1, -1): - entry = self[i] - if not entry.ishidden(): - return entry - return self[-1] - + for i in range(-1, -len(self) - 1, -1): + entry = self[i] + if not entry.ishidden(): + return entry + 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.""" cache: 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 - # the strange metaprogramming in the decorator lib from pypi - # which generates code objects that have hash/value equality - # XXX needs a test - key = entry.frame.code.path, id(entry.frame.code.raw), entry.lineno - # print "checking for recursion at", key - values = cache.setdefault(key, []) - if values: - f = entry.frame - loc = f.f_locals - for otherloc in values: + for i, entry in enumerate(self): + # id for the code.raw is needed to work around + # the strange metaprogramming in the decorator lib from pypi + # which generates code objects that have hash/value equality + # XXX needs a test + key = entry.frame.code.path, id(entry.frame.code.raw), entry.lineno + # print "checking for recursion at", key + values = cache.setdefault(key, []) + if values: + f = entry.frame + loc = f.f_locals + for otherloc in values: if f.eval( co_equal, __recursioncache_locals_1=loc, __recursioncache_locals_2=otherloc, - ): - return i - values.append(entry.frame.f_locals) - return None - - -co_equal = compile( - "__recursioncache_locals_1 == __recursioncache_locals_2", "?", "eval" -) - - + ): + return i + values.append(entry.frame.f_locals) + return None + + +co_equal = compile( + "__recursioncache_locals_1 == __recursioncache_locals_2", "?", "eval" +) + + _E = TypeVar("_E", bound=BaseException, covariant=True) @@ -440,13 +440,13 @@ _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.""" - + _assert_start_repr = "AssertionError('assert " - + _excinfo = attr.ib(type=Optional[Tuple[Type["_E"], "_E", TracebackType]]) _striptext = attr.ib(type=str, default="") _traceback = attr.ib(type=Optional[Traceback], default=None) - + @classmethod def from_exc_info( cls, @@ -454,7 +454,7 @@ class ExceptionInfo(Generic[_E]): exprinfo: Optional[str] = None, ) -> "ExceptionInfo[_E]": """Return an ExceptionInfo for an existing exc_info tuple. - + .. warning:: Experimental API @@ -555,23 +555,23 @@ class ExceptionInfo(Generic[_E]): return "<{} {} tblen={}>".format( self.__class__.__name__, saferepr(self._excinfo[1]), len(self.traceback) ) - + def exconly(self, tryshort: bool = False) -> str: """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). - """ - lines = format_exception_only(self.type, self.value) - text = "".join(lines) - text = text.rstrip() - if tryshort: - if text.startswith(self._striptext): - text = text[len(self._striptext) :] - return text - + """ + lines = format_exception_only(self.type, self.value) + text = "".join(lines) + text = text.rstrip() + if tryshort: + if text.startswith(self._striptext): + text = text[len(self._striptext) :] + return text + def errisinstance( self, exc: Union[Type[BaseException], Tuple[Type[BaseException], ...]] ) -> bool: @@ -579,16 +579,16 @@ class ExceptionInfo(Generic[_E]): Consider using ``isinstance(excinfo.value, exc)`` instead. """ - return isinstance(self.value, exc) - + return isinstance(self.value, exc) + def _getreprcrash(self) -> "ReprFileLocation": - exconly = self.exconly(tryshort=True) - entry = self.traceback.getcrashentry() - path, lineno = entry.frame.code.raw.co_filename, entry.lineno - return ReprFileLocation(path, lineno + 1, exconly) - - def getrepr( - self, + exconly = self.exconly(tryshort=True) + entry = self.traceback.getcrashentry() + path, lineno = entry.frame.code.raw.co_filename, entry.lineno + return ReprFileLocation(path, lineno + 1, exconly) + + def getrepr( + self, showlocals: bool = False, style: "_TracebackStyle" = "long", abspath: bool = False, @@ -598,78 +598,78 @@ class ExceptionInfo(Generic[_E]): chain: bool = True, ) -> Union["ReprExceptionInfo", "ExceptionChainRepr"]: """Return str()able representation of this exception info. - - :param bool showlocals: - Show locals per traceback entry. - Ignored if ``style=="native"``. - + + :param bool showlocals: + Show locals per traceback entry. + Ignored if ``style=="native"``. + :param str style: long|short|no|native|value traceback style. - - :param bool abspath: - If paths should be changed to absolute or left unchanged. - - :param bool tbfilter: - Hide entries that contain a local variable ``__tracebackhide__==True``. - Ignored if ``style=="native"``. - - :param bool funcargs: - Show fixtures ("funcargs" for legacy purposes) per traceback entry. - - :param bool truncate_locals: - With ``showlocals==True``, make sure locals can be safely represented as strings. - + + :param bool abspath: + If paths should be changed to absolute or left unchanged. + + :param bool tbfilter: + Hide entries that contain a local variable ``__tracebackhide__==True``. + Ignored if ``style=="native"``. + + :param bool funcargs: + Show fixtures ("funcargs" for legacy purposes) per traceback entry. + + :param bool truncate_locals: + With ``showlocals==True``, make sure locals can be safely represented as strings. + :param bool chain: If chained exceptions in Python 3 should be shown. - - .. versionchanged:: 3.9 - - Added the ``chain`` parameter. - """ - if style == "native": - return ReprExceptionInfo( - ReprTracebackNative( - traceback.format_exception( - self.type, self.value, self.traceback[0]._rawentry - ) - ), - self._getreprcrash(), - ) - - fmt = FormattedExcinfo( - showlocals=showlocals, - style=style, - abspath=abspath, - tbfilter=tbfilter, - funcargs=funcargs, - truncate_locals=truncate_locals, - chain=chain, - ) - return fmt.repr_excinfo(self) - + + .. versionchanged:: 3.9 + + Added the ``chain`` parameter. + """ + if style == "native": + return ReprExceptionInfo( + ReprTracebackNative( + traceback.format_exception( + self.type, self.value, self.traceback[0]._rawentry + ) + ), + self._getreprcrash(), + ) + + fmt = FormattedExcinfo( + showlocals=showlocals, + style=style, + abspath=abspath, + tbfilter=tbfilter, + funcargs=funcargs, + truncate_locals=truncate_locals, + chain=chain, + ) + return fmt.repr_excinfo(self) + def match(self, regexp: Union[str, Pattern[str]]) -> "Literal[True]": """Check whether the regular expression `regexp` matches the string representation of the exception using :func:`python:re.search`. If it matches `True` is returned, otherwise an `AssertionError` is raised. - """ - __tracebackhide__ = True + """ + __tracebackhide__ = True msg = "Regex pattern {!r} does not match {!r}." if regexp == str(self.value): msg += " Did you mean to `re.escape()` the regex?" assert re.search(regexp, str(self.value)), msg.format(regexp, str(self.value)) # Return True to allow for "assert excinfo.match()". - return True - - -@attr.s + return True + + +@attr.s class FormattedExcinfo: """Presenting information about failing Functions and Generators.""" - - # for traceback entries - flow_marker = ">" - fail_marker = "E" - + + # for traceback entries + flow_marker = ">" + fail_marker = "E" + showlocals = attr.ib(type=bool, default=False) style = attr.ib(type="_TracebackStyle", default="long") abspath = attr.ib(type=bool, default=True) @@ -677,37 +677,37 @@ class FormattedExcinfo: funcargs = attr.ib(type=bool, default=False) truncate_locals = attr.ib(type=bool, default=True) chain = attr.ib(type=bool, default=True) - astcache = attr.ib(default=attr.Factory(dict), init=False, repr=False) - + astcache = attr.ib(default=attr.Factory(dict), init=False, repr=False) + def _getindent(self, source: "Source") -> int: # Figure out indent for the given source. - try: - s = str(source.getstatement(len(source) - 1)) - except KeyboardInterrupt: - raise + try: + s = str(source.getstatement(len(source) - 1)) + except KeyboardInterrupt: + raise except BaseException: - try: - s = str(source[-1]) - except KeyboardInterrupt: - raise + try: + s = str(source[-1]) + except KeyboardInterrupt: + raise except BaseException: - return 0 - return 4 + (len(s) - len(s.lstrip())) - + return 0 + return 4 + (len(s) - len(s.lstrip())) + def _getentrysource(self, entry: TracebackEntry) -> Optional["Source"]: - source = entry.getsource(self.astcache) - if source is not None: - source = source.deindent() - return source - + source = entry.getsource(self.astcache) + if source is not None: + source = source.deindent() + return source + def repr_args(self, entry: TracebackEntry) -> Optional["ReprFuncArgs"]: - if self.funcargs: - args = [] - for argname, argvalue in entry.frame.getargs(var=True): + if self.funcargs: + args = [] + for argname, argvalue in entry.frame.getargs(var=True): args.append((argname, saferepr(argvalue))) - return ReprFuncArgs(args) + return ReprFuncArgs(args) return None - + def get_source( self, source: Optional["Source"], @@ -716,69 +716,69 @@ class FormattedExcinfo: short: bool = False, ) -> List[str]: """Return formatted and marked up source lines.""" - lines = [] - if source is None or line_index >= len(source.lines): + lines = [] + if source is None or line_index >= len(source.lines): source = Source("???") - line_index = 0 - if line_index < 0: - line_index += len(source) - space_prefix = " " - if short: - lines.append(space_prefix + source.lines[line_index].strip()) - else: - for line in source.lines[:line_index]: - lines.append(space_prefix + line) - lines.append(self.flow_marker + " " + source.lines[line_index]) - for line in source.lines[line_index + 1 :]: - lines.append(space_prefix + line) - if excinfo is not None: - indent = 4 if short else self._getindent(source) - lines.extend(self.get_exconly(excinfo, indent=indent, markall=True)) - return lines - + line_index = 0 + if line_index < 0: + line_index += len(source) + space_prefix = " " + if short: + lines.append(space_prefix + source.lines[line_index].strip()) + else: + for line in source.lines[:line_index]: + lines.append(space_prefix + line) + lines.append(self.flow_marker + " " + source.lines[line_index]) + for line in source.lines[line_index + 1 :]: + lines.append(space_prefix + line) + if excinfo is not None: + indent = 4 if short else self._getindent(source) + lines.extend(self.get_exconly(excinfo, indent=indent, markall=True)) + return lines + def get_exconly( self, excinfo: ExceptionInfo[BaseException], indent: int = 4, markall: bool = False, ) -> List[str]: - lines = [] + lines = [] indentstr = " " * indent # Get the real exception information out. - exlines = excinfo.exconly(tryshort=True).split("\n") + exlines = excinfo.exconly(tryshort=True).split("\n") failindent = self.fail_marker + indentstr[1:] - for line in exlines: - lines.append(failindent + line) - if not markall: + for line in exlines: + lines.append(failindent + line) + if not markall: failindent = indentstr - return lines - + return lines + def repr_locals(self, locals: Mapping[str, object]) -> Optional["ReprLocals"]: - if self.showlocals: - lines = [] - keys = [loc for loc in locals if loc[0] != "@"] - keys.sort() - for name in keys: - value = locals[name] - if name == "__builtins__": - lines.append("__builtins__ = <builtins>") - else: - # This formatting could all be handled by the - # _repr() function, which is only reprlib.Repr in - # disguise, so is very configurable. - if self.truncate_locals: + if self.showlocals: + lines = [] + keys = [loc for loc in locals if loc[0] != "@"] + keys.sort() + for name in keys: + value = locals[name] + if name == "__builtins__": + lines.append("__builtins__ = <builtins>") + else: + # This formatting could all be handled by the + # _repr() function, which is only reprlib.Repr in + # disguise, so is very configurable. + if self.truncate_locals: str_repr = saferepr(value) - else: + else: str_repr = safeformat(value) # if len(str_repr) < 70 or not isinstance(value, (list, tuple, dict)): lines.append(f"{name:<10} = {str_repr}") - # else: - # self._line("%-10s =\\" % (name,)) - # # XXX - # pprint.pprint(value, stream=self.excinfowriter) - return ReprLocals(lines) + # else: + # self._line("%-10s =\\" % (name,)) + # # XXX + # pprint.pprint(value, stream=self.excinfowriter) + return ReprLocals(lines) return None - + def repr_traceback_entry( self, entry: TracebackEntry, @@ -786,22 +786,22 @@ class FormattedExcinfo: ) -> "ReprEntry": lines: List[str] = [] style = entry._repr_style if entry._repr_style is not None else self.style - if style in ("short", "long"): + if style in ("short", "long"): source = self._getentrysource(entry) if source is None: source = Source("???") line_index = 0 else: line_index = entry.lineno - entry.getfirstlinesource() - short = style == "short" - reprargs = self.repr_args(entry) if not short else None - s = self.get_source(source, line_index, excinfo, short=short) - lines.extend(s) - if short: - message = "in %s" % (entry.name) - else: - message = excinfo and excinfo.typename or "" - path = self._makepath(entry.path) + short = style == "short" + reprargs = self.repr_args(entry) if not short else None + s = self.get_source(source, line_index, excinfo, short=short) + lines.extend(s) + if short: + message = "in %s" % (entry.name) + else: + message = excinfo and excinfo.typename or "" + path = self._makepath(entry.path) reprfileloc = ReprFileLocation(path, entry.lineno + 1, message) localsrepr = self.repr_locals(entry.locals) return ReprEntry(lines, reprargs, localsrepr, reprfileloc, style) @@ -813,82 +813,82 @@ class FormattedExcinfo: if excinfo: lines.extend(self.get_exconly(excinfo, indent=4)) return ReprEntry(lines, None, None, None, style) - - def _makepath(self, path): - if not self.abspath: - try: - np = py.path.local().bestrelpath(path) - except OSError: - return path - if len(np) < len(str(path)): - path = np - return path - + + def _makepath(self, path): + if not self.abspath: + try: + np = py.path.local().bestrelpath(path) + except OSError: + return path + if len(np) < len(str(path)): + path = np + return path + def repr_traceback(self, excinfo: ExceptionInfo[BaseException]) -> "ReprTraceback": - traceback = excinfo.traceback - if self.tbfilter: - traceback = traceback.filter() - + traceback = excinfo.traceback + if self.tbfilter: + traceback = traceback.filter() + if isinstance(excinfo.value, RecursionError): - traceback, extraline = self._truncate_recursive_traceback(traceback) - else: - extraline = None - - last = traceback[-1] - entries = [] + traceback, extraline = self._truncate_recursive_traceback(traceback) + else: + extraline = None + + last = traceback[-1] + entries = [] if self.style == "value": reprentry = self.repr_traceback_entry(last, excinfo) entries.append(reprentry) return ReprTraceback(entries, None, style=self.style) - for index, entry in enumerate(traceback): - einfo = (last == entry) and excinfo or None - reprentry = self.repr_traceback_entry(entry, einfo) - entries.append(reprentry) - return ReprTraceback(entries, extraline, style=self.style) - + for index, entry in enumerate(traceback): + einfo = (last == entry) and excinfo or None + reprentry = self.repr_traceback_entry(entry, einfo) + entries.append(reprentry) + return ReprTraceback(entries, extraline, style=self.style) + def _truncate_recursive_traceback( self, traceback: Traceback ) -> Tuple[Traceback, Optional[str]]: """Truncate the given recursive traceback trying to find the starting point of the recursion. - + The detection is done by going through each traceback entry and finding the point in which the locals of the frame are equal to the locals of a previous frame (see ``recursionindex()``). - + Handle the situation where the recursion process might raise an exception (for example comparing numpy arrays using equality raises a TypeError), in which case we do our best to warn the user of the error and show a limited traceback. - """ - try: - recursionindex = traceback.recursionindex() - except Exception as e: - max_frames = 10 + """ + try: + recursionindex = traceback.recursionindex() + except Exception as e: + max_frames = 10 extraline: Optional[str] = ( - "!!! Recursion error detected, but an error occurred locating the origin of recursion.\n" - " The following exception happened when comparing locals in the stack frame:\n" - " {exc_type}: {exc_msg}\n" - " Displaying first and last {max_frames} stack frames out of {total}." - ).format( - exc_type=type(e).__name__, + "!!! Recursion error detected, but an error occurred locating the origin of recursion.\n" + " The following exception happened when comparing locals in the stack frame:\n" + " {exc_type}: {exc_msg}\n" + " Displaying first and last {max_frames} stack frames out of {total}." + ).format( + exc_type=type(e).__name__, exc_msg=str(e), - max_frames=max_frames, - total=len(traceback), + max_frames=max_frames, + total=len(traceback), ) # Type ignored because adding two instaces of a List subtype # currently incorrectly has type List instead of the subtype. traceback = traceback[:max_frames] + traceback[-max_frames:] # type: ignore - else: - if recursionindex is not None: - extraline = "!!! Recursion detected (same locals & position)" - traceback = traceback[: recursionindex + 1] - else: - extraline = None - - return traceback, extraline - + else: + if recursionindex is not None: + extraline = "!!! Recursion detected (same locals & position)" + traceback = traceback[: recursionindex + 1] + else: + extraline = None + + return traceback, extraline + def repr_excinfo( self, excinfo: ExceptionInfo[BaseException] ) -> "ExceptionChainRepr": @@ -913,7 +913,7 @@ class FormattedExcinfo: traceback.format_exception(type(e), e, None) ) reprcrash = None - + repr_chain += [(reprtraceback, reprcrash, descr)] if e.__cause__ is not None and self.chain: e = e.__cause__ @@ -937,46 +937,46 @@ class FormattedExcinfo: e = None repr_chain.reverse() return ExceptionChainRepr(repr_chain) - - + + @attr.s(eq=False) class TerminalRepr: def __str__(self) -> str: - # FYI this is called from pytest-xdist's serialization of exception - # information. + # FYI this is called from pytest-xdist's serialization of exception + # information. io = StringIO() tw = TerminalWriter(file=io) - self.toterminal(tw) - return io.getvalue().strip() - + self.toterminal(tw) + return io.getvalue().strip() + def __repr__(self) -> str: return "<{} instance at {:0x}>".format(self.__class__, id(self)) - + def toterminal(self, tw: TerminalWriter) -> None: raise NotImplementedError() - + # This class is abstract -- only subclasses are instantiated. @attr.s(eq=False) -class ExceptionRepr(TerminalRepr): +class ExceptionRepr(TerminalRepr): # Provided by subclasses. reprcrash: Optional["ReprFileLocation"] reprtraceback: "ReprTraceback" - + def __attrs_post_init__(self) -> None: self.sections: List[Tuple[str, str, str]] = [] def addsection(self, name: str, content: str, sep: str = "-") -> None: - self.sections.append((name, content, sep)) - + self.sections.append((name, content, sep)) + def toterminal(self, tw: TerminalWriter) -> None: - for name, content, sep in self.sections: - tw.sep(sep, name) - tw.line(content) - - + for name, content, sep in self.sections: + tw.sep(sep, name) + tw.line(content) + + @attr.s(eq=False) -class ExceptionChainRepr(ExceptionRepr): +class ExceptionChainRepr(ExceptionRepr): chain = attr.ib( type=Sequence[ Tuple["ReprTraceback", Optional["ReprFileLocation"], Optional[str]] @@ -985,81 +985,81 @@ class ExceptionChainRepr(ExceptionRepr): def __attrs_post_init__(self) -> None: 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. self.reprtraceback = self.chain[-1][0] self.reprcrash = self.chain[-1][1] - + def toterminal(self, tw: TerminalWriter) -> None: - for element in self.chain: - element[0].toterminal(tw) - if element[2] is not None: - tw.line("") - tw.line(element[2], yellow=True) + for element in self.chain: + element[0].toterminal(tw) + if element[2] is not None: + tw.line("") + tw.line(element[2], yellow=True) super().toterminal(tw) - - + + @attr.s(eq=False) -class ReprExceptionInfo(ExceptionRepr): +class ReprExceptionInfo(ExceptionRepr): reprtraceback = attr.ib(type="ReprTraceback") reprcrash = attr.ib(type="ReprFileLocation") - + def toterminal(self, tw: TerminalWriter) -> None: - self.reprtraceback.toterminal(tw) + self.reprtraceback.toterminal(tw) super().toterminal(tw) - - + + @attr.s(eq=False) -class ReprTraceback(TerminalRepr): +class ReprTraceback(TerminalRepr): reprentries = attr.ib(type=Sequence[Union["ReprEntry", "ReprEntryNative"]]) extraline = attr.ib(type=Optional[str]) style = attr.ib(type="_TracebackStyle") - entrysep = "_ " - + entrysep = "_ " + def toterminal(self, tw: TerminalWriter) -> None: # The entries might have different styles. - for i, entry in enumerate(self.reprentries): - if entry.style == "long": - tw.line("") - entry.toterminal(tw) - if i < len(self.reprentries) - 1: - next_entry = self.reprentries[i + 1] - if ( - entry.style == "long" - or entry.style == "short" - and next_entry.style == "long" - ): - tw.sep(self.entrysep) - - if self.extraline: - tw.line(self.extraline) - - -class ReprTracebackNative(ReprTraceback): + for i, entry in enumerate(self.reprentries): + if entry.style == "long": + tw.line("") + entry.toterminal(tw) + if i < len(self.reprentries) - 1: + next_entry = self.reprentries[i + 1] + if ( + entry.style == "long" + or entry.style == "short" + and next_entry.style == "long" + ): + tw.sep(self.entrysep) + + if self.extraline: + tw.line(self.extraline) + + +class ReprTracebackNative(ReprTraceback): def __init__(self, tblines: Sequence[str]) -> None: - self.style = "native" - self.reprentries = [ReprEntryNative(tblines)] - self.extraline = None - - + self.style = "native" + self.reprentries = [ReprEntryNative(tblines)] + self.extraline = None + + @attr.s(eq=False) -class ReprEntryNative(TerminalRepr): +class ReprEntryNative(TerminalRepr): lines = attr.ib(type=Sequence[str]) style: "_TracebackStyle" = "native" - + def toterminal(self, tw: TerminalWriter) -> None: - tw.write("".join(self.lines)) - - + tw.write("".join(self.lines)) + + @attr.s(eq=False) -class ReprEntry(TerminalRepr): +class ReprEntry(TerminalRepr): lines = attr.ib(type=Sequence[str]) reprfuncargs = attr.ib(type=Optional["ReprFuncArgs"]) reprlocals = attr.ib(type=Optional["ReprLocals"]) reprfileloc = attr.ib(type=Optional["ReprFileLocation"]) style = attr.ib(type="_TracebackStyle") - + def _write_entry_lines(self, tw: TerminalWriter) -> None: """Write the source code portions of a list of traceback entries with syntax highlighting. @@ -1105,82 +1105,82 @@ class ReprEntry(TerminalRepr): tw.line(line, bold=True, red=True) def toterminal(self, tw: TerminalWriter) -> None: - if self.style == "short": + if self.style == "short": assert self.reprfileloc is not None - self.reprfileloc.toterminal(tw) + self.reprfileloc.toterminal(tw) self._write_entry_lines(tw) if self.reprlocals: self.reprlocals.toterminal(tw, indent=" " * 8) - return + return - if self.reprfuncargs: - self.reprfuncargs.toterminal(tw) + if self.reprfuncargs: + self.reprfuncargs.toterminal(tw) self._write_entry_lines(tw) - if self.reprlocals: - tw.line("") - self.reprlocals.toterminal(tw) - if self.reprfileloc: - if self.lines: - tw.line("") - self.reprfileloc.toterminal(tw) - + if self.reprlocals: + tw.line("") + self.reprlocals.toterminal(tw) + if self.reprfileloc: + if self.lines: + tw.line("") + self.reprfileloc.toterminal(tw) + def __str__(self) -> str: return "{}\n{}\n{}".format( "\n".join(self.lines), self.reprlocals, self.reprfileloc ) - - + + @attr.s(eq=False) -class ReprFileLocation(TerminalRepr): +class ReprFileLocation(TerminalRepr): path = attr.ib(type=str, converter=str) lineno = attr.ib(type=int) message = attr.ib(type=str) - + def toterminal(self, tw: TerminalWriter) -> None: # Filename and lineno output for each entry, using an output format # that most editors understand. - msg = self.message - i = msg.find("\n") - if i != -1: - msg = msg[:i] - tw.write(self.path, bold=True, red=True) + msg = self.message + i = msg.find("\n") + if i != -1: + msg = msg[:i] + tw.write(self.path, bold=True, red=True) tw.line(f":{self.lineno}: {msg}") - - + + @attr.s(eq=False) -class ReprLocals(TerminalRepr): +class ReprLocals(TerminalRepr): lines = attr.ib(type=Sequence[str]) - + def toterminal(self, tw: TerminalWriter, indent="") -> None: - for line in self.lines: + for line in self.lines: tw.line(indent + line) - - + + @attr.s(eq=False) -class ReprFuncArgs(TerminalRepr): +class ReprFuncArgs(TerminalRepr): args = attr.ib(type=Sequence[Tuple[str, object]]) - + def toterminal(self, tw: TerminalWriter) -> None: - if self.args: - linesofar = "" - for name, value in self.args: + if self.args: + linesofar = "" + for name, value in self.args: ns = f"{name} = {value}" - if len(ns) + len(linesofar) + 2 > tw.fullwidth: - if linesofar: - tw.line(linesofar) - linesofar = ns - else: - if linesofar: - linesofar += ", " + ns - else: - linesofar = ns - if linesofar: - tw.line(linesofar) - tw.line("") - - + if len(ns) + len(linesofar) + 2 > tw.fullwidth: + if linesofar: + tw.line(linesofar) + linesofar = ns + else: + if linesofar: + linesofar += ", " + ns + else: + linesofar = ns + if linesofar: + tw.line(linesofar) + tw.line("") + + def getfslineno(obj: object) -> Tuple[Union[str, py.path.local], int]: """Return source location (path, lineno) for the given object. @@ -1195,14 +1195,14 @@ def getfslineno(obj: object) -> Tuple[Union[str, py.path.local], int]: if hasattr(obj, "place_as"): obj = obj.place_as # type: ignore[attr-defined] - try: + try: code = Code.from_function(obj) except TypeError: try: fn = inspect.getsourcefile(obj) or inspect.getfile(obj) # type: ignore[arg-type] except TypeError: return "", -1 - + fspath = fn and py.path.local(fn) or "" lineno = -1 if fspath: @@ -1211,40 +1211,40 @@ def getfslineno(obj: object) -> Tuple[Union[str, py.path.local], int]: except OSError: pass return fspath, lineno - + return code.path, code.firstlineno # Relative paths that we use to filter traceback entries from appearing to the user; # see filter_traceback. -# note: if we need to add more paths than what we have now we should probably use a list +# note: if we need to add more paths than what we have now we should probably use a list # for better maintenance. - + _PLUGGY_DIR = Path(pluggy.__file__.rstrip("oc")) -# pluggy is either a package or a single module depending on the version +# pluggy is either a package or a single module depending on the version if _PLUGGY_DIR.name == "__init__.py": _PLUGGY_DIR = _PLUGGY_DIR.parent _PYTEST_DIR = Path(_pytest.__file__).parent _PY_DIR = Path(py.__file__).parent - - + + def filter_traceback(entry: TracebackEntry) -> bool: """Return True if a TracebackEntry instance should be included in tracebacks. We hide traceback entries of: - * dynamically generated code (no code to show up for it); - * internal traceback from pytest or its internal libraries, py and pluggy. - """ - # entry.path might sometimes return a str object when the entry + * dynamically generated code (no code to show up for it); + * internal traceback from pytest or its internal libraries, py and pluggy. + """ + # entry.path might sometimes return a str object when the entry # points to dynamically generated code. # See https://bitbucket.org/pytest-dev/py/issues/71. - raw_filename = entry.frame.code.raw.co_filename - is_generated = "<" in raw_filename and ">" in raw_filename - if is_generated: - return False + raw_filename = entry.frame.code.raw.co_filename + is_generated = "<" in raw_filename and ">" in raw_filename + if is_generated: + return False - # entry.path might point to a non-existing file, in which case it will + # entry.path might point to a non-existing file, in which case it will # also return a str object. See #1133. p = Path(entry.path) diff --git a/contrib/python/pytest/py3/_pytest/_code/source.py b/contrib/python/pytest/py3/_pytest/_code/source.py index 6f54057c0a..56bf0fdc20 100644 --- a/contrib/python/pytest/py3/_pytest/_code/source.py +++ b/contrib/python/pytest/py3/_pytest/_code/source.py @@ -1,10 +1,10 @@ -import ast -import inspect -import textwrap -import tokenize +import ast +import inspect +import textwrap +import tokenize import types -import warnings -from bisect import bisect_right +import warnings +from bisect import bisect_right from typing import Iterable from typing import Iterator from typing import List @@ -12,14 +12,14 @@ from typing import Optional from typing import overload from typing import Tuple from typing import Union - - + + class Source: """An immutable object holding a source code fragment. - + When using Source(...), the source lines are deindented. - """ - + """ + def __init__(self, obj: object = None) -> None: if not obj: self.lines: List[str] = [] @@ -36,15 +36,15 @@ class Source: except TypeError: src = inspect.getsource(obj) # type: ignore[arg-type] self.lines = deindent(src.split("\n")) - + def __eq__(self, other: object) -> bool: if not isinstance(other, Source): return NotImplemented return self.lines == other.lines - + # Ignore type because of https://github.com/python/mypy/issues/4266. __hash__ = None # type: ignore - + @overload def __getitem__(self, key: int) -> str: ... @@ -54,81 +54,81 @@ class Source: ... def __getitem__(self, key: Union[int, slice]) -> Union[str, "Source"]: - if isinstance(key, int): - return self.lines[key] - else: - if key.step not in (None, 1): - raise IndexError("cannot slice a Source with a step") - newsource = Source() - newsource.lines = self.lines[key.start : key.stop] - return newsource - + if isinstance(key, int): + return self.lines[key] + else: + if key.step not in (None, 1): + raise IndexError("cannot slice a Source with a step") + newsource = Source() + newsource.lines = self.lines[key.start : key.stop] + return newsource + def __iter__(self) -> Iterator[str]: return iter(self.lines) def __len__(self) -> int: - return len(self.lines) - + return len(self.lines) + def strip(self) -> "Source": """Return new Source object with trailing and leading blank lines removed.""" - start, end = 0, len(self) - while start < end and not self.lines[start].strip(): - start += 1 - while end > start and not self.lines[end - 1].strip(): - end -= 1 - source = Source() - source.lines[:] = self.lines[start:end] - return source - + start, end = 0, len(self) + while start < end and not self.lines[start].strip(): + start += 1 + while end > start and not self.lines[end - 1].strip(): + end -= 1 + source = Source() + source.lines[:] = self.lines[start:end] + return source + def indent(self, indent: str = " " * 4) -> "Source": """Return a copy of the source object with all lines indented by the given indent-string.""" - newsource = Source() - newsource.lines = [(indent + line) for line in self.lines] - return newsource - + newsource = Source() + newsource.lines = [(indent + line) for line in self.lines] + return newsource + def getstatement(self, lineno: int) -> "Source": """Return Source statement which contains the given linenumber (counted from 0).""" - start, end = self.getstatementrange(lineno) - return self[start:end] - + start, end = self.getstatementrange(lineno) + return self[start:end] + def getstatementrange(self, lineno: int) -> Tuple[int, int]: """Return (start, end) tuple which spans the minimal statement region which containing the given lineno.""" - if not (0 <= lineno < len(self)): - raise IndexError("lineno out of range") - ast, start, end = getstatementrange_ast(lineno, self) - return start, end - + if not (0 <= lineno < len(self)): + raise IndexError("lineno out of range") + ast, start, end = getstatementrange_ast(lineno, self) + return start, end + def deindent(self) -> "Source": """Return a new Source object deindented.""" - newsource = Source() - newsource.lines[:] = deindent(self.lines) - return newsource - + newsource = Source() + newsource.lines[:] = deindent(self.lines) + return newsource + def __str__(self) -> str: - return "\n".join(self.lines) - - -# -# helper functions -# - - + return "\n".join(self.lines) + + +# +# helper functions +# + + def findsource(obj) -> Tuple[Optional[Source], int]: - try: - sourcelines, lineno = inspect.findsource(obj) + try: + sourcelines, lineno = inspect.findsource(obj) except Exception: - return None, -1 - source = Source() - source.lines = [line.rstrip() for line in sourcelines] - return source, lineno - - + return None, -1 + source = Source() + source.lines = [line.rstrip() for line in sourcelines] + return source, lineno + + def getrawcode(obj: object, trycall: bool = True) -> types.CodeType: """Return code object for given function.""" - try: + try: return obj.__code__ # type: ignore[attr-defined,no-any-return] except AttributeError: pass @@ -137,76 +137,76 @@ def getrawcode(obj: object, trycall: bool = True) -> types.CodeType: if call and not isinstance(obj, type): return getrawcode(call, trycall=False) raise TypeError(f"could not get code object for {obj!r}") - - + + def deindent(lines: Iterable[str]) -> List[str]: - return textwrap.dedent("\n".join(lines)).splitlines() - - + return textwrap.dedent("\n".join(lines)).splitlines() + + def get_statement_startend2(lineno: int, node: ast.AST) -> Tuple[int, Optional[int]]: # Flatten all statements and except handlers into one lineno-list. # AST's line numbers start indexing at 1. values: List[int] = [] - for x in ast.walk(node): - if isinstance(x, (ast.stmt, ast.ExceptHandler)): - values.append(x.lineno - 1) - for name in ("finalbody", "orelse"): + for x in ast.walk(node): + if isinstance(x, (ast.stmt, ast.ExceptHandler)): + values.append(x.lineno - 1) + for name in ("finalbody", "orelse"): val: Optional[List[ast.stmt]] = getattr(x, name, None) - if val: + if val: # Treat the finally/orelse part as its own statement. - values.append(val[0].lineno - 1 - 1) - values.sort() - insert_index = bisect_right(values, lineno) - start = values[insert_index - 1] - if insert_index >= len(values): - end = None - else: - end = values[insert_index] - return start, end - - + values.append(val[0].lineno - 1 - 1) + values.sort() + insert_index = bisect_right(values, lineno) + start = values[insert_index - 1] + if insert_index >= len(values): + end = None + else: + end = values[insert_index] + return start, end + + def getstatementrange_ast( lineno: int, source: Source, assertion: bool = False, astnode: Optional[ast.AST] = None, ) -> Tuple[ast.AST, int, int]: - if astnode is None: - content = str(source) - # See #4260: + if astnode is None: + content = str(source) + # See #4260: # Don't produce duplicate warnings when compiling source to find AST. - with warnings.catch_warnings(): - warnings.simplefilter("ignore") + with warnings.catch_warnings(): + warnings.simplefilter("ignore") astnode = ast.parse(content, "source", "exec") - - start, end = get_statement_startend2(lineno, astnode) + + start, end = get_statement_startend2(lineno, astnode) # We need to correct the end: - # - ast-parsing strips comments - # - there might be empty lines - # - we might have lesser indented code blocks at the end - if end is None: - end = len(source.lines) - - if end > start + 1: + # - ast-parsing strips comments + # - there might be empty lines + # - we might have lesser indented code blocks at the end + if end is None: + end = len(source.lines) + + if end > start + 1: # Make sure we don't span differently indented code blocks # by using the BlockFinder helper used which inspect.getsource() uses itself. - block_finder = inspect.BlockFinder() + block_finder = inspect.BlockFinder() # If we start with an indented line, put blockfinder to "started" mode. - block_finder.started = source.lines[start][0].isspace() - it = ((x + "\n") for x in source.lines[start:end]) - try: - for tok in tokenize.generate_tokens(lambda: next(it)): - block_finder.tokeneater(*tok) - except (inspect.EndOfBlock, IndentationError): - end = block_finder.last + start - except Exception: - pass - + block_finder.started = source.lines[start][0].isspace() + it = ((x + "\n") for x in source.lines[start:end]) + try: + for tok in tokenize.generate_tokens(lambda: next(it)): + block_finder.tokeneater(*tok) + except (inspect.EndOfBlock, IndentationError): + end = block_finder.last + start + except Exception: + pass + # The end might still point to a comment or empty line, correct it. - while end: - line = source.lines[end - 1].lstrip() - if line.startswith("#") or not line: - end -= 1 - else: - break - return astnode, start, end + while end: + line = source.lines[end - 1].lstrip() + if line.startswith("#") or not line: + end -= 1 + else: + break + return astnode, start, end |