how to interpolate the section-name with configpar

2019-06-08 12:50发布

问题:

I'm using configparser to read configuration for a backup-system. one of the options (the output path) often includes the section-name itself.

[DEFAULT]
compression=gzip

[foo]
output=/Backups/foo/
[pizza]
output=/BigDisk/Backups/
[bar]
output=/Backups/bar/

Now I would like to get rid manually appending the section name, and instead would like to use string interpolation. Something like the following:

[DEFAULT]
compression=gzip
output=/Backups/%(SECTION)s/

[foo]
[pizza]
output=/BigDisk/Backups/
[bar]

Unfortunately i haven't found the variable-name that would expand to the section name yet (it's obviously not SECTION).

Is it possible at all (ideally without having to build my own ConfigParser sub-class)?

An ugly workaround would be to add an option append_section_name (and set it to yes in the DEFAULT section and to no in the pizza section) and manually do the interpolation. I'd rather not...

回答1:

I had a need to do the same thing and I found that it's quite easy to do in Python 3.x (tested on Python 3.4):

  1. Make a subclass of the ExtendedInterpolation class (MyExtendedInterpolation) and override the before_get() method. First, create a new value doing your custom substitution. I found it easiest to use the string Template class and a call to the safe_substitute() method. Next, return the result of the call to the superclass before_get() method.
  2. Create your ConfigParser object using your custom interpolation class.
  3. Use the ConfigParser object as normal.

Hope it helps.

Working code below:

#!/usr/bin/env python3

from configparser import ConfigParser, ExtendedInterpolation
from string import Template


class MyExtendedInterpolation(ExtendedInterpolation):
    def before_get(self, parser, section, option, value, defaults):
        value = Template(value).safe_substitute({'SECTION': section})
        return super().before_get(parser, section, option, value, defaults)


if __name__ == '__main__':
    cp = ConfigParser(interpolation=MyExtendedInterpolation())
    cp.read_string("""
        [DEFAULT]
        compression=gzip
        output=/Backups/${SECTION}/

        [foo]

        [pizza]
        output=/BigDisk/Backups/

        [bar]
    """)

    for section in cp.sections():
        print('[{!s}]'.format(section))
        for option in cp[section]:
            print('    {!s} = {!s}'.format(option, cp.get(section, option)))

Script output below. Notice how the output option value includes the section name for the [foo] and [bar] sections.

[foo]
    compression = gzip
    output = /Backups/foo/
[pizza]
    output = /BigDisk/Backups/
    compression = gzip
[bar]
    compression = gzip
    output = /Backups/bar/

Relevant references:

(configparser module) 14.2.5. Interpolation of values

(string module) 6.1.4. Template strings



回答2:

The idea of user @andy20170414 is good but has an unexpected behavior of breaking the escape procedure.

He replaces the original string using that Template() and that will escape the all th $. When the Interpolation receives the text to interpolate, it will receive only one $ (already escaped) and will give you error during parsing.

I think that the correct way to accomplish what op wants is as the @andy20170414 answer with this modification:

    class MyExtendedInterpolation(ExtendedInterpolation):
    def before_get(self, parser, section, option, value, defaults):
        defaults.maps.append({'section': section})
        return super().before_get(parser, section, option, value, defaults)

Keep in mind that the keys are compared in a .tolower() manner, as such keep that section key in lower case.