Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/iniconfig/__init__.py: 63%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

105 statements  

1""" brain-dead simple parser for ini-style files. 

2(C) Ronny Pfannschmidt, Holger Krekel -- MIT licensed 

3""" 

4from __future__ import annotations 

5from typing import ( 

6 Callable, 

7 Iterator, 

8 Mapping, 

9 Optional, 

10 Tuple, 

11 TypeVar, 

12 Union, 

13 TYPE_CHECKING, 

14 NoReturn, 

15 NamedTuple, 

16 overload, 

17 cast, 

18) 

19 

20import os 

21 

22if TYPE_CHECKING: 

23 from typing import Final 

24 

25__all__ = ["IniConfig", "ParseError", "COMMENTCHARS", "iscommentline"] 

26 

27from .exceptions import ParseError 

28from . import _parse 

29from ._parse import COMMENTCHARS, iscommentline 

30 

31_D = TypeVar("_D") 

32_T = TypeVar("_T") 

33 

34 

35class SectionWrapper: 

36 config: Final[IniConfig] 

37 name: Final[str] 

38 

39 def __init__(self, config: IniConfig, name: str) -> None: 

40 self.config = config 

41 self.name = name 

42 

43 def lineof(self, name: str) -> int | None: 

44 return self.config.lineof(self.name, name) 

45 

46 @overload 

47 def get(self, key: str) -> str | None: 

48 ... 

49 

50 @overload 

51 def get( 

52 self, 

53 key: str, 

54 convert: Callable[[str], _T], 

55 ) -> _T | None: 

56 ... 

57 

58 @overload 

59 def get( 

60 self, 

61 key: str, 

62 default: None, 

63 convert: Callable[[str], _T], 

64 ) -> _T | None: 

65 ... 

66 

67 @overload 

68 def get(self, key: str, default: _D, convert: None = None) -> str | _D: 

69 ... 

70 

71 @overload 

72 def get( 

73 self, 

74 key: str, 

75 default: _D, 

76 convert: Callable[[str], _T], 

77 ) -> _T | _D: 

78 ... 

79 

80 # TODO: investigate possible mypy bug wrt matching the passed over data 

81 def get( # type: ignore [misc] 

82 self, 

83 key: str, 

84 default: _D | None = None, 

85 convert: Callable[[str], _T] | None = None, 

86 ) -> _D | _T | str | None: 

87 return self.config.get(self.name, key, convert=convert, default=default) 

88 

89 def __getitem__(self, key: str) -> str: 

90 return self.config.sections[self.name][key] 

91 

92 def __iter__(self) -> Iterator[str]: 

93 section: Mapping[str, str] = self.config.sections.get(self.name, {}) 

94 

95 def lineof(key: str) -> int: 

96 return self.config.lineof(self.name, key) # type: ignore[return-value] 

97 

98 yield from sorted(section, key=lineof) 

99 

100 def items(self) -> Iterator[tuple[str, str]]: 

101 for name in self: 

102 yield name, self[name] 

103 

104 

105class IniConfig: 

106 path: Final[str] 

107 sections: Final[Mapping[str, Mapping[str, str]]] 

108 

109 def __init__( 

110 self, 

111 path: str | os.PathLike[str], 

112 data: str | None = None, 

113 encoding: str = "utf-8", 

114 ) -> None: 

115 self.path = os.fspath(path) 

116 if data is None: 

117 with open(self.path, encoding=encoding) as fp: 

118 data = fp.read() 

119 

120 tokens = _parse.parse_lines(self.path, data.splitlines(True)) 

121 

122 self._sources = {} 

123 sections_data: dict[str, dict[str, str]] 

124 self.sections = sections_data = {} 

125 

126 for lineno, section, name, value in tokens: 

127 if section is None: 

128 raise ParseError(self.path, lineno, "no section header defined") 

129 self._sources[section, name] = lineno 

130 if name is None: 

131 if section in self.sections: 

132 raise ParseError( 

133 self.path, lineno, f"duplicate section {section!r}" 

134 ) 

135 sections_data[section] = {} 

136 else: 

137 if name in self.sections[section]: 

138 raise ParseError(self.path, lineno, f"duplicate name {name!r}") 

139 assert value is not None 

140 sections_data[section][name] = value 

141 

142 def lineof(self, section: str, name: str | None = None) -> int | None: 

143 lineno = self._sources.get((section, name)) 

144 return None if lineno is None else lineno + 1 

145 

146 @overload 

147 def get( 

148 self, 

149 section: str, 

150 name: str, 

151 ) -> str | None: 

152 ... 

153 

154 @overload 

155 def get( 

156 self, 

157 section: str, 

158 name: str, 

159 convert: Callable[[str], _T], 

160 ) -> _T | None: 

161 ... 

162 

163 @overload 

164 def get( 

165 self, 

166 section: str, 

167 name: str, 

168 default: None, 

169 convert: Callable[[str], _T], 

170 ) -> _T | None: 

171 ... 

172 

173 @overload 

174 def get( 

175 self, section: str, name: str, default: _D, convert: None = None 

176 ) -> str | _D: 

177 ... 

178 

179 @overload 

180 def get( 

181 self, 

182 section: str, 

183 name: str, 

184 default: _D, 

185 convert: Callable[[str], _T], 

186 ) -> _T | _D: 

187 ... 

188 

189 def get( # type: ignore 

190 self, 

191 section: str, 

192 name: str, 

193 default: _D | None = None, 

194 convert: Callable[[str], _T] | None = None, 

195 ) -> _D | _T | str | None: 

196 try: 

197 value: str = self.sections[section][name] 

198 except KeyError: 

199 return default 

200 else: 

201 if convert is not None: 

202 return convert(value) 

203 else: 

204 return value 

205 

206 def __getitem__(self, name: str) -> SectionWrapper: 

207 if name not in self.sections: 

208 raise KeyError(name) 

209 return SectionWrapper(self, name) 

210 

211 def __iter__(self) -> Iterator[SectionWrapper]: 

212 for name in sorted(self.sections, key=self.lineof): # type: ignore 

213 yield SectionWrapper(self, name) 

214 

215 def __contains__(self, arg: str) -> bool: 

216 return arg in self.sections