7 Commits
v0.1 ... v0.4

Author SHA1 Message Date
François Dausseur
fffba77497 Merge branch 'main' of https://git.beafrancois.fr/Foue-opensource/pyappengine 2025-04-17 14:40:38 +02:00
François Dausseur
28057dddd6 Removed systemd dependency 2025-04-17 14:40:34 +02:00
François Dausseur
d46e3b9859 release 0.4 2025-02-25 12:41:28 +01:00
François Dausseur
c67c5e3e28 allow to stop the application from a module 2025-02-25 12:39:34 +01:00
François Dausseur
a45d975617 new release 2025-02-25 11:48:24 +01:00
François Dausseur
1ce6d83013 added the SIGINT capture. 2025-02-19 15:30:23 +01:00
François Dausseur
50439e59f5 added a help_module function and renamed some
variables to ease integration with
"cmd" module.
2024-04-26 19:44:25 +02:00
3 changed files with 76 additions and 33 deletions

View File

@@ -1 +1 @@
0.1 0.4

View File

@@ -15,9 +15,7 @@ classifiers = [
"License :: OSI Approved :: CeCILL-C", "License :: OSI Approved :: CeCILL-C",
"Operating System :: OS Independent", "Operating System :: OS Independent",
] ]
dependencies = [ dependencies = [ ]
"systemd-python",
]
dynamic = ["version"] dynamic = ["version"]
[project.urls] [project.urls]

View File

