Skip to content

Commit 36b958c

Browse files
authored
Merge pull request #7438 from bluetech/source-cleanups
code/source: some cleanups
2 parents 64dd700 + 11efe05 commit 36b958c

File tree

7 files changed

+107
-417
lines changed

7 files changed

+107
-417
lines changed

changelog/7438.breaking.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
Some changes were made to the internal ``_pytest._code.source``, listed here
2+
for the benefit of plugin authors who may be using it:
3+
4+
- The ``deindent`` argument to ``Source()`` has been removed, now it is always true.
5+
- Support for zero or multiple arguments to ``Source()`` has been removed.
6+
- Support for comparing ``Source`` with an ``str`` has been removed.
7+
- The methods ``Source.isparseable()`` and ``Source.putaround()`` have been removed.
8+
- The method ``Source.compile()`` and function ``_pytest._code.compile()`` have
9+
been removed; use plain ``compile()`` instead.
10+
- The function ``_pytest._code.source.getsource()`` has been removed; use
11+
``Source()`` directly instead.

src/_pytest/_code/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
from .code import getrawcode
88
from .code import Traceback
99
from .code import TracebackEntry
10-
from .source import compile_ as compile
1110
from .source import Source
1211

1312
__all__ = [
@@ -19,6 +18,5 @@
1918
"getrawcode",
2019
"Traceback",
2120
"TracebackEntry",
22-
"compile",
2321
"Source",
2422
]

src/_pytest/_code/source.py

Lines changed: 23 additions & 197 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,43 @@
11
import ast
22
import inspect
3-
import linecache
4-
import sys
53
import textwrap
64
import tokenize
75
import warnings
86
from bisect import bisect_right
9-
from types import CodeType
10-
from types import FrameType
7+
from typing import Iterable
118
from typing import Iterator
129
from typing import List
1310
from typing import Optional
14-
from typing import Sequence
1511
from typing import Tuple
1612
from typing import Union
1713

18-
import py
19-
2014
from _pytest.compat import overload
21-
from _pytest.compat import TYPE_CHECKING
22-
23-
if TYPE_CHECKING:
24-
from typing_extensions import Literal
2515

2616

2717
class Source:
28-
""" an immutable object holding a source code fragment,
29-
possibly deindenting it.
18+
"""An immutable object holding a source code fragment.
19+
20+
When using Source(...), the source lines are deindented.
3021
"""
3122

32-
_compilecounter = 0
33-
34-
def __init__(self, *parts, **kwargs) -> None:
35-
self.lines = lines = [] # type: List[str]
36-
de = kwargs.get("deindent", True)
37-
for part in parts:
38-
if not part:
39-
partlines = [] # type: List[str]
40-
elif isinstance(part, Source):
41-
partlines = part.lines
42-
elif isinstance(part, (tuple, list)):
43-
partlines = [x.rstrip("\n") for x in part]
44-
elif isinstance(part, str):
45-
partlines = part.split("\n")
46-
else:
47-
partlines = getsource(part, deindent=de).lines
48-
if de:
49-
partlines = deindent(partlines)
50-
lines.extend(partlines)
51-
52-
def __eq__(self, other):
53-
try:
54-
return self.lines == other.lines
55-
except AttributeError:
56-
if isinstance(other, str):
57-
return str(self) == other
58-
return False
23+
def __init__(self, obj: object = None) -> None:
24+
if not obj:
25+
self.lines = [] # type: List[str]
26+
elif isinstance(obj, Source):
27+
self.lines = obj.lines
28+
elif isinstance(obj, (tuple, list)):
29+
self.lines = deindent(x.rstrip("\n") for x in obj)
30+
elif isinstance(obj, str):
31+
self.lines = deindent(obj.split("\n"))
32+
else:
33+
rawcode = getrawcode(obj)
34+
src = inspect.getsource(rawcode)
35+
self.lines = deindent(src.split("\n"))
36+
37+
def __eq__(self, other: object) -> bool:
38+
if not isinstance(other, Source):
39+
return NotImplemented
40+
return self.lines == other.lines
5941

6042
# Ignore type because of https://github.com/python/mypy/issues/4266.
6143
__hash__ = None # type: ignore
@@ -97,19 +79,6 @@ def strip(self) -> "Source":
9779
source.lines[:] = self.lines[start:end]
9880
return source
9981

100-
def putaround(
101-
self, before: str = "", after: str = "", indent: str = " " * 4
102-
) -> "Source":
103-
""" return a copy of the source object with
104-
'before' and 'after' wrapped around it.
105-
"""
106-
beforesource = Source(before)
107-
aftersource = Source(after)
108-
newsource = Source()
109-
lines = [(indent + line) for line in self.lines]
110-
newsource.lines = beforesource.lines + lines + aftersource.lines
111-
return newsource
112-
11382
def indent(self, indent: str = " " * 4) -> "Source":
11483
""" return a copy of the source object with
11584
all lines indented by the given indent-string.
@@ -140,142 +109,9 @@ def deindent(self) -> "Source":
140109
newsource.lines[:] = deindent(self.lines)
141110
return newsource
142111

