Skip to content
Snippets Groups Projects
Commit 2db02d0d authored by Christoph Schmidt's avatar Christoph Schmidt
Browse files

Allows now to define properties that automatically emits signals when emitted

parent f942643d
No related branches found
No related tags found
No related merge requests found
...@@ -22,7 +22,7 @@ class Form(QDialog): ...@@ -22,7 +22,7 @@ class Form(QDialog):
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent) 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_mp_finished.connect(self.updateUI)
child_con.call_without_mp2_changed.connect(self.updateUI2) child_con.call_without_mp2_changed.connect(self.updateUI2)
......
...@@ -47,9 +47,9 @@ class ChildControl(cmp.CProcessControl, Sceleton): ...@@ -47,9 +47,9 @@ class ChildControl(cmp.CProcessControl, Sceleton):
call_without_mp_finished = Signal(int) call_without_mp_finished = Signal(int)
call_without_mp2_changed = Signal(int, int, int) call_without_mp2_changed = Signal(int, int, int)
def __init__(self, parent, enable_internal_logging): def __init__(self, parent, internal_logging):
super().__init__(parent, enable_internal_logging=enable_internal_logging) super().__init__(parent, internal_logging=internal_logging)
self.register_child_process(ChildProc(self.state_queue, self.cmd_queue, enable_interal_logging=enable_internal_logging)) self.register_child_process(ChildProc(self.state_queue, self.cmd_queue, enable_interal_logging=internal_logging))
@cmp.CProcessControl.register_function() @cmp.CProcessControl.register_function()
def call_without_mp(self, a, b, c=None): def call_without_mp(self, a, b, c=None):
......
...@@ -6,8 +6,10 @@ import cmp ...@@ -6,8 +6,10 @@ import cmp
class ChildProcess2(cmp.CProcess): class ChildProcess2(cmp.CProcess):
def __init__(self, state_queue, cmd_queue, enable_internal_logging): def __init__(self, state_queue, cmd_queue, kill_flag, internal_logging, internal_log_level):
super().__init__(state_queue, cmd_queue, enable_internal_logging=enable_internal_logging) super().__init__(state_queue, cmd_queue, kill_flag,
internal_logging=internal_logging,
internal_log_level=internal_log_level)
self.logger = None self.logger = None
def postrun_init(self): def postrun_init(self):
......
...@@ -8,12 +8,10 @@ class ChildProcessControl2(cmp.CProcessControl): ...@@ -8,12 +8,10 @@ class ChildProcessControl2(cmp.CProcessControl):
call_without_mp_finished = Signal(int) call_without_mp_finished = Signal(int)
#call_without_mp2_changed = Signal(int, int, int) #call_without_mp2_changed = Signal(int, int, int)
def __init__(self, parent, signal_class, enable_internal_logging): def __init__(self, parent, signal_class, internal_logging, internal_logging_level):
super().__init__(parent, signal_class, enable_internal_logging=enable_internal_logging) super().__init__(parent, signal_class,
self.register_child_process(ChildProcess2( internal_logging=internal_logging, internal_logging_level=internal_logging_level)
self.state_queue, self.register_child_process(ChildProcess2)
self.cmd_queue,
enable_internal_logging=enable_internal_logging))
@cmp.CProcessControl.register_function() @cmp.CProcessControl.register_function()
def call_without_mp(self, a, b, c=None): def call_without_mp(self, a, b, c=None):
......
...@@ -4,6 +4,7 @@ Author(s): Christoph Schmidt <christoph.schmidt@tugraz.at> ...@@ -4,6 +4,7 @@ Author(s): Christoph Schmidt <christoph.schmidt@tugraz.at>
Created: 2023-10-19 12:35 Created: 2023-10-19 12:35
Package Version: Package Version:
""" """
import logging
import signal import signal
import sys import sys
from multiprocessing import Process, Queue, Pipe from multiprocessing import Process, Queue, Pipe
...@@ -30,7 +31,7 @@ class Form(QDialog): ...@@ -30,7 +31,7 @@ class Form(QDialog):
super().__init__(parent) super().__init__(parent)
mysignals = MySignals() 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) mysignals.call_without_mp2_changed.connect(self.updateUI)
......
...@@ -2,19 +2,36 @@ import os ...@@ -2,19 +2,36 @@ import os
import time import time
import cmp import cmp
from cmp.CProperty import CProperty, Cache
class ChildProcess3(cmp.CProcess): class ChildProcess3(cmp.CProcess):
def __init__(self, state_queue, cmd_queue, enable_internal_logging): def __init__(self, state_queue, cmd_queue, kill_flag, internal_logging, internal_log_level):
super().__init__(state_queue, cmd_queue, enable_internal_logging=enable_internal_logging) super().__init__(state_queue, cmd_queue, kill_flag,
internal_logging=internal_logging,
internal_log_level=internal_log_level)
self.logger = None self.logger = None
def postrun_init(self): def postrun_init(self):
self.logger, self.logger_h = self.create_new_logger(f"{self.__class__.__name__}-({os.getpid()})") self.logger, self.logger_h = self.create_new_logger(f"{self.__class__.__name__}-({os.getpid()})")
@cmp.CProcess.register_for_signal() @cmp.CProcess.register_for_signal()
def test_call(self, a): def test_call(self, a):
self.logger.info(f"{os.getpid()} -> test_call!") self.logger.info(f"{os.getpid()} -> test_call!")
time.sleep(1) time.sleep(1)
self.test_call2 = 1
return a 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
...@@ -6,13 +6,13 @@ from ChildProcess3 import ChildProcess3 ...@@ -6,13 +6,13 @@ from ChildProcess3 import ChildProcess3
class ChildProcessControl3(cmp.CProcessControl): class ChildProcessControl3(cmp.CProcessControl):
mp_finished = Signal(int, name='mp_finished') mp_finished = Signal(int, name='mp_finished')
mp_finished_untriggered = Signal(int)
def __init__(self, parent, enable_internal_logging): def __init__(self, parent, internal_logging, internal_logging_level):
super().__init__(parent, enable_internal_logging=enable_internal_logging) super().__init__(parent,
self.register_child_process(ChildProcess3( internal_logging=internal_logging,
self.state_queue, internal_logging_level=internal_logging_level)
self.cmd_queue, self.register_child_process(ChildProcess3)
enable_internal_logging=enable_internal_logging))
@cmp.CProcessControl.register_function(signal=mp_finished) @cmp.CProcessControl.register_function(signal=mp_finished)
def test_call(self, a): def test_call(self, a):
......
...@@ -4,6 +4,7 @@ Author(s): Christoph Schmidt <christoph.schmidt@tugraz.at> ...@@ -4,6 +4,7 @@ Author(s): Christoph Schmidt <christoph.schmidt@tugraz.at>
Created: 2023-10-19 12:35 Created: 2023-10-19 12:35
Package Version: Package Version:
""" """
import logging
import signal import signal
import sys import sys
from multiprocessing import Process, Queue, Pipe from multiprocessing import Process, Queue, Pipe
...@@ -23,7 +24,7 @@ class Form(QDialog): ...@@ -23,7 +24,7 @@ class Form(QDialog):
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent) 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) child_con.mp_finished.connect(self.updateUI)
......
...@@ -10,10 +10,14 @@ import cmp ...@@ -10,10 +10,14 @@ import cmp
class CProcess(Process): 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) 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 = None
self.logger_handler = None self.logger_handler = None
...@@ -22,11 +26,11 @@ class CProcess(Process): ...@@ -22,11 +26,11 @@ class CProcess(Process):
self.cmd_queue = cmd_queue self.cmd_queue = cmd_queue
self.state_queue = state_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 self._kill_flag = kill_flag
# ==================================================================================================================
# Logging
# ==================================================================================================================
def create_new_logger(self, name: str) -> (logging.Logger, logging.Handler): def create_new_logger(self, name: str) -> (logging.Logger, logging.Handler):
_handler = logging.handlers.QueueHandler(self.state_queue) _handler = logging.handlers.QueueHandler(self.state_queue)
_logger = logging.getLogger(name) _logger = logging.getLogger(name)
...@@ -37,15 +41,36 @@ class CProcess(Process): ...@@ -37,15 +41,36 @@ class CProcess(Process):
_handler.setFormatter(formatter) _handler.setFormatter(formatter)
return _logger, _handler return _logger, _handler
def enable_internal_logging(self, enable: bool): @property
self._enable_internal_logging = enable def internal_log_enabled(self):
if self._internal_logger is not None and enable is False: self._internal_logger.debug(f"internal_log_enabled: {not self._internal_logger.disabled}")
self._internal_logger.disabled = True return not self._internal_logger.disabled
#self._internal_logger.handlers = []
elif self._internal_logger is not None and enable is True: @internal_log_enabled.setter
self._internal_logger.disabled = False def internal_log_enabled(self, enable: bool) -> None:
#self._internal_logger.handlers = [self._il_handler] """
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): def postrun_init(self):
""" """
Dummy function for initializing e.g. loggers (some handlers are not pickable) Dummy function for initializing e.g. loggers (some handlers are not pickable)
...@@ -54,12 +79,14 @@ class CProcess(Process): ...@@ -54,12 +79,14 @@ class CProcess(Process):
pass pass
def run(self): def run(self):
self.name = f"{self.name}/{os.getpid()}" self.name = f"{os.getpid()}({self.name})"
self.postrun_init() self.postrun_init()
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.logger, self.logger_handler = self.create_new_logger(f"{self.__class__.__name__}({os.getpid()})") self.internal_log_enabled = self._internal_log_enabled
self.enable_internal_logging(self._enable_internal_logging) 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.") self._internal_logger.debug(f"Child process {self.__class__.__name__} started.")
try: try:
...@@ -69,9 +96,8 @@ class CProcess(Process): ...@@ -69,9 +96,8 @@ class CProcess(Process):
except: except:
continue continue
if isinstance(cmd, cmp.CCommandRecord): if isinstance(cmd, cmp.CCommandRecord):
self._internal_logger.info(f"Received cmd: {cmd}")
self._internal_logger.debug( 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: try:
cmd.execute(self) cmd.execute(self)
except Exception as e: except Exception as e:
...@@ -89,28 +115,55 @@ class CProcess(Process): ...@@ -89,28 +115,55 @@ class CProcess(Process):
self._internal_logger.warning(f"Received Exception {e}! Exiting Process {os.getpid()}") self._internal_logger.warning(f"Received Exception {e}! Exiting Process {os.getpid()}")
def __del__(self): def __del__(self):
# if self._internal_logger is not None:
# self._internal_logger.warning(f"Exiting Process")
self.cmd_queue.close() self.cmd_queue.close()
self.state_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 @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 _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 register(func):
def get_signature(self, *args, **kwargs): 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: if 'signal_name' in kwargs and kwargs['signal_name'] is not None:
sign = kwargs.pop('signal_name') sign = kwargs.pop('signal_name')
elif _postfix is not None: elif _postfix is not None:
sign = f"{func.__name__}{_postfix}" sign = f"{func.__name__}{_postfix}"
self._internal_logger.debug(f"Constructing signal name for function '{func.__name__}': {sign}") self._internal_logger.debug(f"Constructing signal name for function '{func.__name__}': {sign}")
else: 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._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) res = func(self, *args, **kwargs)
self._internal_logger.debug(f"{func.__name__} finished. Emitting signal {sign} in control class.") self._internal_logger.debug(f"{func_name} finished. Emitting signal {sigal_same} in control class.")
result = cmp.CResultRecord(sign, res) result = cmp.CResultRecord(func_name, sigal_same, res)
self.state_queue.put(result) self.state_queue.put(result)
return res return res
return get_signature return get_signature
return register return register
\ No newline at end of file
...@@ -5,6 +5,7 @@ import re ...@@ -5,6 +5,7 @@ import re
import signal import signal
import time import time
from multiprocessing import Queue, Process, Value from multiprocessing import Queue, Process, Value
from typing import Type
from PySide6.QtCore import QObject, QThreadPool, Signal from PySide6.QtCore import QObject, QThreadPool, Signal
from PySide6.QtGui import QWindow from PySide6.QtGui import QWindow
...@@ -18,22 +19,24 @@ class CProcessControl(QObject): ...@@ -18,22 +19,24 @@ class CProcessControl(QObject):
def __init__(self, parent: QObject = None, def __init__(self, parent: QObject = None,
signal_class: QObject = None, signal_class: QObject = None,
enable_internal_logging: bool = False): internal_logging: bool = False,
internal_logging_level: int = logging.DEBUG):
super().__init__(parent) super().__init__(parent)
# self._kill_child_process_flag = kill_child_process_flag # 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): if isinstance(parent, QWidget) or isinstance(parent, QWindow):
parent.destroyed.connect(lambda: self.safe_exit(reason="Parent destroyed.")) 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: if signal_class is not None:
self.register_signal_class(signal_class) self.register_signal_class(signal_class)
else: else:
self.register_signal_class(self) self.register_signal_class(self)
# The child process # The child process
self._child: Process = None self._child: cmp.CProcess = None
# Queues for data exchange # Queues for data exchange
self.cmd_queue = Queue() self.cmd_queue = Queue()
self.state_queue = Queue() self.state_queue = Queue()
...@@ -42,14 +45,23 @@ class CProcessControl(QObject): ...@@ -42,14 +45,23 @@ class CProcessControl(QObject):
self.thread_manager = QThreadPool() self.thread_manager = QThreadPool()
# The child process pid # The child process pid
self._pid = os.getpid()
self._child_process_pid = None self._child_process_pid = None
self.name = f"{self._pid}({self.__class__.__name__})"
self._child_kill_flag = Value('i', 1) self._child_kill_flag = Value('i', 1)
self._internal_logger, self._il_handler = self.create_new_logger( self._internal_logger, self._il_handler = self.create_new_logger(f"(cmp) {self.name}")
f"{self.__class__.__name__}-Int({os.getpid()})") 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.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): def create_new_logger(self, name: str) -> (logging.Logger, logging.Handler):
qh = RichHandler(rich_tracebacks=True) qh = RichHandler(rich_tracebacks=True)
_internal_logger = logging.getLogger(name) _internal_logger = logging.getLogger(name)
...@@ -60,11 +72,34 @@ class CProcessControl(QObject): ...@@ -60,11 +72,34 @@ class CProcessControl(QObject):
qh.setFormatter(formatter) qh.setFormatter(formatter)
return _internal_logger, qh return _internal_logger, qh
def enable_internal_logging(self, enable: bool): @property
self._enable_internal_logging = enable def internal_log_enabled(self):
if self._internal_logger is not None: 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 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): def register_signal_class(self, signal_class: QObject):
self._signal_class = signal_class self._signal_class = signal_class
...@@ -72,13 +107,17 @@ class CProcessControl(QObject): ...@@ -72,13 +107,17 @@ class CProcessControl(QObject):
def child_process_pid(self): def child_process_pid(self):
return self._child_process_pid return self._child_process_pid
def register_child_process(self, child: cmp.CProcess): def register_child_process(self, child: Type[cmp.CProcess], *args, **kwargs):
self._internal_logger.debug(f"Registering child process {child.__class__.__name__}.") self._internal_logger.debug(f"Registering child process.")
self._child = child self._child = child(self.state_queue, self.cmd_queue,
self._child.register_kill_flag(self._child_kill_flag) 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_process_pid = child.pid
self._child.enable_internal_logging(self._enable_internal_logging)
self._child.start() self._child.start()
self._internal_logger.debug(f"Child process {self._child.name} created.")
self.thread_manager.start(self._monitor_result_state) self.thread_manager.start(self._monitor_result_state)
def _monitor_result_state(self): def _monitor_result_state(self):
......
...@@ -5,13 +5,15 @@ from cmp import CProcessControl as CProcessControl ...@@ -5,13 +5,15 @@ from cmp import CProcessControl as CProcessControl
class CResultRecord: 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.signal_name: str = signal_name
self.result = result self.result = result
def emit_signal(self, class_object: CProcessControl): def emit_signal(self, class_object: CProcessControl):
if hasattr(class_object, '_internal_logger'): 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 emitter = getattr(class_object, self.signal_name).emit
if isinstance(self.result, tuple): if isinstance(self.result, tuple):
emitter(*self.result) emitter(*self.result)
......
...@@ -10,8 +10,8 @@ import unittest ...@@ -10,8 +10,8 @@ import unittest
class ChildProcessCustomSignals(cmp.CProcess): class ChildProcessCustomSignals(cmp.CProcess):
def __init__(self, state_queue, cmd_queue, enable_internal_logging): def __init__(self, state_queue, cmd_queue, internal_logging):
super().__init__(state_queue, cmd_queue, enable_internal_logging=enable_internal_logging) super().__init__(state_queue, cmd_queue, internal_logging=internal_logging)
self.logger = None self.logger = None
def postrun_init(self): def postrun_init(self):
...@@ -29,10 +29,10 @@ class ChildProcessControlCustomSignals(cmp.CProcessControl): ...@@ -29,10 +29,10 @@ class ChildProcessControlCustomSignals(cmp.CProcessControl):
# call_without_mp2_changed = Signal(int, int, int) # call_without_mp2_changed = Signal(int, int, int)
def __init__(self, parent, signal_class, enable_internal_logging): def __init__(self, parent, signal_class, internal_logging):
super().__init__(parent, signal_class, enable_internal_logging=enable_internal_logging) super().__init__(parent, signal_class, internal_logging=internal_logging)
self.register_child_process(ChildProcessCustomSignals(self.state_queue, self.cmd_queue, 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() @cmp.CProcessControl.register_function()
def call_without_mp(self, a, b, c=None): def call_without_mp(self, a, b, c=None):
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment