From a5645cb0d700ab45475ddb32baeda3dd4639299a Mon Sep 17 00:00:00 2001
From: Christoph Schmidt <christoph.,schmidt@tugraz.at>
Date: Fri, 22 Dec 2023 12:13:30 +0100
Subject: [PATCH] Updated to v0.1.0. Child Process functions can be connected
 without triggering a signal when finished

---
 examples/example1/mp_process.py           |   4 +-
 examples/example2/ChildProcess2.py        |   8 +-
 examples/example2/example2.py             |   1 +
 examples/example3/ChildProcess3.py        |  10 +-
 examples/example3/ChildProcessControl3.py |  10 +-
 examples/example3/example3.py             |  12 ++-
 examples/example4/CModel.py               |  23 +++++
 examples/example4/ChildProcess4.py        |  20 ++++
 examples/example4/ChildProcessControl4.py |  25 +++++
 examples/example4/ExampleModel4.py        |  54 ++++++++++
 examples/example4/example4.py             |  75 ++++++++++++++
 pyproject.toml                            |   2 +-
 src/cmp/CBase.py                          |  88 ++++++++++++++++
 src/cmp/CCommandRecord.py                 |  10 +-
 src/cmp/CProcess.py                       | 119 ++++++++++------------
 src/cmp/CProcessControl.py                | 106 ++++++++-----------
 src/cmp/CProperty.py                      |  10 +-
 src/cmp/testings.py                       |  45 ++++++++
 tests/test_interchange_commands.py        |   8 +-
 19 files changed, 471 insertions(+), 159 deletions(-)
 create mode 100644 examples/example4/CModel.py
 create mode 100644 examples/example4/ChildProcess4.py
 create mode 100644 examples/example4/ChildProcessControl4.py
 create mode 100644 examples/example4/ExampleModel4.py
 create mode 100644 examples/example4/example4.py
 create mode 100644 src/cmp/CBase.py
 create mode 100644 src/cmp/testings.py

diff --git a/examples/example1/mp_process.py b/examples/example1/mp_process.py
index fd55ca8..4f2c538 100644
--- a/examples/example1/mp_process.py
+++ b/examples/example1/mp_process.py
@@ -25,13 +25,13 @@ class ChildProc(cmp.CProcess, Sceleton):
     def __init__(self, state_queue, cmd_queue, enable_interal_logging):
         super().__init__(state_queue, cmd_queue, enable_interal_logging=enable_interal_logging)
 
-    @cmp.CProcess.register_for_signal()
+    @cmp.CProcess.register_signal()
     def call_without_mp(self, a, b, c=None, **kwargs):
         print(f"{os.getpid()} -> call_without_mp with {a}, {b}, {c} and {kwargs}!")
         time.sleep(1)
         return c
 
-    @cmp.CProcess.register_for_signal('_changed')
+    @cmp.CProcess.register_signal('_changed')
     def call_without_mp2(self, a, b, c=None, **kwargs):
         print(f"{os.getpid()} -> call_without_mp2 with {a}, {b}, {c} and {kwargs}!")
         time.sleep(1)
diff --git a/examples/example2/ChildProcess2.py b/examples/example2/ChildProcess2.py
index f66c3d4..f3c2676 100644
--- a/examples/example2/ChildProcess2.py
+++ b/examples/example2/ChildProcess2.py
@@ -6,21 +6,21 @@ import cmp
 
 class ChildProcess2(cmp.CProcess):
 
-    def __init__(self, state_queue, cmd_queue, kill_flag, internal_logging, internal_log_level):
+    def __init__(self, state_queue, cmd_queue, kill_flag, internal_log, internal_log_level):
         super().__init__(state_queue, cmd_queue, kill_flag,
-                         internal_logging=internal_logging,
+                         internal_log=internal_log,
                          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()
+    @cmp.CProcess.register_signal()
     def call_without_mp(self, a, b, c=None, **kwargs):
         self.logger.info(f"{os.getpid()} -> call_without_mp with {a}, {b}, {c} and {kwargs}!")
         time.sleep(1)
         return c
 
-    @cmp.CProcess.register_for_signal('_changed')
+    @cmp.CProcess.register_signal('_changed')
     def call_without_mp2(self, a, b, c=None, **kwargs):
         print(f"{os.getpid()} -> call_without_mp2 with {a}, {b}, {c} and {kwargs}!")
         time.sleep(1)
diff --git a/examples/example2/example2.py b/examples/example2/example2.py
index 1dee498..1746101 100644
--- a/examples/example2/example2.py
+++ b/examples/example2/example2.py
@@ -54,6 +54,7 @@ class Form(QDialog):
     def closeEvent(self, arg__1):
         self.destroyed.emit()
         print("Form destroyed.")
+
 if __name__ == '__main__':
 
     try:
diff --git a/examples/example3/ChildProcess3.py b/examples/example3/ChildProcess3.py
index 456a71f..3cf5a2e 100644
--- a/examples/example3/ChildProcess3.py
+++ b/examples/example3/ChildProcess3.py
@@ -7,20 +7,18 @@ from cmp.CProperty import CProperty, Cache
 
 class ChildProcess3(cmp.CProcess):
 
-    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)
+    def __init__(self, state_queue, cmd_queue, kill_flag,*args, **kwargs):
+        super().__init__(state_queue, cmd_queue, kill_flag, *args, **kwargs)
         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()
