# -*- coding: utf-8 -*-
# vim:fenc=utf-8
# Copyright (C) 2010-2020 by Mike Gabriel <mike.gabriel@das-netzwerkteam.de>
#
# X2Go Session Broker is free software; you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# X2Go Session Broker is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program; if not, write to the
# Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
#
# This code was initially written by for the Python X2Go project:
# 2010 Dick Kniep <dick.kniep@lindix.nl>
#
"""\
X2GoConfig - helper class for parsing files in INI file format.
"""
__NAME__ = 'x2goinifiles-pylib'
# modules
import os
import configparser
import io
# Python X2GoBroker modules
import x2gobroker.utils
from x2gobroker.defaults import X2GOBROKER_HOME as _X2GOBROKER_HOME
[docs]class X2GoBrokerConfigFile(object):
"""
Class for processing an INI-file-like configuration file.
If entries are omitted in such a config file, they are filled with
default values (as hard-coded in Python X2GoBroker), so the resulting objects
always contain the same fields.
The default values are also used to define a data type for each configuration
option. An on-the-fly type conversion takes place when loading the configuration
file.
"""
defaultValues = {
'DEFAULT': {
'none': 'empty',
},
}
write_user_config = False
user_config_file = None
def __init__(self, config_files=[], defaults={}):
"""\
:param config_files: a list of configuration file names (e.g. a global filename and a user's home
directory filename)
:type config_files: ``list``
:param defaults: a cascaded Python dicitionary structure with INI file defaults (to override
Python X2GoBroker's hard-coded defaults in L{defaults}
:type defaults: ``dict``
"""
# allow string/unicode objects as config_files, as well
if type(config_files) == str:
config_files = [config_files]
self.config_files = config_files
if x2gobroker.utils._checkConfigFileDefaults(defaults):
self.defaultValues = defaults
# we purposefully do not inherit the RawConfigParser class
# here as we do not want to run into name conflicts between
# X2GoBroker config file options and method / property names in
# RawConfigParser... This is a pre-cautious approach...
self.iniConfig = configparser.RawConfigParser(self.defaultValues)
self.iniConfig.optionxform = str
_create_file = False
for file_name in self.config_files:
if file_name.startswith(_X2GOBROKER_HOME):
if not os.path.exists(file_name):
x2gobroker.utils.touch_file(file_name)
_create_file = True
break
self.load()
if _create_file:
self.write_user_config = True
self.write()
def __repr__(self):
result = 'X2GoConfigFile('
for p in dir(self):
if '__' in p or not p in self.__dict__: continue
result += p + '=' + str(self.__dict__[p]) + ','
result = result.strip(',')
return result + ')'
[docs] def load(self):
"""\
R(e-r)ead configuration file(s).
"""
_found_config_files = self.iniConfig.read(self.config_files)
for file_name in _found_config_files:
if file_name.startswith(os.path.normpath(_X2GOBROKER_HOME)):
# we will use the first file found in the user's home dir for writing modifications
self.user_config_file = file_name
break
self.config_files = _found_config_files
self._fill_defaults()
def _storeValue(self, section, key, value):
"""\
Stores a value for a given section and key.
This methods affects a :class:`configparser.RawConfigParser`
object held in RAM. No configuration file is affected by this
method. To write the configuration to disk use the L{write()}
method.
:param section: the INI file section
:type section: ``str``
:param key: the INI file key in the given section
:type key: ``str``
:param value: the value for the given section and key
:type value: ``str``, ``list``, ``bool``, ...
"""
if type(value) is bool:
self.iniConfig.set(section, key, str(int(value)))
elif type(value) in (list, tuple):
self.iniConfig.set(section, key, ", ".join(value))
elif type(value) is int:
self.iniConfig.set(section, key, str(value))
else:
self.iniConfig.set(section, key, value)
def _fill_defaults(self):
"""\
Fills a :class:`configparser.RawConfigParser` object with the
default config file values as pre-defined in Python X2GoBroker
or. This RawConfigParser object is held in RAM. No configuration
file is affected by this method.
"""
for section, sectiondict in list(self.defaultValues.items()):
if section != 'DEFAULT' and not self.iniConfig.has_section(section):
self.iniConfig.add_section(section)
for key, value in list(sectiondict.items()):
if self.iniConfig.has_option(section, key): continue
self._storeValue(section, key, value)
[docs] def update_value(self, section, key, value):
"""\
Change a value for a given section and key. This method
does not have any effect on configuration files.
:param section: the INI file section
:type section: ``str``
:param key: the INI file key in the given section
:type key: ``str``
:param value: the value for the given section and key
:type value: ``str``, ``list``, ``bool``, ...
"""
if not self.iniConfig.has_section(section):
self.iniConfig.add_section(section)
self._storeValue(section, key, value)
self.write_user_config = True
[docs] def write(self):
"""\
Write the INI file modifications (RawConfigParser object) from RAM to disk.
For writing the first of the ``config_files`` specified on instance construction
that is writable will be used.
"""
if self.user_config_file and self.write_user_config:
fd = open(self.user_config_file, 'w')
self.iniConfig.write(fd)
fd.close()
self.write_user_config = False
[docs] def get_type(self, section, key):
"""\
Retrieve a value type for a given section and key. The returned
value type is based on the default values dictionary.
:param section: the INI file section
:type section: ``str``
:param key: the INI file key in the given section
:type key: ``str``
:returns: a Python variable type
:rtype: class
"""
if section in list(self.defaultValues.keys()) and key in list(self.defaultValues[section].keys()):
return type(self.defaultValues[section][key])
else:
try:
return type(self.defaultValues['DEFAULT'][key])
except KeyError:
return type('')
[docs] def has_value(self, section, key):
"""\
Test if a given ``key`` in ``section`` exists (and
has some sort of a value).
:param section: the INI file section
:type section: ``str``
:param key: the INI file key in the given section
:type key: ``str``
:returns: return ``True`` if <key> in <section> exists
:rtype: ``bool``
"""
if section in self.iniConfig.sections():
return ( key in self.iniConfig.options(section) )
return False
[docs] def get_value(self, section, key, key_type=None):
"""\
Retrieve a value for a given section and key.
:param section: the INI file section
:type section: ``str``
:param key: the INI file key in the given section
:type key: ``str``
:returns: the value for the given section and key
:rtype: class
"""
if key_type is None:
key_type = self.get_type(section, key)
if self.iniConfig.has_option(section, key) or section == 'DEFAULT':
if key_type is None:
return self.iniConfig.get(section, key)
if key_type is bool:
return self.iniConfig.getboolean(section, key)
elif key_type is int:
try:
return self.iniConfig.getint(section, key)
except ValueError:
_val = self.iniConfig.get(section, key)
if _val != "not-set": raise
else: return _val
elif key_type is list:
_val = self.iniConfig.get(section, key)
_val = _val.strip()
if _val.startswith('[') and _val.endswith(']'):
return eval(_val)
elif ',' in _val:
_val = [ v.strip() for v in _val.split(',') ]
else:
_val = [ _val ]
return _val
else:
_val = self.iniConfig.get(section, key)
return _val
get = get_value
__call__ = get_value
[docs] def get_defaults(self):
"""\
Get all keys and values from the [DEFAULT] section of the configuration file.
:returns: the defaults with all keys and values
:rtype: ``dict``
"""
_my_defaults = {}
_ini_defaults = self.iniConfig.defaults()
for option in list(_ini_defaults.keys()):
try:
_my_defaults[option] = self.get('DEFAULT', option, key_type=self.get_type('DEFAULT', option))
except KeyError:
continue
try: del _my_defaults['default']
except KeyError: pass
return _my_defaults
[docs] def get_section(self, section):
"""\
Get all keys and values for a certain section of the config file.
:param section: the name of the section to get
:type section: ``str``
:returns: the section with all keys and values
:rtype: ``dict``
"""
_section_config = {}
for option in self.iniConfig.options(section):
if option not in self.iniConfig.sections():
_section_config[option] = self.get(section, option, key_type=self.get_type(section, option))
return _section_config
[docs] def list_sections(self):
"""\
Return a list of all present sections in a config file.
:returns: list of sections in this config file
:rtype: ``list``
"""
return [ s for s in self.iniConfig.sections() ]
@property
def printable_config_file(self):
"""\
Returns a printable configuration file as a multi-line string.
"""
stdout = io.StringIO()
self.iniConfig.write(stdout)
_ret_val = stdout.getvalue()
stdout.close()
return _ret_val