Search code examples
pythonclassiniconfigparser

Python: Working with configparser inside a class. How to access my attributes names and values dynamically inside my class-methods


My first program is getting much bigger than excepted. :)

import configparser

config = configparser.ConfigParser()    
configfile = 'RealGui.ini'
      
class FolderLeftSettings:
    def __init__(self):
        self.name = "SECTION"
        self.last_directory = ""
        self.row_size = 6
        self.column_size = 9

    def write_to_ini(self):
        if not config.has_section(str(self.name)):
            config.add_section(str(self.name))
        with open('configfile', 'w') as configfile:
            config.set(self.name, 'last_directory', str(self.last_directory))
            config.set(self.name, 'row_size', str(self.row_size))
            config.set(self.name, 'column_size', str(self.column_size))
            config.write(configfile)

    def read_from_ini(self):
        try:
            config.read('configfile')
            if config.has_section(str(self.name)):
                self.last_directory = (config[self.name]['last_directory'])
                self.row_size = int((config[self.name]['row_size']))
                self.column_size = int((config[self.name]['column_size']))
        except Exception as e:
            print("failed to read ini....overwriting with defaults")
            print(e)
            self.write_to_ini()

 settings=FolderLeftSettings()  

My problem was, that every setting from the init needs to be written manually in the 2 methods as well. I found the solution, and the answer is on the bottom. Working Example!


Solution

  • OK I finally solved it, and decided to share, in hope it helps someone else.

    You can test it yourself....code is working even without a .ini file(will be created) Now I can add as many settings in init as I want, without editing the other 2 methods as well.

    As a Bonus, now the values gets correctly parsed from the .ini into the variables. Meaning tuple will become tuple. Not supported normally from configparser. Additionally it checks whether the TYPE in the default section corresponds with the type inside the .ini...if not, ONLY the wrong line gets changed back to default. Except when str is expected, than all types in the ini pass. Which is somehow correct. Like a username is default a string, but custom username could be integer only, or even a tuple..lol. And if integer is expected, True and False pass as integer in ini, what is also technically correct..lol. But anything else gets found, eg. a broken tuple in the ini gets defaulted back, if a tuple is expected. If anything else is broken, ALL defaults are getting written. It supports the correct casting of (bool, tuple, int, float, set, dict, list) and of course str. Bool needed some special love,...now True/False upper/lowercase and inside "" or '' are supportet.

    ############################################### SETTINGS START ########################################################
    import configparser
    import logging.config
    from ast import literal_eval
    
    datatypes = (tuple, int, float, set, dict, list)
    
    class Bcolors:
        HEADER = "\033[95m"
        OKBLUE = "\033[94m"
        OKCYAN = "\033[96m"
        OKGREEN = "\033[92m"
        YELLOW = "\033[93m"
        FAIL = "\033[91m"
        ENDC = "\033[0m"
        BOLD = "\033[1m"
        UNDERLINE = "\033[4m"
    
    class SettingsGeneral:
        config = configparser.ConfigParser()
        configfile = "RealGui.ini"
        section = "GENERAL"
    
        def __init__(self):
            self.window_last_position = True
            self.window_last_size = [1500, 1060]
            self.filename_formatting = (33, 2)  # (33, 2) #2 rows
            self.graphics_directory = "G:\+++CODING+++\Thin-Plate-Spline-Motion-Model-Windows-main\RealGui\graphics\\"
            self.extensions = (".gif", ".png", ".jpg")
    
        def read_from_ini(self):
            tmp = list(self.__dict__.items())  #  This is the magic I was looking for. Need to be converted to a list, or a deep copy, because you cannot iterate over a dic and change it.
            try:
                self.config.read(self.configfile)
                for attr_name, attr_value in tmp:
                        inivalue = self.config[self.section][attr_name]
                        datatype = type(attr_value)
                        if datatype == bool:
                            if ''.join(e for e in inivalue.lower() if e.isalnum()) == 'false':
                                setattr(self, attr_name, bool(False))
                            elif ''.join(e for e in inivalue.lower() if e.isalnum()) == 'true':
                                setattr(self, attr_name, bool(True))
                            else:
                                logging.warning(f"{Bcolors.FAIL}invalid .ini entry: {attr_name} = \"{inivalue}\" ...expected type {datatype} ...overwriting with default{Bcolors.ENDC}")
                                self.config.set(self.section, attr_name, str(attr_value))
                        elif datatype in datatypes:
                            try:
                                if isinstance(literal_eval(inivalue), datatype):
                                    setattr(self, attr_name, literal_eval(inivalue))
                                else:
                                    logging.warning(f"{Bcolors.FAIL}invalid .ini entry: {attr_name} = \"{inivalue}\" ...expected type {datatype} ...overwriting with default{Bcolors.ENDC}")
                                    self.config.set(self.section, attr_name, str(attr_value))
                            except Exception as e:
                                logging.warning(f"{Bcolors.YELLOW}invalid .ini entry: {attr_name} = \"{inivalue}\" ....overwriting with default{Bcolors.ENDC}")
                                logging.warning(f"{Bcolors.FAIL}{e}{Bcolors.ENDC}")
                                self.config.set(self.section, attr_name, str(attr_value))
                        elif datatype == str:
                                if isinstance(inivalue, datatype):
                                    setattr(self, attr_name, inivalue)
            except Exception as e:
                logging.warning(f"{Bcolors.FAIL}failed to read ini..Section:{self.section}....overwriting with defaults{Bcolors.ENDC}")
                logging.warning(f"{Bcolors.FAIL}{e}{Bcolors.ENDC}")
            self.write_to_ini()
    
        def write_to_ini(self):
            with open(self.configfile, "w") as cfg:
                tmp = list(self.__dict__.items())  #  This is the magic I was looking for. Need to be converted to a list, or a deep copy, because you cannot iterate over a dic and change it.
                if not self.config.has_section(str(self.section)):
                    self.config.add_section(str(self.section))
                for attr_name, attr_value in tmp:
                    self.config.set(self.section, attr_name, str(attr_value))
                self.config.write(cfg)
    
    
    class FolderLeftSettings(SettingsGeneral):
        config = configparser.ConfigParser()
        section = "FOLDER-LEFT"
        configfile = "RealGui.ini"
        def __init__(self):
            self.last_directory = "C:\\"
            self.row_size = 8
            self.column_size = 9
    
    ############################################### SETTINGS END ########################################################
    

    and in main it can be used normally like:

    settings=FolderLeftSettings()
    
    print(settings.last_directory)  # Default Value according to __init__
    
    settings.read_from_ini()
    print(settings.last_directory)  # if .ini is correctly read..show new value
    
    settings.last_directory ="Z:\\"
    settings.write_to_ini()
    print(settings.last_directory)  # New updated Value...no need to read from .ini again of course