py.lib.aw_config
py.lib.aw_config/src/aw_config/file.py
Added tag 0.202405.12 for changeset 26be458b26bb
| awgur@0 | 1 # coding: utf-8 |
| awgur@0 | 2 """\ |
| awgur@0 | 3 Функции, помогающие в разборе конфигурационного файла |
| awgur@0 | 4 """ |
| awgur@0 | 5 import toml |
| awgur@0 | 6 from os.path import exists |
| awgur@0 | 7 from typing import Union, Any |
| awgur@0 | 8 |
| awgur@0 | 9 from .error import Error |
| awgur@0 | 10 from .type_helpers import BaseTypeHelper |
| awgur@7 | 11 from ._lib import _UNDEFINED |
| awgur@0 | 12 |
| awgur@0 | 13 |
| awgur@0 | 14 class ConfigFileError(Error): |
| awgur@0 | 15 """\ |
| awgur@0 | 16 Базовое исключение при обработке конфигурационного файла |
| awgur@0 | 17 """ |
| awgur@0 | 18 |
| awgur@0 | 19 |
| awgur@0 | 20 class ConfigSectionNotFound(ConfigFileError): |
| awgur@0 | 21 """\ |
| awgur@0 | 22 Отсутствие нужного раздела в конфигурации |
| awgur@0 | 23 """ |
| awgur@0 | 24 |
| awgur@0 | 25 |
| awgur@0 | 26 class ConfigFileContent(object): |
| awgur@0 | 27 """\ |
| awgur@0 | 28 Работа с содержимым файла конфигурации |
| awgur@0 | 29 """ |
| awgur@0 | 30 def __init__(self, file_data, section_name=None): |
| awgur@0 | 31 """\ |
| awgur@0 | 32 :param file_data: Разобранное в словарь содержимое файла конфигурации |
| awgur@0 | 33 :param section_name: Имя секции в конфигурации |
| awgur@0 | 34 """ |
| awgur@0 | 35 self.d = file_data |
| awgur@0 | 36 self.section = section_name |
| awgur@0 | 37 |
| awgur@0 | 38 def _format_err_msg(self, msg: str) -> str: |
| awgur@0 | 39 if self.section is not None: |
| awgur@0 | 40 return f'Раздел конфигурации "{self.section}": {msg}' |
| awgur@0 | 41 |
| awgur@0 | 42 else: |
| awgur@0 | 43 return msg |
| awgur@0 | 44 |
| awgur@0 | 45 def __enter__(self): |
| awgur@0 | 46 return self |
| awgur@0 | 47 |
| awgur@0 | 48 def __exit__(self, exc_type, exc_val, exc_tb): |
| awgur@0 | 49 pass |
| awgur@0 | 50 |
| awgur@0 | 51 def get_value(self, name: str, |
| awgur@2 | 52 val_type: Union[type, BaseTypeHelper] = str, |
| awgur@7 | 53 default: Any = _UNDEFINED, |
| awgur@0 | 54 mandatory: bool = True): |
| awgur@0 | 55 """\ |
| awgur@0 | 56 Получить из текущего раздела конфигурации нужный элемент |
| awgur@0 | 57 :param name: имя параметра |
| awgur@0 | 58 :param val_type: тип параметра |
| awgur@0 | 59 :param default: значение параметра по умолчанию |
| awgur@0 | 60 :param mandatory: является ли параметр обязательным. Если ``False``, то в случае отсутствия |
| awgur@0 | 61 параметра будет использовано значение None |
| awgur@0 | 62 """ |
| awgur@0 | 63 if name not in self.d: |
| awgur@7 | 64 if mandatory and default is _UNDEFINED: |
| awgur@0 | 65 raise ConfigFileError(self._format_err_msg(f'Параметр "{name}" в файле отсутствует')) |
| awgur@0 | 66 |
| awgur@0 | 67 else: |
| awgur@0 | 68 return default |
| awgur@0 | 69 |
| awgur@0 | 70 else: |
| awgur@0 | 71 _buf = self.d[name] |
| awgur@0 | 72 try: |
| awgur@0 | 73 return val_type(_buf) |
| awgur@0 | 74 |
| awgur@0 | 75 except (ValueError, TypeError) as e: |
| awgur@0 | 76 raise ConfigFileError(self._format_err_msg(f'Не удалось привести значение "{_buf}" к типу ' |
| awgur@0 | 77 f'{repr(val_type)}: {e}')) |
| awgur@0 | 78 |
| awgur@0 | 79 def get_section(self, name: str, mandatory: bool = True): |
| awgur@0 | 80 """\ |
| awgur@0 | 81 Получить раздел конфигурационного файла из текущего или корневого раздела |
| awgur@0 | 82 :param name: имя раздела |
| awgur@0 | 83 :param mandatory: является ли раздел обязательным или он может отсутствовать в конфигурации. Во втором |
| awgur@0 | 84 случае, будет создан пустой словарь и использован в качестве данных раздела. |
| awgur@0 | 85 :returns Экземпляр того-же класса, только для новой секции |
| awgur@0 | 86 """ |
| awgur@0 | 87 if name not in self.d: |
| awgur@0 | 88 if mandatory: |
| awgur@0 | 89 raise ConfigSectionNotFound(self._format_err_msg(f'Раздел "{name}" не найден')) |
| awgur@0 | 90 |
| awgur@0 | 91 else: |
| awgur@0 | 92 return ConfigFileContent(file_data={}, section_name=f'{self.section}.{name}') |
| awgur@0 | 93 |
| awgur@0 | 94 else: |
| awgur@0 | 95 _val = self.d[name] |
| awgur@0 | 96 |
| awgur@0 | 97 if not isinstance(_val, dict): |
| awgur@0 | 98 raise ConfigFileError(self._format_err_msg(f'Параметр конфигурации "{name}" не является разделом')) |
| awgur@0 | 99 |
| awgur@0 | 100 else: |
| awgur@0 | 101 return ConfigFileContent(file_data=_val, section_name=f'{self.section}.{name}') |
| awgur@0 | 102 |
| awgur@0 | 103 |
| awgur@0 | 104 class ConfigFile(object): |
| awgur@0 | 105 """\ |
| awgur@0 | 106 Работа с самим файлом конфигурации |
| awgur@0 | 107 """ |
| awgur@0 | 108 def __init__(self, file_name: str): |
| awgur@0 | 109 if not exists(file_name): |
| awgur@0 | 110 raise ConfigFileError(f'Файл конфигурации "{file_name}" не существует') |
| awgur@0 | 111 |
| awgur@0 | 112 self.file_name = file_name |
| awgur@0 | 113 |
| awgur@0 | 114 def get(self): |
| awgur@0 | 115 """\ |
| awgur@0 | 116 Производим разбор файла конфигурации и создаём корневой раздел конфигурации |
| awgur@0 | 117 """ |
| awgur@3 | 118 try: |
| awgur@3 | 119 return ConfigFileContent(toml.load(self.file_name)) |
| awgur@3 | 120 |
| awgur@3 | 121 except OSError as e: |
| awgur@3 | 122 raise ConfigFileError(f'Ошибка чтения файла "{self.file_name}": {e}') |
| awgur@3 | 123 |
| awgur@3 | 124 except toml.TomlDecodeError as e: |
| awgur@3 | 125 raise ConfigFileError(f'Ошибка в разборе файла конфигурации: {e}') |
| awgur@3 | 126 |
| awgur@3 | 127 except ValueError as e: |
| awgur@3 | 128 raise ConfigFileError(f'Ошибка разбора файла конфигурации: {ConfigFileError.fmt_error(e)}') |
| awgur@0 | 129 |
| awgur@0 | 130 def __exit__(self, exc_type, exc_val, exc_tb): |
| awgur@0 | 131 pass |
| awgur@0 | 132 |
| awgur@0 | 133 def __enter__(self): |
| awgur@0 | 134 return self.get() |