py.lib.aw_config

Yohn Y. 2024-05-04 Child:81fc92335324

0:bece3a8a67a5 Browse Files

.. init

.hgignore pyproject.toml requirements.txt setup.py src/aw_config/__init__.py src/aw_config/env.py src/aw_config/error.py src/aw_config/file.py src/aw_config/type_helpers.py tools/make_pkg.sh

     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/.hgignore	Sat May 04 18:23:23 2024 +0300
     1.3 @@ -0,0 +1,7 @@
     1.4 +syntax: glob
     1.5 +.idea/*
     1.6 +.e/*
     1.7 +build/*
     1.8 +dist/*
     1.9 +*.egg-info/*
    1.10 +
     2.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2 +++ b/pyproject.toml	Sat May 04 18:23:23 2024 +0300
     2.3 @@ -0,0 +1,16 @@
     2.4 +[build-system]
     2.5 +# Minimum requirements for the build system to execute.
     2.6 +requires = ["setuptools", "wheel"]  # PEP 508 specifications.
     2.7 +
     2.8 +[project]
     2.9 +name = "aw_config"
    2.10 +version = "0.202405.1"
    2.11 +description = "Модуль поддержки процесса конфигурирования приложения"
    2.12 +#readme = "README.md"
    2.13 +requires-python = ">=3.9"
    2.14 +dependencies = [
    2.15 +    "toml>=0.10.2"
    2.16 +]
    2.17 +
    2.18 +[project.urls]
    2.19 +src = "https://devel.a0fs.ru/py.lib.aw_config"
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/requirements.txt	Sat May 04 18:23:23 2024 +0300
     3.3 @@ -0,0 +1,2 @@
     3.4 +toml>=0.10.2
     3.5 +setuptools>=59.6.0
     3.6 \ No newline at end of file
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/setup.py	Sat May 04 18:23:23 2024 +0300
     4.3 @@ -0,0 +1,15 @@
     4.4 +# coding: utf-8
     4.5 +from setuptools import setup
     4.6 +
     4.7 +setup(
     4.8 +    name='aw_config',
     4.9 +    version='0.202405.1',
    4.10 +    packages=['aw_config'],
    4.11 +    package_dir={'aw_config': 'src/aw_config'},
    4.12 +    url='https://devel.a0fs.ru/py.lib.aw_config',
    4.13 +    license='BSD',
    4.14 +    author='awgur',
    4.15 +    author_email='devel@a0fs.ru',
    4.16 +    description='Модуль поддержки процесса конфигурирования приложения'
    4.17 +)
    4.18 +
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/src/aw_config/__init__.py	Sat May 04 18:23:23 2024 +0300
     5.3 @@ -0,0 +1,11 @@
     5.4 +# coding: utf-8
     5.5 +from .env import get_env
     5.6 +from .type_helpers import ANY, DICT, LIST
     5.7 +from .file import ConfigFile, ConfigSectionNotFound, ConfigFileError
     5.8 +
     5.9 +
    5.10 +__all__ = [
    5.11 +    'get_env',
    5.12 +    'ANY', 'DICT', 'LIST',
    5.13 +    'ConfigFile', 'ConfigSectionNotFound', 'ConfigFileError',
    5.14 +]
     6.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.2 +++ b/src/aw_config/env.py	Sat May 04 18:23:23 2024 +0300
     6.3 @@ -0,0 +1,25 @@
     6.4 +# coding: utf-8
     6.5 +
     6.6 +from os import environ as sysenv
     6.7 +from typing import Optional, Any
     6.8 +from .error import Error
     6.9 +
    6.10 +
    6.11 +class EnvConfigError(Error):
    6.12 +    def __init__(self, param_name: str, message: Any):
    6.13 +        super().__init__(f'Ошибка в параметре: {param_name} - {message}')
    6.14 +
    6.15 +
    6.16 +def get_env(name: str, type_name: type, default: Optional[Any] = None, mandatory: bool = False):
    6.17 +    try:
    6.18 +        return type_name(sysenv[name])
    6.19 +
    6.20 +    except (ValueError, TypeError) as e:
    6.21 +        raise EnvConfigError(name, f'Ошибка преобразования типа - {e}')
    6.22 +
    6.23 +    except KeyError:
    6.24 +        if default is None and not mandatory:
    6.25 +            raise EnvConfigError(name, 'Не установлен параметр')
    6.26 +
    6.27 +        else:
    6.28 +            return default
     7.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.2 +++ b/src/aw_config/error.py	Sat May 04 18:23:23 2024 +0300
     7.3 @@ -0,0 +1,6 @@
     7.4 +# coding: utf-8
     7.5 +
     7.6 +class Error(Exception):
     7.7 +    @staticmethod
     7.8 +    def fmt_error(err: Exception):
     7.9 +        return '%s(%s)' % (type(err).__name__, err)
     8.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     8.2 +++ b/src/aw_config/file.py	Sat May 04 18:23:23 2024 +0300
     8.3 @@ -0,0 +1,123 @@
     8.4 +# coding: utf-8
     8.5 +"""\
     8.6 +Функции, помогающие в разборе конфигурационного файла
     8.7 +"""
     8.8 +import toml
     8.9 +from os.path import exists
    8.10 +from typing import Union, Any
    8.11 +
    8.12 +from .error import Error
    8.13 +from .type_helpers import BaseTypeHelper
    8.14 +
    8.15 +
    8.16 +class ConfigFileError(Error):
    8.17 +    """\
    8.18 +    Базовое исключение при обработке конфигурационного файла
    8.19 +    """
    8.20 +
    8.21 +
    8.22 +class ConfigSectionNotFound(ConfigFileError):
    8.23 +    """\
    8.24 +    Отсутствие нужного раздела в конфигурации
    8.25 +    """
    8.26 +
    8.27 +
    8.28 +class ConfigFileContent(object):
    8.29 +    """\
    8.30 +    Работа с содержимым файла конфигурации
    8.31 +    """
    8.32 +    def __init__(self, file_data, section_name=None):
    8.33 +        """\
    8.34 +        :param file_data: Разобранное в словарь содержимое файла конфигурации
    8.35 +        :param section_name: Имя секции в конфигурации
    8.36 +        """
    8.37 +        self.d = file_data
    8.38 +        self.section = section_name
    8.39 +
    8.40 +    def _format_err_msg(self, msg: str) -> str:
    8.41 +        if self.section is not None:
    8.42 +            return f'Раздел конфигурации "{self.section}": {msg}'
    8.43 +
    8.44 +        else:
    8.45 +            return msg
    8.46 +
    8.47 +    def __enter__(self):
    8.48 +        return self
    8.49 +
    8.50 +    def __exit__(self, exc_type, exc_val, exc_tb):
    8.51 +        pass
    8.52 +
    8.53 +    def get_value(self, name: str,
    8.54 +                  val_type: Union[type, BaseTypeHelper],
    8.55 +                  default: Any = None,
    8.56 +                  mandatory: bool = True):
    8.57 +        """\
    8.58 +        Получить из текущего раздела конфигурации нужный элемент
    8.59 +        :param name: имя параметра
    8.60 +        :param val_type: тип параметра
    8.61 +        :param default: значение параметра по умолчанию
    8.62 +        :param mandatory: является ли параметр обязательным. Если ``False``, то в случае отсутствия
    8.63 +                          параметра будет использовано значение None
    8.64 +        """
    8.65 +        if name not in self.d:
    8.66 +            if mandatory and default is None:
    8.67 +                raise ConfigFileError(self._format_err_msg(f'Параметр "{name}" в файле отсутствует'))
    8.68 +
    8.69 +            else:
    8.70 +                return default
    8.71 +
    8.72 +        else:
    8.73 +            _buf = self.d[name]
    8.74 +            try:
    8.75 +                return val_type(_buf)
    8.76 +
    8.77 +            except (ValueError, TypeError) as e:
    8.78 +                raise ConfigFileError(self._format_err_msg(f'Не удалось привести значение "{_buf}" к типу '
    8.79 +                                                           f'{repr(val_type)}: {e}'))
    8.80 +
    8.81 +    def get_section(self, name: str, mandatory: bool = True):
    8.82 +        """\
    8.83 +        Получить раздел конфигурационного файла из текущего или корневого раздела
    8.84 +        :param name: имя раздела
    8.85 +        :param mandatory: является ли раздел обязательным или он может отсутствовать в конфигурации. Во втором
    8.86 +                          случае, будет создан пустой словарь и использован в качестве данных раздела.
    8.87 +        :returns Экземпляр того-же класса, только для новой секции
    8.88 +        """
    8.89 +        if name not in self.d:
    8.90 +            if mandatory:
    8.91 +                raise ConfigSectionNotFound(self._format_err_msg(f'Раздел "{name}" не найден'))
    8.92 +
    8.93 +            else:
    8.94 +                return ConfigFileContent(file_data={}, section_name=f'{self.section}.{name}')
    8.95 +
    8.96 +        else:
    8.97 +            _val = self.d[name]
    8.98 +
    8.99 +            if not isinstance(_val, dict):
   8.100 +                raise ConfigFileError(self._format_err_msg(f'Параметр конфигурации "{name}" не является разделом'))
   8.101 +
   8.102 +            else:
   8.103 +                return ConfigFileContent(file_data=_val, section_name=f'{self.section}.{name}')
   8.104 +
   8.105 +
   8.106 +class ConfigFile(object):
   8.107 +    """\
   8.108 +    Работа с самим файлом конфигурации
   8.109 +    """
   8.110 +    def __init__(self, file_name: str):
   8.111 +        if not exists(file_name):
   8.112 +            raise ConfigFileError(f'Файл конфигурации "{file_name}" не существует')
   8.113 +
   8.114 +        self.file_name = file_name
   8.115 +
   8.116 +    def get(self):
   8.117 +        """\
   8.118 +        Производим разбор файла конфигурации и создаём корневой раздел конфигурации
   8.119 +        """
   8.120 +        return ConfigFileContent(toml.load(self.file_name))
   8.121 +
   8.122 +    def __exit__(self, exc_type, exc_val, exc_tb):
   8.123 +        pass
   8.124 +
   8.125 +    def __enter__(self):
   8.126 +        return self.get()
     9.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     9.2 +++ b/src/aw_config/type_helpers.py	Sat May 04 18:23:23 2024 +0300
     9.3 @@ -0,0 +1,88 @@
     9.4 +# coding: utf-8
     9.5 +"""\
     9.6 +Описание различных обёрток над типами, для выделения из конфигураций параметров со сложными типами
     9.7 +"""
     9.8 +
     9.9 +from typing import TypeVar, Type, Any, List, Iterable, Dict, Union, Callable
    9.10 +
    9.11 +
    9.12 +T = TypeVar('T')
    9.13 +K = TypeVar('K')
    9.14 +
    9.15 +
    9.16 +class BaseTypeHelper(object):
    9.17 +    """\
    9.18 +    Базовый класс для типов-обёрток
    9.19 +    """
    9.20 +    __name__ = 'aw_config.type_helpers.BaseTypeHelper'
    9.21 +
    9.22 +    def __call__(self, val: Any):
    9.23 +        raise NotImplemented()
    9.24 +
    9.25 +
    9.26 +class ANY(BaseTypeHelper):
    9.27 +    """\
    9.28 +    Класс не проводящий никакой обработки содержимого
    9.29 +    """
    9.30 +    def __call__(self, val: Any) -> Any:
    9.31 +        return val
    9.32 +
    9.33 +
    9.34 +class LIST(BaseTypeHelper):
    9.35 +    """\
    9.36 +    Преобразуем параметр в список нужного типа
    9.37 +    """
    9.38 +    def __init__(self, el_type: Union[Type[T], Callable[[Any], T]]):
    9.39 +        self.el_type = el_type
    9.40 +
    9.41 +    def __repr__(self) -> str:
    9.42 +        return f"<class '{type(self).__name__}[{repr(self.el_type)}]'>"
    9.43 +
    9.44 +    def __call__(self, val: Iterable) -> List[T]:
    9.45 +        res = []
    9.46 +        for i in val:
    9.47 +            try:
    9.48 +                res.append(self.el_type(i))
    9.49 +
    9.50 +            except (ValueError, TypeError) as e:
    9.51 +                raise ValueError(f'Не удалось привести значение "{i}" к типу "{repr(self.el_type)}" '
    9.52 +                                 f'в составе списка. Ошибка: {e}')
    9.53 +
    9.54 +        return res
    9.55 +
    9.56 +
    9.57 +class DICT(BaseTypeHelper):
    9.58 +    """\
    9.59 +    Преобразуем параметр в словарь, с нужными типами ключа и значения
    9.60 +    """
    9.61 +    __name__ = 'aw_config.type_helpers.DICT'
    9.62 +
    9.63 +    def __init__(self, key_type: Union[Type[K], Callable[[Any], K]], el_type: Union[Type[T], Callable[[Any], T]]):
    9.64 +        self.el_type = el_type
    9.65 +        self.k_type = key_type
    9.66 +
    9.67 +    def __repr__(self) -> str:
    9.68 +        return f"<class '{type(self).__name__}[{repr(self.k_type)}, {repr(self.el_type)}]'>"
    9.69 +
    9.70 +    def __call__(self, val: Dict) -> Dict[K, T]:
    9.71 +        res = dict()
    9.72 +        for k, v in val.items():
    9.73 +            try:
    9.74 +                _k = self.k_type(k)
    9.75 +
    9.76 +            except (ValueError, TypeError) as e:
    9.77 +                raise ValueError(f'Ключ "{k}" не может быть преобразован в тип {repr(self.k_type)} '
    9.78 +                                 f'для комбинации ключ-значение: "{k}", "{v}" '
    9.79 +                                 f'Ошибка: "{e}"')
    9.80 +
    9.81 +            try:
    9.82 +                _v = self.el_type(v)
    9.83 +
    9.84 +            except (ValueError, TypeError) as e:
    9.85 +                raise ValueError(f'Значение "{v}" не может быть преобразован в тип {repr(self.el_type)} '
    9.86 +                                 f'для комбинации ключ-значение: "{k}", "{v}" '
    9.87 +                                 f'Ошибка: "{e}"')
    9.88 +
    9.89 +            res[_k] = _v
    9.90 +
    9.91 +        return res
    10.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    10.2 +++ b/tools/make_pkg.sh	Sat May 04 18:23:23 2024 +0300
    10.3 @@ -0,0 +1,11 @@
    10.4 +#!/bin/sh
    10.5 +# devel.a0fs.ru -- devel.python.tools:make_pkg.sh -- v0.r202405.1
    10.6 +this_dir="$(dirname "$(readlink -f "$0")")"
    10.7 +this_pkg="$(dirname "$this_dir")"
    10.8 +
    10.9 +cd "${this_pkg}" || exit
   10.10 +if [ -d "${this_pkg}/.e" ] ; then
   10.11 +  . "${this_pkg}/.e/bin/activate"
   10.12 +fi
   10.13 +
   10.14 +python3 setup.py bdist_wheel