diff --git a/src/config_manager/config_manager.py b/src/config_manager/config_manager.py index 4873767..9749d12 100644 --- a/src/config_manager/config_manager.py +++ b/src/config_manager/config_manager.py @@ -3,7 +3,7 @@ import json import yaml import logging import logging.config -from typing import Any +from typing import Any, Optional import os @@ -21,6 +21,8 @@ class ConfigManager: self.update_interval = self.DEFAULT_UPDATE_INTERVAL self.work_interval = self.DEFAULT_WORK_INTERVAL self._halt = asyncio.Event() + self._task: Optional[asyncio.Task] = None + self._loop: Optional[asyncio.AbstractEventLoop] = None def _read_file_sync(self) -> str: with open(self.path, "r", encoding="utf-8") as f: @@ -29,7 +31,7 @@ class ConfigManager: async def _read_file_async(self) -> str: return await asyncio.to_thread(self._read_file_sync) - def _parse_config(self, data: str) -> Any: + def _parse_config(self, str) -> Any: ext = os.path.splitext(self.path)[1].lower() if ext in (".yaml", ".yml"): return yaml.safe_load(data) @@ -39,7 +41,6 @@ class ConfigManager: def _update_intervals_from_config(self) -> None: if not self.config: return - # Берём интервалы из секции config обновления, с контролем типа и значений upd = self.config.get("update_interval") wrk = self.config.get("work_interval") @@ -98,19 +99,52 @@ class ConfigManager: await self._update_config() await asyncio.sleep(self.update_interval) - async def start(self) -> None: + async def _run(self) -> None: + """Внутренняя корутина, запускающая все циклы""" self._halt.clear() logger.info("ConfigManager started") - await asyncio.gather( - self._worker_loop(), - self._periodic_update_loop() - ) + try: + await asyncio.gather( + self._worker_loop(), + self._periodic_update_loop() + ) + except asyncio.CancelledError: + logger.info("ConfigManager tasks cancelled") + finally: + logger.info("ConfigManager stopped") - def stop(self) -> None: - self._halt.set() + def start(self) -> None: + """Запускает менеджер конфигурации в текущем event loop""" + if self._task is not None and not self._task.done(): + logger.warning("ConfigManager is already running") + return + + try: + self._loop = asyncio.get_running_loop() + except RuntimeError: + logger.error("start() must be called from within an async context") + raise + + self._task = self._loop.create_task(self._run()) + logger.info("ConfigManager task created") + + async def stop(self) -> None: + """Останавливает менеджер конфигурации и ожидает завершения""" + if self._task is None: + logger.warning("ConfigManager is not running") + return + logger.info("ConfigManager stopping...") - - + self._halt.set() + + # Ждём корректного завершения задачи + try: + await self._task + except asyncio.CancelledError: + pass + + self._task = None + logger.info("ConfigManager stopped successfully") # Пример наследования и переопределения execute @@ -120,15 +154,16 @@ class MyApp(ConfigManager): async def main(): - app = MyApp("config.yaml") # Можно config.json или config.yaml - task = asyncio.create_task(app.start()) + app = MyApp("config.yaml") + app.start() await asyncio.sleep(20) - app.stop() - await task + await app.stop() logger.info("Work finished.") if __name__ == "__main__": - logging.basicConfig(level=logging.INFO, - format="%(asctime)s [%(levelname)s] %(name)s: %(message)s") - asyncio.run(main()) \ No newline at end of file + logging.basicConfig( + level=logging.INFO, + format="%(asctime)s [%(levelname)s] %(name)s: %(message)s" + ) + asyncio.run(main())