10 Commits
v0.3 ... v0.6

Author SHA1 Message Date
François Dausseur
5a60e47c12 rev 0.6 2025-10-28 11:48:17 +01:00
François Dausseur
355915e9c1 Merge branch 'main' of https://git.beafrancois.fr/Foue-opensource/pyappengine 2025-10-28 11:37:14 +01:00
François Dausseur
0da36439a6 management of help functions improved. 2025-10-28 11:37:10 +01:00
François Dausseur
3d402db2c2 free not limited to threaded modules. modified the modules dependencies management. 2025-09-20 16:33:21 +02:00
François Dausseur
de04b2b3b9 robustness. 2025-04-30 17:39:35 +02:00
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
3 changed files with 62 additions and 22 deletions

View File

@@ -1 +1 @@
0.2 0.6

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,7 +5,6 @@ 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 import signal
@@ -13,7 +12,7 @@ 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):
@@ -49,6 +48,8 @@ class AppEngineException(Exception):
def __init__(self, error: AEErrs, mesg=None) -> None: def __init__(self, error: AEErrs, mesg=None) -> None:
if mesg is None: if mesg is None:
self.mesg = str(error) self.mesg = str(error)
else:
self.mesg = mesg
super().__init__(self.mesg) super().__init__(self.mesg)
self.value = error.value self.value = error.value
@@ -63,6 +64,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):
@@ -105,8 +107,8 @@ class Commands(Thread):
self.stopped = True self.stopped = True
def free(self): def free(self):
""" Virtual method used to clean resources for threaded Commands """ Virtual method used to clean resources for all Commands
when they are stopped. when the application is exited.
""" """
pass pass
@@ -174,7 +176,28 @@ class Commands(Thread):
if module == "": if module == "":
if method == 'help': if method == 'help':
return self.list_modules() # No argument was given to the help function
if len(args) == 0 and len(kwargs) == 0:
msg = "Type 'help <module>' to have the list of module's functions.\n"
msg += "Type 'help <module>.<function>' to documentation of a specific function.\n"
success, ret = self.list_modules()
return success, msg + ret
else:
# 1 argument has been provided
if len(args) > 0:
arg = args[0]
else:
arg = list(kwargs.keys())[0]
spl = arg.split(".", 1)
# help <module>
if len(spl) == 1:
module = arg
# help <module>.<method>
else:
module = spl[0]
args = [spl[1]]
kwargs = {}
else: else:
module = self.defmod module = self.defmod
@@ -187,6 +210,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:
@@ -218,7 +245,7 @@ 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))
@@ -232,6 +259,7 @@ class CommandsLoader:
self.prefcmds = prefcmds 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()
@@ -273,9 +301,14 @@ class CommandsLoader:
obj = None obj = None
for n, c in members: for n, c in members:
if issubclass(c, Commands) and (n != "Commands"): if issubclass(c, Commands) and (n != "Commands"):
try:
obj = c(conf, self.log) obj = c(conf, self.log)
except:
self.log.error(f"The object '{c.__name__}' could not be instantiated.")
continue
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,9 +320,15 @@ class CommandsLoader:
def _load_dependencies(self): def _load_dependencies(self):
for k, v in self.cmods.items(): for k, v in self.cmods.items():
if hasattr(v, "dependencies"): if hasattr(v, "dependencies"):
for p in v.dependencies: deps = v.dependencies
if p in self.cmods.keys(): # dependencies can be a list or dictionary
setattr(v, p, self.cmods[p]) if isinstance(v.dependencies, list):
deps = {}
for d in v.dependencies:
deps[d] = d
for p, pv in deps.items():
if pv in self.cmods.keys():
setattr(v, p, self.cmods[pv])
else: else:
self.log.error( self.log.error(
'Dependency "{}" of module "{}" could not be satisfied'.format( 'Dependency "{}" of module "{}" could not be satisfied'.format(
@@ -315,7 +354,6 @@ class CommandsLoader:
def free(self): def free(self):
for k, v in self.cmods.items(): for k, v in self.cmods.items():
if v.threaded:
v.free() v.free()
@@ -338,6 +376,10 @@ class AppEngine:
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): def signal_handler(self, sig, frame):
print("\nExiting.") print("\nExiting.")
@@ -364,17 +406,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 = ""):
self.cl = CommandsLoader(self.conf, self.log, modpath) self.cl = CommandsLoader(self.stop_event, self.conf, self.log, modpath)
self.cl.start() self.cl.start()
self.cl.join() self.cl.join()
self.cl.free() self.cl.free()
def stop(self): def wait_stop(self, evnt):
evnt.wait()
self.cl.stop() self.cl.stop()
def stop(self):
self.stop_event.set()