summaryrefslogtreecommitdiff
path: root/coverage/tomlconfig.py
diff options
context:
space:
mode:
authorNed Batchelder <ned@nedbatchelder.com>2019-11-04 05:50:04 -0500
committerNed Batchelder <ned@nedbatchelder.com>2019-11-04 07:17:19 -0500
commit15f2ffed0113f9642275356c4dfac1ba45d8a74b (patch)
treeb4694d43ffdec90e8e07a32a433f8e9017486565 /coverage/tomlconfig.py
parentdf744f8cbcad7ea7dca893be5017920afa4ce32f (diff)
downloadpython-coveragepy-git-15f2ffed0113f9642275356c4dfac1ba45d8a74b.tar.gz
Refactor the toml logic
- Section names can be dotted. - We only ever read one file, so we don't need to loop over files. - Error messages should show the actual section names where problems happened.
Diffstat (limited to 'coverage/tomlconfig.py')
-rw-r--r--coverage/tomlconfig.py159
1 files changed, 82 insertions, 77 deletions
diff --git a/coverage/tomlconfig.py b/coverage/tomlconfig.py
index f5978820..cf1d82b1 100644
--- a/coverage/tomlconfig.py
+++ b/coverage/tomlconfig.py
@@ -26,13 +26,11 @@ class TomlConfigParser:
def __init__(self, our_file):
self.our_file = our_file
- self.getters = [lambda obj: obj['tool']['coverage']]
- if self.our_file:
- self.getters.append(lambda obj: obj)
-
- self._data = []
+ self.data = None
def read(self, filenames):
+ from coverage.optional import toml
+
# RawConfigParser takes a filename or list of filenames, but we only
# ever call this with a single filename.
assert isinstance(filenames, path_types)
@@ -40,130 +38,137 @@ class TomlConfigParser:
if env.PYVERSION >= (3, 6):
filename = os.fspath(filename)
- from coverage.optional import toml
- if toml is None:
- if self.our_file:
- raise CoverageException("Can't read {!r} without TOML support".format(filename))
-
try:
with io.open(filename, encoding='utf-8') as fp:
- toml_data = fp.read()
- toml_data = substitute_variables(toml_data, os.environ)
- if toml:
- try:
- self._data.append(toml.loads(toml_data))
- except toml.TomlDecodeError as err:
- raise TomlDecodeError(*err.args)
- elif re.search(r"^\[tool\.coverage\.", toml_data, flags=re.MULTILINE):
- # Looks like they meant to read TOML, but we can't.
- raise CoverageException("Can't read {!r} without TOML support".format(filename))
- else:
- return []
+ toml_text = fp.read()
except IOError:
return []
- return [filename]
+ if toml:
+ toml_text = substitute_variables(toml_text, os.environ)
+ try:
+ self.data = toml.loads(toml_text)
+ except toml.TomlDecodeError as err:
+ raise TomlDecodeError(*err.args)
+ return [filename]
+ else:
+ has_toml = re.search(r"^\[tool\.coverage\.", toml_text, flags=re.MULTILINE)
+ if self.our_file or has_toml:
+ # Looks like they meant to read TOML, but we can't read it.
+ msg = "Can't read {!r} without TOML support. Install with [toml] extra"
+ raise CoverageException(msg.format(filename))
+ return []
+
+ def _get_section(self, section):
+ """Get a section from the data.
+
+ Arguments:
+ section (str): A section name, which can be dotted.
+
+ Returns:
+ name (str): the actual name of the section that was found, if any,
+ or None.
+ data (str): the dict of data in the section, or None if not found.
+
+ """
+ prefixes = ["tool.coverage."]
+ if self.our_file:
+ prefixes.append("")
+ for prefix in prefixes:
+ real_section = prefix + section
+ parts = real_section.split(".")
+ try:
+ data = self.data[parts[0]]
+ for part in parts[1:]:
+ data = data[part]
+ except KeyError:
+ continue
+ break
+ else:
+ return None, None
+ return real_section, data
+
+ def _get(self, section, option):
+ """Like .get, but returns the real section name and the value."""
+ name, data = self._get_section(section)
+ if data is None:
+ raise configparser.NoSectionError(section)
+ try:
+ return name, data[option]
+ except KeyError:
+ raise configparser.NoOptionError(option, name)
def has_option(self, section, option):
- for data in self._data:
- for getter in self.getters:
- try:
- getter(data)[section][option]
- except KeyError:
- continue
- return True
- return False
+ _, data = self._get_section(section)
+ if data is None:
+ return False
+ return option in data
def has_section(self, section):
- for data in self._data:
- for getter in self.getters:
- try:
- getter(data)[section]
- except KeyError:
- continue
- return section
- return False
+ name, _ = self._get_section(section)
+ return name
def options(self, section):
- for data in self._data:
- for getter in self.getters:
- try:
- section = getter(data)[section]
- except KeyError:
- continue
- return list(section.keys())
- raise configparser.NoSectionError(section)
+ _, data = self._get_section(section)
+ if data is None:
+ raise configparser.NoSectionError(section)
+ return list(data.keys())
def get_section(self, section):
- d = {}
- for opt in self.options(section):
- d[opt] = self.get(section, opt)
- return d
+ _, data = self._get_section(section)
+ return data
def get(self, section, option):
- found_section = False
- for data in self._data:
- for getter in self.getters:
- try:
- section = getter(data)[section]
- except KeyError:
- continue
-
- found_section = True
- try:
- value = section[option]
- except KeyError:
- continue
- return value
- if not found_section:
- raise configparser.NoSectionError(section)
- raise configparser.NoOptionError(option, section)
+ _, value = self._get(section, option)
+ return value
def getboolean(self, section, option):
- value = self.get(section, option)
+ name, value = self._get(section, option)
if not isinstance(value, bool):
raise ValueError(
'Option {!r} in section {!r} is not a boolean: {!r}'
- .format(option, section, value)
+ .format(option, name, value)
)
return value
def getlist(self, section, option):
- values = self.get(section, option)
+ name, values = self._get(section, option)
if not isinstance(values, list):
raise ValueError(
'Option {!r} in section {!r} is not a list: {!r}'
- .format(option, section, values)
+ .format(option, name, values)
)
return values
def getregexlist(self, section, option):
+ # Use getlist for list-checking.
values = self.getlist(section, option)
+ name, values = self._get(section, option)
for value in values:
value = value.strip()
try:
re.compile(value)
except re.error as e:
raise CoverageException(
- "Invalid [%s].%s value %r: %s" % (section, option, value, e)
+ "Invalid [%s].%s value %r: %s" % (name, option, value, e)
)
return values
def getint(self, section, option):
- value = self.get(section, option)
+ name, value = self._get(section, option)
if not isinstance(value, int):
raise ValueError(
'Option {!r} in section {!r} is not an integer: {!r}'
- .format(option, section, value)
+ .format(option, name, value)
)
return value
def getfloat(self, section, option):
- value = self.get(section, option)
+ name, value = self._get(section, option)
if isinstance(value, int):
value = float(value)
if not isinstance(value, float):
raise ValueError(
'Option {!r} in section {!r} is not a float: {!r}'
- .format(option, section, value)
+ .format(option, name, value)
)
return value