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

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

95 statements  

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

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

3""" 

4 

5from __future__ import annotations 

6from typing import ( 

7 Callable, 

8 Iterator, 

9 Mapping, 

10 TypeVar, 

11 TYPE_CHECKING, 

12 overload, 

13) 

14 

15import os 

16 

17if TYPE_CHECKING: 

18 from typing import Final 

19 

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

21 

22from .exceptions import ParseError 

23from . import _parse 

24from ._parse import COMMENTCHARS, iscommentline 

25 

26_D = TypeVar("_D") 

27_T = TypeVar("_T") 

28 

29 

30class SectionWrapper: 

31 config: Final[IniConfig] 

32 name: Final[str] 

33 

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

35 self.config = config 

36 self.name = name 

37 

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

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

40 

41 @overload 

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

43 

44 @overload 

45 def get( 

46 self, 

47 key: str, 

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

49 ) -> _T | None: ... 

50 

51 @overload 

52 def get( 

53 self, 

54 key: str, 

55 default: None, 

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

57 ) -> _T | None: ... 

58 

59 @overload 

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

61 

62 @overload 

63 def get( 

64 self, 

65 key: str, 

66 default: _D, 

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

68 ) -> _T | _D: ... 

69 

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

71 def get( # type: ignore [misc] 

72 self, 

73 key: str, 

74 default: _D | None = None, 

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

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

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

78 

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

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

81 

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

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

84 

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

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

87 

88 yield from sorted(section, key=lineof) 

89 

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

91 for name in self: 

92 yield name, self[name] 

93 

94 

95class IniConfig: 

96 path: Final[str] 

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

98 

99 def __init__( 

100 self, 

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

102 data: str | None = None, 

103 encoding: str = "utf-8", 

104 ) -> None: 

105 self.path = os.fspath(path) 

106 if data is None: 

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

108 data = fp.read() 

109 

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

111 

112 self._sources = {} 

113 sections_data: dict[str, dict[str, str]] 

114 self.sections = sections_data = {} 

115 

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

117 if section is None: 

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

119 self._sources[section, name] = lineno 

120 if name is None: 

121 if section in self.sections: 

122 raise ParseError( 

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

124 ) 

125 sections_data[section] = {} 

126 else: 

127 if name in self.sections[section]: 

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

129 assert value is not None 

130 sections_data[section][name] = value 

131 

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

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

134 return None if lineno is None else lineno + 1 

135 

136 @overload 

137 def get( 

138 self, 

139 section: str, 

140 name: str, 

141 ) -> str | None: ... 

142 

143 @overload 

144 def get( 

145 self, 

146 section: str, 

147 name: str, 

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

149 ) -> _T | None: ... 

150 

151 @overload 

152 def get( 

153 self, 

154 section: str, 

155 name: str, 

156 default: None, 

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

158 ) -> _T | None: ... 

159 

160 @overload 

161 def get( 

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

163 ) -> str | _D: ... 

164 

165 @overload 

166 def get( 

167 self, 

168 section: str, 

169 name: str, 

170 default: _D, 

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

172 ) -> _T | _D: ... 

173 

174 def get( # type: ignore 

175 self, 

176 section: str, 

177 name: str, 

178 default: _D | None = None, 

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

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

181 try: 

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

183 except KeyError: 

184 return default 

185 else: 

186 if convert is not None: 

187 return convert(value) 

188 else: 

189 return value 

190 

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

192 if name not in self.sections: 

193 raise KeyError(name) 

194 return SectionWrapper(self, name) 

195 

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

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

198 yield SectionWrapper(self, name) 

199 

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

201 return arg in self.sections