diff --git a/examples/example1/example1.py b/examples/example1/example1.py index 1148565fcbc1e674e76384ec7938f5b1ae77f253..24e023c1f7553deffee31cabed776b84175bf9fd 100644 --- a/examples/example1/example1.py +++ b/examples/example1/example1.py @@ -22,7 +22,7 @@ class Form(QDialog): def __init__(self, parent=None): super().__init__(parent) - child_con = ChildControl(self, enable_internal_logging=True) + child_con = ChildControl(self, internal_logging=True) child_con.call_without_mp_finished.connect(self.updateUI) child_con.call_without_mp2_changed.connect(self.updateUI2) diff --git a/examples/example1/mp_process.py b/examples/example1/mp_process.py index 4d93f4f371da9d03be824bba74a2aaf9497e0d99..fd55ca891b640517b8e34ab7948f92281eea8a1f 100644 --- a/examples/example1/mp_process.py +++ b/examples/example1/mp_process.py @@ -47,9 +47,9 @@ class ChildControl(cmp.CProcessControl, Sceleton): call_without_mp_finished = Signal(int) call_without_mp2_changed = Signal(int, int, int) - def __init__(self, parent, enable_internal_logging): - super().__init__(parent, enable_internal_logging=enable_internal_logging) - self.register_child_process(ChildProc(self.state_queue, self.cmd_queue, enable_interal_logging=enable_internal_logging)) + def __init__(self, parent, internal_logging): + super().__init__(parent, internal_logging=internal_logging) + self.register_child_process(ChildProc(self.state_queue, self.cmd_queue, enable_interal_logging=internal_logging)) @cmp.CProcessControl.register_function() def call_without_mp(self, a, b, c=None): diff --git a/examples/example2/ChildProcess2.py b/examples/example2/ChildProcess2.py index 837a02c389c662aca5d213644f3aefe9ed00479c..f66c3d47fcd791706b1480b6fed9bcb34cde2a7f 100644 --- a/examples/example2/ChildProcess2.py +++ b/examples/example2/ChildProcess2.py @@ -6,8 +6,10 @@ import cmp class ChildProcess2(cmp.CProcess): - def __init__(self, state_queue, cmd_queue, enable_internal_logging): - super().__init__(state_queue, cmd_queue, enable_internal_logging=enable_internal_logging) + def __init__(self, state_queue, cmd_queue, kill_flag, internal_logging, internal_log_level): + super().__init__(state_queue, cmd_queue, kill_flag, + internal_logging=internal_logging, + internal_log_level=internal_log_level) self.logger = None def postrun_init(self): diff --git a/examples/example2/ChildProcessControl2.py b/examples/example2/ChildProcessControl2.py index b8ad92546e79f2977762a7952ef149055264f45c..6506870c2a1c4d7b1973189c759a8d84d4107316 100644 --- a/examples/example2/ChildProcessControl2.py +++ b/examples/example2/ChildProcessControl2.py @@ -8,12 +8,10 @@ class ChildProcessControl2(cmp.CProcessControl): call_without_mp_finished = Signal(int) #call_without_mp2_changed = Signal(int, int, int) - def __init__(self, parent, signal_class, enable_internal_logging): - super().__init__(parent, signal_class, enable_internal_logging=enable_internal_logging) - self.register_child_process(ChildProcess2( - self.state_queue, - self.cmd_queue, - enable_internal_logging=enable_internal_logging)) + def __init__(self, parent, signal_class, internal_logging, internal_logging_level): + super().__init__(parent, signal_class, + internal_logging=internal_logging, internal_logging_level=internal_logging_level) + self.register_child_process(ChildProcess2) @cmp.CProcessControl.register_function() def call_without_mp(self, a, b, c=None): diff --git a/examples/example2/example2.py b/examples/example2/example2.py index 822b1a7ae5272c3ce7792329604e58af4a659925..1dee498f8aab2649ffaa01f0bc5b1544f6b65182 100644 --- a/examples/example2/example2.py +++ b/examples/example2/example2.py @@ -4,6 +4,7 @@ Author(s): Christoph Schmidt <christoph.schmidt@tugraz.at> Created: 2023-10-19 12:35 Package Version: """ +import logging import signal import sys from multiprocessing import Process, Queue, Pipe @@ -30,7 +31,7 @@ class Form(QDialog): super().__init__(parent) mysignals = MySignals() - child_con = ChildProcessControl2(self, mysignals, enable_internal_logging=True) + child_con = ChildProcessControl2(self, mysignals, internal_logging=True, internal_logging_level=logging.DEBUG) mysignals.call_without_mp2_changed.connect(self.updateUI) diff --git a/examples/example3/ChildProcess3.py b/examples/example3/ChildProcess3.py index fbfaf6a1efee816b6e66b8b7deafc40804f20034..9d7a37ebc80d23a50ca0197e2d08cd885a49267f 100644 --- a/examples/example3/ChildProcess3.py +++ b/examples/example3/ChildProcess3.py @@ -2,19 +2,36 @@ import os import time import cmp +from cmp.CProperty import CProperty, Cache class ChildProcess3(cmp.CProcess): - def __init__(self, state_queue, cmd_queue, enable_internal_logging): - super().__init__(state_queue, cmd_queue, enable_internal_logging=enable_internal_logging) + def __init__(self, state_queue, cmd_queue, kill_flag, internal_logging, internal_log_level): + super().__init__(state_queue, cmd_queue, kill_flag, + internal_logging=internal_logging, + internal_log_level=internal_log_level) self.logger = None def postrun_init(self): self.logger, self.logger_h = self.create_new_logger(f"{self.__class__.__name__}-({os.getpid()})") + @cmp.CProcess.register_for_signal() def test_call(self, a): self.logger.info(f"{os.getpid()} -> test_call!") time.sleep(1) - return a \ No newline at end of file + self.test_call2 = 1 + return a + + #@CProperty + #def test_call2(self, value: int = 0): + # self.my_value = value + + @CProperty + def test_call2(self, value: int): + self.my_value = value + + @test_call2.setter(emit_to='bar') + def test_call2(self, value: int): + self.my_value = value \ No newline at end of file diff --git a/examples/example3/ChildProcessControl3.py b/examples/example3/ChildProcessControl3.py index b9c4e6c0bbceab88258298c66764ea3390ed01cf..936fa38c44df2c1b75187ba60e5a4dc192ac4874 100644 --- a/examples/example3/ChildProcessControl3.py +++ b/examples/example3/ChildProcessControl3.py @@ -6,13 +6,13 @@ from ChildProcess3 import ChildProcess3 class ChildProcessControl3(cmp.CProcessControl): mp_finished = Signal(int, name='mp_finished') + mp_finished_untriggered = Signal(int) - def __init__(self, parent, enable_internal_logging): - super().__init__(parent, enable_internal_logging=enable_internal_logging) - self.register_child_process(ChildProcess3( - self.state_queue, - self.cmd_queue, - enable_internal_logging=enable_internal_logging)) + def __init__(self, parent, internal_logging, internal_logging_level): + super().__init__(parent, + internal_logging=internal_logging, + internal_logging_level=internal_logging_level) + self.register_child_process(ChildProcess3) @cmp.CProcessControl.register_function(signal=mp_finished) def test_call(self, a): diff --git a/examples/example3/example3.py b/examples/example3/example3.py index 235c98927badfd86567b2a8d5d529820817942d5..8053c4823c325b1b6bd61f41b21ed59951a8fc74 100644 --- a/examples/example3/example3.py +++ b/examples/example3/example3.py @@ -4,6 +4,7 @@ Author(s): Christoph Schmidt <christoph.schmidt@tugraz.at> Created: 2023-10-19 12:35 Package Version: """ +import logging import signal import sys from multiprocessing import Process, Queue, Pipe @@ -23,7 +24,7 @@ class Form(QDialog): def __init__(self, parent=None): super().__init__(parent) - child_con = ChildProcessControl3(self, enable_internal_logging=True) + child_con = ChildProcessControl3(self, internal_logging=True, internal_logging_level=logging.DEBUG) child_con.mp_finished.connect(self.updateUI) diff --git a/src/cmp/CProcess.py b/src/cmp/CProcess.py index f09de52cd446fac796dd6f7dfe585d399b412820..9391edf1865c9b22631f0bb51246908d4225de88 100644 --- a/src/cmp/CProcess.py +++ b/src/cmp/CProcess.py @@ -10,10 +10,14 @@ import cmp class CProcess(Process): - def __init__(self, state_queue: Queue, cmd_queue: Queue, enable_internal_logging: bool = False): + def __init__(self, state_queue: Queue, cmd_queue: Queue, kill_flag, + internal_logging: bool = False, + internal_log_level=logging.DEBUG, + *args, **kwargs): Process.__init__(self) + self._internal_log_enabled = internal_logging + self._internal_log_level = internal_log_level - self._enable_internal_logging = enable_internal_logging self.logger = None self.logger_handler = None @@ -22,11 +26,11 @@ class CProcess(Process): self.cmd_queue = cmd_queue self.state_queue = state_queue - self._kill_flag = Value('i', 0) - - def register_kill_flag(self, kill_flag: Value): self._kill_flag = kill_flag + # ================================================================================================================== + # Logging + # ================================================================================================================== def create_new_logger(self, name: str) -> (logging.Logger, logging.Handler): _handler = logging.handlers.QueueHandler(self.state_queue) _logger = logging.getLogger(name) @@ -37,15 +41,36 @@ class CProcess(Process): _handler.setFormatter(formatter) return _logger, _handler - def enable_internal_logging(self, enable: bool): - self._enable_internal_logging = enable - if self._internal_logger is not None and enable is False: - self._internal_logger.disabled = True - #self._internal_logger.handlers = [] - elif self._internal_logger is not None and enable is True: - self._internal_logger.disabled = False - #self._internal_logger.handlers = [self._il_handler] + @property + def internal_log_enabled(self): + self._internal_logger.debug(f"internal_log_enabled: {not self._internal_logger.disabled}") + return not self._internal_logger.disabled + + @internal_log_enabled.setter + def internal_log_enabled(self, enable: bool) -> None: + """ + Enables or disables internal logging. If disabled, the internal logger will be disabled and no messages will be + emitted to the state queue. + :param enable: True to enable, False to disable + """ + self._internal_logger.disabled = not enable + + @property + def internal_log_level(self): + return self._internal_logger.level + + @internal_log_level.setter + def internal_log_level(self, level: int) -> None: + """ + Sets the internal logging level. + :param level: + :return: + """ + self._internal_logger.setLevel(level) + # ================================================================================================================== + # Process + # ================================================================================================================== def postrun_init(self): """ Dummy function for initializing e.g. loggers (some handlers are not pickable) @@ -54,12 +79,14 @@ class CProcess(Process): pass def run(self): - self.name = f"{self.name}/{os.getpid()}" + self.name = f"{os.getpid()}({self.name})" self.postrun_init() - self._internal_logger, self._il_handler = self.create_new_logger(f"{self.__class__.__name__}-Int({os.getpid()})") - self.logger, self.logger_handler = self.create_new_logger(f"{self.__class__.__name__}({os.getpid()})") - self.enable_internal_logging(self._enable_internal_logging) + self._internal_logger, self._il_handler = self.create_new_logger(f"(cmp) {self.name}") + self.internal_log_enabled = self._internal_log_enabled + self.internal_log_level = self._internal_log_level + + self.logger, self.logger_handler = self.create_new_logger(f"{os.getpid()}({self.__class__.__name__})") self._internal_logger.debug(f"Child process {self.__class__.__name__} started.") try: @@ -69,9 +96,8 @@ class CProcess(Process): except: continue if isinstance(cmd, cmp.CCommandRecord): - self._internal_logger.info(f"Received cmd: {cmd}") self._internal_logger.debug( - f"cmd: {cmd}, args: {cmd.args}, kwargs: {cmd.kwargs}, Signal to emit: {cmd.signal_name}") + f"Received cmd: {cmd}, args: {cmd.args}, kwargs: {cmd.kwargs}, Signal to emit: {cmd.signal_name}") try: cmd.execute(self) except Exception as e: @@ -89,28 +115,55 @@ class CProcess(Process): self._internal_logger.warning(f"Received Exception {e}! Exiting Process {os.getpid()}") def __del__(self): - # if self._internal_logger is not None: - # self._internal_logger.warning(f"Exiting Process") self.cmd_queue.close() self.state_queue.close() + def _put_result_to_queue(self, func_name, signal_name, res): + self._internal_logger.debug(f"{func_name} finished. Emitting signal {signal_name} in control class.") + result = cmp.CResultRecord(func_name, signal_name, res) + self.state_queue.put(result) + + @staticmethod - def register_for_signal(postfix='_finished'): + def register_for_signal(postfix='_finished', signal_name: str = None): _postfix = postfix.strip() if postfix is not None else None + _signal_name = signal_name.strip() if signal_name is not None else None + + def register(func): + def get_signature(self, *args, **kwargs): + func_name = f"{func.__name__}->{self.pid}" + + if _signal_name is not None: + kwargs['signal_name'] = _signal_name + if 'signal_name' in kwargs and kwargs['signal_name'] is not None: sign = kwargs.pop('signal_name') elif _postfix is not None: sign = f"{func.__name__}{_postfix}" self._internal_logger.debug(f"Constructing signal name for function '{func.__name__}': {sign}") else: - raise ValueError(f"Cannot register function '{func.__name__}' for signal. No signal name provided!") + raise ValueError(f"Cannot register function '{func_name}' for signal. No signal name provided!") res = func(self, *args, **kwargs) - self._internal_logger.debug(f"{func.__name__} finished. Emitting signal {sign} in control class.") - result = cmp.CResultRecord(sign, res) - self.state_queue.put(result) + self._put_result_to_queue(func_name, sign, res) return res return get_signature + return register + + @staticmethod + def setter(sigal_same: str = None): + def register(func): + def get_signature(self, *args, **kwargs): + func_name = f"{func.__name__}->{self.pid}" + res = func(self, *args, **kwargs) + self._internal_logger.debug(f"{func_name} finished. Emitting signal {sigal_same} in control class.") + result = cmp.CResultRecord(func_name, sigal_same, res) + self.state_queue.put(result) + return res + + return get_signature + + return register \ No newline at end of file diff --git a/src/cmp/CProcessControl.py b/src/cmp/CProcessControl.py index 576edc8a644e141e0c3712a36e3441d1f6fd6532..b5b83c1c40950b0a87fae5a8046cbcac8c7ccb24 100644 --- a/src/cmp/CProcessControl.py +++ b/src/cmp/CProcessControl.py @@ -5,6 +5,7 @@ import re import signal import time from multiprocessing import Queue, Process, Value +from typing import Type from PySide6.QtCore import QObject, QThreadPool, Signal from PySide6.QtGui import QWindow @@ -18,22 +19,24 @@ class CProcessControl(QObject): def __init__(self, parent: QObject = None, signal_class: QObject = None, - enable_internal_logging: bool = False): + internal_logging: bool = False, + internal_logging_level: int = logging.DEBUG): super().__init__(parent) # self._kill_child_process_flag = kill_child_process_flag - self._enable_internal_logging = enable_internal_logging - print(f"Parent: {type(parent)}") + + if isinstance(parent, QWidget) or isinstance(parent, QWindow): parent.destroyed.connect(lambda: self.safe_exit(reason="Parent destroyed.")) - # Register this class as signal class (all signals will be implemented and emitted from this class) + # Register this class as signal class (all signals will be implemented in and emitted from this class) if signal_class is not None: self.register_signal_class(signal_class) else: self.register_signal_class(self) # The child process - self._child: Process = None + self._child: cmp.CProcess = None + # Queues for data exchange self.cmd_queue = Queue() self.state_queue = Queue() @@ -42,14 +45,23 @@ class CProcessControl(QObject): self.thread_manager = QThreadPool() # The child process pid + self._pid = os.getpid() self._child_process_pid = None + + self.name = f"{self._pid}({self.__class__.__name__})" + self._child_kill_flag = Value('i', 1) - self._internal_logger, self._il_handler = self.create_new_logger( - f"{self.__class__.__name__}-Int({os.getpid()})") + self._internal_logger, self._il_handler = self.create_new_logger(f"(cmp) {self.name}") + self.enable_internal_logging = internal_logging + self.internal_logging_level = internal_logging_level + self.logger, self.logger_handler = self.create_new_logger(f"{self.__class__.__name__}({os.getpid()})") - self.enable_internal_logging(enable_internal_logging) + + # ================================================================================================================== + # Public methods + # ================================================================================================================== def create_new_logger(self, name: str) -> (logging.Logger, logging.Handler): qh = RichHandler(rich_tracebacks=True) _internal_logger = logging.getLogger(name) @@ -60,11 +72,34 @@ class CProcessControl(QObject): qh.setFormatter(formatter) return _internal_logger, qh - def enable_internal_logging(self, enable: bool): - self._enable_internal_logging = enable - if self._internal_logger is not None: - self._internal_logger.disabled = not enable + @property + def internal_log_enabled(self): + return not self._internal_logger.disabled + + @internal_log_enabled.setter + def internal_log_enabled(self, enable: bool) -> None: + """ + Enables or disables internal logging. If disabled, the internal logger will be disabled and no messages will be + emitted to the state queue. + :param enable: True to enable, False to disable + """ + self._internal_logger.disabled = not enable + + @property + def internal_logging_level(self): + return self._internal_logger.level + @internal_logging_level.setter + def internal_logging_level(self, level: int) -> None: + """ + Sets the internal logging level. + :param level: + :return: + """ + self._internal_logger.setLevel(level) + # ================================================================================================================== + # + # ================================================================================================================== def register_signal_class(self, signal_class: QObject): self._signal_class = signal_class @@ -72,13 +107,17 @@ class CProcessControl(QObject): def child_process_pid(self): return self._child_process_pid - def register_child_process(self, child: cmp.CProcess): - self._internal_logger.debug(f"Registering child process {child.__class__.__name__}.") - self._child = child - self._child.register_kill_flag(self._child_kill_flag) + def register_child_process(self, child: Type[cmp.CProcess], *args, **kwargs): + self._internal_logger.debug(f"Registering child process.") + self._child = child(self.state_queue, self.cmd_queue, + kill_flag=self._child_kill_flag, + internal_logging=self.internal_log_enabled, + internal_log_level=self.internal_logging_level, + *args, **kwargs) + #self._child.register_kill_flag(self._child_kill_flag) self._child_process_pid = child.pid - self._child.enable_internal_logging(self._enable_internal_logging) self._child.start() + self._internal_logger.debug(f"Child process {self._child.name} created.") self.thread_manager.start(self._monitor_result_state) def _monitor_result_state(self): diff --git a/src/cmp/CResultRecord.py b/src/cmp/CResultRecord.py index cc3d6310a62a2ab1d81659f32ac2024f536798d3..54c5c8466fb4dda98508e4d9235b8d627df34a21 100644 --- a/src/cmp/CResultRecord.py +++ b/src/cmp/CResultRecord.py @@ -5,13 +5,15 @@ from cmp import CProcessControl as CProcessControl class CResultRecord: - def __init__(self, signal_name: str, result): + def __init__(self, function_name: str, signal_name: str, result): + self.function_name: str = function_name self.signal_name: str = signal_name self.result = result def emit_signal(self, class_object: CProcessControl): if hasattr(class_object, '_internal_logger'): - class_object._internal_logger.info(f"Emitting {self} in {class_object.__class__.__name__}.") + class_object._internal_logger.info(f"Function {self.function_name} returned {self.result}. " + f"Emitting {self} in {class_object.__class__.__name__}.") emitter = getattr(class_object, self.signal_name).emit if isinstance(self.result, tuple): emitter(*self.result) diff --git a/tests/test_interchange_commands.py b/tests/test_interchange_commands.py index 0967f36d79776ce9e84ce2c94951ebf7cdd1aef2..76186e89841630e30866af0ff77ead862fd50fd7 100644 --- a/tests/test_interchange_commands.py +++ b/tests/test_interchange_commands.py @@ -10,8 +10,8 @@ import unittest class ChildProcessCustomSignals(cmp.CProcess): - def __init__(self, state_queue, cmd_queue, enable_internal_logging): - super().__init__(state_queue, cmd_queue, enable_internal_logging=enable_internal_logging) + def __init__(self, state_queue, cmd_queue, internal_logging): + super().__init__(state_queue, cmd_queue, internal_logging=internal_logging) self.logger = None def postrun_init(self): @@ -29,10 +29,10 @@ class ChildProcessControlCustomSignals(cmp.CProcessControl): # call_without_mp2_changed = Signal(int, int, int) - def __init__(self, parent, signal_class, enable_internal_logging): - super().__init__(parent, signal_class, enable_internal_logging=enable_internal_logging) + def __init__(self, parent, signal_class, internal_logging): + super().__init__(parent, signal_class, internal_logging=internal_logging) self.register_child_process(ChildProcessCustomSignals(self.state_queue, self.cmd_queue, - enable_internal_logging=enable_internal_logging)) + internal_logging=internal_logging)) @cmp.CProcessControl.register_function() def call_without_mp(self, a, b, c=None):