Coverage for coverage / config.py: 100.000%

326 statements  

« 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 

3 

4"""Config file for coverage.py""" 

5 

6from __future__ import annotations 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

7 

8import base64 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

9import collections 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

10import configparser 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

11import copy 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

12import json 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

13import os 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

14import os.path 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

15import re 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

16from collections.abc import Iterable 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

17from typing import Any, Callable, Final, Mapping 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

18 

19from coverage.exceptions import ConfigError 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

20from coverage.misc import human_sorted_items, isolate_module, substitute_variables 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

21from coverage.tomlconfig import TomlConfigParser, TomlDecodeError 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

22from coverage.types import ( 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

23 TConfigSectionIn, 

24 TConfigSectionOut, 

25 TConfigurable, 

26 TConfigValueIn, 

27 TConfigValueOut, 

28 TPluginConfig, 

29) 

30 

31os = isolate_module(os) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

32 

33 

34class HandyConfigParser(configparser.ConfigParser): 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

35 """Our specialization of ConfigParser.""" 

36 

37 def __init__(self, our_file: bool) -> None: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

38 """Create the HandyConfigParser. 

39 

40 `our_file` is True if this config file is specifically for coverage, 

41 False if we are examining another config file (tox.ini, setup.cfg) 

42 for possible settings. 

43 """ 

44 

45 super().__init__(interpolation=None) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

46 self.section_prefixes = ["coverage:"] 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

47 if our_file: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

48 self.section_prefixes.append("") 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

49 

50 def read( # type: ignore[override] 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)

51 self, 

52 filenames: Iterable[str], 

53 encoding_unused: str | None = None, 

54 ) -> list[str]: 

55 """Read a file name as UTF-8 configuration data.""" 

56 return super().read(filenames, encoding="utf-8") 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

57 

58 def real_section(self, section: str) -> str | None: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

59 """Get the actual name of a section.""" 

60 for section_prefix in self.section_prefixes: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

61 real_section = section_prefix + section 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

62 has = super().has_section(real_section) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

63 if has: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

64 return real_section 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

65 return None 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

66 

67 def has_option(self, section: str, option: str) -> bool: # type: ignore[override] 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

68 real_section = self.real_section(section) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

69 if real_section is not None: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

70 return super().has_option(real_section, option) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

71 return False 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

72 

73 def has_section(self, section: str) -> bool: # type: ignore[override] 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

74 return bool(self.real_section(section)) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

75 

76 def options(self, section: str) -> list[str]: # type: ignore[override] 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

77 real_section = self.real_section(section) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

78 if real_section is not None: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

79 return super().options(real_section) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

80 raise ConfigError(f"No section: {section!r}") 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UVyz6AB9WXCDEF!YZGH7IJ01234

81 

82 def get_section(self, section: str) -> TConfigSectionOut: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

83 """Get the contents of a section, as a dictionary.""" 

84 d: dict[str, TConfigValueOut] = {} 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

85 for opt in self.options(section): 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

86 d[opt] = self.get(section, opt) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

87 return d 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

88 

89 def get(self, section: str, option: str, *args: Any, **kwargs: Any) -> str: # type: ignore 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

90 """Get a value, replacing environment variables also. 

91 

92 The arguments are the same as `ConfigParser.get`, but in the found 

93 value, ``$WORD`` or ``${WORD}`` are replaced by the value of the 

94 environment variable ``WORD``. 

95 

96 Returns the finished value. 

97 

98 """ 

99 for section_prefix in self.section_prefixes: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

100 real_section = section_prefix + section 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

101 if super().has_option(real_section, option): 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

102 break 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

103 else: 

104 raise ConfigError(f"No option {option!r} in section: {section!r}") 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UVyz6AB9WXCDEF!YZGH7IJ01234

105 

106 v: str = super().get(real_section, option, *args, **kwargs) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

107 v = substitute_variables(v, os.environ) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

108 return v 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

109 

110 def getfile(self, section: str, option: str) -> str: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

111 """Fix up a file path setting.""" 

112 path = self.get(section, option) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

113 return process_file_value(path) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

114 

115 def getlist(self, section: str, option: str) -> list[str]: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

116 """Read a list of strings. 

117 

118 The value of `section` and `option` is treated as a comma- and newline- 

119 separated list of strings. Each value is stripped of white space. 

120 

121 Returns the list of strings. 

122 

123 """ 

124 value_list = self.get(section, option) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

