From 4a81ed04a094184f2eb7e766e915bb6aa147b0e6 Mon Sep 17 00:00:00 2001 From: erjemin Date: Thu, 5 Jun 2025 23:53:17 +0300 Subject: [PATCH] add: read args & parse config --- config/sample.yaml | 31 ++++++++++++++++++++ src/pganec/_init_.py | 11 +++++++ src/pganec/config.py | 63 ++++++++++++++++++++++++++++++++++++++++ src/pganec/main.py | 68 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 173 insertions(+) create mode 100644 config/sample.yaml create mode 100644 src/pganec/_init_.py create mode 100644 src/pganec/config.py create mode 100644 src/pganec/main.py diff --git a/config/sample.yaml b/config/sample.yaml new file mode 100644 index 0000000..2af1503 --- /dev/null +++ b/config/sample.yaml @@ -0,0 +1,31 @@ +# Пример конфигурационного файла для PGanec + +servers: + - name: "PostgreSQL в k3s" + host: "postrges.local" + port: 5432 + user: "postgres" + password: "****" # Здесь должен быть ваш пароль + + - name: "PostgreSQL в Synology DS1522+ (Docker)" + host: "ds1522.local" + port: 5432 + user: "postgres" + password: "****" # Здесь должен быть ваш пароль + + - name: "Резервный сервер PostgreSQL" + host: "192.168.1.200" + port: 5432 + user: "postgres" + password: "****" # Здесь должен быть ваш пароль + +targets: + - mountpoint: "/mnt/backups" + label: "nas01" + type: "nfs" + + - mountpoint: "/mnt/usb" + label: "usb-disk" + type: "ext4" + + diff --git a/src/pganec/_init_.py b/src/pganec/_init_.py new file mode 100644 index 0000000..7664db4 --- /dev/null +++ b/src/pganec/_init_.py @@ -0,0 +1,11 @@ +""" +PGanec - TUI для резервного копирования и восстановления баз данных PostgreSQL. + +Основные возможности: +- Конфигурация через YAML-файл для описания доступных серверов и мест хранения резервных копий. +- Резервное копирование баз данных PostgreSQL. +- Восстановление баз данных PostgreSQL. +""" +__version__ = "0.1.0" +__author__ = "Sergey Erjemin" +__license__ = "MIT" diff --git a/src/pganec/config.py b/src/pganec/config.py new file mode 100644 index 0000000..36a755f --- /dev/null +++ b/src/pganec/config.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +import logging +import yaml +from typing import Optional, Dict # Для Python < 3.9 используйте Dict, для Python 3.9+ можно просто dict + +logger = logging.getLogger(__name__) + +class ConfigError(Exception): + """Базовый класс для ошибок конфигурации.""" + pass + +class ConfigNotFoundError(ConfigError): + """Файл конфигурации не найден.""" + pass + +class InvalidConfigFormatError(ConfigError): + """Ошибка формата YAML в конфигурации.""" + pass + + +def load_config(path: str) -> Optional[Dict]: + """ + Загрузка конфигурации из YAML файла. + Обрабатывает ошибки чтения файла и парсинга YAML. + + :param path: Путь к файлу конфигурации. + :return: Словарь с конфигурацией или None в случае ошибки. + """ + logger.debug(f"Попытка загрузки конфигурации из файла: `{path}`") + try: + with open(path, "r", encoding="utf-8") as f: + config_data = yaml.safe_load(f) + + # Проверим, что YAML действительно распарсился в словарь + if not isinstance(config_data, dict): + msg = f"Содержимое файла конфигурации `{path}` не является словарем. Тип: {type(config_data).__name__}." + logger.error(msg) + raise InvalidConfigFormatError(msg) + + logger.info(msg=f"Конфигурация успешно загружена из `{path}`") + return config_data + + except FileNotFoundError: + msg=f"Файл конфигурации не найден: `{path}`" + logger.error(msg) + raise ConfigNotFoundError(msg) from None # from None подавляет цепочку исключений + except yaml.YAMLError as e: + # YAMLError - базовый класс для ошибок PyYAML (ошибки сканера, парсера и т.п.) + msg = f"Ошибка парсинга YAML в файле `{path}`: {e}" + logger.error(msg) + raise InvalidConfigFormatError(msg) from e + except IOError as e: + # IOError или OSError для других проблем с доступом к файлу (например, права доступа) + msg = f"Ошибка ввода-вывода при чтении файла `{path}`: {e}" + logger.error(msg) + raise ConfigError(msg) from e # Общее исключение для других ошибок ввода-вывода + except Exception as e: + # Отлавливаем любые другие непредвиденные исключения + logger.critical( + msg=f"Непредвиденная ошибка при загрузке конфигурации из `{path}`: {e}", + exc_info=True # Добавляет трассировку стека в лог для лучшей диагностики + ) + raise ConfigError(f"Непредвиденная ошибка: {e}") from e diff --git a/src/pganec/main.py b/src/pganec/main.py new file mode 100644 index 0000000..7401f17 --- /dev/null +++ b/src/pganec/main.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +import argparse +import logging +import sys +from config import load_config, ConfigError, ConfigNotFoundError, InvalidConfigFormatError + +# --- Настройки и инициирование логирования --- +logger = logging.getLogger(__name__) + +def print_hi(name): + # Use a breakpoint in the code line below to debug your script. + print(f'Hi, {name}') # Press ⌘F8 to toggle the breakpoint. + + +# Press the green button in the gutter to run the script. +if __name__ == '__main__': + parser = argparse.ArgumentParser(description="PGanec — TUI резервного копирования и восстановления баз PostgreSQL") + parser.add_argument("-c", "--config", required=True, + help="Путь к файлу конфигурации (YAML)") + parser.add_argument("-d", "--debug-level", default="QUIET", + help="Уровень отладки (QUIET, NOTSET, DEBUG, INFO, WARNING, ERROR, CRITICAL)") + parser.add_argument("-l", "--loging", default="", + help="Путь к файлу логирования (если не указан, логирование не будет вестись)") + + args = parser.parse_args() + + # Настройка отладочных логов + level_input_str = args.debug_level.upper() + chosen_log_level = None + if level_input_str == "QUIET": # Опция "QUIET" + chosen_log_level = logging.CRITICAL + 1 + else: + chosen_log_level = getattr(logging, level_input_str, None) + if chosen_log_level is None: + logger.warning(f"Неизвестный уровень отладки '{args.debug_level}'. Используется DEBUG.") + chosen_log_level = logging.DEBUG + + logging.basicConfig( + level=chosen_log_level, + format="%(asctime)s - %(name)s - %(levelname)s - %(module)s.%(funcName)s:%(lineno)d - %(message)s" + ) + logger.debug(f"Параметрами: {args}") + logger.debug(f"Уровень: {chosen_log_level}") + + # Загрузка конфигурации + try: + config = load_config(args.config) + logger.info(f"Конфигурация успешно загружена: {config}") + except ConfigNotFoundError: + print(f"Ошибка: Файл конфигурации '{args.config}' не найден.", file=sys.stderr) + # Возможно сюда стоит добавить TUI интерфейс для ввода (выбора) файла конфигурации... + # Но так как ошибка может быть связана с правами доступа (например, TUI запущен от неправильного + # пользователя), оставим идею "на-подумать", а пока просто завершим выполнение программы. + sys.exit(1) + except InvalidConfigFormatError as e: + print(f"Ошибка: Файл конфигурации '{args.config}' имеет неверный формат. {e}", file=sys.stderr) + sys.exit(1) + except ConfigError as e: # Общая ошибка конфигурации (должна быть последней в цепочке) + print(f"Ошибка при загрузке конфигурации: {e}", file=sys.stderr) + + sys.exit(1) + + print_hi('PyCharm') + + # with open("config.yaml", "r") as f: + # config = yaml.safe_load(f) + +