Merge pull request #7171 from bluetech/code-import-cycles

code: fix import cycles between code.py and source.py
This commit is contained in:
Ran Benita 2020-05-06 18:15:32 +03:00 committed by GitHub
commit d16ae0bbdc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 81 additions and 86 deletions

View File

@ -1,10 +1,22 @@
""" python inspection/code generation API """ """Python inspection/code generation API."""
from .code import Code # noqa from .code import Code
from .code import ExceptionInfo # noqa from .code import ExceptionInfo
from .code import filter_traceback # noqa from .code import filter_traceback
from .code import Frame # noqa from .code import Frame
from .code import getrawcode # noqa from .code import getfslineno
from .code import Traceback # noqa from .code import getrawcode
from .source import compile_ as compile # noqa from .code import Traceback
from .source import getfslineno # noqa from .source import compile_ as compile
from .source import Source # noqa from .source import Source
__all__ = [
"Code",
"ExceptionInfo",
"filter_traceback",
"Frame",
"getfslineno",
"getrawcode",
"Traceback",
"compile",
"Source",
]

View File

@ -29,10 +29,15 @@ import pluggy
import py import py
import _pytest 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 import TerminalWriter
from _pytest._io.saferepr import safeformat from _pytest._io.saferepr import safeformat
from _pytest._io.saferepr import saferepr from _pytest._io.saferepr import saferepr
from _pytest.compat import ATTRS_EQ_FIELD from _pytest.compat import ATTRS_EQ_FIELD
from _pytest.compat import get_real_func
from _pytest.compat import overload from _pytest.compat import overload
from _pytest.compat import TYPE_CHECKING from _pytest.compat import TYPE_CHECKING
@ -41,8 +46,6 @@ if TYPE_CHECKING:
from typing_extensions import Literal from typing_extensions import Literal
from weakref import ReferenceType # noqa: F401 from weakref import ReferenceType # noqa: F401
from _pytest._code import Source
_TracebackStyle = Literal["long", "short", "line", "no", "native"] _TracebackStyle = Literal["long", "short", "line", "no", "native"]
@ -90,18 +93,14 @@ class Code:
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
""" """
from _pytest._code import source full, _ = findsource(self.raw)
full, _ = source.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
import _pytest._code return Source(self.raw)
return _pytest._code.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
@ -132,10 +131,8 @@ class Frame:
@property @property
def statement(self) -> "Source": def statement(self) -> "Source":
""" statement this frame is at """ """ statement this frame is at """
import _pytest._code
if self.code.fullsource is None: if self.code.fullsource is None:
return _pytest._code.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):
@ -231,8 +228,6 @@ class TracebackEntry:
""" 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
from _pytest._code.source import getstatementrange_ast
source = self.frame.code.fullsource source = self.frame.code.fullsource
if source is None: if source is None:
return None return None
@ -703,11 +698,9 @@ class FormattedExcinfo:
short: bool = False, short: bool = False,
) -> List[str]: ) -> List[str]:
""" return formatted and marked up source lines. """ """ return formatted and marked up source lines. """
import _pytest._code
lines = [] lines = []
if source is None or line_index >= len(source.lines): if source is None or line_index >= len(source.lines):
source = _pytest._code.Source("???") source = Source("???")
line_index = 0 line_index = 0
if line_index < 0: if line_index < 0:
line_index += len(source) line_index += len(source)
@ -769,11 +762,9 @@ class FormattedExcinfo:
def repr_traceback_entry( def repr_traceback_entry(
self, entry: TracebackEntry, excinfo: Optional[ExceptionInfo] = None self, entry: TracebackEntry, excinfo: Optional[ExceptionInfo] = None
) -> "ReprEntry": ) -> "ReprEntry":
import _pytest._code
source = self._getentrysource(entry) source = self._getentrysource(entry)
if source is None: if source is None:
source = _pytest._code.Source("???") source = Source("???")
line_index = 0 line_index = 0
else: else:
line_index = entry.lineno - entry.getfirstlinesource() line_index = entry.lineno - entry.getfirstlinesource()
@ -1150,19 +1141,37 @@ class ReprFuncArgs(TerminalRepr):
tw.line("") tw.line("")
def getrawcode(obj, trycall: bool = True): def getfslineno(obj: Any) -> Tuple[Union[str, py.path.local], int]:
""" return code object for given function. """ """ 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: try:
return obj.__code__ code = Code(obj)
except AttributeError: except TypeError:
obj = getattr(obj, "f_code", obj) try:
obj = getattr(obj, "__code__", obj) fn = inspect.getsourcefile(obj) or inspect.getfile(obj)
if trycall and not hasattr(obj, "co_firstlineno"): except TypeError:
if hasattr(obj, "__call__") and not inspect.isclass(obj): return "", -1
x = getrawcode(obj.__call__, trycall=False)
if hasattr(x, "co_firstlineno"): fspath = fn and py.path.local(fn) or ""
return x lineno = -1
return obj 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; # relative paths that we use to filter traceback entries from appearing to the user;