125 values = [] 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

126 for value_line in value_list.split("\n"): 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

127 for value in value_line.split(","): 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

128 value = value.strip() 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

129 if value: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

130 values.append(value) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

131 return values 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

132 

133 def getregexlist(self, section: str, option: str) -> list[str]: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

134 """Read a list of full-line regexes. 

135 

136 The value of `section` and `option` is treated as a newline-separated 

137 list of regexes. Each value is stripped of white space. 

138 

139 Returns the list of strings. 

140 

141 """ 

142 line_list = self.get(section, option) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

143 return process_regexlist(section, option, line_list.splitlines()) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

144 

145 

146TConfigParser = HandyConfigParser | TomlConfigParser 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

147 

148 

149# The default line exclusion regexes. 

150DEFAULT_EXCLUDE = [ 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

151 r"#\s*(pragma|PRAGMA)[:\s]?\s*(no|NO)\s*(cover|COVER)", 

152 r"^\s*(((async )?def .*?)?\)(\s*->.*?)?:\s*)?\.\.\.\s*(#|$)", 

153 r"if (typing\.)?TYPE_CHECKING:", 

154] 

155 

156# The default partial branch regexes, to be modified by the user. 

157DEFAULT_PARTIAL = [ 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

158 r"#\s*(pragma|PRAGMA)[:\s]?\s*(no|NO)\s*(branch|BRANCH)", 

159] 

160 

161# The default partial branch regexes, based on Python semantics. 

162# These are any Python branching constructs that can't actually execute all 

163# their branches. 

164DEFAULT_PARTIAL_ALWAYS = [ 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

165 "while (True|1|False|0):", 

166 "if (True|1|False|0):", 

167] 

168 

169 

170class CoverageConfig(TConfigurable, TPluginConfig): 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

171 """Coverage.py configuration. 

172 

173 The attributes of this class are the various settings that control the 

174 operation of coverage.py. 

175 

176 """ 

177 

178 # pylint: disable=too-many-instance-attributes 

179 

180 def __init__(self) -> None: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

181 """Initialize the configuration attributes to their defaults.""" 

182 # Metadata about the config. 

183 # We tried to read these config files. 

184 self.config_files_attempted: list[str] = [] 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

185 # We did read these config files, but maybe didn't find any content for us. 

186 self.config_files_read: list[str] = [] 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

187 # The file that gave us our configuration. 

188 self.config_file: str | None = None 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

189 self._config_contents: bytes | None = None 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

190 

191 # Defaults for [run] and [report] 

192 self._include = None 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

193 self._omit = None 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

194 

195 # Defaults for [run] 

196 self.branch = False 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

197 self.command_line: str | None = None 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

198 self.concurrency: list[str] = [] 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

199 self.context: str | None = None 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

200 self.core: str | None = None 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

201 self.cover_pylib = False 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

202 self.data_file = ".coverage" 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

203 self.debug: list[str] = [] 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

204 self.debug_file: str | None = None 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

205 self.disable_warnings: list[str] = [] 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

206 self.dynamic_context: str | None = None 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

207 self.parallel = False 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

208 self.patch: list[str] = [] 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

209 self.plugins: list[str] = [] 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

210 self.relative_files = False 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

211 self.run_include: list[str] = [] 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

212 self.run_omit: list[str] = [] 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

213 self.sigterm = False 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

214 self.source: list[str] | None = None 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

215 self.source_pkgs: list[str] = [] 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

216 self.source_dirs: list[str] = [] 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

217 self.timid = False 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

218 self._crash: str | None = None 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

219 

220 # Defaults for [report] 

221 self.exclude_list = DEFAULT_EXCLUDE[:] 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

222 self.exclude_also: list[str] = [] 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

223 self.fail_under = 0.0 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

224 self.format: str | None = None 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

225 self.ignore_errors = False 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

226 self.include_namespace_packages = False 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

227 self.report_include: list[str] | None = None 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

228 self.report_omit: list[str] | None = None 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

229 self.partial_always_list = DEFAULT_PARTIAL_ALWAYS[:] 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

230 self.partial_list = DEFAULT_PARTIAL[:] 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

231 self.partial_also: list[str] = [] 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

232 self.precision = 0 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

233 self.report_contexts: list[str] | None = None 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

234 self.show_missing = False 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

235 self.skip_covered = False 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

236 self.skip_empty = False 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

