coverage 7.11.2__cp314-cp314t-musllinux_1_2_riscv64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- coverage/__init__.py +40 -0
- coverage/__main__.py +12 -0
- coverage/annotate.py +114 -0
- coverage/bytecode.py +196 -0
- coverage/cmdline.py +1184 -0
- coverage/collector.py +486 -0
- coverage/config.py +731 -0
- coverage/context.py +74 -0
- coverage/control.py +1481 -0
- coverage/core.py +142 -0
- coverage/data.py +227 -0
- coverage/debug.py +669 -0
- coverage/disposition.py +59 -0
- coverage/env.py +135 -0
- coverage/exceptions.py +91 -0
- coverage/execfile.py +329 -0
- coverage/files.py +553 -0
- coverage/html.py +856 -0
- coverage/htmlfiles/coverage_html.js +733 -0
- coverage/htmlfiles/favicon_32.png +0 -0
- coverage/htmlfiles/index.html +164 -0
- coverage/htmlfiles/keybd_closed.png +0 -0
- coverage/htmlfiles/pyfile.html +149 -0
- coverage/htmlfiles/style.css +377 -0
- coverage/htmlfiles/style.scss +824 -0
- coverage/inorout.py +614 -0
- coverage/jsonreport.py +188 -0
- coverage/lcovreport.py +219 -0
- coverage/misc.py +373 -0
- coverage/multiproc.py +120 -0
- coverage/numbits.py +146 -0
- coverage/parser.py +1213 -0
- coverage/patch.py +166 -0
- coverage/phystokens.py +197 -0
- coverage/plugin.py +617 -0
- coverage/plugin_support.py +299 -0
- coverage/py.typed +1 -0
- coverage/python.py +269 -0
- coverage/pytracer.py +369 -0
- coverage/regions.py +127 -0
- coverage/report.py +298 -0
- coverage/report_core.py +117 -0
- coverage/results.py +471 -0
- coverage/sqldata.py +1153 -0
- coverage/sqlitedb.py +239 -0
- coverage/sysmon.py +482 -0
- coverage/templite.py +306 -0
- coverage/tomlconfig.py +210 -0
- coverage/tracer.cpython-314t-riscv64-linux-musl.so +0 -0
- coverage/tracer.pyi +43 -0
- coverage/types.py +206 -0
- coverage/version.py +35 -0
- coverage/xmlreport.py +264 -0
- coverage-7.11.2.dist-info/METADATA +221 -0
- coverage-7.11.2.dist-info/RECORD +59 -0
- coverage-7.11.2.dist-info/WHEEL +5 -0
- coverage-7.11.2.dist-info/entry_points.txt +4 -0
- coverage-7.11.2.dist-info/licenses/LICENSE.txt +177 -0
- coverage-7.11.2.dist-info/top_level.txt +1 -0
coverage/templite.py
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
|
2
|
+
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
|
|
3
|
+
|
|
4
|
+
"""A simple Python template renderer, for a nano-subset of Django syntax.
|
|
5
|
+
|
|
6
|
+
For a detailed discussion of this code, see this chapter from 500 Lines:
|
|
7
|
+
http://aosabook.org/en/500L/a-template-engine.html
|
|
8
|
+
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
# Coincidentally named the same as http://code.activestate.com/recipes/496702/
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import re
|
|
16
|
+
from typing import Any, Callable, NoReturn, cast
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TempliteSyntaxError(ValueError):
|
|
20
|
+
"""Raised when a template has a syntax error."""
|
|
21
|
+
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TempliteValueError(ValueError):
|
|
26
|
+
"""Raised when an expression won't evaluate in a template."""
|
|
27
|
+
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class CodeBuilder:
|
|
32
|
+
"""Build source code conveniently."""
|
|
33
|
+
|
|
34
|
+
def __init__(self, indent: int = 0) -> None:
|
|
35
|
+
self.code: list[str | CodeBuilder] = []
|
|
36
|
+
self.indent_level = indent
|
|
37
|
+
|
|
38
|
+
def __str__(self) -> str:
|
|
39
|
+
return "".join(str(c) for c in self.code)
|
|
40
|
+
|
|
41
|
+
def add_line(self, line: str) -> None:
|
|
42
|
+
"""Add a line of source to the code.
|
|
43
|
+
|
|
44
|
+
Indentation and newline will be added for you, don't provide them.
|
|
45
|
+
|
|
46
|
+
"""
|
|
47
|
+
self.code.extend([" " * self.indent_level, line, "\n"])
|
|
48
|
+
|
|
49
|
+
def add_section(self) -> CodeBuilder:
|
|
50
|
+
"""Add a section, a sub-CodeBuilder."""
|
|
51
|
+
section = CodeBuilder(self.indent_level)
|
|
52
|
+
self.code.append(section)
|
|
53
|
+
return section
|
|
54
|
+
|
|
55
|
+
INDENT_STEP = 4 # PEP8 says so!
|
|
56
|
+
|
|
57
|
+
def indent(self) -> None:
|
|
58
|
+
"""Increase the current indent for following lines."""
|
|
59
|
+
self.indent_level += self.INDENT_STEP
|
|
60
|
+
|
|
61
|
+
def dedent(self) -> None:
|
|
62
|
+
"""Decrease the current indent for following lines."""
|
|
63
|
+
self.indent_level -= self.INDENT_STEP
|
|
64
|
+
|
|
65
|
+
def get_globals(self) -> dict[str, Any]:
|
|
66
|
+
"""Execute the code, and return a dict of globals it defines."""
|
|
67
|
+
# A check that the caller really finished all the blocks they started.
|
|
68
|
+
assert self.indent_level == 0
|
|
69
|
+
# Get the Python source as a single string.
|
|
70
|
+
python_source = str(self)
|
|
71
|
+
# Execute the source, defining globals, and return them.
|
|
72
|
+
global_namespace: dict[str, Any] = {}
|
|
73
|
+
exec(python_source, global_namespace)
|
|
74
|
+
return global_namespace
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class Templite:
|
|
78
|
+
"""A simple template renderer, for a nano-subset of Django syntax.
|
|
79
|
+
|
|
80
|
+
Supported constructs are extended variable access::
|
|
81
|
+
|
|
82
|
+
{{var.modifier.modifier|filter|filter}}
|
|
83
|
+
|
|
84
|
+
loops::
|
|
85
|
+
|
|
86
|
+
{% for var in list %}...{% endfor %}
|
|
87
|
+
|
|
88
|
+
and ifs::
|
|
89
|
+
|
|
90
|
+
{% if var %}...{% endif %}
|
|
91
|
+
|
|
92
|
+
Comments are within curly-hash markers::
|
|
93
|
+
|
|
94
|
+
{# This will be ignored #}
|
|
95
|
+
|
|
96
|
+
Lines between `{% joined %}` and `{% endjoined %}` will have lines stripped
|
|
97
|
+
and joined. Be careful, this could join words together!
|
|
98
|
+
|
|
99
|
+
Any of these constructs can have a hyphen at the end (`-}}`, `-%}`, `-#}`),
|
|
100
|
+
which will collapse the white space following the tag.
|
|
101
|
+
|
|
102
|
+
Construct a Templite with the template text, then use `render` against a
|
|
103
|
+
dictionary context to create a finished string::
|
|
104
|
+
|
|
105
|
+
templite = Templite('''
|
|
106
|
+
<h1>Hello {{name|upper}}!</h1>
|
|
107
|
+
{% for topic in topics %}
|
|
108
|
+
<p>You are interested in {{topic}}.</p>
|
|
109
|
+
{% endif %}
|
|
110
|
+
''',
|
|
111
|
+
{"upper": str.upper},
|
|
112
|
+
)
|
|
113
|
+
text = templite.render({
|
|
114
|
+
"name": "Ned",
|
|
115
|
+
"topics": ["Python", "Geometry", "Juggling"],
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
def __init__(self, text: str, *contexts: dict[str, Any]) -> None:
|
|
121
|
+
"""Construct a Templite with the given `text`.
|
|
122
|
+
|
|
123
|
+
`contexts` are dictionaries of values to use for future renderings.
|
|
124
|
+
These are good for filters and global values.
|
|
125
|
+
|
|
126
|
+
"""
|
|
127
|
+
self.context = {}
|
|
128
|
+
for context in contexts:
|
|
129
|
+
self.context.update(context)
|
|
130
|
+
|
|
131
|
+
self.all_vars: set[str] = set()
|
|
132
|
+
self.loop_vars: set[str] = set()
|
|
133
|
+
|
|
134
|
+
# We construct a function in source form, then compile it and hold onto
|
|
135
|
+
# it, and execute it to render the template.
|
|
136
|
+
code = CodeBuilder()
|
|
137
|
+
|
|
138
|
+
code.add_line("def render_function(context, do_dots):")
|
|
139
|
+
code.indent()
|
|
140
|
+
vars_code = code.add_section()
|
|
141
|
+
code.add_line("result = []")
|
|
142
|
+
code.add_line("append_result = result.append")
|
|
143
|
+
code.add_line("extend_result = result.extend")
|
|
144
|
+
code.add_line("to_str = str")
|
|
145
|
+
|
|
146
|
+
buffered: list[str] = []
|
|
147
|
+
|
|
148
|
+
def flush_output() -> None:
|
|
149
|
+
"""Force `buffered` to the code builder."""
|
|
150
|
+
if len(buffered) == 1:
|
|
151
|
+
code.add_line("append_result(%s)" % buffered[0])
|
|
152
|
+
elif len(buffered) > 1:
|
|
153
|
+
code.add_line("extend_result([%s])" % ", ".join(buffered))
|
|
154
|
+
del buffered[:]
|
|
155
|
+
|
|
156
|
+
ops_stack = []
|
|
157
|
+
|
|
158
|
+
# Split the text to form a list of tokens.
|
|
159
|
+
tokens = re.split(r"(?s)({{.*?}}|{%.*?%}|{#.*?#})", text)
|
|
160
|
+
|
|
161
|
+
squash = in_joined = False
|
|
162
|
+
|
|
163
|
+
for token in tokens:
|
|
164
|
+
if token.startswith("{"):
|
|
165
|
+
start, end = 2, -2
|
|
166
|
+
squash = (token[-3] == "-") # fmt: skip
|
|
167
|
+
if squash:
|
|
168
|
+
end = -3
|
|
169
|
+
|
|
170
|
+
if token.startswith("{#"):
|
|
171
|
+
# Comment: ignore it and move on.
|
|
172
|
+
continue
|
|
173
|
+
elif token.startswith("{{"):
|
|
174
|
+
# An expression to evaluate.
|
|
175
|
+
expr = self._expr_code(token[start:end].strip())
|
|
176
|
+
buffered.append("to_str(%s)" % expr)
|
|
177
|
+
else:
|
|
178
|
+
# token.startswith("{%")
|
|
179
|
+
# Action tag: split into words and parse further.
|
|
180
|
+
flush_output()
|
|
181
|
+
|
|
182
|
+
words = token[start:end].strip().split()
|
|
183
|
+
if words[0] == "if":
|
|
184
|
+
# An if statement: evaluate the expression to determine if.
|
|
185
|
+
if len(words) != 2:
|
|
186
|
+
self._syntax_error("Don't understand if", token)
|
|
187
|
+
ops_stack.append("if")
|
|
188
|
+
code.add_line("if %s:" % self._expr_code(words[1]))
|
|
189
|
+
code.indent()
|
|
190
|
+
elif words[0] == "for":
|
|
191
|
+
# A loop: iterate over expression result.
|
|
192
|
+
if len(words) != 4 or words[2] != "in":
|
|
193
|
+
self._syntax_error("Don't understand for", token)
|
|
194
|
+
ops_stack.append("for")
|
|
195
|
+
self._variable(words[1], self.loop_vars)
|
|
196
|
+
code.add_line(
|
|
197
|
+
f"for c_{words[1]} in {self._expr_code(words[3])}:",
|
|
198
|
+
)
|
|
199
|
+
code.indent()
|
|
200
|
+
elif words[0] == "joined":
|
|
201
|
+
ops_stack.append("joined")
|
|
202
|
+
in_joined = True
|
|
203
|
+
elif words[0].startswith("end"):
|
|
204
|
+
# Endsomething. Pop the ops stack.
|
|
205
|
+
if len(words) != 1:
|
|
206
|
+
self._syntax_error("Don't understand end", token)
|
|
207
|
+
end_what = words[0][3:]
|
|
208
|
+
if not ops_stack:
|
|
209
|
+
self._syntax_error("Too many ends", token)
|
|
210
|
+
start_what = ops_stack.pop()
|
|
211
|
+
if start_what != end_what:
|
|
212
|
+
self._syntax_error("Mismatched end tag", end_what)
|
|
213
|
+
if end_what == "joined":
|
|
214
|
+
in_joined = False
|
|
215
|
+
else:
|
|
216
|
+
code.dedent()
|
|
217
|
+
else:
|
|
218
|
+
self._syntax_error("Don't understand tag", words[0])
|
|
219
|
+
else:
|
|
220
|
+
# Literal content. If it isn't empty, output it.
|
|
221
|
+
if in_joined:
|
|
222
|
+
token = re.sub(r"\s*\n\s*", "", token.strip())
|
|
223
|
+
elif squash:
|
|
224
|
+
token = token.lstrip()
|
|
225
|
+
if token:
|
|
226
|
+
buffered.append(repr(token))
|
|
227
|
+
|
|
228
|
+
if ops_stack:
|
|
229
|
+
self._syntax_error("Unmatched action tag", ops_stack[-1])
|
|
230
|
+
|
|
231
|
+
flush_output()
|
|
232
|
+
|
|
233
|
+
for var_name in self.all_vars - self.loop_vars:
|
|
234
|
+
vars_code.add_line(f"c_{var_name} = context[{var_name!r}]")
|
|
235
|
+
|
|
236
|
+
code.add_line("return ''.join(result)")
|
|
237
|
+
code.dedent()
|
|
238
|
+
self._render_function = cast(
|
|
239
|
+
Callable[
|
|
240
|
+
[dict[str, Any], Callable[..., Any]],
|
|
241
|
+
str,
|
|
242
|
+
],
|
|
243
|
+
code.get_globals()["render_function"],
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
def _expr_code(self, expr: str) -> str:
|
|
247
|
+
"""Generate a Python expression for `expr`."""
|
|
248
|
+
if "|" in expr:
|
|
249
|
+
pipes = expr.split("|")
|
|
250
|
+
code = self._expr_code(pipes[0])
|
|
251
|
+
for func in pipes[1:]:
|
|
252
|
+
self._variable(func, self.all_vars)
|
|
253
|
+
code = f"c_{func}({code})"
|
|
254
|
+
elif "." in expr:
|
|
255
|
+
dots = expr.split(".")
|
|
256
|
+
code = self._expr_code(dots[0])
|
|
257
|
+
args = ", ".join(repr(d) for d in dots[1:])
|
|
258
|
+
code = f"do_dots({code}, {args})"
|
|
259
|
+
else:
|
|
260
|
+
self._variable(expr, self.all_vars)
|
|
261
|
+
code = "c_%s" % expr
|
|
262
|
+
return code
|
|
263
|
+
|
|
264
|
+
def _syntax_error(self, msg: str, thing: Any) -> NoReturn:
|
|
265
|
+
"""Raise a syntax error using `msg`, and showing `thing`."""
|
|
266
|
+
raise TempliteSyntaxError(f"{msg}: {thing!r}")
|
|
267
|
+
|
|
268
|
+
def _variable(self, name: str, vars_set: set[str]) -> None:
|
|
269
|
+
"""Track that `name` is used as a variable.
|
|
270
|
+
|
|
271
|
+
Adds the name to `vars_set`, a set of variable names.
|
|
272
|
+
|
|
273
|
+
Raises an syntax error if `name` is not a valid name.
|
|
274
|
+
|
|
275
|
+
"""
|
|
276
|
+
if not re.match(r"[_a-zA-Z][_a-zA-Z0-9]*$", name):
|
|
277
|
+
self._syntax_error("Not a valid name", name)
|
|
278
|
+
vars_set.add(name)
|
|
279
|
+
|
|
280
|
+
def render(self, context: dict[str, Any] | None = None) -> str:
|
|
281
|
+
"""Render this template by applying it to `context`.
|
|
282
|
+
|
|
283
|
+
`context` is a dictionary of values to use in this rendering.
|
|
284
|
+
|
|
285
|
+
"""
|
|
286
|
+
# Make the complete context we'll use.
|
|
287
|
+
render_context = dict(self.context)
|
|
288
|
+
if context:
|
|
289
|
+
render_context.update(context)
|
|
290
|
+
return self._render_function(render_context, self._do_dots)
|
|
291
|
+
|
|
292
|
+
def _do_dots(self, value: Any, *dots: str) -> Any:
|
|
293
|
+
"""Evaluate dotted expressions at run-time."""
|
|
294
|
+
for dot in dots:
|
|
295
|
+
try:
|
|
296
|
+
value = getattr(value, dot)
|
|
297
|
+
except AttributeError:
|
|
298
|
+
try:
|
|
299
|
+
value = value[dot]
|
|
300
|
+
except (TypeError, KeyError) as exc:
|
|
301
|
+
raise TempliteValueError(
|
|
302
|
+
f"Couldn't evaluate {value!r}.{dot}",
|
|
303
|
+
) from exc
|
|
304
|
+
if callable(value):
|
|
305
|
+
value = value()
|
|
306
|
+
return value
|
coverage/tomlconfig.py
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
|
2
|
+
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
|
|
3
|
+
|
|
4
|
+
"""TOML configuration support for coverage.py"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
from collections.abc import Iterable
|
|
11
|
+
from typing import Any, Callable, TypeVar
|
|
12
|
+
|
|
13
|
+
from coverage import config, env
|
|
14
|
+
from coverage.exceptions import ConfigError
|
|
15
|
+
from coverage.misc import import_third_party, isolate_module, substitute_variables
|
|
16
|
+
from coverage.types import TConfigSectionOut, TConfigValueOut
|
|
17
|
+
|
|
18
|
+
os = isolate_module(os)
|
|
19
|
+
|
|
20
|
+
if env.PYVERSION >= (3, 11, 0, "alpha", 7):
|
|
21
|
+
import tomllib # pylint: disable=import-error
|
|
22
|
+
|
|
23
|
+
has_tomllib = True
|
|
24
|
+
else:
|
|
25
|
+
# TOML support on Python 3.10 and below is an install-time extra option.
|
|
26
|
+
tomllib, has_tomllib = import_third_party("tomli")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class TomlDecodeError(Exception):
|
|
30
|
+
"""An exception class that exists even when toml isn't installed."""
|
|
31
|
+
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
TWant = TypeVar("TWant")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class TomlConfigParser:
|
|
39
|
+
"""TOML file reading with the interface of HandyConfigParser."""
|
|
40
|
+
|
|
41
|
+
# This class has the same interface as config.HandyConfigParser, no
|
|
42
|
+
# need for docstrings.
|
|
43
|
+
# pylint: disable=missing-function-docstring
|
|
44
|
+
|
|
45
|
+
def __init__(self, our_file: bool) -> None:
|
|
46
|
+
self.our_file = our_file
|
|
47
|
+
self.data: dict[str, Any] = {}
|
|
48
|
+
|
|
49
|
+
def read(self, filenames: Iterable[str]) -> list[str]:
|
|
50
|
+
# RawConfigParser takes a filename or list of filenames, but we only
|
|
51
|
+
# ever call this with a single filename.
|
|
52
|
+
assert isinstance(filenames, (bytes, str, os.PathLike))
|
|
53
|
+
filename = os.fspath(filenames)
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
with open(filename, encoding="utf-8") as fp:
|
|
57
|
+
toml_text = fp.read()
|
|
58
|
+
except OSError:
|
|
59
|
+
return []
|
|
60
|
+
if has_tomllib:
|
|
61
|
+
try:
|
|
62
|
+
self.data = tomllib.loads(toml_text)
|
|
63
|
+
except tomllib.TOMLDecodeError as err:
|
|
64
|
+
raise TomlDecodeError(str(err)) from err
|
|
65
|
+
return [filename]
|
|
66
|
+
else:
|
|
67
|
+
has_toml = re.search(r"^\[tool\.coverage(\.|])", toml_text, flags=re.MULTILINE)
|
|
68
|
+
if self.our_file or has_toml:
|
|
69
|
+
# Looks like they meant to read TOML, but we can't read it.
|
|
70
|
+
msg = "Can't read {!r} without TOML support. Install with [toml] extra"
|
|
71
|
+
raise ConfigError(msg.format(filename))
|
|
72
|
+
return []
|
|
73
|
+
|
|
74
|
+
def _get_section(self, section: str) -> tuple[str | None, TConfigSectionOut | None]:
|
|
75
|
+
"""Get a section from the data.
|
|
76
|
+
|
|
77
|
+
Arguments:
|
|
78
|
+
section (str): A section name, which can be dotted.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
name (str): the actual name of the section that was found, if any,
|
|
82
|
+
or None.
|
|
83
|
+
data (str): the dict of data in the section, or None if not found.
|
|
84
|
+
|
|
85
|
+
"""
|
|
86
|
+
prefixes = ["tool.coverage."]
|
|
87
|
+
for prefix in prefixes:
|
|
88
|
+
real_section = prefix + section
|
|
89
|
+
parts = real_section.split(".")
|
|
90
|
+
try:
|
|
91
|
+
data = self.data[parts[0]]
|
|
92
|
+
for part in parts[1:]:
|
|
93
|
+
data = data[part]
|
|
94
|
+
except KeyError:
|
|
95
|
+
continue
|
|
96
|
+
break
|
|
97
|
+
else:
|
|
98
|
+
return None, None
|
|
99
|
+
return real_section, data
|
|
100
|
+
|
|
101
|
+
def _get(self, section: str, option: str) -> tuple[str, TConfigValueOut]:
|
|
102
|
+
"""Like .get, but returns the real section name and the value."""
|
|
103
|
+
name, data = self._get_section(section)
|
|
104
|
+
if data is None:
|
|
105
|
+
raise ConfigError(f"No section: {section!r}")
|
|
106
|
+
assert name is not None
|
|
107
|
+
try:
|
|
108
|
+
value = data[option]
|
|
109
|
+
except KeyError:
|
|
110
|
+
raise ConfigError(f"No option {option!r} in section: {name!r}") from None
|
|
111
|
+
return name, value
|
|
112
|
+
|
|
113
|
+
def _get_single(self, section: str, option: str) -> Any:
|
|
114
|
+
"""Get a single-valued option.
|
|
115
|
+
|
|
116
|
+
Performs environment substitution if the value is a string. Other types
|
|
117
|
+
will be converted later as needed.
|
|
118
|
+
"""
|
|
119
|
+
name, value = self._get(section, option)
|
|
120
|
+
if isinstance(value, str):
|
|
121
|
+
value = substitute_variables(value, os.environ)
|
|
122
|
+
return name, value
|
|
123
|
+
|
|
124
|
+
def has_option(self, section: str, option: str) -> bool:
|
|
125
|
+
_, data = self._get_section(section)
|
|
126
|
+
if data is None:
|
|
127
|
+
return False
|
|
128
|
+
return option in data
|
|
129
|
+
|
|
130
|
+
def real_section(self, section: str) -> str | None:
|
|
131
|
+
name, _ = self._get_section(section)
|
|
132
|
+
return name
|
|
133
|
+
|
|
134
|
+
def has_section(self, section: str) -> bool:
|
|
135
|
+
name, _ = self._get_section(section)
|
|
136
|
+
return bool(name)
|
|
137
|
+
|
|
138
|
+
def options(self, section: str) -> list[str]:
|
|
139
|
+
_, data = self._get_section(section)
|
|
140
|
+
if data is None:
|
|
141
|
+
raise ConfigError(f"No section: {section!r}")
|
|
142
|
+
return list(data.keys())
|
|
143
|
+
|
|
144
|
+
def get_section(self, section: str) -> TConfigSectionOut:
|
|
145
|
+
_, data = self._get_section(section)
|
|
146
|
+
return data or {}
|
|
147
|
+
|
|
148
|
+
def get(self, section: str, option: str) -> Any:
|
|
149
|
+
_, value = self._get_single(section, option)
|
|
150
|
+
return value
|
|
151
|
+
|
|
152
|
+
def _check_type(
|
|
153
|
+
self,
|
|
154
|
+
section: str,
|
|
155
|
+
option: str,
|
|
156
|
+
value: Any,
|
|
157
|
+
type_: type[TWant],
|
|
158
|
+
converter: Callable[[Any], TWant] | None,
|
|
159
|
+
type_desc: str,
|
|
160
|
+
) -> TWant:
|
|
161
|
+
"""Check that `value` has the type we want, converting if needed.
|
|
162
|
+
|
|
163
|
+
Returns the resulting value of the desired type.
|
|
164
|
+
"""
|
|
165
|
+
if isinstance(value, type_):
|
|
166
|
+
return value
|
|
167
|
+
if isinstance(value, str) and converter is not None:
|
|
168
|
+
try:
|
|
169
|
+
return converter(value)
|
|
170
|
+
except Exception as e:
|
|
171
|
+
raise ValueError(
|
|
172
|
+
f"Option [{section}]{option} couldn't convert to {type_desc}: {value!r}",
|
|
173
|
+
) from e
|
|
174
|
+
raise ValueError(
|
|
175
|
+
f"Option [{section}]{option} is not {type_desc}: {value!r}",
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
def getboolean(self, section: str, option: str) -> bool:
|
|
179
|
+
name, value = self._get_single(section, option)
|
|
180
|
+
bool_strings = {"true": True, "false": False}
|
|
181
|
+
return self._check_type(name, option, value, bool, bool_strings.__getitem__, "a boolean")
|
|
182
|
+
|
|
183
|
+
def getfile(self, section: str, option: str) -> str:
|
|
184
|
+
_, value = self._get_single(section, option)
|
|
185
|
+
return config.process_file_value(value)
|
|
186
|
+
|
|
187
|
+
def _get_list(self, section: str, option: str) -> tuple[str, list[str]]:
|
|
188
|
+
"""Get a list of strings, substituting environment variables in the elements."""
|
|
189
|
+
name, values = self._get(section, option)
|
|
190
|
+
values = self._check_type(name, option, values, list, None, "a list")
|
|
191
|
+
values = [substitute_variables(value, os.environ) for value in values]
|
|
192
|
+
return name, values
|
|
193
|
+
|
|
194
|
+
def getlist(self, section: str, option: str) -> list[str]:
|
|
195
|
+
_, values = self._get_list(section, option)
|
|
196
|
+
return values
|
|
197
|
+
|
|
198
|
+
def getregexlist(self, section: str, option: str) -> list[str]:
|
|
199
|
+
name, values = self._get_list(section, option)
|
|
200
|
+
return config.process_regexlist(name, option, values)
|
|
201
|
+
|
|
202
|
+
def getint(self, section: str, option: str) -> int:
|
|
203
|
+
name, value = self._get_single(section, option)
|
|
204
|
+
return self._check_type(name, option, value, int, int, "an integer")
|
|
205
|
+
|
|
206
|
+
def getfloat(self, section: str, option: str) -> float:
|
|
207
|
+
name, value = self._get_single(section, option)
|
|
208
|
+
if isinstance(value, int):
|
|
209
|
+
value = float(value)
|
|
210
|
+
return self._check_type(name, option, value, float, float, "a float")
|
|
Binary file
|
coverage/tracer.pyi
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
|
2
|
+
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
|
|
3
|
+
|
|
4
|
+
"""Typing information for the constructs from our .c files."""
|
|
5
|
+
|
|
6
|
+
from typing import Any, Dict
|
|
7
|
+
|
|
8
|
+
from coverage.types import TFileDisposition, TTraceData, TTraceFn, Tracer
|
|
9
|
+
|
|
10
|
+
class CFileDisposition(TFileDisposition):
|
|
11
|
+
"""CFileDisposition is in ctracer/filedisp.c"""
|
|
12
|
+
|
|
13
|
+
canonical_filename: Any
|
|
14
|
+
file_tracer: Any
|
|
15
|
+
has_dynamic_filename: Any
|
|
16
|
+
original_filename: Any
|
|
17
|
+
reason: Any
|
|
18
|
+
source_filename: Any
|
|
19
|
+
trace: Any
|
|
20
|
+
def __init__(self) -> None: ...
|
|
21
|
+
|
|
22
|
+
class CTracer(Tracer):
|
|
23
|
+
"""CTracer is in ctracer/tracer.c"""
|
|
24
|
+
|
|
25
|
+
check_include: Any
|
|
26
|
+
concur_id_func: Any
|
|
27
|
+
data: TTraceData
|
|
28
|
+
disable_plugin: Any
|
|
29
|
+
file_tracers: Any
|
|
30
|
+
should_start_context: Any
|
|
31
|
+
should_trace: Any
|
|
32
|
+
should_trace_cache: Any
|
|
33
|
+
switch_context: Any
|
|
34
|
+
lock_data: Any
|
|
35
|
+
unlock_data: Any
|
|
36
|
+
trace_arcs: Any
|
|
37
|
+
warn: Any
|
|
38
|
+
def __init__(self) -> None: ...
|
|
39
|
+
def activity(self) -> bool: ...
|
|
40
|
+
def get_stats(self) -> Dict[str, int]: ...
|
|
41
|
+
def reset_activity(self) -> Any: ...
|
|
42
|
+
def start(self) -> TTraceFn: ...
|
|
43
|
+
def stop(self) -> None: ...
|