Coverage for coverage / python.py: 97.884%
145 statements
« prev ^ index » next coverage.py v7.12.1a0.dev1, created at 2025-11-30 17:57 +0000
« prev ^ index » next coverage.py v7.12.1a0.dev1, created at 2025-11-30 17:57 +0000
1# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
2# For details: https://github.com/coveragepy/coveragepy/blob/main/NOTICE.txt
4"""Python source expertise for coverage.py"""
6from __future__ import annotations 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
8import os.path 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
9import types 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
10import zipimport 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
11from collections.abc import Iterable 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
12from typing import TYPE_CHECKING 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
14from coverage import env 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
15from coverage.exceptions import CoverageException, NoSource 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
16from coverage.files import canonical_filename, relative_filename, zip_location 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
17from coverage.misc import isolate_module, join_regex 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
18from coverage.parser import PythonParser 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
19from coverage.phystokens import source_encoding, source_token_lines 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
20from coverage.plugin import CodeRegion, FileReporter 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
21from coverage.regions import code_regions 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
22from coverage.types import TArc, TLineNo, TMorf, TSourceTokenLines 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
24if TYPE_CHECKING: 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
25 from coverage import Coverage
27# Protect ourselves against aggressive mocking.
28os = isolate_module(os) 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
29# Save the original `open` function so later mocks don't break us.
30open = open # pylint: disable=redefined-builtin 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
33def read_python_source(filename: str) -> bytes: 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
34 """Read the Python source text from `filename`.
36 Returns bytes.
38 """
39 with open(filename, "rb") as f: 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
40 source = f.read() 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
42 return source.replace(b"\r\n", b"\n").replace(b"\r", b"\n") 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
45def get_python_source(filename: str) -> str: 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
46 """Return the source code, as unicode."""
47 base, ext = os.path.splitext(filename) 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
48 if ext == ".py" and env.WINDOWS: 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
49 exts = [".py", ".pyw"] 1abcdefghijkl%mn'op(qr)s
50 else:
51 exts = [ext] 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
53 source_bytes: bytes | None
54 for ext in exts: 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
55 try_filename = base + ext 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
56 if os.path.exists(try_filename): 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
57 # A regular text file: open it.
58 source_bytes = read_python_source(try_filename) 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
59 break 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
61 # Maybe it's in a zip file?
62 source_bytes = get_zip_bytes(try_filename) 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
63 if source_bytes is not None: 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
64 break 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
65 else:
66 # Couldn't find source.
67 raise NoSource(f"No source for code: '{filename}'.", slug="no-source") 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
69 # Replace \f because of http://bugs.python.org/issue19035
70 source_bytes = source_bytes.replace(b"\f", b" ") 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
71 source = source_bytes.decode(source_encoding(source_bytes), "replace") 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
73 # Python code should always end with a line with a newline.
74 if source and source[-1] != "\n": 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
75 source += "\n" 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
77 return source 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
80def get_zip_bytes(filename: str) -> bytes | None: 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
81 """Get data from `filename` if it is a zip file path.
83 Returns the bytestring data read from the zip file, or None if no zip file
84 could be found or `filename` isn't in it. The data returned will be
85 an empty string if the file is empty.
87 """
88 zipfile_inner = zip_location(filename) 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
89 if zipfile_inner is not None: 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
90 zipfile, inner = zipfile_inner 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
91 try: 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
92 zi = zipimport.zipimporter(zipfile) 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
93 except zipimport.ZipImportError:
94 return None
95 try: 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
96 data = zi.get_data(inner) 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
97 except OSError:
98 return None
99 return data 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
100 return None 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
103def source_for_file(filename: str) -> str: 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
104 """Return the source filename for `filename`.
106 Given a file name being traced, return the best guess as to the source
107 file to attribute it to.
109 """
110 if filename.endswith(".py"): 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
111 # .py files are themselves source files.
112 return filename 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
114 elif filename.endswith((".pyc", ".pyo")): 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
115 # Bytecode files probably have source files near them.
116 py_filename = filename[:-1] 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ0125qr34s
117 if os.path.exists(py_filename): 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ0125qr34s
118 # Found a .py file, use that.
119 return py_filename 1abcdefghijklmnopqrs
120 if env.WINDOWS: 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ0125qr34s
121 # On Windows, it could be a .pyw file.
122 pyw_filename = py_filename + "w" 1abcdefghijklmnopqrs
123 if os.path.exists(pyw_filename): 1abcdefghijklmnopqrs
124 return pyw_filename 1abcdefghijklmnopqrs
125 # Didn't find source, but it's probably the .py file we want.
126 return py_filename 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ0125qr34s
128 # No idea, just use the file name as-is.
129 return filename 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
132def source_for_morf(morf: TMorf) -> str: 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
133 """Get the source filename for the module-or-file `morf`."""
134 if hasattr(morf, "__file__") and morf.__file__: 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
135 filename = morf.__file__ 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
136 elif isinstance(morf, types.ModuleType): 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
137 # A module should have had .__file__, otherwise we can't use it.
138 # This could be a PEP-420 namespace package.
139 raise CoverageException(f"Module {morf} has no file") 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
140 else:
141 filename = morf 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
143 filename = source_for_file(filename) 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
144 return filename 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
147class PythonFileReporter(FileReporter): 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
148 """Report support for a Python file."""
150 def __init__(self, morf: TMorf, coverage: Coverage | None = None) -> None: 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
151 self.coverage = coverage 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
153 filename = source_for_morf(morf) 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
155 fname = filename 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
156 canonicalize = True 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
157 if self.coverage is not None: 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
158 if self.coverage.config.relative_files: 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
159 canonicalize = False 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXY6opZ012qr34s
160 if canonicalize: 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
161 fname = canonical_filename(filename) 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
162 super().__init__(fname) 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
164 if hasattr(morf, "__name__"): 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
165 name = morf.__name__.replace(".", os.sep) 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8klRS9TU!mnVW#XY6opZ0$125qr34s
166 if os.path.basename(filename).startswith("__init__."): 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8klRS9TU!mnVW#XY6opZ0$125qr34s
167 name += os.sep + "__init__" 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
168 name += ".py" 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8klRS9TU!mnVW#XY6opZ0$125qr34s
169 else:
170 name = relative_filename(filename) 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
171 self.relname = name 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
173 self._source: str | None = None 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
174 self._parser: PythonParser | None = None 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
175 self._excluded = None 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
177 def __repr__(self) -> str: 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
178 return f"<PythonFileReporter {self.filename!r}>"
180 def relative_filename(self) -> str: 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
181 return self.relname 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
183 @property 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
184 def parser(self) -> PythonParser: 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
185 """Lazily create a :class:`PythonParser`."""
186 assert self.coverage is not None 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
187 if self._parser is None: 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
188 self._parser = PythonParser( 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
189 filename=self.filename,
190 exclude=self.coverage._exclude_regex("exclude"),
191 )
192 self._parser.parse_source() 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
193 return self._parser 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
195 def lines(self) -> set[TLineNo]: 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
196 """Return the line numbers of statements in the file."""
197 return self.parser.statements 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
199 def multiline_map(self) -> dict[TLineNo, TLineNo]: 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
200 """A map of line numbers to first-line in a multi-line statement."""
201 return self.parser.multiline_map 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
203 def excluded_lines(self) -> set[TLineNo]: 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
204 """Return the line numbers of statements in the file."""
205 return self.parser.excluded 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
207 def translate_lines(self, lines: Iterable[TLineNo]) -> set[TLineNo]: 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
208 return self.parser.translate_lines(lines) 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
210 def translate_arcs(self, arcs: Iterable[TArc]) -> set[TArc]: 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
211 return self.parser.translate_arcs(arcs) 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
213 def no_branch_lines(self) -> set[TLineNo]: 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
214 assert self.coverage is not None 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
215 no_branch = self.parser.lines_matching( 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
216 join_regex(self.coverage.config.partial_list + self.coverage.config.partial_always_list)
217 )
218 return no_branch 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
220 def arcs(self) -> set[TArc]: 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
221 return self.parser.arcs() 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
223 def exit_counts(self) -> dict[TLineNo, int]: 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
224 return self.parser.exit_counts() 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
226 def missing_arc_description( 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr
227 self,
228 start: TLineNo,
229 end: TLineNo,
230 executed_arcs: Iterable[TArc] | None = None,
231 ) -> str:
232 return self.parser.missing_arc_description(start, end) 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
234 def arc_description(self, start: TLineNo, end: TLineNo) -> str: 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
235 return self.parser.arc_description(start, end) 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
237 def source(self) -> str: 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
238 if self._source is None: 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
239 self._source = get_python_source(self.filename) 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
240 return self._source 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
242 def should_be_python(self) -> bool: 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
243 """Does it seem like this file should contain Python?
245 This is used to decide if a file reported as part of the execution of
246 a program was really likely to have contained Python in the first
247 place.
249 """
250 # Get the file extension.
251 _, ext = os.path.splitext(self.filename) 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
253 # Anything named *.py* should be Python.
254 if ext.startswith(".py"): 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
255 return True 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
256 # A file with no extension should be Python.
257 if not ext: 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
258 return True 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
259 # Everything else is probably not Python.
260 return False 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
262 def source_token_lines(self) -> TSourceTokenLines: 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
263 return source_token_lines(self.source()) 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
265 def code_regions(self) -> Iterable[CodeRegion]: 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
266 return code_regions(self.source()) 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
268 def code_region_kinds(self) -> Iterable[tuple[str, str]]: 1tuvwabxyzAcdBCDEefFGHIghJKLMijNOPQklRSTUmnVWXYopZ012qr34s
269 return [ 1tuvwabxyzAcdBCDEefFGHIghJKLMijNO7PQ8kl%RS9TU!mn'VW#XY6op(Z0$125qr)34s
270 ("function", "functions"),
271 ("class", "classes"),
272 ]