237 self.sort: str | None = None 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

238 

239 # Defaults for [html] 

240 self.extra_css: str | None = None 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

241 self.html_dir = "htmlcov" 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

242 self.html_skip_covered: bool | None = None 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

243 self.html_skip_empty: bool | None = None 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

244 self.html_title = "Coverage report" 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

245 self.show_contexts = False 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

246 

247 # Defaults for [xml] 

248 self.xml_output = "coverage.xml" 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

249 self.xml_package_depth = 99 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

250 

251 # Defaults for [json] 

252 self.json_output = "coverage.json" 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

253 self.json_pretty_print = False 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

254 self.json_show_contexts = False 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

255 

256 # Defaults for [lcov] 

257 self.lcov_output = "coverage.lcov" 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

258 self.lcov_line_checksums = False 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

259 

260 # Defaults for [paths] 

261 self.paths: dict[str, list[str]] = {} 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

262 

263 # Options for plugins 

264 self.plugin_options: dict[str, TConfigSectionOut] = {} 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

265 

266 MUST_BE_LIST = { 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

267 "debug", 

268 "concurrency", 

269 "plugins", 

270 "report_omit", 

271 "report_include", 

272 "run_omit", 

273 "run_include", 

274 "patch", 

275 } 

276 

277 # File paths to make absolute during serialization. 

278 # The pairs are (config_key, must_exist). 

279 SERIALIZE_ABSPATH = { 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

280 ("data_file", False), 

281 ("debug_file", False), 

282 # `source` can be directories or modules, so don't abspath it if it 

283 # doesn't exist. 

284 ("source", True), 

285 ("source_dirs", False), 

286 } 

287 

288 def from_args(self, **kwargs: TConfigValueIn) -> None: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

289 """Read config values from `kwargs`.""" 

290 for k, v in kwargs.items(): 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

291 if v is not None: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

292 if k in self.MUST_BE_LIST and isinstance(v, str): 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

293 v = [v] 1abcdKLefghMNijklOPmnopQRqrstSTuv5wxUV'yz6ABWXCD#EFYZGH7IJ01234

294 setattr(self, k, v) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

295 

296 def from_file(self, filename: str, warn: Callable[[str], None], our_file: bool) -> bool: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

297 """Read configuration from a .rc file. 

298 

299 `filename` is a file name to read. 

300 

301 `our_file` is True if this config file is specifically for coverage, 

302 False if we are examining another config file (tox.ini, setup.cfg) 

303 for possible settings. 

304 

305 Returns True or False, whether the file could be read, and it had some 

306 coverage.py settings in it. 

307 

308 """ 

309 _, ext = os.path.splitext(filename) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

310 cp: TConfigParser 

311 if ext == ".toml": 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

312 cp = TomlConfigParser(our_file) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

313 else: 

314 cp = HandyConfigParser(our_file) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

315 

316 self.config_files_attempted.append(os.path.abspath(filename)) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

317 

318 try: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

319 files_read = cp.read(filename) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

320 except (configparser.Error, TomlDecodeError) as err: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UVyz6AB9WXCD#EF!YZGH7IJ$01234

321 raise ConfigError(f"Couldn't read config file {filename}: {err}") from err 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UVyz6AB9WXCD#EF!YZGH7IJ$01234

322 if not files_read: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

323 return False 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

324 

325 self.config_files_read.extend(map(os.path.abspath, files_read)) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

326 

327 any_set = False 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

328 try: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

329 for option_spec in self.CONFIG_FILE_OPTIONS: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

330 was_set = self._set_attr_from_config_option(cp, *option_spec) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

331 if was_set: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

332 any_set = True 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

333 except ValueError as err: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UVyz6AB9WXCD#EF!YZGH7IJ$01234

334 raise ConfigError(f"Couldn't read config file {filename}: {err}") from err 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UVyz6AB9WXCD#EF!YZGH7IJ$01234

335 

336 # Check that there are no unrecognized options. 

337 all_options = collections.defaultdict(set) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

338 for option_spec in self.CONFIG_FILE_OPTIONS: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

339 section, option = option_spec[1].split(":") 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

340 all_options[section].add(option) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

341 

342 for section, options in all_options.items(): 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

343 real_section = cp.real_section(section) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

344 if real_section: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

345 for unknown in set(cp.options(section)) - options: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

