From 8e7b9a84b3421d74603333129513c9e48141ed9f Mon Sep 17 00:00:00 2001
From: Christoph Schmidt <christoph.,schmidt@tugraz.at>
Date: Wed, 6 Dec 2023 12:53:35 +0100
Subject: [PATCH] Updated to version 1.0.1 Logger can be set now. The handler
 of the logger is now defined during init.

---
 .idea/confighandler.iml                       |  2 +-
 ExampleConfig.py                              |  8 +--
 README.md                                     | 30 ++++-----
 examples/ApplicationConfig.py                 | 12 ++--
 examples/LaserConfig.py                       |  4 +-
 examples/main.py                              |  2 +-
 src/confighandler/__init__.py                 |  8 ---
 src/confighandler/controller/CObject.py       | 61 +++++++++++++++++++
 src/confighandler/controller/ConfigNode.py    | 38 ++++++------
 src/confighandler/controller/Field.py         | 37 ++++++-----
 .../controller/fields/FieldPath.py            |  4 +-
 src/confighandler/view/FieldView.py           |  1 +
 .../view/fields/FieldViewPath.py              |  2 +-
 .../view/fields/FieldViewString.py            |  4 +-
 14 files changed, 134 insertions(+), 79 deletions(-)
 create mode 100644 src/confighandler/controller/CObject.py

diff --git a/.idea/confighandler.iml b/.idea/confighandler.iml
index 42e1ed6..d8e2c80 100644
--- a/.idea/confighandler.iml
+++ b/.idea/confighandler.iml
@@ -5,7 +5,7 @@
       <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
       <excludeFolder url="file://$MODULE_DIR$/.venv" />
     </content>
-    <orderEntry type="jdk" jdkName="Python 3.12 (confighandler)" jdkType="Python SDK" />
+    <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
   </component>
   <component name="PackageRequirementsSettings">
diff --git a/ExampleConfig.py b/ExampleConfig.py
index 35144ab..64a874b 100644
--- a/ExampleConfig.py
+++ b/ExampleConfig.py
@@ -13,9 +13,9 @@ import confighandler as cfg
 
 class SecondConfig(cfg.ConfigNode):
 
-    def __init__(self, enable_log=True) -> None:
+    def __init__(self, internal_log=True) -> None:
         # Call the base class (important!)
-        super().__init__(enable_log=enable_log)
+        super().__init__(internal_log=internal_log)
 
         # Some fields
         # Create a field of type int. Set a default value, a friendly name and a description
@@ -27,9 +27,9 @@ class SecondConfig(cfg.ConfigNode):
 
 class ApplicationConfig(cfg.ConfigNode):
 
-    def __init__(self, enable_log=True) -> None:
+    def __init__(self, internal_log=True) -> None:
         # Call the base class (important!)
-        super().__init__(enable_log=enable_log)
+        super().__init__(internal_log=internal_log)
 
         # Some fields
         # Create a field of type int. Set a default value, a friendly name and a description
diff --git a/README.md b/README.md
index 56e84e1..eaf3c0e 100644
--- a/README.md
+++ b/README.md
@@ -34,47 +34,49 @@ has three advantages:
 1. You can define the type of the fields. You can give them friendly names and descriptions.
 2. The parsing of the config file is much easier, because the parser knows the type of the fields.
 3. When working with this library, you can use the auto completion of your IDE, since it is a class.