+    @cmp.CProcess.register_signal()
     def test_call(self, a):
         self.logger.info(f"{os.getpid()} -> test_call!")
         time.sleep(1)
-        self.test_call2 = 1
+       # self.test_call2 = 1
         return a
 
     @CProperty
diff --git a/examples/example3/ChildProcessControl3.py b/examples/example3/ChildProcessControl3.py
index 936fa38..1431586 100644
--- a/examples/example3/ChildProcessControl3.py
+++ b/examples/example3/ChildProcessControl3.py
@@ -8,14 +8,12 @@ class ChildProcessControl3(cmp.CProcessControl):
     mp_finished = Signal(int, name='mp_finished')
     mp_finished_untriggered = Signal(int)
 
-    def __init__(self, parent, internal_logging, internal_logging_level):
-        super().__init__(parent,
-                         internal_logging=internal_logging,
-                         internal_logging_level=internal_logging_level)
+    def __init__(self, parent, *args, **kwargs):
+        super().__init__(parent, *args, **kwargs)
         self.register_child_process(ChildProcess3)
 
-    @cmp.CProcessControl.register_function(signal=mp_finished)
+    @cmp.CProcessControl.register_function()
     def test_call(self, a):
-        pass
+        print(a)
         #print(f"{os.getpid()} -> call_without_mp with {a}, {b}, {c}!")
 
diff --git a/examples/example3/example3.py b/examples/example3/example3.py
index 8053c48..f3829bd 100644
--- a/examples/example3/example3.py
+++ b/examples/example3/example3.py
@@ -24,10 +24,11 @@ class Form(QDialog):
     def __init__(self, parent=None):
         super().__init__(parent)
 
-        child_con = ChildProcessControl3(self, internal_logging=True, internal_logging_level=logging.DEBUG)
+        child_con = ChildProcessControl3(self, internal_log=False, internal_log_level=logging.DEBUG)
 
         child_con.mp_finished.connect(self.updateUI)
 
+
         self.browser = QTextBrowser()
         self.lineedit = QLineEdit('Type text and press <Enter>')
         self.lineedit.selectAll()
@@ -40,13 +41,18 @@ class Form(QDialog):
         # self.lineedit.returnPressed.connect(lambda: child_con.call_without_mp(1, 2, c=3))
         self.lineedit.returnPressed.connect(lambda: child_con.test_call(1))
 
+
     def updateUI(self, text):
-        print("updateUI: ", text)
+        #print("updateUI: ", text)
+        self.browser.append("->" + str(text))
+
+    def updateUI2(self, text):
+        #print("updateUI: ", text)
         self.browser.append("->" + str(text))
 
     def closeEvent(self, arg__1):
         self.destroyed.emit()
-        print("Form destroyed.")
+        #print("Form destroyed.")
 
 
 if __name__ == '__main__':