346 warn( 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UVyz6AB9WXCD#EF!YZGH7IJ$01234

347 "Unrecognized option '[{}] {}=' in config file {}".format( 

348 real_section, 

349 unknown, 

350 filename, 

351 ), 

352 ) 

353 

354 # [paths] is special 

355 if cp.has_section("paths"): 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

356 for option in cp.options("paths"): 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

357 self.paths[option] = cp.getlist("paths", option) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

358 any_set = True 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

359 

360 # plugins can have options 

361 for plugin in self.plugins: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

362 if cp.has_section(plugin): 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

363 self.plugin_options[plugin] = cp.get_section(plugin) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UVyz6AB9WXCD#EF!YZGH7IJ$01234

364 any_set = True 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UVyz6AB9WXCD#EF!YZGH7IJ$01234

365 

366 # Was this file used as a config file? If it's specifically our file, 

367 # then it was used. If we're piggybacking on someone else's file, 

368 # then it was only used if we found some settings in it. 

369 if our_file: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

370 used = True 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

371 else: 

372 used = any_set 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UVyz6AB9WXCD#EF!YZGH7IJ$01234

373 

374 if used: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

375 self.config_file = os.path.abspath(filename) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

376 with open(filename, "rb") as f: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

377 self._config_contents = f.read() 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

378 

379 return used 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

380 

381 def copy(self) -> CoverageConfig: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

382 """Return a copy of the configuration.""" 

383 return copy.deepcopy(self) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

384 

385 CONCURRENCY_CHOICES: Final[set[str]] = { 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

386 "thread", 

387 "gevent", 

388 "greenlet", 

389 "eventlet", 

390 "multiprocessing", 

391 } 

392 

393 # Mutually exclusive concurrency settings. 

394 LIGHT_THREADS = {"greenlet", "eventlet", "gevent"} 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

395 

396 CONFIG_FILE_OPTIONS = [ 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

397 # These are *args for _set_attr_from_config_option: 

398 # (attr, where, type_="") 

399 # 

400 # attr is the attribute to set on the CoverageConfig object. 

401 # where is the section:name to read from the configuration file. 

402 # type_ is the optional type to apply, by using .getTYPE to read the 

403 # configuration value from the file. 

404 # 

405 # [run] 

406 ("branch", "run:branch", "boolean"), 

407 ("command_line", "run:command_line"), 

408 ("concurrency", "run:concurrency", "list"), 

409 ("context", "run:context"), 

410 ("core", "run:core"), 

411 ("cover_pylib", "run:cover_pylib", "boolean"), 

412 ("data_file", "run:data_file", "file"), 

413 ("debug", "run:debug", "list"), 

414 ("debug_file", "run:debug_file", "file"), 

415 ("disable_warnings", "run:disable_warnings", "list"), 

416 ("dynamic_context", "run:dynamic_context"), 

417 ("parallel", "run:parallel", "boolean"), 

418 ("patch", "run:patch", "list"), 

419 ("plugins", "run:plugins", "list"), 

420 ("relative_files", "run:relative_files", "boolean"), 

421 ("run_include", "run:include", "list"), 

422 ("run_omit", "run:omit", "list"), 

423 ("sigterm", "run:sigterm", "boolean"), 

424 ("source", "run:source", "list"), 

425 ("source_pkgs", "run:source_pkgs", "list"), 

426 ("source_dirs", "run:source_dirs", "list"), 

427 ("timid", "run:timid", "boolean"), 

428 ("_crash", "run:_crash"), 

429 # 

430 # [report] 

431 ("exclude_list", "report:exclude_lines", "regexlist"), 

432 ("exclude_also", "report:exclude_also", "regexlist"), 

433 ("fail_under", "report:fail_under", "float"), 

434 ("format", "report:format"), 

435 ("ignore_errors", "report:ignore_errors", "boolean"), 

436 ("include_namespace_packages", "report:include_namespace_packages", "boolean"), 

437 ("partial_always_list", "report:partial_branches_always", "regexlist"), 

438 ("partial_list", "report:partial_branches", "regexlist"), 

439 ("partial_also", "report:partial_also", "regexlist"), 

440 ("precision", "report:precision", "int"), 

441 ("report_contexts", "report:contexts", "list"), 

442 ("report_include", "report:include", "list"), 

443 ("report_omit", "report:omit", "list"), 

444 ("show_missing", "report:show_missing", "boolean"), 

445 ("skip_covered", "report:skip_covered", "boolean"), 

446 ("skip_empty", "report:skip_empty", "boolean"), 

447 ("sort", "report:sort"), 

448 # 

449 # [html] 

450 ("extra_css", "html:extra_css"), 

451 ("html_dir", "html:directory", "file"), 

452 ("html_skip_covered", "html:skip_covered", "boolean"), 

453 ("html_skip_empty", "html:skip_empty", "boolean"), 

454 ("html_title", "html:title"), 

455 ("show_contexts", "html:show_contexts", "boolean"), 

456 # 

457 # [xml] 

458 ("xml_output", "xml:output", "file"), 

459 ("xml_package_depth", "xml:package_depth", "int"), 

460 # 

461 # [json] 

462 ("json_output", "json:output", "file"), 

463 ("json_pretty_print", "json:pretty_print", "boolean"), 

464 ("json_show_contexts", "json:show_contexts", "boolean"), 

465 # 

466 # [lcov] 

467 ("lcov_output", "lcov:output", "file"), 

468 ("lcov_line_checksums", "lcov:line_checksums", "boolean"), 

469 ] 

