Config Manager
Описание
Пакет предназначен для запуска приложений с периодическим выполнением логики, перезагрузкой конфига и управлением по HTTP API.
Контракт: приложение наследует ConfigManagerV2, переопределяет execute() (периодическая работа). Управление (старт/стоп, health) — через каналы, которые создаются снаружи и передаются в конструктор в control_channels (в т.ч. HttpControlChannel для API).
ConfigManager: устройство и взаимосвязи
ConfigManager (класс ConfigManagerV2) — точка входа приложения. Он наследует внутреннюю логику от _RuntimeController (циклы воркера и обновления конфига, запуск/остановка каналов управления).
Ядро (core):
- ConfigLoader — читает конфиг из файла (YAML/JSON), считает хеш и отдаёт конфиг только при изменении; при ошибке парсинга возвращает последний валидный конфиг.
- WorkerLoop — в отдельном потоке циклически вызывает ваш метод
execute()с паузой между вызовами; реагирует на событие остановки и колбэки успеха/ошибки. - LogManager — применяет секцию
logиз конфига к логированию (dictConfig). - HealthAggregator — собирает состояние: жизненный цикл (idle/starting/running/…), время последнего успешного
execute()и таймаут здоровья; формирует единый ответ для health (ok/unhealthy). - ControlChannelBridge — один мост для всех каналов: обработчики on_start/on_stop/on_status (сброс/установка halt, текст статуса).
Каналы управления (control):
- ControlChannel — абстрактный контракт:
start(on_start, on_stop, on_status),stop(). - HttpControlChannel — HTTP API (
/health,/actions/start,/actions/stop,/actions/status); использует UvicornServerRunner; для/healthвызывает HealthAggregator.collect(), для действий — переданные обработчики из ControlChannelBridge. - TelegramControlChannel — реализация через long polling Telegram; команды
/start,/stop,/statusвызывают переданные обработчики.
Поток работы: при start() менеджер поднимает каналы из control_channels (заданные снаружи), затем запускает два цикла: WorkerLoop и периодическое обновление конфига через ConfigLoader. Управление по API: /health, /actions/start, /actions/stop — если в control_channels передан HttpControlChannel. Остановка по halt завершает оба цикла; в конце останавливаются все каналы.
Запуск приложения с ConfigManagerV2 и HttpControlChannel
-
Наследуйте ConfigManagerV2 и реализуйте метод
execute()(в нём — ваша периодическая работа). При необходимости переопределитеget_health_status()для кастомного ответа/health. -
Создайте каналы снаружи и передайте в конструктор. Для HTTP API создайте HttpControlChannel; для health нужен колбэк менеджера — передайте control_channels как фабрику (lambda, получающую менеджер):
from config_manager.v2.control import HttpControlChannel app = MyApp( str(path_to_config), control_channels=lambda m: [ HttpControlChannel( host="0.0.0.0", port=8000, timeout=3, health_provider=m.get_health_provider(), ) ], )Либо передайте готовый список каналов:
control_channels=[channel1, channel2]. -
Запустите из async-контекста:
await app.start()илиasyncio.create_task(app.start())для фона. Остановка:await app.stop()или запрос/actions/stopпо HTTP.
Минимальный пример с HTTP API:
import asyncio
import logging
from pathlib import Path
from config_manager import ConfigManager
from config_manager.v2.control import HttpControlChannel
class MyApp(ConfigManager):
def execute(self) -> None:
pass # ваша периодическая работа
async def main() -> None:
app = MyApp(
str(Path(__file__).parent / "config.yaml"),
control_channels=lambda m: [
HttpControlChannel(
host="0.0.0.0", port=8000, timeout=3,
health_provider=m.get_health_provider(),
)
],
)
asyncio.create_task(app.start())
await asyncio.sleep(3600)
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
asyncio.run(main())
Готовый пример: tests/test_app.py.
Диаграмма классов
classDiagram
direction TB
class ConfigManagerV2 {
+str path
+Any config
+float update_interval
+float work_interval
-ConfigLoader _loader
-LifecycleState _state
+start() async
+stop() async
+execute()*
+get_health_status() HealthPayload
-_run() async
-_worker_loop() async
-_periodic_update_loop() async
}
class _RuntimeController {
<<внутренний>>
-_on_execute_success()
-_on_execute_error(exc)
-_worker_loop() async
-_periodic_update_loop() async
-_start_control_channels() async
-_stop_control_channels() async
-_run() async
}
class ConfigLoader {
+str path
+Any config
+Any last_valid_config
+load_if_changed() async
+parse_config(data) Any
-_read_file_sync() str
-read_file_async() async
}
class WorkerLoop {
-Callable execute
-Callable get_interval
-Event halt_event
+run() async
}
class LogManager {
+apply_config(config) None
}
class ControlChannel {
<<абстрактный>>
+start(on_start, on_stop, on_status) async*
+stop() async*
}
class TelegramControlChannel {
-str _token
-int _chat_id
+start(on_start, on_stop, on_status) async
+stop() async
-_poll_loop() async
}
class HttpControlChannel {
-UvicornServerRunner _runner
-Callable _health_provider
+start(on_start, on_stop, on_status) async
+stop() async
+int port
}
class HealthAggregator {
-Callable get_state
-Callable get_app_health
+collect() async HealthPayload
}
class ControlChannelBridge {
-Event _halt
-Callable _get_state
-Callable _get_status
+on_start() async str
+on_stop() async str
+on_status() async str
}
class UvicornServerRunner {
-Server _server
-Task _serve_task
+start(app) async
+stop() async
+int port
}
ConfigManagerV2 --|> _RuntimeController : наследует
ConfigManagerV2 --> ConfigLoader : использует
ConfigManagerV2 --> LogManager : использует
ConfigManagerV2 --> HealthAggregator : использует
ConfigManagerV2 --> ControlChannelBridge : использует
ConfigManagerV2 ..> ControlChannel : список каналов
_RuntimeController ..> WorkerLoop : создаёт в _worker_loop
TelegramControlChannel --|> ControlChannel : реализует
HttpControlChannel --|> ControlChannel : реализует
HttpControlChannel --> UvicornServerRunner : использует
HttpControlChannel ..> HealthAggregator : health_provider
ControlChannelBridge ..> ControlChannel : on_start, on_stop, on_status
Логирование
Логирование настраивается из конфигурационного файла только если в нём есть секция log в формате dictConfig. Если секции log нет, менеджер пишет предупреждение в лог, а уровень Python по умолчанию (WARNING) сохраняется — сообщения INFO/DEBUG могут не отображаться.
Как проверить, что конфигурация логирования применилась:
- Убедитесь, что путь к файлу конфига верный и файл загружается при старте (в логах нет ошибки чтения конфига).
- Убедитесь, что в конфиге есть ключ
logсversion: 1,handlersиloggers(пример —tests/config.yaml). - После старта в логе должно появиться сообщение уровня INFO:
"Logging configuration applied"(изconfig_manager.v2.core.log_manager). Если его нет, либо секцияlogотсутствует (будет предупреждение), либо уровень root/пакета выше INFO.
Установка
pip install git+https://git.lesha.spb.ru/alex/config_manager.git
Контакты
- e-mail: lesha.spb@gmail.com
- telegram: https://t.me/lesha_spb