Reputation: 170508
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
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
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
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
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