If you pre-process your config file before parsing with ConfigParser, and post-process the output written by the ConfigParser, then you can update an INI file without removing comments.
I suggest to transform each comment into an option (key/value pair) during pre-processing. Then ConfigParser will then not throw the comments out. During post processing you'd then 'unpack' the comment and restore it.
To simplify the process you may want to subclass the ConfigParser and override the _read and write methods.
I've done this and posted the CommentConfigParser class in this Gist. It has one limitation. It does not support indented section headers, comments and keys. They should have no leading whitespace.
class CommentConfigParser(configparser.ConfigParser):
"""Comment preserving ConfigParser.
Limitation: No support for indenting section headers,
comments and keys. They should have no leading whitespace.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Backup _comment_prefixes
self._comment_prefixes_backup = self._comment_prefixes
# Unset _comment_prefixes so comments won't be skipped
self._comment_prefixes = ()
# Template to store comments as key value pair
self._comment_template = "#{0} = {1}"
# Regex to match the comment id prefix
self._comment_regex = re.compile(r"^#\d+\s*=\s*")
# List to store comments above the first section
self._top_comments = []
def _read(self, fp, fpname):
lines = fp.readlines()
above_first_section = True
# Preprocess config file to preserve comments
for i, line in enumerate(lines):
if line.startswith("["):
above_first_section = False
elif line.startswith(self._comment_prefixes_backup):
if above_first_section:
# Remove this line for now
lines[i] = ""
self._top_comments.append(line)
else:
# Store comment as value with unique key based on line number
lines[i] = self._comment_template.format(i, line)
# Feed the preprocessed file to the original _read method
return super()._read(io.StringIO("".join(lines)), fpname)
def write(self, fp, space_around_delimiters=True):
# Write the config to an in-memory file
with io.StringIO() as sfile:
super().write(sfile, space_around_delimiters)
# Start from the beginning of sfile
sfile.seek(0)
lines = sfile.readlines()
for i, line in enumerate(lines):
# Remove the comment id prefix
lines[i] = self._comment_regex.sub("", line, 1)
fp.write("".join(self._top_comments + lines))
ConfigParser
. You need to use some other library. – Aswarm