470 

471 def _set_attr_from_config_option( 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)

472 self, 

473 cp: TConfigParser, 

474 attr: str, 

475 where: str, 

476 type_: str = "", 

477 ) -> bool: 

478 """Set an attribute on self if it exists in the ConfigParser. 

479 

480 Returns True if the attribute was set. 

481 

482 """ 

483 section, option = where.split(":") 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

484 if cp.has_option(section, option): 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

485 method = getattr(cp, f"get{type_}") 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

486 setattr(self, attr, method(section, option)) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

487 return True 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

488 return False 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

489 

490 def get_plugin_options(self, plugin: str) -> TConfigSectionOut: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

491 """Get a dictionary of options for the plugin named `plugin`.""" 

492 return self.plugin_options.get(plugin, {}) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

493 

494 def set_option(self, option_name: str, value: TConfigValueIn | TConfigSectionIn) -> None: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

495 """Set an option in the configuration. 

496 

497 `option_name` is a colon-separated string indicating the section and 

498 option name. For example, the ``branch`` option in the ``[run]`` 

499 section of the config file would be indicated with `"run:branch"`. 

500 

501 `value` is the new value for the option. 

502 

503 """ 

504 # Special-cased options. 

505 if option_name == "paths": 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

506 # This is ugly, but type-checks and ensures the values are close 

507 # to right. 

508 self.paths = {} 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UVyz6AB9WXCD#EF!YZ%GH7IJ$01234

509 assert isinstance(value, Mapping) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UVyz6AB9WXCD#EF!YZ%GH7IJ$01234

510 for k, v in value.items(): 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UVyz6AB9WXCD#EF!YZ%GH7IJ$01234

511 assert isinstance(v, Iterable) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UVyz6AB9WXCD#EF!YZ%GH7IJ$01234

512 self.paths[k] = list(v) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UVyz6AB9WXCD#EF!YZ%GH7IJ$01234

513 return 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UVyz6AB9WXCD#EF!YZ%GH7IJ$01234

514 

515 # Check all the hard-coded options. 

516 for option_spec in self.CONFIG_FILE_OPTIONS: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

517 attr, where = option_spec[:2] 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

518 if where == option_name: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

519 setattr(self, attr, value) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

520 return 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

521 

522 # See if it's a plugin option. 

523 plugin_name, _, key = option_name.partition(":") 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UVyz6AB9WXCD#EF!YZ%GH7IJ$01234

524 if key and plugin_name in self.plugins: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UVyz6AB9WXCD#EF!YZ%GH7IJ$01234

525 self.plugin_options.setdefault(plugin_name, {})[key] = value # type: ignore[index] 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UVyz6AB9WXCD#EF!YZ%GH7IJ$01234

526 return 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UVyz6AB9WXCD#EF!YZ%GH7IJ$01234

527 

528 # If we get here, we didn't find the option. 

529 raise ConfigError(f"No such option: {option_name!r}") 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UVyz6AB9WXCD#EF!YZ%GH7IJ$01234

530 

531 def get_option(self, option_name: str) -> TConfigValueOut | None: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

532 """Get an option from the configuration. 

533 

534 `option_name` is a colon-separated string indicating the section and 

535 option name. For example, the ``branch`` option in the ``[run]`` 

536 section of the config file would be indicated with `"run:branch"`. 

537 

538 Returns the value of the option. 

539 

540 """ 

