Coverage for /home/pradyumna/Languages/python/packages/xdgpspconf/xdgpspconf/config_io.py: 43%

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

82 statements  

1#!/usr/bin/env python3 

2# -*- coding: utf-8; mode: python; -*- 

3# Copyright © 2021, 2022 Pradyumna Paranjape 

4# 

5# This file is part of xdgpspconf. 

6# 

7# xdgpspconf is free software: you can redistribute it and/or modify 

8# it under the terms of the GNU Lesser General Public License as published by 

9# the Free Software Foundation, either version 3 of the License, or 

10# (at your option) any later version. 

11# 

12# xdgpspconf is distributed in the hope that it will be useful, 

13# but WITHOUT ANY WARRANTY; without even the implied warranty of 

14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

15# GNU Lesser General Public License for more details. 

16# 

17# You should have received a copy of the GNU Lesser General Public License 

18# along with xdgpspconf. If not, see <https://www.gnu.org/licenses/>. 

19# 

20""" 

21Read/Write configurations. 

22 

23""" 

24 

25import configparser 

26from pathlib import Path 

27from typing import Any, Dict 

28 

29import toml 

30import yaml 

31 

32from xdgpspconf.errors import BadConf 

33 

34CONF_EXT = '.yml', '.yaml', '.toml', '.conf' 

35""" 

36Extensions that are supported (parsed) by this module 

37""" 

38 

39 

40def parse_yaml(config: Path) -> Dict[str, Any]: 

41 """ 

42 Read configuration. 

43 

44 Args: 

45 config: path to yaml config file 

46 

47 Returns: 

48 parsed configuration 

49 """ 

50 with open(config, 'r') as rcfile: 

51 conf: Dict[str, Any] = yaml.safe_load(rcfile) 

52 if conf is None: # pragma: no cover 

53 raise yaml.YAMLError 

54 return conf 

55 

56 

57def parse_toml(config: Path, section: str = None) -> Dict[str, Any]: 

58 """ 

59 Read configuration. 

60 

61 Args: 

62 config: path to yaml config file 

63 section: section in ``pyproject.toml`` corresponding to project 

64 

65 Returns: 

66 parsed configuration 

67 """ 

68 if section is not None: 

69 with open(config, 'r') as rcfile: 

70 conf: Dict[str, Any] = toml.load(rcfile).get(section, {}) 

71 return conf 

72 with open(config, 'r') as rcfile: 

73 conf = dict(toml.load(rcfile)) 

74 if conf is None: # pragma: no cover 

75 raise toml.TomlDecodeError 

76 return conf 

77 

78 

79def parse_ini(config: Path, section: str = None) -> Dict[str, Any]: 

80 """ 

81 Read configuration. 

82 

83 

84 Args: 

85 config: path to yaml config file 

86 section: section in ``pyproject.toml`` corresponding to project 

87 

88 Returns: 

89 parsed configuration 

90 """ 

91 parser = configparser.ConfigParser() 

92 parser.read(config) 

93 if section is not None: 

94 return { 

95 pspcfg.replace(f'{section}.', ''): dict(parser.items(pspcfg)) 

96 for pspcfg in parser.sections() if f'{section}.' in pspcfg 

97 } 

98 return { 

99 pspcfg: dict(parser.items(pspcfg)) 

100 for pspcfg in parser.sections() 

101 } # pragma: no cover 

102 

103 

104def parse_rc(config: Path, project: str = None) -> Dict[str, Any]: 

105 """ 

106 Parse rc file. 

107 

108 Args: 

109 config: path to configuration file 

110 project: name of project (to locate subsection from pyptoject.toml) 

111 

112 Returns: 

113 configuration sections 

114 

115 Raises: 

116 BadConf: Bad configuration 

117 

118 """ 

119 if config.name == 'setup.cfg': 

120 # declared inside setup.cfg 

121 return parse_ini(config, section=project) 

122 if config.name == 'pyproject.toml': 

123 # declared inside pyproject.toml 

124 return parse_toml(config, section=project) 

125 try: 

126 # yaml configuration format 

127 return parse_yaml(config) 

128 except yaml.YAMLError: 

129 try: 

130 # toml configuration format 

131 return parse_toml(config) 

132 except toml.TomlDecodeError: 

133 try: 

134 # try generic config-parser 

135 return parse_ini(config) 

136 except configparser.Error: 

137 raise BadConf(config_file=config) from None 

138 

139 

140def write_yaml(data: Dict[str, Any], 

141 config: Path, 

142 force: str = 'fail') -> bool: 

143 """ 

144 Write data to configuration file. 

145 

146 Args: 

147 data: serial data to save 

148 config: configuration file path 

149 force: force overwrite {'overwrite','update','fail'} 

150 

151 Returns: 

152 write success 

153 

154 """ 

155 old_data: Dict[str, Any] = {} 

156 if config.is_file(): 

157 # file already exists 

158 if force == 'fail': 

159 return False 

160 if force == 'update': 

161 old_data = parse_yaml(config) 

162 data = {**old_data, **data} 

163 with open(config, 'w') as rcfile: 

164 yaml.dump(data, rcfile) 

165 return True 

166 

167 

168def write_toml(data: Dict[str, Any], 

169 config: Path, 

170 force: str = 'fail') -> bool: 

171 """ 

172 Write data to configuration file. 

173 

174 Args: 

175 data: serial data to save 

176 config: configuration file path 

177 force: force overwrite {'overwrite', 'update', 'fail'} 

178 

179 Returns: 

180 write success 

181 

182 """ 

183 old_data: Dict[str, Any] = {} 

184 if config.is_file(): 

185 # file already exists 

186 if force == 'fail': 

187 return False 

188 if force == 'update': 

189 old_data = parse_toml(config) 

190 data = {**old_data, **data} 

191 with open(config, 'w') as rcfile: 

192 toml.dump(data, rcfile) 

193 return True 

194 

195 

196def write_ini(data: Dict[str, Any], config: Path, force: str = 'fail') -> bool: 

197 """ 

198 Write data to configuration file. 

199 

200 Args: 

201 data: serial data to save 

202 config: configuration file path 

203 force: force overwrite {'overwrite', 'update', 'fail'} 

204 

205 Returns: 

206 write success 

207 

208 """ 

209 old_data: Dict[str, Any] = {} 

210 if config.is_file(): 

211 # file already exists 

212 if force == 'fail': 

213 return False 

214 if force == 'update': 

215 old_data = parse_ini(config) 

216 data = {**old_data, **data} 

217 parser = configparser.ConfigParser() 

218 parser.update(data) 

219 with open(config, 'w') as rcfile: 

220 parser.write(rcfile) 

221 return True 

222 

223 

224def write_rc(data: Dict[str, Any], config: Path, force: str = 'fail') -> bool: 

225 """ 

226 Write data to configuration file. 

227 

228 Args: 

229 data: serial data to save 

230 config: configuration file path 

231 force: force overwrite {'overwrite', 'update', 'fail'} 

232 

233 Returns: 

234 write success 

235 

236 """ 

237 if config.suffix in ('.conf', '.cfg', '.ini'): 

238 return write_ini(data, config, force) 

239 if config.suffix == '.toml': 

240 return write_toml(data, config, force) 

241 # assume yaml 

242 return write_yaml(data, config, force)