diff --git a/examples/example4/CModel.py b/examples/example4/CModel.py
new file mode 100644
index 0000000..f043b94
--- /dev/null
+++ b/examples/example4/CModel.py
@@ -0,0 +1,23 @@
+import functools
+
+import cmp
+from cmp import CProcess, CProcessControl
+
+
+class CModel:
+
+    def __init__(self, cprocess_control: CProcessControl):
+        self.cprocess_control: CProcessControl = cprocess_control
+
+    @staticmethod
+    def wrap(fun):
+        """ Decoraetor for wrapping a property""
+        """
+        @functools.wraps(fun)
+        def wrapper(self, *args, **kwargs):
+            print(f"Calling {fun.__name__}!")
+            cmd = cmp.CCommandRecord("self._child.name", fun.__name__, *args, **kwargs)
+            print(cmd)
+            #self.cprocess_control.cmd_queue.put(cmd)
+            return fun(self, *args, **kwargs)
+        return wrapper
diff --git a/examples/example4/ChildProcess4.py b/examples/example4/ChildProcess4.py
new file mode 100644
index 0000000..1f53eaf
--- /dev/null
+++ b/examples/example4/ChildProcess4.py
@@ -0,0 +1,20 @@
+import os
+import time
+
+import cmp
+from cmp.CProperty import CProperty, Cache
+from ExampleModel4 import ExampleModel4
+
+
+class ChildProcess4(cmp.CProcess):
+
+    def __init__(self, state_queue, cmd_queue, kill_flag,*args, **kwargs):
+        super().__init__(state_queue, cmd_queue, kill_flag, *args, **kwargs)
+        self.logger = None
+
+
+    def postrun_init(self):
+    #    self.model = ExampleModel4(self)
+        pass
+
+
diff --git a/examples/example4/ChildProcessControl4.py b/examples/example4/ChildProcessControl4.py
new file mode 100644
index 0000000..a8a0846
--- /dev/null
+++ b/examples/example4/ChildProcessControl4.py
@@ -0,0 +1,25 @@
+from PySide6.QtCore import Signal
+
+import cmp
+from ChildProcess4 import ChildProcess4
+from ExampleModel4 import ExampleModel4
+
+
+class ChildProcessControl4(cmp.CProcessControl):
+    mp_finished = Signal(int, name='mp_finished')
+    mp_finished_untriggered = Signal(int)
+    test_call1_finished = Signal(int, name='test_call1_finished')
+
+    def __init__(self, parent, *args, **kwargs):
+        super().__init__(parent, *args, **kwargs)
+        self.register_child_process(ChildProcess4)
+
+
+    @cmp.CProcessControl.register_function(signal=mp_finished)
+    def set_testfield1(self, a):
+        pass
+        #c: ChildProcess3 = self._child
+        #.execute_function(lambda a: c.test_call1(a), signal=ChildProcessControl4.test_call1_finished)
+        #print(a)
+        #print(f"{os.getpid()} -> call_without_mp with {a}, {b}, {c}!")
+
diff --git a/examples/example4/ExampleModel4.py b/examples/example4/ExampleModel4.py
new file mode 100644
index 0000000..7f528a9
--- /dev/null
+++ b/examples/example4/ExampleModel4.py
@@ -0,0 +1,54 @@
+from PySide6.QtCore import Signal, QObject
+
+from cmp.CProperty import CProperty
+from CModel import CModel
+
+
+class ExampleModel4Signals(QObject):
+    testfield1_changed = Signal(int)
+    testfield2_changed = Signal(int)
+    testfield3_changed = Signal(int)
+
+
+class ExampleModel4(CModel):
+
+    def __init__(self, c_process_control):
+        super().__init__(c_process_control)
+        self.signals = ExampleModel4Signals(c_process_control)
+
+        self._testfield1 = 0
+        self._testfield2 = 0
+        self._testfield3 = 0
+
+
+    @property
+
+    def testfield1(self):
+        return self._testfield1
+
+    @testfield1.setter
+    @CModel.wrap
+    def testfield1(self, value):
+        self._testfield1 = value
+        print(f"testfield1: {value}")
+        self.signals.testfield1_changed.emit(value)
+
+    @property
+    def testfield2(self):
+        return self._testfield2
+
+    @testfield2.setter
+    def testfield2(self, value):
+        self._testfield2 = value
+        self.signals.testfield2_changed.emit(value)
+
+    @property
+    def testfield3(self):
+        return self._testfield3
+
+    @testfield3.setter
+    def testfield3(self, value):
+        self._testfield3 = value
+        self.signals.testfield3_changed.emit(value)
+
+
diff --git a/examples/example4/example4.py b/examples/example4/example4.py
new file mode 100644
index 0000000..ec5b2ce
--- /dev/null
+++ b/examples/example4/example4.py
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+"""
+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
+from threading import Thread
+
+from PySide6.QtCore import QObject, Signal, SIGNAL
+from PySide6.QtWidgets import QDialog, QApplication, QTextBrowser, QLineEdit, QVBoxLayout, QMainWindow, QMessageBox
+
+
+
+sys.path.append('./src')
+from ChildProcessControl4 import ChildProcessControl4
+from ExampleModel4 import ExampleModel4
+
+class Form(QDialog):
+    on_text_converted = Signal(str)
+
+    def __init__(self, parent=None):
+        super().__init__(parent)
+
+
+
+        child_con = ChildProcessControl4(self, internal_log=True, internal_log_level=logging.DEBUG)
+        self.model = ExampleModel4(child_con)
+
+        #self.model.signals.testfield1_changed.connect(child_con.child.test_call)
+
+        self.model.testfield1 = 123
+
+        child_con.mp_finished.connect(self.updateUI)
+
+        self.browser = QTextBrowser()
+        self.lineedit = QLineEdit('Type text and press <Enter>')
+        self.lineedit.selectAll()
+        layout = QVBoxLayout()
+        layout.addWidget(self.browser)
+        layout.addWidget(self.lineedit)
+        self.setLayout(layout)
+        self.lineedit.setFocus()
+        self.setWindowTitle('Upper')
+        # self.lineedit.returnPressed.connect(lambda: child_con.call_without_mp(1, 2, c=3))
+        self.lineedit.returnPressed.connect(lambda: child_con.set_testfield1(1))
+
+    def updateUI(self, text):
+        print("updateUI: ", text)
+        self.browser.append("->" + str(text))
+
+    def closeEvent(self, arg__1):
+        self.destroyed.emit()
+        #print("Form destroyed.")
+
+
+if __name__ == '__main__':
+
+    try:
+        app = QApplication(sys.argv)
+        form = Form()
+        form.show()
+        app.exec()
+    except KeyboardInterrupt:
+        print("KeyboardInterrupt")
+        sys.exit(0)
+
+
+#if __name__ == '__main__':
+
+    #model = ExampleModel4()
+    #model.testfield1 = 123
diff --git a/pyproject.toml b/pyproject.toml
index 10db365..1db8b99 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "cmp"
-version = "0.0.2"
+version = "0.1.0"
 authors = [
   { name="Christoph Schmidt", email="cschmidt.fs@gmail.com" },
 ]
diff --git a/src/cmp/CBase.py b/src/cmp/CBase.py
new file mode 100644
index 0000000..53e400f
--- /dev/null
+++ b/src/cmp/CBase.py
@@ -0,0 +1,88 @@
+import logging
+
+from rich.logging import RichHandler
+
+
+class CBase:
+
+    # ==================================================================================================================
+    # Public methods
+    # ==================================================================================================================
+    def __init__(self):
+        self._internal_logger = None
+        self._internal_log_handler = None
+        self.name = self.__class__.__name__
+
+    def create_new_logger(self, logger_name: str,
+                          logger_handler: logging.Handler = None,
+                          logger_format: str = " %(name)s %(message)s") -> (logging.Logger, logging.Handler):
+        """
+        Creates a new logger with the given name and handler. If no handler is given, a RichHandler will be used.
+        :param logger_name:
+        :param logger_handler:
+        :param logger_format:
+        :return:
+        """
+        if logger_handler is None:
+            logger_handler = RichHandler(rich_tracebacks=True)
+
+        _internal_logger = logging.getLogger(logger_name)
+        _internal_logger.handlers = [logger_handler]
+        _internal_logger.setLevel(logging.DEBUG)
+        formatter = logging.Formatter(logger_format)
+        logger_handler.setFormatter(formatter)
+        return _internal_logger, logger_handler
+
+    @property
+    def internal_log_enabled(self):
+        if self._internal_logger is not None:
+            return not self._internal_logger.disabled
+        else:
+            raise Exception("Internal logger not initialized")
+
+    @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
+        """
+        if self._internal_logger is not None:
+            if enable:
+                self._internal_logger.info(f"Internal logger of {self.__class__.__name__} has been enabled.")
+            else:
+                self._internal_logger.warning(f"Internal logger of {self.__class__.__name__} has been disabled.")
+            self._internal_logger.level = logging.ERROR
+        else:
+            raise Exception("Can't enable internal logger. Internal logger not initialized")
+
+    @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:
+        """
+        if self._internal_logger is not None:
+            if level == logging.DEBUG:
+                self._internal_logger.info(f"Internal log level of {self.__class__.__name__} has been set to DEBUG.")
+            elif level == logging.INFO:
+                self._internal_logger.info(f"Internal log level of {self.__class__.__name__} has been set to INFO.")
+            elif level == logging.WARNING:
+                self._internal_logger.info(f"Internal log level of {self.__class__.__name__} has been set to WARNING.")
+            elif level == logging.ERROR:
+                self._internal_logger.info(f"Internal log level of {self.__class__.__name__} has been set to ERROR.")
+            elif level == logging.CRITICAL:
+                self._internal_logger.info(f"Internal log level of {self.__class__.__name__} has been set to CRITICAL.")
+            else:
+                self._internal_logger.info(f"Internal log level of {self.__class__.__name__} has been set to {level}.")
+
+
+            self._internal_logger.setLevel(level)
+        else:
+            raise Exception("Can't set internal log level. Internal logger not initialized")
+
diff --git a/src/cmp/CCommandRecord.py b/src/cmp/CCommandRecord.py
index 503fbbb..52472d8 100644
--- a/src/cmp/CCommandRecord.py
+++ b/src/cmp/CCommandRecord.py
@@ -2,10 +2,11 @@ from cmp import CProcess as CProcess
 
 
 class CCommandRecord:
-    def __init__(self, func_name: str, *args: (), **kwargs: {}):
+    def __init__(self, proc_name: str, func_name: str, *args: (), **kwargs: {}, ):
         self.func_name: str = func_name
         self.args: () = args
         self.kwargs: {} = kwargs
+        self.proc_name = proc_name
 
         self.signal_name: str = None
 
@@ -15,10 +16,13 @@ class CCommandRecord:
     def execute(self, class_object: CProcess):
         if hasattr(class_object, '_internal_logger'):
             class_object._internal_logger.info(f"Executing {self} in {class_object.name}.")
-        getattr(class_object, self.func_name)(signal_name=self.signal_name, *self.args, **self.kwargs)
+        if self.signal_name is not None:
+            getattr(class_object, self.func_name)(signal_name=self.signal_name, *self.args, **self.kwargs)
+        else:
+            getattr(class_object, self.func_name)(*self.args, **self.kwargs)
 
     def __repr__(self):
         args_str = ', '.join(map(repr, self.args))
         kwargs_str = ', '.join(f"{key}={repr(value)}" for key, value in self.kwargs.items())
         all_args = ', '.join(filter(None, [args_str, kwargs_str]))
-        return f"Function {self.func_name}({all_args})"
+        return f"Function <{self.proc_name}>.{self.func_name}({all_args})"
diff --git a/src/cmp/CProcess.py b/src/cmp/CProcess.py
index 9391edf..a219d8d 100644
--- a/src/cmp/CProcess.py
+++ b/src/cmp/CProcess.py
@@ -1,73 +1,39 @@
 import logging.handlers
 import logging
+import multiprocessing
 import os
+import sys
 import time
 import traceback
 from multiprocessing import Process, Queue, Value
 
 import cmp
+from cmp.CBase import CBase
 
+# This is a Queue that behaves like stdout
 
-class CProcess(Process):
 
-    def __init__(self, state_queue: Queue, cmd_queue: Queue, kill_flag,
-                 internal_logging: bool = False,
+class CProcess(CBase, Process):
+
+    # override the print function
+
+    def __init__(self, state_queue: Queue, cmd_queue: Queue,
+                 kill_flag,
+                 internal_log: 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._internal_log_enabled_ = internal_log
+        self._internal_log_level_ = internal_log_level
 
         self.logger = None
         self.logger_handler = None
 
-        self._internal_logger = None
-        self._il_handler = None
-
         self.cmd_queue = cmd_queue
         self.state_queue = state_queue
         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)
-        _logger.handlers.clear()
-        _logger.handlers = [_handler]
-        _logger.setLevel(logging.DEBUG)
-        formatter = logging.Formatter('%(message)s')
-        _handler.setFormatter(formatter)
-        return _logger, _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
     # ==================================================================================================================
@@ -78,23 +44,44 @@ class CProcess(Process):
         """
         pass
 
