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",
"Operating System :: OS Independent",
]
dependencies = [
"systemd-python",
]
dependencies = [ ]
dynamic = ["version"]
[project.urls]

View File

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