Consider the following MWE named foo.py
:
#!/usr/bin/env python3
from configparser import ConfigParser
if __name__ == "__main__":
cfg = ConfigParser()
with open("foo.ini") as ini:
cfg.read_file(ini)
for section in cfg.sections():
print("[%s]" % (section))
for option, value in cfg.items(section):
print("%s = %s" % (option, value))
... and the accompanying foo.ini
:
[DEFAULT]
basedir = /foo/bar
sourcedir = %(basedir)s/baz
[task:1]
sourcedir = /usr
workdir = %(sourcedir)s/src
[task:2]
hdrdir = %(sourcedir)s/include
The script will yield the following when run:
[task:1]
basedir = /foo/bar
sourcedir = /usr
workdir = /usr/src
[task:2]
basedir = /foo/bar
sourcedir = /foo/bar/baz
hdrdir = /foo/bar/baz/include
The desired outcome is to iterate exclusively over the (interpolated) options (via ConfigParser.items()
) local to the given section.
Passing raw=True
to ConfigParser.items()
doesn't affect the list of items other than that the values are not being interpolated (which is anyway not the desired outcome).
Now I could do something like in this modified version of the initial script:
#!/usr/bin/env python3
from configparser import ConfigParser
if __name__ == "__main__":
cfg = ConfigParser()
with open("foo.ini") as ini:
cfg.read_file(ini)
print("; sections: %s" % (cfg.sections()))
print("; defaults: %s" % (cfg.defaults()))
for section in cfg.sections():
print("[%s]" % (section))
print("; options = %s" % (cfg.options(section)))
for option, value in cfg.items(section):
if option in cfg.defaults():
# can't use cfg.defaults()[option] ... that's the raw value
if cfg[cfg.default_section][option] == value:
continue
print("%s = %s" % (option, value))
... which yields:
; sections: ['task:1', 'task:2']
; defaults: OrderedDict([('basedir', '/foo/bar'), ('sourcedir', '%(basedir)s/baz')])
[task:1]
; options = ['sourcedir', 'workdir', 'basedir']
sourcedir = /usr
workdir = /usr/src
[task:2]
; options = ['hdrdir', 'basedir', 'sourcedir']
hdrdir = /foo/bar/baz/include
So it'll work at first glance, but would filter out section-local options which have a value identical to the global one of the same name.
Now this may seem only like a cosmetic issue, but in my case there is a semantic difference, still, between whether the option exists locally inside a section or whether its value was inherited from the default section (think multiple files, multiple inherited options ...). Point being that this is a naïve approach which doesn't work for me.
So the question: how can I get just the options that are really inside a section of an .ini
file, without all those global inherited options?
Is there a way to do this without reaching into the guts of the configparser
module and tampering with non-public methods and stuff?
Inspecting the code of ConfigParser
I don't see a public API to do it. Indeed, any method in ConfigParser
extends the given section with defaults.
The only straightfoward way I see would be to change the default section either in your config or in ConfigParser
constructor (default_section
argument):
if __name__ == "__main__":
cfg = ConfigParser(default_section=None)
# (...)
However the side effect is that the automatic "default" mechanism will not work any more for this instanciated ConfigParser
. However you can still do the fallback mechanism your self:
cfg = ConfigParser(default_section=None)
cfg.get(mysection, myoption, fallback=cfg.get('DEFAULT', myoption))
Also, the interpolation in values will not work from DEFAULT
section. But you can use ExtendedInterpolation
.
Full example:
# test.py
from configparser import ConfigParser, DEFAULTSECT, ExtendedInterpolation
if __name__ == '__main__':
cfg = ConfigParser(default_section=None, interpolation=ExtendedInterpolation())
with open('test.ini') as ini:
cfg.read_file(ini)
for section in cfg.sections():
if section == DEFAULTSECT:
continue
print('[%s]' % (section))
for key, value in cfg.items(section):
print('%s = %s' % (key, value))
# test.ini
[DEFAULT]
foo=bar
[section]
key="value ${DEFAULT:foo}"
Edits:
DEFAULT
section is not a good idea if the purpose of it is to configure default values. I strike this option. default_section
as None
instead of "whatever"