From af992f7721b1ffa0e3069b902c48214bdab3182b Mon Sep 17 00:00:00 2001 From: adator <85586985+adator85@users.noreply.github.com> Date: Tue, 18 Nov 2025 13:34:03 +0100 Subject: [PATCH] last changes for asyncio --- core/__init__.py | 1 + core/base.py | 30 ++++++++++++- core/classes/interfaces/imodule.py | 36 ++++++++-------- core/classes/modules/settings.py | 4 ++ core/context.py | 69 ++++++++++++++++++++++++++++++ core/irc.py | 12 +++--- core/module.py | 14 +++--- defender.py | 14 ------ mods/defender/mod_defender.py | 8 +++- mods/test/mod_test.py | 28 +++++++++--- 10 files changed, 163 insertions(+), 53 deletions(-) create mode 100644 core/context.py diff --git a/core/__init__.py b/core/__init__.py index e69de29..d87fb0c 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -0,0 +1 @@ +__version__ = '6.3' \ No newline at end of file diff --git a/core/base.py b/core/base.py index fbb404a..474050b 100644 --- a/core/base.py +++ b/core/base.py @@ -1,3 +1,4 @@ +import asyncio import os import re import json @@ -8,7 +9,7 @@ import ipaddress import ast import requests from dataclasses import fields -from typing import Any, Optional, TYPE_CHECKING +from typing import Any, Callable, Iterable, Optional, TYPE_CHECKING from base64 import b64decode, b64encode from sqlalchemy import create_engine, Engine, Connection, CursorResult from sqlalchemy.sql import text @@ -35,6 +36,9 @@ class Base: # Liste des threads en cours self.running_threads: list[threading.Thread] = self.Settings.RUNNING_THREADS + # List of all async tasks + self.running_asynctasks: list[asyncio.Task] = self.Settings.RUNNING_ASYNCTASKS + # Les sockets ouvert self.running_sockets: list[socket.socket] = self.Settings.RUNNING_SOCKETS @@ -358,6 +362,30 @@ class Base: except Exception as err: self.logs.error(err, exc_info=True) + def create_asynctask(self, func: Callable, *, async_name: str = None, run_once: bool = False) -> asyncio.Task: + """Create a new asynchrone and store it into running_asynctasks variable + + Args: + func (Callable): The function you want to call in asynchrone way + async_name (str, optional): The task name. Defaults to None. + run_once (bool, optional): If true the task will be run once. Defaults to False. + + Returns: + asyncio.Task: The Task + """ + name = func.__name__ if async_name is None else async_name + + if run_once: + for task in asyncio.all_tasks(): + if task.get_name().lower() == async_name.lower(): + return None + + task = asyncio.create_task(func, name=name) + self.running_asynctasks.append(task) + + self.logs.debug(f"++ New asynchrone task created as: {task.get_name()}") + return task + def is_thread_alive(self, thread_name: str) -> bool: """Check if the thread is still running! using the is_alive method of Threads. diff --git a/core/classes/interfaces/imodule.py b/core/classes/interfaces/imodule.py index 95a8e47..fa22f52 100644 --- a/core/classes/interfaces/imodule.py +++ b/core/classes/interfaces/imodule.py @@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, Optional from dataclasses import dataclass if TYPE_CHECKING: - from core.irc import Irc + from core.loader import Loader class IModule(ABC): @@ -13,19 +13,19 @@ class IModule(ABC): """The Model containing the module parameters """ - def __init__(self, uplink: 'Irc') -> None: + def __init__(self, uplink: 'Loader') -> None: # Module name (Mandatory) self.module_name = 'mod_' + str(self.__class__.__name__).lower() # Add Irc Object to the module (Mandatory) - self.Irc = uplink + self.Irc = uplink.Irc # Add Loader object to the module (Mandatory) - self.Loader = uplink.Loader + self.Loader = uplink # Add Protocol to the module (Mandatory) - self.Protocol = uplink.Protocol + self.Protocol = uplink.Irc.Protocol # Add Global Configuration to the module (Mandatory) self.Config = uplink.Config @@ -40,7 +40,7 @@ class IModule(ABC): self.MainUtils = uplink.Utils # Add logs object to the module (Mandatory) - self.Logs = uplink.Loader.Logs + self.Logs = uplink.Logs # Add User object to the module (Mandatory) self.User = uplink.User @@ -57,19 +57,23 @@ class IModule(ABC): # Add Reputation object to the module (Optional) self.Reputation = uplink.Reputation - # Load the child classes + # Log the module + self.Logs.debug(f'Loading Module {self.module_name} ...') + + def init(self) -> None: self.load() - - # Inspect child classes self.inspect_class() - self.create_tables() # Sync the configuration with core configuration (Mandatory) - uplink.Base.db_sync_core_config(self.module_name, self.ModConfig) + self.Base.db_sync_core_config(self.module_name, self.ModConfig) + return None - # Log the module - self.Logs.debug(f'Loading Module {self.module_name} ...') + def inspect_class(self): + if not hasattr(self, 'ModConfig'): + raise AttributeError("The Module must init ModConfig attribute in the load method!") + if not hasattr(self, 'MOD_HEADER'): + raise NotImplementedError(f"You must declare the header of the module in {self.__class__.__name__}!") def update_configuration(self, param_key: str, param_value: str) -> None: """Update the local and core configuration @@ -80,12 +84,6 @@ class IModule(ABC): """ self.Base.db_update_core_config(self.module_name, self.ModConfig, param_key, param_value) - def inspect_class(self): - if not hasattr(self, 'ModConfig'): - raise AttributeError("The Module must init ModConfig attribute in the load method!") - if not hasattr(self, 'MOD_HEADER'): - raise NotImplementedError(f"You must declare the header of the module in {self.__class__.__name__}!") - @abstractmethod def create_tables(self) -> None: """Method that will create the database if it does not exist. diff --git a/core/classes/modules/settings.py b/core/classes/modules/settings.py index 3026359..216920e 100644 --- a/core/classes/modules/settings.py +++ b/core/classes/modules/settings.py @@ -1,7 +1,9 @@ """This class should never be reloaded. """ +import asyncio from logging import Logger from threading import Timer, Thread, RLock +from asyncio.locks import Lock from socket import socket from typing import Any, Optional, TYPE_CHECKING from core.definition import MSModule, MAdmin @@ -17,9 +19,11 @@ class Settings: RUNNING_TIMERS: list[Timer] = [] RUNNING_THREADS: list[Thread] = [] + RUNNING_ASYNCTASKS: list[asyncio.Task] = [] RUNNING_SOCKETS: list[socket] = [] PERIODIC_FUNC: dict[str, Any] = {} LOCK: RLock = RLock() + AILOCK: Lock = Lock() CONSOLE: bool = False diff --git a/core/context.py b/core/context.py new file mode 100644 index 0000000..c8f4006 --- /dev/null +++ b/core/context.py @@ -0,0 +1,69 @@ +from logging import Logger +from core.classes.modules.settings import global_settings +from core.classes.modules import translation, user, admin, client, channel, reputation, settings, sasl +import core.logs as logs +import core.definition as df +import core.utils as utils +import core.base as base_mod +import core.module as module_mod +import core.classes.modules.commands as commands_mod +import core.classes.modules.config as conf_mod +import core.classes.modules.rpc.rpc as rpc_mod +import core.irc as irc +import core.classes.protocols.factory as factory + +class IrcContext: + + def ctx_modules(self) -> None: + self.Definition: df = df + self.ConfModule: conf_mod = conf_mod + self.BaseModule: base_mod = base_mod + self.CommandModule: commands_mod = commands_mod + self.LoggingModule: logs = logs + self.RpcServerModule: rpc_mod = rpc_mod + self.Utils: utils = utils + + def ctx_system(self) -> None: + self.Settings: settings.Settings = global_settings + self.Settings.global_lang = self.Config.LANG if self.Config.LANG else "EN" + + self.ServiceLogging: logs.ServiceLogging = self.LoggingModule.ServiceLogging() + self.Logs: Logger = self.ServiceLogging.get_logger() + + self.Config: df.MConfig = self.ConfModule.Configuration(self.Logs, self.ServiceLogging).configuration_model + + self.Settings.global_logger = self.Logs + + self.Translation: translation.Translation = translation.Translation(self) + + self.Settings.global_translation = self.Translation.get_translation() + + self.Base: base_mod.Base = self.BaseModule.Base(self) + + self.User: user.User = user.User(self) + + self.Settings.global_user = self.User + + self.Client: client.Client = client.Client(self) + + self.Admin: admin.Admin = admin.Admin(self) + + self.Channel: channel.Channel = channel.Channel(self) + + self.Reputation: reputation.Reputation = reputation.Reputation(self) + + self.Commands: commands_mod.Command = commands_mod.Command(self) + + self.ModuleUtils: module_mod.Module = module_mod.Module(self) + + self.Sasl: sasl.Sasl = sasl.Sasl(self) + + self.Irc: irc.Irc = irc.Irc(self) + + self.PFactory: factory.ProtocolFactorty = factory.ProtocolFactorty(self.Irc) + + self.RpcServer: rpc_mod.JSONRPCServer = rpc_mod.JSONRPCServer(self) + + self.Base.init() + + self.Logs.debug(self.Utils.tr("Loader %s success", __name__)) diff --git a/core/irc.py b/core/irc.py index 89b1811..5469a2b 100644 --- a/core/irc.py +++ b/core/irc.py @@ -582,7 +582,7 @@ class Irc: # Envoyer la commande aux classes dynamiquement chargées if command != 'notallowed': for module in self.ModuleUtils.DB_MODULES: - module.class_instance.hcmds(user, channel, cmd, fullcmd) + await module.class_instance.hcmds(user, channel, cmd, fullcmd) match command: @@ -1048,11 +1048,11 @@ class Irc: try: # Load a module ex: .load mod_defender if len(cmd) < 2: - self.Protocol.send_notice(dnickname, fromuser, tr("Syntax. /msg %s %s MODULE_NAME", dnickname, command.upper())) + await self.Protocol.send_notice(dnickname, fromuser, tr("Syntax. /msg %s %s MODULE_NAME", dnickname, command.upper())) return None mod_name = str(cmd[1]) - self.ModuleUtils.load_one_module(self, mod_name, fromuser) + await self.ModuleUtils.load_one_module(self, mod_name, fromuser) return None except KeyError as ke: self.Logs.error(f"Key Error: {ke} - list recieved: {cmd}") @@ -1077,15 +1077,15 @@ class Irc: try: # ==> mod_defender if len(cmd) < 2: - self.Protocol.send_notice(dnickname, fromuser, tr("Syntax. /msg %s %s MODULE_NAME", dnickname, command.upper())) + await self.Protocol.send_notice(dnickname, fromuser, tr("Syntax. /msg %s %s MODULE_NAME", dnickname, command.upper())) return None module_name = str(cmd[1]).lower() - self.ModuleUtils.reload_one_module(self, module_name, fromuser) + await self.ModuleUtils.reload_one_module(self, module_name, fromuser) return None except Exception as e: self.Logs.error(f"Something went wrong with a module you want to reload: {e}") - self.Protocol.send_priv_msg( + await self.Protocol.send_priv_msg( nick_from=dnickname, msg=f"Something went wrong with the module: {e}", channel=dchanlog diff --git a/core/module.py b/core/module.py index bdd6c2b..df684dd 100644 --- a/core/module.py +++ b/core/module.py @@ -110,7 +110,7 @@ class Module: try: loaded_module = importlib.import_module(f'mods.{module_folder}.{module_name}') my_class = getattr(loaded_module, class_name, None) # Récuperer le nom de classe - create_instance_of_the_class = my_class(uplink) # Créer une nouvelle instance de la classe + create_instance_of_the_class = my_class(uplink.Loader) # Créer une nouvelle instance de la classe self.create_module_header(create_instance_of_the_class.MOD_HEADER) except AttributeError as attr: red = uplink.Config.COLORS.red @@ -151,7 +151,7 @@ class Module: def load_all_modules(self) -> bool: ... - def reload_one_module(self, uplink: 'Irc', module_name: str, nickname: str) -> bool: + async def reload_one_module(self, uplink: 'Irc', module_name: str, nickname: str) -> bool: """Reloading one module and insert it into the model as well as the database Args: @@ -172,7 +172,7 @@ class Module: self.delete_module_header(module_model.class_instance.MOD_HEADER['name']) module_model.class_instance.unload() else: - uplink.Protocol.send_priv_msg( + await uplink.Protocol.send_priv_msg( nick_from=self.__Config.SERVICE_NICKNAME, msg=f"[ {red}RELOAD MODULE ERROR{nogc} ] Module [{module_folder}.{module_name}] hasn't been reloaded! You must use {self.__Config.SERVICE_PREFIX}load {module_name}", channel=self.__Config.SERVICE_CHANLOG @@ -186,13 +186,13 @@ class Module: the_module = sys.modules[f'mods.{module_folder}.{module_name}'] importlib.reload(the_module) my_class = getattr(the_module, class_name, None) - new_instance = my_class(uplink) + new_instance = my_class(uplink.Loader) self.create_module_header(new_instance.MOD_HEADER) module_model.class_instance = new_instance # Créer le module dans la base de données self.db_register_module(module_name, nickname) - uplink.Protocol.send_priv_msg( + await uplink.Protocol.send_priv_msg( nick_from=self.__Config.SERVICE_NICKNAME, msg=f"Module [{module_folder}.{module_name}] has been reloaded!", channel=self.__Config.SERVICE_CHANLOG @@ -202,7 +202,7 @@ class Module: else: # Module is not loaded! Nothing to reload self.__Logs.debug(f"[RELOAD MODULE ERROR] [{module_folder}.{module_name}] is not loaded! You must use {self.__Config.SERVICE_PREFIX}load {module_name}") - uplink.Protocol.send_priv_msg( + await uplink.Protocol.send_priv_msg( nick_from=self.__Config.SERVICE_NICKNAME, msg=f"[ {red}RELOAD MODULE ERROR{nogc} ] Module [{module_folder}.{module_name}] is not loaded! You must use {self.__Config.SERVICE_PREFIX}load {module_name}", channel=self.__Config.SERVICE_CHANLOG @@ -211,7 +211,7 @@ class Module: except (TypeError, AttributeError, KeyError, Exception) as err: self.__Logs.error(f"[RELOAD MODULE ERROR]: {err}", exc_info=True) - uplink.Protocol.send_priv_msg( + await uplink.Protocol.send_priv_msg( nick_from=self.__Config.SERVICE_NICKNAME, msg=f"[RELOAD MODULE ERROR]: {err}", channel=self.__Config.SERVICE_CHANLOG diff --git a/defender.py b/defender.py index 199080f..9b03d89 100644 --- a/defender.py +++ b/defender.py @@ -17,17 +17,3 @@ async def main(): if __name__ == "__main__": asyncio.run(main(), debug=True) - - - -# try: -# # install.update_packages() -# from core.loader import Loader -# loader = Loader() -# loader.Irc.init_irc() - -# except AssertionError as ae: -# print(f'Assertion Error -> {ae}') -# except KeyboardInterrupt as k: -# # ircInstance.Base.execute_periodic_action() -# ... \ No newline at end of file diff --git a/mods/defender/mod_defender.py b/mods/defender/mod_defender.py index 9065325..2a5476c 100644 --- a/mods/defender/mod_defender.py +++ b/mods/defender/mod_defender.py @@ -1,11 +1,14 @@ from dataclasses import dataclass -from typing import Any +from typing import Any, TYPE_CHECKING from core.classes.interfaces.imodule import IModule import mods.defender.schemas as schemas import mods.defender.utils as utils import mods.defender.threads as thds from core.utils import tr +if TYPE_CHECKING: + from core.loader import Loader + class Defender(IModule): @dataclass @@ -20,6 +23,9 @@ class Defender(IModule): 'core_version':'Defender-6' } + def __init__(self, context: 'Loader') -> None: + self.ctx = context + def create_tables(self) -> None: """Methode qui va créer la base de donnée si elle n'existe pas. Une Session unique pour cette classe sera crée, qui sera utilisé dans cette classe / module diff --git a/mods/test/mod_test.py b/mods/test/mod_test.py index 480d763..0425279 100644 --- a/mods/test/mod_test.py +++ b/mods/test/mod_test.py @@ -1,7 +1,11 @@ -from typing import Any +import asyncio +from typing import Any, TYPE_CHECKING from core.classes.interfaces.imodule import IModule from dataclasses import dataclass +if TYPE_CHECKING: + from core.loader import Loader + class Test(IModule): @dataclass @@ -22,6 +26,10 @@ class Test(IModule): } """Module Header (Mandatory)""" + def __init__(self, uplink: 'Loader'): + super().__init__(uplink) + self.init() + def create_tables(self) -> None: """Methode qui va créer la base de donnée si elle n'existe pas. Une Session unique pour cette classe sera crée, qui sera utilisé dans cette classe / module @@ -48,6 +56,7 @@ class Test(IModule): # Create module commands (Mandatory) self.Irc.build_command(0, self.module_name, 'test-command', 'Execute a test command') + self.Irc.build_command(0, self.module_name, 'asyncio', 'Create a new asynchron task!') self.Irc.build_command(1, self.module_name, 'test_level_1', 'Execute a level 1 test command') self.Irc.build_command(2, self.module_name, 'test_level_2', 'Execute a level 2 test command') self.Irc.build_command(3, self.module_name, 'test_level_3', 'Execute a level 3 test command') @@ -72,7 +81,13 @@ class Test(IModule): except Exception as err: self.Logs.error(f"General Error: {err}") - def hcmds(self, user: str, channel: Any, cmd: list, fullcmd: list = []) -> None: + async def asyncio_func(self) -> None: + self.Logs.debug(f"Starting async method in a task: {self.__class__.__name__}") + await asyncio.sleep(2) + await asyncio.sleep(3) + self.Logs.debug(f"End of the task: {self.__class__.__name__}") + + async def hcmds(self, user: str, channel: Any, cmd: list, fullcmd: list = []) -> None: """All messages coming from the user commands (Mandatory) Args: @@ -90,14 +105,17 @@ class Test(IModule): dnickname = self.Config.SERVICE_NICKNAME match command: + + case 'asyncio': + task = self.Base.create_asynctask(self.asyncio_func()) case 'test-command': try: - self.Protocol.send_notice(nick_from=dnickname, nick_to=u.nickname, msg="This is a notice to the sender ...") - self.Protocol.send_priv_msg(nick_from=dnickname, msg=f"This is private message to the sender ...", nick_to=u.nickname) + await self.Protocol.send_notice(nick_from=dnickname, nick_to=u.nickname, msg="This is a notice to the sender ...") + await self.Protocol.send_priv_msg(nick_from=dnickname, msg=f"This is private message to the sender ...", nick_to=u.nickname) if c is not None: - self.Protocol.send_priv_msg(nick_from=dnickname, msg=f"This is private message to the sender ...", channel=c.name) + await self.Protocol.send_priv_msg(nick_from=dnickname, msg=f"This is private message to the sender ...", channel=c.name) # How to update your module configuration self.update_configuration('param_exemple2', 7)