143-
def isparseable(self, deindent: bool = True) -> bool:
144-
""" return True if source is parseable, heuristically
145-
deindenting it by default.
146-
"""
147-
if deindent:
148-
source = str(self.deindent())
149-
else:
150-
source = str(self)
151-
try:
152-
ast.parse(source)
153-
except (SyntaxError, ValueError, TypeError):
154-
return False
155-
else:
156-
return True
157-
158112
def __str__(self) -> str:
159113
return "\n".join(self.lines)
160114

161-
@overload
162-
def compile(
163-
self,
164-
filename: Optional[str] = ...,
165-
mode: str = ...,
166-
flag: "Literal[0]" = ...,
167-
dont_inherit: int = ...,
168-
_genframe: Optional[FrameType] = ...,
169-
) -> CodeType:
170-
raise NotImplementedError()
171-
172-
@overload # noqa: F811
173-
def compile( # noqa: F811
174-
self,
175-
filename: Optional[str] = ...,
176-
mode: str = ...,
177-
flag: int = ...,
178-
dont_inherit: int = ...,
179-
_genframe: Optional[FrameType] = ...,
180-
) -> Union[CodeType, ast.AST]:
181-
raise NotImplementedError()
182-
183-
def compile( # noqa: F811
184-
self,
185-
filename: Optional[str] = None,
186-
mode: str = "exec",
187-
flag: int = 0,
188-
dont_inherit: int = 0,
189-
_genframe: Optional[FrameType] = None,
190-
) -> Union[CodeType, ast.AST]:
191-
""" return compiled code object. if filename is None
192-
invent an artificial filename which displays
193-
the source/line position of the caller frame.
194-
"""
195-
if not filename or py.path.local(filename).check(file=0):
196-
if _genframe is None:
197-
_genframe = sys._getframe(1) # the caller
198-
fn, lineno = _genframe.f_code.co_filename, _genframe.f_lineno
199-
base = "<%d-codegen " % self._compilecounter
200-
self.__class__._compilecounter += 1
201-
if not filename:
202-
filename = base + "%s:%d>" % (fn, lineno)
203-
else:
204-
filename = base + "%r %s:%d>" % (filename, fn, lineno)
205-
source = "\n".join(self.lines) + "\n"
206-
try:
207-
co = compile(source, filename, mode, flag)
208-
except SyntaxError as ex:
209-
# re-represent syntax errors from parsing python strings
210-
msglines = self.lines[: ex.lineno]
211-
if ex.offset:
212-
msglines.append(" " * ex.offset + "^")
213-
msglines.append("(code was compiled probably from here: %s)" % filename)
214-
newex = SyntaxError("\n".join(msglines))
215-
newex.offset = ex.offset
216-
newex.lineno = ex.lineno
217-
newex.text = ex.text
218-
raise newex from ex
219-
else:
220-
if flag & ast.PyCF_ONLY_AST:
221-
assert isinstance(co, ast.AST)
222-
return co
223-
assert isinstance(co, CodeType)
224-
lines = [(x + "\n") for x in self.lines]
225-
# Type ignored because linecache.cache is private.
226-
linecache.cache[filename] = (1, None, lines, filename) # type: ignore
227-
return co
228-
229-
230-
#
231-
# public API shortcut functions
232-
#
233-
234-
235-
@overload
236-
def compile_(
237-
source: Union[str, bytes, ast.mod, ast.AST],
238-
filename: Optional[str] = ...,
239-
mode: str = ...,
240-
flags: "Literal[0]" = ...,
241-
dont_inherit: int = ...,
242-
) -> CodeType:
243-
raise NotImplementedError()
244-
245-
246-
@overload # noqa: F811
247-
def compile_( # noqa: F811
248-
source: Union[str, bytes, ast.mod, ast.AST],
249-
filename: Optional[str] = ...,
250-
mode: str = ...,
251-
flags: int = ...,
252-
dont_inherit: int = ...,
253-
) -> Union[CodeType, ast.AST]:
254-
raise NotImplementedError()
255-
256-
257-
def compile_( # noqa: F811
258-
source: Union[str, bytes, ast.mod, ast.AST],
259-
filename: Optional[str] = None,
260-
mode: str = "exec",
261-
flags: int = 0,
262-
dont_inherit: int = 0,
263-
) -> Union[CodeType, ast.AST]:
264-
""" compile the given source to a raw code object,
265-
and maintain an internal cache which allows later
266-
retrieval of the source code for the code object
267-
and any recursively created code objects.
268-
"""
269-
if isinstance(source, ast.AST):
270-
# XXX should Source support having AST?
271-
assert filename is not None
272-
co = compile(source, filename, mode, flags, dont_inherit)
273-
assert isinstance(co, (CodeType, ast.AST))
274-
return co
275-
_genframe = sys._getframe(1) # the caller
276-
s = Source(source)
277-
return s.compile(filename, mode, flags, _genframe=_genframe)
278-
279115