+    def _typecheck(self):
+        if not isinstance(self.cmd_queue, multiprocessing.queues.Queue):
+            raise TypeError(f"cmd_queue must be of type {Queue}, not {type(self.cmd_queue)}")
+
+        if not isinstance(self.state_queue, multiprocessing.queues.Queue):
+            raise TypeError(f"state_queue must be of type {Queue}, not {type(self.state_queue)}")
+
+        return True
+
     def run(self):
         self.name = f"{os.getpid()}({self.name})"
-        self.postrun_init()
 
-        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._internal_logger, self._internal_log_handler = self.create_new_logger(
+            f"(cmp) {self.name}",
+            logger_handler=logging.handlers.QueueHandler(self.state_queue))
+
+        self.logger, self.logger_handler = self.create_new_logger(f"{os.getpid()}({self.__class__.__name__})",
+                                                                  logger_handler=logging.handlers.QueueHandler(
+                                                                      self.state_queue))
+
+        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.")
 
+        sys.stderr.write = self.logger.error
+        sys.stdout.write = self.logger.info
+
+        self.postrun_init()
+
         try:
+            self._typecheck()
             while self._kill_flag.value:
                 try:
                     cmd = self.cmd_queue.get(block=True, timeout=1)
                 except:
                     continue
+
                 if isinstance(cmd, cmp.CCommandRecord):
                     self._internal_logger.debug(
                         f"Received cmd: {cmd}, args: {cmd.args}, kwargs: {cmd.kwargs}, Signal to emit: {cmd.signal_name}")
