Everytime you try to get or set to a section using configparser
in Python it throws a NoSectionError
if the section does not exist. Is there anyway to avoid this?
Also, can I also avoid the NoOptionError
when getting an option?
For example, using a dictionary, there is the setdefault
option: instead of throwing a KeyError
when the key does not exist, the dictionary adds the key, sets the key's value to the default value, and returns the default value.
I am currently doing the following for getting attributes:
def read_config(section):
config = configparser.ConfigParser()
config.read(location)
try:
apple = config.get(section, 'apple')
except NoSectionError, NoOptionError:
apple = None
try:
pear = config.get(section, 'pear')
except NoSectionError, NoOptionError:
pear = None
try:
banana = config(section, 'banana')
except NoSectionError, NoOptionError:
banana = None
return apple, pear, banana
And the following for setting them:
def save_to_config(section, apple, pear, banana):
config = configparser.ConfigParser()
if not os.path.exists(folder_location):
os.makedirs(folder_location)
config.read(location)
if section not in config.sections():
config.add_section(section)
config.set(section, 'apple', apple)
config.set(section, 'pear', pear)
config.set(section, 'banana', banana)
Setting isn't too bad because they all have the same section, but getting is well... terrible. There has got to be a better way.
Is there perhaps some one liner where I can reduce this:
try:
apple = config.get(section, 'apple')
except NoSectionError, NoOptionError:
apple = None
to this:
apple = config.get_with_default(section, 'apple', None)
-- EDIT --
I have tried to make the following changes per lego's suggestion:
def read_config(section):
defaults = { section : {'apple': None,
'pear': None,
'banana': None }}
config = configparser.ConfigParser(defaults = defaults)
config.read(location)
apple = config.get(section, 'apple')
pear = config.get(section, 'pear')
banana = config(section, 'banana')
return apple, pear, banana
But this still raises a NoSectionError
if the section doesn't exist
Note: I have also tried it where defaults = just {'apple': None, 'pear': None, 'banana': None }
(no section)
An alternative approach:
ConfigParser.get offers a vars
parameter that can be passed in, which is used as the primary lookup if it's provided, however, it ignores whether there exists a value for the option already on the section.
We can, therefore, use vars
via ducktyping, but we'll change the behavior of .items()
to the following:
vars
.Here's a very naive implementation:
class DefaultOption(dict):
def __init__(self, config, section, **kv):
self._config = config
self._section = section
dict.__init__(self, **kv)
def items(self):
_items = []
for option in self:
if not self._config.has_option(self._section, option):
_items.append((option, self[option]))
else:
value_in_config = self._config.get(self._section, option)
_items.append((option, value_in_config))
return _items
In usage:
def read_config(section, location):
config = configparser.ConfigParser()
config.read(location)
apple = config.get(section, 'apple',
vars=DefaultOption(config, section, apple=None))
pear = config.get(section, 'pear',
vars=DefaultOption(config, section, pear=None))
banana = config.get(section, 'banana',
vars=DefaultOption(config, section, banana=None))
return apple, pear, banana
def save_to_config(section, location, apple, pear, banana):
config = configparser.ConfigParser()
config.read(location)
if section not in config.sections():
config.add_section(section)
config.set(section, 'apple', apple)
config.set(section, 'pear', pear)
with open(location, 'wb') as cf:
config.write(cf)
That being said, this is a little indirect, but perfectly valid.
Note, that this will still raise NoSectionError
.
If you're trying to handle that as well, ConfigParser.ConfigParser takes a dict_type
parameter, so you just instantiate the class with a defaultdict.
So, change configparser.ConfigParser()
to configparser.ConfigParser(dict_type=lambda: defaultdict(list))
For all intents and purposes though, I'd probably use Lego's suggestions.
If you want to use the defaults
keyword into ConfigParser, it might help to look at how the implementation is defined. Here's the ConfigParser.__init__()
code for how defaults are initialized. You'll see that defaults are used completely differently than sections. To dive a bit deeper about the role they play during get()
, look at the code for ConfigParser.get()
. Basically, if the section isn't DEFAULTSECT
, then a NoSectionError
is thrown.
You have two ways to overcome this:
defaultdict
idea I proposed aboveread_config
function slightlydef read_config(section):
defaults = {'apple': None,
'pear': None,
'banana': None }
config = configparser.ConfigParser(defaults = defaults)
config.read(location)
if not config.has_section(section):
config.add_section(section)
apple = config.get(section,'apple')
pear = config.get(section, 'pear')
banana = config.get(section, 'banana')
return apple, pear, banana
But I say, since this is not a one-liner, it opens up a lot more paths like the DefaultOption
class I offered. We can make it even a bit less verbose.