1

I have created a .ini-like file with all the values which I need later in my program, see below:

[debugging]
checkForAbort = 10
...

[official]
checkForAbort = 30
...

I would like to read all these items into a single class and make it accessible from other parts of my python project. What I have so far is the code below:

from ConfigParser import SafeConfigParser
import glob

class ConfigurationParameters
    def __init__(self):
        self.checkForAbortDuringIdleTime = None     

    parser = SafeConfigParser()

    # making a list here in case we have multiple files in the future!
    candidatesConfiguration = ['my.cfg']
    foundCandidates = parser.read(candidatesConfiguration)
    missingCandidates = set(candidatesConfiguration) - set(found)
    if foundCandidates:
        print 'Found config files:', sorted(found)
    else
        print 'Missing files     :', sorted(missing)
        print "aborting..."


    # check for mandatory sections below
    for candidateSections in ['official', 'debugging']:
        if not parser.has_section(candidateSections)
            print "the mandatory section", candidateSections " is missing"
            print "aborting..."

    for sectionName in ['official', 'debugging']:
        for name, value in parser.items(section_name):
            self.name = value

I am new to python but I can still see lots of problem with my code:

  • I am forced to add a attribute for each item in my class file. and keep the configuration file and my class in sync all the time.
  • This class is not a singleton, therefore the reading/parsing will be done from wherever it is imported!
  • If a value is added to the config-file with is not defined in my class, it will probably crash!

How should I solve this problem instead? Can class attributes be created dynamically?

My class only need to read from the values, so no writing to the configuration file is needed!

theAlse
  • 5,577
  • 11
  • 68
  • 110
  • is it intentional that values from `debugging` section override corresponding values from `official`? – jfs Sep 27 '12 at 12:06

3 Answers3

12

What J.F. Sebastian said.

Also, you could do it like Alex Martelli does in his Bunch class:

file MyConfig.py:

from ConfigParser import SafeConfigParser


section_names = 'official', 'debugging'


class MyConfiguration(object):

    def __init__(self, *file_names):
        parser = SafeConfigParser()
        parser.optionxform = str  # make option names case sensitive
        found = parser.read(file_names)
        if not found:
            raise ValueError('No config file found!')
        for name in section_names:
            self.__dict__.update(parser.items(name))  # <-- here the magic happens


config = MyConfiguration('my.cfg', 'other.cfg')

file foo.py:

from MyConfig import config
# ...

file MyProgram.py:

from MyConfig import config

print config.checkForAbort

import foo

assert config is foo.config

The Python Language Reference states that "Import statements are executed in two steps: (1) find a module, and initialize it if necessary; (2) define a name or names in the local namespace (of the scope where the import statement occurs)."

What that means is that, when a module gets imported, one or more local names are bound to a module object, and only the first time it is imported during the runtime of a Python program it gets initialized (i.e. read from file and run).

In the code above the name config is just a local name that refers to an attribute of a module object. The module object has been initialized by the Python interpreter when it was referenced (via from MyConfig import config) in MyProgram. When MyProgram imports foo it is already initialized and gets bound to a local name in module foo and in MyProgram we can refer to it as foo.config. But both names refer to the very same object.

Community
  • 1
  • 1
