From 69143fe5b0a1037198b6ff1b151dc571e3800cec Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Thu, 30 Apr 2020 20:54:46 +0300 Subject: [PATCH] code: fix import cycles between code.py and source.py These two files were really intertwined. Make it so code.py depends on source.py without a reverse dependency. No functional changes. --- src/_pytest/_code/__init__.py | 32 ++++++++++----- src/_pytest/_code/code.py | 71 +++++++++++++++++++--------------- src/_pytest/_code/source.py | 56 +++++++-------------------- src/_pytest/fixtures.py | 2 +- src/_pytest/mark/structures.py | 2 +- src/_pytest/nodes.py | 2 +- src/_pytest/python.py | 2 +- 7 files changed, 81 insertions(+), 86 deletions(-) diff --git a/src/_pytest/_code/__init__.py b/src/_pytest/_code/__init__.py index 370e41dc9..38019298c 100644 --- a/src/_pytest/_code/__init__.py +++ b/src/_pytest/_code/__init__.py @@ -1,10 +1,22 @@ -""" python inspection/code generation API """ -from .code import Code # noqa -from .code import ExceptionInfo # noqa -from .code import filter_traceback # noqa -from .code import Frame # noqa -from .code import getrawcode # noqa -from .code import Traceback # noqa -from .source import compile_ as compile # noqa -from .source import getfslineno # noqa -from .source import Source # noqa +"""Python inspection/code generation API.""" +from .code import Code +from .code import ExceptionInfo +from .code import filter_traceback +from .code import Frame +from .code import getfslineno +from .code import getrawcode +from .code import Traceback +from .source import compile_ as compile +from .source import Source + +__all__ = [ + "Code", + "ExceptionInfo", + "filter_traceback", + "Frame", + "getfslineno", + "getrawcode", + "Traceback", + "compile", + "Source", +] diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index 02efc7172..6102084f0 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -29,10 +29,15 @@ 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 +from _pytest._code.source import Source from _pytest._io import TerminalWriter from _pytest._io.saferepr import safeformat from _pytest._io.saferepr import saferepr from _pytest.compat import ATTRS_EQ_FIELD +from _pytest.compat import get_real_func from _pytest.compat import overload from _pytest.compat import TYPE_CHECKING @@ -41,8 +46,6 @@ if TYPE_CHECKING: from typing_extensions import Literal from weakref import ReferenceType # noqa: F401 - from _pytest._code import Source - _TracebackStyle = Literal["long", "short", "line", "no", "native"] @@ -90,18 +93,14 @@ class Code: def fullsource(self) -> Optional["Source"]: """ return a _pytest._code.Source object for the full source file of the code """ - from _pytest._code import source - - full, _ = source.findsource(self.raw) + full, _ = findsource(self.raw) 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 - import _pytest._code - - return _pytest._code.Source(self.raw) + return Source(self.raw) def getargs(self, var: bool = False) -> Tuple[str, ...]: """ return a tuple with the argument names for the code object @@ -132,10 +131,8 @@ class Frame: @property def statement(self) -> "Source": """ statement this frame is at """ - import _pytest._code - if self.code.fullsource is None: - return _pytest._code.Source("") + return Source("") return self.code.fullsource.getstatement(self.lineno) def eval(self, code, **vars): @@ -231,8 +228,6 @@ class TracebackEntry: """ return failing source code. """ # we use the passed in astcache to not reparse asttrees # within exception info printing - from _pytest._code.source import getstatementrange_ast - source = self.frame.code.fullsource if source is None: return None @@ -703,11 +698,9 @@ class FormattedExcinfo: short: bool = False, ) -> List[str]: """ return formatted and marked up source lines. """ - import _pytest._code - lines = [] if source is None or line_index >= len(source.lines): - source = _pytest._code.Source("???") + source = Source("???") line_index = 0 if line_index < 0: line_index += len(source) @@ -769,11 +762,9 @@ class FormattedExcinfo: def repr_traceback_entry( self, entry: TracebackEntry, excinfo: Optional[ExceptionInfo] = None ) -> "ReprEntry": - import _pytest._code - source = self._getentrysource(entry) if source is None: - source = _pytest._code.Source("???") + source = Source("???") line_index = 0 else: line_index = entry.lineno - entry.getfirstlinesource() @@ -1150,19 +1141,37 @@ class ReprFuncArgs(TerminalRepr): tw.line("") -def getrawcode(obj, trycall: bool = True): - """ return code object for given function. """ +def getfslineno(obj: Any) -> 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. + """ + # xxx let decorators etc specify a sane ordering + # NOTE: this used to be done in _pytest.compat.getfslineno, initially added + # 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 + try: - return obj.__code__ - except AttributeError: - obj = getattr(obj, "f_code", obj) - obj = getattr(obj, "__code__", obj) - if trycall and not hasattr(obj, "co_firstlineno"): - if hasattr(obj, "__call__") and not inspect.isclass(obj): - x = getrawcode(obj.__call__, trycall=False) - if hasattr(x, "co_firstlineno"): - return x - return obj + code = Code(obj) + except TypeError: + try: + fn = inspect.getsourcefile(obj) or inspect.getfile(obj) + except TypeError: + return "", -1 + + fspath = fn and py.path.local(fn) or "" + lineno = -1 + if fspath: + try: + _, lineno = findsource(obj) + except OSError: + pass + return fspath, lineno + else: + return code.path, code.firstlineno # relative paths that we use to filter traceback entries from appearing to the user; diff --git a/src/_pytest/_code/source.py b/src/_pytest/_code/source.py index 2e44b69d2..3f732792f 100644 --- a/src/_pytest/_code/source.py +++ b/src/_pytest/_code/source.py @@ -8,7 +8,6 @@ import warnings from bisect import bisect_right from types import CodeType from types import FrameType -from typing import Any from typing import Iterator from typing import List from typing import Optional @@ -18,7 +17,6 @@ from typing import Union import py -from _pytest.compat import get_real_func from _pytest.compat import overload from _pytest.compat import TYPE_CHECKING @@ -279,41 +277,6 @@ def compile_( # noqa: F811 return s.compile(filename, mode, flags, _genframe=_genframe) -def getfslineno(obj: Any) -> 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. - """ - from .code import Code - - # xxx let decorators etc specify a sane ordering - # NOTE: this used to be done in _pytest.compat.getfslineno, initially added - # 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 - - try: - code = Code(obj) - except TypeError: - try: - fn = inspect.getsourcefile(obj) or inspect.getfile(obj) - except TypeError: - return "", -1 - - fspath = fn and py.path.local(fn) or "" - lineno = -1 - if fspath: - try: - _, lineno = findsource(obj) - except OSError: - pass - return fspath, lineno - else: - return code.path, code.firstlineno - - # # helper functions # @@ -329,9 +292,22 @@ def findsource(obj) -> Tuple[Optional[Source], int]: return source, lineno -def getsource(obj, **kwargs) -> Source: - from .code import getrawcode +def getrawcode(obj, trycall: bool = True): + """ return code object for given function. """ + try: + return obj.__code__ + except AttributeError: + obj = getattr(obj, "f_code", obj) + obj = getattr(obj, "__code__", obj) + if trycall and not hasattr(obj, "co_firstlineno"): + if hasattr(obj, "__call__") and not inspect.isclass(obj): + x = getrawcode(obj.__call__, trycall=False) + if hasattr(x, "co_firstlineno"): + return x + return obj + +def getsource(obj, **kwargs) -> Source: obj = getrawcode(obj) try: strsrc = inspect.getsource(obj) @@ -346,8 +322,6 @@ def deindent(lines: Sequence[str]) -> List[str]: def get_statement_startend2(lineno: int, node: ast.AST) -> Tuple[int, Optional[int]]: - import ast - # flatten all statements and except handlers into one lineno-list # AST's line numbers start indexing at 1 values = [] # type: List[int] diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index f673885c7..fef82adac 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -13,9 +13,9 @@ import attr import py import _pytest +from _pytest._code import getfslineno from _pytest._code.code import FormattedExcinfo from _pytest._code.code import TerminalRepr -from _pytest._code.source import getfslineno from _pytest._io import TerminalWriter from _pytest.compat import _format_args from _pytest.compat import _PytestWrapper diff --git a/src/_pytest/mark/structures.py b/src/_pytest/mark/structures.py index bcbfbd72e..a34a0c28d 100644 --- a/src/_pytest/mark/structures.py +++ b/src/_pytest/mark/structures.py @@ -14,7 +14,7 @@ from typing import Union import attr -from .._code.source import getfslineno +from .._code import getfslineno from ..compat import ascii_escaped from ..compat import NOTSET from _pytest.outcomes import fail diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index 03a4b1af8..ad8f77ae8 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -12,10 +12,10 @@ from typing import Union import py import _pytest._code +from _pytest._code import getfslineno from _pytest._code.code import ExceptionChainRepr from _pytest._code.code import ExceptionInfo from _pytest._code.code import ReprExceptionInfo -from _pytest._code.source import getfslineno from _pytest.compat import cached_property from _pytest.compat import TYPE_CHECKING from _pytest.config import Config diff --git a/src/_pytest/python.py b/src/_pytest/python.py index e1bd62f0b..9f4af9c62 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -25,8 +25,8 @@ import _pytest from _pytest import fixtures from _pytest import nodes from _pytest._code import filter_traceback +from _pytest._code import getfslineno from _pytest._code.code import ExceptionInfo -from _pytest._code.source import getfslineno from _pytest._io import TerminalWriter from _pytest._io.saferepr import saferepr from _pytest.compat import ascii_escaped