py.lib.aw_config
2024-10-30
Parent:75523df5b946
py.lib.aw_config/src/aw_config/file.py
.. 0.202410.1
| 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@12 | 64 if default is not _UNDEFINED: |
| awgur@12 | 65 return default |
| awgur@12 | 66 |
| awgur@12 | 67 elif mandatory: |
| awgur@0 | 68 raise ConfigFileError(self._format_err_msg(f'Параметр "{name}" в файле отсутствует')) |
| awgur@0 | 69 |
| awgur@0 | 70 else: |
| awgur@12 | 71 return None |
| awgur@0 | 72 |
| awgur@0 | 73 else: |
| awgur@0 | 74 _buf = self.d[name] |
| awgur@0 | 75 try: |
| awgur@0 | 76 return val_type(_buf) |
| awgur@0 | 77 |
| awgur@0 | 78 except (ValueError, TypeError) as e: |
| awgur@0 | 79 raise ConfigFileError(self._format_err_msg(f'Не удалось привести значение "{_buf}" к типу ' |
| awgur@0 | 80 f'{repr(val_type)}: {e}')) |
| awgur@0 | 81 |
| awgur@0 | 82 def get_section(self, name: str, mandatory: bool = True): |
| awgur@0 | 83 """\ |
| awgur@0 | 84 Получить раздел конфигурационного файла из текущего или корневого раздела |
| awgur@0 | 85 :param name: имя раздела |
| awgur@0 | 86 :param mandatory: является ли раздел обязательным или он может отсутствовать в конфигурации. Во втором |
| awgur@0 | 87 случае, будет создан пустой словарь и использован в качестве данных раздела. |
| awgur@0 | 88 :returns Экземпляр того-же класса, только для новой секции |
| awgur@0 | 89 """ |
| awgur@0 | 90 if name not in self.d: |
| awgur@0 | 91 if mandatory: |
| awgur@0 | 92 raise ConfigSectionNotFound(self._format_err_msg(f'Раздел "{name}" не найден')) |
| awgur@0 | 93 |
| awgur@0 | 94 else: |
| awgur@0 | 95 return ConfigFileContent(file_data={}, section_name=f'{self.section}.{name}') |
| awgur@0 | 96 |
| awgur@0 | 97 else: |
| awgur@0 | 98 _val = self.d[name] |
| awgur@0 | 99 |
| awgur@0 | 100 if not isinstance(_val, dict): |
| awgur@0 | 101 raise ConfigFileError(self._format_err_msg(f'Параметр конфигурации "{name}" не является разделом')) |
| awgur@0 | 102 |
| awgur@0 | 103 else: |
| awgur@0 | 104 return ConfigFileContent(file_data=_val, section_name=f'{self.section}.{name}') |
| awgur@0 | 105 |
| awgur@0 | 106 |
| awgur@0 | 107 class ConfigFile(object): |
| awgur@0 | 108 """\ |
| awgur@0 | 109 Работа с самим файлом конфигурации |
| awgur@0 | 110 """ |
| awgur@0 | 111 def __init__(self, file_name: str): |
| awgur@0 | 112 if not exists(file_name): |
| awgur@0 | 113 raise ConfigFileError(f'Файл конфигурации "{file_name}" не существует') |
| awgur@0 | 114 |
| awgur@0 | 115 self.file_name = file_name |
| awgur@0 | 116 |
| awgur@0 | 117 def get(self): |
| awgur@0 | 118 """\ |
| awgur@0 | 119 Производим разбор файла конфигурации и создаём корневой раздел конфигурации |
| awgur@0 | 120 """ |
| awgur@3 | 121 try: |
| awgur@3 | 122 return ConfigFileContent(toml.load(self.file_name)) |
| awgur@3 | 123 |
| awgur@3 | 124 except OSError as e: |
| awgur@3 | 125 raise ConfigFileError(f'Ошибка чтения файла "{self.file_name}": {e}') |
| awgur@3 | 126 |
| awgur@3 | 127 except toml.TomlDecodeError as e: |
| awgur@3 | 128 raise ConfigFileError(f'Ошибка в разборе файла конфигурации: {e}') |
| awgur@3 | 129 |
| awgur@3 | 130 except ValueError as e: |
| awgur@3 | 131 raise ConfigFileError(f'Ошибка разбора файла конфигурации: {ConfigFileError.fmt_error(e)}') |
| awgur@0 | 132 |
| awgur@0 | 133 def __exit__(self, exc_type, exc_val, exc_tb): |
| awgur@0 | 134 pass |
| awgur@0 | 135 |
| awgur@0 | 136 def __enter__(self): |
| awgur@0 | 137 return self.get() |