@@ -103,10 +90,12 @@ class CProcess(Process):
                     except Exception as e:
                         traceback_str = ''.join(traceback.format_tb(e.__traceback__))
                         self._internal_logger.error(f"Exception '{e}' occurred in {cmd}!. Traceback:\n{traceback_str}")
+                    self._internal_logger.debug(f"Command {cmd} finished.")
+                else:
+                    self._internal_logger.error(f"Received unknown command {cmd}!")
             self._internal_logger.error(f"Control Process exited. Terminating Process {os.getpid()}")
             if self._kill_flag.value == 0:
                 self._internal_logger.error(f"Process {os.getpid()} received kill signal!")
-
         except KeyboardInterrupt:
             self._internal_logger.warning(f"Received KeyboardInterrupt! Exiting Process {os.getpid()}")
             time.sleep(1)
@@ -114,22 +103,26 @@ class CProcess(Process):
         except Exception as e:
             self._internal_logger.warning(f"Received Exception {e}! Exiting Process {os.getpid()}")
 
+        self._internal_logger.debug(f"Child process monitor {self.__class__.__name__} ended.")
+
     def __del__(self):
+        print(f"Child process {self.name} deleted.")
         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.")
+        if signal_name is not None:
+            self._internal_logger.debug(f"{func_name} finished. Emitting signal {signal_name} in control class.")
+        else:
+            self._internal_logger.debug(f"{func_name} finished. No signal to emit.")
         result = cmp.CResultRecord(func_name, signal_name, res)
         self.state_queue.put(result)
 
-
     @staticmethod
-    def register_for_signal(postfix='_finished', signal_name: str = None):
+    def register_signal(postfix=None, 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):
@@ -144,7 +137,7 @@ class CProcess(Process):
                     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!")
+                    sign = None
                 res = func(self, *args, **kwargs)
                 self._put_result_to_queue(func_name, sign, res)
                 return res
@@ -154,16 +147,16 @@ class CProcess(Process):
         return register
 
     @staticmethod
-    def setter(sigal_same: str = None):
+    def setter(signal_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._internal_logger.debug(f"{func_name} finished. Emitting signal {signal_same} in control class.")
+                result = cmp.CResultRecord(func_name, signal_same, res)
                 self.state_queue.put(result)
                 return res
 
             return get_signature
 
-        return register
\ No newline at end of file
+        return register
diff --git a/src/cmp/CProcessControl.py b/src/cmp/CProcessControl.py
index b5b83c1..541714b 100644
--- a/src/cmp/CProcessControl.py
+++ b/src/cmp/CProcessControl.py
@@ -1,3 +1,4 @@
+import inspect
 import logging
 import logging.handlers
 import os
@@ -13,18 +14,19 @@ from PySide6.QtWidgets import QWidget
 from rich.logging import RichHandler
 
 import cmp
+from cmp.CBase import CBase
 
 
-class CProcessControl(QObject):
+class CProcessControl(CBase, QObject):
 
     def __init__(self, parent: QObject = None,
                  signal_class: QObject = None,
-                 internal_logging: bool = False,
-                 internal_logging_level: int = logging.DEBUG):
-        super().__init__(parent)
+                 internal_log: bool = False,
+                 internal_log_level: int = logging.DEBUG):
+        QObject.__init__(self, parent)
+        CBase.__init__(self)
         # self._kill_child_process_flag = kill_child_process_flag
 
-
         if isinstance(parent, QWidget) or isinstance(parent, QWindow):
             parent.destroyed.connect(lambda: self.safe_exit(reason="Parent destroyed."))
 
@@ -41,6 +43,7 @@ class CProcessControl(QObject):
         self.cmd_queue = Queue()
         self.state_queue = Queue()
 
+
         # Thread manager for monitoring the state queue
         self.thread_manager = QThreadPool()
 
@@ -52,51 +55,11 @@ class CProcessControl(QObject):
 
         self._child_kill_flag = Value('i', 1)
 
-        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._internal_logger, self._internal_log_handler = self.create_new_logger(f"(cmp) {self.name}")
+        self.internal_log_enabled = internal_log
+        self.internal_log_level = internal_log_level
 
         self.logger, self.logger_handler = self.create_new_logger(f"{self.__class__.__name__}({os.getpid()})")