541 # Special-cased options. 

542 if option_name == "paths": 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

543 return self.paths 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UVyz6AB9WXCD#EF!YZGH7IJ$01234

544 

545 # Check all the hard-coded options. 

546 for option_spec in self.CONFIG_FILE_OPTIONS: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

547 attr, where = option_spec[:2] 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

548 if where == option_name: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

549 return getattr(self, attr) # type: ignore[no-any-return] 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

550 

551 # See if it's a plugin option. 

552 plugin_name, _, key = option_name.partition(":") 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UVyz6AB9WXCD#EF!YZGH7IJ$01234

553 if key and plugin_name in self.plugins: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UVyz6AB9WXCD#EF!YZGH7IJ$01234

554 return self.plugin_options.get(plugin_name, {}).get(key) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UVyz6AB9WXCD#EF!YZGH7IJ$01234

555 

556 # If we get here, we didn't find the option. 

557 raise ConfigError(f"No such option: {option_name!r}") 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UVyz6AB9WXCD#EF!YZGH7IJ$01234

558 

559 def post_process(self) -> None: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

560 """Make final adjustments to settings to make them usable.""" 

561 self.paths = {k: [process_file_value(f) for f in v] for k, v in self.paths.items()} 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

562 

563 self.exclude_list += self.exclude_also 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

564 self.partial_list += self.partial_also 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

565 

566 if "subprocess" in self.patch: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

567 self.parallel = True 1abcdefghijklmnopqrstuv5wx8yz6AB9CD#EF!GH7IJ$23

568 

569 # We can handle a few concurrency options here, but only one at a time. 

570 concurrencies = set(self.concurrency) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

571 unknown = concurrencies - self.CONCURRENCY_CHOICES 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

572 if unknown: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

573 show = ", ".join(sorted(unknown)) 1abcdKLefghMNijklOPmnopQRqrstSTuvwxUVyzABWXCDEFYZGHIJ01234

574 raise ConfigError(f"Unknown concurrency choices: {show}") 1abcdKLefghMNijklOPmnopQRqrstSTuvwxUVyzABWXCDEFYZGHIJ01234

575 light_threads = concurrencies & self.LIGHT_THREADS 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

576 if len(light_threads) > 1: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

577 show = ", ".join(sorted(light_threads)) 1abcdKLefghMNijklOPmnopQRqrstSTuvwxUVyzABWXCDEFYZGHIJ01234

578 raise ConfigError(f"Conflicting concurrency settings: {show}") 1abcdKLefghMNijklOPmnopQRqrstSTuvwxUVyzABWXCDEFYZGHIJ01234

579 

580 def debug_info(self) -> list[tuple[str, Any]]: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

581 """Make a list of (name, value) pairs for writing debug info.""" 

582 return human_sorted_items((k, v) for k, v in self.__dict__.items() if not k.startswith("_")) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

583 

584 def serialize(self) -> str: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

585 """Convert to a string that can be ingested with `deserialize`. 

586 

587 File paths used by `coverage run` are made absolute to ensure the 

588 deserialized config will refer to the same files. 

589 """ 

590 data = {k: v for k, v in self.__dict__.items() if not k.startswith("_")} 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

591 for k, must_exist in self.SERIALIZE_ABSPATH: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

592 abs_fn = abs_path_if_exists if must_exist else os.path.abspath 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

593 v = data[k] 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

594 if isinstance(v, list): 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

595 v = list(map(abs_fn, v)) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

596 elif isinstance(v, str): 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

597 v = abs_fn(v) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

598 data[k] = v 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

599 return base64.b64encode(json.dumps(data).encode()).decode() 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

600 

601 @classmethod 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

602 def deserialize(cls, config_str: str) -> CoverageConfig: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

603 """Take a string from `serialize`, and make a CoverageConfig.""" 

604 data = json.loads(base64.b64decode(config_str.encode()).decode()) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

605 config = cls() 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

606 config.__dict__.update(data) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

607 return config 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

608 

609 

610def process_file_value(path: str) -> str: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

611 """Make adjustments to a file path to make it usable.""" 

612 return os.path.expanduser(path) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

613 

614 

615def abs_path_if_exists(path: str) -> str: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

616 """os.path.abspath, but only if the path exists.""" 

617 if os.path.exists(path): 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

618 return os.path.abspath(path) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

619 else: 

620 return path 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

621 

622 

