commit c5ce21f5fa33276c0ad82c6def06c7578538dbf5 Author: hhh Date: Sat Jan 4 11:54:01 2025 +0200 dev diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..61b7cc8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/.idea/ +**/__pycache__/ +/tests/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..4267334 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +[nekomata is now in early-dev state](https://nekomata.kotikot.com/) diff --git a/neko_run_controller_asyncio/__init__.py b/neko_run_controller_asyncio/__init__.py new file mode 100644 index 0000000..1bab247 --- /dev/null +++ b/neko_run_controller_asyncio/__init__.py @@ -0,0 +1,4 @@ +from . import modules + + +__all__ = [modules] diff --git a/neko_run_controller_asyncio/modules/__init__.py b/neko_run_controller_asyncio/modules/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/neko_run_controller_asyncio/modules/controller.py b/neko_run_controller_asyncio/modules/controller.py new file mode 100644 index 0000000..7080e8e --- /dev/null +++ b/neko_run_controller_asyncio/modules/controller.py @@ -0,0 +1,37 @@ +import asyncio +from asyncio import subprocess +import psutil +from typing import Callable + + +def kill(proc_pid): + try: + process = psutil.Process(proc_pid) + for proc in process.children(recursive=True): + proc.kill() + process.kill() + except psutil.NoSuchProcess: + return + + +async def run_process( + command: str, env: dict, + try_to_fix_flush: bool = True +) -> subprocess.Process: + process = await subprocess.create_subprocess_shell( + cmd=command, + env=env | {"PYTHONUNBUFFERED": "1"} if try_to_fix_flush else env, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + + return process + + +async def stream_handler(stream: asyncio.streams.StreamReader, real_handler: Callable): + while True: + line = (await stream.readline()).decode().strip() + if not line: + return + await real_handler(line) diff --git a/neko_run_controller_asyncio/modules/default_handlers.py b/neko_run_controller_asyncio/modules/default_handlers.py new file mode 100644 index 0000000..63559ff --- /dev/null +++ b/neko_run_controller_asyncio/modules/default_handlers.py @@ -0,0 +1,2 @@ +async def log_handler(line: str): + print(line) diff --git a/neko_run_controller_asyncio/neko/__init__.py b/neko_run_controller_asyncio/neko/__init__.py new file mode 100644 index 0000000..90821d2 --- /dev/null +++ b/neko_run_controller_asyncio/neko/__init__.py @@ -0,0 +1,2 @@ +from .interfaces import RunControllerHandlersInterface +from .types import CommandRunner diff --git a/neko_run_controller_asyncio/neko/interfaces.py b/neko_run_controller_asyncio/neko/interfaces.py new file mode 100644 index 0000000..e59061b --- /dev/null +++ b/neko_run_controller_asyncio/neko/interfaces.py @@ -0,0 +1,23 @@ +from neko_run_controller_std.neko.types import LogsHandler +from typing import Type +import dataclasses + +from neko_run_controller_std.neko.interfaces import RunControllerHandlersInterface + + +@dataclasses.dataclass +class __HiddenStorage: + logs_handlers: list[Type[LogsHandler]] = dataclasses.field(default_factory=list) + + +_storage = __HiddenStorage() + + +class RRunControllerHandlersInterface(RunControllerHandlersInterface): + @staticmethod + def add_logs_handler(handler: Type[LogsHandler]): + _storage.logs_handlers.append(handler) + + +RunControllerHandlersInterface = RRunControllerHandlersInterface +__all__ = [RunControllerHandlersInterface] diff --git a/neko_run_controller_asyncio/neko/types.py b/neko_run_controller_asyncio/neko/types.py new file mode 100644 index 0000000..4551998 --- /dev/null +++ b/neko_run_controller_asyncio/neko/types.py @@ -0,0 +1,61 @@ +from neko_run_controller_std.neko.types import CommandRunner, LogsHandler +from ..modules.controller import run_process, stream_handler, kill +import os + +import asyncio + +from .interfaces import _storage + +import warnings + + +class RCommandRunner(CommandRunner): + def __init__(self, name: str, command: str, env: dict = None, chdir: str = None): + self.name = name + self.command = command + self.env = env + self.chdir = chdir + self.pid = None + + if not env: + self.env = dict(os.environ) + + async def __start(self): + __start_dir = os.getcwd() + if self.chdir: + os.chdir(self.chdir) + self.process = await run_process( + command=self.command, + env=self.env, + ) + + self.__log_tasks = list() + for handler_builder in _storage.logs_handlers: + handler = handler_builder(runner=self) + + self.__log_tasks.append( + asyncio.create_task(stream_handler(self.process.stdout, handler.stdout)) + ) + self.__log_tasks.append( + asyncio.create_task(stream_handler(self.process.stderr, handler.stderr)) + ) + + self.pid = self.process.pid + + async def wait(self): + await self.process.wait() + + async def start(self): + await self.__start() + + async def kill(self): + if not self.pid: + warnings.warn('Process is not started', RuntimeWarning) + return + [task.cancel() for task in self.__log_tasks] + kill(self.pid) + await self.process.wait() + + +CommandRunner = RCommandRunner +__all__ = [CommandRunner, LogsHandler] diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..a6f858e --- /dev/null +++ b/poetry.lock @@ -0,0 +1,47 @@ +# This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand. + +[[package]] +name = "neko-run-controller-std" +version = "0.1.0" +description = "" +optional = false +python-versions = "^3.11" +files = [] +develop = true + +[package.source] +type = "directory" +url = "../neko-run-controller-std" + +[[package]] +name = "psutil" +version = "5.9.8" +description = "Cross-platform lib for process and system monitoring in Python." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ + {file = "psutil-5.9.8-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:26bd09967ae00920df88e0352a91cff1a78f8d69b3ecabbfe733610c0af486c8"}, + {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:05806de88103b25903dff19bb6692bd2e714ccf9e668d050d144012055cbca73"}, + {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:611052c4bc70432ec770d5d54f64206aa7203a101ec273a0cd82418c86503bb7"}, + {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:50187900d73c1381ba1454cf40308c2bf6f34268518b3f36a9b663ca87e65e36"}, + {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:02615ed8c5ea222323408ceba16c60e99c3f91639b07da6373fb7e6539abc56d"}, + {file = "psutil-5.9.8-cp27-none-win32.whl", hash = "sha256:36f435891adb138ed3c9e58c6af3e2e6ca9ac2f365efe1f9cfef2794e6c93b4e"}, + {file = "psutil-5.9.8-cp27-none-win_amd64.whl", hash = "sha256:bd1184ceb3f87651a67b2708d4c3338e9b10c5df903f2e3776b62303b26cb631"}, + {file = "psutil-5.9.8-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:aee678c8720623dc456fa20659af736241f575d79429a0e5e9cf88ae0605cc81"}, + {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cb6403ce6d8e047495a701dc7c5bd788add903f8986d523e3e20b98b733e421"}, + {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d06016f7f8625a1825ba3732081d77c94589dca78b7a3fc072194851e88461a4"}, + {file = "psutil-5.9.8-cp36-cp36m-win32.whl", hash = "sha256:7d79560ad97af658a0f6adfef8b834b53f64746d45b403f225b85c5c2c140eee"}, + {file = "psutil-5.9.8-cp36-cp36m-win_amd64.whl", hash = "sha256:27cc40c3493bb10de1be4b3f07cae4c010ce715290a5be22b98493509c6299e2"}, + {file = "psutil-5.9.8-cp37-abi3-win32.whl", hash = "sha256:bc56c2a1b0d15aa3eaa5a60c9f3f8e3e565303b465dbf57a1b730e7a2b9844e0"}, + {file = "psutil-5.9.8-cp37-abi3-win_amd64.whl", hash = "sha256:8db4c1b57507eef143a15a6884ca10f7c73876cdf5d51e713151c1236a0e68cf"}, + {file = "psutil-5.9.8-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:d16bbddf0693323b8c6123dd804100241da461e41d6e332fb0ba6058f630f8c8"}, + {file = "psutil-5.9.8.tar.gz", hash = "sha256:6be126e3225486dff286a8fb9a06246a5253f4c7c53b475ea5f5ac934e64194c"}, +] + +[package.extras] +test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.11" +content-hash = "6839aa94726d7cae42726a68d0335068e86fa469a418ca7286a8bba122496d4f" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..f85a131 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,19 @@ +[tool.poetry] +name = "neko-run-controller-asyncio" +version = "0.1.0" +description = "" +authors = ["hhh "] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.11" +psutil = "^5.9.8" + + +[tool.poetry.group.neko.dependencies] +neko-run-controller-std = {path = "../neko-run-controller-std", develop = true} + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api"