Skip to content

code: fix import cycles between code.py and source.py #7171

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 22 additions & 10 deletions src/_pytest/_code/__init__.py
Original file line number Diff line number Diff line change
@@ -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",
]
71 changes: 40 additions & 31 deletions src/_pytest/_code/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,15 @@
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

Expand All @@ -41,8 +46,6 @@
from typing_extensions import Literal
from weakref import ReferenceType # noqa: F401

from _pytest._code import Source

_TracebackStyle = Literal["long", "short", "line", "no", "native"]


Expand Down Expand Up @@ -90,18 +93,14 @@ def path(self) -> Union[py.path.local, str]:
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
Expand Down Expand Up @@ -132,10 +131,8 @@ def __init__(self, frame: FrameType) -> None:
@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):
Expand Down Expand Up @@ -231,8 +228,6 @@ 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
from _pytest._code.source import getstatementrange_ast

source = self.frame.code.fullsource
if source is None:
return None
Expand Down Expand Up @@ -703,11 +698,9 @@ def get_source(
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)
Expand Down Expand Up @@ -769,11 +762,9 @@ def repr_locals(self, locals: Dict[str, object]) -> Optional["ReprLocals"]:
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()
Expand Down Expand Up @@ -1150,19 +1141,37 @@ def toterminal(self, tw: TerminalWriter) -> None:
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;
Expand Down
56 changes: 15 additions & 41 deletions src/_pytest/_code/source.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
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
Expand All @@ -18,7 +17,6 @@

import py

from _pytest.compat import get_real_func
from _pytest.compat import overload
from _pytest.compat import TYPE_CHECKING

Expand Down Expand Up @@ -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
#
Expand All @@ -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)
Expand All @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion src/_pytest/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
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
Expand Down
2 changes: 1 addition & 1 deletion src/_pytest/mark/structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

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
Expand Down
2 changes: 1 addition & 1 deletion src/_pytest/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
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
Expand Down
2 changes: 1 addition & 1 deletion src/_pytest/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
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
Expand Down