I.T. Security and Linux Administration

Jul 26 2012   1:25PM GMT

[Python] INI Parser Script



Posted by: Eric Hansen
Tags:
security

So, long story short I’ve been busy working on a new service for my business.  I’ve gotten it close enough to the end now I’m happy to talk about it, and thought a blog post about one of the features would be a nice way to start things off.

I’ve been learning a lot of Python over the past week or so, and have come to appreciate it a lot more than PHP (that of which I used to adore).  It does have its faults, but all-in-all it’s a fairly powerful language that is also extremely extendable.

With that being said, my service relies on user configurations.  There’s a million ways anyone can set up a configuration file, but for the ease of use I decided to use the INI file format.  Mostly for two reasons:

  1. Easier for end-users to understand (i.e.: comments can be made in-file instead of having to read an online help document)
  2. Python has an internal parser for such files

Another option I could have done was a JSON configuration file, or even XML, but as my service will be supporting both Linux and Windows, it just seemed logical to me to use a straight-forward format.  This leads me into one of the components of my service, the configuration parser.

A link to this will be at the bottom of the page, but I’ll be going over the code as well, like usual.  This post will also be covering some points of Python for those who don’t know how to use it or know nothing about it.

from ConfigParser import SafeConfigParser
Python’s native INI file parser is called SafeConfigParser, which is derived from ConfigParser, which offers a lot of skeleton functionality for various other systems.  Instead of including ALL of the information from ConfigParser, we will only be interested in the SafeConfigParser class.
class Config:
Define’s the name of the class, so when we do “from INIConfig import Config” it will act as the same as the above from line.
def __init__(self, path=os.path.normpath(os.getcwd()), filename=”config.ini”):
__init__ is the same as “function __construct()” in PHP and likewise in C++.  It’s basically a shorthand of having to do Config.init(), so we can just do Config() instead.  To make this portable for future use, the path and filename are also possible, but have defaults of <program’s directory>/config.ini.  This is the format of my program, but you can change as you deem fit.
# Default config file: <current directory of script>/config.ini
self.file = “%s/%s” % (path, filename)
All of the “self.*” like “self.file” are variables set for the class itself, so they’re not global.  This means if I want to access the file variable somewhere else in the class, I just have to do “filename = self.file” or “return self.file”.
# Initialize the ini parser class
self.parser = SafeConfigParser()
# Read and parse the ini file
self.parser.read(self.file)
By now you can tell I comment a lot of my code, and that’s mostly because I’ll forget what it does the next day if I don’t.  This simply loads the SafeConfigParser() class and initializes it, then reads in the file the parser.
# We initially have an empty config to deal with
self.config = {}
# So we don’t initialize this every time.
val = “”
Dictionaries offer faster lookup times than other data storage, and are also easier to work with in this regard.  The ultimate idea is to be able to access sections and options by simply doing:
config = Config()
username = config['api']['username']
Where the config.ini file looks like this:
[api]
username = ehansen24
password = somerandomhashofapassword
The below code will essentially create a 2-D dictionary.  The initial key will be the section name, and then in the 2nd array the key will be the option name, followed by the value.  This is all stored in self.config, which I will show how to fetch data from in the next code block:
        # Loop through each section in the ini file
        for section in self.parser.sections():
            # Create an empty dictionary entry for each entry (otherwise: exceptions)
            self.config.update({section : dict()})
            # Loop through each option in the section
            for option in self.parser.options(section):
                # Get the value of the option (just done for clarity)
                val = self.parser.get(section, option)
                # Store the new ‘option’ : ‘option val’ dictionary entry into the section dictionary
                self.config[section].update({ option : val })
Now, as this code is pretty much commented thoroughly enough, I’ll show the entire code first, then talk about it:
    def __getitem__(self, section, option=None):
        # First we MUST have a section name given, and only continue if it’s valid
        if self.config.has_key(section):
            # If an option key was given and it exists, return the value
            if option != None and self.config[section].has_key(option):
                return self.config[section][option]
            else if option == None:
                # No option was given, user wants just the section data
                return self.config[section]
            else:
                # No option was found, even tho it was requested, return empty string
                return “”
        else:
            return {” : ”}
__getitem__ is a built-in function of Python, so when you do something such as list_array[key_name], Python is internally calling list_array.__getitem__(key_name).  So, in turn, for us to return a configuration option, for ease of use we call __getitem__.  Section itself is always mandatory, or else it’ll return an empty dictionary result.  However, if the section is provided and does exist, then we will either return the complete section, or a value of the section’s option (if the option is provided).
If you’re wondering why there’s the line “self.config[section].has_key(option)”, that’s because it’s basically short hand for this:
section = self.config[section]
section.has_key(option)
When we were storing the options and their values, the option name itself was the key, so instead of having to cycle through each entry in a for loop, we use a built in function to improve speed.  If the option was requested and it exists, provide the option (a non-dictionary result by default).  If no option was given, but the section exists, return the entire section.  Otherwise, the option doesn’t exist that was requested, so return an empty string.  Typically you’d return None instead, but for the sake of me being lazy I didn’t do it that way.
Download Link
I have the script uploaded onto my business’ GitHub at the following URL: https://github.com/SecurityForUs/PythonUtils/blob/master/config.py
My business’ website is Security For Us where I provide server and security management, PCI compliance and web hosting

 Comment on this Post

 
There was an error processing your information. Please try again later.
Thanks. We'll let you know when a new response is added.
Send me notifications when other members comment.

REGISTER or login:

Forgot Password?
By submitting you agree to receive email from TechTarget and its partners. If you reside outside of the United States, you consent to having your personal data transferred to and processed in the United States. Privacy

Forgot Password

No problem! Submit your e-mail address below. We'll send you an e-mail containing your password.

Your password has been sent to: