Initial commit: add config_manager module with ConfigParameter and ConfigManager
This commit is contained in:
9
.env.example
Normal file
9
.env.example
Normal file
@@ -0,0 +1,9 @@
|
||||
# komAI environment variables
|
||||
|
||||
# Path to config file (optional)
|
||||
# KOMAI_CONFIG=config/global.yaml
|
||||
|
||||
# Logging (optional)
|
||||
# LOGGING_LOG_PATH=./log
|
||||
# LOGGING_LOG_FILE=app.log
|
||||
# LOGGING_LEVEL=INFO
|
||||
49
.gitignore
vendored
Normal file
49
.gitignore
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
*.egg
|
||||
|
||||
# Virtual environments
|
||||
venv/
|
||||
env/
|
||||
ENV/
|
||||
.venv/
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Log files (runtime)
|
||||
log/*.log
|
||||
|
||||
# Models (large binary files)
|
||||
models/
|
||||
|
||||
# Environment
|
||||
.env
|
||||
.env.local
|
||||
|
||||
.opencode/
|
||||
32
AGENTS.md
Normal file
32
AGENTS.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# komAI Agent Guidelines
|
||||
|
||||
## Entry Point
|
||||
```
|
||||
python -m app.komAI
|
||||
```
|
||||
|
||||
## Architecture
|
||||
- `app/` - application entry point (komAI.py expected)
|
||||
- `src/` - source code, `src/__init__.py` exposes centralized API
|
||||
- `config/` - YAML configuration; `config/global.yaml` is the main config
|
||||
- `modules/` - pluggable modules, configured via `global.modules`
|
||||
- `log/` - runtime logs
|
||||
- `tests/` - standalone unit tests (executable from CLI)
|
||||
|
||||
## Config System
|
||||
- Modules register parameters at initialization
|
||||
- Save config: `from src import save_config; save_config()`
|
||||
|
||||
## Logging
|
||||
- All console output is duplicated to log files
|
||||
- Log config in `config/global.yaml` (level, file, path)
|
||||
|
||||
## Env Vars (see `.env.example`)
|
||||
- `KOMAI_CONFIG` - path to config file (optional)
|
||||
- `LOGGING_LOG_PATH`, `LOGGING_LOG_FILE`, `LOGGING_LEVEL`
|
||||
|
||||
## Requirements
|
||||
- Python >3.10
|
||||
|
||||
## No Code Yet
|
||||
This repo is a scaffold. No `.py` source files exist yet. Do not assume any modules, classes, or APIs are implemented.
|
||||
21
CHECKLIST.md
Normal file
21
CHECKLIST.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# komAI Development Checklist
|
||||
|
||||
## Completed
|
||||
|
||||
- [x] Создан модуль `src/utils/config_manager` для работы с конфигурацией
|
||||
- Реализован `ConfigParameter` с поддержкой валидации
|
||||
- Реализован `ConfigManager` с регистрацией, загрузкой, сохранением параметров
|
||||
- Глобальный экземпляр `config` доступен при импорте модуля
|
||||
- Поддержка категорий, описаний, переменных окружения
|
||||
- 8/8 тестов проходят
|
||||
- Документация: `doc/src.utils.config_manager.md`, `src/utils/config_manager/AGENTS.md`
|
||||
|
||||
## In Progress
|
||||
|
||||
## Pending
|
||||
|
||||
- [ ] Создать приложение `app/komAI.py` как точку входа
|
||||
- [ ] Реализовать модуль логирования
|
||||
- [ ] Реализовать систему модулей (`modules/`)
|
||||
- [ ] Настроить CI/CD
|
||||
- [ ] Написать интеграционные тесты
|
||||
28
README.md
Normal file
28
README.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# komAI - локальный AI-ассистент
|
||||
|
||||
## Требования
|
||||
|
||||
- Python >3.10
|
||||
- Все консольные выводы дублируются в логах
|
||||
|
||||
## Структура
|
||||
|
||||
- `app/` - точка входа (komAI.py)
|
||||
- `src/` - исходный код
|
||||
- `src/__init__.py` - централизованный API
|
||||
- `config/` - YAML конфигурация
|
||||
- `tests/` - юнит-тесты
|
||||
- `modules/` - подключаемые модули
|
||||
- `log/` - файлы логов
|
||||
|
||||
## Быстрый старт
|
||||
|
||||
```bash
|
||||
python -m app.komAI
|
||||
```
|
||||
|
||||
## Конфигурация
|
||||
|
||||
- Файл: `config/global.yaml`
|
||||
- Параметры регистрируются модулями при инициализации
|
||||
- Сохранение: `from src import save_config; save_config()`
|
||||
2
app/README.md
Normal file
2
app/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# Приложения проекта
|
||||
|
||||
3
config/README.md
Normal file
3
config/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Каталог хранения конфигурационных файлов проекта.
|
||||
|
||||
Глобальный конфигурационный файл: `global.yaml`
|
||||
11
config/global.yaml
Normal file
11
config/global.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
#Уровень логирования
|
||||
#logging.level: "INFO"
|
||||
logging.level: "INFO"
|
||||
|
||||
#Имя файла логов
|
||||
#logging.log_file: "app.log"
|
||||
logging.log_file: "app.log"
|
||||
|
||||
#Путь к директории логов
|
||||
#logging.log_path: "./log"
|
||||
logging.log_path: "./log"
|
||||
172
doc/src.utils.config_manager.md
Normal file
172
doc/src.utils.config_manager.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# ConfigManager
|
||||
|
||||
Управление конфигурацией с регистрацией параметров и описаний.
|
||||
|
||||
## Установка
|
||||
|
||||
```python
|
||||
import src.utils.config_manager
|
||||
config = src.utils.config_manager.config
|
||||
```
|
||||
|
||||
## Быстрый старт
|
||||
|
||||
```python
|
||||
import src.utils.config_manager
|
||||
config = src.utils.config_manager.config
|
||||
|
||||
# Регистрация параметра
|
||||
config.register(name="app_name", val="komAI", desc="Наименование проекта", cat="app")
|
||||
|
||||
# Получение значения
|
||||
name = config.get("app_name", cat="app")
|
||||
|
||||
# Изменение значения
|
||||
config.set("app_name", "NewName", cat="app")
|
||||
|
||||
# Сохранение в файл
|
||||
config.save()
|
||||
|
||||
# Сброс и перезагрузка
|
||||
config.reset()
|
||||
```
|
||||
|
||||
## Константы
|
||||
|
||||
```python
|
||||
from src.utils.config_manager import DEFAULT_CONFIG_FILE, DEFAULT_CONFIG_ENV, DEFAULT_CATEGORY
|
||||
```
|
||||
|
||||
| Константа | Значение | Описание |
|
||||
|----------|----------|----------|
|
||||
| `DEFAULT_CONFIG_FILE` | `"config/global.yaml"` | Путь к файлу конфигурации |
|
||||
| `DEFAULT_CONFIG_ENV` | `"KOMAI_CONFIG_FILE"` | Переменная окружения для переопределения пути |
|
||||
| `DEFAULT_CATEGORY` | `"global"` | Категория по умолчанию |
|
||||
|
||||
## Регистрация параметров
|
||||
|
||||
```python
|
||||
config.register(
|
||||
name="param_name", # Имя параметра (обязательно)
|
||||
val="value", # Текущее значение (обязательно)
|
||||
default="def_value", # Значение по умолчанию
|
||||
desc="Description", # Описание параметра
|
||||
cat="category", # Категория (по умолчанию "global")
|
||||
env="ENV_VAR", # Переменная окружения
|
||||
validator=int # Функция валидации
|
||||
)
|
||||
```
|
||||
|
||||
### Категории
|
||||
|
||||
Рекомендуемые категории:
|
||||
- `app` - параметры приложения
|
||||
- `logging` - параметры логирования
|
||||
- `global` - общие параметры (по умолчанию)
|
||||
|
||||
## Получение значений
|
||||
|
||||
```python
|
||||
# Получить значение параметра
|
||||
value = config.get("param_name") # категория по умолчанию
|
||||
value = config.get("param_name", cat="app") # категория app
|
||||
|
||||
# Получить описание параметра
|
||||
desc = config.get_description("param_name", cat="app")
|
||||
|
||||
# Получить объект ConfigParameter
|
||||
param = config.get_parameter("param_name", cat="app")
|
||||
```
|
||||
|
||||
## Изменение значений
|
||||
|
||||
```python
|
||||
# Изменить значение параметра
|
||||
config.set("param_name", "new_value", cat="app")
|
||||
```
|
||||
|
||||
## Сохранение и загрузка
|
||||
|
||||
```python
|
||||
# Сохранить конфигурацию в файл
|
||||
config.save() # использовать путь по умолчанию
|
||||
config.save("/path/to/config.yaml") # сохранить в указанный файл
|
||||
|
||||
# Загрузить конфигурацию из файла
|
||||
config.load() # использовать путь по умолчанию
|
||||
config.load("/path/to/config.yaml") # загрузить из указанного файла
|
||||
|
||||
# Сбросить конфигурацию
|
||||
config.reset() # очищает все параметры и перезагружает
|
||||
```
|
||||
|
||||
## Примеры
|
||||
|
||||
### Базовое использование
|
||||
|
||||
```python
|
||||
import src.utils.config_manager
|
||||
config = src.utils.config_manager.config
|
||||
|
||||
# Регистрация параметра приложения
|
||||
config.register(
|
||||
name="name",
|
||||
val="komAI",
|
||||
default="MyApp",
|
||||
desc="Наименование проекта",
|
||||
cat="app"
|
||||
)
|
||||
|
||||
# Получение значения
|
||||
print(config.get("name", cat="app")) # "komAI"
|
||||
```
|
||||
|
||||
### С валидатором
|
||||
|
||||
```python
|
||||
def validate_level(old_val, new_val):
|
||||
valid_levels = ["DEBUG", "INFO", "WARNING", "ERROR"]
|
||||
if new_val not in valid_levels:
|
||||
raise ValueError(f"Invalid level: {new_val}")
|
||||
|
||||
config.register(
|
||||
name="level",
|
||||
val="INFO",
|
||||
default="INFO",
|
||||
desc="Уровень логирования",
|
||||
cat="logging",
|
||||
validator=validate_level
|
||||
)
|
||||
|
||||
# При изменении значения вызывается валидатор
|
||||
config.set("level", "DEBUG") # OK
|
||||
config.set("level", "INVALID") # Ошибка валидации, значение не меняется
|
||||
```
|
||||
|
||||
### Переопределение через env
|
||||
|
||||
```python
|
||||
# Установить путь через переменную окружения
|
||||
# export KOMAI_CONFIG_FILE=/path/to/config.yaml
|
||||
|
||||
config.load() # автоматически использует KOMAI_CONFIG_FILE
|
||||
```
|
||||
|
||||
### Работа с ConfigParameter
|
||||
|
||||
```python
|
||||
param = config.get_parameter("name", cat="app")
|
||||
|
||||
# Конвертация значений
|
||||
param.as_int() # в целое число
|
||||
param.as_bool() # в boolean
|
||||
param.as_str() # в строку
|
||||
|
||||
# Доступ к свойствам
|
||||
param.name # имя параметра
|
||||
param.val # текущее значение
|
||||
param.default # значение по умолчанию
|
||||
param.desc # описание
|
||||
param.cat # категория
|
||||
param.env # переменная окружения
|
||||
```
|
||||
5
log/README.md
Normal file
5
log/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Каталог для хранения логов.
|
||||
|
||||
Параметр конфига: `global.log_path`
|
||||
|
||||
`(gitignored)`
|
||||
3
modules/README.md
Normal file
3
modules/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Каталог модулей проекта
|
||||
|
||||
Параметр конфига: `global.modules`
|
||||
7
src/README.md
Normal file
7
src/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# src - основные исходники
|
||||
|
||||
## Централизованный API
|
||||
|
||||
## Утилиты
|
||||
|
||||
- `config_manager/` - управление конфигурацией
|
||||
0
src/__init__.py
Normal file
0
src/__init__.py
Normal file
3
src/utils/README.md
Normal file
3
src/utils/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# src/utils - утилиты
|
||||
|
||||
- `config_manager/` - ConfigManager
|
||||
0
src/utils/__init__.py
Normal file
0
src/utils/__init__.py
Normal file
45
src/utils/config_manager/AGENTS.md
Normal file
45
src/utils/config_manager/AGENTS.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# ConfigManager
|
||||
|
||||
## Usage
|
||||
|
||||
```python
|
||||
import src.utils.config_manager
|
||||
config = src.utils.config_manager.config
|
||||
```
|
||||
|
||||
## Key APIs
|
||||
|
||||
- `config.register(name, val, default, desc, cat, env, validator)` - register a parameter
|
||||
- `config.get(name, cat)` - get parameter value
|
||||
- `config.set(name, value, cat)` - set parameter value (triggers validator if present)
|
||||
- `config.get_parameter(name, cat)` - get ConfigParameter object
|
||||
- `config.get_description(name, cat)` - get parameter description
|
||||
- `config.load(path?)` - load from file or env
|
||||
- `config.save(path?)` - save to file
|
||||
- `config.reset()` - clear all and reload
|
||||
|
||||
## Validator
|
||||
|
||||
Signature: `validator(old_val, new_val)` - must raise on invalid values.
|
||||
|
||||
Called on every value change (via `set()`, direct assignment, or `load()`).
|
||||
|
||||
On failure: logs error via `logger.error()` and raises exception. Value unchanged.
|
||||
|
||||
## Constants
|
||||
|
||||
- `DEFAULT_CONFIG_FILE = "config/global.yaml"`
|
||||
- `DEFAULT_CONFIG_ENV = "KOMAI_CONFIG_FILE"`
|
||||
- `DEFAULT_CATEGORY = "global"`
|
||||
|
||||
## ConfigParameter Properties
|
||||
|
||||
- `name`, `val`, `default`, `desc`, `cat`, `env`, `validator`
|
||||
- `as_int(default)`, `as_bool()`, `as_str()`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- Single global instance created at module import (`config` object in `__init__.py`)
|
||||
- All `__init__.py` files re-export only `config`
|
||||
- Validator called via property setter on `_val` attribute
|
||||
- `reset()` re-creates params dict and reloads from file
|
||||
92
src/utils/config_manager/README.md
Normal file
92
src/utils/config_manager/README.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# ConfigManager
|
||||
|
||||
Управление конфигурацией с регистрацией параметров и описаний.
|
||||
|
||||
## Описание
|
||||
|
||||
### Класс ConfigParameter
|
||||
|
||||
Базовый класс для описания параметра
|
||||
|
||||
name: Имя параметра
|
||||
val: Значение
|
||||
def: Значение по умолчанию
|
||||
desc: Описание параметра (не обязательно)
|
||||
cat: Категория параметра (не обязательно, по умолчанию 'global')
|
||||
env: Имя переменной окружения (не обязательно)
|
||||
validator: Функция проверки допустимости параметра (если не используется, то значение -- строка. не обязательно)
|
||||
|
||||
Параметры хранятся к конфиге так:
|
||||
|
||||
```config
|
||||
#$description
|
||||
#$category.$name: $value
|
||||
$category.$name: $value
|
||||
```
|
||||
например:
|
||||
```config
|
||||
#Наименование проекта
|
||||
#global.name: "komAI"
|
||||
global.name: "SuperAPP"
|
||||
```
|
||||
|
||||
Пример использования параметра:
|
||||
|
||||
```python
|
||||
param1 = ConfigParameter(cat="global", name="name", val="komAI", desc="Наименование проекта", def="SuppaPuppa")
|
||||
|
||||
#name:name value:Наименование проекта
|
||||
print(f"name:{param1.name} value:{param1.desc}")
|
||||
|
||||
#name.as_int:0
|
||||
print(f"{param1.name}.as_int:{param1.as_int()}")
|
||||
|
||||
##Наименование проекта\n#global.name: "SuppaPuppa"\nglobal.name: "komAI"
|
||||
print(param1)
|
||||
```
|
||||
|
||||
### Класс ConfigManager
|
||||
|
||||
- Базовый класс для работы с параметрами
|
||||
- Параметры хранятся текстовом конфиге (например в `config/global.yaml`)
|
||||
- Файл можно переопределить из командной строки (например `--config ~/myApp/config/app.yaml`)
|
||||
- Файл можно переопределить через переменную окружения `$projectname_CONFIG=~/superApp/config.yaml`
|
||||
- Приоритет определения параметров (от высшего к низшему):
|
||||
коммандная строка --> переменные окружения --> файл ~/.ENV
|
||||
- Для ConfigManager значение файла конфигурации по умолчанию задано в тексте конcтантой. Например:
|
||||
```python
|
||||
config_file_default = "config/global.yaml"
|
||||
config_env_default = "KOMAI_CONFIG_FILE"
|
||||
```
|
||||
- Регистрация и инициализация класса должна быть глобальная
|
||||
|
||||
Пример использования:
|
||||
```python
|
||||
from src.utils.config_manager.config_manager import get_config
|
||||
|
||||
config = get_config()
|
||||
config.register(cat="global", name="app.name", val="komAI", desc="Наименование проекта")
|
||||
#если не задана cat, использовать значение по умолчанию
|
||||
#данный вызов вернёт значение параметра 'global.app.name'
|
||||
value = config.get("app.name")
|
||||
#данный вызов вернёт значение описания параметра 'global.app.name'
|
||||
desc = config.get_description("app.name", cat="global")
|
||||
```
|
||||
|
||||
## Глобальный доступ
|
||||
|
||||
`get_config()` - возвращает singleton ConfigManager
|
||||
|
||||
## Регистрация параметров модулями
|
||||
|
||||
Каждый модуль регистрирует свои параметры при инициализации:
|
||||
```python
|
||||
config.register(name="level", val="INFO", desc="Уровень логирования", cat="logging")
|
||||
```
|
||||
|
||||
## Категории
|
||||
|
||||
Возможные категории параметров
|
||||
- `app` - приложение
|
||||
- `logging` - логирование
|
||||
- `global` - общие
|
||||
3
src/utils/config_manager/__init__.py
Normal file
3
src/utils/config_manager/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .config_manager import config
|
||||
|
||||
__all__ = ["config"]
|
||||
208
src/utils/config_manager/config_manager.py
Normal file
208
src/utils/config_manager/config_manager.py
Normal file
@@ -0,0 +1,208 @@
|
||||
import logging
|
||||
import os
|
||||
import yaml
|
||||
from pathlib import Path
|
||||
from typing import Callable, Optional
|
||||
|
||||
DEFAULT_CONFIG_FILE = "config/global.yaml"
|
||||
DEFAULT_CONFIG_ENV = "KOMAI_CONFIG_FILE"
|
||||
DEFAULT_CATEGORY = "global"
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ConfigParameter:
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
val,
|
||||
default=None,
|
||||
desc: str = None,
|
||||
cat: str = DEFAULT_CATEGORY,
|
||||
env: str = None,
|
||||
validator: Callable = None,
|
||||
):
|
||||
self.name = name
|
||||
self.default = default if default is not None else val
|
||||
self.desc = desc
|
||||
self.cat = cat
|
||||
self.env = env
|
||||
self.validator = validator
|
||||
self._val = val
|
||||
|
||||
@property
|
||||
def val(self):
|
||||
return self._val
|
||||
|
||||
@val.setter
|
||||
def val(self, new_val):
|
||||
self._set_val(self._val, new_val)
|
||||
|
||||
def _set_val(self, old_val, new_val):
|
||||
if self.validator:
|
||||
try:
|
||||
self.validator(old_val, new_val)
|
||||
self._val = new_val
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"ConfigParameter '{self.cat}.{self.name}': validation failed for value '{new_val}': {e}"
|
||||
)
|
||||
raise
|
||||
else:
|
||||
self._val = new_val
|
||||
|
||||
def as_int(self, default: int = 0) -> int:
|
||||
try:
|
||||
return int(self.val)
|
||||
except (ValueError, TypeError):
|
||||
return default
|
||||
|
||||
def as_str(self) -> str:
|
||||
return str(self.val)
|
||||
|
||||
def as_bool(self) -> bool:
|
||||
if isinstance(self.val, bool):
|
||||
return self.val
|
||||
if isinstance(self.val, str):
|
||||
return self.val.lower() in ("true", "yes", "1", "on")
|
||||
return bool(self.val)
|
||||
|
||||
def __str__(self) -> str:
|
||||
comment_line = f"#{self.desc}" if self.desc else None
|
||||
default_line = f"#{self.cat}.{self.name}: {self._format_value(self.default)}"
|
||||
value_line = f"{self.cat}.{self.name}: {self._format_value(self.val)}"
|
||||
lines = [l for l in [comment_line, default_line, value_line] if l is not None]
|
||||
return "\n".join(lines)
|
||||
|
||||
def _format_value(self, val) -> str:
|
||||
if val is None:
|
||||
return "null"
|
||||
if isinstance(val, bool):
|
||||
return "true" if val else "false"
|
||||
if isinstance(val, str):
|
||||
if not val or val.lower() in ("true", "false", "null"):
|
||||
return f'"{val}"'
|
||||
return f'"{val}"'
|
||||
return str(val).lower() if isinstance(val, bool) else str(val)
|
||||
|
||||
|
||||
class ConfigManager:
|
||||
def __init__(
|
||||
self,
|
||||
config_file: str = DEFAULT_CONFIG_FILE,
|
||||
config_env: str = DEFAULT_CONFIG_ENV,
|
||||
):
|
||||
self._config_file = config_file
|
||||
self._config_env = config_env
|
||||
self._params: dict[str, ConfigParameter] = {}
|
||||
self._config: dict = {}
|
||||
self._config_path: Optional[Path] = None
|
||||
|
||||
def register(
|
||||
self,
|
||||
name: str,
|
||||
val=None,
|
||||
default=None,
|
||||
desc: str = None,
|
||||
cat: str = DEFAULT_CATEGORY,
|
||||
env: str = None,
|
||||
validator: Callable = None,
|
||||
) -> ConfigParameter:
|
||||
param = ConfigParameter(
|
||||
name=name,
|
||||
val=val,
|
||||
default=default,
|
||||
desc=desc,
|
||||
cat=cat,
|
||||
env=env,
|
||||
validator=validator,
|
||||
)
|
||||
key = f"{cat}.{name}"
|
||||
self._params[key] = param
|
||||
return param
|
||||
|
||||
def get(self, name: str, cat: str = DEFAULT_CATEGORY) -> Optional[str]:
|
||||
key = f"{cat}.{name}"
|
||||
param = self._params.get(key)
|
||||
if param is None:
|
||||
return None
|
||||
return param.val
|
||||
|
||||
def get_description(self, name: str, cat: str = DEFAULT_CATEGORY) -> Optional[str]:
|
||||
key = f"{cat}.{name}"
|
||||
param = self._params.get(key)
|
||||
if param is None:
|
||||
return None
|
||||
return param.desc
|
||||
|
||||
def get_parameter(
|
||||
self, name: str, cat: str = DEFAULT_CATEGORY
|
||||
) -> Optional[ConfigParameter]:
|
||||
key = f"{cat}.{name}"
|
||||
return self._params.get(key)
|
||||
|
||||
def set(self, name: str, value, cat: str = DEFAULT_CATEGORY) -> None:
|
||||
key = f"{cat}.{name}"
|
||||
param = self._params.get(key)
|
||||
if param is not None:
|
||||
param.val = value
|
||||
|
||||
def load(self, path: str = None) -> dict:
|
||||
if path:
|
||||
self._config_path = Path(path)
|
||||
else:
|
||||
env_path = os.environ.get(self._config_env)
|
||||
if env_path:
|
||||
self._config_path = Path(env_path)
|
||||
else:
|
||||
self._config_path = Path(self._config_file)
|
||||
|
||||
if self._config_path.exists():
|
||||
with open(self._config_path, "r", encoding="utf-8") as f:
|
||||
self._config = yaml.safe_load(f) or {}
|
||||
else:
|
||||
self._config = {}
|
||||
|
||||
self._apply_config_to_params()
|
||||
return self._config
|
||||
|
||||
def reset(self) -> None:
|
||||
self._params = {}
|
||||
self._config = {}
|
||||
self._config_path = None
|
||||
self.load()
|
||||
|
||||
def save(self, path: str = None) -> None:
|
||||
if path:
|
||||
self._config_path = Path(path)
|
||||
elif self._config_path is None:
|
||||
self._config_path = Path(self._config_file)
|
||||
|
||||
self._config = {}
|
||||
for key, param in self._params.items():
|
||||
self._config[key] = param.val
|
||||
|
||||
self._config_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(self._config_path, "w", encoding="utf-8") as f:
|
||||
yaml.safe_dump(
|
||||
self._config, f, default_flow_style=False, allow_unicode=True
|
||||
)
|
||||
|
||||
def _apply_config_to_params(self) -> None:
|
||||
for key, param in self._params.items():
|
||||
if key in self._config:
|
||||
param.val = self._config[key]
|
||||
elif param.env:
|
||||
env_val = os.environ.get(param.env)
|
||||
if env_val is not None:
|
||||
param.val = env_val
|
||||
|
||||
def __str__(self) -> str:
|
||||
lines = []
|
||||
for param in self._params.values():
|
||||
lines.append(str(param))
|
||||
return "\n\n".join(lines)
|
||||
|
||||
|
||||
config = ConfigManager()
|
||||
config.load()
|
||||
1
tests/README.md
Normal file
1
tests/README.md
Normal file
@@ -0,0 +1 @@
|
||||
# Тестовые юниты для тестирования различных аспектов проекта, которы можно выполнить автономно из консоли.
|
||||
131
tests/test_config_manager.py
Normal file
131
tests/test_config_manager.py
Normal file
@@ -0,0 +1,131 @@
|
||||
import os
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
from src.utils.config_manager import config
|
||||
|
||||
|
||||
def test_config_manager_register_and_get():
|
||||
config.reset()
|
||||
config.register(name="app_name", val="komAI", desc="Application name", cat="app")
|
||||
assert config.get("app_name", cat="app") == "komAI"
|
||||
assert config.get_description("app_name", cat="app") == "Application name"
|
||||
|
||||
|
||||
def test_config_manager_register_default_category():
|
||||
config.reset()
|
||||
config.register(name="param1", val="value1")
|
||||
assert config.get("param1") == "value1"
|
||||
|
||||
|
||||
def test_config_manager_singleton():
|
||||
from src.utils.config_manager import config as config2
|
||||
|
||||
assert config is config2
|
||||
|
||||
|
||||
def test_config_manager_load_save():
|
||||
config.reset()
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
config_path = Path(tmpdir) / "config.yaml"
|
||||
config.register(name="app_name", val="TestApp", cat="app")
|
||||
config.register(name="debug", val="true", cat="app")
|
||||
|
||||
config.save(str(config_path))
|
||||
assert config_path.exists()
|
||||
|
||||
config.reset()
|
||||
config.register(name="app_name", val="Other", cat="app")
|
||||
config.register(name="debug", val="false", cat="app")
|
||||
config.load(str(config_path))
|
||||
|
||||
assert config.get("app_name", cat="app") == "TestApp"
|
||||
assert config.get("debug", cat="app") == "true"
|
||||
|
||||
|
||||
def test_config_manager_env_override():
|
||||
config.reset()
|
||||
env_var = "TEST_APP_CONFIG"
|
||||
os.environ[env_var] = "/nonexistent/path.yaml"
|
||||
|
||||
from src.utils.config_manager.config_manager import ConfigManager
|
||||
|
||||
test_config = ConfigManager(config_env=env_var)
|
||||
test_config.register(name="param1", val="original")
|
||||
test_config.load()
|
||||
|
||||
assert test_config._config_path == Path("/nonexistent/path.yaml")
|
||||
del os.environ[env_var]
|
||||
|
||||
|
||||
def test_config_parameter_get_parameter():
|
||||
config.reset()
|
||||
config.register(name="param1", val="value1", cat="test")
|
||||
param = config.get_parameter("param1", cat="test")
|
||||
assert param is not None
|
||||
assert param.val == "value1"
|
||||
assert param.name == "param1"
|
||||
assert param.default == "value1"
|
||||
|
||||
|
||||
def test_config_set_value():
|
||||
config.reset()
|
||||
config.register(name="param1", val="original", cat="test")
|
||||
assert config.get("param1", cat="test") == "original"
|
||||
|
||||
config.set("param1", "modified", cat="test")
|
||||
assert config.get("param1", cat="test") == "modified"
|
||||
|
||||
|
||||
def test_config_validator():
|
||||
config.reset()
|
||||
|
||||
def validate_level(old_val, new_val):
|
||||
valid_levels = ["DEBUG", "INFO", "WARNING", "ERROR"]
|
||||
if new_val not in valid_levels:
|
||||
raise ValueError(f"Invalid level: {new_val}")
|
||||
|
||||
config.register(name="level", val="INFO", cat="test", validator=validate_level)
|
||||
assert config.get("level", cat="test") == "INFO"
|
||||
|
||||
config.set("level", "DEBUG", cat="test")
|
||||
assert config.get("level", cat="test") == "DEBUG"
|
||||
|
||||
try:
|
||||
config.set("level", "INVALID", cat="test")
|
||||
assert False, "Should have raised ValueError"
|
||||
except ValueError:
|
||||
assert config.get("level", cat="test") == "DEBUG"
|
||||
|
||||
|
||||
def run_tests():
|
||||
tests = [
|
||||
test_config_manager_register_and_get,
|
||||
test_config_manager_register_default_category,
|
||||
test_config_manager_singleton,
|
||||
test_config_manager_load_save,
|
||||
test_config_manager_env_override,
|
||||
test_config_parameter_get_parameter,
|
||||
test_config_set_value,
|
||||
test_config_validator,
|
||||
]
|
||||
|
||||
passed = 0
|
||||
failed = 0
|
||||
|
||||
for test in tests:
|
||||
try:
|
||||
test()
|
||||
print(f"PASS: {test.__name__}")
|
||||
passed += 1
|
||||
except Exception as e:
|
||||
print(f"FAIL: {test.__name__} - {e}")
|
||||
failed += 1
|
||||
|
||||
print(f"\n{passed}/{passed + failed} tests passed")
|
||||
return failed == 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = run_tests()
|
||||
exit(0 if success else 1)
|
||||
Reference in New Issue
Block a user