-
-
-    # ==================================================================================================================
-    # Public methods
-    # ==================================================================================================================
-    def create_new_logger(self, name: str) -> (logging.Logger, logging.Handler):
-        qh = RichHandler(rich_tracebacks=True)
-        _internal_logger = logging.getLogger(name)
-        _internal_logger.handlers = [qh]
-        _internal_logger.setLevel(logging.DEBUG)
-        formatter = logging.Formatter(
-            '%(name)s %(message)s')
-        qh.setFormatter(formatter)
-        return _internal_logger, qh
-
-    @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)
     # ==================================================================================================================
     #
     # ==================================================================================================================
@@ -107,19 +70,24 @@ class CProcessControl(QObject):
     def child_process_pid(self):
         return self._child_process_pid
 
-    def register_child_process(self, child: Type[cmp.CProcess], *args, **kwargs):
+    def register_child_process(self, child, *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,
+                            internal_log=self.internal_log_enabled,
+                            internal_log_level=self.internal_log_level,
                             *args, **kwargs)
-        #self._child.register_kill_flag(self._child_kill_flag)
+        # self._child.register_kill_flag(self._child_kill_flag)
         self._child_process_pid = child.pid
         self._child.start()
         self._internal_logger.debug(f"Child process {self._child.name} created.")
         self.thread_manager.start(self._monitor_result_state)
 
+    @property
+    def child(self):
+        return self._child
+
     def _monitor_result_state(self):
         self._internal_logger.info("Starting monitor thread.")
         try:
@@ -136,18 +104,25 @@ class CProcessControl(QObject):
                         self.logger.warning(f"Error cannot handle log record: {e}")
                 elif isinstance(res, cmp.CResultRecord):
                     try:
-                        res.emit_signal(self._signal_class)
-                        self._internal_logger.debug(f"Emitted {res} in {self._signal_class.__class__.__name__}.")
+                        if isinstance(res, Signal):
+                            res.emit_signal(self._signal_class)
+                            self._internal_logger.debug(f"Emitted {res} in {self._signal_class.__class__.__name__}.")
                     except Exception as e:
                         self._internal_logger.error(f"Error while emitting {res} in {self.__class__.__name__}: {e}")
+                else:
+                    self._internal_logger.error(f"Received unknown result {res}!")
+
         except:
-            print(f"Error in monitor thread")
+            self._internal_logger.error(f"Error in monitor thread")
             time.sleep(1)
 
-        self._internal_logger.info("Ended monitor thread.")
+        self._internal_logger.info(f"Ended monitor thread. Child process alive: {self._child.is_alive()}")
         self.state_queue.close()
         self.cmd_queue.close()
 
+    def execute_function(self, func: callable, signal: Signal = None):
+        self.register_function(signal)(func)(self)
+
     @staticmethod
     def register_function(signal: Signal = None):
         """
@@ -157,6 +132,7 @@ class CProcessControl(QObject):
         :param signal:
         :return:
         """
+
         def register(func):
             def match_signal_name(_signal: Signal):
                 pattern = re.compile(r'(\w+)\(([^)]*)\)')
@@ -174,18 +150,26 @@ class CProcessControl(QObject):
                 args = arguments.pop("args")
                 kwargs = arguments.pop("kwargs")
 
-                cmd = cmp.CCommandRecord(name, *args, **kwargs)
+                cmd = cmp.CCommandRecord(self._child.name, name, *args, **kwargs)
                 if signal is not None:
-                    sig_name, args = match_signal_name(signal)
+                    sig_name, sig_args = match_signal_name(signal)
                     cmd.register_signal(sig_name)
+                    self._internal_logger.debug(f"New function registered: {cmd} -> "
+                                                f"{cmd.signal_name if cmd.signal_name is not None else 'None'}("
+                                                f"{', '.join(str(a) for a in sig_args) if sig_args is not None else 'None'})")
                 else:
+                    self._internal_logger.debug(f"New function registered: {cmd}")
+
+                try:
+                    self._internal_logger.debug(f"Executing {name} with args {args} and kwargs {kwargs}")
                     func(self, *args, **kwargs)
+                except Exception as e:
+                    self._internal_logger.error(f"Error while executing {cmd}: {e}")
+                    raise e
 
                 try:
-                    self._internal_logger.debug(f"New function registered: {cmd} -> "
-                                                f"{cmd.signal_name if cmd.signal_name is not None else 'None'}("
-                                                f"{', '.join(a for a in args) if args is not None else 'None'})")
                     self.cmd_queue.put(cmd)
+                    self._internal_logger.debug(f"{cmd} put into cmd_queue.")
                 except Exception as e:
                     self._internal_logger.error(f"Error while putting {cmd} into cmd_queue: {e}")
                     raise e
diff --git a/src/cmp/CProperty.py b/src/cmp/CProperty.py
index 1cac741..9f7c597 100644
--- a/src/cmp/CProperty.py
+++ b/src/cmp/CProperty.py
@@ -19,12 +19,11 @@ class _Cache(object):
     def __set__(self, obj, value):
         if self.fset is None:
             raise AttributeError("can't set attribute")
-        print(f"Setting {obj}, {value}!")
         self.fset(obj, value)
 
-    @staticmethod
-    def set(signal_name):
-        print("************ 1 Setting!")
+    #@staticmethod
+    #def set(signal_name):
+    #    print("************ 1 Setting!")
 
     def __call__(self, *args, **kwargs):
         """Invoked on every call of decorated method"""
@@ -66,10 +65,8 @@ class CProperty:
         return self.fget(obj)
 
     def __set__(self, obj: cmp.CProcess, value):
-
         if self.fset is None:
             raise AttributeError("can't set attribute")
-
         obj._internal_logger.debug(f"Setting {self.signal_name}!")
         result = cmp.CResultRecord(str(self.fset.__name__), self.signal_name, value)
         obj.state_queue.put(result)
@@ -77,6 +74,7 @@ class CProperty:
         self.fset(obj, value)
 
     def getter(self, fget):
+
         return type(self)(fget, self.fset, self.signal_name)
 
     def _setter(self, fset):
diff --git a/src/cmp/testings.py b/src/cmp/testings.py
new file mode 100644
index 0000000..5c527ed
--- /dev/null
+++ b/src/cmp/testings.py
@@ -0,0 +1,45 @@
+class _Cache(object):
+
+    def __init__(self, func, fset, signal_name):
+        # Plain function as argument to be decorated
+        self.func = func
+        self.fset = fset
+        self.signal_name = signal_name
+
+    def __get__(self, instance, owner):
+        self.instance_ = instance
+        return self.__call__
+
+    def __set__(self, obj, value):
+        if self.fset is None:
+            raise AttributeError("can't set attribute")
+        print(f"Setting {obj}, {value}!")
+        self.fset(obj, value)
+
+    @staticmethod
+    def set(signal_name):
+        print("************ 1 Setting!")
+
+    def __call__(self, *args, **kwargs):
+        """Invoked on every call of decorated method"""
+
+        # set attribute on instance
+        name = '%s_called' % self.func.__name__
+        # print(f"Setting {name} in {}!")
+        self.instance_._internal_logger.debug(
+            f"Setting {name} in {self.instance_.__class__.__name__} and emitting {self.signal_name}!")
+        # setattr(self.instance_, name, datetime.utcnow())
+
+        # returning original function with class' instance as self
+        return self.func(self.instance_, *args, **kwargs)
+
+
+# wrap _Cache to allow for deferred calling
+def Cache(function=None, signal_name=None):
+    if function:
+        return _Cache(function)
+    else:
+        def wrapper(function):
+            return _Cache(function, signal_name)
+
+        return wrapper
diff --git a/tests/test_interchange_commands.py b/tests/test_interchange_commands.py
index 76186e8..30222ac 100644
--- a/tests/test_interchange_commands.py
+++ b/tests/test_interchange_commands.py
@@ -10,14 +10,14 @@ import unittest
 
 class ChildProcessCustomSignals(cmp.CProcess):
 
-    def __init__(self, state_queue, cmd_queue, internal_logging):
-        super().__init__(state_queue, cmd_queue, internal_logging=internal_logging)
+    def __init__(self, state_queue, cmd_queue, internal_log):
+        super().__init__(state_queue, cmd_queue, internal_log=internal_log)
         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()
+    @cmp.CProcess.register_signal()
     def call_without_mp(self, a, b, c=None, **kwargs):
         self.logger.info(f"{os.getpid()} -> call_without_mp with {a}, {b}, {c} and {kwargs}!")
         time.sleep(1)
@@ -32,7 +32,7 @@ class ChildProcessControlCustomSignals(cmp.CProcessControl):
     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,
-                                                              internal_logging=internal_logging))
+                                                              internal_log=internal_logging))
 
     @cmp.CProcessControl.register_function()
     def call_without_mp(self, a, b, c=None):
-- 
GitLab