+
 ```python
 import confighandler as cfg
 
+
 class SecondConfig(cfg.ConfigNode):
-    
+
     def __init__(self, enable_log=True) -> None:
         # Call the base class (important!)
-        super().__init__(enable_log=enable_log)
-        
+        super().__init__(internal_log=enable_log)
+
         # Some fields
         # Create a field of type int. Set a default value, a friendly name and a description
         self.test_int: cfg.Field[int] = cfg.Field(1,
-                                              friendly_name="My Test Int",
-                                              description="This is just an integer")  
-#
+                                                  friendly_name="My Test Int",
+                                                  description="This is just an integer")
+    #
+
+
 class ApplicationConfig(cfg.ConfigNode):
-    
+
     def __init__(self, enable_log=True) -> None:
         # Call the base class (important!)
-        super().__init__(enable_log=enable_log)
-        
+        super().__init__(internal_log=enable_log)
+
         # Some fields
         # Create a field of type int. Set a default value, a friendly name and a description
         self.counter: cfg.Field[int] = cfg.Field(1,
                                                  friendly_name="My Counter",
                                                  description="This is just an integer")
 
-        
         self.version: cfg.Field[str] = cfg.Field("v1.0",
                                                  friendly_name="Version",
                                                  description="The version")
-        
+
         # You can also omit the friendly name and description
         self.check: cfg.Field[bool] = cfg.Field(False)
-        
 
         # Some other fields
         # Also possible to create a field of type list
-        self.my_tuple: cfg.Field[tuple] = cfg.Field((1,2))
+        self.my_tuple: cfg.Field[tuple] = cfg.Field((1, 2))
         self.any_list: cfg.Field[list] = cfg.Field([1, 2])
-        
+
         # Even a nested config is possible
         self.second_config: SecondConfig = SecondConfig()
 
diff --git a/examples/ApplicationConfig.py b/examples/ApplicationConfig.py
index 0ce64ed..9111fe6 100644
--- a/examples/ApplicationConfig.py
+++ b/examples/ApplicationConfig.py
@@ -1,3 +1,4 @@
+import logging
 import sys
 from pathlib import Path
 sys.path.append('../src/')
@@ -8,12 +9,10 @@ from LaserConfig import LaserConfig
 
 class ApplicationConfig(cfg.ConfigNode):
 
-    def __init__(self, enable_log=True) -> None:
-        super().__init__(enable_log=enable_log)
+    def __init__(self, internal_log=True, internal_log_level= logging.DEBUG) -> None:
+        super().__init__(internal_log=internal_log, internal_log_level=internal_log_level)
 
-        self.output_directory: cfg.Field[Path] = cfg.Field(Path("C:\\{wafer_nr}"),
-                                                           friendly_name="Output Directory",
-                                                           description="The version of the wafer")
+        self.output_directory: cfg.Field[Path] = cfg.Field(Path("C:\\{wafer_nr}"))
 
         self.wafer_version: cfg.Field[str] = cfg.Field("v1.0",
                                                        friendly_name="wafer_version",
@@ -39,7 +38,8 @@ class ApplicationConfig(cfg.ConfigNode):
                                                      friendly_name="wafer_list",
                                                      description="The version of the wafer")
 
-        self.laser_config: LaserConfig = LaserConfig()
+        self.laser_config: LaserConfig = LaserConfig(internal_log=internal_log,
+                                                     internal_log_level=internal_log_level)
 
 
         self.register()
diff --git a/examples/LaserConfig.py b/examples/LaserConfig.py
index ce3b5c7..3d1956b 100644
--- a/examples/LaserConfig.py
+++ b/examples/LaserConfig.py
@@ -3,8 +3,8 @@ import confighandler as cfg
 
 class LaserConfig(cfg.ConfigNode):
 
-    def __init__(self, enable_log=False) -> None:
-        super().__init__(enable_log=enable_log)
+    def __init__(self, internal_log, internal_log_level) -> None:
+        super().__init__(internal_log=internal_log, internal_log_level=internal_log_level)
         self.wavelength_range = cfg.Field(850)
         self.velocity = cfg.Field(2.0)
         self.acceleration = cfg.Field(1.0)
diff --git a/examples/main.py b/examples/main.py
index 5eb5459..b01d12a 100644
--- a/examples/main.py
+++ b/examples/main.py
@@ -12,7 +12,7 @@ if __name__ == "__main__":
 
     # setup the logging module
 
-    config = ApplicationConfig(enable_log=True)
+    config = ApplicationConfig(internal_log_level=logging.INFO)
     #print(config.load('./configs/ApplicationConfig.yaml'))
     #config.autosave(enable=True, path='./configs_autosave')
 
diff --git a/src/confighandler/__init__.py b/src/confighandler/__init__.py
index 89aeb3f..3d96edc 100644
--- a/src/confighandler/__init__.py
+++ b/src/confighandler/__init__.py
@@ -3,14 +3,6 @@ import os
 import sys
 
 from rich.logging import RichHandler
-
-FORMAT = "%(message)s"
-logging.basicConfig(
-    level="DEBUG", format=FORMAT, datefmt="[%X]", handlers=[
-        RichHandler(rich_tracebacks=True)
-    ]
-)
-
 sys.path.append(os.path.join(os.path.dirname(__file__), '../'))
 from .controller.Field import Field
 from .controller.ConfigNode import ConfigNode
\ No newline at end of file
diff --git a/src/confighandler/controller/CObject.py b/src/confighandler/controller/CObject.py
new file mode 100644
index 0000000..aac7580
--- /dev/null
+++ b/src/confighandler/controller/CObject.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+"""
+Author(s): Christoph Schmidt <christoph.schmidt@tugraz.at>
+Created: 2023-10-19 12:35
+Package Version: 
+Description: 
+"""
+import logging
+import os
+
+from rich.logging import RichHandler
+
+class CObject:
+
+    def __init__(self, name: str = ""):
+        if name is None or name == "":
+            self.name = f"{self.__class__.__name__.replace('Field', '')}({hash(self)})"
+        else:
+            self.name = f"{name}({os.getpid()})"
+        self._internal_logger, self._internal_log_handler = self.create_new_logger(f"(cfg) {self.name}")
+
+
+
+    # ==================================================================================================================
+    # Logging
+    # ==================================================================================================================
+    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_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)
\ No newline at end of file
diff --git a/src/confighandler/controller/ConfigNode.py b/src/confighandler/controller/ConfigNode.py
index 9f0d70b..a181216 100644
--- a/src/confighandler/controller/ConfigNode.py
+++ b/src/confighandler/controller/ConfigNode.py
@@ -11,26 +11,31 @@ import logging
 
 import yaml
 from PySide6.QtWidgets import QMessageBox
+from rich.logging import RichHandler
 
+from .CObject import CObject
 from .CSignal import CSignal
 from .Field import Field
 from ..view.ConfigView import ConfigView
 
 import pathlib
+import rich
 
-
-class ConfigNode(object):
+class ConfigNode(CObject):
     field_changed = CSignal()
 
     cur_time = datetime.datetime.now()
 
-    def __init__(self, enable_log: bool = False):
+    def __init__(self, internal_log, internal_log_level):
         super().__init__()
 
         self._autosave = False
-        self.enable_log = enable_log
+
+        self.internal_log_enabled = internal_log
+        self.internal_log_level = internal_log_level
+
         self.name = self.__class__.__name__
-        self.logger = logging.getLogger(self.name)
+        self.logger, self.log_handler = self.create_new_logger(self.name)
 
         self.owner = None
         self._level = 0
@@ -46,7 +51,7 @@ class ConfigNode(object):
         }
 
         self.field_changed.connect(self._on_field_changed)
-        self.config_logger(enable=self.enable_log)
+
 
 
     # ==================================================================================================================
@@ -100,7 +105,7 @@ class ConfigNode(object):
 
     def deserialize(self, content):
         """Deserializes the content of the config based on the yaml file"""
-        self.logger.info(f"Deserializing {content}")
+        self._internal_logger.info(f"Deserializing {content}")
         for attr, val in content.items():
             # Check if the attribute is not of type GenericConfig
             # therefore get the attribute type of this class
@@ -110,11 +115,11 @@ class ConfigNode(object):
                 self.deserialize(val)
             elif attr in self.__dict__:
                 if not isinstance(getattr(self, attr), ConfigNode):
-                    self.logger.info(f"Deserializing field {attr} with content: {val}")
+                    self._internal_logger.info(f"Deserializing field {attr} with content: {val}")
                     val = getattr(self, attr)._field_parser(val)
                     getattr(self, attr).set(val)
                 else:
-                    self.logger.info(f"Deserializing config {attr} with content: {val}")
+                    self._internal_logger.info(f"Deserializing config {attr} with content: {val}")
                     getattr(self, attr).deserialize(val)
 
 
@@ -134,7 +139,8 @@ class ConfigNode(object):
                 self.fields[attr] = val
                 # val.register(self.keywords, self.view.keywords_changed)
                 val.register(self.__class__.__name__, attr, self.keywords, self.field_changed)
-                val.config_logger(enable=self.enable_log)
+                val.internal_log_enabled = self.internal_log_enabled
+                val.internal_log_level = self.internal_log_level
         self.view.keywords_changed.emit(self.keywords)
 
     def _register_config(self):
@@ -157,7 +163,7 @@ class ConfigNode(object):
             stream.write(self.serialize())
             # print(self.serialize())
         if not background_save:
-            self.logger.debug(f"Saved config to {file}")
+            self._internal_logger.debug(f"Saved config to {file}")
         # with open(file, 'w+') as stream:
         #    yaml.dump(self, stream) # Dump it as a xaml file
         # with open(file, 'w+') as stream:
@@ -196,11 +202,5 @@ class ConfigNode(object):
                 # Saves on every field change
                 self.save(file=file, background_save=True)
 
-    # ==================================================================================================================
-    # Miscs
-    # ==================================================================================================================
-    def config_logger(self, enable: bool = True, level: str = "DEBUG"):
-        self.logger.warning(f"Disabled logging for {self.name}")
-        self.logger.disabled = not enable
-        for attr, val in self.fields.items():
-            val.enable_log(enable, level)
+
+
diff --git a/src/confighandler/controller/Field.py b/src/confighandler/controller/Field.py
index c57f07f..b634f06 100644
--- a/src/confighandler/controller/Field.py
+++ b/src/confighandler/controller/Field.py
@@ -11,6 +11,7 @@ import re
 from pathlib import Path
 from typing import Generic, T, TypeVar
 
+from confighandler.controller.CObject import CObject
 from confighandler.controller.CSignal import CSignal
 from confighandler.view.FieldView import FieldView
 
@@ -24,14 +25,15 @@ class FieldData(object):
 T = TypeVar('T')
 
 
-class Field(Generic[T]):
+class Field(Generic[T], CObject):
 
-    def __init__(self, value: T, friendly_name: str = None, description: str = None, enable_log: bool = False):
+    def __init__(self, value: T, friendly_name: str = None, description: str = None,
+                 internal_log=True, internal_log_level=logging.INFO):
         super().__init__()
-        self.enable_log = enable_log
-        self.name = self.__class__.__name__
-        self.logger = logging.getLogger(self.name)
-        self.config_logger(enable=self.enable_log)
+        self.internal_log_enabled = internal_log
+        self.internal_log_level = internal_log_level
+
+        self.logger, self.log_handler = self.create_new_logger(self.name)
 
         self._data = FieldData(self.name, value, friendly_name, description)
 
@@ -43,7 +45,8 @@ class Field(Generic[T]):
 
         # The view, usd for handling the UI
         self.view = FieldView(self)
-        self.logger.debug(f"Field {self.__class__.__name__} created with value {value} of type {type(value)}")
+
+        self._internal_logger.debug(f"Field {self.__class__.__name__} created with value {value} of type {type(value)}")
 
     def __new__(cls, value, friendly_name: str = None, description: str = None):
         # print(f"Field {cls.__name__} created with value {value} of type {type(value)} -> {isinstance(value, int)}")
@@ -85,8 +88,13 @@ class Field(Generic[T]):
         :param keywords: The keywords dict
         :param csig_field_changed: The signal that is emitted when the keywords are changed
         """
+
+
         self.name = name
         self.owner = owner
+        formatter = logging.Formatter(f'%(name)s [{self.name}] %(message)s')
+
+        self._internal_log_handler.setFormatter(formatter)
         if self._friendly_name is None:
             self._friendly_name = self.name
 
@@ -97,7 +105,7 @@ class Field(Generic[T]):
         # self.keywords_changed = keyword_changed
         # self.keywords_changed.connect(self._on_keyword_changed)
         self.set_keywords()
-        self.logger.info(f"Field assigned to {self.owner} with name {self.name}")
+        self._internal_logger.info(f"Field '{self.name}' assigned to {self.owner}")
 
     # ==================================================================================================================
     # Set the keywords for the field
@@ -105,7 +113,7 @@ class Field(Generic[T]):
     def set_keywords(self):
         """Set the keywords for the field. Also updates the keywords dict if a value of a field is changed."""
         # self.keywords["{" + self.name + "}"] = self.value
-        # self.logger.info(f"Setting keywords for {self.name} to {self.value}")
+        # self._internal_logger.info(f"Setting keywords for {self.name} to {self.value}")
         self.keywords[self.name] = str(self.value).replace(' ', '_').replace('.', '_').replace(',', '_')
         self.csig_field_changed.emit()
 
@@ -148,7 +156,7 @@ class Field(Generic[T]):
 
     def set(self, value: T):
         if not self.value == value:
-            self.logger.info(f"{self.name} = {value} ({type(value)})")
+            self._internal_logger.info(f"{self.name} = {value} ({type(value)})")
             self._set(value)
             self.set_keywords()
             # self.view.value_changed.emit(self.value)
@@ -178,12 +186,3 @@ class Field(Generic[T]):
     def __repr__(self):
         return str(f"{self.__class__.__name__}({self.value})")
 
-    # ==================================================================================================================
-    # Miscs
-    # ==================================================================================================================
-    def config_logger(self, enable: bool = True, level: str = "DEBUG"):
-        if enable:
-            self.logger.info(f"Enabled logging for {self.name}")
-        else:
-            self.logger.warning(f"Disabled logging for {self.name}")
-        self.logger.disabled = not enable
diff --git a/src/confighandler/controller/fields/FieldPath.py b/src/confighandler/controller/fields/FieldPath.py
index 51ef60c..d9cc2fb 100644
--- a/src/confighandler/controller/fields/FieldPath.py
+++ b/src/confighandler/controller/fields/FieldPath.py
@@ -37,10 +37,10 @@ class FieldPath(Field):
         # Overwritten function, to replace the @Path keyword
         match = re.findall(r'@Path:<([^>]*)>', val)
         if len(match) > 0:
-            self.logger.info(f"Found @Path: {val}. Check syntax, multiple @Path: are not allowed in one field.")
+            self._internal_logger.info(f"Found @Path: {val}. Check syntax, multiple @Path: are not allowed in one field.")
             return Path(match[0])
         elif len(match) == 0:
-            self.logger.debug(f"No @Path: found in {val}. Please check field.")
+            self._internal_logger.debug(f"No @Path: found in {val}. Please check field.")
             return Path('./')
     def __str__(self):
         return str(Path(self.value).as_posix())
\ No newline at end of file
diff --git a/src/confighandler/view/FieldView.py b/src/confighandler/view/FieldView.py
index 2ea4337..26f7468 100644
--- a/src/confighandler/view/FieldView.py
+++ b/src/confighandler/view/FieldView.py
@@ -12,6 +12,7 @@ from PySide6.QtCore import Signal
 from PySide6.QtWidgets import QWidget, QTreeWidgetItem, QMessageBox
 
 import confighandler.controller.Field as Field
+from confighandler.controller.CObject import CObject
 
 
 class FieldView(QWidget):
diff --git a/src/confighandler/view/fields/FieldViewPath.py b/src/confighandler/view/fields/FieldViewPath.py
index cc0af70..ab8dd10 100644
--- a/src/confighandler/view/fields/FieldViewPath.py
+++ b/src/confighandler/view/fields/FieldViewPath.py
@@ -52,7 +52,7 @@ class FieldViewPath(FieldView):
         grd.addWidget(btn_open, 0, 1)
         grd.addWidget(self.ui_edit_fields_lbl[-1], 1, 0, 1, 2)
 
-        self.parent_field.logger.info(f"Registered QEditField for {self.ui_edit_fields[-1]}")
+        self.parent_field._internal_logger.info(f"Registered QEditField for {self.ui_edit_fields[-1]}")
 
 
         wdg.setLayout(grd)
diff --git a/src/confighandler/view/fields/FieldViewString.py b/src/confighandler/view/fields/FieldViewString.py
index 50b6af7..5d1c89b 100644
--- a/src/confighandler/view/fields/FieldViewString.py
+++ b/src/confighandler/view/fields/FieldViewString.py
@@ -31,14 +31,14 @@ class FieldViewString(FieldView):
             le: QLineEdit = view
         le.setToolTip(f"({self.parent_field.name}) {self.parent_field._description}")
         self.ui_edit_fields.append(le)
-        self.parent_field.logger.debug(f"Registering LineEdit {le}")
+        self.parent_field._internal_logger.debug(f"Registering LineEdit {le}")
         self.ui_edit_fields[-1].textEdited.connect(lambda d: self._on_text_edited(le, d))
 
         # new
         return le
 
     def _on_text_edited(self, f, value):
-        self.parent_field.logger.debug(f"LineEdit {f} changed to {value}.")
+        self.parent_field._internal_logger.debug(f"LineEdit {f} changed to {value}.")
         self.parent_field.set(value)
 
     def _on_value_changed(self, value):
-- 
GitLab