sorin
sorin

Reputation: 170508

How to load a configuration file in Python and be able to access values using dot notation (attributes)?

I am looking for a solution that would allow me be use attributes for a config file data.

I would like to be able to do something like this:

config = Config('config.ini')
print config.section1.user
print config.section2.password

I do know the ConfigParser would allow me to do someting like config['section1']['user'] but that's too ugly, can't we do better?

The solution has to work with Python 2.5 or newer.

Upvotes: 6

Views: 4158

Answers (4)

Aleksander
Aleksander

Reputation: 1

I have managed to obtain what You needed in quite neat way. Having dot notation is always better and I also wanted this in my project. Lets say in your project root you have the Setup directory with setup.cfg inside. You also have the project root path added to env variable (for purpose of this code to be readable)

import os
import configparser


class Section:
    def __init__(self):
        pass


class Setup:
    def __init__(self):
        self.setup_dict = self.load_setup()
        self.fill_setup()

    def load_setup(self):
        setup = configparser.RawConfigParser()
        setup.read(os.environ.get('PROJECT_ROOT_PATH') + "/Setup/setup.cfg")
        return {section: item_dict for (section, item_dict) in zip(list(setup), [dict(section) for section in dict(setup).values()])}

    @staticmethod
    def add_section(data):
        section = Section()
        vars(section).update(data)
        return section

    def fill_setup(self):
        for name in self.setup_dict.keys():
            vars(self).update({name.lower(): self.add_section(self.setup_dict[name])})

The thing is that for dot notation when reading config file, you need a class that has classes inside that represent sections of the cfg file (Sectiion classes above), which in turn have the key-value pairs of cfg section values as the instance variables.

Then you can import the Setup (or whatever name you will call your class) from file and use like that:

from load_setup import Setup

setup = Setup()
value = setup.section.item

Upvotes: 0

Jussi Nurminen
Jussi Nurminen

Reputation: 2408

I wrote a simple package configdot: https://github.com/jjnurminen/configdot. It allows access of config items by the syntax config.section.item (read and write). As a bonus, it also allows you to use (a limited number of) Python data types directly in your INI file.

Upvotes: -1

dmmfll
dmmfll

Reputation: 2836

I too wanted to use dot notation to access attributes read from config files by ConfigParser. (Available on github).

Here is my attempt to extend ConfigParser:

from ConfigParser import ConfigParser as BaseClass

SPACE = " "
UNDERSCORE = "_"


def internal_name(name, needle=SPACE, replacement=UNDERSCORE):
    return name.replace(needle, replacement)


def reverse_name_internalization(name):
    return internal_name(name, needle=UNDERSCORE, replacement=SPACE)


class DotNotationConfigParser(BaseClass, object):

    def __init__(self, coersion_map=None, *args, **kwargs):
        super(DotNotationConfigParser, self).__init__(*args, **kwargs)

        self.optionxform = internal_name
        self.section_attr = None

    def get_internalized_section(self, section):
        if self.has_section(section):
            return internal_name(section)

    def __set_section_obj(self, internalized_section):
        if self.has_section(internalized_section):
            section = internalized_section
        else:
            section = reverse_name_internalization(internalized_section)

        if self.get_internalized_section(section):
            # set an attr to an object instance with section items
            obj = type('', (), dict(self.items(section)))()
            setattr(self, internalized_section, obj)

    def __getattr__(self, attr):
        try:
            return super(DotNotationConfigParser, self).__getattribute__(attr)
        except AttributeError:
            section = attr
            self.__set_section_obj(section)
            return super(DotNotationConfigParser, self).__getattribute__(attr)


try:
    from cStringIO import StringIO
except ImportError:
    from StringIO import StringIO
configuration_file = """
[section 1]
foo = the foo value
bar = the bar value
[section 2]
index = 3
repeat = False
[section_n]
string = This is some text.
ip = 10.0.1.1
"""
configuration_file = StringIO(configuration_file)

parser = DotNotationConfigParser()
parser.readfp(configuration_file)

assert parser.section_1.foo == 'the foo value'
assert parser.section_1.bar == 'the bar value'
assert type(parser.section_2.index) is not int
for section_name in ('section_1', 'section_2', 'section_n'):
    section = getattr(parser, section_name)
    options = [option for option in dir(section)
               if not option.startswith('__')]
    for option in options:
        print section_name, ": ", getattr(section, option)

print "dot notation", parser.section_1.foo

Upvotes: 1

Tadeck
Tadeck

Reputation: 137390

It is not ugly, it is better - dot notation means there is probably some custom class' object within another custom class' object. The more feasible way is to use dictionaries (that use bracket notation).

But if you insist, you can probably translate the code like that:

def config2object(config):
    """
    Convert dictionary into instance allowing access to dictionary keys using
    dot notation (attributes).
    """
    class ConfigObject(dict):
        """
        Represents configuration options' group, works like a dict
        """
        def __init__(self, *args, **kwargs):
            dict.__init__(self, *args, **kwargs)
        def __getattr__(self, name):
            return self[name]
        def __setattr__(self, name, val):
            self[name] = val
    if isinstance(config, dict):
        result = ConfigObject()
        for key in config:
            result[key] = config2object(config[key])
        return result
    else:
        return config

And the tests show the expected results:

>>> c1 = {
    'conf1': {
        'key1': 'aaa',
        'key2': 12321,
        'key3': False,
        },
    'conf2': 'bbbb',
    }
>>> c1
{'conf2': 'bbbb', 'conf1': {'key3': False, 'key2': 12321, 'key1': 'aaa'}}
>>> c2 = config2object(c1)
>>> c2.conf1
{'key3': False, 'key2': 12321, 'key1': 'aaa'}
>>> c2.conf1.key1
'aaa'
>>> c2.conf1.key3
False
>>> c2.conf2
'bbbb'

EDIT: Sven Marnach noted that Config('config.ini') is some custom class instance. It is not a dictionary, it has some custom methods that could be quite useful, but could make some configuration options inaccessible (when there is name conflict). Therefore the preferred approach is not to use the solution I mentioned, but to use bracket notation to access configuration options.

Upvotes: 2

Related Questions