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
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
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.
23"""
25import configparser
26from pathlib import Path
27from typing import Any, Dict
29import toml
30import yaml
32from xdgpspconf.errors import BadConf
34CONF_EXT = '.yml', '.yaml', '.toml', '.conf'
35"""
36Extensions that are supported (parsed) by this module
37"""
40def parse_yaml(config: Path) -> Dict[str, Any]:
41 """
42 Read configuration.
44 Args:
45 config: path to yaml config file
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
57def parse_toml(config: Path, section: str = None) -> Dict[str, Any]:
58 """
59 Read configuration.
61 Args:
62 config: path to yaml config file
63 section: section in ``pyproject.toml`` corresponding to project
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
79def parse_ini(config: Path, section: str = None) -> Dict[str, Any]:
80 """
81 Read configuration.
84 Args:
85 config: path to yaml config file
86 section: section in ``pyproject.toml`` corresponding to project
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
104def parse_rc(config: Path, project: str = None) -> Dict[str, Any]:
105 """
106 Parse rc file.
108 Args:
109 config: path to configuration file
110 project: name of project (to locate subsection from pyptoject.toml)
112 Returns:
113 configuration sections
115 Raises:
116 BadConf: Bad configuration
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
140def write_yaml(data: Dict[str, Any],
141 config: Path,
142 force: str = 'fail') -> bool:
143 """
144 Write data to configuration file.
146 Args:
147 data: serial data to save
148 config: configuration file path
149 force: force overwrite {'overwrite','update','fail'}
151 Returns:
152 write success
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
168def write_toml(data: Dict[str, Any],
169 config: Path,
170 force: str = 'fail') -> bool:
171 """
172 Write data to configuration file.
174 Args:
175 data: serial data to save
176 config: configuration file path
177 force: force overwrite {'overwrite', 'update', 'fail'}
179 Returns:
180 write success
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
196def write_ini(data: Dict[str, Any], config: Path, force: str = 'fail') -> bool:
197 """
198 Write data to configuration file.
200 Args:
201 data: serial data to save
202 config: configuration file path
203 force: force overwrite {'overwrite', 'update', 'fail'}
205 Returns:
206 write success
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
224def write_rc(data: Dict[str, Any], config: Path, force: str = 'fail') -> bool:
225 """
226 Write data to configuration file.
228 Args:
229 data: serial data to save
230 config: configuration file path
231 force: force overwrite {'overwrite', 'update', 'fail'}
233 Returns:
234 write success
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)