From 5cd6c6e0ca7450be132123063618bedec11fe62b Mon Sep 17 00:00:00 2001 From: Romanin <60302782+romanin-rf@users.noreply.github.com> Date: Tue, 28 Nov 2023 16:23:42 +0200 Subject: [PATCH] Update 0.7.0 - Added installation of modules from a file `requirements.txt` in the folder of any plugin, if there is one - Added more intuitive logging --- .github/workflows/python-app.yml | 2 - plugins/RichDiscordStatus/requirements.txt | 1 + pyproject.toml | 2 +- seaplayer/plug/pipw.py | 18 +++++ seaplayer/plug/pluginloader.py | 77 ++++++++++++++++------ seaplayer/units.py | 3 +- 6 files changed, 78 insertions(+), 25 deletions(-) create mode 100644 seaplayer/plug/pipw.py diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 801b52c..17e3154 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -14,9 +14,7 @@ permissions: jobs: build: - runs-on: ubuntu-latest - steps: - uses: actions/checkout@v3 - name: Set up Python 3.10 diff --git a/plugins/RichDiscordStatus/requirements.txt b/plugins/RichDiscordStatus/requirements.txt index a0cfd77..e7b389e 100644 --- a/plugins/RichDiscordStatus/requirements.txt +++ b/plugins/RichDiscordStatus/requirements.txt @@ -1,2 +1,3 @@ +asyncio nest-asyncio pypresence \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 9e4b164..ebae75d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "SeaPlayer" -version = "0.6.0" +version = "0.7.0" description = "SeaPlayer is a player that works in the terminal." repository = "https://github.com/romanin-rf/SeaPlayer" authors = ["Romanin "] diff --git a/seaplayer/plug/pipw.py b/seaplayer/plug/pipw.py new file mode 100644 index 0000000..273c139 --- /dev/null +++ b/seaplayer/plug/pipw.py @@ -0,0 +1,18 @@ +import sys +import subprocess +from typing import Tuple + +# ! Main Class +class PIPManager: + def __init__(self) -> None: + self.python_path = sys.executable + + def __call__(self, *args: str) -> Tuple[int, str]: + return subprocess.getstatusoutput(" ".join([self.python_path, "-m", *args])) + + def install(self, *args: str) -> bool: + code, output = self.__call__("install", *args) + return code == 0 + +# ! Initial +pip = PIPManager() \ No newline at end of file diff --git a/seaplayer/plug/pluginloader.py b/seaplayer/plug/pluginloader.py index c3c841b..5514a73 100644 --- a/seaplayer/plug/pluginloader.py +++ b/seaplayer/plug/pluginloader.py @@ -10,13 +10,15 @@ from types import ModuleType from typing import Optional, Dict, Union, Any, List, Tuple, Type # > Local Import's +from .pipw import pip from .pluginbase import PluginInfo, PluginBase from ..functions import aiter from ..units import ( PLUGINS_DIRPATH, PLUGINS_CONFIG_PATH, GLOB_PLUGINS_INFO_SEARCH, - GLOB_PLUGINS_INIT_SEARCH + GLOB_PLUGINS_INIT_SEARCH, + GLOB_PLUGINS_DEPS_SEARCH ) # ! Types @@ -59,7 +61,7 @@ class PluginLoaderConfigManager: @staticmethod def dump(path: str, data: PluginLoaderConfigModel) -> None: with open(path, 'w') as file: - file.write(data.json()) + file.write(data.model_dump_json()) @staticmethod def load(path: str, default_data: Dict[str, Any]) -> PluginLoaderConfigModel: @@ -121,7 +123,7 @@ def enable_plugin_by_name_id(self, name_id: str) -> None: # ! Plugin Loader Class class PluginLoader: __title__: str = "PluginLoader" - __version__: str = "0.1.5" + __version__: str = "0.2.0" __author__: str = "Romanin" __email__: str = "semina054@gmail.com" @@ -153,24 +155,48 @@ def __init__( @staticmethod async def aio_search_plugins_paths(): - info_search, init_search = glob.glob(GLOB_PLUGINS_INFO_SEARCH), glob.glob(GLOB_PLUGINS_INIT_SEARCH) - async for info_path in aiter(info_search): - info_dirpath = os.path.dirname(info_path) - async for init_path in aiter(init_search): - init_dirpath = os.path.dirname(init_path) - if init_dirpath == info_dirpath: - yield info_path, init_path - await asyncio.sleep(0) + init_search, info_search, deps_search = \ + glob.glob(GLOB_PLUGINS_INIT_SEARCH), \ + glob.glob(GLOB_PLUGINS_INFO_SEARCH), \ + glob.glob(GLOB_PLUGINS_DEPS_SEARCH) + async for init_path in aiter(init_search): + _init_dirpath = os.path.dirname(init_path) + info_path, deps_path = None, None + async for _info_path in aiter(info_search): + _info_dirpath = os.path.dirname(_info_path) + if _info_dirpath == _init_dirpath: + info_path = _info_path + break + async for _deps_path in aiter(deps_search): + _deps_dirpath = os.path.dirname(_deps_path) + if _deps_dirpath == _init_dirpath: + deps_path = _deps_path + break + if info_path is not None: + yield init_path, info_path, deps_path + await asyncio.sleep(0) @staticmethod def search_plugins_paths(): - info_search, init_search = glob.glob(GLOB_PLUGINS_INFO_SEARCH), glob.glob(GLOB_PLUGINS_INIT_SEARCH) - for info_path in info_search: - info_dirpath = os.path.dirname(info_path) - for init_path in init_search: - init_dirpath = os.path.dirname(init_path) - if init_dirpath == info_dirpath: - yield info_path, init_path + init_search, info_search, deps_search = \ + glob.glob(GLOB_PLUGINS_INIT_SEARCH), \ + glob.glob(GLOB_PLUGINS_INFO_SEARCH), \ + glob.glob(GLOB_PLUGINS_DEPS_SEARCH) + for init_path in init_search: + _init_dirpath = os.path.dirname(init_path) + info_path, deps_path = None, None + for _info_path in info_search: + _info_dirpath = os.path.dirname(_info_path) + if _info_dirpath == _init_dirpath: + info_path = _info_path + break + for _deps_path in deps_search: + _deps_dirpath = os.path.dirname(_deps_path) + if _deps_dirpath == _init_dirpath: + deps_path = _deps_path + break + if info_path is not None: + yield init_path, info_path, deps_path @staticmethod def load_plugin_info(path: str) -> PluginInfo: @@ -182,22 +208,31 @@ def on_init(self) -> None: self.app.info(f"{self.__title__} [#00ffee]v{self.__version__}[/#00ffee] from {self.__author__} ({self.__email__})") plugins_paths = list(self.search_plugins_paths()) self.app.info(f"Found plugins : {repr([os.path.basename(os.path.dirname(i[0])) for i in plugins_paths])}") - for info_path, init_path in plugins_paths: + self.app.info(f"Initialization plugins...") + for init_path, info_path, deps_path in plugins_paths: info = None try: info = self.load_plugin_info(info_path) if not self.config.exists_plugin(info): self.config.add_plugin(info) + self.app.info(f"{info.name} ({repr(info.name_id)}) > New plugin added to config!") if self.config.is_enable_plugin(info): + self.app.info(f"{info.name} ({repr(info.name_id)}) > Plugin is [green]enabled[/green]!") + if deps_path is not None: + self.app.info(f"{info.name} ({repr(info.name_id)}) > Installing plugin dependencies...") + pip.install("-U", "-r", deps_path) + self.app.info(f"{info.name} ({repr(info.name_id)}) > Installed!") + self.app.info(f"{info.name} ({repr(info.name_id)}) > Importing in SeaPlayer...") plugin_module = load_module(init_path) plugin = plugin_from_module(self.app, self, info, plugin_module) + self.app.info(f"{info.name} ({repr(info.name_id)}) > Imported!") try: plugin.on_init() except: - self.app.error(f"Failed to do [green]`on_init`[/green] in: {plugin}") - + self.app.error(f"Failed to do [green]`on_init`[/green] in: {plugin.info}") self.on_plugins.append(plugin) else: + self.app.info(f"{info.name} ({repr(info.name_id)}) > Plugin is [red]disabled[/red]!") self.off_plugins.append(info) except Exception as e: self.error_plugins.append( (info_path, init_path) ) diff --git a/seaplayer/units.py b/seaplayer/units.py index f072312..7855bc4 100644 --- a/seaplayer/units.py +++ b/seaplayer/units.py @@ -6,7 +6,7 @@ # ! Metadata __title__ = "SeaPlayer" -__version__ = "0.5.8" +__version__ = "0.7.0" __author__ = "Romanin" __email__ = "semina054@gmail.com" __url__ = "https://github.com/romanin-rf/SeaPlayer" @@ -32,6 +32,7 @@ GLOB_PLUGINS_INFO_SEARCH = os.path.join(PLUGINS_DIRPATH, "*", "info.json") GLOB_PLUGINS_INIT_SEARCH = os.path.join(PLUGINS_DIRPATH, "*", "__init__.py") +GLOB_PLUGINS_DEPS_SEARCH = os.path.join(PLUGINS_DIRPATH, "*", "requirements.txt") # ! Assets Paths IMGPATH_IMAGE_NOT_FOUND = os.path.join(ASSETS_DIRPATH, "image-not-found.png")