pillmuncher
  • 10,094
  • 2
  • 35
  • 33
  • that is certainly magic. thanks. But still the user is required to write config = MyConfiguration('my.cfg', 'other.cfg') whever he want to use an item, right? or am I totally missing something here? – theAlse Sep 27 '12 at 13:43
  • 1. `.update()` accepts a dict (no need for `**`). 2. [`.update()` breaks on properties](http://stackoverflow.com/a/12620919/4279) (`setattr()` works). – jfs Sep 27 '12 at 13:54
  • @J.F.Sebastian: 1. Yeah, fixed. 2. I'll remember to change that as soon as I'll add properties. – pillmuncher Sep 27 '12 at 14:22
  • @pillmuncher, I tried your code and it works well and as expected. But what exactly is __dict__. are the items stored in a dict? – theAlse Sep 27 '12 at 14:49
  • `o.__dict__` for an object o is a dictionary or other mapping that contains its namespace. More about the structure of Python's objects can be found [here](http://docs.python.org/reference/datamodel.html?highlight=__dict__). Scroll down to *"Classes"*. – pillmuncher Sep 27 '12 at 14:54
  • I needed to implement this in an already existing class and therefore only needed the two last lines like: ```for name in section_names: CLASS.__dict__.update(parser.items(name))```. Thanks! – crazjo Dec 13 '17 at 10:58
  • Won't this `for name in section_names: self.__dict__.update(parser.items(name))` just overwrite [debugging] section's `checkForAbort` value with that of [official]? I don't see how to use the dictionary built in this example... – clipitar Nov 17 '20 at 15:26
3

self.name = value doesn't work as you expect. You might mean setattr(self, name, value) It creates instance attributes dynamicallly.

To make it a singleton you could make the instance to be a global variable in config/settings module. Initialize it once on a program startup e.g., like the logging module does it for the root logger: logging.config.fileConfig('logging.conf').

It is a common desire to use an attribute access for what is essentially a dict e.g., argparse.Namespace that carries command-line options.

You could access the config later by importing it: from mypackage.config import config, e.g., mypackage/config.py:

class Config(object):
    ...

config = Config() # empty or default config without any configuration

In mypackage/__main__.py:

from .config import config
...
# initialization code
config.update_from_file('mypackage.conf') # it might call `setattr()` inside

Note: setattr() works correctly even you set properties. __dict__.update() breaks in this case:

class XValidatorMixin(object):
    @property
    def x(self):
        return self._x

    @x.setter
    def x(self, value):
        if value < 0:
            raise ValueError
        self._x = value


class CUpdate(XValidatorMixin):
    def __init__(self, **options):
        self.__dict__.update(options)


class CSetAttr(XValidatorMixin):
    def __init__(self, **options):
        for name, value in options.items():
            setattr(self, name, value)

for C in [CUpdate, CSetAttr]:
    o = C(a=1, b=2) # non-property attributes work as expected
    assert o.a == 1 and o.b == 2

o = CSetAttr(x=1)
assert o.x == 1 # setattr also sets property

o = CUpdate(x=1)
try:
    o.x # .update() doesn't set property
except AttributeError:
    pass
else:
    assert 0


try:
    o = CSetAttr(x=-1)  # invokes property setter
except ValueError: # that correctly reject negative values
    pass
else:
    assert 0
jfs
  • 399,953
  • 195
  • 994
  • 1,670
  • ok, so setattr creates attributes dynamically, that is usefull so I don´t need to declare the attributes in the class. But I really don´t undrestand what you mean in your second part of answer regarding making the class a singleton. – theAlse Sep 27 '12 at 12:11
  • 1
    @theAlse: the purpose to make *an instance of a class* a singleton, not the class object itself: `config = Config(); ...` the whole program uses the same config object. – jfs Sep 27 '12 at 12:15
  • So whenever we create an instance of a class, it is global and accessible by whoever import the class? – theAlse Sep 27 '12 at 14:06
  • 2
    @theAlse: No. It is not the object that is local or global, but the name with which we refer to that object. If it is defined in a function or method, then it is not global. If it is defined at module level, then it is global *in that module*. If that module gets imported somewhere via `import the_module`, then the name is not global to the importing module, since we can only refer to that object as `the_module.the_name`. If we import via `from the_module import the_name` then `the_name` is global in the namespace of the importing module. – pillmuncher Sep 27 '12 at 14:30
0

The Answer by pillmuncher is very helpful and can be easily adapted to use within an existing class. Furthermore, it is also possible to automatically convert the data types using the localconfig module (link). To get these additional functionalities you can use something like:

from localconfig import LocalConfig

configfile = 'config.ini'
config = LocalConfig(configfile)
sections = list(config)

for section in sections:
    items = list(config.items(section))
    CLASS.__dict__.update(items)
crazjo
  • 485
  • 1
  • 8
  • 20