BodyBalanceEvaluation/backend/venv/Lib/site-packages/coverage/tomlconfig.py
2025-07-31 17:23:05 +08:00

209 lines
7.4 KiB
Python

# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
"""TOML configuration support for coverage.py"""
from __future__ import annotations
import os
import re
from typing import Any, Callable, TypeVar
from collections.abc import Iterable
from coverage import config, env
from coverage.exceptions import ConfigError
from coverage.misc import import_third_party, isolate_module, substitute_variables
from coverage.types import TConfigSectionOut, TConfigValueOut
os = isolate_module(os)
if env.PYVERSION >= (3, 11, 0, "alpha", 7):
import tomllib # pylint: disable=import-error
has_tomllib = True
else:
# TOML support on Python 3.10 and below is an install-time extra option.
tomllib, has_tomllib = import_third_party("tomli")
class TomlDecodeError(Exception):
"""An exception class that exists even when toml isn't installed."""
pass
TWant = TypeVar("TWant")
class TomlConfigParser:
"""TOML file reading with the interface of HandyConfigParser."""
# This class has the same interface as config.HandyConfigParser, no
# need for docstrings.
# pylint: disable=missing-function-docstring
def __init__(self, our_file: bool) -> None:
self.our_file = our_file
self.data: dict[str, Any] = {}
def read(self, filenames: Iterable[str]) -> list[str]:
# RawConfigParser takes a filename or list of filenames, but we only
# ever call this with a single filename.
assert isinstance(filenames, (bytes, str, os.PathLike))
filename = os.fspath(filenames)
try:
with open(filename, encoding='utf-8') as fp:
toml_text = fp.read()
except OSError:
return []
if has_tomllib:
try:
self.data = tomllib.loads(toml_text)
except tomllib.TOMLDecodeError as err:
raise TomlDecodeError(str(err)) from err
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 ConfigError(msg.format(filename))
return []
def _get_section(self, section: str) -> tuple[str | None, TConfigSectionOut | None]:
"""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."]
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: str, option: str) -> tuple[str, TConfigValueOut]:
"""Like .get, but returns the real section name and the value."""
name, data = self._get_section(section)
if data is None:
raise ConfigError(f"No section: {section!r}")
assert name is not None
try:
value = data[option]
except KeyError:
raise ConfigError(f"No option {option!r} in section: {name!r}") from None
return name, value
def _get_single(self, section: str, option: str) -> Any:
"""Get a single-valued option.
Performs environment substitution if the value is a string. Other types
will be converted later as needed.
"""
name, value = self._get(section, option)
if isinstance(value, str):
value = substitute_variables(value, os.environ)
return name, value
def has_option(self, section: str, option: str) -> bool:
_, data = self._get_section(section)
if data is None:
return False
return option in data
def real_section(self, section: str) -> str | None:
name, _ = self._get_section(section)
return name
def has_section(self, section: str) -> bool:
name, _ = self._get_section(section)
return bool(name)
def options(self, section: str) -> list[str]:
_, data = self._get_section(section)
if data is None:
raise ConfigError(f"No section: {section!r}")
return list(data.keys())
def get_section(self, section: str) -> TConfigSectionOut:
_, data = self._get_section(section)
return data or {}
def get(self, section: str, option: str) -> Any:
_, value = self._get_single(section, option)
return value
def _check_type(
self,
section: str,
option: str,
value: Any,
type_: type[TWant],
converter: Callable[[Any], TWant] | None,
type_desc: str,
) -> TWant:
"""Check that `value` has the type we want, converting if needed.
Returns the resulting value of the desired type.
"""
if isinstance(value, type_):
return value
if isinstance(value, str) and converter is not None:
try:
return converter(value)
except Exception as e:
raise ValueError(
f"Option [{section}]{option} couldn't convert to {type_desc}: {value!r}",
) from e
raise ValueError(
f"Option [{section}]{option} is not {type_desc}: {value!r}",
)
def getboolean(self, section: str, option: str) -> bool:
name, value = self._get_single(section, option)
bool_strings = {"true": True, "false": False}
return self._check_type(name, option, value, bool, bool_strings.__getitem__, "a boolean")
def getfile(self, section: str, option: str) -> str:
_, value = self._get_single(section, option)
return config.process_file_value(value)
def _get_list(self, section: str, option: str) -> tuple[str, list[str]]:
"""Get a list of strings, substituting environment variables in the elements."""
name, values = self._get(section, option)
values = self._check_type(name, option, values, list, None, "a list")
values = [substitute_variables(value, os.environ) for value in values]
return name, values
def getlist(self, section: str, option: str) -> list[str]:
_, values = self._get_list(section, option)
return values
def getregexlist(self, section: str, option: str) -> list[str]:
name, values = self._get_list(section, option)
return config.process_regexlist(name, option, values)
def getint(self, section: str, option: str) -> int:
name, value = self._get_single(section, option)
return self._check_type(name, option, value, int, int, "an integer")
def getfloat(self, section: str, option: str) -> float:
name, value = self._get_single(section, option)
if isinstance(value, int):
value = float(value)
return self._check_type(name, option, value, float, float, "a float")