|
1 | 1 | import ast
|
2 | 2 | import inspect
|
3 |
| -import linecache |
4 |
| -import sys |
5 | 3 | import textwrap
|
6 | 4 | import tokenize
|
7 | 5 | import warnings
|
8 | 6 | from bisect import bisect_right
|
9 |
| -from types import CodeType |
10 |
| -from types import FrameType |
| 7 | +from typing import Iterable |
11 | 8 | from typing import Iterator
|
12 | 9 | from typing import List
|
13 | 10 | from typing import Optional
|
14 |
| -from typing import Sequence |
15 | 11 | from typing import Tuple
|
16 | 12 | from typing import Union
|
17 | 13 |
|
18 |
| -import py |
19 |
| - |
20 | 14 | from _pytest.compat import overload
|
21 |
| -from _pytest.compat import TYPE_CHECKING |
22 |
| - |
23 |
| -if TYPE_CHECKING: |
24 |
| - from typing_extensions import Literal |
25 | 15 |
|
26 | 16 |
|
27 | 17 | 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. |
30 | 21 | """
|
31 | 22 |
|
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 |
59 | 41 |
|
60 | 42 | # Ignore type because of https://github.com/python/mypy/issues/4266.
|
61 | 43 | __hash__ = None # type: ignore
|
@@ -97,19 +79,6 @@ def strip(self) -> "Source":
|
97 | 79 | source.lines[:] = self.lines[start:end]
|
98 | 80 | return source
|
99 | 81 |
|
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 |
| - |
113 | 82 | def indent(self, indent: str = " " * 4) -> "Source":
|
114 | 83 | """ return a copy of the source object with
|
115 | 84 | all lines indented by the given indent-string.
|
@@ -140,142 +109,9 @@ def deindent(self) -> "Source":
|
140 | 109 | newsource.lines[:] = deindent(self.lines)
|
141 | 110 | return newsource
|
142 | 111 |
|
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 |
| - |
158 | 112 | def __str__(self) -> str:
|
159 | 113 | return "\n".join(self.lines)
|
160 | 114 |
|
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 |
| - |
279 | 115 |
|
280 | 116 | #
|
281 | 117 | # helper functions
|
@@ -307,17 +143,7 @@ def getrawcode(obj, trycall: bool = True):
|
307 | 143 | return obj
|
308 | 144 |
|
309 | 145 |
|
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]: |
321 | 147 | return textwrap.dedent("\n".join(lines)).splitlines()
|
322 | 148 |
|
323 | 149 |
|
|
0 commit comments