623def process_regexlist(name: str, option: str, values: list[str]) -> list[str]: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

624 """Check the values in a regex list and keep the non-blank ones.""" 

625 value_list = [] 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

626 for value in values: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

627 value = value.strip() 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

628 try: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

629 re.compile(value) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

630 except re.error as e: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

631 raise ConfigError(f"Invalid [{name}].{option} value {value!r}: {e}") from e 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

632 if value: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

633 value_list.append(value) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

634 return value_list 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

635 

636 

637def config_files_to_try(config_file: bool | str) -> list[tuple[str, bool, bool]]: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

638 """What config files should we try to read? 

639 

640 Returns a list of tuples: 

641 (filename, is_our_file, was_file_specified) 

642 """ 

643 

644 # Some API users were specifying ".coveragerc" to mean the same as 

645 # True, so make it so. 

646 if config_file == ".coveragerc": 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

647 config_file = True 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UVyz6AB9WXCD#EF!YZGH7IJ$01234

648 specified_file = config_file is not True 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

649 if not specified_file: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

650 # No file was specified. Check COVERAGE_RCFILE. 

651 rcfile = os.getenv("COVERAGE_RCFILE") 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

652 if rcfile: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

653 config_file = rcfile 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

654 specified_file = True 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

655 if not specified_file: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

656 # Still no file specified. Default to .coveragerc 

657 config_file = ".coveragerc" 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

658 assert isinstance(config_file, str) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

659 files_to_try = [ 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

660 (config_file, True, specified_file), 

661 ("setup.cfg", False, False), 

662 ("tox.ini", False, False), 

663 ("pyproject.toml", False, False), 

664 ] 

665 return files_to_try 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

666 

667 

668def read_coverage_config( 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

669 config_file: bool | str, 

670 warn: Callable[[str], None], 

671 **kwargs: TConfigValueIn, 

672) -> CoverageConfig: 

673 """Read the coverage.py configuration. 

674 

675 Arguments: 

676 config_file: a boolean or string, see the `Coverage` class for the 

677 tricky details. 

678 warn: a function to issue warnings. 

679 all others: keyword arguments from the `Coverage` class, used for 

680 setting values in the configuration. 

681 

682 Returns: 

683 config: 

684 config is a CoverageConfig object read from the appropriate 

685 configuration file. 

686 

687 """ 

688 # Build the configuration from a number of sources: 

689 # 1) defaults: 

690 config = CoverageConfig() 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

691 

692 # 2) from a file: 

693 if config_file: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

694 files_to_try = config_files_to_try(config_file) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

695 

696 for fname, our_file, specified_file in files_to_try: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

697 config_read = config.from_file(fname, warn, our_file=our_file) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

698 if config_read: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

699 break 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

700 if specified_file: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

701 raise ConfigError(f"Couldn't read {fname!r} as a config file") 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UVyz6AB9WXCD#EF!YZGH7IJ$01234

702 

703 # 3) from environment variables: 

704 env_data_file = os.getenv("COVERAGE_FILE") 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

705 if env_data_file: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

706 config.data_file = env_data_file 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UVyz6AB9WXCD#EF!YZGH7IJ$01234

707 

708 # $set_env.py: COVERAGE_DEBUG - Debug options: https://coverage.rtfd.io/cmd.html#debug 

709 debugs = os.getenv("COVERAGE_DEBUG") 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

710 if debugs: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

711 config.debug.extend(d.strip() for d in debugs.split(",")) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

712 

713 # Read the COVERAGE_CORE environment variable for backward compatibility, 

714 # and because we use it in the test suite to pick a specific core. 

715 env_core = os.getenv("COVERAGE_CORE") 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

716 if env_core: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

717 config.core = env_core 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

718 

719 # 4) from constructor arguments: 

720 config.from_args(**kwargs) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

721 

722 # 5) for our benchmark, force settings using a secret environment variable: 

723 force_file = os.getenv("COVERAGE_FORCE_CONFIG") 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

724 if force_file: 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

725 config.from_file(force_file, warn, our_file=True) 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UVyz6AB9WXCD#EF!YZGH7IJ$01234

726 

727 # Once all the config has been collected, there's a little post-processing 

728 # to do. 

729 config.post_process() 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234

730 

731 return config 1abcdKLefghMNijklOPmnopQRqrstSTuv5wx8UV'yz6AB9WX(CD#EF!YZ%GH7IJ$01)234