Add getall/getrawall for nested params, ROOT_KEY constant, tests and docs
This commit is contained in:
53
README.md
53
README.md
@@ -3,17 +3,12 @@
|
||||
## Требования
|
||||
|
||||
- Python >3.10
|
||||
- Все консольные выводы дублируются в логах
|
||||
|
||||
## Структура
|
||||
## Установка
|
||||
|
||||
- `app/` - точка входа (komAI.py)
|
||||
- `src/` - исходный код
|
||||
- `src/__init__.py` - централизованный API
|
||||
- `config/` - YAML конфигурация
|
||||
- `tests/` - юнит-тесты
|
||||
- `modules/` - подключаемые модули
|
||||
- `log/` - файлы логов
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## Быстрый старт
|
||||
|
||||
@@ -21,8 +16,42 @@
|
||||
python -m app.komAI
|
||||
```
|
||||
|
||||
## Структура
|
||||
|
||||
- `app/` - точка входа приложения
|
||||
- `src/` - исходный код
|
||||
- `config/` - YAML конфигурация
|
||||
- `modules/` - подключаемые модули
|
||||
- `tests/` - юнит-тесты
|
||||
- `log/` - файлы логов
|
||||
- `doc/` - документация
|
||||
|
||||
## Конфигурация
|
||||
|
||||
- Файл: `config/global.yaml`
|
||||
- Параметры регистрируются модулями при инициализации
|
||||
- Сохранение: `from src import save_config; save_config()`
|
||||
Модуль `config_manager` управляет конфигурацией с регистрацией параметров.
|
||||
|
||||
```python
|
||||
import src.utils.config_manager
|
||||
config = src.utils.config_manager.config
|
||||
|
||||
# Регистрация параметра
|
||||
config.register(name="app_name", val="komAI", desc="Наименование проекта", cat="app")
|
||||
|
||||
# Получение значения
|
||||
config.get("app_name", cat="app")
|
||||
|
||||
# Сохранение в файл
|
||||
config.save()
|
||||
```
|
||||
|
||||
См. [doc/src.utils.config_manager.md](doc/src.utils.config_manager.md) для подробной документации.
|
||||
|
||||
## Логирование
|
||||
|
||||
Все консольные выводы дублируются в логах. Настройка логирования в `config/global.yaml`.
|
||||
|
||||
## Тесты
|
||||
|
||||
```bash
|
||||
python -m tests.test_config_manager
|
||||
```
|
||||
|
||||
@@ -34,14 +34,15 @@ config.reset()
|
||||
## Константы
|
||||
|
||||
```python
|
||||
from src.utils.config_manager import DEFAULT_CONFIG_FILE, DEFAULT_CONFIG_ENV, DEFAULT_CATEGORY
|
||||
from src.utils.config_manager import DEFAULT_CONFIG_FILE, DEFAULT_CONFIG_ENV, DEFAULT_CATEGORY, ROOT_KEY
|
||||
```
|
||||
|
||||
| Константа | Значение | Описание |
|
||||
|----------|----------|----------|
|
||||
|----------|---------|----------|
|
||||
| `DEFAULT_CONFIG_FILE` | `"config/global.yaml"` | Путь к файлу конфигурации |
|
||||
| `DEFAULT_CONFIG_ENV` | `"KOMAI_CONFIG_FILE"` | Переменная окружения для переопределения пути |
|
||||
| `DEFAULT_CATEGORY` | `"global"` | Категория по умолчанию |
|
||||
| `ROOT_KEY` | `"$root$"` | Ключ для корневого значения во вложенных структурах |
|
||||
|
||||
## Регистрация параметров
|
||||
|
||||
@@ -49,11 +50,11 @@ from src.utils.config_manager import DEFAULT_CONFIG_FILE, DEFAULT_CONFIG_ENV, DE
|
||||
config.register(
|
||||
name="param_name", # Имя параметра (обязательно)
|
||||
val="value", # Текущее значение (обязательно)
|
||||
default="def_value", # Значение по умолчанию
|
||||
desc="Description", # Описание параметра
|
||||
default="def_value", # Значение по умолчанию
|
||||
desc="Description", # Описание параметра
|
||||
cat="category", # Категория (по умолчанию "global")
|
||||
env="ENV_VAR", # Переменная окружения
|
||||
validator=int # Функция валидации
|
||||
validator=int # Функция валидации
|
||||
)
|
||||
```
|
||||
|
||||
@@ -68,8 +69,11 @@ config.register(
|
||||
|
||||
```python
|
||||
# Получить значение параметра
|
||||
value = config.get("param_name") # категория по умолчанию
|
||||
value = config.get("param_name", cat="app") # категория app
|
||||
value = config.get("param_name") # категория по умолчанию (global)
|
||||
value = config.get("param_name", cat="app") # категория app
|
||||
|
||||
# Получить по полному ключу (без подстановки категории)
|
||||
value = config.getraw("app.param_name")
|
||||
|
||||
# Получить описание параметра
|
||||
desc = config.get_description("param_name", cat="app")
|
||||
@@ -83,6 +87,34 @@ param = config.get_parameter("param_name", cat="app")
|
||||
```python
|
||||
# Изменить значение параметра
|
||||
config.set("param_name", "new_value", cat="app")
|
||||
|
||||
# Изменить по полному ключу
|
||||
config.setraw("app.param_name", "new_value")
|
||||
```
|
||||
|
||||
## Вложенные параметры
|
||||
|
||||
Параметры с точками в имени создают вложенные структуры:
|
||||
|
||||
```python
|
||||
config.register(name="coder", val="llama-coder", cat="models")
|
||||
config.register(name="coder.thinking", val="full", cat="models")
|
||||
config.register(name="coder.temperature", val="0.3", cat="models")
|
||||
config.register(name="chatter", val="gemmini", cat="models")
|
||||
config.register(name="chatter.thinking", val="none", cat="models")
|
||||
config.register(name="chatter.temperature", val="0.9", cat="models")
|
||||
|
||||
# Получить корневое значение
|
||||
config.get("coder", cat="models") # "llama-coder"
|
||||
|
||||
# Получить все параметры группы с корневым значением
|
||||
config.getall("coder", cat="models")
|
||||
# {"$root$": "llama-coder", "thinking": "full", "temperature": "0.3"}
|
||||
|
||||
# Получить все параметры категории в виде вложенного dict
|
||||
config.getrawall("models")
|
||||
# {"coder": {"$root$": "llama-coder", "thinking": "full", "temperature": "0.3"},
|
||||
# "chatter": {"$root$": "gemmini", "thinking": "none", "temperature": "0.9"}}
|
||||
```
|
||||
|
||||
## Сохранение и загрузка
|
||||
@@ -90,7 +122,7 @@ config.set("param_name", "new_value", cat="app")
|
||||
```python
|
||||
# Сохранить конфигурацию в файл
|
||||
config.save() # использовать путь по умолчанию
|
||||
config.save("/path/to/config.yaml") # сохранить в указанный файл
|
||||
config.save("/path/to/config.yaml") # сохранить в указанный файл
|
||||
|
||||
# Загрузить конфигурацию из файла
|
||||
config.load() # использовать путь по умолчанию
|
||||
@@ -100,6 +132,28 @@ config.load("/path/to/config.yaml") # загрузить из указанн
|
||||
config.reset() # очищает все параметры и перезагружает
|
||||
```
|
||||
|
||||
## Валидация
|
||||
|
||||
```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") # Ошибка валидации, значение не меняется
|
||||
```
|
||||
|
||||
## Примеры
|
||||
|
||||
### Базовое использование
|
||||
@@ -121,28 +175,6 @@ config.register(
|
||||
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
|
||||
|
||||
1
requirements.txt
Normal file
1
requirements.txt
Normal file
@@ -0,0 +1 @@
|
||||
PyYAML>=6.0
|
||||
@@ -12,6 +12,10 @@ config = src.utils.config_manager.config
|
||||
- `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.getraw(key)` - get by full key path (no cat substitution)
|
||||
- `config.setraw(key, value)` - set by full key path
|
||||
- `config.getall(name, cat)` - get root + all nested params as dict with `ROOT_KEY`
|
||||
- `config.getrawall(cat)` - get all params under category as nested dict
|
||||
- `config.get_parameter(name, cat)` - get ConfigParameter object
|
||||
- `config.get_description(name, cat)` - get parameter description
|
||||
- `config.load(path?)` - load from file or env
|
||||
@@ -31,6 +35,19 @@ On failure: logs error via `logger.error()` and raises exception. Value unchange
|
||||
- `DEFAULT_CONFIG_FILE = "config/global.yaml"`
|
||||
- `DEFAULT_CONFIG_ENV = "KOMAI_CONFIG_FILE"`
|
||||
- `DEFAULT_CATEGORY = "global"`
|
||||
- `ROOT_KEY = "$root$"` - key name for root value in nested getall/getrawall results
|
||||
|
||||
## Nested Parameters
|
||||
|
||||
Params with dots in name create nested structures:
|
||||
```python
|
||||
config.register(name="coder", val="llama-coder", cat="models")
|
||||
config.register(name="coder.thinking", val="full", cat="models")
|
||||
|
||||
config.get("coder", cat="models") # "llama-coder"
|
||||
config.getall("coder", cat="models") # {"$root$": "llama-coder", "thinking": "full"}
|
||||
config.getrawall("models") # {"coder": {"$root$": "llama-coder", "thinking": "full"}}
|
||||
```
|
||||
|
||||
## ConfigParameter Properties
|
||||
|
||||
@@ -40,6 +57,6 @@ On failure: logs error via `logger.error()` and raises exception. Value unchange
|
||||
## Implementation Notes
|
||||
|
||||
- Single global instance created at module import (`config` object in `__init__.py`)
|
||||
- All `__init__.py` files re-export only `config`
|
||||
- `ROOT_KEY` exported from module for custom notation if needed
|
||||
- Validator called via property setter on `_val` attribute
|
||||
- `reset()` re-creates params dict and reloads from file
|
||||
|
||||
@@ -7,6 +7,7 @@ from typing import Callable, Optional
|
||||
DEFAULT_CONFIG_FILE = "config/global.yaml"
|
||||
DEFAULT_CONFIG_ENV = "KOMAI_CONFIG_FILE"
|
||||
DEFAULT_CATEGORY = "global"
|
||||
ROOT_KEY = "$root$"
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -121,32 +122,97 @@ class ConfigManager:
|
||||
self._params[key] = param
|
||||
return param
|
||||
|
||||
def get(self, name: str, cat: str = DEFAULT_CATEGORY) -> Optional[str]:
|
||||
def get(self, name: str, cat: str = None) -> Optional[str]:
|
||||
cat = cat or DEFAULT_CATEGORY
|
||||
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]:
|
||||
def getraw(self, key: str):
|
||||
param = self._params.get(key)
|
||||
if param is None:
|
||||
return None
|
||||
return param.val
|
||||
|
||||
def get_description(self, name: str, cat: str = None) -> Optional[str]:
|
||||
cat = cat or DEFAULT_CATEGORY
|
||||
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]:
|
||||
def get_parameter(self, name: str, cat: str = None) -> Optional[ConfigParameter]:
|
||||
cat = cat or DEFAULT_CATEGORY
|
||||
key = f"{cat}.{name}"
|
||||
return self._params.get(key)
|
||||
|
||||
def set(self, name: str, value, cat: str = DEFAULT_CATEGORY) -> None:
|
||||
def set(self, name: str, value, cat: str = None) -> None:
|
||||
cat = cat or DEFAULT_CATEGORY
|
||||
key = f"{cat}.{name}"
|
||||
param = self._params.get(key)
|
||||
if param is not None:
|
||||
param.val = value
|
||||
|
||||
def setraw(self, key: str, value) -> None:
|
||||
param = self._params.get(key)
|
||||
if param is not None:
|
||||
param.val = value
|
||||
|
||||
def getall(self, name: str, cat: str = None) -> Optional[dict]:
|
||||
cat = cat or DEFAULT_CATEGORY
|
||||
root_key = f"{cat}.{name}"
|
||||
root_param = self._params.get(root_key)
|
||||
if root_param is None:
|
||||
return None
|
||||
|
||||
result = {ROOT_KEY: root_param.val}
|
||||
prefix = root_key + "."
|
||||
|
||||
for key, param in self._params.items():
|
||||
if key.startswith(prefix):
|
||||
nested_key = key[len(prefix) :]
|
||||
parts = nested_key.split(".")
|
||||
if len(parts) == 1:
|
||||
result[parts[0]] = param.val
|
||||
else:
|
||||
current = result
|
||||
for part in parts[:-1]:
|
||||
if part not in current:
|
||||
current[part] = {}
|
||||
current = current[part]
|
||||
current[parts[-1]] = param.val
|
||||
|
||||
return result
|
||||
|
||||
def getrawall(self, key: str) -> Optional[dict]:
|
||||
if not key.endswith("."):
|
||||
key = key + "."
|
||||
result = {}
|
||||
|
||||
for param_key, param in self._params.items():
|
||||
if not param_key.startswith(key):
|
||||
continue
|
||||
|
||||
nested_key = param_key[len(key) :]
|
||||
parts = nested_key.split(".")
|
||||
|
||||
if len(parts) == 1:
|
||||
result[parts[0]] = param.val
|
||||
else:
|
||||
current = result
|
||||
for i, part in enumerate(parts[:-1]):
|
||||
if part not in current:
|
||||
current[part] = {}
|
||||
elif isinstance(current[part], str):
|
||||
current[part] = {ROOT_KEY: current[part]}
|
||||
current = current[part]
|
||||
current[parts[-1]] = param.val
|
||||
|
||||
return result if result else None
|
||||
|
||||
def load(self, path: str = None) -> dict:
|
||||
if path:
|
||||
self._config_path = Path(path)
|
||||
|
||||
@@ -98,6 +98,45 @@ def test_config_validator():
|
||||
assert config.get("level", cat="test") == "DEBUG"
|
||||
|
||||
|
||||
def test_config_getraw_setraw():
|
||||
config.reset()
|
||||
config.register(name="path", val="val1", cat="model1")
|
||||
config.register(name="opt.temperature", val="val2", cat="model1")
|
||||
config.register(name="path", val="val3", cat="global")
|
||||
|
||||
assert config.getraw("model1.path") == "val1"
|
||||
assert config.getraw("model1.opt.temperature") == "val2"
|
||||
assert config.getraw("global.path") == "val3"
|
||||
|
||||
config.setraw("model1.path", "new_val")
|
||||
assert config.getraw("model1.path") == "new_val"
|
||||
|
||||
|
||||
def test_config_getall_getrawall():
|
||||
config.reset()
|
||||
config.register(name="coder", val="llama-coder", cat="models")
|
||||
config.register(name="coder.thinking", val="full", cat="models")
|
||||
config.register(name="coder.temperature", val="0.3", cat="models")
|
||||
config.register(name="chatter", val="gemmini", cat="models")
|
||||
config.register(name="chatter.thinking", val="none", cat="models")
|
||||
config.register(name="chatter.temperature", val="0.9", cat="models")
|
||||
|
||||
assert config.get("coder", cat="models") == "llama-coder"
|
||||
|
||||
all_coder = config.getall("coder", cat="models")
|
||||
assert all_coder == {
|
||||
"$root$": "llama-coder",
|
||||
"thinking": "full",
|
||||
"temperature": "0.3",
|
||||
}
|
||||
|
||||
rawall = config.getrawall("models")
|
||||
assert rawall == {
|
||||
"coder": {"$root$": "llama-coder", "thinking": "full", "temperature": "0.3"},
|
||||
"chatter": {"$root$": "gemmini", "thinking": "none", "temperature": "0.9"},
|
||||
}
|
||||
|
||||
|
||||
def run_tests():
|
||||
tests = [
|
||||
test_config_manager_register_and_get,
|
||||
@@ -108,6 +147,8 @@ def run_tests():
|
||||
test_config_parameter_get_parameter,
|
||||
test_config_set_value,
|
||||
test_config_validator,
|
||||
test_config_getraw_setraw,
|
||||
test_config_getall_getrawall,
|
||||
]
|
||||
|
||||
passed = 0
|
||||
|
||||
Reference in New Issue
Block a user