280116
#
281117
# helper functions
@@ -307,17 +143,7 @@ def getrawcode(obj, trycall: bool = True):
307143
return obj
308144

309145

310-
def getsource(obj, **kwargs) -> Source:
311-
obj = getrawcode(obj)
312-
try:
313-
strsrc = inspect.getsource(obj)
314-
except IndentationError:
315-
strsrc = '"Buggy python version consider upgrading, cannot get source"'
316-
assert isinstance(strsrc, str)
317-
return Source(strsrc, **kwargs)
318-
319-
320-
def deindent(lines: Sequence[str]) -> List[str]:
146+
def deindent(lines: Iterable[str]) -> List[str]:
321147
return textwrap.dedent("\n".join(lines)).splitlines()
322148

323149

src/_pytest/skipping.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
import attr
1111

12-
import _pytest._code
1312
from _pytest.compat import TYPE_CHECKING
1413
from _pytest.config import Config
1514
from _pytest.config import hookimpl
@@ -105,7 +104,8 @@ def evaluate_condition(item: Item, mark: Mark, condition: object) -> Tuple[bool,
105104
if hasattr(item, "obj"):
106105
globals_.update(item.obj.__globals__) # type: ignore[attr-defined]
107106
try:
108-
condition_code = _pytest._code.compile(condition, mode="eval")
107+
filename = "<{} condition>".format(mark.name)
108+
condition_code = compile(condition, filename, "eval")
109109
result = eval(condition_code, globals_)
110110
except SyntaxError as exc:
111111
msglines = [

testing/code/test_code.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from _pytest._code import Code
77
from _pytest._code import ExceptionInfo
88
from _pytest._code import Frame
9+
from _pytest._code import Source
910
from _pytest._code.code import ExceptionChainRepr
1011
from _pytest._code.code import ReprFuncArgs
1112

@@ -67,7 +68,7 @@ def func() -> FrameType:
6768

6869
f = Frame(func())
6970
with mock.patch.object(f.code.__class__, "fullsource", None):
70-
assert f.statement == ""
71+
assert f.statement == Source("")
7172

7273

7374
def test_code_from_func() -> None:

0 commit comments

Comments
 (0)