src/eric7/DebugClients/Python/coverage/tomlconfig.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 9099
0e511e0e94a3
child 9252
32dd11232e06
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
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 import configparser
7 import os
8 import re
9
10 from coverage import env
11 from coverage.exceptions import ConfigError
12 from coverage.misc import import_third_party, substitute_variables
13
14
15 if env.PYVERSION >= (3, 11):
16 import tomllib # pylint: disable=import-error
17 else:
18 # TOML support on Python 3.10 and below is an install-time extra option.
19 # (Import typing is here because import_third_party will unload any module
20 # that wasn't already imported. tomli imports typing, and if we unload it,
21 # later it's imported again, and on Python 3.6, this causes infinite
22 # recursion.)
23 import typing # pylint: disable=unused-import
24 tomllib = import_third_party("tomli")
25
26
27 class TomlDecodeError(Exception):
28 """An exception class that exists even when toml isn't installed."""
29 pass
30
31
32 class TomlConfigParser:
33 """TOML file reading with the interface of HandyConfigParser."""
34
35 # This class has the same interface as config.HandyConfigParser, no
36 # need for docstrings.
37 # pylint: disable=missing-function-docstring
38
39 def __init__(self, our_file):
40 self.our_file = our_file
41 self.data = None
42
43 def read(self, filenames):
44 # RawConfigParser takes a filename or list of filenames, but we only
45 # ever call this with a single filename.
46 assert isinstance(filenames, (bytes, str, os.PathLike))
47 filename = os.fspath(filenames)
48
49 try:
50 with open(filename, encoding='utf-8') as fp:
51 toml_text = fp.read()
52 except OSError:
53 return []
54 if tomllib is not None:
55 toml_text = substitute_variables(toml_text, os.environ)
56 try:
57 self.data = tomllib.loads(toml_text)
58 except tomllib.TOMLDecodeError as err:
59 raise TomlDecodeError(str(err)) from err
60 return [filename]
61 else:
62 has_toml = re.search(r"^\[tool\.coverage\.", toml_text, flags=re.MULTILINE)
63 if self.our_file or has_toml:
64 # Looks like they meant to read TOML, but we can't read it.
65 msg = "Can't read {!r} without TOML support. Install with [toml] extra"
66 raise ConfigError(msg.format(filename))
67 return []
68
69 def _get_section(self, section):
70 """Get a section from the data.
71
72 Arguments:
73 section (str): A section name, which can be dotted.
74
75 Returns:
76 name (str): the actual name of the section that was found, if any,
77 or None.
78 data (str): the dict of data in the section, or None if not found.
79
80 """
81 prefixes = ["tool.coverage."]
82 if self.our_file:
83 prefixes.append("")
84 for prefix in prefixes:
85 real_section = prefix + section
86 parts = real_section.split(".")
87 try:
88 data = self.data[parts[0]]
89 for part in parts[1:]:
90 data = data[part]
91 except KeyError:
92 continue
93 break
94 else:
95 return None, None
96 return real_section, data
97
98 def _get(self, section, option):
99 """Like .get, but returns the real section name and the value."""
100 name, data = self._get_section(section)
101 if data is None:
102 raise configparser.NoSectionError(section)
103 try:
104 return name, data[option]
105 except KeyError as exc:
106 raise configparser.NoOptionError(option, name) from exc
107
108 def has_option(self, section, option):
109 _, data = self._get_section(section)
110 if data is None:
111 return False
112 return option in data
113
114 def has_section(self, section):
115 name, _ = self._get_section(section)
116 return name
117
118 def options(self, section):
119 _, data = self._get_section(section)
120 if data is None:
121 raise configparser.NoSectionError(section)
122 return list(data.keys())
123
124 def get_section(self, section):
125 _, data = self._get_section(section)
126 return data
127
128 def get(self, section, option):
129 _, value = self._get(section, option)
130 return value
131
132 def _check_type(self, section, option, value, type_, type_desc):
133 if not isinstance(value, type_):
134 raise ValueError(
135 'Option {!r} in section {!r} is not {}: {!r}'
136 .format(option, section, type_desc, value)
137 )
138
139 def getboolean(self, section, option):
140 name, value = self._get(section, option)
141 self._check_type(name, option, value, bool, "a boolean")
142 return value
143
144 def getlist(self, section, option):
145 name, values = self._get(section, option)
146 self._check_type(name, option, values, list, "a list")
147 return values
148
149 def getregexlist(self, section, option):
150 name, values = self._get(section, option)
151 self._check_type(name, option, values, list, "a list")
152 for value in values:
153 value = value.strip()
154 try:
155 re.compile(value)
156 except re.error as e:
157 raise ConfigError(f"Invalid [{name}].{option} value {value!r}: {e}") from e
158 return values
159
160 def getint(self, section, option):
161 name, value = self._get(section, option)
162 self._check_type(name, option, value, int, "an integer")
163 return value
164
165 def getfloat(self, section, option):
166 name, value = self._get(section, option)
167 if isinstance(value, int):
168 value = float(value)
169 self._check_type(name, option, value, float, "a float")
170 return value

eric ide

mercurial