Home > Blockchain >  Python: Working with configparser inside a class. How to access my attributes names and values dynam
Python: Working with configparser inside a class. How to access my attributes names and values dynam

Time:11-15

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!

CodePudding user response:

My comments were getting lengthy, so here is a post:

Dynamically Accessing Attributes

As per this answer, you can access attributes for an instance using vars(my_object).

It became more clear from our discussion, because the original post didn't include some details, but you can access instance variables, like so as the OP determined from the link above:

{key: value for key, value in self.__dict__.items() if not key.startswith("__")}

but at that point, it sounds like you should be considering, Abstract Base Classes and why / how you would use them.

Accessing Static Attributes

Hopefully I'm not missing your question, but as it stands now ("How to access my attributes name and values inside my class-methods?"), you already know how to access your attributes and functions available to your class - you were doing that with self. If you mean after you've created an object from your class, then its almost no different:

settings=FolderLeftSettings()
print(settings.name)

will return the name that was given to it. If you want to set them, you can do:

settings.name = "whatever"

Exceptions

Check out this link in regards to how you should use general exceptions.

What's funny is, your side note about the broad exceptions "(wrong header, missing X, integer is string, and so on)" is actually how you would catch those exceptions,

except (ValueError, OSError)

and so on. So if you know what could go wrong, you could replace your broad except with a tuple of exceptions that you are expecting. In some since, the exceptions have been handled. Consider an ini file that is so corrupt that your parser can't handle it. What would you want your parser to do? I'd want mine to exit with an error code. If you don't want that, why not? Catching the exceptions allows for you to handle the bad data that might potentially be passed, but if not enough data is passed (like if I can't load ini file), then you can't really do anything with that object, because you don't know what the user wants to do to the data - at that point it'd be better if the object didn't even exist. At the end of the day, it largely depends on context. If you wanted to build a GUI and allow users to use your parser through it, I wouldn't want the GUI to crash, so I'd have to do more error handling, but if you're using a CLI, you'd want it to either just exit, or (lets say you were using a loop to iterate over the files) you'd want it to skip that file or take defaults. Let me take the one method that utilizes the try, except clause and recode it to how I would have it for you to consider it ->

From the docs:

If a file named in filenames cannot be opened, that file will be ignored. This is designed so that you can specify an iterable of potential configuration file locations (for example, the current directory, the user’s home directory, and some system-wide directory), and all existing configuration files in the iterable will be read.

def read_from_ini(self):
    # the below doesn't throw an error - they handle exceptions on the read() call.
    config.read('configfile')
    # the if statement won't throw an error, unless self.name is not a datatype
    # handled by has_section, but if you convert it to a str() in your __init__ 
    # method, you wont have to worry about that.
    if config.has_section(self.name):
        try:
            # not going to throw an error, unless it doesn't have the key
            # I didn't check the docs to see if it will always have this key
            # or not, so I am including it under the try clause...
            self.last_directory = config[self.name]['last_directory']
            # the 2 below might throw a value error, but not likely, since you
            # are reading the integers from the config as opposed to taking user
            # input and setting them
            self.row_size = int((config[self.name]['row_size']))
            self.column_size = int((config[self.name]['column_size']))
        except (ValueError, KeyError):
            print(f"Failed to read {self.name}... Overwriting with defaults")
            self.write_to_ini()
    else:
        print(f"Failed to read {self.name}... Overwriting with defaults")
        self.write_to_ini()

The above can be reworked to be more elegant in my opinion, but I chose to make it more simple to understand from your section of code. Here is one way that would make it more elegant:

# returns True if it could use the settings, else False
def read_from_ini(self):
    config.read('configfile')

    if not config.has_section(self.name):
        print(f"Failed to read {self.name}... Overwriting with defaults")
        self.write_to_ini()
        return False

    try:
        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 (ValueError, KeyError):
        print(f"Failed to read {self.name}... Overwriting with defaults")
        self.write_to_ini()
        return False
    else:
        return True

Notice now, how all the tries, ifs, elses, etc are all on the same block level, simply because I inverted the original if.

Another Suggestion

I would say don't do this:

config = configparser.ConfigParser()    
configfile = 'RealGui.ini'
      
class FolderLeftSettings:
    ...

A suggestion is to either assign a config instance to the class you are creating or use 1 for all of your instances.

Option 1

class FolderLeftSettings:
    def __init__(self, configfile, name, last_directory, row_size, column_size):
        self.config = configparser.ConfigParser()
        self.configfile = configfile
        self.name = name
        self.last_directory = last_directory
        self.row_size = row_size
        self.column_size = column_size

Option 2

main.py

import my_class as mc
# here you can instantiate new configs in a for loop
files = ["path/to/config.ini", "another/path/to/new_config.ini]
for file in files:
    config = configparser.ConfigParser()
    configfile = file
    name = "dynamically_allocated_name_here"
    last_directory = "last_dir_here"
    row_size = 6
    column_size = 9
    mc.FolderLeftSettings(config, configfile, name, last_directory, row_size, columns_size)

my_class.py

class FolderLeftSettings:
    def __init__(self, config, configfile, name, last_directory, row_size, column_size):
        self.config = config
        self.configfile = configfile
        self.name = name
        self.last_directory = last_directory
        self.row_size = row_size
        self.column_size = column_size

or something of the sort.

CodePudding user response:

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. Since the .ini is not meant to be for the user (all the settings can be dynamically changed inside the program), I have used a general exception, which in my case is best, since I don't need to worry about all the exceptions configparser has, and risk termination of my program in the final built. No matter how messed up the .ini is, or exist at all, program works fine. I added correct casting of type str, int as well

import configparser
    
class FolderLeftSettings:
    config = configparser.ConfigParser()
    section = "FOLDER-LEFT"
    configfile = 'RealGui.ini'

    def __init__(self):
        self.last_directory = "C:\\"
        self.row_size = 6
        self.column_size = 9
    
    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:
                if isinstance(attr_value, int):
                    setattr(self, attr_name, int(self.config[self.section][attr_name]))
                elif isinstance(attr_value, str):
                    setattr(self, attr_name, self.config[self.section][attr_name])
        except Exception as e:
            print("failed to read ini....overwriting with defaults")
            print(e)
            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)

In the main it can be used like that:

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

If I want another section in the ini, I can just inherent the class:

class SettingsGeneral(FolderLeftSettings):
    section = "GENERAL"
    def __init__(self):
        self.filename_formatting = (33, 2)  # (33, 2) #2 rows
        self.graphics_directory = "G:\   CODING   \RealGui\graphics\\"
        self.extensions = (".gif", ".png", ".jpg")
  • Related