David White
David White

Reputation: 127

ConfigParser - write a list as a duplicate key

I have searched stackoverflow but can't seem to find an answer similar to mine.

Most people want to parse duplicate keys, however I want to write them. (I'd prefer to do this using async file writes but that's another story)

Specifically for systemd-networkd config. They use the ini format, but allow duplicate keys. For example

[Match]
Name=eth0

[Network]
Address=10.0.0.12/24
Gateway=10.0.0.1
DNS=1.1.1.1
DNS=8.8.8.8

I want to be able to write this like so:

conf = ConfigParser()
conf.optionxform = str

conf["Match"] = {"Name": "eth0"}
conf["Network"] = {
    "DHCP": "no",
    "Address": "10.0.0.12/24",
    "Gateway": "10.0.0.1",
    "Dns": ["1.1.1.1", "8.8.8.8"],
}

However, obviously, this gets written as a literal string.

I've written the following abomination that does the job, but surely there has to be an easier / more sane way?

from configparser import ConfigParser
import ast

class CustomParser(ConfigParser):
    def _write_section(self, fp, section_name, section_items, delimiter):
        """Write a single section to the specified `fp`."""

        def write_item(item):
            item = self._interpolation.before_write(
                self, section_name, key, item
            )
            if item is not None or not self._allow_no_value:
                item = delimiter + str(item).replace("\n", "\n\t")
            else:
                item = ""
            fp.write("{}{}\n".format(key, item))

        fp.write("[{}]\n".format(section_name))
        for key, value in section_items:
            parsed = None
            try:
                parsed = ast.literal_eval(value)
            except Exception:
                pass

            if isinstance(parsed, list):
                for list_item in parsed:
                    write_item(list_item)
            else:
                write_item(value)
        fp.write("\n")

Upvotes: 0

Views: 48

Answers (1)

Andrej Kesely
Andrej Kesely

Reputation: 195573

One solution would be create custom dict and initialize ConfigParser() with that dict (dict_type= parameter in constructor):

import sys
from ast import literal_eval
from configparser import ConfigParser


class my_dict(dict):
    def items(self):
        for k, v in super().items():
            if v.startswith("[") and v.endswith("]"):
                for i in literal_eval(v):
                    yield k, i
            else:
                yield k, v


conf = ConfigParser(dict_type=my_dict)

conf["Match"] = {"Name": "eth0"}
conf["Network"] = {
    "DHCP": "no",
    "Address": "10.0.0.12/24",
    "Gateway": "10.0.0.1",
    "Dns": ["1.1.1.1", "8.8.8.8"],
}


conf.write(sys.stdout)

Prints:

[Match]
name = eth0

[Network]
dhcp = no
address = 10.0.0.12/24
gateway = 10.0.0.1
dns = 1.1.1.1
dns = 8.8.8.8

Upvotes: 1

Related Questions