import codecs
import logging
import os
import sys
import six
from six.moves import configparser
from six import text_type as str
try:
from collections import OrderedDict
except ImportError: # pragma: no cover
# if on python 2.6
from ordereddict import OrderedDict
from . import ConfigSource
[docs]class INIFile(ConfigSource):
def __init__(self,
inifilename=None,
rootsection="__root__",
sectionsep=".",
writable=True,
**kwargs):
"""Loads and optionally saves configuration files in INI format, as
handled by :py:mod:`configparser`.
:param inifile: The name of a ini-style configuration
file. The file should have a top-level
section, by default named ``__root__``, whose
keys are turned into top-level configuration
parameters. Any other sections in this file
are turned into nested config objects.
:type inifile: str
:param rootsection: An alternative name for the top-level section.
See note below.
:type rootsection: str
:param sectionsep: separator to use in section names to
separate nested subsections. See note below.
:type sectionsep: str
:param writable: Whether changes to the LayeredConfig object
that has this INIFile object amongst its
sources should be saved in the INI file.
:type writable: bool
.. note::
Nested subsections is possible, but since the INI format
does not natively support nesting, this is accomplished
through specially-formatted section names, eg the config
value mymodule.mysection.example would be expressed in the
ini file as::
[mymodule.mysection]
example = value
Since this source uses :py:mod:`configparser`, and since
that module handles sections named ``[DEFAULT]``
differently, this module will have a sort-of automatic
cascading feature for subsections if ``DEFAULT`` is used as
``rootsection``
"""
super(INIFile, self).__init__(**kwargs)
if inifilename:
if not os.path.exists(inifilename):
logging.warning("INI file %s does not exist" % inifilename)
# create a empty RawConfigParser (Raw to avoid the
# interpolation behaviour of other classes)
self.source = configparser.RawConfigParser(dict_type=OrderedDict)
self.inifilename = inifilename
if rootsection != "DEFAULT":
self.source.add_section(rootsection)
else:
self.source = configparser.RawConfigParser(dict_type=OrderedDict)
if sys.version_info >= (3,2):
reader = self.source.read_file
else:
reader = self.source.readfp
# we don't know the encoding of this file; assume utf-8
with codecs.open(inifilename, encoding="utf-8") as fp:
reader(fp)
self.inifilename = inifilename
# only used when creating new INIFile objects internally
elif 'config' in kwargs:
self.source = kwargs['config']
self.inifilename = None
else:
# This is an "empty" INIFile object
self.source = configparser.ConfigParser(dict_type=OrderedDict)
self.source.add_section(rootsection)
self.inifilename = None
if 'section' in kwargs:
self.sectionkey = kwargs['section']
else:
self.sectionkey = rootsection
self.dirty = False
self.writable = writable
self.rootsection = rootsection
self.sectionsep = sectionsep
[docs] def typed(self, key):
# INI files carry no intrinsic type information
return False
[docs] def subsections(self):
# self.source may be None if we provided the path to a
# nonexistent inifile (this should probably throw an exception
# instead)
if not self.source:
return []
else:
allsections = [x for x in self.source.sections() if x != self.rootsection]
if self.sectionkey != self.rootsection:
# find out what subsections are under this subsection (eg nested sections)
return [x[len(self.sectionkey+self.sectionsep):].split(self.sectionsep)[0] for x in allsections if x.startswith(self.sectionkey+self.sectionsep)]
else:
return [x for x in allsections if self.sectionsep not in x]
[docs] def subsection(self, key):
if self.sectionkey == self.rootsection:
section = key
else:
section = self.sectionkey + self.sectionsep + key
return INIFile(config=self.source, section=section,
parent=self, identifier=self.identifier)
[docs] def has(self, key):
if self.sectionkey == "DEFAULT":
return key in self.source.defaults()
else:
return self.source.has_option(self.sectionkey, key)
[docs] def get(self, key):
return str(self.source.get(self.sectionkey, key))
[docs] def set(self, key, value):
self.source.set(self.sectionkey, key, self._strvalue(value))
[docs] def keys(self):
if self.source.has_section(self.sectionkey):
for k in self.source.options(self.sectionkey):
yield k
[docs] def save(self):
# this should only be called on root objects
assert not self.parent, "save() should only be called on root objects"
if self.inifilename:
with open(self.inifilename, "w") as fp:
self.source.write(fp)