Reputation: 6422
I am using a program which uses python's configparser.ConfigParser
to work with config files as instructions to build a thing. It is set up so that multiple command line file arguments can be specified, and specifications in later files override things set in earlier files.
This means I can set up a basic model in a trivial.ini
file
[admin]
basename = trivial_model
[model basic]
data = data.tsv
and extend it with a more complex model extension.ini
[admin]
basename = trivial_model_extended
[model basic]
model = bsvs
and program trivial.ini extension.ini
will behave as if it had got
[admin]
basename = trivial_model_extended
[model basic]
data = data.tsv
model = bsvs
Now it would be nice to write this somewhat more modular, to be able to combine multiple such extensions without caring about details too much, giving each a different base file name. I thought maybe this might work
[admin]
basename = %(basename)s_extended
[model basic]
model = bsvs
but with the current implementation, I get configparser.InterpolationDepthError: Recursion limit exceeded in value substitution: option 'basename' in section 'admin' contains an interpolation key which cannot be substituted in 10 steps. Raw value: '%(basename)s_extended'
.
Is there an easy, builtin, or elegant way to enable incremental specifications like this, either through changing the current implementation (which seems to boil down to
parser.add_argument(
"config",
nargs="+")
args = parser.parse_args()
c = configparser.ConfigParser()
for conf in args.config:
c.read(conf)
) or through some clever [default]
sections or values in the config files (or both if necessary)?
Upvotes: 3
Views: 2536
Reputation: 6422
get
-timeRecursive interpolation in the way you are looking for is not possible out-of-the-box because interpolation only acts at the time when ConfigParser.get(section, option)
is called, not at the time the configuration file is read. From BasicInterpolation
's docstring:
[...] All reference expansions are done late, on demand. [...]
The _read
method of ConfigParser
does calls self._interpolation.before_read
, but by that time it has already overwritten the old internal values of the ConfigParser
object, so even
class BasicReadInterpolation (configparser.BasicInterpolation):
def before_read(self, parser, section, option, value):
L = []
import pdb
pdb.set_trace()
interpolations = parser[parser.default_section]
interpolations.update(parser[section])
self._interpolate_some(
parser, option, L, value, section, interpolations, 1)
return ''.join(L)
will not do the trick all by itself.
You will also have to overload ConfigParser._read
, the method which contains most of the parsing magic, and ConfigParser._join_multiline_values
, which currently contains the call to self._interpolation.before_read
. (Which to me is a silly place to put it.)
Upvotes: 0
Reputation: 40773
A few comments:
You cannot recurse the basename
definition like you did above. My approach is to have a [DEFAULT]
section with something other than basename
, for example, the trivial.ini might look like this:
[DEFAULT]
basename_default = default from trivial.ini
[admin]
basename = trivial_model
[model basic]
data = data.tsv
Note that the [DEFAULT]
section needs to be all uppercase
Next, I might have an additiona .ini file, which I call more.ini and it looks like this:
[admin]
basename = %(basename_default)s and more
Also, you don't need a loop to read the config files: just give the read()
method a list of filenames where the later file will overwrite the first.
Putting it together:
parser = argparse.ArgumentParser()
parser.add_argument("config", nargs="+")
args = parser.parse_args('trivial.ini extension.ini more.ini'.split())
cfg = ConfigParser.ConfigParser()
cfg.read(args.config)
admin = 'admin'
model_basic = 'model basic'
print('basename:', cfg.get(admin, 'basename'))
print('defaults:', cfg.defaults())
The output:
basename: default from trivial.ini and more
defaults: OrderedDict([('basename_default', 'default from trivial.ini')])
Upvotes: 1