Module Gnumed.pycommon.gmCfgINI
GNUmed INI style configuration handling.
Expand source code
"""GNUmed INI style configuration handling.
"""
#==================================================================
__author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>"
__licence__ = "GPL"
import logging
import sys
import re as regex
import shutil
import tempfile
if __name__ == "__main__":
sys.path.insert(0, '../../')
from Gnumed.pycommon import gmBorg
_log = logging.getLogger('gm.cfg')
#==================================================================
# helper functions
#==================================================================
def __set_opt_in_INI_file(src=None, sink=None, group=None, option=None, value=None):
group_seen = False
option_seen = False
for line in src:
# after option already ?
if option_seen:
sink.write(line)
continue
# start of list ?
if regex.match(r'(?P<list_name>.+)(\s|\t)*=(\s|\t)*\$(?P=list_name)\$', line) is not None:
sink.write(line)
continue
# end of list ?
if regex.match(r'\$.+\$.*', line) is not None:
sink.write(line)
continue
# our group ?
if line.strip() == '[%s]' % group:
group_seen = True
sink.write(line)
continue
# another group ?
if regex.match(r'\[.+\].*', line) is not None:
# next group but option not seen yet ?
if group_seen and not option_seen:
sink.write('%s = %s\n\n\n' % (option, value))
option_seen = True
sink.write(line)
continue
# our option ?
if regex.match(r'%s(\s|\t)*=' % option, line) is not None:
if group_seen:
sink.write('%s = %s\n' % (option, value))
option_seen = True
continue
sink.write(line)
continue
# something else (comment, empty line, or other option)
sink.write(line)
# all done ?
if option_seen:
return
# need to add group ?
if not group_seen:
sink.write('[%s]\n' % group)
# We either just added the group or it was the last group
# but did not contain the option. It must have been the
# last group then or else the following group would have
# triggered the option writeout.
sink.write('%s = %s\n' % (option, value))
#==================================================================
def __set_list_in_INI_file(src=None, sink=None, group=None, option=None, value=None):
our_group_seen = False
inside_our_group = False
our_list_seen = False
inside_our_list = False
# loop until group found or src empty
for line in src:
if inside_our_list: # can only be true if already inside our group
# new list has been written already
# so now at end of our (old) list ?
if regex.match(r'\$%s\$' % option, line.strip()) is not None:
inside_our_list = False
continue
# skip old list entries
continue
if inside_our_group:
# our option ?
if regex.match(r'%s(\s|\t)*=(\s|\t)*\$%s\$' % (option, option), line.strip()) is not None:
sink.write(line) # list header
sink.write('\n'.join(value))
sink.write('\n')
sink.write('$%s$\n' % option) # list footer
our_list_seen = True
inside_our_list = True
continue
# next group (= end of our group) ?
if regex.match(r'\[.+\]', line.strip()) is not None:
# our list already handled ? (if so must already be finished)
if not our_list_seen:
# no, so need to add our list to the group before ...
sink.write('%s = $%s$\n' % (option, option)) # list header
sink.write('\n'.join(value))
sink.write('\n')
sink.write('$%s$\n' % option) # list footer
our_list_seen = True
inside_our_list = False
# ... starting the next group
sink.write(line) # next group header
inside_our_group = False
continue
# other lines inside our group
sink.write(line)
continue
# our group ?
if line.strip() == '[%s]' % group:
our_group_seen = True
inside_our_group = True
sink.write(line) # group header
continue
sink.write(line)
# looped over all lines but did not find our group, so add group
if not our_group_seen:
sink.write('[%s]\n' % group)
if not our_list_seen:
# We either just added the group or it was the last group
# but did not contain the option. It must have been the
# last group then or else the group following it would have
# triggered the option writeout.
sink.write('%s = $%s$\n' % (option, option))
sink.write('\n'.join(value))
sink.write('\n')
sink.write('$%s$\n' % option)
#==================================================================
def set_option_in_INI_file(filename=None, group=None, option=None, value=None, encoding='utf8'):
_log.debug('setting option "%s" to "%s" in group [%s]', option, value, group)
_log.debug('file: %s (%s)', filename, encoding)
sink = tempfile.NamedTemporaryFile(suffix = '.cfg', delete = True)
sink_name = sink.name
sink.close() # close it so it gets deleted so we can safely open it again
src = open(filename, mode = 'rt', encoding = encoding)
sink = open(sink_name, mode = 'wt', encoding = encoding)
# is value a list ?
if isinstance(value, type([])):
__set_list_in_INI_file(src, sink, group, option, value)
else:
__set_opt_in_INI_file(src, sink, group, option, value)
sink.close()
src.close()
shutil.copy2(sink_name, filename)
#==================================================================
def parse_INI_stream(stream=None, encoding=None):
"""Parse an iterable for INI-style data.
Returns a dict by sections containing a dict of values per section.
"""
_log.debug('parsing INI-style data stream [%s] using [%s]', stream, encoding)
if encoding is None:
encoding = 'utf8'
data = {}
current_group = None
current_option = None
current_option_path = None
inside_list = False
line_idx = 0
for line in stream:
if type(line) is bytes:
line = line.decode(encoding)
line = line.replace('\015', '').replace('\012', '').strip()
line_idx += 1
if inside_list:
if line == '$%s$' % current_option: # end of list
inside_list = False
continue
data[current_option_path].append(line)
continue
# noise
if line == '' or line.startswith('#') or line.startswith(';'):
continue
# group
if line.startswith('['):
if not line.endswith(']'):
_log.error('group line does not end in "]", aborting')
_log.error(line)
raise ValueError('INI-stream parsing error')
group = line.strip('[]').strip()
if group == '':
_log.error('group name is empty, aborting')
_log.error(line)
raise ValueError('INI-stream parsing error')
current_group = group
continue
# option
if current_group is None:
_log.warning('option found before first group, ignoring')
_log.error(line)
continue
name, remainder = regex.split(r'\s*[=:]\s*', line, maxsplit = 1)
if name == '':
_log.error('option name empty, aborting')
_log.error(line)
raise ValueError('INI-stream parsing error')
if remainder.strip() == '':
if ('=' not in line) and (':' not in line):
_log.error('missing name/value separator (= or :), aborting')
_log.error(line)
raise ValueError('INI-stream parsing error')
current_option = name
current_option_path = '%s::%s' % (current_group, current_option)
if current_option_path in data:
_log.warning('duplicate option [%s]', current_option_path)
value = remainder.split('#', 1)[0].strip()
# start of list ?
if value == '$%s$' % current_option:
inside_list = True
data[current_option_path] = []
continue
data[current_option_path] = value
if inside_list:
_log.critical('unclosed list $%s$ detected at end of config stream [%s]', current_option, stream)
raise SyntaxError('end of config stream but still in list')
return data
#==================================================================
class gmCfgData(gmBorg.cBorg):
def __init__(self):
try:
self.__cfg_data
except AttributeError:
self.__cfg_data = {}
self.source_files = {}
#--------------------------------------------------
def get(self, group=None, option=None, source_order=None):
"""Get the value of a configuration option in a config file.
<source_order> the order in which config files are searched
a list of tuples (source, policy)
policy:
return: return only this value immediately
append: append to list of potential values to return
extend: if the value per source happens to be a list
extend (rather than append to) the result list
returns NONE when there's no value for an option
"""
if source_order is None:
source_order = [('internal', 'return')]
results = []
for source, policy in source_order:
_log.debug('searching "%s" in [%s] in %s', option, group, source)
if group is None:
group = source
option_path = '%s::%s' % (group, option)
try: source_data = self.__cfg_data[source]
except KeyError:
_log.error('invalid config source [%s]', source)
_log.debug('currently known sources: %s', list(self.__cfg_data))
continue
try: value = source_data[option_path]
except KeyError:
_log.debug('option [%s] not in group [%s] in source [%s]', option, group, source)
continue
_log.debug('option [%s] found in source [%s]', option_path, source)
if policy == 'return':
return value
if policy == 'extend':
if isinstance(value, type([])):
results.extend(value)
else:
results.append(value)
else:
results.append(value)
if len(results) == 0:
return None
return results
#--------------------------------------------------
def set_option(self, option=None, value=None, group=None, source=None):
"""Set a particular option to a particular value.
Note that this does NOT PERSIST the option anywhere !
"""
if None in [option, value]:
raise ValueError('neither <option> nor <value> can be None')
if source is None:
source = 'internal'
try:
self.__cfg_data[source]
except KeyError:
self.__cfg_data[source] = {}
if group is None:
group = source
option_path = '%s::%s' % (group, option)
self.__cfg_data[source][option_path] = value
#--------------------------------------------------
# API: source related
#--------------------------------------------------
def add_stream_source(self, source=None, stream=None, encoding=None):
data = parse_INI_stream(stream = stream, encoding = encoding)
if source in self.__cfg_data:
_log.warning('overriding source <%s> with [%s]', source, stream)
self.__cfg_data[source] = data
#--------------------------------------------------
def add_file_source(self, source=None, file=None, encoding='utf8'):
"""Add a source (a file) to the instance."""
_log.info('file source "%s": %s (%s)', source, file, encoding)
for existing_source, existing_file in self.source_files.items():
if existing_file == file:
if source != existing_source:
_log.warning('file [%s] already known as source [%s]', file, existing_source)
_log.warning('adding it as source [%s] may provoke trouble', source)
cfg_file = None
if file is not None:
try:
cfg_file = open(file, mode = 'rt', encoding = encoding)
except IOError:
_log.error('cannot open [%s], keeping as dummy source', file)
if cfg_file is None:
file = None
if source in self.__cfg_data:
_log.warning('overriding source <%s> with dummy', source)
self.__cfg_data[source] = {}
else:
self.add_stream_source(source = source, stream = cfg_file)
cfg_file.close()
self.source_files[source] = file
#--------------------------------------------------
def remove_source(self, source):
"""Remove a source from the instance."""
_log.info('removing source <%s>', source)
try:
del self.__cfg_data[source]
except KeyError:
_log.warning("source <%s> doesn't exist", source)
try:
del self.source_files[source]
except KeyError:
pass
#--------------------------------------------------
def reload_file_source(self, file=None, encoding='utf8'):
if file not in self.source_files.values():
return
for src, fname in self.source_files.items():
if fname == file:
self.add_file_source(source = src, file = fname, encoding = encoding)
# don't break the loop because there could be other sources
# with the same file (not very reasonable, I know)
#break
#--------------------------------------------------
def add_cli(self, short_options='', long_options=None):
"""Add command line parameters to config data.
short:
string containing one-letter options such as u'h?' for -h -?
long:
list of strings
'conf-file=' -> --conf-file=<...>
'debug' -> --debug
"""
_log.info('adding command line arguments')
_log.debug('raw command line is:')
_log.debug('%s', sys.argv)
import getopt
if long_options is None:
long_options = []
opts, remainder = getopt.gnu_getopt (
sys.argv[1:],
short_options,
long_options
)
data = {}
for opt, val in opts:
if val == '':
data['%s::%s' % ('cli', opt)] = True
else:
data['%s::%s' % ('cli', opt)] = val
self.__cfg_data['cli'] = data
#==================================================================
# main
#==================================================================
if __name__ == "__main__":
if len(sys.argv) < 2:
sys.exit()
if sys.argv[1] != 'test':
sys.exit()
logging.basicConfig(level = logging.DEBUG)
#-----------------------------------------
def test_gmCfgData():
cfg = gmCfgData()
cfg.add_cli(short_options='h?', long_options=['help', 'conf-file='])
cfg.set_option('internal option', True)
print (cfg.get(option = '--help', source_order = [('cli', 'return')]))
print (cfg.get(option = '-?', source_order = [('cli', 'return')]))
fname = cfg.get(option = '--conf-file', source_order = [('cli', 'return')])
if fname is not None:
cfg.add_file_source(source = 'explicit', file = fname)
#-----------------------------------------
def test_set_list_opt():
src = [
'# a comment',
'',
'[empty group]',
'[second group]',
'some option = in second group',
'# another comment',
'[test group]',
'',
'test list = $test list$',
'old 1',
'old 2',
'$test list$',
'# another group:',
'[dummy group]'
]
__set_list_in_INI_file (
src = src,
sink = sys.stdout,
group = 'test group',
option = 'test list',
value = list('123')
)
#-----------------------------------------
def test_set_opt():
src = [
'# a comment',
'[empty group]',
'# another comment',
'',
'[second group]',
'some option = in second group',
'',
'[trap group]',
'trap list = $trap list$',
'dummy 1',
'test option = a trap',
'dummy 2',
'$trap list$',
'',
'[test group]',
'test option = for real (old)',
''
]
__set_opt_in_INI_file (
src = src,
sink = sys.stdout,
group = 'test group',
option = 'test option',
value = 'for real (new)'
)
#-----------------------------------------
def test_parse_ini_stream():
data = parse_INI_stream(stream = open(sys.argv[2], 'r', encoding = 'utf8'))
for key in data:
print(key, data[key])
input()
_cfg = gmCfgData()
_cfg.add_file_source(source = 'prefs', file = sys.argv[2])
print(_cfg.get('preferences', 'login', [('prefs', 'return')]))
print(_cfg.get('preferences', 'most recently used praxis branch', [('prefs', 'return')]))
#-----------------------------------------
#test_gmCfgData()
#test_set_list_opt()
test_set_opt()
#test_parse_ini_stream()
#==================================================================
Functions
def parse_INI_stream(stream=None, encoding=None)
-
Parse an iterable for INI-style data.
Returns a dict by sections containing a dict of values per section.
Expand source code
def parse_INI_stream(stream=None, encoding=None): """Parse an iterable for INI-style data. Returns a dict by sections containing a dict of values per section. """ _log.debug('parsing INI-style data stream [%s] using [%s]', stream, encoding) if encoding is None: encoding = 'utf8' data = {} current_group = None current_option = None current_option_path = None inside_list = False line_idx = 0 for line in stream: if type(line) is bytes: line = line.decode(encoding) line = line.replace('\015', '').replace('\012', '').strip() line_idx += 1 if inside_list: if line == '$%s$' % current_option: # end of list inside_list = False continue data[current_option_path].append(line) continue # noise if line == '' or line.startswith('#') or line.startswith(';'): continue # group if line.startswith('['): if not line.endswith(']'): _log.error('group line does not end in "]", aborting') _log.error(line) raise ValueError('INI-stream parsing error') group = line.strip('[]').strip() if group == '': _log.error('group name is empty, aborting') _log.error(line) raise ValueError('INI-stream parsing error') current_group = group continue # option if current_group is None: _log.warning('option found before first group, ignoring') _log.error(line) continue name, remainder = regex.split(r'\s*[=:]\s*', line, maxsplit = 1) if name == '': _log.error('option name empty, aborting') _log.error(line) raise ValueError('INI-stream parsing error') if remainder.strip() == '': if ('=' not in line) and (':' not in line): _log.error('missing name/value separator (= or :), aborting') _log.error(line) raise ValueError('INI-stream parsing error') current_option = name current_option_path = '%s::%s' % (current_group, current_option) if current_option_path in data: _log.warning('duplicate option [%s]', current_option_path) value = remainder.split('#', 1)[0].strip() # start of list ? if value == '$%s$' % current_option: inside_list = True data[current_option_path] = [] continue data[current_option_path] = value if inside_list: _log.critical('unclosed list $%s$ detected at end of config stream [%s]', current_option, stream) raise SyntaxError('end of config stream but still in list') return data
def set_option_in_INI_file(filename=None, group=None, option=None, value=None, encoding='utf8')
-
Expand source code
def set_option_in_INI_file(filename=None, group=None, option=None, value=None, encoding='utf8'): _log.debug('setting option "%s" to "%s" in group [%s]', option, value, group) _log.debug('file: %s (%s)', filename, encoding) sink = tempfile.NamedTemporaryFile(suffix = '.cfg', delete = True) sink_name = sink.name sink.close() # close it so it gets deleted so we can safely open it again src = open(filename, mode = 'rt', encoding = encoding) sink = open(sink_name, mode = 'wt', encoding = encoding) # is value a list ? if isinstance(value, type([])): __set_list_in_INI_file(src, sink, group, option, value) else: __set_opt_in_INI_file(src, sink, group, option, value) sink.close() src.close() shutil.copy2(sink_name, filename)
Classes
class gmCfgData
-
A generic Borg mixin for new-style classes.
-
mixin this class with your class' ancestors to borg it
-
there may be many instances of this - PER CHILD CLASS - but they all share state
Expand source code
class gmCfgData(gmBorg.cBorg): def __init__(self): try: self.__cfg_data except AttributeError: self.__cfg_data = {} self.source_files = {} #-------------------------------------------------- def get(self, group=None, option=None, source_order=None): """Get the value of a configuration option in a config file. <source_order> the order in which config files are searched a list of tuples (source, policy) policy: return: return only this value immediately append: append to list of potential values to return extend: if the value per source happens to be a list extend (rather than append to) the result list returns NONE when there's no value for an option """ if source_order is None: source_order = [('internal', 'return')] results = [] for source, policy in source_order: _log.debug('searching "%s" in [%s] in %s', option, group, source) if group is None: group = source option_path = '%s::%s' % (group, option) try: source_data = self.__cfg_data[source] except KeyError: _log.error('invalid config source [%s]', source) _log.debug('currently known sources: %s', list(self.__cfg_data)) continue try: value = source_data[option_path] except KeyError: _log.debug('option [%s] not in group [%s] in source [%s]', option, group, source) continue _log.debug('option [%s] found in source [%s]', option_path, source) if policy == 'return': return value if policy == 'extend': if isinstance(value, type([])): results.extend(value) else: results.append(value) else: results.append(value) if len(results) == 0: return None return results #-------------------------------------------------- def set_option(self, option=None, value=None, group=None, source=None): """Set a particular option to a particular value. Note that this does NOT PERSIST the option anywhere ! """ if None in [option, value]: raise ValueError('neither <option> nor <value> can be None') if source is None: source = 'internal' try: self.__cfg_data[source] except KeyError: self.__cfg_data[source] = {} if group is None: group = source option_path = '%s::%s' % (group, option) self.__cfg_data[source][option_path] = value #-------------------------------------------------- # API: source related #-------------------------------------------------- def add_stream_source(self, source=None, stream=None, encoding=None): data = parse_INI_stream(stream = stream, encoding = encoding) if source in self.__cfg_data: _log.warning('overriding source <%s> with [%s]', source, stream) self.__cfg_data[source] = data #-------------------------------------------------- def add_file_source(self, source=None, file=None, encoding='utf8'): """Add a source (a file) to the instance.""" _log.info('file source "%s": %s (%s)', source, file, encoding) for existing_source, existing_file in self.source_files.items(): if existing_file == file: if source != existing_source: _log.warning('file [%s] already known as source [%s]', file, existing_source) _log.warning('adding it as source [%s] may provoke trouble', source) cfg_file = None if file is not None: try: cfg_file = open(file, mode = 'rt', encoding = encoding) except IOError: _log.error('cannot open [%s], keeping as dummy source', file) if cfg_file is None: file = None if source in self.__cfg_data: _log.warning('overriding source <%s> with dummy', source) self.__cfg_data[source] = {} else: self.add_stream_source(source = source, stream = cfg_file) cfg_file.close() self.source_files[source] = file #-------------------------------------------------- def remove_source(self, source): """Remove a source from the instance.""" _log.info('removing source <%s>', source) try: del self.__cfg_data[source] except KeyError: _log.warning("source <%s> doesn't exist", source) try: del self.source_files[source] except KeyError: pass #-------------------------------------------------- def reload_file_source(self, file=None, encoding='utf8'): if file not in self.source_files.values(): return for src, fname in self.source_files.items(): if fname == file: self.add_file_source(source = src, file = fname, encoding = encoding) # don't break the loop because there could be other sources # with the same file (not very reasonable, I know) #break #-------------------------------------------------- def add_cli(self, short_options='', long_options=None): """Add command line parameters to config data. short: string containing one-letter options such as u'h?' for -h -? long: list of strings 'conf-file=' -> --conf-file=<...> 'debug' -> --debug """ _log.info('adding command line arguments') _log.debug('raw command line is:') _log.debug('%s', sys.argv) import getopt if long_options is None: long_options = [] opts, remainder = getopt.gnu_getopt ( sys.argv[1:], short_options, long_options ) data = {} for opt, val in opts: if val == '': data['%s::%s' % ('cli', opt)] = True else: data['%s::%s' % ('cli', opt)] = val self.__cfg_data['cli'] = data
Ancestors
Methods
def add_cli(self, short_options='', long_options=None)
-
Add command line parameters to config data.
short: string containing one-letter options such as u'h?' for -h -? long: list of strings 'conf-file=' -> –conf-file=<…> 'debug' -> –debug
Expand source code
def add_cli(self, short_options='', long_options=None): """Add command line parameters to config data. short: string containing one-letter options such as u'h?' for -h -? long: list of strings 'conf-file=' -> --conf-file=<...> 'debug' -> --debug """ _log.info('adding command line arguments') _log.debug('raw command line is:') _log.debug('%s', sys.argv) import getopt if long_options is None: long_options = [] opts, remainder = getopt.gnu_getopt ( sys.argv[1:], short_options, long_options ) data = {} for opt, val in opts: if val == '': data['%s::%s' % ('cli', opt)] = True else: data['%s::%s' % ('cli', opt)] = val self.__cfg_data['cli'] = data
def add_file_source(self, source=None, file=None, encoding='utf8')
-
Add a source (a file) to the instance.
Expand source code
def add_file_source(self, source=None, file=None, encoding='utf8'): """Add a source (a file) to the instance.""" _log.info('file source "%s": %s (%s)', source, file, encoding) for existing_source, existing_file in self.source_files.items(): if existing_file == file: if source != existing_source: _log.warning('file [%s] already known as source [%s]', file, existing_source) _log.warning('adding it as source [%s] may provoke trouble', source) cfg_file = None if file is not None: try: cfg_file = open(file, mode = 'rt', encoding = encoding) except IOError: _log.error('cannot open [%s], keeping as dummy source', file) if cfg_file is None: file = None if source in self.__cfg_data: _log.warning('overriding source <%s> with dummy', source) self.__cfg_data[source] = {} else: self.add_stream_source(source = source, stream = cfg_file) cfg_file.close() self.source_files[source] = file
def add_stream_source(self, source=None, stream=None, encoding=None)
-
Expand source code
def add_stream_source(self, source=None, stream=None, encoding=None): data = parse_INI_stream(stream = stream, encoding = encoding) if source in self.__cfg_data: _log.warning('overriding source <%s> with [%s]', source, stream) self.__cfg_data[source] = data
def get(self, group=None, option=None, source_order=None)
-
Get the value of a configuration option in a config file.
the order in which config files are searched a list of tuples (source, policy) policy: return: return only this value immediately append: append to list of potential values to return extend: if the value per source happens to be a list extend (rather than append to) the result list returns NONE when there's no value for an option
Expand source code
def get(self, group=None, option=None, source_order=None): """Get the value of a configuration option in a config file. <source_order> the order in which config files are searched a list of tuples (source, policy) policy: return: return only this value immediately append: append to list of potential values to return extend: if the value per source happens to be a list extend (rather than append to) the result list returns NONE when there's no value for an option """ if source_order is None: source_order = [('internal', 'return')] results = [] for source, policy in source_order: _log.debug('searching "%s" in [%s] in %s', option, group, source) if group is None: group = source option_path = '%s::%s' % (group, option) try: source_data = self.__cfg_data[source] except KeyError: _log.error('invalid config source [%s]', source) _log.debug('currently known sources: %s', list(self.__cfg_data)) continue try: value = source_data[option_path] except KeyError: _log.debug('option [%s] not in group [%s] in source [%s]', option, group, source) continue _log.debug('option [%s] found in source [%s]', option_path, source) if policy == 'return': return value if policy == 'extend': if isinstance(value, type([])): results.extend(value) else: results.append(value) else: results.append(value) if len(results) == 0: return None return results
def reload_file_source(self, file=None, encoding='utf8')
-
Expand source code
def reload_file_source(self, file=None, encoding='utf8'): if file not in self.source_files.values(): return for src, fname in self.source_files.items(): if fname == file: self.add_file_source(source = src, file = fname, encoding = encoding) # don't break the loop because there could be other sources # with the same file (not very reasonable, I know) #break
def remove_source(self, source)
-
Remove a source from the instance.
Expand source code
def remove_source(self, source): """Remove a source from the instance.""" _log.info('removing source <%s>', source) try: del self.__cfg_data[source] except KeyError: _log.warning("source <%s> doesn't exist", source) try: del self.source_files[source] except KeyError: pass
def set_option(self, option=None, value=None, group=None, source=None)
-
Set a particular option to a particular value.
Note that this does NOT PERSIST the option anywhere !
Expand source code
def set_option(self, option=None, value=None, group=None, source=None): """Set a particular option to a particular value. Note that this does NOT PERSIST the option anywhere ! """ if None in [option, value]: raise ValueError('neither <option> nor <value> can be None') if source is None: source = 'internal' try: self.__cfg_data[source] except KeyError: self.__cfg_data[source] = {} if group is None: group = source option_path = '%s::%s' % (group, option) self.__cfg_data[source][option_path] = value
-