From f7d757cd5dcd66c68bb7ebb3c40f7311ed0ff95d Mon Sep 17 00:00:00 2001 From: Christoph Schmidt <christoph.,schmidt@tugraz.at> Date: Mon, 27 Nov 2023 12:52:35 +0100 Subject: [PATCH] Updated to version 1.0.0 Added new example. Updated README.md --- .idea/confighandler.iml | 3 + ExampleConfig.py | 56 ++++++++ README.md | 175 ++++++++++++++++++++--- examples/main.py | 10 +- pyproject.toml | 2 +- requirements.txt | 3 + src/confighandler/__init__.py | 14 ++ src/confighandler/controller/__init__.py | 11 ++ src/confighandler/view/__init__.py | 11 ++ 9 files changed, 255 insertions(+), 30 deletions(-) create mode 100644 ExampleConfig.py create mode 100644 requirements.txt create mode 100644 src/confighandler/controller/__init__.py create mode 100644 src/confighandler/view/__init__.py diff --git a/.idea/confighandler.iml b/.idea/confighandler.iml index 0f34eda..42e1ed6 100644 --- a/.idea/confighandler.iml +++ b/.idea/confighandler.iml @@ -8,6 +8,9 @@ <orderEntry type="jdk" jdkName="Python 3.12 (confighandler)" jdkType="Python SDK" /> <orderEntry type="sourceFolder" forTests="false" /> </component> + <component name="PackageRequirementsSettings"> + <option name="keepMatchingSpecifier" value="false" /> + </component> <component name="PyNamespacePackagesService"> <option name="namespacePackageFolders"> <list> diff --git a/ExampleConfig.py b/ExampleConfig.py new file mode 100644 index 0000000..35144ab --- /dev/null +++ b/ExampleConfig.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +""" +Author(s): Christoph Schmidt <christoph.schmidt@tugraz.at> +Created: 2023-10-19 12:35 +Package Version: +Description: +""" +import sys + +sys.path.append("../src") +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) + + # 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") + self.register() + + +class ApplicationConfig(cfg.ConfigNode): + + def __init__(self, enable_log=True) -> None: + # Call the base class (important!) + super().__init__(enable_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.any_list: cfg.Field[list] = cfg.Field([1, 2]) + + # Even a nested config is possible + self.second_config: SecondConfig = SecondConfig() + + # Don't forget to register the fields (important!) + self.register() diff --git a/README.md b/README.md index b622602..56e84e1 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,172 @@ # ConfigHandler ## Description -This module is used to handle configuration files for flexsensor. -It hgas been designed, so the user can create a config with an UI, automatically connected to the signal and slots. +This module is used to handle configuration files for FlexSensor. +It has been designed, so the user can create a config with a UI, automatically connected to the signal and slots. ## Requirements Install the requirements with pip: -```python -pip install pyside6, rich, pyyaml +```bash +pip install -r requirements.txt ```` +or +```bash +pip install PySide6 PyYAML rich +``` +## Installing this repo to yout project using pip +Just add the following line to your requirements.txt +```bash +git+https://gitlab.tugraz.at/flexsensor-public/lasercontrol.git@<branch> +# e.g., from branch main +git+https://gitlab.tugraz.at/flexsensor-public/lasercontrol.git@main +``` +oder directly using pip (without requirements.txt) +```bash +# or manually +pip install git+https://gitlab.tugraz.at/flexsensor-public/lasercontrol.git@<branch> +``` ## Usage -The Usage is straight forward. Just create a new ```ConfigNode``` object and call the show() method. +Example files can be found in `./examples`. +The usage is straight forward. Just create a new ```ConfigNode``` object and call the show() method. +### Creating a config +Before you can start working woith the config, you need to create some kind of sceleton. This +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 ApplicationConfig(ConfigNode): - - def __init__(self) -> None: +class SecondConfig(cfg.ConfigNode): + + def __init__(self, enable_log=True) -> None: # Call the base class (important!) - super().__init__() - + super().__init__(enable_log=enable_log) + # Some fields - self.output_directory: Field[Path] = Field(Path("C:\\{wafer_nr}")) - self.wafer_version: Field[str] = Field("v1.0") - self.wafer_number: Field[int] = Field(1) - # A field with an description - self.wafer_nr: Field[str] = Field("12345ABCD_{wafer_number}", - friendly_name="Wafer Number", - description="The version of the wafer") + # 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") +# +class ApplicationConfig(cfg.ConfigNode): + + def __init__(self, enable_log=True) -> None: + # Call the base class (important!) + super().__init__(enable_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 - self.my_tuple: Field[tuple] = Field((1,2)) - self.any_list: Field[list] = Field([1, 2]) + # Also possible to create a field of type list + 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.other_config: OtherConfig = OtherConfig() + self.second_config: SecondConfig = SecondConfig() # Don't forget to register the fields (important!) self.register() -``` \ No newline at end of file +``` + +### Loading and saving a config +It is possible to load and save a config. The config is saved as a yaml file. +```python +from ApplicationConfig import ApplicationConfig + +if __name__ == "__main__": + config = ApplicationConfig(enable_log=True) + config.save('./configs/ApplicationConfig.yaml') + config.load('./configs/ApplicationConfig.yaml') +``` + +### Autosaving +It is also possible to autosave a config. This is useful, if you want to save the config, when the user changes a value. +```python +from ApplicationConfig import ApplicationConfig + +if __name__ == "__main__": + config = ApplicationConfig(enable_log=True) + config.autosave(enable=True, path='./configs_autosave') +``` + +### Creating a UI +The condifg handler build an UI in the background. You can access the widget using +```python +import logging +import sys + +from PySide6 import QtWidgets +from PySide6.QtWidgets import QApplication, QMainWindow, QWidget, QTreeWidget + +from ApplicationConfig import ApplicationConfig + +if __name__ == "__main__": + # Creating the UI + window = QMainWindow() + wdg = QWidget() + grd = QtWidgets.QGridLayout() + wdg.setLayout(grd) + + # Add the config to the UI + config = ApplicationConfig(enable_log=True) + conf_view = config.view.widget() + grd.addWidget(conf_view, 0, 0) + + window.setCentralWidget(wdg) + window.show() + sys.exit(app.exec()) +``` +or using a tree view (which is sometimes more useful) +```python +import logging +import sys + +from PySide6 import QtWidgets +from PySide6.QtWidgets import QApplication, QMainWindow, QWidget, QTreeWidget + +from ApplicationConfig import ApplicationConfig + +if __name__ == "__main__": + # Creating the UI + window = QMainWindow() + wdg = QWidget() + grd = QtWidgets.QGridLayout() + wdg.setLayout(grd) + + # Add the config to the UI + config = ApplicationConfig(enable_log=True) + # Create a tree view + tree = QTreeWidget() + tree.setColumnCount(3) + tree.setHeaderLabels(["Name", "Type", "Description"]) + + # Get the tree item + config_item = config.view.ui_tree_widget_item(tree) + + tree.addTopLevelItem(config_item) + grd.addWidget(tree, 2, 0) + + window.setCentralWidget(wdg) + window.show() + sys.exit(app.exec()) +``` +## Troubleshoting +When working insde the exmaples folder, you need to add the confighandler folder to the python path. +```python +import sys +sys.path.append('../src/') +``` diff --git a/examples/main.py b/examples/main.py index 4eac1a3..5eb5459 100644 --- a/examples/main.py +++ b/examples/main.py @@ -11,16 +11,10 @@ if __name__ == "__main__": app = QApplication(sys.argv) # setup the logging module - FORMAT = "%(message)s" - logging.basicConfig( - level="DEBUG", format=FORMAT, datefmt="[%X]", handlers=[ - RichHandler(rich_tracebacks=True) - ] - ) config = ApplicationConfig(enable_log=True) - print(config.load('./configs/ApplicationConfig.yaml')) - config.autosave(enable=True, path='./configs_autosave') + #print(config.load('./configs/ApplicationConfig.yaml')) + #config.autosave(enable=True, path='./configs_autosave') #print(config.wafer_version) #config.wafer_version.get() diff --git a/pyproject.toml b/pyproject.toml index 6338df0..9791031 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "confighandler" -version = "0.0.3" +version = "1.0.0" authors = [ { name="Christoph Schmidt", email="cschmidt.fs@gmail.com" }, ] diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..23b0de5 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +PySide6 +PyYAML +rich \ No newline at end of file diff --git a/src/confighandler/__init__.py b/src/confighandler/__init__.py index bfa7857..89aeb3f 100644 --- a/src/confighandler/__init__.py +++ b/src/confighandler/__init__.py @@ -1,2 +1,16 @@ +import logging +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/__init__.py b/src/confighandler/controller/__init__.py new file mode 100644 index 0000000..62c0acc --- /dev/null +++ b/src/confighandler/controller/__init__.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +""" +Author(s): Christoph Schmidt <christoph.schmidt@tugraz.at> +Created: 2023-10-19 12:35 +Package Version: +Description: +""" +import os +import sys + +sys.path.append(os.path.join(os.path.dirname(__file__), '../')) \ No newline at end of file diff --git a/src/confighandler/view/__init__.py b/src/confighandler/view/__init__.py new file mode 100644 index 0000000..62c0acc --- /dev/null +++ b/src/confighandler/view/__init__.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +""" +Author(s): Christoph Schmidt <christoph.schmidt@tugraz.at> +Created: 2023-10-19 12:35 +Package Version: +Description: +""" +import os +import sys + +sys.path.append(os.path.join(os.path.dirname(__file__), '../')) \ No newline at end of file -- GitLab