py.lib.aw_config

Yohn Y. 2026-06-23 Parent:ed99d4db56a7 Child:260bceaa2d62

16:34b98b38a9f0 Go to Latest

py.lib.aw_config/src/aw_config/file.py

.. 0.202606.2 + Возможность удаления конфигурации из менеджера

History
1 # coding: utf-8
2 """\
3 Функции, помогающие в разборе конфигурационного файла
4 """
5 import toml
6 from os.path import exists
7 from typing import Union, Any
9 from .error import Error
10 from .type_helpers import BaseTypeHelper
11 from ._lib import _UNDEFINED
14 class ConfigFileError(Error):
15 """\
16 Базовое исключение при обработке конфигурационного файла
17 """
20 class ConfigSectionNotFound(ConfigFileError):
21 """\
22 Отсутствие нужного раздела в конфигурации
23 """
26 class ConfigFileContent(object):
27 """\
28 Работа с содержимым файла конфигурации
29 """
30 def __init__(self, file_data, section_name=None):
31 """\
32 :param file_data: Разобранное в словарь содержимое файла конфигурации
33 :param section_name: Имя секции в конфигурации
34 """
35 self.d = file_data
36 self.section = section_name
38 def _format_err_msg(self, msg: str) -> str:
39 if self.section is not None:
40 return f'Раздел конфигурации "{self.section}": {msg}'
42 else:
43 return msg
45 def __enter__(self):
46 return self
48 def __exit__(self, exc_type, exc_val, exc_tb):
49 pass
51 def get_value(self, name: str,
52 val_type: Union[type, BaseTypeHelper] = str,
53 default: Any = _UNDEFINED,
54 mandatory: bool = True):
55 """\
56 Получить из текущего раздела конфигурации нужный элемент
58 :param name: имя параметра
59 :param val_type: тип параметра
60 :param default: значение параметра по умолчанию
61 :param mandatory: является ли параметр обязательным. Если ``False``, то в случае отсутствия
62 параметра будет использовано значение None
63 """
64 if name not in self.d:
65 if default is not _UNDEFINED:
66 return default
68 elif mandatory:
69 raise ConfigFileError(self._format_err_msg(f'Параметр "{name}" в файле отсутствует'))
71 else:
72 return None
74 else:
75 _buf = self.d[name]
76 try:
77 return val_type(_buf)
79 except (ValueError, TypeError) as e:
80 raise ConfigFileError(self._format_err_msg(f'Не удалось привести значение "{_buf}" к типу '
81 f'{repr(val_type)}: {e}'))
83 def _get_section_name(self, *names: str) -> str:
84 """\
85 Поучить имя текущего раздели или подраздела по заданному имени
87 :type names: str
88 :param names: Последовательные имена подразделов для получения доступа к глубокой иерархии
89 :returns: Имя подраздела
90 """
91 res = self.section if self.section is not None else ''
92 if not names:
93 return res
95 else:
96 if res:
97 res += '.'
99 return res + '.'.join(names)
101 def _get_part_by_path(self, *names: str):
102 """\
103 Получить конфигурационный элемент по заданному пути
104 """
105 if not names:
106 raise ValueError(f'Не заданно имя запрашиваемого раздела')
108 upper_path = names[:-1]
109 last_path_el = names[-1]
110 cur_level = self.d
111 cur_section = self._get_section_name()
113 for el in upper_path:
114 cur_section = el if not cur_section else f'{cur_section}.{el}'
116 if el in cur_level:
117 cur_level = cur_level[el]
119 else:
120 raise ConfigSectionNotFound(f'Раздел "{cur_section}" не найден')
122 cur_section = last_path_el if not cur_section else f'{cur_section}.{last_path_el}'
123 if last_path_el in cur_level:
124 return cur_level[last_path_el]
126 else:
127 raise ConfigSectionNotFound(f'Раздел "{cur_section}" не найден')
130 def get_section(self, name: str, mandatory: bool = True):
131 """\
132 Получить раздел конфигурационного файла из текущего или корневого раздела
134 :param name: имя раздела
135 :param mandatory: является ли раздел обязательным или он может отсутствовать в конфигурации. Во втором
136 случае, будет создан пустой словарь и использован в качестве данных раздела.
137 :returns: Экземпляр того-же класса, только для новой секции
138 """
139 section_name = self._get_section_name(name)
140 try:
141 section_val = self._get_part_by_path(name)
143 if not isinstance(section_val, dict):
144 raise ConfigFileError(f'Параметр конфигурации "{section_name}" не является разделом')
146 else:
147 return ConfigFileContent(file_data=section_val, section_name=f'{section_name}')
149 except ConfigSectionNotFound as e:
150 if mandatory:
151 raise e
153 else:
154 return ConfigFileContent(file_data={}, section_name=f'{section_name}')
156 def get_section_by_path(self, *names: str, mandatory: bool = True):
157 """\
158 Поучить секцию конфигурации по явно заданному пути
160 :type names: str
161 :param names: Имя секции на текущем уровне. Можно задавать несколько
162 для прохода по всей иерархии необходимой иерархии одним вызовом.
164 :type mandatory: bool
165 :param mandatory: Является ли искомая секция обязательной?
167 :returns: Экземпляр того-же класса, только для новой секции
168 """
169 section_name = self._get_section_name(*names)
170 try:
171 section_val = self._get_part_by_path(*names)
173 if not isinstance(section_val, dict):
174 raise ConfigFileError(f'Параметр конфигурации "{section_name}" не является разделом')
176 else:
177 return ConfigFileContent(file_data=section_val, section_name=f'{section_name}')
179 except ConfigSectionNotFound as e:
180 if mandatory:
181 raise e
183 else:
184 return ConfigFileContent(file_data={}, section_name=f'{section_name}')
186 def get_list_section(self, name: str, mandatory: bool = True):
187 """\
188 Для случая, когда раздел конфигурации задан массивом toml ([[${! SOME_SECTION !}]])
190 :type name: str
191 :param name: Имя раздела
193 :type mandatory: bool
194 :param mandatory: Данный раздел обязателен и не может отсутствовать
196 :returns: Спискол из экземпляров данного класса, нацеленные на элементы массива хэш-таблиц файла toml
197 """
198 section_name = self._get_section_name(name)
199 try:
200 section_val = self._get_part_by_path(name)
202 if isinstance(section_val, (list, tuple)):
203 res = []
204 for el in section_val:
205 if isinstance(el, dict):
206 res.append(ConfigFileContent(file_data=el, section_name=f'{section_name}'))
208 else:
209 raise ConfigFileError(f'Параметр конфигурации "{section_name}"'
210 f' со значением "{el}"'
211 f' не является разделом')
212 return res
214 elif isinstance(section_val, dict):
215 return [
216 ConfigFileContent(file_data=section_val, section_name=f'{section_name}')
219 else:
220 raise ConfigFileError(f'Параметр конфигурации "{section_name}" не является разделом')
222 except ConfigSectionNotFound as e:
223 if mandatory:
224 raise e
226 else:
227 return [
228 ConfigFileContent(file_data={}, section_name=f'{section_name}')
232 class ConfigFile(object):
233 """\
234 Работа с самим файлом конфигурации
235 """
236 def __init__(self, file_name: str):
237 if not exists(file_name):
238 raise ConfigFileError(f'Файл конфигурации "{file_name}" не существует')
240 self.file_name = file_name
242 def get(self):
243 """\
244 Производим разбор файла конфигурации и создаём корневой раздел конфигурации
245 """
246 try:
247 return ConfigFileContent(toml.load(self.file_name))
249 except OSError as e:
250 raise ConfigFileError(f'Ошибка чтения файла "{self.file_name}": {e}')
252 except toml.TomlDecodeError as e:
253 raise ConfigFileError(f'Ошибка в разборе файла конфигурации: {e}')
255 except ValueError as e:
256 raise ConfigFileError(f'Ошибка разбора файла конфигурации: {ConfigFileError.fmt_error(e)}')
258 def __exit__(self, exc_type, exc_val, exc_tb):
259 pass
261 def __enter__(self):
262 return self.get()