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
|
- Python >3.10
|
||||||
- Все консольные выводы дублируются в логах
|
|
||||||
|
|
||||||
## Структура
|
## Установка
|
||||||
|
|
||||||
- `app/` - точка входа (komAI.py)
|
```bash
|
||||||
- `src/` - исходный код
|
pip install -r requirements.txt
|
||||||
- `src/__init__.py` - централизованный API
|
```
|
||||||
- `config/` - YAML конфигурация
|
|
||||||
- `tests/` - юнит-тесты
|
|
||||||
- `modules/` - подключаемые модули
|
|
||||||
- `log/` - файлы логов
|
|
||||||
|
|
||||||
## Быстрый старт
|
## Быстрый старт
|
||||||
|
|
||||||
@@ -21,8 +16,42 @@
|
|||||||
python -m app.komAI
|
python -m app.komAI
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Структура
|
||||||
|
|
||||||
|
- `app/` - точка входа приложения
|
||||||
|
- `src/` - исходный код
|
||||||
|
- `config/` - YAML конфигурация
|
||||||
|
- `modules/` - подключаемые модули
|
||||||
|
- `tests/` - юнит-тесты
|
||||||
|
- `log/` - файлы логов
|
||||||
|
- `doc/` - документация
|
||||||
|
|
||||||
## Конфигурация
|
## Конфигурация
|
||||||
|
|
||||||
- Файл: `config/global.yaml`
|
Модуль `config_manager` управляет конфигурацией с регистрацией параметров.
|
||||||
- Параметры регистрируются модулями при инициализации
|
|
||||||
- Сохранение: `from src import save_config; save_config()`
|
```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
|
```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_FILE` | `"config/global.yaml"` | Путь к файлу конфигурации |
|
||||||
| `DEFAULT_CONFIG_ENV` | `"KOMAI_CONFIG_FILE"` | Переменная окружения для переопределения пути |
|
| `DEFAULT_CONFIG_ENV` | `"KOMAI_CONFIG_FILE"` | Переменная окружения для переопределения пути |
|
||||||
| `DEFAULT_CATEGORY` | `"global"` | Категория по умолчанию |
|
| `DEFAULT_CATEGORY` | `"global"` | Категория по умолчанию |
|
||||||
|
| `ROOT_KEY` | `"$root$"` | Ключ для корневого значения во вложенных структурах |
|
||||||
|
|
||||||
## Регистрация параметров
|
## Регистрация параметров
|
||||||
|
|
||||||
@@ -68,9 +69,12 @@ config.register(
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
# Получить значение параметра
|
# Получить значение параметра
|
||||||
value = config.get("param_name") # категория по умолчанию
|
value = config.get("param_name") # категория по умолчанию (global)
|
||||||
value = config.get("param_name", cat="app") # категория app
|
value = config.get("param_name", cat="app") # категория app
|
||||||
|
|
||||||
|
# Получить по полному ключу (без подстановки категории)
|
||||||
|
value = config.getraw("app.param_name")
|
||||||
|
|
||||||
# Получить описание параметра
|
# Получить описание параметра
|
||||||
desc = config.get_description("param_name", cat="app")
|
desc = config.get_description("param_name", cat="app")
|
||||||
|
|
||||||
@@ -83,6 +87,34 @@ param = config.get_parameter("param_name", cat="app")
|
|||||||
```python
|
```python
|
||||||
# Изменить значение параметра
|
# Изменить значение параметра
|
||||||
config.set("param_name", "new_value", cat="app")
|
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"}}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Сохранение и загрузка
|
## Сохранение и загрузка
|
||||||
@@ -100,6 +132,28 @@ config.load("/path/to/config.yaml") # загрузить из указанн
|
|||||||
config.reset() # очищает все параметры и перезагружает
|
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"
|
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
|
### Переопределение через env
|
||||||
|
|
||||||
```python
|
```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.register(name, val, default, desc, cat, env, validator)` - register a parameter
|
||||||
- `config.get(name, cat)` - get parameter value
|
- `config.get(name, cat)` - get parameter value
|
||||||
- `config.set(name, value, cat)` - set parameter value (triggers validator if present)
|
- `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_parameter(name, cat)` - get ConfigParameter object
|
||||||
- `config.get_description(name, cat)` - get parameter description
|
- `config.get_description(name, cat)` - get parameter description
|
||||||
- `config.load(path?)` - load from file or env
|
- `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_FILE = "config/global.yaml"`
|
||||||
- `DEFAULT_CONFIG_ENV = "KOMAI_CONFIG_FILE"`
|
- `DEFAULT_CONFIG_ENV = "KOMAI_CONFIG_FILE"`
|
||||||
- `DEFAULT_CATEGORY = "global"`
|
- `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
|
## ConfigParameter Properties
|
||||||
|
|
||||||
@@ -40,6 +57,6 @@ On failure: logs error via `logger.error()` and raises exception. Value unchange
|
|||||||
## Implementation Notes
|
## Implementation Notes
|
||||||
|
|
||||||
- Single global instance created at module import (`config` object in `__init__.py`)
|
- 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
|
- Validator called via property setter on `_val` attribute
|
||||||
- `reset()` re-creates params dict and reloads from file
|
- `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_FILE = "config/global.yaml"
|
||||||
DEFAULT_CONFIG_ENV = "KOMAI_CONFIG_FILE"
|
DEFAULT_CONFIG_ENV = "KOMAI_CONFIG_FILE"
|
||||||
DEFAULT_CATEGORY = "global"
|
DEFAULT_CATEGORY = "global"
|
||||||
|
ROOT_KEY = "$root$"
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -121,32 +122,97 @@ class ConfigManager:
|
|||||||
self._params[key] = param
|
self._params[key] = param
|
||||||
return 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}"
|
key = f"{cat}.{name}"
|
||||||
param = self._params.get(key)
|
param = self._params.get(key)
|
||||||
if param is None:
|
if param is None:
|
||||||
return None
|
return None
|
||||||
return param.val
|
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}"
|
key = f"{cat}.{name}"
|
||||||
param = self._params.get(key)
|
param = self._params.get(key)
|
||||||
if param is None:
|
if param is None:
|
||||||
return None
|
return None
|
||||||
return param.desc
|
return param.desc
|
||||||
|
|
||||||
def get_parameter(
|
def get_parameter(self, name: str, cat: str = None) -> Optional[ConfigParameter]:
|
||||||
self, name: str, cat: str = DEFAULT_CATEGORY
|
cat = cat or DEFAULT_CATEGORY
|
||||||
) -> Optional[ConfigParameter]:
|
|
||||||
key = f"{cat}.{name}"
|
key = f"{cat}.{name}"
|
||||||
return self._params.get(key)
|
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}"
|
key = f"{cat}.{name}"
|
||||||
param = self._params.get(key)
|
param = self._params.get(key)
|
||||||
if param is not None:
|
if param is not None:
|
||||||
param.val = value
|
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:
|
def load(self, path: str = None) -> dict:
|
||||||
if path:
|
if path:
|
||||||
self._config_path = Path(path)
|
self._config_path = Path(path)
|
||||||
|
|||||||
@@ -98,6 +98,45 @@ def test_config_validator():
|
|||||||
assert config.get("level", cat="test") == "DEBUG"
|
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():
|
def run_tests():
|
||||||
tests = [
|
tests = [
|
||||||
test_config_manager_register_and_get,
|
test_config_manager_register_and_get,
|
||||||
@@ -108,6 +147,8 @@ def run_tests():
|
|||||||
test_config_parameter_get_parameter,
|
test_config_parameter_get_parameter,
|
||||||
test_config_set_value,
|
test_config_set_value,
|
||||||
test_config_validator,
|
test_config_validator,
|
||||||
|
test_config_getraw_setraw,
|
||||||
|
test_config_getall_getrawall,
|
||||||
]
|
]
|
||||||
|
|
||||||
passed = 0
|
passed = 0
|
||||||
|
|||||||
Reference in New Issue
Block a user