View File

@ -8,7 +8,6 @@ import warnings
from bisect import bisect_right from bisect import bisect_right
from types import CodeType from types import CodeType
from types import FrameType from types import FrameType
from typing import Any
from typing import Iterator from typing import Iterator
from typing import List from typing import List
from typing import Optional from typing import Optional
@ -18,7 +17,6 @@ from typing import Union
import py import py
from _pytest.compat import get_real_func
from _pytest.compat import overload from _pytest.compat import overload
from _pytest.compat import TYPE_CHECKING from _pytest.compat import TYPE_CHECKING
@ -279,41 +277,6 @@ def compile_( # noqa: F811
return s.compile(filename, mode, flags, _genframe=_genframe) 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 # helper functions
# #
@ -329,9 +292,22 @@ def findsource(obj) -> Tuple[Optional[Source], int]:
return source, lineno return source, lineno
def getsource(obj, **kwargs) -> Source: def getrawcode(obj, trycall: bool = True):
from .code import getrawcode """ 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) obj = getrawcode(obj)
try: try:
strsrc = inspect.getsource(obj) 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]]: 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 # flatten all statements and except handlers into one lineno-list
# AST's line numbers start indexing at 1 # AST's line numbers start indexing at 1
values = [] # type: List[int] values = [] # type: List[int]

View File

@ -12,9 +12,9 @@ import attr
import py import py
import _pytest import _pytest
from _pytest._code import getfslineno
from _pytest._code.code import FormattedExcinfo from _pytest._code.code import FormattedExcinfo
from _pytest._code.code import TerminalRepr from _pytest._code.code import TerminalRepr
from _pytest._code.source import getfslineno
from _pytest._io import TerminalWriter from _pytest._io import TerminalWriter
from _pytest.compat import _format_args from _pytest.compat import _format_args
from _pytest.compat import _PytestWrapper from _pytest.compat import _PytestWrapper

View File

@ -14,7 +14,7 @@ from typing import Union
import attr import attr
from .._code.source import getfslineno from .._code import getfslineno
from ..compat import ascii_escaped from ..compat import ascii_escaped
from ..compat import NOTSET from ..compat import NOTSET
from _pytest.outcomes import fail from _pytest.outcomes import fail

View File

@ -12,10 +12,10 @@ from typing import Union
import py import py
import _pytest._code import _pytest._code
from _pytest._code import getfslineno
from _pytest._code.code import ExceptionChainRepr from _pytest._code.code import ExceptionChainRepr
from _pytest._code.code import ExceptionInfo from _pytest._code.code import ExceptionInfo
from _pytest._code.code import ReprExceptionInfo from _pytest._code.code import ReprExceptionInfo
from _pytest._code.source import getfslineno
from _pytest.compat import cached_property from _pytest.compat import cached_property
from _pytest.compat import TYPE_CHECKING from _pytest.compat import TYPE_CHECKING
from _pytest.config import Config from _pytest.config import Config

View File

@ -25,8 +25,8 @@ import _pytest
from _pytest import fixtures from _pytest import fixtures
from _pytest import nodes from _pytest import nodes
from _pytest._code import filter_traceback from _pytest._code import filter_traceback
from _pytest._code import getfslineno
from _pytest._code.code import ExceptionInfo from _pytest._code.code import ExceptionInfo
from _pytest._code.source import getfslineno
from _pytest._io import TerminalWriter from _pytest._io import TerminalWriter
from _pytest._io.saferepr import saferepr from _pytest._io.saferepr import saferepr
from _pytest.compat import ascii_escaped from _pytest.compat import ascii_escaped