2

I want to write an INI file with duplicate options,ie:

[test]
foo = value1
foo = value2
xxx = yyy

With ConfigParser.set only the last value is writed.

config = ConfigParser.ConfigParser()
config.read('example.cfg')

config.add_section('test')
config.set('test', service['foo'], service['value1'])
config.set('test', service['foo'], service['value2'])
config.set('test', service['xxx'], service['yyy'])

The result is:

[test]
foo = value2
xxx = yyy

Is there any way?

John Fadria
  • 1,863
  • 2
  • 25
  • 31
  • i have a custom `.ini` parser in python (built for a nother project), which uses a list to store values but only if they are not in `key=value` format. if `key=value` then last `key` will be held since these are stored in a dictionary – Nikos M. Jun 03 '15 at 08:53
  • Why not use a character separated string as value? Like `;` or even configure that as well¿ – theking2 Jul 22 '22 at 12:00

2 Answers2

0

i have a simple custom .ini parser in python (built for another project), which uses a list to store values but only if they are not in key=value format. if key=value then last key will be held since these are stored in a dictionary

The parser can also parse nested sections like:

[SECTION1][SECTION2]
key1=value1
; etc..

The code is below, it is easy to modify to store key/value in list instead of dictionary or even detect multiple key and rename to avoid collisions (e.g key, key$1 second key with same key value key and so on). use/modify as needed

##
#
#   Simple .ini Parser for Python 2.x, 3.x
#
##
import re

class Ini_Parser():
    """Simple .ini parser for Python"""

    NL = None

    ACTUAL = {
        '\\n' : "\n",
        '\\t' : "\t",
        '\\v' : "\v",
        '\\f' : "\f"
    }

    def parseStr(s, q):
        _self = Ini_Parser

        endq = s.find(q, 1)
        quoted = s[1:endq]
        rem = s[endq+1:].strip()
        for c,actual in _self.ACTUAL.items():
            quoted = ( actual ).join( quoted.split( c ) )
        quoted = ( '\\' ).join( quoted.split( '\\\\' ) )
        return quoted, rem

    def fromString(s, keysList=True, rootSection='_'):
        _self = Ini_Parser

        comments = [';', '#']

        if rootSection:  rootSection = str(rootSection)
        else: rootSection = '_'

        if not _self.NL:
            _self.NL = re.compile(r'\n\r|\r\n|\r|\n')

        sections = {}
        currentSection = str(rootSection)
        if keysList:
            sections[currentSection] = { '__list__' : [] }
        else:
            sections[currentSection] = {  }
        currentRoot = sections

        # parse the lines
        lines = re.split(_self.NL, str(s))

        # parse it line-by-line
        for line in lines:
            # strip the line of extra spaces
            line = line.strip()
            lenline = len(line)

            # comment or empty line, skip it
            if not lenline or (line[0] in comments): continue

            linestartswith = line[0]

            # section line
            if '['==linestartswith:

                SECTION = True

                # parse any sub-sections
                while '['==linestartswith:

                    if SECTION:
                        currentRoot = sections
                    else:
                        currentRoot = currentRoot[currentSection]

                    SECTION = False

                    endsection = line.find(']', 1)
                    currentSection = line[1:endsection]

                    if currentSection not in currentRoot:

                        if keysList:
                            currentRoot[currentSection] = { '__list__' : [] }
                        else:
                            currentRoot[currentSection] = {  }


                    # has sub-section ??
                    line = line[endsection+1:].strip()

                    if not len(line):  break

                    linestartswith = line[0]

            # key-value pairs
            else:

                # quoted string
                if '"'==linestartswith or "'"==linestartswith:

                    key, line = _self.parseStr(line, linestartswith)

                    # key-value pair
                    if line.find('=', 0)>-1:
                        line = line.split('=')
                        line.pop(0)
                        value = "=".join(line).strip()
                        valuestartswith = value[0]

                        # quoted value
                        if '"'==valuestartswith or "'"==valuestartswith:
                            value, rem = _self.parseStr(value, valuestartswith)

                        currentRoot[currentSection][key] = value

                    # single value
                    else:

                        if keysList:
                            currentRoot[currentSection]['__list__'].append(key)
                        else:
                            currentRoot[currentSection][key] = True


                # un-quoted string
                else:

                    line = line.split('=')
                    key = line.pop(0).strip()

                    # single value
                    if 1>len(line):

                        if keysList:
                            currentRoot[currentSection]['__list__'].append(key)
                        else:
                            currentRoot[currentSection][key] = True

                    # key-value pair
                    else:

                        value = "=".join(line).strip()
                        valuestartswith = value[0]

                        # quoted value
                        if '"'==valuestartswith or "'"==valuestartswith:
                            value, rem = _self.parseStr(value, valuestartswith)

                        currentRoot[currentSection][key] = value


        return sections


    def fromFile(filename, keysList=True, rootSection='_'):
        s = ''
        with open(filename, 'r') as f:  s = f.read()
        return Ini_Parser.fromString(s, keysList, rootSection)


    def walk(o, key=None, top='', q='', EOL="\n"):
        s = ''

        if len(o):

            o = dict(o)

            if key: keys = [key]
            else: keys = o.keys()

            for section in keys:

                keyvals = o[section]
                if not len(keyvals):  continue

                s += str(top) + "[" + str(section) + "]" + EOL

                if ('__list__' in keyvals) and len(keyvals['__list__']):

                    # only values as a list
                    s += q + (q+EOL+q).join(keyvals['__list__']) + q + EOL
                    del keyvals['__list__']


                if len(keyvals):

                    for k,v in keyvals.items():

                        if not len(v): continue

                        if isinstance(v, dict) or isinstance(v, list):

                            # sub-section
                            s += Ini_Parser.walk(keyvals, k, top + "[" + str(section) + "]", q, EOL)

                        else:

                            # key-value pair
                            s += q+k+q+ '=' +q+v+q + EOL



                s += EOL

        return s

    def toString(o, rootSection='_', quote=False, EOL="\n"):
        s = ''

        if rootSection: root = str(rootSection)
        else: root = '_'

        if quote: q = '"'
        else: q = ''

        # dump the root section first, if exists
        if root in o:
            section = dict(o[root])

            llist = None
            if '__list__' in section:
                llist = section['__list__']

                if llist and isinstance(llist, list) and len(llist):

                    s += q + (q+EOL+q).join(llist) + q + EOL
                    del section['__list__']


            for k,v in section.items():

                if not len(v): continue
                s += q+k+q+ '=' +q+v+q + EOL


            s += EOL

            del o[root]


        # walk the sections and sub-sections, if any
        s += Ini_Parser.walk(o, None, '', q, EOL)

        return s

    def toFile(filename, o, rootSection='_', quote=False, EOL="\n"):
        with open(filename, 'w') as f:  
            f.write( Ini_Parser.toString(o, rootSection, quote, EOL) )



# for use with 'import *'
__all__ = [ 'Ini_Parser' ]
Nikos M.
  • 8,033
  • 4
  • 36
  • 43
0

It looks like it isn't possible in a simple way. The default way ConfigParser stores is with dict's, i.e. one value per unique key.

In a similar question Python's ConfigParser unique keys per section the suggestions are to go with:

Community
  • 1
  • 1
Jørgen
  • 853
  • 9
  • 16