@@ -5,14 +5,14 @@ import sys
import traceback import traceback
from configparser import ConfigParser from configparser import ConfigParser
import logging import logging
from systemd import journal
import inspect import inspect
from enum import Enum, auto from enum import Enum, auto
import signal
from importlib import import_module from importlib import import_module
from threading import Thread, Lock from threading import Thread, Lock
import inspect import inspect
from threading import Thread from threading import Thread, Event
def is_number(s): def is_number(s):
@@ -54,7 +54,7 @@ class AppEngineException(Exception):
class Commands(Thread): class Commands(Thread):
defmod = None defmod = None
precmd = "cmd_" prefcmd = "cmd_"
def __init__(self, config: ConfigParser, log: logging.Handler) -> None: def __init__(self, config: ConfigParser, log: logging.Handler) -> None:
super().__init__() super().__init__()
@@ -62,6 +62,7 @@ class Commands(Thread):
self.cmods = {} self.cmods = {}
self.config = config self.config = config
self.log = log self.log = log
self.stop_all_event = None
self.stopped = False self.stopped = False
self.nickname = None self.nickname = None
if not (self.config is None): if not (self.config is None):
@@ -103,6 +104,12 @@ class Commands(Thread):
def stop(self): def stop(self):
self.stopped = True self.stopped = True
def free(self):
""" Virtual method used to clean resources for threaded Commands
when they are stopped.
"""
pass
def list_modules(self): def list_modules(self):
success = True success = True
ret = "List of modules:\n" ret = "List of modules:\n"
@@ -110,15 +117,24 @@ class Commands(Thread):
ret = ret + " " + module + "\n" ret = ret + " " + module + "\n"
return success, ret.strip() return success, ret.strip()
def help_module(self, module, *args):
if module in self.cmods.keys():
if len(args) > 0:
return self.cmods[module].cmd_help(args[0])
else:
return self.cmods[module].cmd_help()
else:
return "No module with this name"
def _execute_command(self, method: str, *args, **kwargs) -> tuple: def _execute_command(self, method: str, *args, **kwargs) -> tuple:
success = False success = False
ret = (AEErrs.INTERNAL_ERROR.value, "function not found") ret = (AEErrs.INTERNAL_ERROR.value, "function not found")
if hasattr(self, self.precmd + method) and inspect.ismethod( if hasattr(self, self.prefcmd + method) and inspect.ismethod(
getattr(self, self.precmd + method) getattr(self, self.prefcmd + method)
): ):
self.lock.acquire() self.lock.acquire()
try: try:
f = getattr(self, self.precmd + method) f = getattr(self, self.prefcmd + method)
ret = f(*args, **kwargs) ret = f(*args, **kwargs)
success = True success = True
self.log.info( self.log.info(
@@ -171,6 +187,10 @@ class Commands(Thread):
return self.cmods[module]._execute_command(method, *args, **kwargs) return self.cmods[module]._execute_command(method, *args, **kwargs)
def stop_all(self):
if self.stop_all_event is not None:
self.stop_all_event.set()
def cmd_help(self, *args, **kwargs): def cmd_help(self, *args, **kwargs):
"""Help of module commands. """Help of module commands.
Params: Params:
@@ -181,12 +201,12 @@ class Commands(Thread):
if len(args) == 0: if len(args) == 0:
cmds = inspect.getmembers(self, predicate=inspect.ismethod) cmds = inspect.getmembers(self, predicate=inspect.ismethod)
cmds = [x for x in cmds if x[0].startswith(self.precmd)] cmds = [x for x in cmds if x[0].startswith(self.prefcmd)]
for m in cmds: for m in cmds:
ret = ret + m[0][len(self.precmd):] + "\n" ret = ret + m[0][len(self.prefcmd):] + "\n"
else: else:
if isinstance(args[0], str): if isinstance(args[0], str):
cmd = self.precmd + args[0] cmd = self.prefcmd + args[0]
c = getattr(self, cmd, None) c = getattr(self, cmd, None)
if c: if c:
r = inspect.getdoc(c) r = inspect.getdoc(c)
@@ -202,20 +222,21 @@ class Commands(Thread):
class CommandsLoader: class CommandsLoader:
def __init__(self, config: ConfigParser, log: logging.Handler, modpath: str): def __init__(self, stop_event: Event, config: ConfigParser, log: logging.Handler, modpath: str):
self.config = config self.config = config
self.modpath = modpath self.modpath = modpath
sys.path.append(os.path.join(modpath)) sys.path.append(os.path.join(modpath))
Commands.defmod = self.config["general"].get("default") Commands.defmod = self.config["general"].get("default")
precmd = self.config["general"].get("methods_prefix") prefcmd = self.config["general"].get("methods_prefix")
if not precmd is None: if not prefcmd is None:
Commands.precmd = precmd Commands.prefcmd = prefcmd
self.precmds = "cmds_" self.prefcmds = "cmds_"
precmds = self.config["general"].get("modules_prefix") prefcmds = self.config["general"].get("modules_prefix")
if not precmds is None: if not prefcmds is None:
self.precmds = precmds self.prefcmds = prefcmds
self.log = log self.log = log
self.lock = Lock() self.lock = Lock()
self.stop_event = stop_event
self._load_commands() self._load_commands()
self._set_cmods() self._set_cmods()
@@ -224,7 +245,7 @@ class CommandsLoader:
with os.scandir(self.modpath) as it: with os.scandir(self.modpath) as it:
for entry in it: for entry in it:
if ( if (
entry.name.startswith(self.precmds) entry.name.startswith(self.prefcmds)
and entry.name.endswith(".py") and entry.name.endswith(".py")
and entry.is_file() and entry.is_file()
): ):
@@ -232,7 +253,7 @@ class CommandsLoader:
if not obj is None: if not obj is None:
nmod = obj.nickname nmod = obj.nickname
if nmod is None: if nmod is None:
nmod = (entry.name[:-3])[len(self.precmds):] nmod = (entry.name[:-3])[len(self.prefcmds):]
cmds.update({nmod: obj}) cmds.update({nmod: obj})
self.log.info('module "{}" loaded'.format(nmod)) self.log.info('module "{}" loaded'.format(nmod))
else: else:
@@ -251,8 +272,8 @@ class CommandsLoader:
members = inspect.getmembers(module, inspect.isclass) members = inspect.getmembers(module, inspect.isclass)
conf = None conf = None
if name[len(self.precmds):] in self.config.sections(): if name[len(self.prefcmds):] in self.config.sections():
conf = self.config[name[len(self.precmds):]] conf = self.config[name[len(self.prefcmds):]]
obj = None obj = None
for n, c in members: for n, c in members:
@@ -260,6 +281,7 @@ class CommandsLoader:
obj = c(conf, self.log) obj = c(conf, self.log)
obj.log = self.log obj.log = self.log
obj.lock = self.lock obj.lock = self.lock
obj.stop_all_event = self.stop_event
break break
return obj return obj
@@ -287,11 +309,21 @@ class CommandsLoader:
if v.threaded: if v.threaded:
v.start() v.start()
def stop(self):
for k, v in self.cmods.items():
if v.threaded:
v.stop()
def join(self): def join(self):
for k, v in self.cmods.items(): for k, v in self.cmods.items():
if v.threaded: if v.threaded:
v.join() v.join()
def free(self):
for k, v in self.cmods.items():
if v.threaded:
v.free()
class AppEngine: class AppEngine:
def __init__( def __init__(
@@ -301,16 +333,25 @@ class AppEngine:
log_file: str = "", log_file: str = "",
debug: bool = False, debug: bool = False,
) -> None: ) -> None:
self.cl = None
self.app_name = app_name self.app_name = app_name
self.conf = ConfigParser() self.conf = ConfigParser()
if conf_file != "": if conf_file != "":
self.parse_config(conf_file) self.parse_config(conf_file)
self.def_log(log_file) self.def_log(log_file)
signal.signal(signal.SIGINT, self.signal_handler)
if debug: if debug:
self.log.setLevel(logging.DEBUG) self.log.setLevel(logging.DEBUG)
else: else:
self.log.setLevel(logging.WARNING) self.log.setLevel(logging.WARNING)
# Mechanism to stop the application
self.stop_event = Event()
self.stop_thread = Thread(target=self.wait_stop, args=(self.stop_event,))
self.stop_thread.start()
def signal_handler(self, sig, frame):
print("\nExiting.")
self.stop()
def parse_config(self, cf: str): def parse_config(self, cf: str):
if os.path.exists(cf) and os.path.isfile(cf): if os.path.exists(cf) and os.path.isfile(cf):
@@ -333,13 +374,17 @@ class AppEngine:
if is_writeable: if is_writeable:
self.log.addHandler(logging.FileHandler(fname)) self.log.addHandler(logging.FileHandler(fname))
else: else:
self.log.addHandler(journal.JournalHandler())
self.log.error('No write permissions: "{}"'.format(fname)) self.log.error('No write permissions: "{}"'.format(fname))
else:
self.log.addHandler(journal.JournalHandler())
def exec(self, modpath: str = ""): def exec(self, modpath: str = ""):
cl = CommandsLoader(self.conf, self.log, modpath) self.cl = CommandsLoader(self.stop_event, self.conf, self.log, modpath)
cl.start() self.cl.start()
cl.join() self.cl.join()
self.cl.free()
def wait_stop(self, evnt):
evnt.wait()
self.cl.stop()
def stop(self):
self.stop_event.set()