py.lib.aw_config

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

19:0fe90487ecdf Go to Latest

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

.. 0.202606.4 . Исправление неточности с списке экспортов модуля

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(
100 [(name if name.find('.') == -1 else f'"{name}"') for name in names]
103 def _get_part_by_path(self, *names: str):
104 """\
105 Получить конфигурационный элемент по заданному пути
106 """
107 if not names:
108 raise ValueError(f'Не заданно имя запрашиваемого раздела')
110 upper_path = names[:-1]
111 last_path_el = names[-1]
112 cur_level = self.d
113 cur_section = self._get_section_name()
115 for el in upper_path:
116 cur_section = el if not cur_section else f'{cur_section}.{el}'
118 if el in cur_level:
119 cur_level = cur_level[el]
121 else:
122 raise ConfigSectionNotFound(f'Раздел "{cur_section}" не найден')
124 cur_section = last_path_el if not cur_section else f'{cur_section}.{last_path_el}'
125 if last_path_el in cur_level:
126 return cur_level[last_path_el]
128 else:
129 raise ConfigSectionNotFound(f'Раздел "{cur_section}" не найден')
132 def get_section(self, name: str, mandatory: bool = True):
133 """\
134 Получить раздел конфигурационного файла из текущего или корневого раздела
136 :param name: имя раздела
137 :param mandatory: является ли раздел обязательным или он может отсутствовать в конфигурации. Во втором
138 случае, будет создан пустой словарь и использован в качестве данных раздела.
139 :returns: Экземпляр того-же класса, только для новой секции
140 """
141 section_name = self._get_section_name(name)
142 try:
143 section_val = self._get_part_by_path(name)
145 if not isinstance(section_val, dict):
146 raise ConfigFileError(f'Параметр конфигурации "{section_name}" не является разделом')
148 else:
149 return ConfigFileContent(file_data=section_val, section_name=f'{section_name}')
151 except ConfigSectionNotFound as e:
152 if mandatory:
153 raise e
155 else:
156 return ConfigFileContent(file_data={}, section_name=f'{section_name}')
158 def get_section_by_path(self, *names: str, mandatory: bool = True):
159 """\
160 Поучить секцию конфигурации по явно заданному пути
162 :type names: str
163 :param names: Имя секции на текущем уровне. Можно задавать несколько
164 для прохода по всей иерархии необходимой иерархии одним вызовом.
166 :type mandatory: bool
167 :param mandatory: Является ли искомая секция обязательной?
169 :returns: Экземпляр того-же класса, только для новой секции
170 """
171 section_name = self._get_section_name(*names)
172 try:
173 section_val = self._get_part_by_path(*names)
175 if not isinstance(section_val, dict):
176 raise ConfigFileError(f'Параметр конфигурации "{section_name}" не является разделом')
178 else:
179 return ConfigFileContent(file_data=section_val, section_name=f'{section_name}')
181 except ConfigSectionNotFound as e:
182 if mandatory:
183 raise e
185 else:
186 return ConfigFileContent(file_data={}, section_name=f'{section_name}')
188 def get_list_section(self, name: str, mandatory: bool = True):
189 """\
190 Для случая, когда раздел конфигурации задан массивом toml ([[${! SOME_SECTION !}]])
192 :type name: str
193 :param name: Имя раздела
195 :type mandatory: bool
196 :param mandatory: Данный раздел обязателен и не может отсутствовать
198 :returns: Спискол из экземпляров данного класса, нацеленные на элементы массива хэш-таблиц файла toml
199 """
200 section_name = self._get_section_name(name)
201 try:
202 section_val = self._get_part_by_path(name)
204 if isinstance(section_val, (list, tuple)):
205 res = []
206 for el in section_val:
207 if isinstance(el, dict):
208 res.append(ConfigFileContent(file_data=el, section_name=f'{section_name}'))
210 else:
211 raise ConfigFileError(f'Параметр конфигурации "{section_name}"'
212 f' со значением "{el}"'
213 f' не является разделом')
214 return res
216 elif isinstance(section_val, dict):
217 return [
218 ConfigFileContent(file_data=section_val, section_name=f'{section_name}')
221 else:
222 raise ConfigFileError(f'Параметр конфигурации "{section_name}" не является разделом')
224 except ConfigSectionNotFound as e:
225 if mandatory:
226 raise e
228 else:
229 return [
230 ConfigFileContent(file_data={}, section_name=f'{section_name}')
234 class ConfigFile(object):
235 """\
236 Работа с самим файлом конфигурации
237 """
238 def __init__(self, file_name: str):
239 if not exists(file_name):
240 raise ConfigFileError(f'Файл конфигурации "{file_name}" не существует')
242 self.file_name = file_name
244 def get(self):
245 """\
246 Производим разбор файла конфигурации и создаём корневой раздел конфигурации
247 """
248 try:
249 return ConfigFileContent(toml.load(self.file_name))
251 except OSError as e:
252 raise ConfigFileError(f'Ошибка чтения файла "{self.file_name}": {e}')
254 except toml.TomlDecodeError as e:
255 raise ConfigFileError(f'Ошибка в разборе файла конфигурации: {e}')
257 except ValueError as e:
258 raise ConfigFileError(f'Ошибка разбора файла конфигурации: {ConfigFileError.fmt_error(e)}')
260 def __exit__(self, exc_type, exc_val, exc_tb):
261 pass
263 def __enter__(self):
264 return self.get()